From 4db5db7fa02d00685d0768e9ba6bd83833a4d14a Mon Sep 17 00:00:00 2001 From: Diego Mello Date: Fri, 1 Oct 2021 15:12:09 -0300 Subject: [PATCH] Merge 4.20.0 into master (#3412) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [FIX] App not prompting join code for password protected channels (#2514) * Adding joinCode parameter Co-authored-by: Vitor Leal Co-authored-by: Fernando Aguilar * Insert join code input Signed-off-by: Vitor.Leal * Add joinCode field on db Signed-off-by: Vitor.Leal * Add label i18 pt-br and en-us Signed-off-by: Vitor.Leal * Add insert join code text Signed-off-by: Vitor.Leal * Fix atribute name Signed-off-by: Vitor.Leal * Add join text Signed-off-by: Vitor.Leal Co-authored-by: Daniel Maike Co-authored-by: Fernando Aguilar * Fix attributes joinCode, joinCodeRequired and pass attribute param in navigation Signed-off-by: Daniel Maike Co-authored-by: Vitor Leal * Fixing attribute joinCodeRequired pass to goRoom Signed-off-by: Daniel Maike * Changed textinput style Signed-off-by: Daniel Maike Co-authored-by: Vitor Leal * Delete not necessary attribute Signed-off-by: Daniel Maike * Fixing input style Co-authored-by: Vitor Leal * Undo unncessary changes * use a join code modal * tests: e2e tests to join protected channel * fix: undo unnecessary change * tests: cancel join code * Remove some tests * Minor fixes Co-authored-by: Vitor Leal Co-authored-by: Fernando Aguilar Co-authored-by: Djorkaeff Alexandre Co-authored-by: youssef-md Co-authored-by: Diego Mello * [I18n] Add Arabic (#2537) * Arabic language setup * Added arabic translation * Arabic translation Proofreading Co-authored-by: Diego Mello * [I18N] Add missing zh_TW and zh_CN strings (#2680) * feat(i18n): add some missing strings and improve some translation * fix: add missing server version Co-authored-by: Diego Mello * [IMPROVEMENT] Add username on status messages (#2553) * 1689 - missing user name for status messages * 1689 - missing user name for status messages. Fixed broken e2e test "should pin message". * Minor tweak * Remove center style * Small refactor on User * Remove toLowerCase * Update tests Co-authored-by: Diego Mello * [FIX] Filenames are incorrect in non-latin alphabets on upload (#2671) * fix: filename on react-native-image-crop-picker * fix: use rn-fetch-blob to upload files * fix: FileUpload as a service * fix: cancel upload on iOS * fix: file upload from share extension Co-authored-by: Diego Mello * [IMPROVEMENT] Ease white labelling for Android (#2685) * improve white labelling for Android * Move application ID to gradle properties * Fix CI * Point foss sufix to main app * Use npx on android-whitelabel script Co-authored-by: Diego Mello * [FIX] Chats order (#2688) * Persist highest value on subscription.roomUpdatedAt * Update tests Co-authored-by: Djorkaeff Alexandre * [REGRESSION] Re-enable Jitsi Chat (#2687) * Fix main jitsi * Fix iOS * Clear build.gradle cache * Don't restore gradle * cache is back * Use master * Point to react-native-jitsi-meet#master * [CHORE] Build official apps on CI (#2701) * Duplicated target and changed Bridging Header * Display name * Unnecessary dumb swift file removed * Buildable name * Reorder Info.plist * Rename Official target's bundle id * Ignore .mobileprovision * Fix provisioning of official app * Starting signing * stash fastfile * starting official ci iOS * Uncomment Fastfile keychain * Fix CI config * allowProvisioningUpdates * Changing AppIcon and Splash Screen * Remove unnecessary folder inside of Images.xcassets * Reorder notificationservice and shareextension plists * Fix signing * Manual signing style for official * Split official signing * Update project provisioning * Use ENV as profile * Output match * Keys * TestFlight refactor * Setting up android * android-official-play-build job * Start removing unnecessary fastlane tasks on Android * Trying to refactor Android jobs * android-env * Remove foss build for now * Fork * Fix if conditions * Fix push * ios-build command * Rename Android builds * Upload dSYMs * Refactoring workflow * Reorder upload-to-testflight * upload-to-google-play-beta command * Fix ci * Fix android fork build * Fix keystore * Fix options on fastlane android * Fix keystore * Check isOfficial on iOS * Check isOfficial on db * Remove unused imports * Database names on Android * Tag fix * Minor fixes * Set IS_OFFICIAL on CI * Fix detox * follow review suggestions Co-authored-by: Djorkaeff Alexandre * [i18n] Update fr (#2697) Co-authored-by: Diego Mello * [i18n] Update fr (#2705) Typo Co-authored-by: Diego Mello * [FIX] Empty space on Messagebox (#2704) Co-authored-by: Diego Mello * [FIX] Yarn android scripts (#2716) * [CHORE] Rename Experimental iOS lane (#2717) * Move build_fork to the end * Rename release to build_experimental * [IMPROVEMENT] Use class variable instead of state for List's animated (#2718) * [FIX] Bottom sheet being hidden sometimes (#2722) * [IMPROVEMENT] Match background and text mention colors (#2723) * [FIX] App freezing if Markdown preview contains sequential empty spaces (#2726) * Remove sequential empty spaces from Markdown preview * Use Markdown preview on RepliedThread * [FIX] Official app without sharedUserId (#2734) * [CHORE] Update React Native to 0.63.4 (#2737) * Bump version to 4.13.1 (#2739) * [REGRESSION] Multiple uploads not working on iOS (#2738) * Update React Native to 0.63.4 * Fix multiple uploads not working on iOS * [FIX] Unable to save attachment on iOS (#2743) * Fix rn-fetch-blob's document dir without forward slash * Update camera roll * [FIX] Generate Jitsi access token when making a call (#2694) fixes: #2693 # Please enter the commit message for your changes. Lines starting Co-authored-by: Diego Mello * [FIX] Jitsi notification delay (#2668) Co-authored-by: Diego Mello * [FIX] Channels list not following the same sorting logic from web client (#2763) * [FIX] Pods lost on Official target (#2764) * [FIX] RoomItem using deprecated animated event signature (#2771) * [FIX] Server autocomplete text breaking line (#2774) * [FIX] ServerDropdown flashing bigger server icon (#2775) * [FIX] ServerDropdown flashing bigger server icon * Remove unused logo and update image path where needed * Minor tweak Co-authored-by: Diego Mello * [FIX] Rooms list not being updated on some cases (#2765) * Request subscriptions on RoomsListView.constructor * Removes opened rooms from last message persisting * Change server reducer * Prevent undefined ids causing query error * [FIX] Share Extension hitting memory limit on iOS (#2788) * [FIX] Disallow swipe to dismiss on share extension * Limit query to 20 and clean up props * Remove rn-extension-share branch pointer * Test new branch * Remove branch * [IMPROVEMENT] Threads layout tweaks (#2686) * improvement: Thread Details * fix: re-render Thread Messages Item * fix: update snapshots * improve: thread details component * fix: cast replies length * improvement: format date of threads * improvement: thread details styles * fix: wrap text * tests: update snapshot * improvement: use same date format for all dates * Icon size 24 * Remove date * Remove prop drill * Badge position * Badge container tweak * Fix inline style * Move ThreadDetails to containers * Update stories * Fix lint * Remove wrong prop Co-authored-by: Diego Mello * [CHORE] Remove some migrations (#2792) * Remove force rooms refresh * Remove MMKV migration * Bump version to 4.14.0 (#2797) * [FIX] Messagebox tracking lost on pop gesture navigation (#2799) * Use setTimeout instead of InteractionManager * Update tracking lib * [FIX] Back button closing activity when on root stack screen (#2804) * Make hardware back button to behave as home button on root screens * Remove unnecessary code * Remove handleBackPress from OnboardingView * Fix lint * [i18n] Add missing German strings (#2715) Co-authored-by: Diego Mello * [NEW] Encrypted Discussions (#2813) * I18n key fix * Add encrypted switch * Remove unused i18n keys * Add enabled to encryption reducer * Show encrypted option on CreateDiscussionView only when e2e encryption is properly set * Add localSearch and use it on search * Use encrypted from parent channel * Fix method calls as rest api with 2fa enabled * Fix logout after reset keys * Use encryption reducer instead of lib directly to check render * Check for room type logic to display encryption option on create discussion * Check toggle-room-e2e-encryption permission on RoomActionsView * Check for encryption status instead of setting on server * Fix * Disable switch instead of hide it * Fix spotlight for DMs * Fix server test * [FIX] Messagebox missing style for text color (#2786) * Changing auxilaryTintColor * Changed Placeholder color to BodyText color * added color prop * eslint changes * used array for styles Co-authored-by: Diego Mello * [I18N] Update arabic (#2696) * Update ar.js * Update ar.js Co-authored-by: Diego Mello * [FIX] Workspace input without i18n (#2689) * [FIX] Translation of strings in Login page * Strings are added for translation. fixes: #2620 * Add pt-BR Co-authored-by: Diego Mello * [FIX] Spotlight returning duplicated entries (#2805) * Update rocketchat.js * Updated search function * Minor improvements * Remove atIndex * Add remove logic to remove duplicate data from response Co-authored-by: Diego Mello * [CHORE] Refactor ServerItem (#2778) * Updated ServerDropdown and ServerItem * Added ServerItem stories * Update ServerDropdown.js * Updated ServerItem stories * Updated ServerItem stories and ServerItem component * Updated SelectServerView, ServerItem and ServerItem stories * Updated ServerItem stories * Updated ServerItem stories * Update tests Co-authored-by: Diego Mello * [DOCS] Updated Quick Start docs link in e2e/readme (#2802) Co-authored-by: Diego Mello * [I18N] Add Turkish (#2793) * Turkish language support added * Update tr.js Co-authored-by: Diego Mello * [FIX] Lint on #2793 (#2818) * [I18N] Add missing german strings (#2689) (#2820) * [I18N] Add missing italian strings (#2817) * [FIX] Server version becoming null on server change (#2821) * [FIX] Wrong styling on E2E encryption banner (#2767) * [FIX] Wrong styling on E2E encryption banner * [FIX] Wrong styling on E2E encryption banner * [FIX] Wrong styling on E2E encryption banner * [FIX] Wrong styling on E2E encryption banner (#2767) * Updated SortDropdown, ListHeader, ListItem and added stories for List.Item * Updated SortDropdown * Removed unused component * Updated List.Item and stories * Reverted unnecessary changes and updated ListItem stories * Fix minor indentation * Stop breaking Touch's default underlay color * Fix indentation * Remove falsy comparison from render * Fix left icon * Use List.Item on OmnichannelStatus * Add missing separator * Lint * Fix sort dropdown * Remove unnecessary styles * Fix detox Co-authored-by: Diego Mello * [FIX] App Store using Experimental's app id (#2826) * [FIX] Wrong username on push notifications (#2825) * [FIX] Share extension memory issues on iOS (#2845) * Remove unnecessary class prop * Stop rendering servers when there's only one * Map and alloc only necessary columns from query * Fetch servers count instead of all servers records * Fetch only needed servers * Separators * Remove renderContent * Minor fix * Refactor query * Smaller avatars in memory * Fix getItemLayout * Add topic * Load less pods * tests * Import only used functions from lodash * Fix pods * Import only used functions from semver * Fix media sharing * Update pods * Disables preview and thumb on iOS * Update expo-video-thumbnail * Unnecessary change * [FIX] Logout from other locations not prompting confirmation option (#2854) * Fixed logout toast bug for the iOS * Removing callToAction and replacing with confirmationText Co-authored-by: Diego Mello * Bump version to 4.14.1 (#2859) * [IMPROVEMENT] Check for focused rooms on in-app notifications (#2857) * Update InAppNotification and room reducer * Update InAppNotification This reverts commit 60330a1e04cfe8d2e5aa311f367083d831682c49. * Stop subscribing to threads * Remove ref * Fix prop-types Co-authored-by: Diego Mello * [FIX] Real name being ignored in SearchMessagesView (#2838) Co-authored-by: Gerzon Z Co-authored-by: Diego Mello * [CHORE] Remove unnecessary share reducer calls (#2861) * Remove unnecesary share reducer calls * Update Avatar Co-authored-by: Diego Mello * [FIX] Breadcrumbs exceeding characters limit (#2862) * [FIX] breadcrumbs exceeding * fix.breadcrumbs-exceeding-change-events Co-authored-by: Diego Mello * [FIX] App compressing videos on iOS (#2915) * Update index.js * Update index.js * [FIX] Real name setting ignored on reply preview (#2908) Co-authored-by: Diego Mello * [FIX] Reply component sending unused prop to Description (#2900) Co-authored-by: Diego Mello * [CHORE] BackdropOpacity based on themes (#2863) * Added backdropOpacity based on theme * Updated ActionSheet, ReactionsModal, ReactionPicker and Sidebar * Updated MultiSelect Co-authored-by: Diego Mello * [FIX] Webview not falling back to default auth challenge when no cert is provided (#2918) * [FIX] Android - fallback to default auth challenge handling when no cert is provided * If a certificate auth challenge is requested on Android the webview will hang if no certificate is loaded. To prevent this, fallback to default Android behavior and cancel the challenge with request.cancel() * No client certificate case defaults to super implementation * Update react-native-webview * Downgrade to previous dependency version Co-authored-by: Diego Mello Co-authored-by: Gerzon Z Co-authored-by: Jan Garaj * [FIX] Support Jitsi_URL_Room_Hash (#2905) * [FIX] Temp attachment files not being flushed after saved to gallery (#2871) * Update AttachmentView.js * Update AttachmentView.js * Update AttachmentView.js * Update AttachmentView.js Co-authored-by: Diego Mello * [CHORE] Update iOS profiles for Experimental app (#2933) * [IMPROVE] Deleted thread reply redirects to thread (#2840) Co-authored-by: Diego Mello * [FIX] Thread showing typing indicator from main room (#2869) * [FIX] Remove typing indicator from thread's header * remove unnecessary props and change usersTyping condition Co-authored-by: Diego Mello * [FIX] DM rooms show typing status from last group room (#2878) * [FIX] DM rooms show typing status from last group room * Undo changes * Check if current typing is from focused room before dispatching to redux Co-authored-by: Diego Mello * [FIX] Can't copy or edit media's description (#2885) * [FIX] Image descriptions issues * shorten the condition string * fix selectedMessage state Co-authored-by: Diego Mello * [FIX] RightButtonsContainer re-render check not returning default value (#2899) Co-authored-by: Diego Mello * [CHORE] Remove InteractionManager blocks (#2906) * [FIX] Remove InteractionManager blocks * Minor fix Co-authored-by: Diego Mello * [FIX] App not sending second argument for EventEmitter.removeListener on some places (#2909) Co-authored-by: Diego Mello * [FIX] Temp message ignoring real name (#2919) Co-authored-by: Diego Mello * [FIX] System message of e2e encryption is missing (#2888) * [FIX] System message of e2e encryption missing * add new encryption string * add to stories * Add pt-BR * Move stories Co-authored-by: Diego Mello * [CHORE] Add permissions to Redux (#2914) * [FIX] Add permissions to Redux store * add only permissions being used in the app * add clear permissions reducer * call RocketChat.hasPermission from reducer * add server version comparison on getPermissions * refactor hasPermission function * refactor hasPermission function * remove uncomment code * use Q.experimentalSortBy() * add coerce function * Change Rocketchat.hasPermission * Apply on isReadOnly * Apply to RoomInfoEditView * Apply to RoomInfoView and RoomInfoEditView * canAutoTranslate * Unnecessary clear permissions * Revert getUpdatedSince * Naming fix Co-authored-by: Diego Mello * [CHORE] Add hold step for ios and android build experimental (#2943) * [CHORE] Add hold step for ios-build-experimental and android-build-experimental * Android hold step * add ios hold step Co-authored-by: Diego Mello * [IMPROVEMENT] Remove lodash.isEqual (#2893) * Added dequal and react-fast-compare as substitutes to lodash.isEqual * Update ReplyPreview.js * Remove react-fast-compare * Removed deep-equal and upgrade babel-eslint dev dependency * Fix avatar * Fix Messagebox * Fix CreateDiscussionView * ModalBlockView * NewMessageView * ProfileView * RoomInfoEditView * ServerDropdown * Return local search as object instead of observable * SelectedUsersView Co-authored-by: Diego Mello * [I18N] Add missing Russian strings (#2946) * [i18n] Add missing Russian strings * Couple fixes * Fix Direct_message Translate Direct_message as already has been translated Co-authored-by: Diego Mello * [CHORE] Use shortcut syntax for get collections (#2932) Co-authored-by: Diego Mello * [FIX] Use List.Separator in all places (#2931) * [FIX] Use List.Separator in all places * add List.Separator * change List.Separator Co-authored-by: Diego Mello * [FIX] Limit new message list query size to 50 (#2947) * Limit query to 50 * Remove observable * [FIX] Support chats order for older versions of the server (#2934) * Update mergeSubscriptionsRooms.js * Update mergeSubscriptionsRooms.js * Update mergeSubscriptionsRooms.js * Minor refactor Co-authored-by: Diego Mello * [FIX] Reactions modal's backdrop color too light (#2949) Co-authored-by: Diego Mello * Bump version to 4.15.0 (#2950) * [FIX] Share extension not working correctly on Official app (#2963) * [FIX] Cannot read property 'some' of undefined on hasPermission (#2966) Co-authored-by: Diego Mello * [FIX] Deep linking and other connectivity issues (#2894) * Navigate from push notification only if necessary * Use JS SDK branch * Stop reconnecting if it's already connected * Fix RoomsListView forever loading after tapping push notification of another server * Execute fewer operations on app/index * Remove roomsRequest call from onForeground * Apply check and reopen * Stop opening in-app notification when the app is on backgorund * Connecting tweaks * Fix deep linking not working if the app is on background * Force reset yarn cache * Upgrade JS SDK * Remove listener on unmount * Fix resume on Android after back button is pressed * Fix local authentication resume * Fix back button android * Change JS SDK branch * [FIX] Messagebox's placeholder color is too bright (#2968) * [IMPROVEMENT] Message attachment colors (#2860) * Added convertStrToHex function and updated Reply component * Removed convertStrtToHex function and added attachmentBackground * Added color2k, removed transparent view and applied transparentize to backgroundColor * Added stories * Update Reply stories * Update Reply stories * Fix lint * Update Reply stories * Fix props * Move tests to Message stories Co-authored-by: Diego Mello * [FIX] App forgetting workspace when server is not finished added (#2798) * [FIX] App forgetting workspace * Added e2e tests * Update login.js * Update logout.js * Reverted changes on login and share, updated init * Update 08-persistantworkspace.spec.js * Revert unnecessary changes * Revert line change * Update share.js * Tweak tests * Use wm shorthand * Remove irrelevant calls to RocketChat.TOKEN_KEY Co-authored-by: Diego Mello * [TESTS] Add E2E tests to draft message (#2960) * [E2E TEST] Draft message * Fix tests Co-authored-by: Diego Mello * [TESTS] Add E2E tests to group DM (#2961) Co-authored-by: Diego Mello * [TESTS] Add E2E tests to directory (#2964) * [E2E TEST] Directory * Fix tests Co-authored-by: Diego Mello * [CHORE] Simplify server version comparison (#2922) * Simplify server version where needed * Added lte and gte functions and updated imports * Updated functions names * Update util functions * Update util function and added methods * Remove lt and coerce from getPermissions and mergeSubscriptionsRooms * Fix comparison * Update getPermissions.js * Remove unused import * Fix lint * Fix lint Co-authored-by: Diego Mello * [TESTS] Add E2E tests to discussions (#2970) * [E2E TEST] Discussions * fix error Cannot find UI elemen * Fix tests Co-authored-by: Diego Mello * [FIX] Attachment not rendering markdown (#2924) * [FIX] Render markdown in Fields content * Added stories Co-authored-by: Diego Mello * [TESTS] Add e2e tests for mark message as unread (#2953) * [E2E TEST] Add e2e tests for mark message as unread * fixed test for draft message * change test name * move test to other file * Remove unnecessary tests * Rename file * Update jest tests Co-authored-by: Diego Mello * [TESTS] Add E2E tests to delete server (#2954) * [E2E TEST] Delete server * fixed test for delete server * fix tests * minor changes * Rename file Co-authored-by: Diego Mello * [CHORE] Refactor RoomActionsView permissions (#2872) Co-authored-by: Diego Mello * [CHORE] Add status and teams icons (#2989) Co-authored-by: Gerzon Z * [FIX] SSO not working with 2FA (TOTP) (#2978) * Update AuthenticationWebView.js * Updated loginTOTP * Added validation * Update rocketchat.js * Update rocketchat.js * Update rocketchat.js * Update rocketchat.js * Fix resolve * Remove incognito * Fix totp being requested on webview Co-authored-by: Diego Mello * [IMPROVEMENT] User status icons (#2991) * Add status and teams * Update icons, icon size and getUsersPresence * Minor changes * Refactor RoomTypeIcon * Minor tweaks * Update unit tests * Minor fixes * Fix styles * Small refactor * Update jest Co-authored-by: Diego Mello * [REGRESSION] Auth via deep linking not working (#3015) * Update rocketchat and add e2e test for deep linking * Update rocketchat and add e2e test for deep linking * Update deeplinking e2e * fix deep linking auth * Test deep linking auth * Fix deeplink to rid and add tests * Small refactor * Add non existing server test Co-authored-by: Diego Mello * [FIX] Create discussion request being sent with null value on encryption param (#3033) * [CHORE] Use JSON files for i18n (#3011) * [IMPROVEMENT] Load only i18n files needed (#3014) * Use json * Load only i18n files needed * [REGRESSION] Clear local server cache not loading rooms (#3007) * Fix clear cache * Write e2e tests * Fix lint * Fix isRTL * [FIX] Custom OAuth and iframe login attempts being called multiple times (#3020) * [FIX] App crashing when attachment color is an invalid HEX (#3021) * [IMPROVEMENT] Add "Message" option to Room Info (#3029) * [CHORE] Go to room from hashtag * Layout tweaks Co-authored-by: Diego Mello * [FIX] Can't change status (#3018) Co-authored-by: Diego Mello * [FIX] Search input not using the whole header space (#3012) * [FIX] Search input not using the whole space * Fix on getHeaderTitlePosition Co-authored-by: Diego Mello * [FIX] E2EE password hiding automatically (#2972) * [FIX] E2EE password hiding automatically * add e2e test * fixed hiding banner * move e2e tests to 01-e2eencryption * remove console.log * Fix tests Co-authored-by: Diego Mello * [TESTS] Move threads tests to its own file (#2965) * [E2E TEST] Move threads test to another file * changed descirbe title * Rearrange files Co-authored-by: Diego Mello * [FIX] Regex typo on markdown (#2928) * [FIX] Fix Regex Typo * Add story for testing Co-authored-by: Diego Mello * [FIX] Make attachment validation compatible with web client (#2927) * [FIX] Make attachment validation compatible with web client * Added stories Co-authored-by: Diego Mello * [FIX] Non-reply attachments displaying time (#2902) * Remove time if no message_link * Fix message stories for replies * Final stories fix Co-authored-by: Diego Mello * [FIX] i18n not being applied on login/register labels (#2930) * Use I18n translate in login text input label * Add to register and add missing strings Co-authored-by: Diego Mello * Revert "[FIX] Make attachment validation compatible with web client (#2927)" (#3036) This reverts commit d6200745c028dd47b4ce0f11eb396c8f2a4cf807. * Bump version to 4.16.0 (#3037) * [NEW] Basic support to Teams (#3016) * Database migration * RoomItem icon * Team icons * Teams group * Small tweak on RoomTypeIcon * RoomView Header * Add team's channels to RoomView header * Starting TeamChannelsView * Icon size * o data found * Update TeamChannelsView, add teams subscriptions and send params to TeamChannelsView * Use teams.ListRooms endpoint, render rooms list, remove unused functions * Show team main on TeamChannelsView * Disable swipe * Pagination working * Fix blinking no data found * Search working * Refactor to use BackgroundContainer while loading * Go to room * Cleanup * Go to actions * Events * Lint * Add debounce to go room * Fix for tablet * i18n * Small fix * Minor refactor * Use local data when it exists * Show last message * Force teams migration * Add stories to BackgroundContainer * Remove unused component * Move RoomViewHeader into containers folder * Refactoring * Testing RoomHeader * i18n * Fix server endpoint version * Fix events Co-authored-by: Gerzon Z * [CHORE] Refactor mention tracking logic (#2997) * [Improvement] Improve mentions This PR focuses on improving command, emoji, channel and user mentions. * [Tests] Added e2e tests for mention improvement * [Improvement] Modify slash command mention logic. Added slash command with argument preview Slash command should show only if the message bigins with / * Return data on search for empty text * Minor fixes * Update e2e tests * Minor fix * [FIX] allow command mentioning in between text Co-authored-by: Diego Mello * [FIX] Status text not being updated on sidebar (#3041) * Update StatusView.js * Minor tweak * Minor tweaks Co-authored-by: Diego Mello * [FIX] Unable to search non-latin alphabet names on members list (#3039) * Add search by name in members list * Update RoomMembersView search Co-authored-by: Diego Mello * Search stops working after some time (#3044) * Bump version to 4.17.0 (#3058) * [CHORE] Add job to upload Experimental to Google Play production (#3050) * [REGRESSION] SAML stopped working after #2978 (#3060) * [REGRESSION] Room actions not loading on tablet (#3061) * Bump version to 4.16.1 (#3063) * [REGRESSION] Fallback language stopped working (#3072) * [CHORE] Update Detox to 18.10.0 (#3052) * Updated detox and 5 tests * Update e2e cases for Detox v18, update setUserStatus and added SET_STATUS_FAIL * Downgrade mocha * Exclude arm64 from building and update tests cases * Update more tests cases, add registeringUser4 * Update more test files and add room-actions-scrollview testID * Update package.json * Remove unused username from test file and update 08-roominfo test file * Fixing * Mark as unread * Fixing flaky tests * Minor fixes Co-authored-by: Diego Mello * [FIX] Message author touchable taking whole space available (#3048) Co-authored-by: Gerzon Z * [CHORE] Improve stories (#3028) * [CHORE] Improve stories * Refactor Avatar and UIKitModal * fixed undefined 'name' * Remove commented stories * Remove Markdown from stories/index, update Markdown test file and remove markdown stories from Message stories * Remove StoriesSeparator * Refactor Markdown * Remove commented lines of code * Small refactor * Re-add stories Co-authored-by: Gerzon Z Co-authored-by: Gerzon Z Co-authored-by: Diego Mello * Bump version to 4.17.0 (#3083) * [REGRESSION] Fallback not working when device's language is available (#3091) * Always add 'en' i18n * Add tests * Bump version to 4.16.2 (#3092) * [FIX] Connecting stream listener not being cleared (#3008) Co-authored-by: Diego Mello * [FIX] App making calls to DDP after socket was killed by OS (#3062) Co-authored-by: Gerzon Z * [NEW] Create Team (#3082) Co-authored-by: Diego Mello * Language update from LingoHub 🤖 (#3139) Project Name: Rocket.Chat.ReactNative Project Link: https://translate.lingohub.com/rocketchat/dashboard/rocket-dot-chat-dot-reactnative User: Robot LingoHub Easy language translations with LingoHub 🚀 Co-authored-by: Robot LingoHub * [NEW] Add/Create/Remove channel on a team (#3090) * Added Create Team * Added actionTypes, actions, ENG strings for Teams and updated NewMessageView * Added createTeam sagas, createTeam reducer, new Team string and update CreateChannelView * Remove unnecessary actionTypes, reducers and sagas, e2e tests and navigation to team view * Minor tweaks * Show TeamChannelsView only if joined the team * Minor tweak * Added AddChannelTeamView * Added permissions, translations strings for teams, deleteTeamRoom and addTeamRooms, AddExistingChannelView, updated CreateChannelView, TeamChannelsView * Refactor touch component and update removeRoom and deleteRoom methods * Minor tweaks * Minor tweaks for removing channels and addExistingChannelView * Added missing events and fixed channels list * Minor tweaks for refactored touch component * Minor tweaks * Remove unnecesary changes, update TeamChannelsView, AddExistingChannelView, AddChannelTeamView, createChannel, goRoom and Touchable * Add screens to ModalStack, events, autoJoin, update createChannel, addRoomsToTeam and Touchable * Minor tweak * Update loadMessagesForRoom.js * Updated schema, tag component, touch, AddChannelTeamView, AddExistingChannelView, ActionSheet Item * Fix unnecessary changes * Add i18n, update createChannel, AddExistingChannelTeamView, AddChannelTeamView, RightButton and TeamChannelsView * Updated styles, added tag story * Minor tweak * Minor tweaks * Auto-join tweak * Minor tweaks * Minor tweak on search * One way to refactor :P * Next level refactor :) * Fix create group dm * Refactor renderItem * Minor bug fixes * Fix stories Co-authored-by: Diego Mello * [FIX] E2E Tests not working because of ES6 import (#3147) * Update ITeam.js * Minor tweak * [NEW] Leave Teams (#3116) * Added Create Team * Added actionTypes, actions, ENG strings for Teams and updated NewMessageView * Added createTeam sagas, createTeam reducer, new Team string and update CreateChannelView * Remove unnecessary actionTypes, reducers and sagas, e2e tests and navigation to team view * Minor tweaks * Show TeamChannelsView only if joined the team * Minor tweak * Added AddChannelTeamView * Added permissions, translations strings for teams, deleteTeamRoom and addTeamRooms, AddExistingChannelView, updated CreateChannelView, TeamChannelsView * Refactor touch component and update removeRoom and deleteRoom methods * Minor tweaks * Minor tweaks for removing channels and addExistingChannelView * Added missing events and fixed channels list * Minor tweaks for refactored touch component * Added SelectListView and logic for leaving team * Minor tweak * Minor tweak * Minor tweaks * Remove unnecesary changes, update TeamChannelsView, AddExistingChannelView, AddChannelTeamView, createChannel, goRoom and Touchable * Remove unnecesary prop * Add screens to ModalStack, events, autoJoin, update createChannel, addRoomsToTeam and Touchable * Minor tweak * Update loadMessagesForRoom.js * Updated schema, tag component, touch, AddChannelTeamView, AddExistingChannelView, ActionSheet Item * Fix unnecessary changes * Add i18n, update createChannel, AddExistingChannelTeamView, AddChannelTeamView, RightButton and TeamChannelsView * Updated styles, added tag story * Minor tweak * Minor tweaks * Auto-join tweak * Minor tweaks * Minor tweak on search * Minor refactor to ListItem, add SelectListView to ModalStack, update handleLeaveTeam * Minor tweaks * Update SelectListView * Update handleLeaveTeam, remove unnecessary method, add story * Minor tweak * Minor visual tweaks * Updated SelectListView, RoomActionsView, leaveTeam method and string translations * Update SelectListVIew * Minor tweak * Update SelectListView * Minor tweak * Fix for List.Item subtitles being pushed down by title's flex * Minor tweaks * Update RoomActionsView * Use showConfirmationAlert and showErrorAlert * Lint Co-authored-by: Diego Mello * [NEW] Jump to message (#3099) * Scrolling * Add loadMore button at the end of loadMessagesForRoom * Delete dummy item on tap * Only insert loadMore dummy if there's more data * load surrounding messages * fixes and load next * First dummy and dummy-next * Save load next messages * Check if message exists before fetching surroundings * Refactoring List * Jumping to message :) * Showing blocking loader while scrolling/fetching message * Check if message exists on local db before inserting dummy * Delete dummies automatically when the message sent to updateMessages again * Minor cleanup * Fix scroll * Highlight message * Jump to bottom * Load more on scroll * Adding stories to LoadMore * Refactoring * Add loading indicator to LoadMore * Small refactor * Add LoadMore to threads * getMoreMessages * chat.getThreadMessages -> getThreadMessages * Start jumping to threads * Add jumpToMessageId on RoomView * Nav to correct channel * Fix PK issue on thread_messages * Disable jump to thread from another room * Fix nav to thread params * Add navToRoom * Refactor styles * Test notch * Fix Android border * Fix thread message on title * Fix NavBottomFAB on threads * Minor cleanup * Workaround for readThreads being called too often * Lint * Update tests * Jump from search * Go to threads from search * Remove getItemLayout and rely on viewable items * Fix load older * stash working * Fix infinite loading * Lower itemVisiblePercentThreshhold to 10, so very long messages behave as viewable * Add generateLoadMoreId util * Minor cleanup * Jump to message from notification/deep linking * Add getMessageInfo * Nav to threads from other rooms * getThreadName * Unnecessary logic * getRoomInfo * Colocate getMessageInfo closer to RoomView * Minor cleanup * Remove search from RoomActionsView * Minor fix for search on not joined public channels * Jump to any link * Fix tablets * Jump to message from MessagesView and other bug fixes * Fix issue on Urls * Adds race condition to cancel jump to message if it's stuck or after 5 seconds * Jump from message search quote * lint * Stop onPress * Small refactor on load methods * Minor fixes for loadThreadMessages * Minor typo * LoadMore i18n * Minor cleanup * [FIX] Method calls not sending date params as EJSON (#3159) * [FIX] Read receipt not displaying full date (#3133) Co-authored-by: Diego Mello * [NEW] Remove member from team (#3117) * Added Create Team * Added actionTypes, actions, ENG strings for Teams and updated NewMessageView * Added createTeam sagas, createTeam reducer, new Team string and update CreateChannelView * Remove unnecessary actionTypes, reducers and sagas, e2e tests and navigation to team view * Minor tweaks * Show TeamChannelsView only if joined the team * Minor tweak * Added AddChannelTeamView * Added permissions, translations strings for teams, deleteTeamRoom and addTeamRooms, AddExistingChannelView, updated CreateChannelView, TeamChannelsView * Refactor touch component and update removeRoom and deleteRoom methods * Minor tweaks * Minor tweaks for removing channels and addExistingChannelView * Added missing events and fixed channels list * Minor tweaks for refactored touch component * Added SelectListView and logic for leaving team * Added addTeamMember and removeTeamMember * Minor tweak * Minor tweak * Minor tweaks * Remove unnecesary changes, update TeamChannelsView, AddExistingChannelView, AddChannelTeamView, createChannel, goRoom and Touchable * Remove unnecesary prop * Add screens to ModalStack, events, autoJoin, update createChannel, addRoomsToTeam and Touchable * Minor tweak * Update loadMessagesForRoom.js * Updated schema, tag component, touch, AddChannelTeamView, AddExistingChannelView, ActionSheet Item * Fix unnecessary changes * Add i18n, update createChannel, AddExistingChannelTeamView, AddChannelTeamView, RightButton and TeamChannelsView * Updated styles, added tag story * Minor tweak * Minor tweaks * Auto-join tweak * Minor tweaks * Minor tweak on search * Minor refactor to ListItem, add SelectListView to ModalStack, update handleLeaveTeam * Minor tweaks * Update SelectListView * Update handleLeaveTeam, remove unnecessary method, add story * Minor tweak * Minor visual tweaks * Update SelectListView.js * Update RoomMembersView * Updated SelectListView, RoomActionsView, leaveTeam method and string translations * Update SelectListVIew * Minor tweak * Update SelectListView * Minor tweak * Minor tweaks * Fix for List.Item subtitles being pushed down by title's flex * Minor tweaks * Update RoomActionsView * Use showConfirmationAlert and showErrorAlert * Remove addTeamMember, update removeTeamMember * Update Alert * Minor tweaks * Minor tweaks * Minor tweak * Update showActionSheet on RoomMembersView * Remove team main from query and move code around * Fetch roles * Update RoomMembersView and SelectListView * Updated leaveTeam and handleRemoveFromTeam * Fix validation * Remove unnecessary function * Added confirmationAlert for missing permissions case Co-authored-by: Diego Mello * [FIX] Add Existing Channel screen showing discussions and channels without permission (#3151) * [Fix] the filter to show the existing channel * [Refactor] the function that filter to isolate it * Refactor how to wsearch properly Co-authored-by: Diego Mello * [FIX] Member search not trimming search text (#3129) * Fixed logout toast bug for the iOS * Removing callToAction and replacing with confirmationText * Handling member search with spaces to the left and right of name/username * Changing location of string trimmer Co-authored-by: Diego Mello * [FIX] Discussions not subscribing properly to messages when opened from inside the room (#3149) * [FIX] Promise at subscription Room * E2E - Update previous roomView count after send msg in discussion * Not needed rn Co-authored-by: Diego Mello * [FIX] Team creation not raising error if something unexpected happens (#3152) * [IMPROVEMENT] Add error to AddExistingChannel * Fix the alert error when create a channel * Fix the error alert box when create channel and teams Co-authored-by: Diego Mello * [FIX] Check permissions on team channels action sheet (#3155) * [IMPROVEMENT] Show only the option that user can manage in TeamChannelsView * Refactor the showActionSheet function * Added remove team channel permission * Cleanup Co-authored-by: Diego Mello * [FIX] Add channels to team's flow using different navigators (#3157) * [FIX] the navigation to AddChannelTeamView and next screens * Fix the order inside the NewMessageStackNavigator * Delete spaces after arrow function in onPress * Adjusted InsideStackNavigator to a conditional animation * Fixed route for iPad * Small change Co-authored-by: Diego Mello * [IMPROVEMENT] Allow discussions to be edited (#3137) * Refactored the filter to work the edit for channel and discussion * Removed the filter which type of room can be edit Co-authored-by: Diego Mello * Fix tests Co-authored-by: Diego Mello * [NEW] Delete Teams (#3123) * Added Create Team * Added actionTypes, actions, ENG strings for Teams and updated NewMessageView * Added createTeam sagas, createTeam reducer, new Team string and update CreateChannelView * Remove unnecessary actionTypes, reducers and sagas, e2e tests and navigation to team view * Minor tweaks * Show TeamChannelsView only if joined the team * Minor tweak * Added AddChannelTeamView * Added permissions, translations strings for teams, deleteTeamRoom and addTeamRooms, AddExistingChannelView, updated CreateChannelView, TeamChannelsView * Refactor touch component and update removeRoom and deleteRoom methods * Minor tweaks * Minor tweaks for removing channels and addExistingChannelView * Added missing events and fixed channels list * Minor tweaks for refactored touch component * Added SelectListView and logic for leaving team * Added addTeamMember and removeTeamMember * Minor tweak * Added deleteTeam function * Minor tweak * Minor tweaks * Remove unnecesary changes, update TeamChannelsView, AddExistingChannelView, AddChannelTeamView, createChannel, goRoom and Touchable * Remove unnecesary prop * Add screens to ModalStack, events, autoJoin, update createChannel, addRoomsToTeam and Touchable * Minor tweak * Update loadMessagesForRoom.js * Updated schema, tag component, touch, AddChannelTeamView, AddExistingChannelView, ActionSheet Item * Fix unnecessary changes * Add i18n, update createChannel, AddExistingChannelTeamView, AddChannelTeamView, RightButton and TeamChannelsView * Updated styles, added tag story * Minor tweak * Minor tweaks * Auto-join tweak * Minor tweaks * Minor tweak on search * Minor refactor to ListItem, add SelectListView to ModalStack, update handleLeaveTeam * Minor tweaks * Update SelectListView * Update handleLeaveTeam, remove unnecessary method, add story * Minor tweak * Minor visual tweaks * Update SelectListView.js * Update index.js * Update RoomMembersView * Updated SelectListView, RoomActionsView, leaveTeam method and string translations * Update SelectListVIew * Minor tweak * Update SelectListView * Minor tweak * Minor tweaks * Fix for List.Item subtitles being pushed down by title's flex * Minor tweaks * Update RoomActionsView * Use showConfirmationAlert and showErrorAlert * Remove addTeamMember, update removeTeamMember * Update Alert * Minor tweaks * Minor tweaks * Minor tweak * Update showActionSheet on RoomMembersView * Remove team main from query and move code around * Fetch roles * Update RoomMembersView and SelectListView * Update rocketchat.js * Updated leaveTeam and handleRemoveFromTeam * Fix validation * Remove unnecessary function * Update RoomActionsView * Update en.json * updated deleteTeam function and permissions * Added showConfirmationAlert * Added string translations for teams * Fix permission * Minor tweaks * Typo Co-authored-by: Diego Mello * [FIX] Android navigation bar color when Loading modal appears (#3165) * [FIX] Modal appearance * Undo and only add android:navigationBarColor Co-authored-by: Diego Mello * [FIX] Check for old servers for Teams (#3171) Co-authored-by: Diego Mello * [NEW] Convert/Move Channel to Team (#3164) * Added Create Team * Added actionTypes, actions, ENG strings for Teams and updated NewMessageView * Added createTeam sagas, createTeam reducer, new Team string and update CreateChannelView * Remove unnecessary actionTypes, reducers and sagas, e2e tests and navigation to team view * Minor tweaks * Show TeamChannelsView only if joined the team * Minor tweak * Added AddChannelTeamView * Added permissions, translations strings for teams, deleteTeamRoom and addTeamRooms, AddExistingChannelView, updated CreateChannelView, TeamChannelsView * Refactor touch component and update removeRoom and deleteRoom methods * Minor tweaks * Minor tweaks for removing channels and addExistingChannelView * Added missing events and fixed channels list * Minor tweaks for refactored touch component * Added SelectListView and logic for leaving team * Added addTeamMember and removeTeamMember * Minor tweak * Added deleteTeam function * Minor tweak * Minor tweaks * Remove unnecesary changes, update TeamChannelsView, AddExistingChannelView, AddChannelTeamView, createChannel, goRoom and Touchable * Remove unnecesary prop * Add screens to ModalStack, events, autoJoin, update createChannel, addRoomsToTeam and Touchable * Minor tweak * Update loadMessagesForRoom.js * Updated schema, tag component, touch, AddChannelTeamView, AddExistingChannelView, ActionSheet Item * Fix unnecessary changes * Add i18n, update createChannel, AddExistingChannelTeamView, AddChannelTeamView, RightButton and TeamChannelsView * Updated styles, added tag story * Minor tweak * Minor tweaks * Auto-join tweak * Minor tweaks * Minor tweak on search * Minor refactor to ListItem, add SelectListView to ModalStack, update handleLeaveTeam * Minor tweaks * Update SelectListView * Update handleLeaveTeam, remove unnecessary method, add story * Minor tweak * Minor visual tweaks * Update SelectListView.js * Update index.js * Update RoomMembersView * Updated SelectListView, RoomActionsView, leaveTeam method and string translations * Update SelectListVIew * Minor tweak * Update SelectListView * Minor tweak * Minor tweaks * Fix for List.Item subtitles being pushed down by title's flex * Minor tweaks * Update RoomActionsView * Use showConfirmationAlert and showErrorAlert * Remove addTeamMember, update removeTeamMember * Update Alert * Minor tweaks * Minor tweaks * Minor tweak * Update showActionSheet on RoomMembersView * Remove team main from query and move code around * Fetch roles * Update RoomMembersView and SelectListView * Update rocketchat.js * Updated leaveTeam and handleRemoveFromTeam * Fix validation * Remove unnecessary function * Update RoomActionsView * Update en.json * updated deleteTeam function and permissions * Added showConfirmationAlert * Added string translations for teams * Fix permission * Added moveChannelToTeam and convertToTeam functionality * Fix SelectListView RadioButton * Fix moveToTeam * Added searchBar to SelectListVIew * Update RoomView , SelectListVIew and string translation for error Co-authored-by: Diego Mello * [TEST] E2E Tests for Teams (#3178) * Added Create Team * Added actionTypes, actions, ENG strings for Teams and updated NewMessageView * Added createTeam sagas, createTeam reducer, new Team string and update CreateChannelView * Remove unnecessary actionTypes, reducers and sagas, e2e tests and navigation to team view * Minor tweaks * Show TeamChannelsView only if joined the team * Minor tweak * Added AddChannelTeamView * Added permissions, translations strings for teams, deleteTeamRoom and addTeamRooms, AddExistingChannelView, updated CreateChannelView, TeamChannelsView * Refactor touch component and update removeRoom and deleteRoom methods * Minor tweaks * Minor tweaks for removing channels and addExistingChannelView * Added missing events and fixed channels list * Minor tweaks for refactored touch component * Added SelectListView and logic for leaving team * Added addTeamMember and removeTeamMember * Minor tweak * Added deleteTeam function * Minor tweak * Minor tweaks * Remove unnecesary changes, update TeamChannelsView, AddExistingChannelView, AddChannelTeamView, createChannel, goRoom and Touchable * Remove unnecesary prop * Add screens to ModalStack, events, autoJoin, update createChannel, addRoomsToTeam and Touchable * Minor tweak * Update loadMessagesForRoom.js * Updated schema, tag component, touch, AddChannelTeamView, AddExistingChannelView, ActionSheet Item * Fix unnecessary changes * Add i18n, update createChannel, AddExistingChannelTeamView, AddChannelTeamView, RightButton and TeamChannelsView * Updated styles, added tag story * Minor tweak * Minor tweaks * Auto-join tweak * Minor tweaks * Minor tweak on search * Minor refactor to ListItem, add SelectListView to ModalStack, update handleLeaveTeam * Minor tweaks * Update SelectListView * Update handleLeaveTeam, remove unnecessary method, add story * Minor tweak * Minor visual tweaks * Update SelectListView.js * Update index.js * Update RoomMembersView * Updated SelectListView, RoomActionsView, leaveTeam method and string translations * Update SelectListVIew * Minor tweak * Update SelectListView * Minor tweak * Minor tweaks * Fix for List.Item subtitles being pushed down by title's flex * Minor tweaks * Update RoomActionsView * Use showConfirmationAlert and showErrorAlert * Remove addTeamMember, update removeTeamMember * Update Alert * Minor tweaks * Minor tweaks * Minor tweak * Update showActionSheet on RoomMembersView * Remove team main from query and move code around * Fetch roles * Update RoomMembersView and SelectListView * Update rocketchat.js * Updated leaveTeam and handleRemoveFromTeam * Fix validation * Remove unnecessary function * Update RoomActionsView * Update en.json * updated deleteTeam function and permissions * Added showConfirmationAlert * Added string translations for teams * Fix permission * Added moveChannelToTeam and convertToTeam functionality * Fix SelectListView RadioButton * Fix moveToTeam * Added searchBar to SelectListVIew * Update RoomView , SelectListVIew and string translation for error * E2E for Teams * Fix tests and cleanup * Minor refactor * Wrong label * Move/convert * Fix convert Co-authored-by: Diego Mello * [NEW] Add Teams to Directory (#3181) * Added Teams to DirectoryView * Fix icon * Minor tweaks * add tests Co-authored-by: Diego Mello * [CHORE] Add logEvents for Teams (#3182) * added events for team channels view and add existing channel view * add logevents for room actions view and room info edit view Co-authored-by: Diego Mello * [FIX] Disable jitsi call for teams (#3183) Co-authored-by: Diego Mello * [FIX] Show alert `Not allowed` when click on a private channel that you don't be invited before (#3177) * [FIX] Showing only channel you joined * [FIX] How to get the params to mnavigation to other room from TeamChannelList * Show alert Not allowed when trying access private channel that you don't joined Co-authored-by: Diego Mello * [IMPROVEMENT] Load team's rooms from local database on team leave (#3185) * [IMPROVEMENT] Search team list rooms of user in watermelon db * Minor nitpick Co-authored-by: Diego Mello * [FIX] Option to prevent users from using Invisible status (#3186) * [FIX] Option to prevent users from using Invisible status * Added error to pt-BR Co-authored-by: Diego Mello * [FIX] Item not animating on tap on team's channels view (#3187) * [FIX] Directory sending incorrect room type (#3188) Co-authored-by: Diego Mello * [FIX] App not showing proper alert on team leave (#3161) * [IMPROVEMENT] refactoring how to leave team * Fix the data passed to leaveTeam * Fixed the lint error in i18n, the path of i18n, merged two ifs in one * Fixed the Saga's flow when try to leave a room * Fixed params passed to leaveRoom * Fix the function name of leaveTeam Co-authored-by: Diego Mello * Language update from LingoHub 🤖 (#3192) Project Name: Rocket.Chat.ReactNative Project Link: https://translate.lingohub.com/rocketchat/dashboard/rocket-dot-chat-dot-reactnative User: Robot LingoHub Easy language translations with LingoHub 🚀 Co-authored-by: Robot LingoHub Co-authored-by: Diego Mello * [NEW] Support Google OAuth from external browser (#3134) * Deep linking to the app * Handle deep linking * Bump version to 4.17.0 (#3093) * Revert "[IMPROVEMENT] Load team's rooms from local database on team leave (#3185)" (#3194) This reverts commit fa00ef92efa45fef3938afbb92be52b97cb16358. * [FIX] Teams tests (#3196) * Make team_main not optional and fix tests * Undo isOptional and fix query * Comment * [FIX] Wrong system messages being passed as parameters to room save (#3197) * [FIX] RoomItem's long press crashing the app if prop is missing (#3199) * Check onLongPress prop * Add Touch stories * [FIX] Crashing on link press (#3204) * [FIX] Don't show Block Button inside Group DM Actions (#3195) * [FIX] Don't show Block Button inside Group DM Actions * Use RocketChat.isGroupChat instead of simple if condition * Add return Co-authored-by: Diego Mello * [TEST] Fixed E2E tests (#3201) * [FIX] Test E2E i18n * 01-createroom and 02-room fixed * 03-roomactions and 04-discussions * 05-threads and 07-markasunread from room * Test 07-markasunread * Set notifications 'YES' and delete true in 03-forgotpassword and 04-createuser * Fixed the data that 02-team uses and changed the message in 07-markasunread * Added group.alternate to data.docker and commented the test for the fallback language Co-authored-by: Diego Mello * [TEST] E2E for Jump to Message (#3202) * E2E tests for jump to message * Fix thread tests * Remove unnecessary function and uncomment tests * Minor tweak * Fix tests and minor tweaks * Minor tweaks * Update docker data * Fix docker * Fix duplicated testid * Minor refactor * Fix jump to old message test * Fix load on scroll * Add fab test * Minor addition * stash * almost there * Final changes Co-authored-by: Diego Mello * [IMPROVE] Subscribe to permissions (#2993) * [CHORE] Subscribe to permissions * add redux action for update * Minor tweaks Co-authored-by: Gerzon Z Co-authored-by: Gerzon Z Co-authored-by: Diego Mello * [IMPROVE] Subscribe to roles (#2992) * [CHORE] Subscribe to Roles * subscribe to roles-change * add subscribe for stream-roles * fixed subscribe roles * Add componentDidUpdate to RoomMembersView and propType * Update componentDidUpdate in RoomMembersView, roles reducer, getRoles method and actionType * Minor tweaks * Remove componentDidUpdate * Fix add role * Fix initialState and remove role * Minor try/catch fix * Fix lint * Fix offline first Co-authored-by: Diego Mello Co-authored-by: Gerzon Z Co-authored-by: Gerzon Z * [IMPROVE] Subscribe to settings (#3222) * Add action and reducer * Add streamNotifyAll listener * Minor tweak * Minor tweak * Fix update not taking in consideration other type columnns Co-authored-by: Diego Mello * Chore: Add Lint to E2E tests (#3217) * Added eslint plugin dependencie and fixed the eslint.js * E2E Tests folder Assorted * Linted all e2e, just e2e/docker that don't changed * Update 09-jumptomessage.spec.js * Removed async from describe function * Remove outdated detox linter lib * Add overrides to eslintrc Co-authored-by: Gerzon Z Co-authored-by: Gerzon Z Co-authored-by: Diego Mello * [FIX] App not showing proper alert on team delete (#3219) * [FIX] Rule to delete team's channel * Fixed Saga and flow to delete team and team's channel * Adjusted the warning alert as the Figma Co-authored-by: Gerzon Z Co-authored-by: Diego Mello * [IMPROVE] Add Jitsi button to Teams (#3223) * [IMPROVE] Add Jitsi button to teams * Added setting to check is Jitsi is Enable for Channel too * Fix typo Co-authored-by: Gerzon Z Co-authored-by: Gerzon Z Co-authored-by: Diego Mello * [FIX] Jump to message from in-app notification (#3225) * [FIX] Jump to message by in-app notification * Bug fix to scroll proper the last message * Minor tweak Co-authored-by: Gerzon Z Co-authored-by: Gerzon Z Co-authored-by: Diego Mello * [FIX] Google OAuth triggering cookies logic (#3244) * Remove checkCookiesAndLogout * Add loginEmailPassword to loginOAuthOrSso * Add isFromWebView field * Fix migrations * Minor tweak * Fix OAuth for other services * Fix migrations * Stop persisting loginEmailPassword Co-authored-by: Diego Mello * Language update from LingoHub 🤖 (#3251) Project Name: Rocket.Chat.ReactNative Project Link: https://translate.lingohub.com/rocketchat/dashboard/rocket-dot-chat-dot-reactnative User: Robot LingoHub Easy language translations with LingoHub 🚀 Co-authored-by: Robot LingoHub Co-authored-by: Diego Mello * [IMPROVE] Message body readability on dark themes (#2981) * [CHORE] Apply auxiliaryText on message body * change bodyText to uxiliaryText * Update tests * Update bodyText color and rollback PR changes * Update Storyshots.test.js.snap * Minor tweak Co-authored-by: Diego Mello Co-authored-by: Gerzon Z Co-authored-by: Gerzon Z * [FIX] Subscribe to settings making app to hang on login (#3254) * [FIX] Poor performance in messages list on Android 11 (#3260) * Bump version to 4.18.0 (#3252) * [FIX] Create team crashing the app (#3248) Co-authored-by: Gerzon Z Co-authored-by: Gerzon Z Co-authored-by: Diego Mello * [IMPROVE] Convert Team to Channel (#3249) * [IMPROVE] Add convert team to a channel * Action to SelectListView and new words to i18n * Implemented the post and it's working with selected channels or not * Fixed the Convert Team Warning at english i18n and changed the function name * E2E test completed in sequence the convert/move teams * [IMPROVE] Add convert team to a channel * Action to SelectListView and new words to i18n * Implemented the post and it's working with selected channels or not * Fixed the Convert Team Warning at english i18n and changed the function name * rebase develop into this branch * [IMPROVE] Add convert team to a channel * Action to SelectListView and new words to i18n * Implemented the post and it's working with selected channels or not * Fixed the Convert Team Warning at english i18n and changed the function name * rebase develop into this branch Co-authored-by: Diego Mello * [IMPROVE] Set black as default dark theme (#3270) * Update default darkLevel * Minor tweak * [IMPROVE] Make `system default` the default browser (#3265) * [FIX] use systemdefault: as the default browser, not inApp * Fix Co-authored-by: Diego Mello * Language update from LingoHub 🤖 (#3269) Project Name: Rocket.Chat.ReactNative Project Link: https://translate.lingohub.com/rocketchat/dashboard/rocket-dot-chat-dot-reactnative User: Robot LingoHub Easy language translations with LingoHub 🚀 Co-authored-by: Robot LingoHub Co-authored-by: Diego Mello * [IMPROVE] Remove difference between public/private on "Group by type" (#3271) * Merge channels and private groups * Remove i18n * Regression: Settings pagination not working (#3277) * Regression: Markdown handlePress not working properly (#3278) Co-authored-by: Diego Mello * Chore: Improve QA workflow (#3285) * Chore: Update dependencies (#3206) * Update non-dev patches * Update dev patches * Update minors * Update dev minors * Update few non semver * Cookies * datepicker, netinfo, base64 and bootsplash * Patch cookies * Update navigation * Device info * mocha * localize * react-native-picker-select * vector icons, xregexp, popover * try save husky * document picker * Remove emotion dev * Downgrade some libs and make sure jest is passing * Update storybook to stable * mocha, axios, bootsplash * Update lint job to node 15 * Chore: Update React Native to 0.64.2 (#3245) * Update non-dev patches * Update dev patches * Update minors * Update dev minors * Update few non semver * Cookies * datepicker, netinfo, base64 and bootsplash * Patch cookies * Update navigation * Device info * mocha * localize * react-native-picker-select * vector icons, xregexp, popover * try save husky * document picker * Remove emotion dev * Downgrade some libs and make sure jest is passing * Update storybook to stable * mocha, axios, bootsplash * Update lint job to node 15 * Update android image to api 29 and xcode to 12.4 * building * Fix lint * Get rid of Storybooks errors * Patch react-native-simple-crypto * Remove pods from git * Stash simple crypto * Stash Flipper * Remove single crypto patch * Add manage-pods command * Update Xcode to 12.5.0 * Fix E2E tests * Cleanup podfile * Fix Storybook * Remove RN patch * Fix iOS build release * Fix cocoapods cache on CI * Try to fix pods using bundle * Update gems * Add app_store_connect_api_key env to CI * APP_STORE_CONNECT_API_KEY -> APP_STORE_CONNECT_API_BASE64 * Rollback to older usage of app_store_connect_api_key * tmp * Run manage-pods on TestFlight * Use Podfile instead of Podfile.lock for cache * Increase no_output_timeout from 20 minutes to 40 * Restore node modules on upload-to-testflight * Add pod install to docs * Chore: Run lint and tests on staged files only (#3291) * Bump version to 4.19.0 (#3307) * Chore: Update Bugsnag (#3300) * Remove bugsnag-react-native * Really remove bugsnag from android * Install @bugsnag/react-native * Logging error on Android correctly * Cleanup * Fix bugsnag mock * iOS builds * Fix CI mistake * Upload dSYMs to Bugsnag * Upload source maps automatically on iOS * Cleanup * Enable Bugsnag on share extension * Add test error * Use large macos * Bump to 4.19.0 temporarily to test on TestFlight official * Use temp keys * Fix upload source maps for Official iOS build * Remove tests * Set version back to 4.18.0 * Language update from LingoHub 🤖 (#3297) Project Name: Rocket.Chat.ReactNative Project Link: https://translate.lingohub.com/rocketchat/dashboard/rocket-dot-chat-dot-reactnative User: Robot LingoHub Easy language translations with LingoHub 🚀 Co-authored-by: Robot LingoHub Co-authored-by: Diego Mello * [FIX] Unarchive permission not honored (#3237) * [FIX] Show alert when unarchive error * Title in alert * Disable button when the user donesn't have the role permission * Use ARCHIVE/UNARCHIVE instead of their lowercase in alert and removed capitalize lodash * Check if the error eis translated before parse through i18n * Remove unnecessary code Co-authored-by: Diego Mello Co-authored-by: Levy Costa * [FIX] Hardcoded backdrop opacity on loading component (#3255) * Added withTheme and themes to Loading * Added animation to backdrop opacity * Minor tweak * Fix internal image impacted by opacity Co-authored-by: Diego Mello Co-authored-by: Levy Costa * [FIX] Share extension not working on iOS (#3310) * Temp add all pods to share extension * Cleanup * [FIX] Permissions to edit livechat when the user is a livechat-agent (#3294) * [FIX] Permissions to edit livechat * Added the permission to edit livechat room custom fields Co-authored-by: Levy Costa Co-authored-by: Diego Mello * [FIX] Reactive footer when agents take chats (#3288) Co-authored-by: Diego Mello * [FIX] Omnichannel custom fields are not rendered properly (#3295) * [FIX] Permissions to edit livechat * [FIX] Custom fields labels and values * refactor field * Added the permission to edit livechat room custom fields * Fix the inputs.focus() Co-authored-by: Levy Costa Co-authored-by: Diego Mello * [FIX] Wrong message when room is closed by the Guest (#3289) Co-authored-by: Gerzon Z Co-authored-by: Diego Mello * [FIX] Dealing well with pre-configured tags in Omnichannel (#3298) * [FIX] Permissions to edit livechat * [FIX] Tags with multiselect and tagParamsSelected * Removed console.log and the new set to filter * Added the permission to edit livechat room custom fields * Change Title Livechat_edit to Edit * Added marginBottom to multiSelect * Added marginBottom to multiSelect Co-authored-by: Gerzon Z * [FIX] Bugsnag and Analytics opt-out (#3335) * Deleted redux actions for bugsnag and analytics, in addition fixed to eon/off reports for both * Removed console.log * minor tweak * Enable and disable crashlytics and remove breadcrumb from bugsnag * minor tweaks with the names of the variables * minor tweak Co-authored-by: Diego Mello * [FIX] Show thumbnails in message view (#2975) * [FIX] Show thumbnails in message view fixes: #2853 * Add stories for thumbnails and update test * [Test] Update tests * added stories Co-authored-by: Reinaldo Neto Co-authored-by: Levy Costa Co-authored-by: Diego Mello * [FIX] Show button attachment on messages (#2980) * [FIX] Show button attachment in message list fixes: #2684 * Changed the Button and theme, text theme and how to call the function * Fix the props passed in Message * Function to context * Added button attachment to stories * New snapshot Co-authored-by: Reinaldo Neto <47038980+reinaldonetof@users.noreply.github.com> Co-authored-by: Reinaldo Neto Co-authored-by: Levy Costa Co-authored-by: Diego Mello * [FIX] The unread section is not removed after receiving a new message and swipe to read (#3281) * Fix unread section from direct messages and thread messages * Minor tweak * removed the thread unread, but the thread unread is on branch fix.unread-thread-from-listview Co-authored-by: Diego Mello * [FIX] Evaluate values in handle failure (#3235) * [FIX] HEvaluating proper the error for channel, team and undefined * Added some team errors in i18n * Added unauthorized to i18n * Test if there is channel name too, to prevent to show {missing roomName} * Refactor the treatment error to check if exists before translate with i18n * Remove some check conditional points * Minor tweak * Added array with error inside the createChannel * Moved error array to inside the handleFailure * added creating_discussion Co-authored-by: Gerzon Z Co-authored-by: Gerzon Z Co-authored-by: Diego Mello * [FIX] E2E Encryption button doesn't appear (#3343) Co-authored-by: Diego Mello * Regression: Orientation lock on Android not working (#3345) * Update MainApplication.java * Update MainApplication.java * Downgrade react-native-orientation-locker * Pods Co-authored-by: Diego Mello * [FIX] TextInput breaking line (#2873) * Update TextInput's padding * Chante textAlign to auto and to ellipses longer text than the width * Added story with changes in text input * Changed in TextInput stories Co-authored-by: Reinaldo Neto <47038980+reinaldonetof@users.noreply.github.com> Co-authored-by: Reinaldo Neto Co-authored-by: Diego Mello * Chore: Update Jitsi to 3.6.0 (#3292) * Fix RN deps * Update react-native-jitsi-meet * Working on iOS from Jitsi source * Dependencies installed * Temp android * Kinda working android * Working on iOS with our SDK * Use our maven repo * Fix temp maven url * Cleanup * Fix maven url * Bring chat back * Add activity indicator * Update react-native-jitsi-meet * Fix loading on iOS * Clear gradle cache * Try 3.6.0 * Dummy change to update gradle cache * Point to merged forks * update pod commit * Bump version to 4.20.0 (#3366) * Chore: Start Typescript migration (#3279) * [IMPROVE] Show full image when available (#3370) Co-authored-by: Gerzon Z * [FIX] Black screen on share extension if lock screen is enabled (#3320) * Resolve issue causing black screen when sharing * Add logEvent to error in local authenticate * minor tweak * Revert changes Co-authored-by: Reinaldo Neto Co-authored-by: Reinaldo Neto <47038980+reinaldonetof@users.noreply.github.com> Co-authored-by: Diego Mello * Fix: lint-staged not working properly(#3382) * Chore: Remove CocoaPods folder (#3381) * Chore: Migrate AdminPanelView to Typescript (#3377) Co-authored-by: Diego Mello * Chore: Migrate AutoTranslateView to Typescript (#3380) * [improve] - migrate the view: AutoTranslateView to typescript * TODO -> TODO: Co-authored-by: Diego Mello * Fix: @rocketchat/sdk not fetching correct commit (#3384) Co-authored-by: AlexAlexandre * Chore: Migrate CreateDiscussionView to Typescript (#3378) * [improve] - migrate the view: CreateDiscussionView to typescript * minor changes Co-authored-by: Diego Mello * Chore: Migrate DirectoryView to Typescript (#3379) * [improve] - migrate the view: DirectoryView to typescript * [improve] - migrate the view: removing unnecessary variables * minor changes Co-authored-by: Diego Mello * [IMPROVE] Fetch members from API endpoint (#3351) Co-authored-by: Diego Mello * Language update from LingoHub 🤖 (#3374) Project Name: Rocket.Chat.ReactNative Project Link: https://translate.lingohub.com/rocketchat/dashboard/rocket-dot-chat-dot-reactnative User: Robot LingoHub Easy language translations with LingoHub 🚀 Co-authored-by: Robot LingoHub Co-authored-by: Diego Mello * [IMPROVE] Voice messages improvements (#3385) Co-authored-by: Diego Mello Co-authored-by: Marco Jacotec * [NEW] Canned responses (#3355) Co-authored-by: Diego Mello * [FIX] Preserve voice message if recording is interrupted (#3397) * https://github.com/RocketChat/Rocket.Chat.ReactNative/pull/3388/commits/7c259096713a73e4e39467b45298222ca16a9d5e * Minor changes Co-authored-by: Marco Jakobs * [IMPROVE] Onboarding changes (#3387) - Change the first screen of the app - Minor changes on NewServerView and make it the first screen of the app - Add "Create workspace" to ServerDropdown Co-authored-by: Diego Mello * Chore: Point to new white label URL (#3402) * [FIX] Canned Responses minor fixes (#3400) * fix onChangeText usedCanned on tablet * removed refreshControl Co-authored-by: Diego Mello * [FIX] Room Actions buttons not showing after taking a channel from Omnichannel Queue (#3399) Co-authored-by: Diego Mello * [FIX] Fetch members on RoomMembersView (#3403) * [FIX] Fetch members on RoomMembersView * needed to add a conditional to the response * result back properly from rocketchat lib Co-authored-by: Diego Mello Co-authored-by: Daniel Maike Co-authored-by: Vitor Leal Co-authored-by: Fernando Aguilar Co-authored-by: Djorkaeff Alexandre Co-authored-by: youssef-md Co-authored-by: Abdullah Alhamoud <10301923+abalhamoud@users.noreply.github.com> Co-authored-by: David-Tsui Co-authored-by: Dave Koo Co-authored-by: Graham Smith Co-authored-by: Fazil Boudjelal Co-authored-by: Lucas Dousse Co-authored-by: Sumukha Hegde Co-authored-by: Gerzon Z Co-authored-by: Gerzon Z Co-authored-by: phriedrich Co-authored-by: yash-rajpal <58601732+yash-rajpal@users.noreply.github.com> Co-authored-by: Hakan YILMAZ Co-authored-by: Vincenzo Esposito Co-authored-by: Arkadyuti Bandyopadhyay Co-authored-by: Anant Bhasin <38764067+aKn1ghtOut@users.noreply.github.com> Co-authored-by: Gung Wah <41157464+kresnaputra@users.noreply.github.com> Co-authored-by: Billy Newman Co-authored-by: Jan Garaj Co-authored-by: ankar84 Co-authored-by: sadegh Co-authored-by: Noach Magedman Co-authored-by: lingohub[bot] <69908207+lingohub[bot]@users.noreply.github.com> Co-authored-by: Robot LingoHub Co-authored-by: Reinaldo Neto <47038980+reinaldonetof@users.noreply.github.com> Co-authored-by: Levy Costa Co-authored-by: Reinaldo Neto Co-authored-by: Alex Junior Co-authored-by: Diego Sampaio Co-authored-by: Chris Price <56982873+cprice-kgi@users.noreply.github.com> Co-authored-by: Marco Jacotec Co-authored-by: Debdut Chakraborty --- .circleci/config.yml | 4 +- .eslintrc.js | 381 +- .prettierignore | 25 + .prettierrc.js | 10 + CONTRIBUTING.md | 12 + README.md | 2 +- .../__snapshots__/Storyshots.test.js.snap | 703 ++- android/app/build.gradle | 4 +- app.json | 8 +- app/{AppContainer.js => AppContainer.tsx} | 60 +- app/ReactotronConfig.js | 25 +- app/actions/actionsTypes.js | 36 +- app/actions/app.js | 1 - app/actions/inviteLinks.js | 1 - app/actions/rooms.js | 1 - app/commands.js | 15 +- app/constants/{colors.js => colors.ts} | 4 +- .../{environment.js => environment.ts} | 0 app/constants/{links.js => links.ts} | 8 +- ...thentication.js => localAuthentication.ts} | 0 ...{messageTypeLoad.js => messageTypeLoad.ts} | 0 .../{messagesStatus.js => messagesStatus.ts} | 0 app/constants/{settings.js => settings.ts} | 3 + app/constants/{tablet.js => tablet.ts} | 0 app/containers/ActionSheet/ActionSheet.js | 208 - app/containers/ActionSheet/ActionSheet.tsx | 194 + .../ActionSheet/{Button.js => Button.ts} | 0 .../ActionSheet/{Handle.js => Handle.tsx} | 6 +- .../ActionSheet/{Item.js => Item.tsx} | 42 +- app/containers/ActionSheet/Provider.js | 45 - app/containers/ActionSheet/Provider.tsx | 45 + .../ActionSheet/{index.js => index.ts} | 0 .../ActionSheet/{styles.js => styles.ts} | 0 app/containers/ActivityIndicator.js | 40 - app/containers/ActivityIndicator.tsx | 34 + .../{AppVersion.js => AppVersion.tsx} | 14 +- app/containers/Avatar/Avatar.js | 130 - app/containers/Avatar/Avatar.tsx | 96 + app/containers/Avatar/{index.js => index.tsx} | 43 +- app/containers/Avatar/interfaces.ts | 23 + .../BackgroundContainer/index.stories.js | 38 +- .../{index.js => index.tsx} | 22 +- app/containers/Button/index.js | 94 - app/containers/Button/index.tsx | 84 + app/containers/{Check.js => Check.tsx} | 14 +- app/containers/EmojiPicker/CustomEmoji.js | 26 - app/containers/EmojiPicker/CustomEmoji.tsx | 24 + .../{EmojiCategory.js => EmojiCategory.tsx} | 40 +- app/containers/EmojiPicker/TabBar.js | 49 - app/containers/EmojiPicker/TabBar.tsx | 50 + .../{categories.js => categories.ts} | 0 .../EmojiPicker/{index.js => index.tsx} | 99 +- app/containers/EmojiPicker/interfaces.ts | 22 + .../EmojiPicker/{styles.js => styles.ts} | 0 .../{FormContainer.js => FormContainer.tsx} | 35 +- app/containers/Header/{index.js => index.tsx} | 37 +- app/containers/HeaderButton/Common.js | 84 - app/containers/HeaderButton/Common.tsx | 60 + ...Container.js => HeaderButtonContainer.tsx} | 23 +- ...aderButtonItem.js => HeaderButtonItem.tsx} | 40 +- ...ItemBadge.js => HeaderButtonItemBadge.tsx} | 8 +- .../HeaderButton/{index.js => index.ts} | 0 ...fierComponent.js => NotifierComponent.tsx} | 69 +- app/containers/InAppNotification/index.js | 57 - app/containers/InAppNotification/index.tsx | 54 + .../{ListContainer.js => ListContainer.tsx} | 15 +- .../List/{ListHeader.js => ListHeader.tsx} | 25 +- app/containers/List/ListIcon.js | 44 - app/containers/List/ListIcon.tsx | 32 + .../List/{ListInfo.js => ListInfo.tsx} | 21 +- app/containers/List/ListItem.js | 163 - app/containers/List/ListItem.tsx | 154 + .../List/{ListSection.js => ListSection.tsx} | 18 +- app/containers/List/ListSeparator.js | 32 - app/containers/List/ListSeparator.tsx | 24 + .../List/{constants.js => constants.ts} | 0 app/containers/List/{index.js => index.ts} | 0 app/containers/List/{styles.js => styles.ts} | 0 app/containers/{Loading.js => Loading.tsx} | 98 +- .../{LoginServices.js => LoginServices.tsx} | 204 +- .../MessageActions/{Header.js => Header.tsx} | 89 +- app/containers/MessageActions/index.js | 472 -- app/containers/MessageActions/index.tsx | 487 ++ .../CommandsPreview/{Item.js => Item.tsx} | 46 +- .../MessageBox/CommandsPreview/index.js | 46 - .../MessageBox/CommandsPreview/index.tsx | 48 + app/containers/MessageBox/Context.js | 4 - app/containers/MessageBox/Context.ts | 5 + .../{EmojiKeyboard.js => EmojiKeyboard.tsx} | 21 +- .../MessageBox/LeftButtons.android.js | 31 - .../MessageBox/LeftButtons.android.tsx | 23 + app/containers/MessageBox/LeftButtons.ios.js | 28 - app/containers/MessageBox/LeftButtons.ios.tsx | 27 + ...xedMentionItem.js => FixedMentionItem.tsx} | 22 +- .../{MentionEmoji.js => MentionEmoji.tsx} | 21 +- .../MessageBox/Mentions/MentionHeaderList.js | 49 + .../{MentionItem.js => MentionItem.tsx} | 68 +- app/containers/MessageBox/Mentions/index.js | 45 - app/containers/MessageBox/Mentions/index.tsx | 55 + .../{RecordAudio.js => RecordAudio.tsx} | 141 +- app/containers/MessageBox/ReplyPreview.js | 97 - app/containers/MessageBox/ReplyPreview.tsx | 112 + .../MessageBox/RightButtons.android.js | 29 - .../MessageBox/RightButtons.android.tsx | 28 + app/containers/MessageBox/RightButtons.ios.js | 19 - .../MessageBox/RightButtons.ios.tsx | 18 + .../MessageBox/buttons/ActionsButton.js | 21 - .../MessageBox/buttons/ActionsButton.tsx | 14 + .../buttons/{BaseButton.js => BaseButton.tsx} | 29 +- ...itingButton.js => CancelEditingButton.tsx} | 13 +- .../buttons/{SendButton.js => SendButton.tsx} | 13 +- ...leEmojiButton.js => ToggleEmojiButton.tsx} | 19 +- .../MessageBox/buttons/{index.js => index.ts} | 7 +- .../MessageBox/{constants.js => constants.ts} | 1 + .../MessageBox/{index.js => index.tsx} | 551 +- .../MessageBox/{styles.js => styles.ts} | 39 +- ...rrorActions.js => MessageErrorActions.tsx} | 24 +- .../{OrSeparator.js => OrSeparator.tsx} | 15 +- app/containers/Passcode/Base/Button.js | 47 - app/containers/Passcode/Base/Button.tsx | 37 + .../Passcode/Base/{Dots.js => Dots.tsx} | 19 +- .../Base/{LockIcon.js => LockIcon.tsx} | 7 +- .../Passcode/Base/{Locked.js => Locked.tsx} | 37 +- .../Base/{Subtitle.js => Subtitle.tsx} | 15 +- .../Passcode/Base/{Title.js => Title.tsx} | 15 +- app/containers/Passcode/Base/index.js | 139 - app/containers/Passcode/Base/index.tsx | 143 + .../Passcode/Base/{styles.js => styles.ts} | 0 .../{PasscodeChoose.js => PasscodeChoose.tsx} | 27 +- .../{PasscodeEnter.js => PasscodeEnter.tsx} | 38 +- .../Passcode/{constants.js => constants.ts} | 2 +- .../Passcode/{index.js => index.ts} | 0 .../Passcode/{utils.js => utils.ts} | 6 +- .../{ReactionsModal.js => ReactionsModal.tsx} | 109 +- app/containers/RoomHeader/RoomHeader.js | 207 - .../RoomHeader/RoomHeader.stories.js | 26 +- app/containers/RoomHeader/RoomHeader.tsx | 197 + .../RoomHeader/{index.js => index.tsx} | 58 +- .../{RoomTypeIcon.js => RoomTypeIcon.tsx} | 46 +- .../{SafeAreaView.js => SafeAreaView.tsx} | 25 +- .../{SearchBox.js => SearchBox.tsx} | 41 +- .../SearchHeader.js | 12 +- .../Status/{Status.js => Status.tsx} | 35 +- app/containers/Status/index.js | 19 - app/containers/Status/index.tsx | 20 + .../{StatusBar.js => StatusBar.tsx} | 15 +- app/containers/TextInput.stories.js | 16 +- .../{TextInput.js => TextInput.tsx} | 86 +- .../{ThreadDetails.js => ThreadDetails.tsx} | 47 +- app/containers/{Toast.js => Toast.tsx} | 22 +- .../TwoFactor/{index.js => index.tsx} | 44 +- .../TwoFactor/{styles.js => styles.ts} | 0 app/containers/UIKit/Actions.js | 31 - app/containers/UIKit/Actions.tsx | 29 + .../UIKit/{Context.js => Context.tsx} | 7 +- .../UIKit/{DatePicker.js => DatePicker.tsx} | 76 +- .../UIKit/{Divider.js => Divider.tsx} | 0 app/containers/UIKit/Image.js | 61 - app/containers/UIKit/Image.tsx | 66 + app/containers/UIKit/{Input.js => Input.tsx} | 29 +- app/containers/UIKit/MessageBlock.js | 27 - app/containers/UIKit/MessageBlock.tsx | 22 + .../UIKit/MultiSelect/{Chips.js => Chips.tsx} | 54 +- .../UIKit/MultiSelect/{Input.js => Input.tsx} | 42 +- .../UIKit/MultiSelect/{Items.js => Items.tsx} | 56 +- app/containers/UIKit/MultiSelect/index.js | 205 - app/containers/UIKit/MultiSelect/index.tsx | 207 + .../MultiSelect/{styles.js => styles.ts} | 2 +- .../UIKit/{Overflow.js => Overflow.tsx} | 83 +- app/containers/UIKit/Section.js | 65 - app/containers/UIKit/Section.tsx | 65 + .../UIKit/{Select.js => Select.tsx} | 51 +- app/containers/UIKit/{index.js => index.tsx} | 137 +- app/containers/UIKit/utils.js | 63 - app/containers/UIKit/utils.ts | 73 + .../markdown/{AtMention.js => AtMention.tsx} | 45 +- .../{BlockQuote.js => BlockQuote.tsx} | 18 +- app/containers/markdown/Emoji.js | 51 - app/containers/markdown/Emoji.tsx | 42 + .../markdown/{Hashtag.js => Hashtag.tsx} | 39 +- app/containers/markdown/{Link.js => Link.tsx} | 29 +- app/containers/markdown/List.js | 46 - app/containers/markdown/List.tsx | 38 + app/containers/markdown/ListItem.js | 63 - app/containers/markdown/ListItem.tsx | 52 + .../markdown/{Table.js => Table.tsx} | 38 +- .../markdown/{TableCell.js => TableCell.tsx} | 24 +- .../markdown/{TableRow.js => TableRow.tsx} | 20 +- .../markdown/{index.js => index.tsx} | 291 +- .../{mergeTextNodes.js => mergeTextNodes.ts} | 4 +- .../markdown/{styles.js => styles.ts} | 4 +- app/containers/message/Attachments.js | 76 - app/containers/message/Attachments.tsx | 72 + .../message/{Audio.js => Audio.tsx} | 122 +- .../message/{Blocks.js => Blocks.ts} | 20 +- .../message/{Broadcast.js => Broadcast.tsx} | 16 +- .../message/{CallButton.js => CallButton.tsx} | 15 +- app/containers/message/Content.js | 140 - app/containers/message/Content.tsx | 118 + app/containers/message/Context.js | 4 - app/containers/message/Context.ts | 5 + app/containers/message/Discussion.js | 68 - app/containers/message/Discussion.tsx | 62 + app/containers/message/Emoji.js | 28 - app/containers/message/Emoji.tsx | 22 + .../message/{Encrypted.js => Encrypted.tsx} | 12 +- app/containers/message/Image.js | 93 - app/containers/message/Image.tsx | 94 + .../message/{Message.js => Message.tsx} | 65 +- app/containers/message/MessageAvatar.js | 46 - app/containers/message/MessageAvatar.tsx | 36 + app/containers/message/MessageError.js | 29 - app/containers/message/MessageError.tsx | 32 + .../message/{Reactions.js => Reactions.tsx} | 77 +- .../{ReadReceipt.js => ReadReceipt.tsx} | 15 +- .../{RepliedThread.js => RepliedThread.tsx} | 26 +- app/containers/message/Reply.js | 274 - app/containers/message/Reply.tsx | 284 + app/containers/message/Thread.js | 63 - app/containers/message/Thread.tsx | 51 + .../message/{Touchable.js => Touchable.tsx} | 15 +- app/containers/message/Urls.js | 152 - app/containers/message/Urls.tsx | 169 + app/containers/message/User.js | 111 - app/containers/message/User.tsx | 110 + app/containers/message/Video.js | 69 - app/containers/message/Video.tsx | 75 + .../message/{constants.js => constants.ts} | 0 .../message/{index.js => index.tsx} | 203 +- app/containers/message/interfaces.ts | 147 + .../message/{styles.js => styles.ts} | 2 +- app/containers/message/{utils.js => utils.ts} | 91 +- app/dimensions.js | 26 - app/dimensions.tsx | 42 + .../containers/OmnichannelStatus.js | 29 +- app/ee/omnichannel/lib/index.js | 8 +- .../omnichannel/lib/subscriptions/inquiry.js | 15 +- app/ee/omnichannel/reducers/inquiry.js | 2 +- app/ee/omnichannel/sagas/inquiry.js | 6 +- app/ee/omnichannel/selectors/inquiry.js | 5 +- app/ee/omnichannel/views/QueueListView.js | 24 +- app/{emojis.js => emojis.ts} | 2 +- app/externalModules.d.ts | 11 + app/i18n/index.js | 41 +- app/i18n/isRTL.js | 14 +- app/i18n/locales/ar.json | 1310 ++--- app/i18n/locales/de.json | 1546 +++--- app/i18n/locales/en.json | 1559 +++--- app/i18n/locales/es-ES.json | 906 +-- app/i18n/locales/fr.json | 1546 +++--- app/i18n/locales/it.json | 1402 ++--- app/i18n/locales/ja.json | 982 ++-- app/i18n/locales/nl.json | 1546 +++--- app/i18n/locales/pt-BR.json | 1358 ++--- app/i18n/locales/pt-PT.json | 1040 ++-- app/i18n/locales/ru.json | 1546 +++--- app/i18n/locales/tr.json | 1402 ++--- app/i18n/locales/zh-CN.json | 1360 ++--- app/i18n/locales/zh-TW.json | 1366 ++--- app/{index.js => index.tsx} | 135 +- app/lib/Icons.js | 6 +- app/lib/Navigation.js | 25 - app/lib/Navigation.ts | 25 + app/lib/ShareNavigation.js | 9 - app/lib/ShareNavigation.ts | 10 + app/lib/appStateMiddleware.js | 52 +- app/lib/createStore.js | 7 +- app/lib/database/index.js | 14 +- app/lib/database/model/CustomEmoji.js | 2 +- app/lib/database/model/Message.js | 6 +- app/lib/database/model/Permission.js | 2 +- app/lib/database/model/ServersHistory.js | 4 +- app/lib/database/model/Setting.js | 2 +- app/lib/database/model/SlashCommand.js | 10 +- app/lib/database/model/Subscription.js | 7 +- app/lib/database/model/Thread.js | 6 +- app/lib/database/model/ThreadMessage.js | 6 +- app/lib/database/model/Upload.js | 2 +- app/lib/database/model/migrations.js | 70 +- app/lib/database/model/servers/Server.js | 2 +- app/lib/database/model/servers/migrations.js | 29 +- app/lib/database/schema/app.js | 4 +- app/lib/database/services/Message.js | 2 +- app/lib/database/services/Subscription.js | 2 +- app/lib/database/services/Thread.js | 2 +- app/lib/database/services/ThreadMessage.js | 2 +- app/lib/database/utils.test.js | 3 +- app/lib/encryption/encryption.js | 233 +- app/lib/encryption/room.js | 100 +- app/lib/encryption/utils.js | 12 +- app/lib/methods/actions.js | 13 +- app/lib/methods/callJitsi.js | 12 +- app/lib/methods/canOpenRoom.js | 10 +- app/lib/methods/enterpriseModules.js | 9 +- app/lib/methods/getCustomEmojis.js | 36 +- app/lib/methods/getPermissions.js | 47 +- app/lib/methods/getRoles.js | 45 +- app/lib/methods/getRoomInfo.js | 2 +- app/lib/methods/getRooms.js | 2 +- app/lib/methods/getSettings.js | 56 +- app/lib/methods/getSingleMessage.js | 21 +- app/lib/methods/getSlashCommands.js | 39 +- app/lib/methods/getThreadName.js | 14 +- app/lib/methods/getUsersPresence.js | 10 +- app/lib/methods/helpers/buildMessage.js | 4 +- .../methods/helpers/findSubscriptionsRooms.js | 2 +- .../methods/helpers/getFileUrlFromMessage.js | 2 +- .../helpers/mergeSubscriptionsRooms.js | 12 +- app/lib/methods/helpers/normalizeMessage.js | 16 +- app/lib/methods/helpers/parseQuery.js | 14 +- app/lib/methods/helpers/parseUrls.js | 45 +- app/lib/methods/helpers/protectedFunction.js | 15 +- app/lib/methods/loadMessagesForRoom.js | 6 +- app/lib/methods/loadMissedMessages.js | 6 +- app/lib/methods/loadNextMessages.js | 4 +- app/lib/methods/loadSurroundingMessages.js | 4 +- app/lib/methods/loadThreadMessages.js | 45 +- app/lib/methods/logout.js | 24 +- app/lib/methods/readMessages.js | 4 +- app/lib/methods/sendFileMessage.js | 33 +- app/lib/methods/sendMessage.js | 32 +- app/lib/methods/subscriptions/room.js | 111 +- app/lib/methods/subscriptions/rooms.js | 63 +- app/lib/methods/updateMessages.js | 102 +- app/lib/rocketchat.js | 625 ++- app/lib/selection.json | 4875 ++++++++++++++++- app/lib/utils.js | 13 +- app/notifications/push/index.js | 16 +- app/notifications/push/push.android.js | 6 +- app/notifications/push/push.ios.js | 14 +- app/presentation/DirectoryItem/index.js | 68 - app/presentation/DirectoryItem/index.tsx | 72 + .../DirectoryItem/{styles.js => styles.ts} | 0 .../{ImageComponent.js => ImageComponent.ts} | 2 +- ...wer.android.js => ImageViewer.android.tsx} | 220 +- ...ImageViewer.ios.js => ImageViewer.ios.tsx} | 38 +- .../ImageViewer/{index.js => index.ts} | 2 + .../ImageViewer/{types.js => types.ts} | 0 app/presentation/KeyboardView.js | 37 - app/presentation/KeyboardView.tsx | 32 + .../RoomItem/{Actions.js => Actions.tsx} | 66 +- .../{LastMessage.js => LastMessage.tsx} | 80 +- app/presentation/RoomItem/RoomItem.js | 206 - app/presentation/RoomItem/RoomItem.tsx | 163 + app/presentation/RoomItem/{Tag.js => Tag.tsx} | 23 +- app/presentation/RoomItem/Title.js | 31 - app/presentation/RoomItem/Title.tsx | 23 + app/presentation/RoomItem/Touchable.js | 284 - app/presentation/RoomItem/Touchable.tsx | 278 + app/presentation/RoomItem/TypeIcon.js | 18 - app/presentation/RoomItem/TypeIcon.tsx | 18 + app/presentation/RoomItem/UpdatedAt.js | 48 - app/presentation/RoomItem/UpdatedAt.tsx | 41 + app/presentation/RoomItem/Wrapper.js | 52 - app/presentation/RoomItem/Wrapper.tsx | 33 + .../RoomItem/{index.js => index.tsx} | 104 +- .../RoomItem/{styles.js => styles.ts} | 4 +- app/presentation/ServerItem/index.js | 69 - app/presentation/ServerItem/index.tsx | 67 + .../ServerItem/{styles.js => styles.ts} | 0 .../{TextInput.js => TextInput.tsx} | 19 +- .../UnreadBadge/getUnreadStyle.test.js | 74 +- .../{getUnreadStyle.js => getUnreadStyle.ts} | 9 +- app/presentation/UnreadBadge/index.js | 93 - app/presentation/UnreadBadge/index.tsx | 91 + .../{UserItem.js => UserItem.tsx} | 49 +- app/reducers/createChannel.js | 2 +- app/reducers/createDiscussion.js | 2 +- app/reducers/index.js | 4 +- app/reducers/room.js | 5 +- app/reducers/selectedUsers.js | 2 +- app/reducers/server.js | 4 - app/reducers/sortPreferences.js | 1 - app/sagas/createChannel.js | 42 +- app/sagas/createDiscussion.js | 12 +- app/sagas/deepLinking.js | 23 +- app/sagas/encryption.js | 16 +- app/sagas/index.js | 4 +- app/sagas/init.js | 6 +- app/sagas/inviteLinks.js | 10 +- app/sagas/login.js | 60 +- app/sagas/messages.js | 2 +- app/sagas/room.js | 26 +- app/sagas/rooms.js | 50 +- app/sagas/selectServer.js | 27 +- app/sagas/state.js | 2 +- app/selectors/login.js | 7 +- app/{share.js => share.tsx} | 148 +- app/stacks/InsideStack.js | 214 +- .../MasterDetailStack/ModalContainer.js | 8 +- app/stacks/MasterDetailStack/index.js | 177 +- app/stacks/OutsideStack.js | 62 +- app/theme.js | 16 - app/theme.tsx | 23 + app/utils/avatar.js | 27 +- app/utils/base64-js/base64-js.test.js | 22 +- app/utils/base64-js/index.js | 73 +- app/utils/debounce.js | 8 +- app/utils/events.js | 2 +- app/utils/fetch.js | 9 +- app/utils/fileUpload/index.android.js | 4 +- app/utils/fileUpload/index.ios.js | 20 +- app/utils/goRoom.js | 2 +- app/utils/info.js | 11 +- app/utils/isReadOnly.js | 4 +- app/utils/isValidEmail.js | 3 +- app/utils/layoutAnimation.js | 40 +- app/utils/localAuthentication.js | 73 +- app/utils/log/events.js | 7 +- app/utils/log/index.js | 22 +- app/utils/messageTypes.js | 36 +- app/utils/navigation/animations.js | 15 +- app/utils/navigation/{index.js => index.ts} | 13 +- app/utils/openLink.js | 6 +- app/utils/review.js | 35 +- app/utils/room.js | 36 +- app/utils/scaling.js | 11 +- app/utils/server.js | 4 +- app/utils/shortnameToUnicode/ascii.js | 121 +- app/utils/shortnameToUnicode/emojis.js | 4630 +++++++++++++++- app/utils/shortnameToUnicode/index.js | 16 +- .../shortnameToUnicode.test.js | 4 +- app/utils/sslPinning.js | 71 +- app/utils/theme.js | 6 +- app/utils/touch.js | 9 +- app/utils/twoFactor.js | 31 +- app/utils/url.js | 17 +- app/views/AddChannelTeamView.js | 16 +- app/views/AddExistingChannelView.js | 74 +- .../AdminPanelView/{index.js => index.tsx} | 29 +- app/views/AttachmentView.js | 55 +- app/views/AuthLoadingView.js | 7 +- app/views/AuthenticationWebView.js | 40 +- .../AutoTranslateView/{index.js => index.tsx} | 96 +- app/views/CannedResponseDetail.js | 171 + .../CannedResponseItem.js | 61 + .../CannedResponseItem.stories.js | 64 + .../Dropdown/DropdownItem.js | 44 + .../Dropdown/DropdownItemFilter.js | 20 + .../Dropdown/DropdownItemHeader.js | 15 + .../CannedResponsesListView/Dropdown/index.js | 106 + app/views/CannedResponsesListView/index.js | 363 ++ app/views/CannedResponsesListView/styles.js | 73 + app/views/ChangePasscodeView.js | 29 +- app/views/CreateChannelView.js | 95 +- .../{SelectChannel.js => SelectChannel.tsx} | 51 +- .../{SelectUsers.js => SelectUsers.tsx} | 71 +- .../{index.js => index.tsx} | 139 +- app/views/CreateDiscussionView/interfaces.ts | 55 + .../{styles.js => styles.ts} | 0 app/views/DefaultBrowserView.js | 28 +- app/views/DirectoryView/Options.js | 132 - app/views/DirectoryView/Options.tsx | 131 + .../DirectoryView/{index.js => index.tsx} | 173 +- .../DirectoryView/{styles.js => styles.ts} | 1 + app/views/E2EEncryptionSecurityView.js | 34 +- app/views/E2EEnterYourPasswordView.js | 28 +- app/views/E2EHowItWorksView.js | 33 +- app/views/E2ESaveYourPasswordView.js | 41 +- app/views/ForgotPasswordView.js | 22 +- app/views/ForwardLivechatView.js | 29 +- app/views/InviteUsersEditView/index.js | 118 +- app/views/InviteUsersEditView/styles.js | 2 +- app/views/InviteUsersView/index.js | 55 +- app/views/InviteUsersView/styles.js | 1 + app/views/JitsiMeetView.js | 22 +- app/views/LanguageView/index.js | 32 +- app/views/LegalView.js | 6 +- app/views/LivechatEditView.js | 89 +- app/views/LoginView.js | 52 +- app/views/MarkdownTableView.js | 8 +- app/views/MessagesView/index.js | 135 +- app/views/ModalBlockView.js | 108 +- app/views/NewMessageView.js | 100 +- app/views/NewServerView/ServerInput/Item.js | 20 +- app/views/NewServerView/ServerInput/index.js | 47 +- app/views/NewServerView/index.js | 248 +- .../NotificationPreferencesView/index.js | 65 +- .../NotificationPreferencesView/options.js | 203 +- app/views/OnboardingView/index.js | 89 - app/views/OnboardingView/styles.js | 41 - app/views/PickerView.js | 42 +- app/views/ProfileView/index.js | 200 +- app/views/ReadReceiptView/index.js | 73 +- app/views/ReadReceiptView/styles.js | 1 + app/views/RegisterView.js | 125 +- app/views/RoomActionsView/index.js | 892 +-- app/views/RoomActionsView/styles.js | 8 +- app/views/RoomInfoEditView/SwitchContainer.js | 85 +- app/views/RoomInfoEditView/index.js | 377 +- app/views/RoomInfoView/Channel.js | 6 +- app/views/RoomInfoView/CustomFields.js | 20 +- app/views/RoomInfoView/Direct.js | 29 +- app/views/RoomInfoView/Item.js | 21 +- app/views/RoomInfoView/Livechat.js | 106 +- app/views/RoomInfoView/Timezone.js | 15 +- app/views/RoomInfoView/index.js | 225 +- app/views/RoomMembersView/index.js | 242 +- app/views/RoomView/Banner.js | 92 +- app/views/RoomView/EmptyRoom.js | 11 +- app/views/RoomView/JoinCode.js | 141 +- app/views/RoomView/LeftButtons.js | 54 +- app/views/RoomView/List/NavBottomFAB.js | 25 +- app/views/RoomView/List/index.js | 202 +- .../RoomView/LoadMore/LoadMore.stories.js | 17 +- app/views/RoomView/LoadMore/index.js | 23 +- app/views/RoomView/ReactionPicker.js | 67 +- app/views/RoomView/RightButtons.js | 84 +- app/views/RoomView/Separator.js | 2 +- app/views/RoomView/UploadProgress.js | 108 +- app/views/RoomView/index.js | 477 +- app/views/RoomView/services/getMessageInfo.js | 2 +- app/views/RoomView/services/getMessages.js | 2 +- .../RoomView/services/getMoreMessages.js | 20 +- app/views/RoomsListView/Header/Header.js | 121 +- app/views/RoomsListView/Header/index.js | 27 +- app/views/RoomsListView/ListHeader/index.js | 70 +- app/views/RoomsListView/ServerDropdown.js | 151 +- app/views/RoomsListView/SortDropdown/index.js | 78 +- app/views/RoomsListView/index.js | 290 +- app/views/RoomsListView/styles.js | 5 + app/views/ScreenLockConfigView.js | 129 +- app/views/ScreenLockedView.js | 9 +- app/views/SearchMessagesView/index.js | 44 +- app/views/SecurityPrivacyView.js | 52 +- app/views/SelectListView.js | 52 +- app/views/SelectServerView.js | 26 +- app/views/SelectedUsersView.js | 66 +- app/views/SetUsernameView.js | 36 +- app/views/SettingsView/index.js | 102 +- .../ShareListView/Header/Header.android.js | 2 +- app/views/ShareListView/Header/Header.ios.js | 23 +- app/views/ShareListView/Header/index.js | 6 +- app/views/ShareListView/index.js | 141 +- app/views/ShareListView/styles.js | 1 + app/views/ShareView/Header.js | 17 +- app/views/ShareView/Preview.js | 25 +- app/views/ShareView/Thumbs.js | 62 +- app/views/ShareView/index.js | 174 +- app/views/SidebarView/SidebarItem.js | 19 +- app/views/SidebarView/index.js | 91 +- app/views/StatusView.js | 73 +- app/views/Styles.js | 2 +- app/views/TeamChannelsView.js | 216 +- app/views/ThemeView.js | 37 +- .../Dropdown/DropdownItem.js | 6 +- .../Dropdown/DropdownItemFilter.js | 8 +- .../Dropdown/DropdownItemHeader.js | 2 +- .../ThreadMessagesView/Dropdown/index.js | 60 +- app/views/ThreadMessagesView/Item.js | 34 +- app/views/ThreadMessagesView/Item.stories.js | 25 +- app/views/ThreadMessagesView/index.js | 227 +- .../UserNotificationPreferencesView/index.js | 110 +- .../options.js | 42 +- app/views/UserPreferencesView/index.js | 10 +- app/views/VisitorNavigationView.js | 12 +- ...tServersView.js => WithoutServersView.tsx} | 24 +- app/views/WorkspaceView/ServerAvatar.js | 4 +- app/views/WorkspaceView/index.js | 67 +- babel.config.js | 4 +- e2e/.mocharc.json | 10 +- e2e/data.js | 46 +- e2e/data/data.cloud.js | 44 +- e2e/data/data.docker.js | 46 +- e2e/helpers/app.js | 103 +- e2e/helpers/data_setup.js | 71 +- e2e/tests/assorted/01-e2eencryption.spec.js | 287 +- e2e/tests/assorted/02-broadcast.spec.js | 96 +- e2e/tests/assorted/03-profile.spec.js | 72 +- e2e/tests/assorted/04-setting.spec.js | 57 +- e2e/tests/assorted/05-joinpublicroom.spec.js | 86 +- e2e/tests/assorted/06-status.spec.js | 29 +- e2e/tests/assorted/07-changeserver.spec.js | 82 +- .../assorted/08-joinprotectedroom.spec.js | 36 +- .../assorted/09-joinfromdirectory.spec.js | 40 +- e2e/tests/assorted/10-deleteserver.spec.js | 48 +- e2e/tests/assorted/11-deeplinking.spec.js | 82 +- e2e/tests/assorted/12-i18n.spec.js | 76 +- e2e/tests/init.js | 9 +- e2e/tests/onboarding/01-onboarding.spec.js | 49 +- e2e/tests/onboarding/02-legal.spec.js | 32 +- .../onboarding/03-forgotpassword.spec.js | 22 +- e2e/tests/onboarding/04-createuser.spec.js | 32 +- e2e/tests/onboarding/05-login.spec.js | 40 +- e2e/tests/onboarding/06-roomslist.spec.js | 34 +- .../onboarding/07-server-history.spec.js | 47 +- e2e/tests/room/01-createroom.spec.js | 204 +- e2e/tests/room/02-room.spec.js | 260 +- e2e/tests/room/03-roomactions.spec.js | 384 +- e2e/tests/room/04-discussion.spec.js | 138 +- e2e/tests/room/05-threads.spec.js | 190 +- e2e/tests/room/06-createdmgroup.spec.js | 42 +- e2e/tests/room/07-markasunread.spec.js | 34 +- e2e/tests/room/08-roominfo.spec.js | 164 +- e2e/tests/room/09-jumptomessage.spec.js | 182 +- e2e/tests/team/01-createteam.spec.js | 68 +- e2e/tests/team/02-team.spec.js | 351 +- e2e/tests/team/03-moveconvert.spec.js | 160 +- index.js | 1 + ios/RocketChatRN.xcodeproj/project.pbxproj | 4 +- ios/RocketChatRN/Info.plist | 3 +- ios/ShareRocketChatRN/Info.plist | 2 +- jsconfig.json | 13 +- metro.config.js | 4 +- package.json | 464 +- storybook/stories/Avatar.js | 123 +- storybook/stories/HeaderButtons.js | 52 +- storybook/stories/List.js | 11 +- storybook/stories/Markdown.js | 120 +- storybook/stories/Message.js | 914 ++- storybook/stories/RoomItem.js | 45 +- storybook/stories/ServerItem.js | 17 +- storybook/stories/UiKitMessage.js | 763 +-- storybook/stories/UiKitModal.js | 957 ++-- storybook/stories/UnreadBadge.js | 12 +- storybook/stories/index.js | 3 +- storybook/utils.js | 3 +- tsconfig.json | 73 + yarn.lock | 582 +- 619 files changed, 39539 insertions(+), 27554 deletions(-) create mode 100644 .prettierignore create mode 100644 .prettierrc.js rename app/{AppContainer.js => AppContainer.tsx} (64%) rename app/constants/{colors.js => colors.ts} (98%) rename app/constants/{environment.js => environment.ts} (100%) rename app/constants/{links.js => links.ts} (66%) rename app/constants/{localAuthentication.js => localAuthentication.ts} (100%) rename app/constants/{messageTypeLoad.js => messageTypeLoad.ts} (100%) rename app/constants/{messagesStatus.js => messagesStatus.ts} (100%) rename app/constants/{settings.js => settings.ts} (98%) rename app/constants/{tablet.js => tablet.ts} (100%) delete mode 100644 app/containers/ActionSheet/ActionSheet.js create mode 100644 app/containers/ActionSheet/ActionSheet.tsx rename app/containers/ActionSheet/{Button.js => Button.ts} (100%) rename app/containers/ActionSheet/{Handle.js => Handle.tsx} (73%) rename app/containers/ActionSheet/{Item.js => Item.tsx} (57%) delete mode 100644 app/containers/ActionSheet/Provider.js create mode 100644 app/containers/ActionSheet/Provider.tsx rename app/containers/ActionSheet/{index.js => index.ts} (100%) rename app/containers/ActionSheet/{styles.js => styles.ts} (100%) delete mode 100644 app/containers/ActivityIndicator.js create mode 100644 app/containers/ActivityIndicator.tsx rename app/containers/{AppVersion.js => AppVersion.tsx} (65%) delete mode 100644 app/containers/Avatar/Avatar.js create mode 100644 app/containers/Avatar/Avatar.tsx rename app/containers/Avatar/{index.js => index.tsx} (73%) create mode 100644 app/containers/Avatar/interfaces.ts rename app/containers/BackgroundContainer/{index.js => index.tsx} (57%) delete mode 100644 app/containers/Button/index.js create mode 100644 app/containers/Button/index.tsx rename app/containers/{Check.js => Check.tsx} (52%) delete mode 100644 app/containers/EmojiPicker/CustomEmoji.js create mode 100644 app/containers/EmojiPicker/CustomEmoji.tsx rename app/containers/EmojiPicker/{EmojiCategory.js => EmojiCategory.tsx} (58%) delete mode 100644 app/containers/EmojiPicker/TabBar.js create mode 100644 app/containers/EmojiPicker/TabBar.tsx rename app/containers/EmojiPicker/{categories.js => categories.ts} (100%) rename app/containers/EmojiPicker/{index.js => index.tsx} (65%) create mode 100644 app/containers/EmojiPicker/interfaces.ts rename app/containers/EmojiPicker/{styles.js => styles.ts} (100%) rename app/containers/{FormContainer.js => FormContainer.tsx} (72%) rename app/containers/Header/{index.js => index.tsx} (73%) delete mode 100644 app/containers/HeaderButton/Common.js create mode 100644 app/containers/HeaderButton/Common.tsx rename app/containers/HeaderButton/{HeaderButtonContainer.js => HeaderButtonContainer.tsx} (55%) rename app/containers/HeaderButton/{HeaderButtonItem.js => HeaderButtonItem.tsx} (56%) rename app/containers/HeaderButton/{HeaderButtonItemBadge.js => HeaderButtonItemBadge.tsx} (75%) rename app/containers/HeaderButton/{index.js => index.ts} (100%) rename app/containers/InAppNotification/{NotifierComponent.js => NotifierComponent.tsx} (72%) delete mode 100644 app/containers/InAppNotification/index.js create mode 100644 app/containers/InAppNotification/index.tsx rename app/containers/List/{ListContainer.js => ListContainer.tsx} (76%) rename app/containers/List/{ListHeader.js => ListHeader.tsx} (60%) delete mode 100644 app/containers/List/ListIcon.js create mode 100644 app/containers/List/ListIcon.tsx rename app/containers/List/{ListInfo.js => ListInfo.tsx} (67%) delete mode 100644 app/containers/List/ListItem.js create mode 100644 app/containers/List/ListItem.tsx rename app/containers/List/{ListSection.js => ListSection.tsx} (66%) delete mode 100644 app/containers/List/ListSeparator.js create mode 100644 app/containers/List/ListSeparator.tsx rename app/containers/List/{constants.js => constants.ts} (100%) rename app/containers/List/{index.js => index.ts} (100%) rename app/containers/List/{styles.js => styles.ts} (100%) rename app/containers/{Loading.js => Loading.tsx} (62%) rename app/containers/{LoginServices.js => LoginServices.tsx} (66%) rename app/containers/MessageActions/{Header.js => Header.tsx} (62%) delete mode 100644 app/containers/MessageActions/index.js create mode 100644 app/containers/MessageActions/index.tsx rename app/containers/MessageBox/CommandsPreview/{Item.js => Item.tsx} (51%) delete mode 100644 app/containers/MessageBox/CommandsPreview/index.js create mode 100644 app/containers/MessageBox/CommandsPreview/index.tsx delete mode 100644 app/containers/MessageBox/Context.js create mode 100644 app/containers/MessageBox/Context.ts rename app/containers/MessageBox/{EmojiKeyboard.js => EmojiKeyboard.tsx} (65%) delete mode 100644 app/containers/MessageBox/LeftButtons.android.js create mode 100644 app/containers/MessageBox/LeftButtons.android.tsx delete mode 100644 app/containers/MessageBox/LeftButtons.ios.js create mode 100644 app/containers/MessageBox/LeftButtons.ios.tsx rename app/containers/MessageBox/Mentions/{FixedMentionItem.js => FixedMentionItem.tsx} (69%) rename app/containers/MessageBox/Mentions/{MentionEmoji.js => MentionEmoji.tsx} (58%) create mode 100644 app/containers/MessageBox/Mentions/MentionHeaderList.js rename app/containers/MessageBox/Mentions/{MentionItem.js => MentionItem.tsx} (53%) delete mode 100644 app/containers/MessageBox/Mentions/index.js create mode 100644 app/containers/MessageBox/Mentions/index.tsx rename app/containers/MessageBox/{RecordAudio.js => RecordAudio.tsx} (59%) delete mode 100644 app/containers/MessageBox/ReplyPreview.js create mode 100644 app/containers/MessageBox/ReplyPreview.tsx delete mode 100644 app/containers/MessageBox/RightButtons.android.js create mode 100644 app/containers/MessageBox/RightButtons.android.tsx delete mode 100644 app/containers/MessageBox/RightButtons.ios.js create mode 100644 app/containers/MessageBox/RightButtons.ios.tsx delete mode 100644 app/containers/MessageBox/buttons/ActionsButton.js create mode 100644 app/containers/MessageBox/buttons/ActionsButton.tsx rename app/containers/MessageBox/buttons/{BaseButton.js => BaseButton.tsx} (54%) rename app/containers/MessageBox/buttons/{CancelEditingButton.js => CancelEditingButton.tsx} (55%) rename app/containers/MessageBox/buttons/{SendButton.js => SendButton.tsx} (64%) rename app/containers/MessageBox/buttons/{ToggleEmojiButton.js => ToggleEmojiButton.tsx} (63%) rename app/containers/MessageBox/buttons/{index.js => index.ts} (70%) rename app/containers/MessageBox/{constants.js => constants.ts} (82%) rename app/containers/MessageBox/{index.js => index.tsx} (70%) rename app/containers/MessageBox/{styles.js => styles.ts} (76%) rename app/containers/{MessageErrorActions.js => MessageErrorActions.tsx} (78%) rename app/containers/{OrSeparator.js => OrSeparator.tsx} (70%) delete mode 100644 app/containers/Passcode/Base/Button.js create mode 100644 app/containers/Passcode/Base/Button.tsx rename app/containers/Passcode/Base/{Dots.js => Dots.tsx} (78%) rename app/containers/Passcode/Base/{LockIcon.js => LockIcon.tsx} (76%) rename app/containers/Passcode/Base/{Locked.js => Locked.tsx} (69%) rename app/containers/Passcode/Base/{Subtitle.js => Subtitle.tsx} (64%) rename app/containers/Passcode/Base/{Title.js => Title.tsx} (64%) delete mode 100644 app/containers/Passcode/Base/index.js create mode 100644 app/containers/Passcode/Base/index.tsx rename app/containers/Passcode/Base/{styles.js => styles.ts} (100%) rename app/containers/Passcode/{PasscodeChoose.js => PasscodeChoose.tsx} (72%) rename app/containers/Passcode/{PasscodeEnter.js => PasscodeEnter.tsx} (75%) rename app/containers/Passcode/{constants.js => constants.ts} (74%) rename app/containers/Passcode/{index.js => index.ts} (100%) rename app/containers/Passcode/{utils.js => utils.ts} (71%) rename app/containers/{ReactionsModal.js => ReactionsModal.tsx} (58%) delete mode 100644 app/containers/RoomHeader/RoomHeader.js create mode 100644 app/containers/RoomHeader/RoomHeader.tsx rename app/containers/RoomHeader/{index.js => index.tsx} (72%) rename app/containers/{RoomTypeIcon.js => RoomTypeIcon.tsx} (62%) rename app/containers/{SafeAreaView.js => SafeAreaView.tsx} (63%) rename app/containers/{SearchBox.js => SearchBox.tsx} (77%) rename app/{views/ThreadMessagesView => containers}/SearchHeader.js (78%) rename app/containers/Status/{Status.js => Status.tsx} (52%) delete mode 100644 app/containers/Status/index.js create mode 100644 app/containers/Status/index.tsx rename app/containers/{StatusBar.js => StatusBar.tsx} (75%) rename app/containers/{TextInput.js => TextInput.tsx} (74%) rename app/containers/{ThreadDetails.js => ThreadDetails.tsx} (76%) rename app/containers/{Toast.js => Toast.tsx} (80%) rename app/containers/TwoFactor/{index.js => index.tsx} (77%) rename app/containers/TwoFactor/{styles.js => styles.ts} (100%) delete mode 100644 app/containers/UIKit/Actions.js create mode 100644 app/containers/UIKit/Actions.tsx rename app/containers/UIKit/{Context.js => Context.tsx} (64%) rename app/containers/UIKit/{DatePicker.js => DatePicker.tsx} (60%) rename app/containers/UIKit/{Divider.js => Divider.tsx} (100%) delete mode 100644 app/containers/UIKit/Image.js create mode 100644 app/containers/UIKit/Image.tsx rename app/containers/UIKit/{Input.js => Input.tsx} (69%) delete mode 100644 app/containers/UIKit/MessageBlock.js create mode 100644 app/containers/UIKit/MessageBlock.tsx rename app/containers/UIKit/MultiSelect/{Chips.js => Chips.tsx} (55%) rename app/containers/UIKit/MultiSelect/{Input.js => Input.tsx} (52%) rename app/containers/UIKit/MultiSelect/{Items.js => Items.tsx} (59%) delete mode 100644 app/containers/UIKit/MultiSelect/index.js create mode 100644 app/containers/UIKit/MultiSelect/index.tsx rename app/containers/UIKit/MultiSelect/{styles.js => styles.ts} (97%) rename app/containers/UIKit/{Overflow.js => Overflow.tsx} (56%) delete mode 100644 app/containers/UIKit/Section.js create mode 100644 app/containers/UIKit/Section.tsx rename app/containers/UIKit/{Select.js => Select.tsx} (68%) rename app/containers/UIKit/{index.js => index.tsx} (56%) delete mode 100644 app/containers/UIKit/utils.js create mode 100644 app/containers/UIKit/utils.ts rename app/containers/markdown/{AtMention.js => AtMention.tsx} (52%) rename app/containers/markdown/{BlockQuote.js => BlockQuote.tsx} (54%) delete mode 100644 app/containers/markdown/Emoji.js create mode 100644 app/containers/markdown/Emoji.tsx rename app/containers/markdown/{Hashtag.js => Hashtag.tsx} (53%) rename app/containers/markdown/{Link.js => Link.tsx} (61%) delete mode 100644 app/containers/markdown/List.js create mode 100644 app/containers/markdown/List.tsx delete mode 100644 app/containers/markdown/ListItem.js create mode 100644 app/containers/markdown/ListItem.tsx rename app/containers/markdown/{Table.js => Table.tsx} (66%) rename app/containers/markdown/{TableCell.js => TableCell.tsx} (68%) rename app/containers/markdown/{TableRow.js => TableRow.tsx} (63%) rename app/containers/markdown/{index.js => index.tsx} (53%) rename app/containers/markdown/{mergeTextNodes.js => mergeTextNodes.ts} (89%) rename app/containers/markdown/{styles.js => styles.ts} (96%) delete mode 100644 app/containers/message/Attachments.js create mode 100644 app/containers/message/Attachments.tsx rename app/containers/message/{Audio.js => Audio.tsx} (74%) rename app/containers/message/{Blocks.js => Blocks.ts} (59%) rename app/containers/message/{Broadcast.js => Broadcast.tsx} (78%) rename app/containers/message/{CallButton.js => CallButton.tsx} (76%) delete mode 100644 app/containers/message/Content.js create mode 100644 app/containers/message/Content.tsx delete mode 100644 app/containers/message/Context.js create mode 100644 app/containers/message/Context.ts delete mode 100644 app/containers/message/Discussion.js create mode 100644 app/containers/message/Discussion.tsx delete mode 100644 app/containers/message/Emoji.js create mode 100644 app/containers/message/Emoji.tsx rename app/containers/message/{Encrypted.js => Encrypted.tsx} (80%) delete mode 100644 app/containers/message/Image.js create mode 100644 app/containers/message/Image.tsx rename app/containers/message/{Message.js => Message.tsx} (67%) delete mode 100644 app/containers/message/MessageAvatar.js create mode 100644 app/containers/message/MessageAvatar.tsx delete mode 100644 app/containers/message/MessageError.js create mode 100644 app/containers/message/MessageError.tsx rename app/containers/message/{Reactions.js => Reactions.tsx} (60%) rename app/containers/message/{ReadReceipt.js => ReadReceipt.tsx} (74%) rename app/containers/message/{RepliedThread.js => RepliedThread.tsx} (68%) delete mode 100644 app/containers/message/Reply.js create mode 100644 app/containers/message/Reply.tsx delete mode 100644 app/containers/message/Thread.js create mode 100644 app/containers/message/Thread.tsx rename app/containers/message/{Touchable.js => Touchable.tsx} (57%) delete mode 100644 app/containers/message/Urls.js create mode 100644 app/containers/message/Urls.tsx delete mode 100644 app/containers/message/User.js create mode 100644 app/containers/message/User.tsx delete mode 100644 app/containers/message/Video.js create mode 100644 app/containers/message/Video.tsx rename app/containers/message/{constants.js => constants.ts} (100%) rename app/containers/message/{index.js => index.tsx} (72%) create mode 100644 app/containers/message/interfaces.ts rename app/containers/message/{styles.js => styles.ts} (98%) rename app/containers/message/{utils.js => utils.ts} (63%) delete mode 100644 app/dimensions.js create mode 100644 app/dimensions.tsx rename app/{emojis.js => emojis.ts} (99%) create mode 100644 app/externalModules.d.ts rename app/{index.js => index.tsx} (69%) delete mode 100644 app/lib/Navigation.js create mode 100644 app/lib/Navigation.ts delete mode 100644 app/lib/ShareNavigation.js create mode 100644 app/lib/ShareNavigation.ts delete mode 100644 app/presentation/DirectoryItem/index.js create mode 100644 app/presentation/DirectoryItem/index.tsx rename app/presentation/DirectoryItem/{styles.js => styles.ts} (100%) rename app/presentation/ImageViewer/{ImageComponent.js => ImageComponent.ts} (85%) rename app/presentation/ImageViewer/{ImageViewer.android.js => ImageViewer.android.tsx} (65%) rename app/presentation/ImageViewer/{ImageViewer.ios.js => ImageViewer.ios.tsx} (51%) rename app/presentation/ImageViewer/{index.js => index.ts} (58%) rename app/presentation/ImageViewer/{types.js => types.ts} (100%) delete mode 100644 app/presentation/KeyboardView.js create mode 100644 app/presentation/KeyboardView.tsx rename app/presentation/RoomItem/{Actions.js => Actions.tsx} (75%) rename app/presentation/RoomItem/{LastMessage.js => LastMessage.tsx} (51%) delete mode 100644 app/presentation/RoomItem/RoomItem.js create mode 100644 app/presentation/RoomItem/RoomItem.tsx rename app/presentation/RoomItem/{Tag.js => Tag.tsx} (52%) delete mode 100644 app/presentation/RoomItem/Title.js create mode 100644 app/presentation/RoomItem/Title.tsx delete mode 100644 app/presentation/RoomItem/Touchable.js create mode 100644 app/presentation/RoomItem/Touchable.tsx delete mode 100644 app/presentation/RoomItem/TypeIcon.js create mode 100644 app/presentation/RoomItem/TypeIcon.tsx delete mode 100644 app/presentation/RoomItem/UpdatedAt.js create mode 100644 app/presentation/RoomItem/UpdatedAt.tsx delete mode 100644 app/presentation/RoomItem/Wrapper.js create mode 100644 app/presentation/RoomItem/Wrapper.tsx rename app/presentation/RoomItem/{index.js => index.tsx} (70%) rename app/presentation/RoomItem/{styles.js => styles.ts} (95%) delete mode 100644 app/presentation/ServerItem/index.js create mode 100644 app/presentation/ServerItem/index.tsx rename app/presentation/ServerItem/{styles.js => styles.ts} (100%) rename app/presentation/{TextInput.js => TextInput.tsx} (61%) rename app/presentation/UnreadBadge/{getUnreadStyle.js => getUnreadStyle.ts} (69%) delete mode 100644 app/presentation/UnreadBadge/index.js create mode 100644 app/presentation/UnreadBadge/index.tsx rename app/presentation/{UserItem.js => UserItem.tsx} (65%) rename app/{share.js => share.tsx} (65%) delete mode 100644 app/theme.js create mode 100644 app/theme.tsx rename app/utils/navigation/{index.js => index.ts} (76%) rename app/views/AdminPanelView/{index.js => index.tsx} (63%) rename app/views/AutoTranslateView/{index.js => index.tsx} (64%) create mode 100644 app/views/CannedResponseDetail.js create mode 100644 app/views/CannedResponsesListView/CannedResponseItem.js create mode 100644 app/views/CannedResponsesListView/CannedResponseItem.stories.js create mode 100644 app/views/CannedResponsesListView/Dropdown/DropdownItem.js create mode 100644 app/views/CannedResponsesListView/Dropdown/DropdownItemFilter.js create mode 100644 app/views/CannedResponsesListView/Dropdown/DropdownItemHeader.js create mode 100644 app/views/CannedResponsesListView/Dropdown/index.js create mode 100644 app/views/CannedResponsesListView/index.js create mode 100644 app/views/CannedResponsesListView/styles.js rename app/views/CreateDiscussionView/{SelectChannel.js => SelectChannel.tsx} (59%) rename app/views/CreateDiscussionView/{SelectUsers.js => SelectUsers.tsx} (58%) rename app/views/CreateDiscussionView/{index.js => index.tsx} (70%) create mode 100644 app/views/CreateDiscussionView/interfaces.ts rename app/views/CreateDiscussionView/{styles.js => styles.ts} (100%) delete mode 100644 app/views/DirectoryView/Options.js create mode 100644 app/views/DirectoryView/Options.tsx rename app/views/DirectoryView/{index.js => index.tsx} (68%) rename app/views/DirectoryView/{styles.js => styles.ts} (98%) delete mode 100644 app/views/OnboardingView/index.js delete mode 100644 app/views/OnboardingView/styles.js rename app/views/{WithoutServersView.js => WithoutServersView.tsx} (70%) create mode 100644 tsconfig.json diff --git a/.circleci/config.yml b/.circleci/config.yml index 7277eef8c..9b681e2b6 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -340,7 +340,7 @@ jobs: <<: *defaults docker: - image: circleci/node:15 - + resource_class: large environment: CODECOV_TOKEN: caa771ab-3d45-4756-8e2a-e1f25996fef6 @@ -376,6 +376,7 @@ jobs: environment: <<: *android-env <<: *bash-env + resource_class: large steps: - android-build @@ -386,6 +387,7 @@ jobs: environment: <<: *android-env <<: *bash-env + resource_class: large steps: - android-build diff --git a/.eslintrc.js b/.eslintrc.js index 409efb70a..f5b6d39c8 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,161 +1,156 @@ module.exports = { - "settings": { - "import/resolver": { - "node": { - "extensions": [".js", ".ios.js", ".android.js", ".native.js", ".tsx"] - } - } - }, - "parser": "@babel/eslint-parser", - "extends": "airbnb", - "parserOptions": { - "sourceType": "module", - "ecmaVersion": 2017, - "ecmaFeatures": { - "experimentalObjectRestSpread" : true, - "jsx": true, - "legacyDecorators": true + settings: { + 'import/resolver': { + node: { + extensions: ['.js', '.ios.js', '.android.js', '.native.js', '.ts', '.tsx'] + } } }, - "plugins": [ - "react", - "jsx-a11y", - "import", - "react-native", - "@babel" - ], - "env": { - "browser": true, - "commonjs": true, - "es6": true, - "node": true, - "jquery": true, - "mocha": true + parser: '@babel/eslint-parser', + extends: ['@rocket.chat/eslint-config', 'prettier'], + parserOptions: { + sourceType: 'module', + ecmaVersion: 2017, + ecmaFeatures: { + experimentalObjectRestSpread: true, + jsx: true, + legacyDecorators: true + } }, - "rules": { - "react/jsx-filename-extension": [1, { - "extensions": [".js", ".jsx"] - }], - "react/require-default-props": [0], - "react/no-unused-prop-types": [2, { - "skipShapeProps": true - }], - "react/no-did-mount-set-state": 0, - "react/no-multi-comp": [0], - "react/jsx-indent": [2, "tab"], - "react/jsx-indent-props": [2, "tab"], - "react/forbid-prop-types": 0, - "jsx-quotes": [2, "prefer-single"], - "jsx-a11y/href-no-hash": 0, - "jsx-a11y/aria-role": 0, - "import/prefer-default-export": 0, - "import/no-cycle": 0, - "camelcase": 0, - "no-underscore-dangle": 0, - "no-return-assign": 0, - "no-param-reassign": 0, - "no-tabs": 0, - "no-multi-spaces": 2, - "no-eval": 2, - "no-extend-native": 2, - "no-multi-str": 2, - "no-use-before-define": 2, - "no-const-assign": 2, - "no-cond-assign": 2, - "no-constant-condition": 2, - "no-control-regex": 2, - "no-debugger": 2, - "no-delete-var": 2, - "no-dupe-keys": 2, - "no-dupe-args": 2, - "no-dupe-class-members": 2, - "no-duplicate-case": 2, - "no-else-return": [0, {allowElseIf: true}], - "no-empty": 2, - "no-empty-character-class": 2, - "no-ex-assign": 2, - "no-extra-boolean-cast": 2, - "no-extra-semi": 2, - "no-fallthrough": 2, - "no-func-assign": 2, - "no-inner-declarations": [2, "functions"], - "no-invalid-regexp": 2, - "no-irregular-whitespace": 2, - "no-mixed-spaces-and-tabs": 2, - "no-sparse-arrays": 2, - "no-negated-in-lhs": 2, - "no-obj-calls": 2, - "no-octal": 2, - "no-redeclare": 2, - "no-regex-spaces": 2, - "no-undef": 2, - "no-unreachable": 2, - "no-unused-expressions": 0, - "no-unused-vars": [2, { - "vars": "all", - "args": "after-used" - }], - "max-len": 0, - "react/jsx-uses-vars": 2, - "no-void": 2, - "no-var": 2, - "one-var": [2, "never"], - "no-lonely-if": 2, - "no-trailing-spaces": 2, - "complexity": [1, 31], - "space-in-parens": [2, "never"], - "space-before-function-paren": [2, "never"], - "space-before-blocks": [2, "always"], - "indent": [2, "tab", {"SwitchCase": 1}], - "eol-last": [2, "always"], - "comma-dangle": [2, "never"], - "keyword-spacing": 2, - "block-spacing": 2, - "brace-style": [2, "1tbs", { "allowSingleLine": true }], - "computed-property-spacing": 2, - "comma-spacing": 2, - "comma-style": 2, - "guard-for-in": 2, - "wrap-iife": 2, - "block-scoped-var": 2, - "curly": [2, "all"], - "eqeqeq": [2, "allow-null"], - "new-cap": [2], - "use-isnan": 2, - "valid-typeof": 2, - "linebreak-style": 0, - "prefer-template": 2, - "template-curly-spacing": [2, "always"], - "quotes": [2, "single"], - "semi": [2, "always"], - "prefer-const": 2, - "object-shorthand": 2, - "consistent-return": 0, - "global-require": "off", - "react-native/no-unused-styles": 2, - "react/jsx-one-expression-per-line": 0, - "require-await": 2, - "func-names": 0, - "react/sort-comp": ["error", { - "order": [ - "static-variables", - "static-methods", - "lifecycle", - "everything-else", - "render" - ] - }], - "react/static-property-placement": [0], - "arrow-parens": ["error", "as-needed", { requireForBlockBody: true }], - "react/jsx-props-no-spreading": [1], - "react/jsx-curly-newline": [0], - "react/state-in-constructor": [0], - "no-async-promise-executor": [0], - "max-classes-per-file": [0], - "no-multiple-empty-lines": [0] + plugins: ['react', 'jsx-a11y', 'import', 'react-native', '@babel'], + env: { + browser: true, + commonjs: true, + es6: true, + node: true, + jquery: true, + mocha: true }, - "globals": { - "__DEV__": true + rules: { + 'import/extensions': [ + 'error', + 'ignorePackages', + { + js: 'warning', + jsx: 'warning', + ts: 'warning', + tsx: 'warning' + } + ], + 'react/jsx-filename-extension': [ + 1, + { + extensions: ['.js', '.jsx', '.ts', '.tsx'] + } + ], + 'react/require-default-props': [0], + 'ordered-imports': [0], + 'react/no-did-mount-set-state': 0, + 'react/no-multi-comp': [0], + 'react/jsx-indent-props': [2, 'tab'], + 'jsx-quotes': [2, 'prefer-single'], + 'jsx-a11y/href-no-hash': 0, + 'jsx-a11y/aria-role': 0, + 'import/prefer-default-export': 0, + 'import/no-cycle': 0, + 'import/order': [ + 'error', + { + 'newlines-between': 'ignore' + } + ], + camelcase: 0, + 'no-underscore-dangle': 0, + 'no-return-assign': 0, + 'no-param-reassign': 0, + 'no-tabs': 0, + 'no-multi-spaces': 2, + 'no-eval': 2, + 'no-extend-native': 2, + 'no-multi-str': 2, + 'no-use-before-define': 2, + 'no-const-assign': 2, + 'no-cond-assign': 2, + 'no-constant-condition': 2, + 'no-control-regex': 2, + 'no-debugger': 2, + 'no-delete-var': 2, + 'no-dupe-keys': 2, + 'no-dupe-args': 2, + 'no-dupe-class-members': 2, + 'no-duplicate-case': 2, + 'no-else-return': [0, { allowElseIf: true }], + 'no-empty': 2, + 'no-empty-character-class': 2, + 'no-ex-assign': 2, + 'no-extra-boolean-cast': 2, + 'no-extra-semi': 2, + 'no-fallthrough': 2, + 'no-func-assign': 2, + 'no-inner-declarations': [2, 'functions'], + 'no-invalid-regexp': 2, + 'no-irregular-whitespace': 2, + 'no-mixed-spaces-and-tabs': 1, + 'no-sparse-arrays': 2, + 'no-negated-in-lhs': 2, + 'no-obj-calls': 2, + 'no-octal': 2, + 'no-redeclare': 2, + 'no-regex-spaces': 2, + 'no-undef': 2, + 'no-unreachable': 2, + 'no-unused-expressions': 0, + 'no-unused-vars': 'off', + 'max-len': 0, + 'react/jsx-uses-vars': 2, + 'no-void': 2, + 'no-var': 2, + 'one-var': [2, 'never'], + 'no-lonely-if': 2, + 'no-trailing-spaces': 2, + complexity: [1, 31], + 'space-in-parens': [2, 'never'], + 'space-before-blocks': [2, 'always'], + indent: 'off', + 'eol-last': [2, 'always'], + 'comma-dangle': [2, 'never'], + 'keyword-spacing': 2, + 'block-spacing': 2, + 'brace-style': [2, '1tbs', { allowSingleLine: true }], + 'computed-property-spacing': 2, + 'comma-spacing': 2, + 'comma-style': 2, + 'guard-for-in': 2, + 'wrap-iife': 2, + 'block-scoped-var': 2, + curly: [2, 'all'], + eqeqeq: [2, 'allow-null'], + 'new-cap': 'off', + 'use-isnan': 2, + 'valid-typeof': 2, + 'linebreak-style': 0, + 'prefer-template': 2, + quotes: [1, 'single'], + semi: [2, 'always'], + 'prefer-const': 2, + 'object-shorthand': 2, + 'consistent-return': 0, + 'global-require': 'off', + 'react-native/no-unused-styles': 2, + 'react/jsx-one-expression-per-line': 0, + 'require-await': 2, + 'func-names': 0, + 'react/static-property-placement': [0], + 'arrow-parens': ['warn', 'as-needed', { requireForBlockBody: true }], + 'react/jsx-curly-newline': [0], + 'react/state-in-constructor': [0], + 'no-async-promise-executor': [0], + 'max-classes-per-file': [0], + 'no-multiple-empty-lines': [0], + 'no-sequences': 'off' + }, + globals: { + __DEV__: true }, overrides: [ { @@ -173,6 +168,86 @@ module.exports = { 'no-await-in-loop': 0, 'no-restricted-syntax': 0 } + }, + { + files: ['**/*.ts', '**/*.tsx'], + extends: [ + 'plugin:@typescript-eslint/recommended', + 'plugin:@typescript-eslint/eslint-recommended', + '@rocket.chat/eslint-config', + 'prettier' + ], + parser: '@typescript-eslint/parser', + parserOptions: { + sourceType: 'module', + ecmaVersion: 2018, + warnOnUnsupportedTypeScriptVersion: false, + ecmaFeatures: { + experimentalObjectRestSpread: true, + legacyDecorators: true + } + }, + plugins: ['react', '@typescript-eslint'], + rules: { + '@typescript-eslint/no-var-requires': 'off', + '@typescript-eslint/no-empty-function': [0], + '@typescript-eslint/ban-types': [0], + 'func-call-spacing': 'off', + 'jsx-quotes': ['error', 'prefer-single'], + indent: 'off', + 'comma-dangle': [2, 'never'], + 'no-return-assign': 0, + 'no-dupe-class-members': 'off', + 'no-extra-parens': 'off', + 'no-spaced-func': 'off', + 'no-unused-vars': 'off', + 'no-useless-constructor': 'off', + 'no-use-before-define': 'off', + 'react/jsx-uses-react': 'error', + 'react/jsx-uses-vars': 'error', + 'react/jsx-no-undef': 'error', + 'react/jsx-fragments': ['error', 'syntax'], + '@typescript-eslint/ban-ts-comment': 'off', + '@typescript-eslint/indent': [ + 'warn', + 'tab', + { + SwitchCase: 1 + } + ], + '@typescript-eslint/no-extra-parens': [ + 'warn', + 'all', + { + conditionalAssign: true, + nestedBinaryExpressions: false, + returnAssign: true, + ignoreJSX: 'all', + enforceForArrowConditionals: false + } + ], + '@typescript-eslint/no-dupe-class-members': 'error', + '@typescript-eslint/no-explicit-any': 'off', + '@typescript-eslint/no-unused-vars': [ + 'error', + { + argsIgnorePattern: '^_', + ignoreRestSiblings: true + } + ], + 'new-cap': 'off', + 'lines-between-class-members': 'off' + }, + globals: { + JSX: true + }, + settings: { + 'import/resolver': { + node: { + extensions: ['.js', '.ts', '.tsx'] + } + } + } } ] }; diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 000000000..16e40ac82 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,25 @@ +.circleci/ +.github/ +.husky +build/ +node_modules/ +coverage/ +e2e/docker/ +artifacts/ +android/ +ios/ +patches/ +scripts/ + +.bettercodehub.yml +.buckconfig +.gitattributes +.gitignore +.snyk +.watchmanconfig +CONTRIBUTING.md +README.md +SECURITY.md +npm-debug.log +yarn-error.log + diff --git a/.prettierrc.js b/.prettierrc.js new file mode 100644 index 000000000..7321592a1 --- /dev/null +++ b/.prettierrc.js @@ -0,0 +1,10 @@ +module.exports = { + bracketSpacing: true, + jsxBracketSameLine: true, + singleQuote: true, + jsxSingleQuote: true, + trailingComma: 'none', + printWidth: 130, + useTabs: true, + arrowParens: 'avoid' +}; diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 6775dbd8c..dc78c1d9f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -54,6 +54,18 @@ To check for lint issues on your code, run this on your terminal: yarn lint ``` +## Code formatting + +We use [Prettier](https://prettier.io) to format the code style in our project. We have a pre-commit hook enforcing commits to follow our style guides. + +To fix your code formatting issues, run this on your terminal: + +```sh +yarn prettier +``` + +[Check this link](https://prettier.io/docs/en/editors.html) to see how to integrate Prettier with your preferred code editor, and run Prettier when save your file for example. + ## Tests It's always important to ensure everything is working properly and that's why tests are great. We have unit and e2e tests on this project. diff --git a/README.md b/README.md index e4e7bdecf..50e97ea19 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ Also check the [#react-native](https://open.rocket.chat/channel/react-native) co Are you a dev and would like to help? Found a bug that you would like to report or a missing feature that you would like to work on? Great! We have written down a [Contribution guide](https://github.com/RocketChat/Rocket.Chat.ReactNative/blob/develop/CONTRIBUTING.md) so you can start easily. ## Whitelabel -Do you want to make the app run on your own server only? [Follow our whitelabel documentation.](https://docs.rocket.chat/guides/developer/mobile-apps/whitelabeling-mobile-apps) +Do you want to make the app run on your own server only? [Follow our whitelabel documentation.](https://developer.rocket.chat/mobile-app/mobile-app-white-labelling) ## Engage with us ### Share your story diff --git a/__tests__/__snapshots__/Storyshots.test.js.snap b/__tests__/__snapshots__/Storyshots.test.js.snap index fc2569dd2..0ad82c0d0 100644 --- a/__tests__/__snapshots__/Storyshots.test.js.snap +++ b/__tests__/__snapshots__/Storyshots.test.js.snap @@ -899,27 +899,22 @@ exports[`Storyshots BackgroundContainer black theme - loading 1`] = ` @@ -1037,27 +1032,22 @@ exports[`Storyshots BackgroundContainer dark theme - loading 1`] = ` @@ -1175,27 +1165,22 @@ exports[`Storyshots BackgroundContainer loading 1`] = ` @@ -1337,6 +1322,595 @@ exports[`Storyshots BackgroundContainer text 1`] = ` `; +exports[`Storyshots CannedResponseItem Itens 1`] = ` +Array [ + + + + + ! + !FAQ4 + + + Private + + + + + Use + + + + + “ + ZCVXZVXCZVZXVZXCVZXCVXZCVZX + ” + + + , + + + + + ! + test4mobilePrivate + + + Private + + + + + Use + + + + + “ + test for mobile private + ” + + + + + HQ + + + + + Closed + + + + + HQ + + + + + Problem in Product Y + + + + + HQ + + + + + Closed + + + + + Problem in Product Y + + + + , +] +`; + exports[`Storyshots Header Buttons badge 1`] = ` diff --git a/android/app/build.gradle b/android/app/build.gradle index b25b12f7c..d18b7869f 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -25,7 +25,7 @@ import com.android.build.OutputFile * bundleAssetName: "index.android.bundle", * * // the entry file for bundle generation. If none specified and - * // "index.android.js" exists, it will be used. Otherwise "index.js" is + * // "index.android.js" exists, it will be used. Otherwise "index.tsx" is * // default. Can be overridden with ENTRY_FILE environment variable. * entryFile: "index.android.js", * @@ -144,7 +144,7 @@ android { minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion versionCode VERSIONCODE as Integer - versionName "4.19.0" + versionName "4.20.0" vectorDrawables.useSupportLibrary = true if (!isFoss) { manifestPlaceholders = [BugsnagAPIKey: BugsnagAPIKey as String] diff --git a/app.json b/app.json index 7b3a422dc..5b802fd60 100644 --- a/app.json +++ b/app.json @@ -1,5 +1,5 @@ { - "name": "RocketChatRN", - "share": "ShareRocketChatRN", - "displayName": "RocketChatRN" -} \ No newline at end of file + "name": "RocketChatRN", + "share": "ShareRocketChatRN", + "displayName": "RocketChatRN" +} diff --git a/app/AppContainer.js b/app/AppContainer.tsx similarity index 64% rename from app/AppContainer.js rename to app/AppContainer.tsx index 9ec17ad43..f7f08bb27 100644 --- a/app/AppContainer.js +++ b/app/AppContainer.tsx @@ -1,21 +1,15 @@ import React from 'react'; -import PropTypes from 'prop-types'; import { NavigationContainer } from '@react-navigation/native'; import { createStackNavigator } from '@react-navigation/stack'; import { connect } from 'react-redux'; import Navigation from './lib/Navigation'; import { defaultHeader, getActiveRouteName, navigationTheme } from './utils/navigation'; -import { - ROOT_LOADING, ROOT_OUTSIDE, ROOT_NEW_SERVER, ROOT_INSIDE, ROOT_SET_USERNAME -} from './actions/app'; - +import { ROOT_INSIDE, ROOT_LOADING, ROOT_OUTSIDE, ROOT_SET_USERNAME } from './actions/app'; // Stacks import AuthLoadingView from './views/AuthLoadingView'; - // SetUsername Stack import SetUsernameView from './views/SetUsernameView'; - import OutsideStack from './stacks/OutsideStack'; import InsideStack from './stacks/InsideStack'; import MasterDetailStack from './stacks/MasterDetailStack'; @@ -26,16 +20,13 @@ import { setCurrentScreen } from './utils/log'; const SetUsername = createStackNavigator(); const SetUsernameStack = () => ( - + ); // App const Stack = createStackNavigator(); -const App = React.memo(({ root, isMasterDetail }) => { +const App = React.memo(({ root, isMasterDetail }: { root: string; isMasterDetail: boolean }) => { if (!root) { return null; } @@ -54,61 +45,32 @@ const App = React.memo(({ root, isMasterDetail }) => { { + onStateChange={state => { const previousRouteName = Navigation.routeNameRef.current; const currentRouteName = getActiveRouteName(state); if (previousRouteName !== currentRouteName) { setCurrentScreen(currentRouteName); } Navigation.routeNameRef.current = currentRouteName; - }} - > + }}> <> - {root === ROOT_LOADING ? ( - - ) : null} - {root === ROOT_OUTSIDE || root === ROOT_NEW_SERVER ? ( - - ) : null} + {root === ROOT_LOADING ? : null} + {root === ROOT_OUTSIDE ? : null} {root === ROOT_INSIDE && isMasterDetail ? ( - - ) : null} - {root === ROOT_INSIDE && !isMasterDetail ? ( - - ) : null} - {root === ROOT_SET_USERNAME ? ( - + ) : null} + {root === ROOT_INSIDE && !isMasterDetail ? : null} + {root === ROOT_SET_USERNAME ? : null} ); }); -const mapStateToProps = state => ({ +const mapStateToProps = (state: any) => ({ root: state.app.root, isMasterDetail: state.app.isMasterDetail }); -App.propTypes = { - root: PropTypes.string, - isMasterDetail: PropTypes.bool -}; - const AppContainer = connect(mapStateToProps)(App); export default AppContainer; diff --git a/app/ReactotronConfig.js b/app/ReactotronConfig.js index e2c43b4ce..80cb90beb 100644 --- a/app/ReactotronConfig.js +++ b/app/ReactotronConfig.js @@ -2,21 +2,16 @@ import { NativeModules } from 'react-native'; import Reactotron from 'reactotron-react-native'; import { reactotronRedux } from 'reactotron-redux'; -import sagaPlugin from 'reactotron-redux-saga' +import sagaPlugin from 'reactotron-redux-saga'; if (__DEV__) { - const scriptURL = NativeModules.SourceCode.scriptURL; - const scriptHostname = scriptURL.split('://')[1].split(':')[0]; - Reactotron - .configure({ host: scriptHostname }) - .useReactNative() - .use(reactotronRedux()) - .use(sagaPlugin()) - .connect(); - // Running on android device - // $ adb reverse tcp:9090 tcp:9090 - Reactotron.clear(); - console.warn = Reactotron.log; - console.log = Reactotron.log; - console.disableYellowBox = true; + const scriptURL = NativeModules.SourceCode.scriptURL; + const scriptHostname = scriptURL.split('://')[1].split(':')[0]; + Reactotron.configure({ host: scriptHostname }).useReactNative().use(reactotronRedux()).use(sagaPlugin()).connect(); + // Running on android device + // $ adb reverse tcp:9090 tcp:9090 + Reactotron.clear(); + console.warn = Reactotron.log; + console.log = Reactotron.log; + console.disableYellowBox = true; } diff --git a/app/actions/actionsTypes.js b/app/actions/actionsTypes.js index 70555acb8..fcf881fd3 100644 --- a/app/actions/actionsTypes.js +++ b/app/actions/actionsTypes.js @@ -4,23 +4,13 @@ const FAILURE = 'FAILURE'; const defaultTypes = [REQUEST, SUCCESS, FAILURE]; function createRequestTypes(base, types = defaultTypes) { const res = {}; - types.forEach(type => (res[type] = `${ base }_${ type }`)); + types.forEach(type => (res[type] = `${base}_${type}`)); return res; } // Login events -export const LOGIN = createRequestTypes('LOGIN', [ - ...defaultTypes, - 'SET_SERVICES', - 'SET_PREFERENCE', - 'SET_LOCAL_AUTHENTICATED' -]); -export const SHARE = createRequestTypes('SHARE', [ - 'SELECT_SERVER', - 'SET_USER', - 'SET_SETTINGS', - 'SET_SERVER_INFO' -]); +export const LOGIN = createRequestTypes('LOGIN', [...defaultTypes, 'SET_SERVICES', 'SET_PREFERENCE', 'SET_LOCAL_AUTHENTICATED']); +export const SHARE = createRequestTypes('SHARE', ['SELECT_SERVER', 'SET_USER', 'SET_SETTINGS', 'SET_SERVER_INFO']); export const USER = createRequestTypes('USER', ['SET']); export const ROOMS = createRequestTypes('ROOMS', [ ...defaultTypes, @@ -33,8 +23,24 @@ export const ROOMS = createRequestTypes('ROOMS', [ 'OPEN_SEARCH_HEADER', 'CLOSE_SEARCH_HEADER' ]); -export const ROOM = createRequestTypes('ROOM', ['SUBSCRIBE', 'UNSUBSCRIBE', 'LEAVE', 'DELETE', 'REMOVED', 'CLOSE', 'FORWARD', 'USER_TYPING']); -export const INQUIRY = createRequestTypes('INQUIRY', [...defaultTypes, 'SET_ENABLED', 'RESET', 'QUEUE_ADD', 'QUEUE_UPDATE', 'QUEUE_REMOVE']); +export const ROOM = createRequestTypes('ROOM', [ + 'SUBSCRIBE', + 'UNSUBSCRIBE', + 'LEAVE', + 'DELETE', + 'REMOVED', + 'CLOSE', + 'FORWARD', + 'USER_TYPING' +]); +export const INQUIRY = createRequestTypes('INQUIRY', [ + ...defaultTypes, + 'SET_ENABLED', + 'RESET', + 'QUEUE_ADD', + 'QUEUE_UPDATE', + 'QUEUE_REMOVE' +]); export const APP = createRequestTypes('APP', ['START', 'READY', 'INIT', 'INIT_LOCAL_SETTINGS', 'SET_MASTER_DETAIL']); export const MESSAGES = createRequestTypes('MESSAGES', ['REPLY_BROADCAST']); export const CREATE_CHANNEL = createRequestTypes('CREATE_CHANNEL', [...defaultTypes]); diff --git a/app/actions/app.js b/app/actions/app.js index 9ced0c6c2..fe6981d67 100644 --- a/app/actions/app.js +++ b/app/actions/app.js @@ -3,7 +3,6 @@ import { APP } from './actionsTypes'; export const ROOT_OUTSIDE = 'outside'; export const ROOT_INSIDE = 'inside'; export const ROOT_LOADING = 'loading'; -export const ROOT_NEW_SERVER = 'newServer'; export const ROOT_SET_USERNAME = 'setUsername'; export function appStart({ root, ...args }) { diff --git a/app/actions/inviteLinks.js b/app/actions/inviteLinks.js index b456603cf..cd2fd1639 100644 --- a/app/actions/inviteLinks.js +++ b/app/actions/inviteLinks.js @@ -32,7 +32,6 @@ export function inviteLinksClear() { }; } - export function inviteLinksCreate(rid) { return { type: types.INVITE_LINKS.CREATE, diff --git a/app/actions/rooms.js b/app/actions/rooms.js index a37c17cae..63e095cea 100644 --- a/app/actions/rooms.js +++ b/app/actions/rooms.js @@ -1,6 +1,5 @@ import * as types from './actionsTypes'; - export function roomsRequest(params = { allData: false }) { return { type: types.ROOMS.REQUEST, diff --git a/app/commands.js b/app/commands.js index c0f4c5575..300473019 100644 --- a/app/commands.js +++ b/app/commands.js @@ -125,15 +125,15 @@ const keyCommands = [ discoverabilityTitle: I18n.t('Add_server') }, // Refers to select rooms on list - ...([1, 2, 3, 4, 5, 6, 7, 8, 9].map(value => ({ - input: `${ value }`, + ...[1, 2, 3, 4, 5, 6, 7, 8, 9].map(value => ({ + input: `${value}`, modifierFlags: constants.keyModifierCommand - }))), + })), // Refers to select servers on list - ...([1, 2, 3, 4, 5, 6, 7, 8, 9].map(value => ({ - input: `${ value }`, + ...[1, 2, 3, 4, 5, 6, 7, 8, 9].map(value => ({ + input: `${value}`, modifierFlags: constants.keyModifierCommand | constants.keyModifierAlternate - }))) + })) ]; export const setKeyCommands = () => KeyCommands.setKeyCommands(keyCommands); @@ -161,7 +161,8 @@ export const handleCommandSubmit = event => commandHandle(event, KEY_SEND_MESSAG export const handleCommandShowUpload = event => commandHandle(event, KEY_UPLOAD, ['command']); -export const handleCommandScroll = event => commandHandle(event, [constants.keyInputUpArrow, constants.keyInputDownArrow], ['alternate']); +export const handleCommandScroll = event => + commandHandle(event, [constants.keyInputUpArrow, constants.keyInputDownArrow], ['alternate']); export const handleCommandRoomActions = event => commandHandle(event, KEY_ROOM_ACTIONS, ['command']); diff --git a/app/constants/colors.js b/app/constants/colors.ts similarity index 98% rename from app/constants/colors.js rename to app/constants/colors.ts index 1172b4bc9..ab358679c 100644 --- a/app/constants/colors.js +++ b/app/constants/colors.ts @@ -1,4 +1,4 @@ -export const STATUS_COLORS = { +export const STATUS_COLORS: any = { online: '#2de0a5', busy: '#f5455c', away: '#ffd21f', @@ -19,7 +19,7 @@ const mentions = { mentionOtherColor: '#F3BE08' }; -export const themes = { +export const themes: any = { light: { backgroundColor: '#ffffff', focusedBackground: '#ffffff', diff --git a/app/constants/environment.js b/app/constants/environment.ts similarity index 100% rename from app/constants/environment.js rename to app/constants/environment.ts diff --git a/app/constants/links.js b/app/constants/links.ts similarity index 66% rename from app/constants/links.js rename to app/constants/links.ts index 8ca76da5e..af93b5e39 100644 --- a/app/constants/links.js +++ b/app/constants/links.ts @@ -2,8 +2,10 @@ import { getBundleId, isIOS } from '../utils/deviceInfo'; const APP_STORE_ID = '1148741252'; -export const PLAY_MARKET_LINK = `https://play.google.com/store/apps/details?id=${ getBundleId }`; +export const PLAY_MARKET_LINK = `https://play.google.com/store/apps/details?id=${getBundleId}`; export const FDROID_MARKET_LINK = 'https://f-droid.org/en/packages/chat.rocket.android'; -export const APP_STORE_LINK = `https://itunes.apple.com/app/id${ APP_STORE_ID }`; +export const APP_STORE_LINK = `https://itunes.apple.com/app/id${APP_STORE_ID}`; export const LICENSE_LINK = 'https://github.com/RocketChat/Rocket.Chat.ReactNative/blob/develop/LICENSE'; -export const STORE_REVIEW_LINK = isIOS ? `itms-apps://itunes.apple.com/app/id${ APP_STORE_ID }?action=write-review` : `market://details?id=${ getBundleId }`; +export const STORE_REVIEW_LINK = isIOS + ? `itms-apps://itunes.apple.com/app/id${APP_STORE_ID}?action=write-review` + : `market://details?id=${getBundleId}`; diff --git a/app/constants/localAuthentication.js b/app/constants/localAuthentication.ts similarity index 100% rename from app/constants/localAuthentication.js rename to app/constants/localAuthentication.ts diff --git a/app/constants/messageTypeLoad.js b/app/constants/messageTypeLoad.ts similarity index 100% rename from app/constants/messageTypeLoad.js rename to app/constants/messageTypeLoad.ts diff --git a/app/constants/messagesStatus.js b/app/constants/messagesStatus.ts similarity index 100% rename from app/constants/messagesStatus.js rename to app/constants/messagesStatus.ts diff --git a/app/constants/settings.js b/app/constants/settings.ts similarity index 98% rename from app/constants/settings.js rename to app/constants/settings.ts index 14ad8fb36..fb9c7e6b5 100644 --- a/app/constants/settings.js +++ b/app/constants/settings.ts @@ -202,5 +202,8 @@ export default { }, Jitsi_Enable_Channels: { type: 'valuesAsBoolean' + }, + Canned_Responses_Enable: { + type: 'valueAsBoolean' } }; diff --git a/app/constants/tablet.js b/app/constants/tablet.ts similarity index 100% rename from app/constants/tablet.js rename to app/constants/tablet.ts diff --git a/app/containers/ActionSheet/ActionSheet.js b/app/containers/ActionSheet/ActionSheet.js deleted file mode 100644 index af98a75ce..000000000 --- a/app/containers/ActionSheet/ActionSheet.js +++ /dev/null @@ -1,208 +0,0 @@ -import React, { - useRef, - useState, - useEffect, - forwardRef, - useImperativeHandle, - useCallback, - isValidElement -} from 'react'; -import PropTypes from 'prop-types'; -import { Keyboard, Text } from 'react-native'; -import { useSafeAreaInsets } from 'react-native-safe-area-context'; -import { TapGestureHandler, State } from 'react-native-gesture-handler'; -import ScrollBottomSheet from 'react-native-scroll-bottom-sheet'; -import Animated, { - Extrapolate, - interpolate, - Value, - Easing -} from 'react-native-reanimated'; -import * as Haptics from 'expo-haptics'; -import { useBackHandler } from '@react-native-community/hooks'; - -import { Item } from './Item'; -import { Handle } from './Handle'; -import { Button } from './Button'; -import { themes } from '../../constants/colors'; -import styles, { ITEM_HEIGHT } from './styles'; -import { isTablet, isIOS } from '../../utils/deviceInfo'; -import * as List from '../List'; -import I18n from '../../i18n'; -import { useOrientation, useDimensions } from '../../dimensions'; - -const getItemLayout = (data, index) => ({ length: ITEM_HEIGHT, offset: ITEM_HEIGHT * index, index }); - -const HANDLE_HEIGHT = isIOS ? 40 : 56; -const MAX_SNAP_HEIGHT = 16; -const CANCEL_HEIGHT = 64; - -const ANIMATION_DURATION = 250; - -const ANIMATION_CONFIG = { - duration: ANIMATION_DURATION, - // https://easings.net/#easeInOutCubic - easing: Easing.bezier(0.645, 0.045, 0.355, 1.0) -}; - -const ActionSheet = React.memo(forwardRef(({ children, theme }, ref) => { - const bottomSheetRef = useRef(); - const [data, setData] = useState({}); - const [isVisible, setVisible] = useState(false); - const { height } = useDimensions(); - const { isLandscape } = useOrientation(); - const insets = useSafeAreaInsets(); - - const maxSnap = Math.max( - ( - height - // Items height - - (ITEM_HEIGHT * (data?.options?.length || 0)) - // Handle height - - HANDLE_HEIGHT - // Custom header height - - (data?.headerHeight || 0) - // Insets bottom height (Notch devices) - - insets.bottom - // Cancel button height - - (data?.hasCancel ? CANCEL_HEIGHT : 0) - ), - MAX_SNAP_HEIGHT - ); - - /* - * if the action sheet cover more - * than 60% of the whole screen - * and it's not at the landscape mode - * we'll provide more one snap - * that point 50% of the whole screen - */ - const snaps = (height - maxSnap > height * 0.6) && !isLandscape ? [maxSnap, height * 0.5, height] : [maxSnap, height]; - const openedSnapIndex = snaps.length > 2 ? 1 : 0; - const closedSnapIndex = snaps.length - 1; - - const toggleVisible = () => setVisible(!isVisible); - - const hide = () => { - bottomSheetRef.current?.snapTo(closedSnapIndex); - }; - - const show = (options) => { - setData(options); - toggleVisible(); - }; - - const onBackdropPressed = ({ nativeEvent }) => { - if (nativeEvent.oldState === State.ACTIVE) { - hide(); - } - }; - - useBackHandler(() => { - if (isVisible) { - hide(); - } - return isVisible; - }); - - useEffect(() => { - if (isVisible) { - Keyboard.dismiss(); - Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light); - bottomSheetRef.current?.snapTo(openedSnapIndex); - } - }, [isVisible]); - - // Hides action sheet when orientation changes - useEffect(() => { - setVisible(false); - }, [isLandscape]); - - useImperativeHandle(ref, () => ({ - showActionSheet: show, - hideActionSheet: hide - })); - - const renderHandle = useCallback(() => ( - <> - - {isValidElement(data?.customHeader) ? data.customHeader : null} - - )); - - const renderFooter = useCallback(() => (data?.hasCancel ? ( - - ) : null)); - - const renderItem = useCallback(({ item }) => ); - - const animatedPosition = React.useRef(new Value(0)); - const opacity = interpolate(animatedPosition.current, { - inputRange: [0, 1], - outputRange: [0, themes[theme].backdropOpacity], - extrapolate: Extrapolate.CLAMP - }); - - return ( - <> - {children} - {isVisible && ( - <> - - - - (index === closedSnapIndex) && toggleVisible()} - animatedPosition={animatedPosition.current} - containerStyle={[ - styles.container, - { backgroundColor: themes[theme].focusedBackground }, - (isLandscape || isTablet) && styles.bottomSheet - ]} - animationConfig={ANIMATION_CONFIG} - // FlatList props - data={data?.options} - renderItem={renderItem} - keyExtractor={item => item.title} - style={{ backgroundColor: themes[theme].focusedBackground }} - contentContainerStyle={styles.content} - ItemSeparatorComponent={List.Separator} - ListHeaderComponent={List.Separator} - ListFooterComponent={renderFooter} - getItemLayout={getItemLayout} - removeClippedSubviews={isIOS} - /> - - )} - - ); -})); -ActionSheet.propTypes = { - children: PropTypes.node, - theme: PropTypes.string -}; - -export default ActionSheet; diff --git a/app/containers/ActionSheet/ActionSheet.tsx b/app/containers/ActionSheet/ActionSheet.tsx new file mode 100644 index 000000000..f926f01e7 --- /dev/null +++ b/app/containers/ActionSheet/ActionSheet.tsx @@ -0,0 +1,194 @@ +import React, { forwardRef, isValidElement, useEffect, useImperativeHandle, useRef, useState } from 'react'; +import { Keyboard, Text } from 'react-native'; +import { useSafeAreaInsets } from 'react-native-safe-area-context'; +import { State, TapGestureHandler } from 'react-native-gesture-handler'; +import ScrollBottomSheet from 'react-native-scroll-bottom-sheet'; +import Animated, { Easing, Extrapolate, Value, interpolate } from 'react-native-reanimated'; +import * as Haptics from 'expo-haptics'; +import { useBackHandler } from '@react-native-community/hooks'; + +import { Item } from './Item'; +import { Handle } from './Handle'; +import { Button } from './Button'; +import { themes } from '../../constants/colors'; +import styles, { ITEM_HEIGHT } from './styles'; +import { isIOS, isTablet } from '../../utils/deviceInfo'; +import * as List from '../List'; +import I18n from '../../i18n'; +import { IDimensionsContextProps, useDimensions, useOrientation } from '../../dimensions'; + +interface IActionSheetData { + options: any; + headerHeight?: number; + hasCancel?: boolean; + customHeader: any; +} + +const getItemLayout = (data: any, index: number) => ({ length: ITEM_HEIGHT, offset: ITEM_HEIGHT * index, index }); + +const HANDLE_HEIGHT = isIOS ? 40 : 56; +const MAX_SNAP_HEIGHT = 16; +const CANCEL_HEIGHT = 64; + +const ANIMATION_DURATION = 250; + +const ANIMATION_CONFIG = { + duration: ANIMATION_DURATION, + // https://easings.net/#easeInOutCubic + easing: Easing.bezier(0.645, 0.045, 0.355, 1.0) +}; + +const ActionSheet = React.memo( + forwardRef(({ children, theme }: { children: JSX.Element; theme: string }, ref) => { + const bottomSheetRef: any = useRef(); + const [data, setData] = useState({} as IActionSheetData); + const [isVisible, setVisible] = useState(false); + const { height }: Partial = useDimensions(); + const { isLandscape } = useOrientation(); + const insets = useSafeAreaInsets(); + + const maxSnap = Math.max( + height! - + // Items height + ITEM_HEIGHT * (data?.options?.length || 0) - + // Handle height + HANDLE_HEIGHT - + // Custom header height + (data?.headerHeight || 0) - + // Insets bottom height (Notch devices) + insets.bottom - + // Cancel button height + (data?.hasCancel ? CANCEL_HEIGHT : 0), + MAX_SNAP_HEIGHT + ); + + /* + * if the action sheet cover more + * than 60% of the whole screen + * and it's not at the landscape mode + * we'll provide more one snap + * that point 50% of the whole screen + */ + const snaps: any = height! - maxSnap > height! * 0.6 && !isLandscape ? [maxSnap, height! * 0.5, height] : [maxSnap, height]; + const openedSnapIndex = snaps.length > 2 ? 1 : 0; + const closedSnapIndex = snaps.length - 1; + + const toggleVisible = () => setVisible(!isVisible); + + const hide = () => { + bottomSheetRef.current?.snapTo(closedSnapIndex); + }; + + const show = (options: any) => { + setData(options); + toggleVisible(); + }; + + const onBackdropPressed = ({ nativeEvent }: any) => { + if (nativeEvent.oldState === State.ACTIVE) { + hide(); + } + }; + + useBackHandler(() => { + if (isVisible) { + hide(); + } + return isVisible; + }); + + useEffect(() => { + if (isVisible) { + Keyboard.dismiss(); + Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light); + bottomSheetRef.current?.snapTo(openedSnapIndex); + } + }, [isVisible]); + + // Hides action sheet when orientation changes + useEffect(() => { + setVisible(false); + }, [isLandscape]); + + useImperativeHandle(ref, () => ({ + showActionSheet: show, + hideActionSheet: hide + })); + + const renderHandle = () => ( + <> + + {isValidElement(data?.customHeader) ? data.customHeader : null} + + ); + + const renderFooter = () => + data?.hasCancel ? ( + + ) : null; + + const renderItem = ({ item }: any) => ; + + const animatedPosition = React.useRef(new Value(0)); + const opacity = interpolate(animatedPosition.current, { + inputRange: [0, 1], + outputRange: [0, themes[theme].backdropOpacity], + extrapolate: Extrapolate.CLAMP + }); + + return ( + <> + {children} + {isVisible && ( + <> + + + + index === closedSnapIndex && toggleVisible()} + animatedPosition={animatedPosition.current} + containerStyle={ + [ + styles.container, + { backgroundColor: themes[theme].focusedBackground }, + (isLandscape || isTablet) && styles.bottomSheet + ] as any + } + animationConfig={ANIMATION_CONFIG} + // FlatList props + data={data?.options} + renderItem={renderItem} + keyExtractor={(item: any) => item.title} + style={{ backgroundColor: themes[theme].focusedBackground }} + contentContainerStyle={styles.content} + ItemSeparatorComponent={List.Separator} + ListHeaderComponent={List.Separator} + ListFooterComponent={renderFooter} + getItemLayout={getItemLayout} + removeClippedSubviews={isIOS} + /> + + )} + + ); + }) +); + +export default ActionSheet; diff --git a/app/containers/ActionSheet/Button.js b/app/containers/ActionSheet/Button.ts similarity index 100% rename from app/containers/ActionSheet/Button.js rename to app/containers/ActionSheet/Button.ts diff --git a/app/containers/ActionSheet/Handle.js b/app/containers/ActionSheet/Handle.tsx similarity index 73% rename from app/containers/ActionSheet/Handle.js rename to app/containers/ActionSheet/Handle.tsx index 0f19ce3f2..d95262d1e 100644 --- a/app/containers/ActionSheet/Handle.js +++ b/app/containers/ActionSheet/Handle.tsx @@ -1,15 +1,11 @@ import React from 'react'; -import PropTypes from 'prop-types'; import { View } from 'react-native'; import styles from './styles'; import { themes } from '../../constants/colors'; -export const Handle = React.memo(({ theme }) => ( +export const Handle = React.memo(({ theme }: { theme: string }) => ( )); -Handle.propTypes = { - theme: PropTypes.string -}; diff --git a/app/containers/ActionSheet/Item.js b/app/containers/ActionSheet/Item.tsx similarity index 57% rename from app/containers/ActionSheet/Item.js rename to app/containers/ActionSheet/Item.tsx index 2cacd0855..47a3de5a7 100644 --- a/app/containers/ActionSheet/Item.js +++ b/app/containers/ActionSheet/Item.tsx @@ -1,13 +1,25 @@ import React from 'react'; -import PropTypes from 'prop-types'; import { Text, View } from 'react-native'; import { themes } from '../../constants/colors'; import { CustomIcon } from '../../lib/Icons'; -import styles from './styles'; import { Button } from './Button'; +import styles from './styles'; -export const Item = React.memo(({ item, hide, theme }) => { +interface IActionSheetItem { + item: { + title: string; + icon: string; + danger: boolean; + testID: string; + onPress(): void; + right: Function; + }; + theme: string; + hide(): void; +} + +export const Item = React.memo(({ item, hide, theme }: IActionSheetItem) => { const onPress = () => { hide(); item?.onPress(); @@ -18,34 +30,16 @@ export const Item = React.memo(({ item, hide, theme }) => { onPress={onPress} style={[styles.item, { backgroundColor: themes[theme].focusedBackground }]} theme={theme} - testID={item.testID} - > + testID={item.testID}> + style={[styles.title, { color: item.danger ? themes[theme].dangerColor : themes[theme].bodyText }]}> {item.title} - { item.right ? ( - - {item.right ? item.right() : null} - - ) : null } + {item.right ? {item.right ? item.right() : null} : null} ); }); -Item.propTypes = { - item: PropTypes.shape({ - title: PropTypes.string, - icon: PropTypes.string, - danger: PropTypes.bool, - onPress: PropTypes.func, - right: PropTypes.func, - testID: PropTypes.string - }), - hide: PropTypes.func, - theme: PropTypes.string -}; diff --git a/app/containers/ActionSheet/Provider.js b/app/containers/ActionSheet/Provider.js deleted file mode 100644 index 3708e6744..000000000 --- a/app/containers/ActionSheet/Provider.js +++ /dev/null @@ -1,45 +0,0 @@ -import React, { useRef, useContext, forwardRef } from 'react'; -import PropTypes from 'prop-types'; - -import ActionSheet from './ActionSheet'; -import { useTheme } from '../../theme'; - -const context = React.createContext({ - showActionSheet: () => {}, - hideActionSheet: () => {} -}); - -export const useActionSheet = () => useContext(context); - -const { Provider, Consumer } = context; - -export const withActionSheet = Component => forwardRef((props, ref) => ( - - {contexts => } - -)); - -export const ActionSheetProvider = React.memo(({ children }) => { - const ref = useRef(); - const { theme } = useTheme(); - - const getContext = () => ({ - showActionSheet: (options) => { - ref.current?.showActionSheet(options); - }, - hideActionSheet: () => { - ref.current?.hideActionSheet(); - } - }); - - return ( - - - {children} - - - ); -}); -ActionSheetProvider.propTypes = { - children: PropTypes.node -}; diff --git a/app/containers/ActionSheet/Provider.tsx b/app/containers/ActionSheet/Provider.tsx new file mode 100644 index 000000000..8b75577d5 --- /dev/null +++ b/app/containers/ActionSheet/Provider.tsx @@ -0,0 +1,45 @@ +import React, { ForwardedRef, forwardRef, useContext, useRef } from 'react'; + +import ActionSheet from './ActionSheet'; +import { useTheme } from '../../theme'; + +interface IActionSheetProvider { + Provider: any; + Consumer: any; +} + +const context: IActionSheetProvider = React.createContext({ + showActionSheet: () => {}, + hideActionSheet: () => {} +}); + +export const useActionSheet = () => useContext(context); + +const { Provider, Consumer } = context; + +export const withActionSheet = (Component: React.FC) => + forwardRef((props: any, ref: ForwardedRef) => ( + {(contexts: any) => } + )); + +export const ActionSheetProvider = React.memo(({ children }: { children: JSX.Element | JSX.Element[] }) => { + const ref: ForwardedRef = useRef(); + const { theme }: any = useTheme(); + + const getContext = () => ({ + showActionSheet: (options: any) => { + ref.current?.showActionSheet(options); + }, + hideActionSheet: () => { + ref.current?.hideActionSheet(); + } + }); + + return ( + + + <>{children} + + + ); +}); diff --git a/app/containers/ActionSheet/index.js b/app/containers/ActionSheet/index.ts similarity index 100% rename from app/containers/ActionSheet/index.js rename to app/containers/ActionSheet/index.ts diff --git a/app/containers/ActionSheet/styles.js b/app/containers/ActionSheet/styles.ts similarity index 100% rename from app/containers/ActionSheet/styles.js rename to app/containers/ActionSheet/styles.ts diff --git a/app/containers/ActivityIndicator.js b/app/containers/ActivityIndicator.js deleted file mode 100644 index 964d9c267..000000000 --- a/app/containers/ActivityIndicator.js +++ /dev/null @@ -1,40 +0,0 @@ -import React from 'react'; -import { ActivityIndicator, StyleSheet } from 'react-native'; -import { PropTypes } from 'prop-types'; -import { themes } from '../constants/colors'; - -const styles = StyleSheet.create({ - indicator: { - padding: 16, - flex: 1 - }, - absolute: { - position: 'absolute', - left: 0, - right: 0, - top: 0, - bottom: 0, - alignItems: 'center', - justifyContent: 'center' - } -}); - -const RCActivityIndicator = ({ theme, absolute, ...props }) => ( - -); - -RCActivityIndicator.propTypes = { - theme: PropTypes.string, - absolute: PropTypes.bool, - props: PropTypes.object -}; - -RCActivityIndicator.defaultProps = { - theme: 'light' -}; - -export default RCActivityIndicator; diff --git a/app/containers/ActivityIndicator.tsx b/app/containers/ActivityIndicator.tsx new file mode 100644 index 000000000..69ef22ceb --- /dev/null +++ b/app/containers/ActivityIndicator.tsx @@ -0,0 +1,34 @@ +import React from 'react'; +import { ActivityIndicator, ActivityIndicatorProps, StyleSheet } from 'react-native'; + +import { themes } from '../constants/colors'; + +type TTheme = 'light' | 'dark' | 'black' | string; + +interface IActivityIndicator extends ActivityIndicatorProps { + theme?: TTheme; + absolute?: boolean; + props?: object; +} + +const styles = StyleSheet.create({ + indicator: { + padding: 16, + flex: 1 + }, + absolute: { + position: 'absolute', + left: 0, + right: 0, + top: 0, + bottom: 0, + alignItems: 'center', + justifyContent: 'center' + } +}); + +const RCActivityIndicator = ({ theme = 'light', absolute, ...props }: IActivityIndicator) => ( + +); + +export default RCActivityIndicator; diff --git a/app/containers/AppVersion.js b/app/containers/AppVersion.tsx similarity index 65% rename from app/containers/AppVersion.js rename to app/containers/AppVersion.tsx index 2e679d968..217f46d44 100644 --- a/app/containers/AppVersion.js +++ b/app/containers/AppVersion.tsx @@ -1,6 +1,5 @@ import React from 'react'; -import { StyleSheet, View, Text } from 'react-native'; -import PropTypes from 'prop-types'; +import { StyleSheet, Text, View } from 'react-native'; import { themes } from '../constants/colors'; import sharedStyles from '../views/Styles'; @@ -21,14 +20,13 @@ const styles = StyleSheet.create({ } }); -const AppVersion = React.memo(({ theme }) => ( +const AppVersion = React.memo(({ theme }: { theme: string }) => ( - {I18n.t('Version_no', { version: '' })}{getReadableVersion} + + {I18n.t('Version_no', { version: '' })} + {getReadableVersion} + )); -AppVersion.propTypes = { - theme: PropTypes.string -}; - export default AppVersion; diff --git a/app/containers/Avatar/Avatar.js b/app/containers/Avatar/Avatar.js deleted file mode 100644 index 0b898a3c8..000000000 --- a/app/containers/Avatar/Avatar.js +++ /dev/null @@ -1,130 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import { View } from 'react-native'; -import FastImage from '@rocket.chat/react-native-fast-image'; -import Touchable from 'react-native-platform-touchable'; -import { settings as RocketChatSettings } from '@rocket.chat/sdk'; - -import { avatarURL } from '../../utils/avatar'; -import Emoji from '../markdown/Emoji'; - -const Avatar = React.memo(({ - text, - size, - server, - borderRadius, - style, - avatar, - type, - children, - user, - onPress, - emoji, - theme, - getCustomEmoji, - avatarETag, - isStatic, - rid, - blockUnauthenticatedAccess, - serverVersion -}) => { - if ((!text && !avatar && !emoji && !rid) || !server) { - return null; - } - - const avatarStyle = { - width: size, - height: size, - borderRadius - }; - - let image; - if (emoji) { - image = ( - - ); - } else { - let uri = avatar; - if (!isStatic) { - uri = avatarURL({ - type, - text, - size, - user, - avatar, - server, - avatarETag, - serverVersion, - rid, - blockUnauthenticatedAccess - }); - } - - image = ( - - ); - } - - if (onPress) { - image = ( - - {image} - - ); - } - - - return ( - - {image} - {children} - - ); -}); - -Avatar.propTypes = { - server: PropTypes.string, - style: PropTypes.any, - text: PropTypes.string, - avatar: PropTypes.string, - emoji: PropTypes.string, - size: PropTypes.number, - borderRadius: PropTypes.number, - type: PropTypes.string, - children: PropTypes.object, - user: PropTypes.shape({ - id: PropTypes.string, - token: PropTypes.string - }), - theme: PropTypes.string, - onPress: PropTypes.func, - getCustomEmoji: PropTypes.func, - avatarETag: PropTypes.string, - isStatic: PropTypes.bool, - rid: PropTypes.string, - blockUnauthenticatedAccess: PropTypes.bool, - serverVersion: PropTypes.string -}; - -Avatar.defaultProps = { - text: '', - size: 25, - type: 'd', - borderRadius: 4 -}; - -export default Avatar; diff --git a/app/containers/Avatar/Avatar.tsx b/app/containers/Avatar/Avatar.tsx new file mode 100644 index 000000000..e35f0f589 --- /dev/null +++ b/app/containers/Avatar/Avatar.tsx @@ -0,0 +1,96 @@ +import React from 'react'; +import { View } from 'react-native'; +import FastImage from '@rocket.chat/react-native-fast-image'; +import Touchable from 'react-native-platform-touchable'; +import { settings as RocketChatSettings } from '@rocket.chat/sdk'; + +import { avatarURL } from '../../utils/avatar'; +import Emoji from '../markdown/Emoji'; +import { IAvatar } from './interfaces'; + +const Avatar = React.memo( + ({ + server, + style, + avatar, + children, + user, + onPress, + emoji, + theme, + getCustomEmoji, + avatarETag, + isStatic, + rid, + blockUnauthenticatedAccess, + serverVersion, + text, + size = 25, + borderRadius = 4, + type = 'd' + }: Partial) => { + if ((!text && !avatar && !emoji && !rid) || !server) { + return null; + } + + const avatarStyle = { + width: size, + height: size, + borderRadius + }; + + let image; + if (emoji) { + image = ( + + ); + } else { + let uri = avatar; + if (!isStatic) { + uri = avatarURL({ + type, + text, + size, + user, + avatar, + server, + avatarETag, + serverVersion, + rid, + blockUnauthenticatedAccess + }); + } + + image = ( + + ); + } + + if (onPress) { + image = {image}; + } + + return ( + + {image} + {children} + + ); + } +); + +export default Avatar; diff --git a/app/containers/Avatar/index.js b/app/containers/Avatar/index.tsx similarity index 73% rename from app/containers/Avatar/index.js rename to app/containers/Avatar/index.tsx index 4d7c6d7a5..9c95db839 100644 --- a/app/containers/Avatar/index.js +++ b/app/containers/Avatar/index.tsx @@ -1,27 +1,23 @@ import React from 'react'; -import PropTypes from 'prop-types'; import { connect } from 'react-redux'; import { Q } from '@nozbe/watermelondb'; import database from '../../lib/database'; import { getUserSelector } from '../../selectors/login'; import Avatar from './Avatar'; +import { IAvatar } from './interfaces'; -class AvatarContainer extends React.Component { - static propTypes = { - rid: PropTypes.string, - text: PropTypes.string, - type: PropTypes.string, - blockUnauthenticatedAccess: PropTypes.bool, - serverVersion: PropTypes.string - }; +class AvatarContainer extends React.Component, any> { + private mounted: boolean; + + private subscription!: any; static defaultProps = { text: '', type: 'd' }; - constructor(props) { + constructor(props: Partial) { super(props); this.mounted = false; this.state = { avatarETag: '' }; @@ -32,7 +28,7 @@ class AvatarContainer extends React.Component { this.mounted = true; } - componentDidUpdate(prevProps) { + componentDidUpdate(prevProps: any) { const { text, type } = this.props; if (prevProps.text !== text || prevProps.type !== type) { this.init(); @@ -50,7 +46,7 @@ class AvatarContainer extends React.Component { return type === 'd'; } - init = async() => { + init = async () => { const db = database.active; const usersCollection = db.get('users'); const subsCollection = db.get('subscriptions'); @@ -59,7 +55,7 @@ class AvatarContainer extends React.Component { try { if (this.isDirect) { const { text } = this.props; - const [user] = await usersCollection.query(Q.where('username', text)).fetch(); + const [user] = await usersCollection.query(Q.where('username', text!)).fetch(); record = user; } else { const { rid } = this.props; @@ -71,37 +67,32 @@ class AvatarContainer extends React.Component { if (record) { const observable = record.observe(); - this.subscription = observable.subscribe((r) => { + this.subscription = observable.subscribe((r: any) => { const { avatarETag } = r; if (this.mounted) { this.setState({ avatarETag }); } else { + // @ts-ignore this.state.avatarETag = avatarETag; } }); } - } + }; render() { const { avatarETag } = this.state; const { serverVersion } = this.props; - return ( - - ); + return ; } } -const mapStateToProps = state => ({ +const mapStateToProps = (state: any) => ({ user: getUserSelector(state), server: state.share.server.server || state.server.server, serverVersion: state.share.server.version || state.server.version, blockUnauthenticatedAccess: - state.share.settings?.Accounts_AvatarBlockUnauthenticatedAccess - ?? state.settings.Accounts_AvatarBlockUnauthenticatedAccess - ?? true + state.share.settings?.Accounts_AvatarBlockUnauthenticatedAccess ?? + state.settings.Accounts_AvatarBlockUnauthenticatedAccess ?? + true }); export default connect(mapStateToProps)(AvatarContainer); diff --git a/app/containers/Avatar/interfaces.ts b/app/containers/Avatar/interfaces.ts new file mode 100644 index 000000000..692c4d0a3 --- /dev/null +++ b/app/containers/Avatar/interfaces.ts @@ -0,0 +1,23 @@ +export interface IAvatar { + server: string; + style: any; + text: string; + avatar: string; + emoji: string; + size: number; + borderRadius: number; + type: string; + children: JSX.Element; + user: { + id: string; + token: string; + }; + theme: string; + onPress(): void; + getCustomEmoji(): any; + avatarETag: string; + isStatic: boolean; + rid: string; + blockUnauthenticatedAccess: boolean; + serverVersion: string; +} diff --git a/app/containers/BackgroundContainer/index.stories.js b/app/containers/BackgroundContainer/index.stories.js index 0b6c9a1c6..4d1abfb65 100644 --- a/app/containers/BackgroundContainer/index.stories.js +++ b/app/containers/BackgroundContainer/index.stories.js @@ -2,48 +2,30 @@ import React from 'react'; import { storiesOf } from '@storybook/react-native'; -import BackgroundContainer from '.'; import { ThemeContext } from '../../theme'; import { longText } from '../../../storybook/utils'; +import BackgroundContainer from '.'; const stories = storiesOf('BackgroundContainer', module); -stories.add('basic', () => ( - -)); +stories.add('basic', () => ); -stories.add('loading', () => ( - -)); +stories.add('loading', () => ); -stories.add('text', () => ( - -)); +stories.add('text', () => ); -stories.add('long text', () => ( - -)); +stories.add('long text', () => ); const ThemeStory = ({ theme, ...props }) => ( - + ); -stories.add('dark theme - loading', () => ( - -)); +stories.add('dark theme - loading', () => ); -stories.add('dark theme - text', () => ( - -)); +stories.add('dark theme - text', () => ); -stories.add('black theme - loading', () => ( - -)); +stories.add('black theme - loading', () => ); -stories.add('black theme - text', () => ( - -)); +stories.add('black theme - text', () => ); diff --git a/app/containers/BackgroundContainer/index.js b/app/containers/BackgroundContainer/index.tsx similarity index 57% rename from app/containers/BackgroundContainer/index.js rename to app/containers/BackgroundContainer/index.tsx index ed4716b74..fbe5d3077 100644 --- a/app/containers/BackgroundContainer/index.js +++ b/app/containers/BackgroundContainer/index.tsx @@ -1,13 +1,16 @@ import React from 'react'; -import { - ImageBackground, StyleSheet, Text, View, ActivityIndicator -} from 'react-native'; -import PropTypes from 'prop-types'; +import { ActivityIndicator, ImageBackground, StyleSheet, Text, View } from 'react-native'; import { withTheme } from '../../theme'; import sharedStyles from '../../views/Styles'; import { themes } from '../../constants/colors'; +interface IBackgroundContainer { + text: string; + theme: string; + loading: boolean; +} + const styles = StyleSheet.create({ container: { flex: 1 @@ -29,17 +32,12 @@ const styles = StyleSheet.create({ } }); -const BackgroundContainer = ({ theme, text, loading }) => ( +const BackgroundContainer = ({ theme, text, loading }: IBackgroundContainer) => ( - + {text ? {text} : null} - {loading ? : null} + {loading ? : null} ); -BackgroundContainer.propTypes = { - text: PropTypes.string, - theme: PropTypes.string, - loading: PropTypes.bool -}; export default withTheme(BackgroundContainer); diff --git a/app/containers/Button/index.js b/app/containers/Button/index.js deleted file mode 100644 index 75a09290f..000000000 --- a/app/containers/Button/index.js +++ /dev/null @@ -1,94 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import { StyleSheet, Text } from 'react-native'; -import Touchable from 'react-native-platform-touchable'; - -import { themes } from '../../constants/colors'; -import sharedStyles from '../../views/Styles'; -import ActivityIndicator from '../ActivityIndicator'; - -const styles = StyleSheet.create({ - container: { - paddingHorizontal: 14, - justifyContent: 'center', - height: 48, - borderRadius: 2, - marginBottom: 12 - }, - text: { - fontSize: 16, - ...sharedStyles.textMedium, - ...sharedStyles.textAlignCenter - }, - disabled: { - opacity: 0.3 - } -}); - -export default class Button extends React.PureComponent { - static propTypes = { - title: PropTypes.oneOfType([PropTypes.string, PropTypes.element]), - type: PropTypes.string, - onPress: PropTypes.func, - disabled: PropTypes.bool, - backgroundColor: PropTypes.string, - loading: PropTypes.bool, - theme: PropTypes.string, - color: PropTypes.string, - fontSize: PropTypes.string, - style: PropTypes.any - } - - static defaultProps = { - title: 'Press me!', - type: 'primary', - onPress: () => alert('It works!'), - disabled: false, - loading: false - } - - render() { - const { - title, type, onPress, disabled, backgroundColor, color, loading, style, theme, fontSize, ...otherProps - } = this.props; - const isPrimary = type === 'primary'; - - let textColor = isPrimary ? themes[theme].buttonText : themes[theme].bodyText; - if (color) { - textColor = color; - } - - return ( - - { - loading - ? - : ( - - {title} - - ) - } - - ); - } -} diff --git a/app/containers/Button/index.tsx b/app/containers/Button/index.tsx new file mode 100644 index 000000000..9e475a679 --- /dev/null +++ b/app/containers/Button/index.tsx @@ -0,0 +1,84 @@ +import React from 'react'; +import { StyleSheet, Text } from 'react-native'; +import Touchable from 'react-native-platform-touchable'; + +import { themes } from '../../constants/colors'; +import sharedStyles from '../../views/Styles'; +import ActivityIndicator from '../ActivityIndicator'; + +interface IButtonProps { + title: string; + type: string; + onPress(): void; + disabled: boolean; + backgroundColor: string; + loading: boolean; + theme: string; + color: string; + fontSize: any; + style: any; + styleText?: any; + testID: string; +} + +const styles = StyleSheet.create({ + container: { + paddingHorizontal: 14, + justifyContent: 'center', + height: 48, + borderRadius: 2, + marginBottom: 12 + }, + text: { + fontSize: 16, + ...sharedStyles.textMedium, + ...sharedStyles.textAlignCenter + }, + disabled: { + opacity: 0.3 + } +}); + +export default class Button extends React.PureComponent, any> { + static defaultProps = { + title: 'Press me!', + type: 'primary', + onPress: () => alert('It works!'), + disabled: false, + loading: false + }; + + render() { + const { title, type, onPress, disabled, backgroundColor, color, loading, style, theme, fontSize, styleText, ...otherProps } = + this.props; + const isPrimary = type === 'primary'; + + let textColor = isPrimary ? themes[theme!].buttonText : themes[theme!].bodyText; + if (color) { + textColor = color; + } + + return ( + + {loading ? ( + + ) : ( + + {title} + + )} + + ); + } +} diff --git a/app/containers/Check.js b/app/containers/Check.tsx similarity index 52% rename from app/containers/Check.js rename to app/containers/Check.tsx index e9a6b73b8..9ee489dfd 100644 --- a/app/containers/Check.js +++ b/app/containers/Check.tsx @@ -1,10 +1,13 @@ import React from 'react'; import { StyleSheet } from 'react-native'; -import PropTypes from 'prop-types'; import { CustomIcon } from '../lib/Icons'; import { themes } from '../constants/colors'; +interface ICheck { + style?: object; + theme: string; +} const styles = StyleSheet.create({ icon: { width: 22, @@ -13,11 +16,8 @@ const styles = StyleSheet.create({ } }); -const Check = React.memo(({ theme, style }) => ); - -Check.propTypes = { - style: PropTypes.object, - theme: PropTypes.string -}; +const Check = React.memo(({ theme, style }: ICheck) => ( + +)); export default Check; diff --git a/app/containers/EmojiPicker/CustomEmoji.js b/app/containers/EmojiPicker/CustomEmoji.js deleted file mode 100644 index 934155fec..000000000 --- a/app/containers/EmojiPicker/CustomEmoji.js +++ /dev/null @@ -1,26 +0,0 @@ -import React from 'react'; -import FastImage from '@rocket.chat/react-native-fast-image'; -import PropTypes from 'prop-types'; - -const CustomEmoji = React.memo(({ baseUrl, emoji, style }) => ( - -), (prevProps, nextProps) => { - const prevEmoji = prevProps.emoji.content || prevProps.emoji.name; - const nextEmoji = nextProps.emoji.content || nextProps.emoji.name; - return prevEmoji === nextEmoji; -}); - -CustomEmoji.propTypes = { - baseUrl: PropTypes.string.isRequired, - emoji: PropTypes.object.isRequired, - style: PropTypes.any -}; - -export default CustomEmoji; diff --git a/app/containers/EmojiPicker/CustomEmoji.tsx b/app/containers/EmojiPicker/CustomEmoji.tsx new file mode 100644 index 000000000..f64fa2ae5 --- /dev/null +++ b/app/containers/EmojiPicker/CustomEmoji.tsx @@ -0,0 +1,24 @@ +import React from 'react'; +import FastImage from '@rocket.chat/react-native-fast-image'; + +import { ICustomEmoji } from './interfaces'; + +const CustomEmoji = React.memo( + ({ baseUrl, emoji, style }: ICustomEmoji) => ( + + ), + (prevProps, nextProps) => { + const prevEmoji = prevProps.emoji.content || prevProps.emoji.name; + const nextEmoji = nextProps.emoji.content || nextProps.emoji.name; + return prevEmoji === nextEmoji; + } +); + +export default CustomEmoji; diff --git a/app/containers/EmojiPicker/EmojiCategory.js b/app/containers/EmojiPicker/EmojiCategory.tsx similarity index 58% rename from app/containers/EmojiPicker/EmojiCategory.js rename to app/containers/EmojiPicker/EmojiCategory.tsx index 8e1a124ff..62285fe87 100644 --- a/app/containers/EmojiPicker/EmojiCategory.js +++ b/app/containers/EmojiPicker/EmojiCategory.tsx @@ -1,44 +1,41 @@ import React from 'react'; -import PropTypes from 'prop-types'; -import { Text, TouchableOpacity, FlatList } from 'react-native'; +import { FlatList, Text, TouchableOpacity } from 'react-native'; import shortnameToUnicode from '../../utils/shortnameToUnicode'; import styles from './styles'; import CustomEmoji from './CustomEmoji'; import scrollPersistTaps from '../../utils/scrollPersistTaps'; +import { IEmoji, IEmojiCategory } from './interfaces'; const EMOJI_SIZE = 50; -const renderEmoji = (emoji, size, baseUrl) => { +const renderEmoji = (emoji: IEmoji, size: number, baseUrl: string) => { if (emoji && emoji.isCustom) { - return ; + return ( + + ); } return ( - {shortnameToUnicode(`:${ emoji }:`)} + {shortnameToUnicode(`:${emoji}:`)} ); }; -class EmojiCategory extends React.Component { - static propTypes = { - baseUrl: PropTypes.string.isRequired, - emojis: PropTypes.any, - onEmojiSelected: PropTypes.func, - emojisPerRow: PropTypes.number, - width: PropTypes.number - } - - renderItem(emoji) { +class EmojiCategory extends React.Component> { + renderItem(emoji: any) { const { baseUrl, onEmojiSelected } = this.props; return ( onEmojiSelected(emoji)} - testID={`reaction-picker-${ emoji && emoji.isCustom ? emoji.content : emoji }`} - > - {renderEmoji(emoji, EMOJI_SIZE, baseUrl)} + onPress={() => onEmojiSelected!(emoji)} + testID={`reaction-picker-${emoji && emoji.isCustom ? emoji.content : emoji}`}> + {renderEmoji(emoji, EMOJI_SIZE, baseUrl!)} ); } @@ -51,13 +48,14 @@ class EmojiCategory extends React.Component { } const numColumns = Math.trunc(width / EMOJI_SIZE); - const marginHorizontal = (width - (numColumns * EMOJI_SIZE)) / 2; + const marginHorizontal = (width - numColumns * EMOJI_SIZE) / 2; return ( + // @ts-ignore (item && item.isCustom && item.content) || item} data={emojis} extraData={this.props} diff --git a/app/containers/EmojiPicker/TabBar.js b/app/containers/EmojiPicker/TabBar.js deleted file mode 100644 index 834f587a5..000000000 --- a/app/containers/EmojiPicker/TabBar.js +++ /dev/null @@ -1,49 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import { View, TouchableOpacity, Text } from 'react-native'; -import styles from './styles'; -import { themes } from '../../constants/colors'; - -export default class TabBar extends React.Component { - static propTypes = { - goToPage: PropTypes.func, - activeTab: PropTypes.number, - tabs: PropTypes.array, - tabEmojiStyle: PropTypes.object, - theme: PropTypes.string - } - - shouldComponentUpdate(nextProps) { - const { activeTab, theme } = this.props; - if (nextProps.activeTab !== activeTab) { - return true; - } - if (nextProps.theme !== theme) { - return true; - } - return false; - } - - render() { - const { - tabs, goToPage, tabEmojiStyle, activeTab, theme - } = this.props; - - return ( - - {tabs.map((tab, i) => ( - goToPage(i)} - style={styles.tab} - testID={`reaction-picker-${ tab }`} - > - {tab} - {activeTab === i ? : } - - ))} - - ); - } -} diff --git a/app/containers/EmojiPicker/TabBar.tsx b/app/containers/EmojiPicker/TabBar.tsx new file mode 100644 index 000000000..2517d6d15 --- /dev/null +++ b/app/containers/EmojiPicker/TabBar.tsx @@ -0,0 +1,50 @@ +import React from 'react'; +import { Text, TouchableOpacity, View } from 'react-native'; + +import styles from './styles'; +import { themes } from '../../constants/colors'; + +interface ITabBarProps { + goToPage: Function; + activeTab: number; + tabs: []; + tabEmojiStyle: object; + theme: string; +} + +export default class TabBar extends React.Component> { + shouldComponentUpdate(nextProps: any) { + const { activeTab, theme } = this.props; + if (nextProps.activeTab !== activeTab) { + return true; + } + if (nextProps.theme !== theme) { + return true; + } + return false; + } + + render() { + const { tabs, goToPage, tabEmojiStyle, activeTab, theme } = this.props; + + return ( + + {tabs!.map((tab, i) => ( + goToPage!(i)} + style={styles.tab} + testID={`reaction-picker-${tab}`}> + {tab} + {activeTab === i ? ( + + ) : ( + + )} + + ))} + + ); + } +} diff --git a/app/containers/EmojiPicker/categories.js b/app/containers/EmojiPicker/categories.ts similarity index 100% rename from app/containers/EmojiPicker/categories.js rename to app/containers/EmojiPicker/categories.ts diff --git a/app/containers/EmojiPicker/index.js b/app/containers/EmojiPicker/index.tsx similarity index 65% rename from app/containers/EmojiPicker/index.js rename to app/containers/EmojiPicker/index.tsx index 89446c067..64f5dbfe3 100644 --- a/app/containers/EmojiPicker/index.js +++ b/app/containers/EmojiPicker/index.tsx @@ -1,6 +1,5 @@ import React, { Component } from 'react'; import { View } from 'react-native'; -import PropTypes from 'prop-types'; import ScrollableTabView from 'react-native-scrollable-tab-view'; import { dequal } from 'dequal'; import { connect } from 'react-redux'; @@ -18,22 +17,33 @@ import shortnameToUnicode from '../../utils/shortnameToUnicode'; import log from '../../utils/log'; import { themes } from '../../constants/colors'; import { withTheme } from '../../theme'; +import { IEmoji } from './interfaces'; const scrollProps = { keyboardShouldPersistTaps: 'always', keyboardDismissMode: 'none' }; -class EmojiPicker extends Component { - static propTypes = { - baseUrl: PropTypes.string.isRequired, - customEmojis: PropTypes.object, - onEmojiSelected: PropTypes.func, - tabEmojiStyle: PropTypes.object, - theme: PropTypes.string - }; +interface IEmojiPickerProps { + isMessageContainsOnlyEmoji: boolean; + getCustomEmoji?: Function; + baseUrl: string; + customEmojis?: any; + style: object; + theme?: string; + onEmojiSelected?: Function; + tabEmojiStyle?: object; +} - constructor(props) { +interface IEmojiPickerState { + frequentlyUsed: []; + customEmojis: any; + show: boolean; + width: number | null; +} + +class EmojiPicker extends Component { + constructor(props: IEmojiPickerProps) { super(props); const customEmojis = Object.keys(props.customEmojis) .filter(item => item === props.customEmojis[item].name) @@ -55,7 +65,7 @@ class EmojiPicker extends Component { this.setState({ show: true }); } - shouldComponentUpdate(nextProps, nextState) { + shouldComponentUpdate(nextProps: any, nextState: any) { const { frequentlyUsed, show, width } = this.state; const { theme } = this.props; if (nextProps.theme !== theme) { @@ -73,67 +83,72 @@ class EmojiPicker extends Component { return false; } - onEmojiSelected = (emoji) => { + onEmojiSelected = (emoji: IEmoji) => { try { const { onEmojiSelected } = this.props; if (emoji.isCustom) { this._addFrequentlyUsed({ - content: emoji.content, extension: emoji.extension, isCustom: true + content: emoji.content, + extension: emoji.extension, + isCustom: true }); - onEmojiSelected(`:${ emoji.content }:`); + onEmojiSelected!(`:${emoji.content}:`); } else { const content = emoji; this._addFrequentlyUsed({ content, isCustom: false }); - const shortname = `:${ emoji }:`; - onEmojiSelected(shortnameToUnicode(shortname), shortname); + const shortname = `:${emoji}:`; + onEmojiSelected!(shortnameToUnicode(shortname), shortname); } } catch (e) { log(e); } - } + }; - // eslint-disable-next-line react/sort-comp - _addFrequentlyUsed = protectedFunction(async(emoji) => { + _addFrequentlyUsed = protectedFunction(async (emoji: IEmoji) => { const db = database.active; const freqEmojiCollection = db.get('frequently_used_emojis'); - let freqEmojiRecord; + let freqEmojiRecord: any; try { freqEmojiRecord = await freqEmojiCollection.find(emoji.content); } catch (error) { // Do nothing } - await db.action(async() => { + await db.action(async () => { if (freqEmojiRecord) { - await freqEmojiRecord.update((f) => { + await freqEmojiRecord.update((f: any) => { f.count += 1; }); } else { - await freqEmojiCollection.create((f) => { + await freqEmojiCollection.create((f: any) => { f._raw = sanitizedRaw({ id: emoji.content }, freqEmojiCollection.schema); Object.assign(f, emoji); f.count = 1; }); } }); - }) + }); - updateFrequentlyUsed = async() => { + updateFrequentlyUsed = async () => { const db = database.active; const frequentlyUsedRecords = await db.get('frequently_used_emojis').query().fetch(); - let frequentlyUsed = orderBy(frequentlyUsedRecords, ['count'], ['desc']); - frequentlyUsed = frequentlyUsed.map((item) => { + let frequentlyUsed: any = orderBy(frequentlyUsedRecords, ['count'], ['desc']); + frequentlyUsed = frequentlyUsed.map((item: IEmoji) => { if (item.isCustom) { return { content: item.content, extension: item.extension, isCustom: item.isCustom }; } - return shortnameToUnicode(`${ item.content }`); + return shortnameToUnicode(`${item.content}`); }); this.setState({ frequentlyUsed }); - } + }; - onLayout = ({ nativeEvent: { layout: { width } } }) => this.setState({ width }); + onLayout = ({ + nativeEvent: { + layout: { width } + } + }: any) => this.setState({ width }); - renderCategory(category, i, label) { + renderCategory(category: any, i: number, label: string) { const { frequentlyUsed, customEmojis, width } = this.state; const { baseUrl } = this.props; @@ -148,9 +163,9 @@ class EmojiPicker extends Component { return ( this.onEmojiSelected(emoji)} + onEmojiSelected={(emoji: IEmoji) => this.onEmojiSelected(emoji)} style={styles.categoryContainer} - width={width} + width={width!} baseUrl={baseUrl} tabLabel={label} /> @@ -168,23 +183,21 @@ class EmojiPicker extends Component { } + /* @ts-ignore*/ contentProps={scrollProps} - style={{ backgroundColor: themes[theme].focusedBackground }} - > - { - categories.tabs.map((tab, i) => ( - (i === 0 && frequentlyUsed.length === 0) ? null // when no frequentlyUsed don't show the tab - : ( - this.renderCategory(tab.category, i, tab.tabLabel) - ))) - } + style={{ backgroundColor: themes[theme!].focusedBackground }}> + {categories.tabs.map((tab, i) => + i === 0 && frequentlyUsed.length === 0 + ? null // when no frequentlyUsed don't show the tab + : this.renderCategory(tab.category, i, tab.tabLabel) + )} ); } } -const mapStateToProps = state => ({ +const mapStateToProps = (state: any) => ({ customEmojis: state.customEmojis }); diff --git a/app/containers/EmojiPicker/interfaces.ts b/app/containers/EmojiPicker/interfaces.ts new file mode 100644 index 000000000..288497f9a --- /dev/null +++ b/app/containers/EmojiPicker/interfaces.ts @@ -0,0 +1,22 @@ +export interface IEmoji { + content: any; + name: string; + extension: any; + isCustom: boolean; +} + +export interface ICustomEmoji { + baseUrl: string; + emoji: IEmoji; + style: any; +} + +export interface IEmojiCategory { + baseUrl: string; + emojis: IEmoji[]; + onEmojiSelected: Function; + emojisPerRow: number; + width: number; + style: any; + tabLabel: string; +} diff --git a/app/containers/EmojiPicker/styles.js b/app/containers/EmojiPicker/styles.ts similarity index 100% rename from app/containers/EmojiPicker/styles.js rename to app/containers/EmojiPicker/styles.ts diff --git a/app/containers/FormContainer.js b/app/containers/FormContainer.tsx similarity index 72% rename from app/containers/FormContainer.js rename to app/containers/FormContainer.tsx index ac58502c7..522bf489e 100644 --- a/app/containers/FormContainer.js +++ b/app/containers/FormContainer.tsx @@ -1,6 +1,5 @@ import React from 'react'; import { ScrollView, StyleSheet, View } from 'react-native'; -import PropTypes from 'prop-types'; import { themes } from '../constants/colors'; import sharedStyles from '../views/Styles'; @@ -11,33 +10,35 @@ import AppVersion from './AppVersion'; import { isTablet } from '../utils/deviceInfo'; import SafeAreaView from './SafeAreaView'; +interface IFormContainer { + theme: string; + testID: string; + children: JSX.Element; +} + const styles = StyleSheet.create({ scrollView: { minHeight: '100%' } }); -export const FormContainerInner = ({ children }) => ( - - {children} - +export const FormContainerInner = ({ children }: { children: JSX.Element }) => ( + {children} ); -const FormContainer = ({ - children, theme, testID, ...props -}) => ( +const FormContainer = ({ children, theme, testID, ...props }: IFormContainer) => ( + // @ts-ignore + keyboardVerticalOffset={128}> + {/* @ts-ignore*/} + {...props}> {children} @@ -46,14 +47,4 @@ const FormContainer = ({ ); -FormContainer.propTypes = { - theme: PropTypes.string, - testID: PropTypes.string, - children: PropTypes.element -}; - -FormContainerInner.propTypes = { - children: PropTypes.element -}; - export default FormContainer; diff --git a/app/containers/Header/index.js b/app/containers/Header/index.tsx similarity index 73% rename from app/containers/Header/index.js rename to app/containers/Header/index.tsx index 4663c7d43..e9f7837c6 100644 --- a/app/containers/Header/index.js +++ b/app/containers/Header/index.tsx @@ -1,7 +1,7 @@ import React from 'react'; -import PropTypes from 'prop-types'; import { SafeAreaView } from 'react-native-safe-area-context'; -import { View, StyleSheet } from 'react-native'; +import { StyleSheet, View } from 'react-native'; + import { themes } from '../../constants/colors'; import { themedHeader } from '../../utils/navigation'; import { isIOS, isTablet } from '../../utils/deviceInfo'; @@ -10,18 +10,25 @@ import { withTheme } from '../../theme'; // Get from https://github.com/react-navigation/react-navigation/blob/master/packages/stack/src/views/Header/HeaderSegment.tsx#L69 export const headerHeight = isIOS ? 44 : 56; -export const getHeaderHeight = (isLandscape) => { +export const getHeaderHeight = (isLandscape: boolean) => { if (isIOS) { if (isLandscape && !isTablet) { return 32; - } else { - return 44; } + return 44; } return 56; }; -export const getHeaderTitlePosition = ({ insets, numIconsRight }) => ({ +interface IHeaderTitlePosition { + insets: { + left: number; + right: number; + }; + numIconsRight: number; +} + +export const getHeaderTitlePosition = ({ insets, numIconsRight }: IHeaderTitlePosition) => ({ left: insets.left + 60, right: insets.right + Math.max(45 * numIconsRight, 15) }); @@ -35,9 +42,14 @@ const styles = StyleSheet.create({ } }); -const Header = ({ - theme, headerLeft, headerTitle, headerRight -}) => ( +interface IHeader { + theme: string; + headerLeft(): void; + headerTitle(): void; + headerRight(): void; +} + +const Header = ({ theme, headerLeft, headerTitle, headerRight }: IHeader) => ( {headerLeft ? headerLeft() : null} @@ -47,11 +59,4 @@ const Header = ({ ); -Header.propTypes = { - theme: PropTypes.string, - headerLeft: PropTypes.element, - headerTitle: PropTypes.element, - headerRight: PropTypes.element -}; - export default withTheme(Header); diff --git a/app/containers/HeaderButton/Common.js b/app/containers/HeaderButton/Common.js deleted file mode 100644 index a1d55bd4e..000000000 --- a/app/containers/HeaderButton/Common.js +++ /dev/null @@ -1,84 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; - -import { isIOS } from '../../utils/deviceInfo'; -import I18n from '../../i18n'; -import Container from './HeaderButtonContainer'; -import Item from './HeaderButtonItem'; - -// Left -export const Drawer = React.memo(({ navigation, testID, ...props }) => ( - - navigation.toggleDrawer()} testID={testID} {...props} /> - -)); - -export const CloseModal = React.memo(({ - navigation, testID, onPress = () => navigation.pop(), ...props -}) => ( - - - -)); - -export const CancelModal = React.memo(({ onPress, testID }) => ( - - {isIOS - ? - : - } - -)); - -// Right -export const More = React.memo(({ onPress, testID }) => ( - - - -)); - -export const Download = React.memo(({ onPress, testID, ...props }) => ( - - - -)); - -export const Preferences = React.memo(({ onPress, testID, ...props }) => ( - - - -)); - -export const Legal = React.memo(({ navigation, testID }) => ( - navigation.navigate('LegalView')} testID={testID} /> -)); - -Drawer.propTypes = { - navigation: PropTypes.object.isRequired, - testID: PropTypes.string.isRequired -}; -CloseModal.propTypes = { - navigation: PropTypes.object.isRequired, - testID: PropTypes.string.isRequired, - onPress: PropTypes.func -}; -CancelModal.propTypes = { - onPress: PropTypes.func.isRequired, - testID: PropTypes.string.isRequired -}; -More.propTypes = { - onPress: PropTypes.func.isRequired, - testID: PropTypes.string.isRequired -}; -Download.propTypes = { - onPress: PropTypes.func.isRequired, - testID: PropTypes.string.isRequired -}; -Preferences.propTypes = { - onPress: PropTypes.func.isRequired, - testID: PropTypes.string.isRequired -}; -Legal.propTypes = { - navigation: PropTypes.object.isRequired, - testID: PropTypes.string.isRequired -}; diff --git a/app/containers/HeaderButton/Common.tsx b/app/containers/HeaderButton/Common.tsx new file mode 100644 index 000000000..b40543ddb --- /dev/null +++ b/app/containers/HeaderButton/Common.tsx @@ -0,0 +1,60 @@ +import React from 'react'; + +import { isIOS } from '../../utils/deviceInfo'; +import I18n from '../../i18n'; +import Container from './HeaderButtonContainer'; +import Item from './HeaderButtonItem'; + +interface IHeaderButtonCommon { + navigation: any; + onPress?(): void; + testID?: string; +} + +// Left +export const Drawer = React.memo(({ navigation, testID, ...props }: Partial) => ( + + navigation.toggleDrawer()} testID={testID} {...props} /> + +)); + +export const CloseModal = React.memo( + ({ navigation, testID, onPress = () => navigation.pop(), ...props }: IHeaderButtonCommon) => ( + + + + ) +); + +export const CancelModal = React.memo(({ onPress, testID }: Partial) => ( + + {isIOS ? ( + + ) : ( + + )} + +)); + +// Right +export const More = React.memo(({ onPress, testID }: Partial) => ( + + + +)); + +export const Download = React.memo(({ onPress, testID, ...props }: Partial) => ( + + + +)); + +export const Preferences = React.memo(({ onPress, testID, ...props }: Partial) => ( + + + +)); + +export const Legal = React.memo(({ navigation, testID }: Partial) => ( + navigation.navigate('LegalView')} testID={testID} /> +)); diff --git a/app/containers/HeaderButton/HeaderButtonContainer.js b/app/containers/HeaderButton/HeaderButtonContainer.tsx similarity index 55% rename from app/containers/HeaderButton/HeaderButtonContainer.js rename to app/containers/HeaderButton/HeaderButtonContainer.tsx index 1521faa95..2d4c45b6f 100644 --- a/app/containers/HeaderButton/HeaderButtonContainer.js +++ b/app/containers/HeaderButton/HeaderButtonContainer.tsx @@ -1,6 +1,10 @@ import React from 'react'; -import { View, StyleSheet } from 'react-native'; -import PropTypes from 'prop-types'; +import { StyleSheet, View } from 'react-native'; + +interface IHeaderButtonContainer { + children: JSX.Element; + left?: boolean; +} const styles = StyleSheet.create({ container: { @@ -16,21 +20,10 @@ const styles = StyleSheet.create({ } }); -const Container = ({ children, left }) => ( - - {children} - +const Container = ({ children, left = false }: IHeaderButtonContainer) => ( + {children} ); -Container.propTypes = { - children: PropTypes.arrayOf(PropTypes.element), - left: PropTypes.bool -}; - -Container.defaultProps = { - left: false -}; - Container.displayName = 'HeaderButton.Container'; export default Container; diff --git a/app/containers/HeaderButton/HeaderButtonItem.js b/app/containers/HeaderButton/HeaderButtonItem.tsx similarity index 56% rename from app/containers/HeaderButton/HeaderButtonItem.js rename to app/containers/HeaderButton/HeaderButtonItem.tsx index 5e9bb8631..b350232d0 100644 --- a/app/containers/HeaderButton/HeaderButtonItem.js +++ b/app/containers/HeaderButton/HeaderButtonItem.tsx @@ -1,6 +1,5 @@ import React from 'react'; -import { Text, StyleSheet, Platform } from 'react-native'; -import PropTypes from 'prop-types'; +import { Platform, StyleSheet, Text } from 'react-native'; import Touchable from 'react-native-platform-touchable'; import { CustomIcon } from '../../lib/Icons'; @@ -8,8 +7,20 @@ import { withTheme } from '../../theme'; import { themes } from '../../constants/colors'; import sharedStyles from '../../views/Styles'; +interface IHeaderButtonItem { + title: string; + iconName: string; + onPress(): void; + testID: string; + theme: string; + badge(): void; +} + export const BUTTON_HIT_SLOP = { - top: 5, right: 5, bottom: 5, left: 5 + top: 5, + right: 5, + bottom: 5, + left: 5 }; const styles = StyleSheet.create({ @@ -29,30 +40,19 @@ const styles = StyleSheet.create({ } }); -const Item = ({ - title, iconName, onPress, testID, theme, badge -}) => ( +const Item = ({ title, iconName, onPress, testID, theme, badge }: IHeaderButtonItem) => ( <> - { - iconName - ? - : {title} - } + {iconName ? ( + + ) : ( + {title} + )} {badge ? badge() : null} ); -Item.propTypes = { - onPress: PropTypes.func.isRequired, - title: PropTypes.string, - iconName: PropTypes.string, - testID: PropTypes.string, - theme: PropTypes.string, - badge: PropTypes.func -}; - Item.displayName = 'HeaderButton.Item'; export default withTheme(Item); diff --git a/app/containers/HeaderButton/HeaderButtonItemBadge.js b/app/containers/HeaderButton/HeaderButtonItemBadge.tsx similarity index 75% rename from app/containers/HeaderButton/HeaderButtonItemBadge.js rename to app/containers/HeaderButton/HeaderButtonItemBadge.tsx index 9a760b828..9634c8bde 100644 --- a/app/containers/HeaderButton/HeaderButtonItemBadge.js +++ b/app/containers/HeaderButton/HeaderButtonItemBadge.tsx @@ -15,12 +15,6 @@ const styles = StyleSheet.create({ } }); -export const Badge = ({ ...props }) => ( - -); +export const Badge = ({ ...props }) => ; export default Badge; diff --git a/app/containers/HeaderButton/index.js b/app/containers/HeaderButton/index.ts similarity index 100% rename from app/containers/HeaderButton/index.js rename to app/containers/HeaderButton/index.ts diff --git a/app/containers/InAppNotification/NotifierComponent.js b/app/containers/InAppNotification/NotifierComponent.tsx similarity index 72% rename from app/containers/InAppNotification/NotifierComponent.js rename to app/containers/InAppNotification/NotifierComponent.tsx index 6268cd098..4264b2eea 100644 --- a/app/containers/InAppNotification/NotifierComponent.js +++ b/app/containers/InAppNotification/NotifierComponent.tsx @@ -1,6 +1,5 @@ import React from 'react'; -import { StyleSheet, View, Text } from 'react-native'; -import PropTypes from 'prop-types'; +import { StyleSheet, Text, View } from 'react-native'; import Touchable from 'react-native-platform-touchable'; import { connect } from 'react-redux'; import { Notifier } from 'react-native-notifier'; @@ -16,10 +15,13 @@ import { goRoom } from '../../utils/goRoom'; import Navigation from '../../lib/Navigation'; import { useOrientation } from '../../dimensions'; +interface INotifierComponent { + notification: object; + isMasterDetail: boolean; +} + const AVATAR_SIZE = 48; -const BUTTON_HIT_SLOP = { - top: 12, right: 12, bottom: 12, left: 12 -}; +const BUTTON_HIT_SLOP = { top: 12, right: 12, bottom: 12, left: 12 }; const styles = StyleSheet.create({ container: { @@ -64,16 +66,16 @@ const styles = StyleSheet.create({ const hideNotification = () => Notifier.hideNotification(); -const NotifierComponent = React.memo(({ notification, isMasterDetail }) => { - const { theme } = useTheme(); +const NotifierComponent = React.memo(({ notification, isMasterDetail }: INotifierComponent) => { + const { theme }: any = useTheme(); const insets = useSafeAreaInsets(); const { isLandscape } = useOrientation(); - const { text, payload } = notification; + const { text, payload }: any = notification; const { type, rid } = payload; const name = type === 'd' ? payload.sender.username : payload.name; // if sub is not on local database, title and avatar will be null, so we use payload from notification - const { title = name, avatar = name } = notification; + const { title = name, avatar = name }: any = notification; const onPress = () => { const { prid, _id } = payload; @@ -81,7 +83,10 @@ const NotifierComponent = React.memo(({ notification, isMasterDetail }) => { return; } const item = { - rid, name: title, t: type, prid + rid, + name: title, + t: type, + prid }; if (isMasterDetail) { @@ -94,47 +99,41 @@ const NotifierComponent = React.memo(({ notification, isMasterDetail }) => { }; return ( - + + background={Touchable.SelectableBackgroundBorderless()}> <> - {title} - {text} + + {title} + + + {text} + - + ); }); -NotifierComponent.propTypes = { - notification: PropTypes.object, - isMasterDetail: PropTypes.bool -}; - -const mapStateToProps = state => ({ +const mapStateToProps = (state: any) => ({ isMasterDetail: state.app.isMasterDetail }); diff --git a/app/containers/InAppNotification/index.js b/app/containers/InAppNotification/index.js deleted file mode 100644 index 7d22b127a..000000000 --- a/app/containers/InAppNotification/index.js +++ /dev/null @@ -1,57 +0,0 @@ -import React, { memo, useEffect } from 'react'; -import PropTypes from 'prop-types'; -import { NotifierRoot, Notifier, Easing } from 'react-native-notifier'; -import { connect } from 'react-redux'; -import { dequal } from 'dequal'; - -import NotifierComponent from './NotifierComponent'; -import EventEmitter from '../../utils/events'; -import Navigation from '../../lib/Navigation'; -import { getActiveRoute } from '../../utils/navigation'; - -export const INAPP_NOTIFICATION_EMITTER = 'NotificationInApp'; - -const InAppNotification = memo(({ rooms, appState }) => { - const show = (notification) => { - if (appState !== 'foreground') { - return; - } - - const { payload } = notification; - const state = Navigation.navigationRef.current?.getRootState(); - const route = getActiveRoute(state); - if (payload.rid) { - if (rooms.includes(payload.rid) || route?.name === 'JitsiMeetView') { - return; - } - Notifier.showNotification({ - showEasing: Easing.inOut(Easing.quad), - Component: NotifierComponent, - componentProps: { - notification - } - }); - } - }; - - useEffect(() => { - const listener = EventEmitter.addEventListener(INAPP_NOTIFICATION_EMITTER, show); - return () => { - EventEmitter.removeListener(INAPP_NOTIFICATION_EMITTER, listener); - }; - }, [rooms]); - - return ; -}, (prevProps, nextProps) => dequal(prevProps.rooms, nextProps.rooms)); - -const mapStateToProps = state => ({ - rooms: state.room.rooms, - appState: state.app.ready && state.app.foreground ? 'foreground' : 'background' -}); - -InAppNotification.propTypes = { - rooms: PropTypes.array, - appState: PropTypes.string -}; - -export default connect(mapStateToProps)(InAppNotification); diff --git a/app/containers/InAppNotification/index.tsx b/app/containers/InAppNotification/index.tsx new file mode 100644 index 000000000..e708231b1 --- /dev/null +++ b/app/containers/InAppNotification/index.tsx @@ -0,0 +1,54 @@ +import React, { memo, useEffect } from 'react'; +import { Easing, Notifier, NotifierRoot } from 'react-native-notifier'; +import { connect } from 'react-redux'; +import { dequal } from 'dequal'; + +import NotifierComponent from './NotifierComponent'; +import EventEmitter from '../../utils/events'; +import Navigation from '../../lib/Navigation'; +import { getActiveRoute } from '../../utils/navigation'; + +export const INAPP_NOTIFICATION_EMITTER = 'NotificationInApp'; + +const InAppNotification = memo( + ({ rooms, appState }: { rooms: any; appState: string }) => { + const show = (notification: any) => { + if (appState !== 'foreground') { + return; + } + + const { payload } = notification; + const state = Navigation.navigationRef.current?.getRootState(); + const route = getActiveRoute(state); + if (payload.rid) { + if (rooms.includes(payload.rid) || route?.name === 'JitsiMeetView') { + return; + } + Notifier.showNotification({ + showEasing: Easing.inOut(Easing.quad), + Component: NotifierComponent, + componentProps: { + notification + } + }); + } + }; + + useEffect(() => { + const listener = EventEmitter.addEventListener(INAPP_NOTIFICATION_EMITTER, show); + return () => { + EventEmitter.removeListener(INAPP_NOTIFICATION_EMITTER, listener); + }; + }, [rooms]); + + return ; + }, + (prevProps, nextProps) => dequal(prevProps.rooms, nextProps.rooms) +); + +const mapStateToProps = (state: any) => ({ + rooms: state.room.rooms, + appState: state.app.ready && state.app.foreground ? 'foreground' : 'background' +}); + +export default connect(mapStateToProps)(InAppNotification); diff --git a/app/containers/List/ListContainer.js b/app/containers/List/ListContainer.tsx similarity index 76% rename from app/containers/List/ListContainer.js rename to app/containers/List/ListContainer.tsx index f1be3705a..408310d9f 100644 --- a/app/containers/List/ListContainer.js +++ b/app/containers/List/ListContainer.tsx @@ -1,6 +1,6 @@ import React from 'react'; import { ScrollView, StyleSheet } from 'react-native'; -import PropTypes from 'prop-types'; + import { withTheme } from '../../theme'; import scrollPersistTaps from '../../utils/scrollPersistTaps'; @@ -10,21 +10,20 @@ const styles = StyleSheet.create({ } }); -const ListContainer = React.memo(({ children, ...props }) => ( +interface IListContainer { + children: JSX.Element; +} +const ListContainer = React.memo(({ children, ...props }: IListContainer) => ( + // @ts-ignore + {...props}> {children} )); -ListContainer.propTypes = { - children: PropTypes.array.isRequired -}; - ListContainer.displayName = 'List.Container'; export default withTheme(ListContainer); diff --git a/app/containers/List/ListHeader.js b/app/containers/List/ListHeader.tsx similarity index 60% rename from app/containers/List/ListHeader.js rename to app/containers/List/ListHeader.tsx index 1166e25bd..0b67550e3 100644 --- a/app/containers/List/ListHeader.js +++ b/app/containers/List/ListHeader.tsx @@ -1,6 +1,5 @@ import React from 'react'; -import { View, Text, StyleSheet } from 'react-native'; -import PropTypes from 'prop-types'; +import { StyleSheet, Text, View } from 'react-native'; import sharedStyles from '../../views/Styles'; import { themes } from '../../constants/colors'; @@ -19,22 +18,20 @@ const styles = StyleSheet.create({ } }); -const ListHeader = React.memo(({ title, theme, translateTitle }) => ( +interface IListHeader { + title: string; + theme: string; + translateTitle: boolean; +} + +const ListHeader = React.memo(({ title, theme, translateTitle = true }: IListHeader) => ( - {translateTitle ? I18n.t(title) : title} + + {translateTitle ? I18n.t(title) : title} + )); -ListHeader.propTypes = { - title: PropTypes.string, - theme: PropTypes.string, - translateTitle: PropTypes.bool -}; - -ListHeader.defaultProps = { - translateTitle: true -}; - ListHeader.displayName = 'List.Header'; export default withTheme(ListHeader); diff --git a/app/containers/List/ListIcon.js b/app/containers/List/ListIcon.js deleted file mode 100644 index 44941f2ca..000000000 --- a/app/containers/List/ListIcon.js +++ /dev/null @@ -1,44 +0,0 @@ -import React from 'react'; -import { View, StyleSheet } from 'react-native'; -import PropTypes from 'prop-types'; - -import { themes } from '../../constants/colors'; -import { CustomIcon } from '../../lib/Icons'; -import { withTheme } from '../../theme'; -import { ICON_SIZE } from './constants'; - -const styles = StyleSheet.create({ - icon: { - alignItems: 'center', - justifyContent: 'center' - } -}); - -const ListIcon = React.memo(({ - theme, - name, - color, - style, - testID -}) => ( - - - -)); - -ListIcon.propTypes = { - theme: PropTypes.string, - name: PropTypes.string, - color: PropTypes.string, - style: PropTypes.object, - testID: PropTypes.string -}; - -ListIcon.displayName = 'List.Icon'; - -export default withTheme(ListIcon); diff --git a/app/containers/List/ListIcon.tsx b/app/containers/List/ListIcon.tsx new file mode 100644 index 000000000..7d569dce7 --- /dev/null +++ b/app/containers/List/ListIcon.tsx @@ -0,0 +1,32 @@ +import React from 'react'; +import { StyleSheet, View } from 'react-native'; + +import { themes } from '../../constants/colors'; +import { CustomIcon } from '../../lib/Icons'; +import { withTheme } from '../../theme'; +import { ICON_SIZE } from './constants'; + +interface IListIcon { + theme: string; + name: string; + color: string; + style: object; + testID: string; +} + +const styles = StyleSheet.create({ + icon: { + alignItems: 'center', + justifyContent: 'center' + } +}); + +const ListIcon = React.memo(({ theme, name, color, style, testID }: IListIcon) => ( + + + +)); + +ListIcon.displayName = 'List.Icon'; + +export default withTheme(ListIcon); diff --git a/app/containers/List/ListInfo.js b/app/containers/List/ListInfo.tsx similarity index 67% rename from app/containers/List/ListInfo.js rename to app/containers/List/ListInfo.tsx index e0cb9eae7..bc4f02e33 100644 --- a/app/containers/List/ListInfo.js +++ b/app/containers/List/ListInfo.tsx @@ -1,6 +1,5 @@ import React from 'react'; -import { View, Text, StyleSheet } from 'react-native'; -import PropTypes from 'prop-types'; +import { StyleSheet, Text, View } from 'react-native'; import sharedStyles from '../../views/Styles'; import { themes } from '../../constants/colors'; @@ -19,22 +18,18 @@ const styles = StyleSheet.create({ } }); -const ListInfo = React.memo(({ info, translateInfo, theme }) => ( +interface IListHeader { + info: string; + theme: string; + translateInfo: boolean; +} + +const ListInfo = React.memo(({ info, theme, translateInfo = true }: IListHeader) => ( {translateInfo ? I18n.t(info) : info} )); -ListInfo.propTypes = { - info: PropTypes.string, - theme: PropTypes.string, - translateInfo: PropTypes.bool -}; - -ListInfo.defaultProps = { - translateInfo: true -}; - ListInfo.displayName = 'List.Info'; export default withTheme(ListInfo); diff --git a/app/containers/List/ListItem.js b/app/containers/List/ListItem.js deleted file mode 100644 index aa3ecbdf0..000000000 --- a/app/containers/List/ListItem.js +++ /dev/null @@ -1,163 +0,0 @@ -import React from 'react'; -import { - View, Text, StyleSheet, I18nManager -} from 'react-native'; -import PropTypes from 'prop-types'; - -import Touch from '../../utils/touch'; -import { themes } from '../../constants/colors'; -import sharedStyles from '../../views/Styles'; -import { withTheme } from '../../theme'; -import I18n from '../../i18n'; -import { Icon } from '.'; -import { BASE_HEIGHT, ICON_SIZE, PADDING_HORIZONTAL } from './constants'; -import { withDimensions } from '../../dimensions'; -import { CustomIcon } from '../../lib/Icons'; - -const styles = StyleSheet.create({ - container: { - flex: 1, - flexDirection: 'row', - alignItems: 'center', - justifyContent: 'center', - paddingHorizontal: PADDING_HORIZONTAL - }, - leftContainer: { - paddingRight: PADDING_HORIZONTAL - }, - rightContainer: { - paddingLeft: PADDING_HORIZONTAL - }, - disabled: { - opacity: 0.3 - }, - textContainer: { - flex: 1, - justifyContent: 'center' - }, - textAlertContainer: { - flexDirection: 'row', - alignItems: 'center' - }, - alertIcon: { - paddingLeft: 4 - }, - title: { - flexShrink: 1, - fontSize: 16, - ...sharedStyles.textRegular - }, - subtitle: { - fontSize: 14, - ...sharedStyles.textRegular - }, - actionIndicator: { - ...I18nManager.isRTL - ? { transform: [{ rotate: '180deg' }] } - : {} - } -}); - -const Content = React.memo(({ - title, subtitle, disabled, testID, left, right, color, theme, translateTitle, translateSubtitle, showActionIndicator, fontScale, alert -}) => ( - - {left - ? ( - - {left()} - - ) - : null} - - - {translateTitle ? I18n.t(title) : title} - {alert ? ( - - ) : null} - - {subtitle - ? {translateSubtitle ? I18n.t(subtitle) : subtitle} - : null - } - - {right || showActionIndicator - ? ( - - {right ? right() : null} - {showActionIndicator ? : null} - - ) - : null} - -)); - -const Button = React.memo(({ - onPress, backgroundColor, underlayColor, ...props -}) => ( - onPress(props.title)} - style={{ backgroundColor: backgroundColor || themes[props.theme].backgroundColor }} - underlayColor={underlayColor} - enabled={!props.disabled} - theme={props.theme} - > - - -)); - -const ListItem = React.memo(({ ...props }) => { - if (props.onPress) { - return )); -HeaderItem.propTypes = { - item: PropTypes.string, - onReaction: PropTypes.func, - server: PropTypes.string, - theme: PropTypes.string -}; -const HeaderFooter = React.memo(({ onReaction, theme }) => ( +const HeaderFooter = React.memo(({ onReaction, theme }: THeaderFooter) => ( )); -HeaderFooter.propTypes = { - onReaction: PropTypes.func, - theme: PropTypes.string -}; -const Header = React.memo(({ - handleReaction, server, message, isMasterDetail, theme -}) => { +const Header = React.memo(({ handleReaction, server, message, isMasterDetail, theme }: IHeader) => { const [items, setItems] = useState([]); - const { width, height } = useDimensions(); + const { width, height }: any = useDimensions(); - const setEmojis = async() => { + const setEmojis = async () => { try { const db = database.active; const freqEmojiCollection = db.get('frequently_used_emojis'); let freqEmojis = await freqEmojiCollection.query().fetch(); const isLandscape = width > height; - const size = (isLandscape || isMasterDetail ? width / 2 : width) - (CONTAINER_MARGIN * 2); - const quantity = (size / (ITEM_SIZE + (ITEM_MARGIN * 2))) - 1; + const size = (isLandscape || isMasterDetail ? width / 2 : width) - CONTAINER_MARGIN * 2; + const quantity = size / (ITEM_SIZE + ITEM_MARGIN * 2) - 1; freqEmojis = freqEmojis.concat(DEFAULT_EMOJIS).slice(0, quantity); setItems(freqEmojis); @@ -114,11 +114,14 @@ const Header = React.memo(({ setEmojis(); }, []); - const onReaction = ({ emoji }) => handleReaction(emoji, message); + const onReaction = ({ emoji }: { emoji: IEmoji }) => handleReaction(emoji, message); - const renderItem = useCallback(({ item }) => ); + const renderItem = useCallback( + ({ item }) => , + [] + ); - const renderFooter = useCallback(() => ); + const renderFooter = useCallback(() => , []); return ( @@ -135,11 +138,5 @@ const Header = React.memo(({ ); }); -Header.propTypes = { - handleReaction: PropTypes.func, - server: PropTypes.string, - message: PropTypes.object, - isMasterDetail: PropTypes.bool, - theme: PropTypes.string -}; + export default withTheme(Header); diff --git a/app/containers/MessageActions/index.js b/app/containers/MessageActions/index.js deleted file mode 100644 index 91f0934cf..000000000 --- a/app/containers/MessageActions/index.js +++ /dev/null @@ -1,472 +0,0 @@ -import React, { forwardRef, useImperativeHandle } from 'react'; -import PropTypes from 'prop-types'; -import { Alert, Clipboard, Share } from 'react-native'; -import { connect } from 'react-redux'; -import moment from 'moment'; - -import RocketChat from '../../lib/rocketchat'; -import database from '../../lib/database'; -import I18n from '../../i18n'; -import log, { logEvent } from '../../utils/log'; -import Navigation from '../../lib/Navigation'; -import { getMessageTranslation } from '../message/utils'; -import { LISTENER } from '../Toast'; -import EventEmitter from '../../utils/events'; -import { showConfirmationAlert } from '../../utils/info'; -import { useActionSheet } from '../ActionSheet'; -import Header, { HEADER_HEIGHT } from './Header'; -import events from '../../utils/log/events'; - -const MessageActions = React.memo(forwardRef(({ - room, - tmid, - user, - editInit, - reactionInit, - onReactionPress, - replyInit, - isReadOnly, - server, - Message_AllowDeleting, - Message_AllowDeleting_BlockDeleteInMinutes, - Message_AllowEditing, - Message_AllowEditing_BlockEditInMinutes, - Message_AllowPinning, - Message_AllowStarring, - Message_Read_Receipt_Store_Users, - isMasterDetail, - editMessagePermission, - deleteMessagePermission, - forceDeleteMessagePermission, - pinMessagePermission -}, ref) => { - let permissions = {}; - const { showActionSheet, hideActionSheet } = useActionSheet(); - - const getPermissions = async() => { - try { - const permission = [editMessagePermission, deleteMessagePermission, forceDeleteMessagePermission, pinMessagePermission]; - const result = await RocketChat.hasPermission(permission, room.rid); - permissions = { - hasEditPermission: result[0], - hasDeletePermission: result[1], - hasForceDeletePermission: result[2], - hasPinPermission: result[3] - }; - } catch { - // Do nothing - } - }; - - const isOwn = message => message.u && message.u._id === user.id; - - const allowEdit = (message) => { - if (isReadOnly) { - return false; - } - const editOwn = isOwn(message); - - if (!(permissions.hasEditPermission || (Message_AllowEditing && editOwn))) { - return false; - } - const blockEditInMinutes = Message_AllowEditing_BlockEditInMinutes; - if (blockEditInMinutes) { - let msgTs; - if (message.ts != null) { - msgTs = moment(message.ts); - } - let currentTsDiff; - if (msgTs != null) { - currentTsDiff = moment().diff(msgTs, 'minutes'); - } - return currentTsDiff < blockEditInMinutes; - } - return true; - }; - - const allowDelete = (message) => { - if (isReadOnly) { - return false; - } - - // Prevent from deleting thread start message when positioned inside the thread - if (tmid === message.id) { - return false; - } - const deleteOwn = isOwn(message); - if (!(permissions.hasDeletePermission || (Message_AllowDeleting && deleteOwn) || permissions.hasForceDeletePermission)) { - return false; - } - if (permissions.hasForceDeletePermission) { - return true; - } - const blockDeleteInMinutes = Message_AllowDeleting_BlockDeleteInMinutes; - if (blockDeleteInMinutes != null && blockDeleteInMinutes !== 0) { - let msgTs; - if (message.ts != null) { - msgTs = moment(message.ts); - } - let currentTsDiff; - if (msgTs != null) { - currentTsDiff = moment().diff(msgTs, 'minutes'); - } - return currentTsDiff < blockDeleteInMinutes; - } - return true; - }; - - const getPermalink = message => RocketChat.getPermalinkMessage(message); - - const handleReply = (message) => { - logEvent(events.ROOM_MSG_ACTION_REPLY); - replyInit(message, true); - }; - - const handleEdit = (message) => { - logEvent(events.ROOM_MSG_ACTION_EDIT); - editInit(message); - }; - - const handleCreateDiscussion = (message) => { - logEvent(events.ROOM_MSG_ACTION_DISCUSSION); - const params = { message, channel: room, showCloseModal: true }; - if (isMasterDetail) { - Navigation.navigate('ModalStackNavigator', { screen: 'CreateDiscussionView', params }); - } else { - Navigation.navigate('NewMessageStackNavigator', { screen: 'CreateDiscussionView', params }); - } - }; - - const handleUnread = async(message) => { - logEvent(events.ROOM_MSG_ACTION_UNREAD); - 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.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) { - logEvent(events.ROOM_MSG_ACTION_UNREAD_F); - log(e); - } - }; - - const handlePermalink = async(message) => { - logEvent(events.ROOM_MSG_ACTION_PERMALINK); - try { - const permalink = await getPermalink(message); - Clipboard.setString(permalink); - EventEmitter.emit(LISTENER, { message: I18n.t('Permalink_copied_to_clipboard') }); - } catch { - logEvent(events.ROOM_MSG_ACTION_PERMALINK_F); - } - }; - - const handleCopy = async(message) => { - logEvent(events.ROOM_MSG_ACTION_COPY); - await Clipboard.setString(message?.attachments?.[0]?.description || message.msg); - EventEmitter.emit(LISTENER, { message: I18n.t('Copied_to_clipboard') }); - }; - - const handleShare = async(message) => { - logEvent(events.ROOM_MSG_ACTION_SHARE); - try { - const permalink = await getPermalink(message); - Share.share({ message: permalink }); - } catch { - logEvent(events.ROOM_MSG_ACTION_SHARE_F); - } - }; - - const handleQuote = (message) => { - logEvent(events.ROOM_MSG_ACTION_QUOTE); - replyInit(message, false); - }; - - const handleStar = async(message) => { - logEvent(message.starred ? events.ROOM_MSG_ACTION_UNSTAR : events.ROOM_MSG_ACTION_STAR); - try { - await RocketChat.toggleStarMessage(message.id, message.starred); - EventEmitter.emit(LISTENER, { message: message.starred ? I18n.t('Message_unstarred') : I18n.t('Message_starred') }); - } catch (e) { - logEvent(events.ROOM_MSG_ACTION_STAR_F); - log(e); - } - }; - - const handlePin = async(message) => { - logEvent(events.ROOM_MSG_ACTION_PIN); - try { - await RocketChat.togglePinMessage(message.id, message.pinned); - } catch (e) { - logEvent(events.ROOM_MSG_ACTION_PIN_F); - log(e); - } - }; - - const handleReaction = (shortname, message) => { - logEvent(events.ROOM_MSG_ACTION_REACTION); - if (shortname) { - onReactionPress(shortname, message.id); - } else { - reactionInit(message); - } - // close actionSheet when click at header - hideActionSheet(); - }; - - const handleReadReceipt = (message) => { - if (isMasterDetail) { - Navigation.navigate('ModalStackNavigator', { screen: 'ReadReceiptsView', params: { messageId: message.id } }); - } else { - Navigation.navigate('ReadReceiptsView', { messageId: message.id }); - } - }; - - const handleToggleTranslation = async(message) => { - try { - const db = database.active; - await db.action(async() => { - await message.update((m) => { - m.autoTranslate = !m.autoTranslate; - m._updatedAt = new Date(); - }); - }); - const translatedMessage = getMessageTranslation(message, room.autoTranslateLanguage); - if (!translatedMessage) { - const m = { - _id: message.id, - rid: message.subscription.id, - u: message.u, - msg: message.msg - }; - await RocketChat.translateMessage(m, room.autoTranslateLanguage); - } - } catch (e) { - log(e); - } - }; - - const handleReport = async(message) => { - logEvent(events.ROOM_MSG_ACTION_REPORT); - try { - await RocketChat.reportMessage(message.id); - Alert.alert(I18n.t('Message_Reported')); - } catch (e) { - logEvent(events.ROOM_MSG_ACTION_REPORT_F); - log(e); - } - }; - - const handleDelete = (message) => { - showConfirmationAlert({ - message: I18n.t('You_will_not_be_able_to_recover_this_message'), - confirmationText: I18n.t('Delete'), - onPress: async() => { - try { - logEvent(events.ROOM_MSG_ACTION_DELETE); - await RocketChat.deleteMessage(message.id, message.subscription.id); - } catch (e) { - logEvent(events.ROOM_MSG_ACTION_DELETE_F); - log(e); - } - } - }); - }; - - const getOptions = (message) => { - let options = []; - - // Reply - if (!isReadOnly) { - options = [{ - title: I18n.t('Reply_in_Thread'), - icon: 'threads', - onPress: () => handleReply(message) - }]; - } - - // Quote - if (!isReadOnly) { - options.push({ - title: I18n.t('Quote'), - icon: 'quote', - onPress: () => handleQuote(message) - }); - } - - // Edit - if (allowEdit(message)) { - options.push({ - title: I18n.t('Edit'), - icon: 'edit', - onPress: () => handleEdit(message) - }); - } - - // Permalink - options.push({ - title: I18n.t('Permalink'), - icon: 'link', - onPress: () => handlePermalink(message) - }); - - // Create Discussion - options.push({ - title: I18n.t('Start_a_Discussion'), - icon: 'discussions', - onPress: () => handleCreateDiscussion(message) - }); - - // Mark as unread - if (message.u && message.u._id !== user.id) { - options.push({ - title: I18n.t('Mark_unread'), - icon: 'flag', - onPress: () => handleUnread(message) - }); - } - - // Copy - options.push({ - title: I18n.t('Copy'), - icon: 'copy', - onPress: () => handleCopy(message) - }); - - // Share - options.push({ - title: I18n.t('Share'), - icon: 'share', - onPress: () => handleShare(message) - }); - - // Star - if (Message_AllowStarring) { - options.push({ - title: I18n.t(message.starred ? 'Unstar' : 'Star'), - icon: message.starred ? 'star-filled' : 'star', - onPress: () => handleStar(message) - }); - } - - // Pin - if (Message_AllowPinning && permissions?.hasPinPermission) { - options.push({ - title: I18n.t(message.pinned ? 'Unpin' : 'Pin'), - icon: 'pin', - onPress: () => handlePin(message) - }); - } - - // Read Receipts - if (Message_Read_Receipt_Store_Users) { - options.push({ - title: I18n.t('Read_Receipt'), - icon: 'info', - onPress: () => handleReadReceipt(message) - }); - } - - // Toggle Auto-translate - if (room.autoTranslate && message.u && message.u._id !== user.id) { - options.push({ - title: I18n.t(message.autoTranslate ? 'View_Original' : 'Translate'), - icon: 'language', - onPress: () => handleToggleTranslation(message) - }); - } - - // Report - options.push({ - title: I18n.t('Report'), - icon: 'warning', - danger: true, - onPress: () => handleReport(message) - }); - - // Delete - if (allowDelete(message)) { - options.push({ - title: I18n.t('Delete'), - icon: 'delete', - danger: true, - onPress: () => handleDelete(message) - }); - } - - return options; - }; - - const showMessageActions = async(message) => { - logEvent(events.ROOM_SHOW_MSG_ACTIONS); - await getPermissions(); - showActionSheet({ - options: getOptions(message), - headerHeight: HEADER_HEIGHT, - customHeader: (!isReadOnly || room.reactWhenReadOnly ? ( -
- ) : null) - }); - }; - - useImperativeHandle(ref, () => ({ showMessageActions })); - - return null; -})); -MessageActions.propTypes = { - room: PropTypes.object, - tmid: PropTypes.string, - user: PropTypes.object, - editInit: PropTypes.func, - reactionInit: PropTypes.func, - onReactionPress: PropTypes.func, - replyInit: PropTypes.func, - isReadOnly: PropTypes.bool, - Message_AllowDeleting: PropTypes.bool, - Message_AllowDeleting_BlockDeleteInMinutes: PropTypes.number, - Message_AllowEditing: PropTypes.bool, - Message_AllowEditing_BlockEditInMinutes: PropTypes.number, - Message_AllowPinning: PropTypes.bool, - Message_AllowStarring: PropTypes.bool, - Message_Read_Receipt_Store_Users: PropTypes.bool, - server: PropTypes.string, - editMessagePermission: PropTypes.array, - deleteMessagePermission: PropTypes.array, - forceDeleteMessagePermission: PropTypes.array, - pinMessagePermission: PropTypes.array -}; - -const mapStateToProps = state => ({ - server: state.server.server, - Message_AllowDeleting: state.settings.Message_AllowDeleting, - Message_AllowDeleting_BlockDeleteInMinutes: state.settings.Message_AllowDeleting_BlockDeleteInMinutes, - Message_AllowEditing: state.settings.Message_AllowEditing, - Message_AllowEditing_BlockEditInMinutes: state.settings.Message_AllowEditing_BlockEditInMinutes, - Message_AllowPinning: state.settings.Message_AllowPinning, - Message_AllowStarring: state.settings.Message_AllowStarring, - Message_Read_Receipt_Store_Users: state.settings.Message_Read_Receipt_Store_Users, - isMasterDetail: state.app.isMasterDetail, - editMessagePermission: state.permissions['edit-message'], - deleteMessagePermission: state.permissions['delete-message'], - forceDeleteMessagePermission: state.permissions['force-delete-message'], - pinMessagePermission: state.permissions['pin-message'] -}); - -export default connect(mapStateToProps, null, null, { forwardRef: true })(MessageActions); diff --git a/app/containers/MessageActions/index.tsx b/app/containers/MessageActions/index.tsx new file mode 100644 index 000000000..eb9be9675 --- /dev/null +++ b/app/containers/MessageActions/index.tsx @@ -0,0 +1,487 @@ +import React, { forwardRef, useImperativeHandle } from 'react'; +import { Alert, Clipboard, Share } from 'react-native'; +import { connect } from 'react-redux'; +import moment from 'moment'; + +import RocketChat from '../../lib/rocketchat'; +import database from '../../lib/database'; +import I18n from '../../i18n'; +import log, { logEvent } from '../../utils/log'; +import Navigation from '../../lib/Navigation'; +import { getMessageTranslation } from '../message/utils'; +import { LISTENER } from '../Toast'; +import EventEmitter from '../../utils/events'; +import { showConfirmationAlert } from '../../utils/info'; +import { useActionSheet } from '../ActionSheet'; +import Header, { HEADER_HEIGHT } from './Header'; +import events from '../../utils/log/events'; + +interface IMessageActions { + room: { + rid: string | number; + autoTranslateLanguage: any; + autoTranslate: any; + reactWhenReadOnly: any; + }; + tmid: string; + user: { + id: string | number; + }; + editInit: Function; + reactionInit: Function; + onReactionPress: Function; + replyInit: Function; + isMasterDetail: boolean; + isReadOnly: boolean; + Message_AllowDeleting: boolean; + Message_AllowDeleting_BlockDeleteInMinutes: number; + Message_AllowEditing: boolean; + Message_AllowEditing_BlockEditInMinutes: number; + Message_AllowPinning: boolean; + Message_AllowStarring: boolean; + Message_Read_Receipt_Store_Users: boolean; + server: string; + editMessagePermission: []; + deleteMessagePermission: []; + forceDeleteMessagePermission: []; + pinMessagePermission: []; +} + +const MessageActions = React.memo( + forwardRef( + ( + { + room, + tmid, + user, + editInit, + reactionInit, + onReactionPress, + replyInit, + isReadOnly, + server, + Message_AllowDeleting, + Message_AllowDeleting_BlockDeleteInMinutes, + Message_AllowEditing, + Message_AllowEditing_BlockEditInMinutes, + Message_AllowPinning, + Message_AllowStarring, + Message_Read_Receipt_Store_Users, + isMasterDetail, + editMessagePermission, + deleteMessagePermission, + forceDeleteMessagePermission, + pinMessagePermission + }: IMessageActions, + ref + ): any => { + let permissions: any = {}; + const { showActionSheet, hideActionSheet }: any = useActionSheet(); + + const getPermissions = async () => { + try { + const permission = [editMessagePermission, deleteMessagePermission, forceDeleteMessagePermission, pinMessagePermission]; + const result = await RocketChat.hasPermission(permission, room.rid); + permissions = { + hasEditPermission: result[0], + hasDeletePermission: result[1], + hasForceDeletePermission: result[2], + hasPinPermission: result[3] + }; + } catch { + // Do nothing + } + }; + + const isOwn = (message: any) => message.u && message.u._id === user.id; + + const allowEdit = (message: any) => { + if (isReadOnly) { + return false; + } + const editOwn = isOwn(message); + + if (!(permissions.hasEditPermission || (Message_AllowEditing && editOwn))) { + return false; + } + const blockEditInMinutes = Message_AllowEditing_BlockEditInMinutes; + if (blockEditInMinutes) { + let msgTs; + if (message.ts != null) { + msgTs = moment(message.ts); + } + let currentTsDiff: any; + if (msgTs != null) { + currentTsDiff = moment().diff(msgTs, 'minutes'); + } + return currentTsDiff < blockEditInMinutes; + } + return true; + }; + + const allowDelete = (message: any) => { + if (isReadOnly) { + return false; + } + + // Prevent from deleting thread start message when positioned inside the thread + if (tmid === message.id) { + return false; + } + const deleteOwn = isOwn(message); + if (!(permissions.hasDeletePermission || (Message_AllowDeleting && deleteOwn) || permissions.hasForceDeletePermission)) { + return false; + } + if (permissions.hasForceDeletePermission) { + return true; + } + const blockDeleteInMinutes = Message_AllowDeleting_BlockDeleteInMinutes; + if (blockDeleteInMinutes != null && blockDeleteInMinutes !== 0) { + let msgTs; + if (message.ts != null) { + msgTs = moment(message.ts); + } + let currentTsDiff: any; + if (msgTs != null) { + currentTsDiff = moment().diff(msgTs, 'minutes'); + } + return currentTsDiff < blockDeleteInMinutes; + } + return true; + }; + + const getPermalink = (message: any) => RocketChat.getPermalinkMessage(message); + + const handleReply = (message: any) => { + logEvent(events.ROOM_MSG_ACTION_REPLY); + replyInit(message, true); + }; + + const handleEdit = (message: any) => { + logEvent(events.ROOM_MSG_ACTION_EDIT); + editInit(message); + }; + + const handleCreateDiscussion = (message: any) => { + logEvent(events.ROOM_MSG_ACTION_DISCUSSION); + const params = { message, channel: room, showCloseModal: true }; + if (isMasterDetail) { + Navigation.navigate('ModalStackNavigator', { screen: 'CreateDiscussionView', params }); + } else { + Navigation.navigate('NewMessageStackNavigator', { screen: 'CreateDiscussionView', params }); + } + }; + + const handleUnread = async (message: any) => { + logEvent(events.ROOM_MSG_ACTION_UNREAD); + 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.get('subscriptions'); + const subRecord = await subCollection.find(rid); + await db.action(async () => { + try { + await subRecord.update((sub: any) => (sub.lastOpen = ts)); + } catch { + // do nothing + } + }); + Navigation.navigate('RoomsListView'); + } + } catch (e) { + logEvent(events.ROOM_MSG_ACTION_UNREAD_F); + log(e); + } + }; + + const handlePermalink = async (message: any) => { + logEvent(events.ROOM_MSG_ACTION_PERMALINK); + try { + const permalink: any = await getPermalink(message); + Clipboard.setString(permalink); + EventEmitter.emit(LISTENER, { message: I18n.t('Permalink_copied_to_clipboard') }); + } catch { + logEvent(events.ROOM_MSG_ACTION_PERMALINK_F); + } + }; + + const handleCopy = async (message: any) => { + logEvent(events.ROOM_MSG_ACTION_COPY); + await Clipboard.setString(message?.attachments?.[0]?.description || message.msg); + EventEmitter.emit(LISTENER, { message: I18n.t('Copied_to_clipboard') }); + }; + + const handleShare = async (message: any) => { + logEvent(events.ROOM_MSG_ACTION_SHARE); + try { + const permalink: any = await getPermalink(message); + Share.share({ message: permalink }); + } catch { + logEvent(events.ROOM_MSG_ACTION_SHARE_F); + } + }; + + const handleQuote = (message: any) => { + logEvent(events.ROOM_MSG_ACTION_QUOTE); + replyInit(message, false); + }; + + const handleStar = async (message: any) => { + logEvent(message.starred ? events.ROOM_MSG_ACTION_UNSTAR : events.ROOM_MSG_ACTION_STAR); + try { + await RocketChat.toggleStarMessage(message.id, message.starred); + EventEmitter.emit(LISTENER, { message: message.starred ? I18n.t('Message_unstarred') : I18n.t('Message_starred') }); + } catch (e) { + logEvent(events.ROOM_MSG_ACTION_STAR_F); + log(e); + } + }; + + const handlePin = async (message: any) => { + logEvent(events.ROOM_MSG_ACTION_PIN); + try { + await RocketChat.togglePinMessage(message.id, message.pinned); + } catch (e) { + logEvent(events.ROOM_MSG_ACTION_PIN_F); + log(e); + } + }; + + const handleReaction = (shortname: any, message: any) => { + logEvent(events.ROOM_MSG_ACTION_REACTION); + if (shortname) { + onReactionPress(shortname, message.id); + } else { + reactionInit(message); + } + // close actionSheet when click at header + hideActionSheet(); + }; + + const handleReadReceipt = (message: any) => { + if (isMasterDetail) { + Navigation.navigate('ModalStackNavigator', { screen: 'ReadReceiptsView', params: { messageId: message.id } }); + } else { + Navigation.navigate('ReadReceiptsView', { messageId: message.id }); + } + }; + + const handleToggleTranslation = async (message: any) => { + try { + const db = database.active; + await db.action(async () => { + await message.update((m: any) => { + m.autoTranslate = !m.autoTranslate; + m._updatedAt = new Date(); + }); + }); + const translatedMessage = getMessageTranslation(message, room.autoTranslateLanguage); + if (!translatedMessage) { + const m = { + _id: message.id, + rid: message.subscription.id, + u: message.u, + msg: message.msg + }; + await RocketChat.translateMessage(m, room.autoTranslateLanguage); + } + } catch (e) { + log(e); + } + }; + + const handleReport = async (message: any) => { + logEvent(events.ROOM_MSG_ACTION_REPORT); + try { + await RocketChat.reportMessage(message.id); + Alert.alert(I18n.t('Message_Reported')); + } catch (e) { + logEvent(events.ROOM_MSG_ACTION_REPORT_F); + log(e); + } + }; + + const handleDelete = (message: any) => { + // TODO - migrate this function for ts when fix the lint erros + // @ts-ignore + showConfirmationAlert({ + message: I18n.t('You_will_not_be_able_to_recover_this_message'), + confirmationText: I18n.t('Delete'), + onPress: async () => { + try { + logEvent(events.ROOM_MSG_ACTION_DELETE); + await RocketChat.deleteMessage(message.id, message.subscription.id); + } catch (e) { + logEvent(events.ROOM_MSG_ACTION_DELETE_F); + log(e); + } + } + }); + }; + + const getOptions = (message: any) => { + let options: any = []; + + // Reply + if (!isReadOnly) { + options = [ + { + title: I18n.t('Reply_in_Thread'), + icon: 'threads', + onPress: () => handleReply(message) + } + ]; + } + + // Quote + if (!isReadOnly) { + options.push({ + title: I18n.t('Quote'), + icon: 'quote', + onPress: () => handleQuote(message) + }); + } + + // Edit + if (allowEdit(message)) { + options.push({ + title: I18n.t('Edit'), + icon: 'edit', + onPress: () => handleEdit(message) + }); + } + + // Permalink + options.push({ + title: I18n.t('Permalink'), + icon: 'link', + onPress: () => handlePermalink(message) + }); + + // Create Discussion + options.push({ + title: I18n.t('Start_a_Discussion'), + icon: 'discussions', + onPress: () => handleCreateDiscussion(message) + }); + + // Mark as unread + if (message.u && message.u._id !== user.id) { + options.push({ + title: I18n.t('Mark_unread'), + icon: 'flag', + onPress: () => handleUnread(message) + }); + } + + // Copy + options.push({ + title: I18n.t('Copy'), + icon: 'copy', + onPress: () => handleCopy(message) + }); + + // Share + options.push({ + title: I18n.t('Share'), + icon: 'share', + onPress: () => handleShare(message) + }); + + // Star + if (Message_AllowStarring) { + options.push({ + title: I18n.t(message.starred ? 'Unstar' : 'Star'), + icon: message.starred ? 'star-filled' : 'star', + onPress: () => handleStar(message) + }); + } + + // Pin + if (Message_AllowPinning && permissions?.hasPinPermission) { + options.push({ + title: I18n.t(message.pinned ? 'Unpin' : 'Pin'), + icon: 'pin', + onPress: () => handlePin(message) + }); + } + + // Read Receipts + if (Message_Read_Receipt_Store_Users) { + options.push({ + title: I18n.t('Read_Receipt'), + icon: 'info', + onPress: () => handleReadReceipt(message) + }); + } + + // Toggle Auto-translate + if (room.autoTranslate && message.u && message.u._id !== user.id) { + options.push({ + title: I18n.t(message.autoTranslate ? 'View_Original' : 'Translate'), + icon: 'language', + onPress: () => handleToggleTranslation(message) + }); + } + + // Report + options.push({ + title: I18n.t('Report'), + icon: 'warning', + danger: true, + onPress: () => handleReport(message) + }); + + // Delete + if (allowDelete(message)) { + options.push({ + title: I18n.t('Delete'), + icon: 'delete', + danger: true, + onPress: () => handleDelete(message) + }); + } + + return options; + }; + + const showMessageActions = async (message: any) => { + logEvent(events.ROOM_SHOW_MSG_ACTIONS); + await getPermissions(); + showActionSheet({ + options: getOptions(message), + headerHeight: HEADER_HEIGHT, + customHeader: + !isReadOnly || room.reactWhenReadOnly ? ( +
+ ) : null + }); + }; + + useImperativeHandle(ref, () => ({ showMessageActions })); + + return null; + } + ) +); + +const mapStateToProps = (state: any) => ({ + server: state.server.server, + Message_AllowDeleting: state.settings.Message_AllowDeleting, + Message_AllowDeleting_BlockDeleteInMinutes: state.settings.Message_AllowDeleting_BlockDeleteInMinutes, + Message_AllowEditing: state.settings.Message_AllowEditing, + Message_AllowEditing_BlockEditInMinutes: state.settings.Message_AllowEditing_BlockEditInMinutes, + Message_AllowPinning: state.settings.Message_AllowPinning, + Message_AllowStarring: state.settings.Message_AllowStarring, + Message_Read_Receipt_Store_Users: state.settings.Message_Read_Receipt_Store_Users, + isMasterDetail: state.app.isMasterDetail, + editMessagePermission: state.permissions['edit-message'], + deleteMessagePermission: state.permissions['delete-message'], + forceDeleteMessagePermission: state.permissions['force-delete-message'], + pinMessagePermission: state.permissions['pin-message'] +}); + +export default connect(mapStateToProps, null, null, { forwardRef: true })(MessageActions); diff --git a/app/containers/MessageBox/CommandsPreview/Item.js b/app/containers/MessageBox/CommandsPreview/Item.tsx similarity index 51% rename from app/containers/MessageBox/CommandsPreview/Item.js rename to app/containers/MessageBox/CommandsPreview/Item.tsx index 9fa026dd5..6f89d254e 100644 --- a/app/containers/MessageBox/CommandsPreview/Item.js +++ b/app/containers/MessageBox/CommandsPreview/Item.tsx @@ -1,5 +1,4 @@ import React, { useContext, useState } from 'react'; -import PropTypes from 'prop-types'; import { TouchableOpacity } from 'react-native'; import FastImage from '@rocket.chat/react-native-fast-image'; @@ -9,7 +8,16 @@ import { themes } from '../../../constants/colors'; import MessageboxContext from '../Context'; import ActivityIndicator from '../../ActivityIndicator'; -const Item = ({ item, theme }) => { +interface IMessageBoxCommandsPreviewItem { + item: { + type: string; + id: string; + value: string; + }; + theme: string; +} + +const Item = ({ item, theme }: IMessageBoxCommandsPreviewItem) => { const context = useContext(MessageboxContext); const { onPressCommandPreview } = context; const [loading, setLoading] = useState(true); @@ -18,29 +26,21 @@ const Item = ({ item, theme }) => { onPressCommandPreview(item)} - testID={`command-preview-item${ item.id }`} - > - {item.type === 'image' - ? ( - setLoading(true)} - onLoad={() => setLoading(false)} - > - { loading ? : null } - - ) - : - } + testID={`command-preview-item${item.id}`}> + {item.type === 'image' ? ( + setLoading(true)} + onLoad={() => setLoading(false)}> + {loading ? : null} + + ) : ( + + )} ); }; -Item.propTypes = { - item: PropTypes.object, - theme: PropTypes.string -}; - export default Item; diff --git a/app/containers/MessageBox/CommandsPreview/index.js b/app/containers/MessageBox/CommandsPreview/index.js deleted file mode 100644 index 1bb03ca8b..000000000 --- a/app/containers/MessageBox/CommandsPreview/index.js +++ /dev/null @@ -1,46 +0,0 @@ -import React from 'react'; -import { FlatList } from 'react-native'; -import PropTypes from 'prop-types'; -import { dequal } from 'dequal'; - -import Item from './Item'; -import styles from '../styles'; -import { themes } from '../../../constants/colors'; -import { withTheme } from '../../../theme'; - -const CommandsPreview = React.memo(({ theme, commandPreview, showCommandPreview }) => { - if (!showCommandPreview) { - return null; - } - return ( - } - keyExtractor={item => item.id} - keyboardShouldPersistTaps='always' - horizontal - showsHorizontalScrollIndicator={false} - /> - ); -}, (prevProps, nextProps) => { - if (prevProps.theme !== nextProps.theme) { - return false; - } - if (prevProps.showCommandPreview !== nextProps.showCommandPreview) { - return false; - } - if (!dequal(prevProps.commandPreview, nextProps.commandPreview)) { - return false; - } - return true; -}); - -CommandsPreview.propTypes = { - commandPreview: PropTypes.array, - showCommandPreview: PropTypes.bool, - theme: PropTypes.string -}; - -export default withTheme(CommandsPreview); diff --git a/app/containers/MessageBox/CommandsPreview/index.tsx b/app/containers/MessageBox/CommandsPreview/index.tsx new file mode 100644 index 000000000..3820f6fa6 --- /dev/null +++ b/app/containers/MessageBox/CommandsPreview/index.tsx @@ -0,0 +1,48 @@ +import React from 'react'; +import { FlatList } from 'react-native'; +import { dequal } from 'dequal'; + +import Item from './Item'; +import styles from '../styles'; +import { themes } from '../../../constants/colors'; +import { withTheme } from '../../../theme'; + +interface IMessageBoxCommandsPreview { + commandPreview: []; + showCommandPreview: boolean; + theme: string; +} + +const CommandsPreview = React.memo( + ({ theme, commandPreview, showCommandPreview }: IMessageBoxCommandsPreview) => { + if (!showCommandPreview) { + return null; + } + return ( + } + keyExtractor={(item: any) => item.id} + keyboardShouldPersistTaps='always' + horizontal + showsHorizontalScrollIndicator={false} + /> + ); + }, + (prevProps, nextProps) => { + if (prevProps.theme !== nextProps.theme) { + return false; + } + if (prevProps.showCommandPreview !== nextProps.showCommandPreview) { + return false; + } + if (!dequal(prevProps.commandPreview, nextProps.commandPreview)) { + return false; + } + return true; + } +); + +export default withTheme(CommandsPreview); diff --git a/app/containers/MessageBox/Context.js b/app/containers/MessageBox/Context.js deleted file mode 100644 index 3ed07ad43..000000000 --- a/app/containers/MessageBox/Context.js +++ /dev/null @@ -1,4 +0,0 @@ -import React from 'react'; - -const MessageboxContext = React.createContext(); -export default MessageboxContext; diff --git a/app/containers/MessageBox/Context.ts b/app/containers/MessageBox/Context.ts new file mode 100644 index 000000000..4978b78db --- /dev/null +++ b/app/containers/MessageBox/Context.ts @@ -0,0 +1,5 @@ +import React from 'react'; + +// @ts-ignore +const MessageboxContext = React.createContext(); +export default MessageboxContext; diff --git a/app/containers/MessageBox/EmojiKeyboard.js b/app/containers/MessageBox/EmojiKeyboard.tsx similarity index 65% rename from app/containers/MessageBox/EmojiKeyboard.js rename to app/containers/MessageBox/EmojiKeyboard.tsx index bbffa4a92..bbb0e20ad 100644 --- a/app/containers/MessageBox/EmojiKeyboard.js +++ b/app/containers/MessageBox/EmojiKeyboard.tsx @@ -1,7 +1,6 @@ import React from 'react'; import { View } from 'react-native'; import { KeyboardRegistry } from 'react-native-ui-lib/keyboard'; -import PropTypes from 'prop-types'; import store from '../../lib/createStore'; import EmojiPicker from '../EmojiPicker'; @@ -9,25 +8,29 @@ import styles from './styles'; import { themes } from '../../constants/colors'; import { withTheme } from '../../theme'; -export default class EmojiKeyboard extends React.PureComponent { - static propTypes = { - theme: PropTypes.string - }; +interface IMessageBoxEmojiKeyboard { + theme: string; +} - constructor(props) { +export default class EmojiKeyboard extends React.PureComponent { + private readonly baseUrl: any; + + constructor(props: IMessageBoxEmojiKeyboard) { super(props); const state = store.getState(); this.baseUrl = state.share.server.server || state.server.server; } - onEmojiSelected = (emoji) => { + onEmojiSelected = (emoji: any) => { KeyboardRegistry.onItemSelected('EmojiKeyboard', { emoji }); - } + }; render() { const { theme } = this.props; return ( - + ); diff --git a/app/containers/MessageBox/LeftButtons.android.js b/app/containers/MessageBox/LeftButtons.android.js deleted file mode 100644 index f118ccf6e..000000000 --- a/app/containers/MessageBox/LeftButtons.android.js +++ /dev/null @@ -1,31 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; - -import { CancelEditingButton, ToggleEmojiButton } from './buttons'; - -const LeftButtons = React.memo(({ - theme, showEmojiKeyboard, editing, editCancel, openEmoji, closeEmoji -}) => { - if (editing) { - return ; - } - return ( - - ); -}); - -LeftButtons.propTypes = { - theme: PropTypes.string, - showEmojiKeyboard: PropTypes.bool, - openEmoji: PropTypes.func.isRequired, - closeEmoji: PropTypes.func.isRequired, - editing: PropTypes.bool, - editCancel: PropTypes.func.isRequired -}; - -export default LeftButtons; diff --git a/app/containers/MessageBox/LeftButtons.android.tsx b/app/containers/MessageBox/LeftButtons.android.tsx new file mode 100644 index 000000000..105ce6297 --- /dev/null +++ b/app/containers/MessageBox/LeftButtons.android.tsx @@ -0,0 +1,23 @@ +import React from 'react'; + +import { CancelEditingButton, ToggleEmojiButton } from './buttons'; + +interface IMessageBoxLeftButtons { + theme: string; + showEmojiKeyboard: boolean; + openEmoji(): void; + closeEmoji(): void; + editing: boolean; + editCancel(): void; +} + +const LeftButtons = React.memo( + ({ theme, showEmojiKeyboard, editing, editCancel, openEmoji, closeEmoji }: IMessageBoxLeftButtons) => { + if (editing) { + return ; + } + return ; + } +); + +export default LeftButtons; diff --git a/app/containers/MessageBox/LeftButtons.ios.js b/app/containers/MessageBox/LeftButtons.ios.js deleted file mode 100644 index 29c038466..000000000 --- a/app/containers/MessageBox/LeftButtons.ios.js +++ /dev/null @@ -1,28 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import { View } from 'react-native'; - -import { CancelEditingButton, ActionsButton } from './buttons'; -import styles from './styles'; - -const LeftButtons = React.memo(({ - theme, showMessageBoxActions, editing, editCancel, isActionsEnabled -}) => { - if (editing) { - return ; - } - if (isActionsEnabled) { - return ; - } - return ; -}); - -LeftButtons.propTypes = { - theme: PropTypes.string, - showMessageBoxActions: PropTypes.func.isRequired, - editing: PropTypes.bool, - editCancel: PropTypes.func.isRequired, - isActionsEnabled: PropTypes.bool -}; - -export default LeftButtons; diff --git a/app/containers/MessageBox/LeftButtons.ios.tsx b/app/containers/MessageBox/LeftButtons.ios.tsx new file mode 100644 index 000000000..3ed9b5060 --- /dev/null +++ b/app/containers/MessageBox/LeftButtons.ios.tsx @@ -0,0 +1,27 @@ +import React from 'react'; +import { View } from 'react-native'; + +import { ActionsButton, CancelEditingButton } from './buttons'; +import styles from './styles'; + +interface IMessageBoxLeftButtons { + theme: string; + showMessageBoxActions(): void; + editing: boolean; + editCancel(): void; + isActionsEnabled: boolean; +} + +const LeftButtons = React.memo( + ({ theme, showMessageBoxActions, editing, editCancel, isActionsEnabled }: IMessageBoxLeftButtons) => { + if (editing) { + return ; + } + if (isActionsEnabled) { + return ; + } + return ; + } +); + +export default LeftButtons; diff --git a/app/containers/MessageBox/Mentions/FixedMentionItem.js b/app/containers/MessageBox/Mentions/FixedMentionItem.tsx similarity index 69% rename from app/containers/MessageBox/Mentions/FixedMentionItem.js rename to app/containers/MessageBox/Mentions/FixedMentionItem.tsx index dc2901318..a1326b6b1 100644 --- a/app/containers/MessageBox/Mentions/FixedMentionItem.js +++ b/app/containers/MessageBox/Mentions/FixedMentionItem.tsx @@ -1,12 +1,19 @@ import React from 'react'; -import { TouchableOpacity, Text } from 'react-native'; -import PropTypes from 'prop-types'; +import { Text, TouchableOpacity } from 'react-native'; import styles from '../styles'; import I18n from '../../../i18n'; import { themes } from '../../../constants/colors'; -const FixedMentionItem = ({ item, onPress, theme }) => ( +interface IMessageBoxFixedMentionItem { + item: { + username: string; + }; + onPress: Function; + theme: string; +} + +const FixedMentionItem = ({ item, onPress, theme }: IMessageBoxFixedMentionItem) => ( ( borderTopColor: themes[theme].separatorColor } ]} - onPress={() => onPress(item)} - > + onPress={() => onPress(item)}> {item.username} {item.username === 'here' ? I18n.t('Notify_active_in_this_room') : I18n.t('Notify_all_in_this_room')} @@ -24,10 +30,4 @@ const FixedMentionItem = ({ item, onPress, theme }) => ( ); -FixedMentionItem.propTypes = { - item: PropTypes.object, - onPress: PropTypes.func, - theme: PropTypes.string -}; - export default FixedMentionItem; diff --git a/app/containers/MessageBox/Mentions/MentionEmoji.js b/app/containers/MessageBox/Mentions/MentionEmoji.tsx similarity index 58% rename from app/containers/MessageBox/Mentions/MentionEmoji.js rename to app/containers/MessageBox/Mentions/MentionEmoji.tsx index 3d25729e2..5e012c136 100644 --- a/app/containers/MessageBox/Mentions/MentionEmoji.js +++ b/app/containers/MessageBox/Mentions/MentionEmoji.tsx @@ -6,25 +6,20 @@ import shortnameToUnicode from '../../../utils/shortnameToUnicode'; import styles from '../styles'; import MessageboxContext from '../Context'; import CustomEmoji from '../../EmojiPicker/CustomEmoji'; +import { IEmoji } from '../../EmojiPicker/interfaces'; -const MentionEmoji = ({ item }) => { +interface IMessageBoxMentionEmoji { + item: IEmoji; +} + +const MentionEmoji = ({ item }: IMessageBoxMentionEmoji) => { const context = useContext(MessageboxContext); const { baseUrl } = context; if (item.name) { - return ( - - ); + return ; } - return ( - - {shortnameToUnicode(`:${ item }:`)} - - ); + return {shortnameToUnicode(`:${item}:`)}; }; MentionEmoji.propTypes = { diff --git a/app/containers/MessageBox/Mentions/MentionHeaderList.js b/app/containers/MessageBox/Mentions/MentionHeaderList.js new file mode 100644 index 000000000..10b48e4e9 --- /dev/null +++ b/app/containers/MessageBox/Mentions/MentionHeaderList.js @@ -0,0 +1,49 @@ +import React, { useContext } from 'react'; +import { View, Text, ActivityIndicator, TouchableOpacity } from 'react-native'; +import PropTypes from 'prop-types'; + +import { MENTIONS_TRACKING_TYPE_CANNED } from '../constants'; +import styles from '../styles'; +import sharedStyles from '../../../views/Styles'; +import I18n from '../../../i18n'; +import { themes } from '../../../constants/colors'; +import { CustomIcon } from '../../../lib/Icons'; +import MessageboxContext from '../Context'; + +const MentionHeaderList = ({ trackingType, hasMentions, theme, loading }) => { + const context = useContext(MessageboxContext); + const { onPressNoMatchCanned } = context; + + if (trackingType === MENTIONS_TRACKING_TYPE_CANNED) { + if (loading) { + return ( + + + {I18n.t('Searching')} + + ); + } + + if (!hasMentions) { + return ( + + + {I18n.t('No_match_found')} {I18n.t('Check_canned_responses')} + + + + ); + } + } + + return null; +}; + +MentionHeaderList.propTypes = { + trackingType: PropTypes.string, + hasMentions: PropTypes.bool, + theme: PropTypes.string, + loading: PropTypes.bool +}; + +export default MentionHeaderList; diff --git a/app/containers/MessageBox/Mentions/MentionItem.js b/app/containers/MessageBox/Mentions/MentionItem.tsx similarity index 53% rename from app/containers/MessageBox/Mentions/MentionItem.js rename to app/containers/MessageBox/Mentions/MentionItem.tsx index 36144de6f..c315b9250 100644 --- a/app/containers/MessageBox/Mentions/MentionItem.js +++ b/app/containers/MessageBox/Mentions/MentionItem.tsx @@ -1,32 +1,43 @@ import React, { useContext } from 'react'; -import { TouchableOpacity, Text } from 'react-native'; -import PropTypes from 'prop-types'; +import { Text, TouchableOpacity } from 'react-native'; import styles from '../styles'; import Avatar from '../../Avatar'; import MessageboxContext from '../Context'; import FixedMentionItem from './FixedMentionItem'; import MentionEmoji from './MentionEmoji'; -import { - MENTIONS_TRACKING_TYPE_EMOJIS, - MENTIONS_TRACKING_TYPE_COMMANDS -} from '../constants'; +import { MENTIONS_TRACKING_TYPE_EMOJIS, MENTIONS_TRACKING_TYPE_COMMANDS, MENTIONS_TRACKING_TYPE_CANNED } from '../constants'; import { themes } from '../../../constants/colors'; +import { IEmoji } from '../../EmojiPicker/interfaces'; -const MentionItem = ({ - item, trackingType, theme -}) => { +interface IMessageBoxMentionItem { + item: { + name: string; + command: string; + username: string; + t: string; + id: string; + shortcut: string; + text: string; + } & IEmoji; + trackingType: string; + theme: string; +} + +const MentionItem = ({ item, trackingType, theme }: IMessageBoxMentionItem) => { const context = useContext(MessageboxContext); const { onPressMention } = context; - const defineTestID = (type) => { + const defineTestID = (type: string) => { switch (type) { case MENTIONS_TRACKING_TYPE_EMOJIS: - return `mention-item-${ item.name || item }`; + return `mention-item-${item.name || item}`; case MENTIONS_TRACKING_TYPE_COMMANDS: - return `mention-item-${ item.command || item }`; + return `mention-item-${item.command || item}`; + case MENTIONS_TRACKING_TYPE_CANNED: + return `mention-item-${item.shortcut || item}`; default: - return `mention-item-${ item.username || item.name || item }`; + return `mention-item-${item.username || item.name || item}`; } }; @@ -38,13 +49,8 @@ const MentionItem = ({ let content = ( <> - - { item.username || item.name || item } + + {item.username || item.name || item} ); @@ -52,7 +58,7 @@ const MentionItem = ({ content = ( <> - :{ item.name || item }: + :{item.name || item}: ); } @@ -66,6 +72,17 @@ const MentionItem = ({ ); } + if (trackingType === MENTIONS_TRACKING_TYPE_CANNED) { + content = ( + <> + !{item.shortcut} + + {item.text} + + + ); + } + return ( onPressMention(item)} - testID={testID} - > + testID={testID}> {content} ); }; -MentionItem.propTypes = { - item: PropTypes.object, - trackingType: PropTypes.string, - theme: PropTypes.string -}; - export default MentionItem; diff --git a/app/containers/MessageBox/Mentions/index.js b/app/containers/MessageBox/Mentions/index.js deleted file mode 100644 index cb99dcdd7..000000000 --- a/app/containers/MessageBox/Mentions/index.js +++ /dev/null @@ -1,45 +0,0 @@ -import React from 'react'; -import { FlatList, View } from 'react-native'; -import PropTypes from 'prop-types'; -import { dequal } from 'dequal'; - -import styles from '../styles'; -import MentionItem from './MentionItem'; -import { themes } from '../../../constants/colors'; - -const Mentions = React.memo(({ mentions, trackingType, theme }) => { - if (!trackingType) { - return null; - } - return ( - - } - keyExtractor={item => item.rid || item.name || item.command || item} - keyboardShouldPersistTaps='always' - /> - - ); -}, (prevProps, nextProps) => { - if (prevProps.theme !== nextProps.theme) { - return false; - } - if (prevProps.trackingType !== nextProps.trackingType) { - return false; - } - if (!dequal(prevProps.mentions, nextProps.mentions)) { - return false; - } - return true; -}); - -Mentions.propTypes = { - mentions: PropTypes.array, - trackingType: PropTypes.string, - theme: PropTypes.string -}; - -export default Mentions; diff --git a/app/containers/MessageBox/Mentions/index.tsx b/app/containers/MessageBox/Mentions/index.tsx new file mode 100644 index 000000000..13f82ac7c --- /dev/null +++ b/app/containers/MessageBox/Mentions/index.tsx @@ -0,0 +1,55 @@ +import React from 'react'; +import { FlatList, View } from 'react-native'; +import { dequal } from 'dequal'; + +import MentionHeaderList from './MentionHeaderList'; +import styles from '../styles'; +import MentionItem from './MentionItem'; +import { themes } from '../../../constants/colors'; + +interface IMessageBoxMentions { + mentions: any[]; + trackingType: string; + theme: string; + loading: boolean; +} + +const Mentions = React.memo( + ({ mentions, trackingType, theme, loading }: IMessageBoxMentions) => { + if (!trackingType) { + return null; + } + return ( + + ( + 0} theme={theme} loading={loading} /> + )} + data={mentions} + extraData={mentions} + renderItem={({ item }) => } + keyExtractor={item => item.rid || item.name || item.command || item.shortcut || item} + keyboardShouldPersistTaps='always' + /> + + ); + }, + (prevProps, nextProps) => { + if (prevProps.loading !== nextProps.loading) { + return false; + } + if (prevProps.theme !== nextProps.theme) { + return false; + } + if (prevProps.trackingType !== nextProps.trackingType) { + return false; + } + if (!dequal(prevProps.mentions, nextProps.mentions)) { + return false; + } + return true; + } +); + +export default Mentions; diff --git a/app/containers/MessageBox/RecordAudio.js b/app/containers/MessageBox/RecordAudio.tsx similarity index 59% rename from app/containers/MessageBox/RecordAudio.js rename to app/containers/MessageBox/RecordAudio.tsx index fca259f33..fa6c509ef 100644 --- a/app/containers/MessageBox/RecordAudio.js +++ b/app/containers/MessageBox/RecordAudio.tsx @@ -1,22 +1,27 @@ import React from 'react'; -import PropTypes from 'prop-types'; -import { View, Text } from 'react-native'; +import { Text, View } from 'react-native'; import { Audio } from 'expo-av'; import { BorderlessButton } from 'react-native-gesture-handler'; import { getInfoAsync } from 'expo-file-system'; -import { deactivateKeepAwake, activateKeepAwake } from 'expo-keep-awake'; +import { activateKeepAwake, deactivateKeepAwake } from 'expo-keep-awake'; import styles from './styles'; import I18n from '../../i18n'; import { themes } from '../../constants/colors'; import { CustomIcon } from '../../lib/Icons'; -import { logEvent, events } from '../../utils/log'; +import { events, logEvent } from '../../utils/log'; -const RECORDING_EXTENSION = '.aac'; +interface IMessageBoxRecordAudioProps { + theme: string; + recordingCallback: Function; + onFinish: Function; +} + +const RECORDING_EXTENSION = '.m4a'; const RECORDING_SETTINGS = { android: { extension: RECORDING_EXTENSION, - outputFormat: Audio.RECORDING_OPTION_ANDROID_OUTPUT_FORMAT_AAC_ADTS, + outputFormat: Audio.RECORDING_OPTION_ANDROID_OUTPUT_FORMAT_MPEG_4, audioEncoder: Audio.RECORDING_OPTION_ANDROID_AUDIO_ENCODER_AAC, sampleRate: Audio.RECORDING_OPTIONS_PRESET_LOW_QUALITY.android.sampleRate, numberOfChannels: Audio.RECORDING_OPTIONS_PRESET_LOW_QUALITY.android.numberOfChannels, @@ -34,42 +39,48 @@ const RECORDING_SETTINGS = { const RECORDING_MODE = { allowsRecordingIOS: true, playsInSilentModeIOS: true, - staysActiveInBackground: false, + staysActiveInBackground: true, shouldDuckAndroid: true, playThroughEarpieceAndroid: false, interruptionModeIOS: Audio.INTERRUPTION_MODE_IOS_DO_NOT_MIX, interruptionModeAndroid: Audio.INTERRUPTION_MODE_ANDROID_DO_NOT_MIX }; -const formatTime = function(seconds) { - let minutes = Math.floor(seconds / 60); +const formatTime = function (seconds: any) { + let minutes: any = Math.floor(seconds / 60); seconds %= 60; - if (minutes < 10) { minutes = `0${ minutes }`; } - if (seconds < 10) { seconds = `0${ seconds }`; } - return `${ minutes }:${ seconds }`; + if (minutes < 10) { + minutes = `0${minutes}`; + } + if (seconds < 10) { + seconds = `0${seconds}`; + } + return `${minutes}:${seconds}`; }; -export default class RecordAudio extends React.PureComponent { - static propTypes = { - theme: PropTypes.string, - recordingCallback: PropTypes.func, - onFinish: PropTypes.func - } +export default class RecordAudio extends React.PureComponent { + private isRecorderBusy: boolean; - constructor(props) { + private recording: any; + + private LastDuration: number; + + constructor(props: IMessageBoxRecordAudioProps) { super(props); this.isRecorderBusy = false; + this.LastDuration = 0; this.state = { isRecording: false, + isRecorderActive: false, recordingDurationMillis: 0 }; } componentDidUpdate() { const { recordingCallback } = this.props; - const { isRecording } = this.state; + const { isRecorderActive } = this.state; - recordingCallback(isRecording); + recordingCallback(isRecorderActive); } componentWillUnmount() { @@ -83,7 +94,11 @@ export default class RecordAudio extends React.PureComponent { return formatTime(Math.floor(recordingDurationMillis / 1000)); } - isRecordingPermissionGranted = async() => { + get GetLastDuration() { + return formatTime(Math.floor(this.LastDuration / 1000)); + } + + isRecordingPermissionGranted = async () => { try { const permission = await Audio.getPermissionsAsync(); if (permission.status === 'granted') { @@ -94,24 +109,27 @@ export default class RecordAudio extends React.PureComponent { // Do nothing } return false; - } + }; - onRecordingStatusUpdate = (status) => { + onRecordingStatusUpdate = (status: any) => { this.setState({ isRecording: status.isRecording, recordingDurationMillis: status.durationMillis }); - } + this.LastDuration = status.durationMillis; + }; - startRecordingAudio = async() => { + startRecordingAudio = async () => { logEvent(events.ROOM_AUDIO_RECORD); if (!this.isRecorderBusy) { this.isRecorderBusy = true; + this.LastDuration = 0; try { const canRecord = await this.isRecordingPermissionGranted(); if (canRecord) { await Audio.setAudioModeAsync(RECORDING_MODE); + this.setState({ isRecorderActive: true }); this.recording = new Audio.Recording(); await this.recording.prepareToRecordAsync(RECORDING_SETTINGS); this.recording.setOnRecordingStatusUpdate(this.onRecordingStatusUpdate); @@ -128,7 +146,7 @@ export default class RecordAudio extends React.PureComponent { } }; - finishRecordingAudio = async() => { + finishRecordingAudio = async () => { logEvent(events.ROOM_AUDIO_FINISH); if (!this.isRecorderBusy) { const { onFinish } = this.props; @@ -140,7 +158,7 @@ export default class RecordAudio extends React.PureComponent { const fileURI = this.recording.getURI(); const fileData = await getInfoAsync(fileURI); const fileInfo = { - name: `${ Date.now() }.aac`, + name: `${Date.now()}.m4a`, mime: 'audio/aac', type: 'audio/aac', store: 'Uploads', @@ -152,13 +170,13 @@ export default class RecordAudio extends React.PureComponent { } catch (error) { logEvent(events.ROOM_AUDIO_FINISH_F); } - this.setState({ isRecording: false, recordingDurationMillis: 0 }); + this.setState({ isRecording: false, isRecorderActive: false, recordingDurationMillis: 0 }); deactivateKeepAwake(); this.isRecorderBusy = false; } }; - cancelRecordingAudio = async() => { + cancelRecordingAudio = async () => { logEvent(events.ROOM_AUDIO_CANCEL); if (!this.isRecorderBusy) { this.isRecorderBusy = true; @@ -167,7 +185,7 @@ export default class RecordAudio extends React.PureComponent { } catch (error) { logEvent(events.ROOM_AUDIO_CANCEL_F); } - this.setState({ isRecording: false, recordingDurationMillis: 0 }); + this.setState({ isRecording: false, isRecorderActive: false, recordingDurationMillis: 0 }); deactivateKeepAwake(); this.isRecorderBusy = false; } @@ -175,54 +193,69 @@ export default class RecordAudio extends React.PureComponent { render() { const { theme } = this.props; - const { isRecording } = this.state; + const { isRecording, isRecorderActive } = this.state; - if (!isRecording) { + if (!isRecording && !isRecorderActive) { return ( + accessibilityTraits='button'> ); } + if (!isRecording && isRecorderActive) { + return ( + + + + + + {this.GetLastDuration} + + + + + + ); + } + return ( - + style={styles.actionButton}> + - - {this.duration} - + {this.duration} + - + style={styles.actionButton}> + ); diff --git a/app/containers/MessageBox/ReplyPreview.js b/app/containers/MessageBox/ReplyPreview.js deleted file mode 100644 index eee81236d..000000000 --- a/app/containers/MessageBox/ReplyPreview.js +++ /dev/null @@ -1,97 +0,0 @@ -import React from 'react'; -import { View, Text, StyleSheet } from 'react-native'; -import PropTypes from 'prop-types'; -import moment from 'moment'; -import { connect } from 'react-redux'; - -import Markdown from '../markdown'; -import { CustomIcon } from '../../lib/Icons'; -import sharedStyles from '../../views/Styles'; -import { themes } from '../../constants/colors'; - -const styles = StyleSheet.create({ - container: { - flexDirection: 'row', - paddingTop: 10 - }, - messageContainer: { - flex: 1, - marginHorizontal: 10, - paddingHorizontal: 15, - paddingVertical: 10, - borderRadius: 4 - }, - header: { - flexDirection: 'row', - alignItems: 'center' - }, - username: { - fontSize: 16, - ...sharedStyles.textMedium - }, - time: { - fontSize: 12, - lineHeight: 16, - marginLeft: 6, - ...sharedStyles.textRegular, - fontWeight: '300' - }, - close: { - marginRight: 10 - } -}); - -const ReplyPreview = React.memo(({ - message, Message_TimeFormat, baseUrl, username, replying, getCustomEmoji, close, theme, useRealName -}) => { - if (!replying) { - return null; - } - - const time = moment(message.ts).format(Message_TimeFormat); - return ( - - - - {useRealName ? message.u?.name : message.u?.username} - {time} - - - - - - ); -}, (prevProps, nextProps) => prevProps.replying === nextProps.replying && prevProps.theme === nextProps.theme && prevProps.message.id === nextProps.message.id); - -ReplyPreview.propTypes = { - replying: PropTypes.bool, - message: PropTypes.object.isRequired, - Message_TimeFormat: PropTypes.string.isRequired, - close: PropTypes.func.isRequired, - baseUrl: PropTypes.string.isRequired, - username: PropTypes.string.isRequired, - getCustomEmoji: PropTypes.func, - theme: PropTypes.string, - useRealName: PropTypes.bool -}; - -const mapStateToProps = state => ({ - Message_TimeFormat: state.settings.Message_TimeFormat, - baseUrl: state.server.server, - useRealName: state.settings.UI_Use_Real_Name -}); - -export default connect(mapStateToProps)(ReplyPreview); diff --git a/app/containers/MessageBox/ReplyPreview.tsx b/app/containers/MessageBox/ReplyPreview.tsx new file mode 100644 index 000000000..c6681af0a --- /dev/null +++ b/app/containers/MessageBox/ReplyPreview.tsx @@ -0,0 +1,112 @@ +import React from 'react'; +import { StyleSheet, Text, View } from 'react-native'; +import moment from 'moment'; +import { connect } from 'react-redux'; + +import Markdown from '../markdown'; +import { CustomIcon } from '../../lib/Icons'; +import sharedStyles from '../../views/Styles'; +import { themes } from '../../constants/colors'; + +const styles = StyleSheet.create({ + container: { + flexDirection: 'row', + paddingTop: 10 + }, + messageContainer: { + flex: 1, + marginHorizontal: 10, + paddingHorizontal: 15, + paddingVertical: 10, + borderRadius: 4 + }, + header: { + flexDirection: 'row', + alignItems: 'center' + }, + username: { + fontSize: 16, + ...sharedStyles.textMedium + }, + time: { + fontSize: 12, + lineHeight: 16, + marginLeft: 6, + ...sharedStyles.textRegular, + fontWeight: '300' + }, + close: { + marginRight: 10 + } +}); + +interface IMessageBoxReplyPreview { + replying: boolean; + message: { + ts: Date; + msg: string; + u: any; + }; + Message_TimeFormat: string; + close(): void; + baseUrl: string; + username: string; + getCustomEmoji: Function; + theme: string; + useRealName: boolean; +} + +const ReplyPreview = React.memo( + ({ + message, + Message_TimeFormat, + baseUrl, + username, + replying, + getCustomEmoji, + close, + theme, + useRealName + }: IMessageBoxReplyPreview) => { + if (!replying) { + return null; + } + + const time = moment(message.ts).format(Message_TimeFormat); + return ( + + + + + {useRealName ? message.u?.name : message.u?.username} + + {time} + + {/* @ts-ignore*/} + + + + + ); + }, + (prevProps: any, nextProps: any) => + prevProps.replying === nextProps.replying && + prevProps.theme === nextProps.theme && + prevProps.message.id === nextProps.message.id +); + +const mapStateToProps = (state: any) => ({ + Message_TimeFormat: state.settings.Message_TimeFormat, + baseUrl: state.server.server, + useRealName: state.settings.UI_Use_Real_Name +}); + +export default connect(mapStateToProps)(ReplyPreview); diff --git a/app/containers/MessageBox/RightButtons.android.js b/app/containers/MessageBox/RightButtons.android.js deleted file mode 100644 index 6da835642..000000000 --- a/app/containers/MessageBox/RightButtons.android.js +++ /dev/null @@ -1,29 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import { View } from 'react-native'; - -import { SendButton, ActionsButton } from './buttons'; -import styles from './styles'; - -const RightButtons = React.memo(({ - theme, showSend, submit, showMessageBoxActions, isActionsEnabled -}) => { - if (showSend) { - return ; - } - if (isActionsEnabled) { - return ; - } - - return ; -}); - -RightButtons.propTypes = { - theme: PropTypes.string, - showSend: PropTypes.bool, - submit: PropTypes.func.isRequired, - showMessageBoxActions: PropTypes.func.isRequired, - isActionsEnabled: PropTypes.bool -}; - -export default RightButtons; diff --git a/app/containers/MessageBox/RightButtons.android.tsx b/app/containers/MessageBox/RightButtons.android.tsx new file mode 100644 index 000000000..0ad3b22ee --- /dev/null +++ b/app/containers/MessageBox/RightButtons.android.tsx @@ -0,0 +1,28 @@ +import React from 'react'; +import { View } from 'react-native'; + +import { ActionsButton, SendButton } from './buttons'; +import styles from './styles'; + +interface IMessageBoxRightButtons { + theme: string; + showSend: boolean; + submit(): void; + showMessageBoxActions(): void; + isActionsEnabled: boolean; +} + +const RightButtons = React.memo( + ({ theme, showSend, submit, showMessageBoxActions, isActionsEnabled }: IMessageBoxRightButtons) => { + if (showSend) { + return ; + } + if (isActionsEnabled) { + return ; + } + + return ; + } +); + +export default RightButtons; diff --git a/app/containers/MessageBox/RightButtons.ios.js b/app/containers/MessageBox/RightButtons.ios.js deleted file mode 100644 index 9ea5fc74e..000000000 --- a/app/containers/MessageBox/RightButtons.ios.js +++ /dev/null @@ -1,19 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; - -import { SendButton } from './buttons'; - -const RightButtons = React.memo(({ theme, showSend, submit }) => { - if (showSend) { - return ; - } - return null; -}); - -RightButtons.propTypes = { - theme: PropTypes.string, - showSend: PropTypes.bool, - submit: PropTypes.func.isRequired -}; - -export default RightButtons; diff --git a/app/containers/MessageBox/RightButtons.ios.tsx b/app/containers/MessageBox/RightButtons.ios.tsx new file mode 100644 index 000000000..c295d37dc --- /dev/null +++ b/app/containers/MessageBox/RightButtons.ios.tsx @@ -0,0 +1,18 @@ +import React from 'react'; + +import { SendButton } from './buttons'; + +interface IMessageBoxRightButtons { + theme: string; + showSend: boolean; + submit(): void; +} + +const RightButtons = React.memo(({ theme, showSend, submit }: IMessageBoxRightButtons) => { + if (showSend) { + return ; + } + return null; +}); + +export default RightButtons; diff --git a/app/containers/MessageBox/buttons/ActionsButton.js b/app/containers/MessageBox/buttons/ActionsButton.js deleted file mode 100644 index 5738e71ba..000000000 --- a/app/containers/MessageBox/buttons/ActionsButton.js +++ /dev/null @@ -1,21 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; - -import BaseButton from './BaseButton'; - -const ActionsButton = React.memo(({ theme, onPress }) => ( - -)); - -ActionsButton.propTypes = { - theme: PropTypes.string, - onPress: PropTypes.func.isRequired -}; - -export default ActionsButton; diff --git a/app/containers/MessageBox/buttons/ActionsButton.tsx b/app/containers/MessageBox/buttons/ActionsButton.tsx new file mode 100644 index 000000000..e40513b4a --- /dev/null +++ b/app/containers/MessageBox/buttons/ActionsButton.tsx @@ -0,0 +1,14 @@ +import React from 'react'; + +import BaseButton from './BaseButton'; + +interface IActionsButton { + theme: string; + onPress(): void; +} + +const ActionsButton = React.memo(({ theme, onPress }: IActionsButton) => ( + +)); + +export default ActionsButton; diff --git a/app/containers/MessageBox/buttons/BaseButton.js b/app/containers/MessageBox/buttons/BaseButton.tsx similarity index 54% rename from app/containers/MessageBox/buttons/BaseButton.js rename to app/containers/MessageBox/buttons/BaseButton.tsx index b2d374451..ca14cb015 100644 --- a/app/containers/MessageBox/buttons/BaseButton.js +++ b/app/containers/MessageBox/buttons/BaseButton.tsx @@ -1,33 +1,30 @@ import React from 'react'; import { BorderlessButton } from 'react-native-gesture-handler'; -import PropTypes from 'prop-types'; import { themes } from '../../../constants/colors'; import { CustomIcon } from '../../../lib/Icons'; import styles from '../styles'; import I18n from '../../../i18n'; -const BaseButton = React.memo(({ - onPress, testID, accessibilityLabel, icon, theme, color -}) => ( +interface IBaseButton { + theme: string; + onPress(): void; + testID: string; + accessibilityLabel: string; + icon: string; + color: string; +} + +const BaseButton = React.memo(({ onPress, testID, accessibilityLabel, icon, theme, color }: Partial) => ( - + accessibilityTraits='button'> + )); -BaseButton.propTypes = { - theme: PropTypes.string, - onPress: PropTypes.func.isRequired, - testID: PropTypes.string.isRequired, - accessibilityLabel: PropTypes.string.isRequired, - icon: PropTypes.string.isRequired, - color: PropTypes.string -}; - export default BaseButton; diff --git a/app/containers/MessageBox/buttons/CancelEditingButton.js b/app/containers/MessageBox/buttons/CancelEditingButton.tsx similarity index 55% rename from app/containers/MessageBox/buttons/CancelEditingButton.js rename to app/containers/MessageBox/buttons/CancelEditingButton.tsx index 1a6e69bb1..9f026dabc 100644 --- a/app/containers/MessageBox/buttons/CancelEditingButton.js +++ b/app/containers/MessageBox/buttons/CancelEditingButton.tsx @@ -1,9 +1,13 @@ import React from 'react'; -import PropTypes from 'prop-types'; import BaseButton from './BaseButton'; -const CancelEditingButton = React.memo(({ theme, onPress }) => ( +interface ICancelEditingButton { + theme: string; + onPress(): void; +} + +const CancelEditingButton = React.memo(({ theme, onPress }: ICancelEditingButton) => ( ( /> )); -CancelEditingButton.propTypes = { - theme: PropTypes.string, - onPress: PropTypes.func.isRequired -}; - export default CancelEditingButton; diff --git a/app/containers/MessageBox/buttons/SendButton.js b/app/containers/MessageBox/buttons/SendButton.tsx similarity index 64% rename from app/containers/MessageBox/buttons/SendButton.js rename to app/containers/MessageBox/buttons/SendButton.tsx index 7f465db8b..7f24ad7e8 100644 --- a/app/containers/MessageBox/buttons/SendButton.js +++ b/app/containers/MessageBox/buttons/SendButton.tsx @@ -1,10 +1,14 @@ import React from 'react'; -import PropTypes from 'prop-types'; import BaseButton from './BaseButton'; import { themes } from '../../../constants/colors'; -const SendButton = React.memo(({ theme, onPress }) => ( +interface ISendButton { + theme: string; + onPress(): void; +} + +const SendButton = React.memo(({ theme, onPress }: ISendButton) => ( ( /> )); -SendButton.propTypes = { - theme: PropTypes.string, - onPress: PropTypes.func.isRequired -}; - export default SendButton; diff --git a/app/containers/MessageBox/buttons/ToggleEmojiButton.js b/app/containers/MessageBox/buttons/ToggleEmojiButton.tsx similarity index 63% rename from app/containers/MessageBox/buttons/ToggleEmojiButton.js rename to app/containers/MessageBox/buttons/ToggleEmojiButton.tsx index 86206b3c1..cd04fff14 100644 --- a/app/containers/MessageBox/buttons/ToggleEmojiButton.js +++ b/app/containers/MessageBox/buttons/ToggleEmojiButton.tsx @@ -1,11 +1,15 @@ import React from 'react'; -import PropTypes from 'prop-types'; import BaseButton from './BaseButton'; -const ToggleEmojiButton = React.memo(({ - theme, show, open, close -}) => { +interface IToggleEmojiButton { + theme: string; + show: boolean; + open(): void; + close(): void; +} + +const ToggleEmojiButton = React.memo(({ theme, show, open, close }: IToggleEmojiButton) => { if (show) { return ( { + private text: string; + + private selection: { start: number; end: number }; + + private focused: boolean; + + private options: any; + + private imagePickerConfig: any; + + private libraryPickerConfig: any; + + private videoPickerConfig: any; + + private room: any; + + private thread: any; + + private unsubscribeFocus: any; + + private trackingTimeout: any; + + private tracking: any; + + private unsubscribeBlur: any; + + private component: any; + + private typingTimeout: any; static defaultProps = { message: { @@ -117,9 +165,9 @@ class MessageBox extends Component { iOSScrollBehavior: NativeModules.KeyboardTrackingViewTempManager?.KeyboardTrackingScrollBehaviorFixedOffset, isActionsEnabled: true, getCustomEmoji: () => {} - } + }; - constructor(props) { + constructor(props: IMessageBoxProps) { super(props); this.state = { mentions: [], @@ -130,7 +178,8 @@ class MessageBox extends Component { commandPreview: [], showCommandPreview: false, command: {}, - tshow: false + tshow: false, + mentionLoading: false }; this.text = ''; this.selection = { start: 0, end: 0 }; @@ -170,14 +219,17 @@ class MessageBox extends Component { cropperCancelText: I18n.t('Cancel'), loadingLabelText: I18n.t('Processing') }; + this.imagePickerConfig = { ...imagePickerConfig, ...libPickerLabels }; + this.libraryPickerConfig = { ...libraryPickerConfig, ...libPickerLabels }; + this.videoPickerConfig = { ...videoPickerConfig, ...libPickerLabels @@ -186,9 +238,7 @@ class MessageBox extends Component { async componentDidMount() { const db = database.active; - const { - rid, tmid, navigation, sharing - } = this.props; + const { rid, tmid, navigation, sharing, usedCannedResponse, isMasterDetail } = this.props; let msg; try { const threadsCollection = db.get('threads'); @@ -223,6 +273,10 @@ class MessageBox extends Component { EventEmiter.addEventListener(KEY_COMMAND, this.handleCommands); } + if (isMasterDetail && usedCannedResponse) { + this.onChangeText(usedCannedResponse); + } + this.unsubscribeFocus = navigation.addListener('focus', () => { // didFocus // We should wait pushed views be dismissed @@ -238,13 +292,14 @@ class MessageBox extends Component { }); } - UNSAFE_componentWillReceiveProps(nextProps) { - const { - isFocused, editing, replying, sharing - } = this.props; + UNSAFE_componentWillReceiveProps(nextProps: any) { + const { isFocused, editing, replying, sharing, usedCannedResponse } = this.props; if (!isFocused?.()) { return; } + if (usedCannedResponse !== nextProps.usedCannedResponse) { + this.onChangeText(nextProps.usedCannedResponse ?? ''); + } if (sharing) { this.setInput(nextProps.message.msg ?? ''); return; @@ -266,14 +321,10 @@ class MessageBox extends Component { } } - shouldComponentUpdate(nextProps, nextState) { - const { - showEmojiKeyboard, showSend, recording, mentions, commandPreview, tshow - } = this.state; + shouldComponentUpdate(nextProps: any, nextState: any) { + const { showEmojiKeyboard, showSend, recording, mentions, commandPreview, tshow, mentionLoading, trackingType } = this.state; - const { - roomType, replying, editing, isFocused, message, theme - } = this.props; + const { roomType, replying, editing, isFocused, message, theme, usedCannedResponse } = this.props; if (nextProps.theme !== theme) { return true; } @@ -292,6 +343,12 @@ class MessageBox extends Component { if (nextState.showEmojiKeyboard !== showEmojiKeyboard) { return true; } + if (nextState.trackingType !== trackingType) { + return true; + } + if (nextState.mentionLoading !== mentionLoading) { + return true; + } if (nextState.showSend !== showSend) { return true; } @@ -310,11 +367,14 @@ class MessageBox extends Component { if (!dequal(nextProps.message?.id, message?.id)) { return true; } + if (nextProps.usedCannedResponse !== usedCannedResponse) { + return true; + } return false; } componentWillUnmount() { - console.countReset(`${ this.constructor.name }.render calls`); + console.countReset(`${this.constructor.name}.render calls`); if (this.onChangeText && this.onChangeText.stop) { this.onChangeText.stop(); } @@ -330,6 +390,9 @@ class MessageBox extends Component { if (this.getSlashCommands && this.getSlashCommands.stop) { this.getSlashCommands.stop(); } + if (this.getCannedResponses && this.getCannedResponses.stop) { + this.getCannedResponses.stop(); + } if (this.unsubscribeFocus) { this.unsubscribeFocus(); } @@ -341,20 +404,20 @@ class MessageBox extends Component { } } - onChangeText = (text) => { + onChangeText: any = (text: string): void => { const isTextEmpty = text.length === 0; this.setShowSend(!isTextEmpty); this.debouncedOnChangeText(text); this.setInput(text); - } + }; - onSelectionChange = (e) => { + onSelectionChange = (e: any) => { this.selection = e.nativeEvent.selection; - } + }; // eslint-disable-next-line react/sort-comp - debouncedOnChangeText = debounce(async(text) => { - const { sharing } = this.props; + debouncedOnChangeText = debounce(async (text: any) => { + const { sharing, roomType } = this.props; const isTextEmpty = text.length === 0; if (isTextEmpty) { this.stopTrackingMention(); @@ -371,6 +434,7 @@ class MessageBox extends Component { const channelMention = lastWord.match(/^#/); const userMention = lastWord.match(/^@/); const emojiMention = lastWord.match(/^:/); + const cannedMention = lastWord.match(/^!/); if (commandMention && !sharing) { const command = text.substr(1); @@ -389,22 +453,27 @@ class MessageBox extends Component { } } return this.identifyMentionKeyword(command, MENTIONS_TRACKING_TYPE_COMMANDS); - } else if (channelMention) { - return this.identifyMentionKeyword(result, MENTIONS_TRACKING_TYPE_ROOMS); - } else if (userMention) { - return this.identifyMentionKeyword(result, MENTIONS_TRACKING_TYPE_USERS); - } else if (emojiMention) { - return this.identifyMentionKeyword(result, MENTIONS_TRACKING_TYPE_EMOJIS); - } else { - return this.stopTrackingMention(); } - }, 100) + if (channelMention) { + return this.identifyMentionKeyword(result, MENTIONS_TRACKING_TYPE_ROOMS); + } + if (userMention) { + return this.identifyMentionKeyword(result, MENTIONS_TRACKING_TYPE_USERS); + } + if (emojiMention) { + return this.identifyMentionKeyword(result, MENTIONS_TRACKING_TYPE_EMOJIS); + } + if (cannedMention && roomType === 'l') { + return this.identifyMentionKeyword(result, MENTIONS_TRACKING_TYPE_CANNED); + } + return this.stopTrackingMention(); + }, 100); onKeyboardResigned = () => { this.closeEmoji(); - } + }; - onPressMention = (item) => { + onPressMention = (item: any) => { if (!this.component) { return; } @@ -413,24 +482,34 @@ class MessageBox extends Component { const { start, end } = this.selection; const cursor = Math.max(start, end); const regexp = /([a-z0-9._-]+)$/im; - const result = msg.substr(0, cursor).replace(regexp, ''); - const mentionName = trackingType === MENTIONS_TRACKING_TYPE_EMOJIS - ? `${ item.name || item }:` - : (item.username || item.name || item.command); - const text = `${ result }${ mentionName } ${ msg.slice(cursor) }`; - if ((trackingType === MENTIONS_TRACKING_TYPE_COMMANDS) && item.providesPreview) { + let result = msg.substr(0, cursor).replace(regexp, ''); + // Remove the ! after select the canned response + if (trackingType === MENTIONS_TRACKING_TYPE_CANNED) { + const lastIndexOfExclamation = msg.lastIndexOf('!', cursor); + result = msg.substr(0, lastIndexOfExclamation).replace(regexp, ''); + } + const mentionName = + trackingType === MENTIONS_TRACKING_TYPE_EMOJIS + ? `${item.name || item}:` + : item.username || item.name || item.command || item.text; + const text = `${result}${mentionName} ${msg.slice(cursor)}`; + if (trackingType === MENTIONS_TRACKING_TYPE_COMMANDS && item.providesPreview) { this.setState({ showCommandPreview: true }); } + const newCursor = cursor + mentionName.length; this.setInput(text, { start: newCursor, end: newCursor }); this.focus(); requestAnimationFrame(() => this.stopTrackingMention()); - } + }; - onPressCommandPreview = (item) => { + onPressCommandPreview = (item: any) => { const { command } = this.state; const { - rid, tmid, message: { id: messageTmid }, replyCancel + rid, + tmid, + message: { id: messageTmid }, + replyCancel } = this.props; const { text } = this; const name = text.substr(0, text.indexOf(' ')).slice(1); @@ -447,9 +526,9 @@ class MessageBox extends Component { } catch (e) { log(e); } - } + }; - onEmojiSelected = (keyboardId, params) => { + onEmojiSelected = (keyboardId: any, params: any) => { const { text } = this; const { emoji } = params; let newText = ''; @@ -457,22 +536,22 @@ class MessageBox extends Component { // if messagebox has an active cursor const { start, end } = this.selection; const cursor = Math.max(start, end); - newText = `${ text.substr(0, cursor) }${ emoji }${ text.substr(cursor) }`; + newText = `${text.substr(0, cursor)}${emoji}${text.substr(cursor)}`; const newCursor = cursor + emoji.length; this.setInput(newText, { start: newCursor, end: newCursor }); this.setShowSend(true); - } + }; - getPermalink = async(message) => { + getPermalink = async (message: any) => { try { return await RocketChat.getPermalinkMessage(message); } catch (error) { return null; } - } + }; - getFixedMentions = (keyword) => { - let result = []; + getFixedMentions = (keyword: any) => { + let result: any = []; if ('all'.indexOf(keyword) !== -1) { result = [{ rid: -1, username: 'all' }]; } @@ -480,51 +559,54 @@ class MessageBox extends Component { result = [{ rid: -2, username: 'here' }, ...result]; } return result; - } + }; - getUsers = debounce(async(keyword) => { + getUsers = debounce(async (keyword: any) => { let res = await RocketChat.search({ text: keyword, filterRooms: false, filterUsers: true }); res = [...this.getFixedMentions(keyword), ...res]; - this.setState({ mentions: res }); - }, 300) + this.setState({ mentions: res, mentionLoading: false }); + }, 300); - getRooms = debounce(async(keyword = '') => { + getRooms = debounce(async (keyword = '') => { const res = await RocketChat.search({ text: keyword, filterRooms: true, filterUsers: false }); - this.setState({ mentions: res }); - }, 300) + this.setState({ mentions: res, mentionLoading: false }); + }, 300); - getEmojis = debounce(async(keyword) => { + getEmojis = debounce(async (keyword: any) => { const db = database.active; const customEmojisCollection = db.get('custom_emojis'); const likeString = sanitizeLikeString(keyword); const whereClause = []; if (likeString) { - whereClause.push(Q.where('name', Q.like(`${ likeString }%`))); + whereClause.push(Q.where('name', Q.like(`${likeString}%`))); } let customEmojis = await customEmojisCollection.query(...whereClause).fetch(); customEmojis = customEmojis.slice(0, MENTIONS_COUNT_TO_DISPLAY); const filteredEmojis = emojis.filter(emoji => emoji.indexOf(keyword) !== -1).slice(0, MENTIONS_COUNT_TO_DISPLAY); const mergedEmojis = [...customEmojis, ...filteredEmojis].slice(0, MENTIONS_COUNT_TO_DISPLAY); - this.setState({ mentions: mergedEmojis || [] }); - }, 300) + this.setState({ mentions: mergedEmojis || [], mentionLoading: false }); + }, 300); - getSlashCommands = debounce(async(keyword) => { + getSlashCommands = debounce(async (keyword: any) => { const db = database.active; const commandsCollection = db.get('slash_commands'); const likeString = sanitizeLikeString(keyword); - const commands = await commandsCollection.query( - Q.where('id', Q.like(`${ likeString }%`)) - ).fetch(); - this.setState({ mentions: commands || [] }); - }, 300) + const commands = await commandsCollection.query(Q.where('id', Q.like(`${likeString}%`))).fetch(); + this.setState({ mentions: commands || [], mentionLoading: false }); + }, 300); + + getCannedResponses = debounce(async (text?: string) => { + const res = await RocketChat.getListCannedResponse({ text }); + this.setState({ mentions: res?.cannedResponses || [], mentionLoading: false }); + }, 500); focus = () => { if (this.component && this.component.focus) { this.component.focus(); } - } + }; - handleTyping = (isTyping) => { + handleTyping = (isTyping: boolean) => { const { typing, rid, sharing } = this.props; if (sharing) { return; @@ -546,11 +628,11 @@ class MessageBox extends Component { typing(rid, true); this.typingTimeout = false; }, 1000); - } + }; - setCommandPreview = async(command, name, params) => { + setCommandPreview = async (command: any, name: string, params: any) => { const { rid } = this.props; - try { + try { const { success, preview } = await RocketChat.getCommandPreview(name, rid, params); if (success) { return this.setState({ commandPreview: preview?.items, showCommandPreview: true, command }); @@ -559,31 +641,31 @@ class MessageBox extends Component { log(e); } this.setState({ commandPreview: [], showCommandPreview: true, command: {} }); - } + }; - setInput = (text, selection) => { + setInput = (text: any, selection?: any) => { this.text = text; if (selection) { return this.component.setTextAndSelection(text, selection); } this.component.setNativeProps({ text }); - } + }; - setShowSend = (showSend) => { + setShowSend = (showSend: any) => { const { showSend: prevShowSend } = this.state; const { showSend: propShowSend } = this.props; if (prevShowSend !== showSend && !propShowSend) { this.setState({ showSend }); } - } + }; clearInput = () => { this.setInput(''); this.setShowSend(false); this.setState({ tshow: false }); - } + }; - canUploadFile = (file) => { + canUploadFile = (file: any) => { const { FileUpload_MediaTypeWhiteList, FileUpload_MaxFileSize } = this.props; const result = canUploadFile(file, FileUpload_MediaTypeWhiteList, FileUpload_MaxFileSize); if (result.success) { @@ -591,9 +673,9 @@ class MessageBox extends Component { } Alert.alert(I18n.t('Error_uploading'), I18n.t(result.error)); return false; - } + }; - takePhoto = async() => { + takePhoto = async () => { logEvent(events.ROOM_BOX_ACTION_PHOTO); try { const image = await ImagePicker.openCamera(this.imagePickerConfig); @@ -603,9 +685,9 @@ class MessageBox extends Component { } catch (e) { logEvent(events.ROOM_BOX_ACTION_PHOTO_F); } - } + }; - takeVideo = async() => { + takeVideo = async () => { logEvent(events.ROOM_BOX_ACTION_VIDEO); try { const video = await ImagePicker.openCamera(this.videoPickerConfig); @@ -615,9 +697,9 @@ class MessageBox extends Component { } catch (e) { logEvent(events.ROOM_BOX_ACTION_VIDEO_F); } - } + }; - chooseFromLibrary = async() => { + chooseFromLibrary = async () => { logEvent(events.ROOM_BOX_ACTION_LIBRARY); try { const attachments = await ImagePicker.openPicker(this.libraryPickerConfig); @@ -625,9 +707,9 @@ class MessageBox extends Component { } catch (e) { logEvent(events.ROOM_BOX_ACTION_LIBRARY_F); } - } + }; - chooseFile = async() => { + chooseFile = async () => { logEvent(events.ROOM_BOX_ACTION_FILE); try { const res = await DocumentPicker.pick({ @@ -642,15 +724,25 @@ class MessageBox extends Component { if (this.canUploadFile(file)) { this.openShareView([file]); } - } catch (e) { + } catch (e: any) { if (!DocumentPicker.isCancel(e)) { logEvent(events.ROOM_BOX_ACTION_FILE_F); log(e); } } - } + }; - openShareView = (attachments) => { + onPressNoMatchCanned = () => { + const { isMasterDetail, rid } = this.props; + const params = { rid }; + if (isMasterDetail) { + Navigation.navigate('ModalStackNavigator', { screen: 'CannedResponsesListView', params }); + } else { + Navigation.navigate('CannedResponsesListView', params); + } + }; + + openShareView = (attachments: any) => { const { message, replyCancel, replyWithMention } = this.props; // Start a thread with an attachment let { thread } = this; @@ -659,7 +751,7 @@ class MessageBox extends Component { replyCancel(); } Navigation.navigate('ShareView', { room: this.room, thread, attachments }); - } + }; createDiscussion = () => { logEvent(events.ROOM_BOX_ACTION_DISCUSSION); @@ -670,33 +762,31 @@ class MessageBox extends Component { } else { Navigation.navigate('NewMessageStackNavigator', { screen: 'CreateDiscussionView', params }); } - } + }; showMessageBoxActions = () => { logEvent(events.ROOM_SHOW_BOX_ACTIONS); const { showActionSheet } = this.props; showActionSheet({ options: this.options }); - } + }; editCancel = () => { const { editCancel } = this.props; editCancel(); this.clearInput(); - } + }; openEmoji = () => { logEvent(events.ROOM_OPEN_EMOJI); this.setState({ showEmojiKeyboard: true }); - } + }; - recordingCallback = (recording) => { + recordingCallback = (recording: any) => { this.setState({ recording }); - } + }; - finishAudioMessage = async(fileInfo) => { - const { - rid, tmid, baseUrl: server, user - } = this.props; + finishAudioMessage = async (fileInfo: any) => { + const { rid, tmid, baseUrl: server, user } = this.props; if (fileInfo) { try { @@ -707,17 +797,15 @@ class MessageBox extends Component { log(e); } } - } + }; closeEmoji = () => { this.setState({ showEmojiKeyboard: false }); - } + }; - submit = async() => { + submit = async () => { const { tshow } = this.state; - const { - onSubmit, rid: roomId, tmid, showSend, sharing - } = this.props; + const { onSubmit, rid: roomId, tmid, showSend, sharing } = this.props; const message = this.text; // if sharing, only execute onSubmit prop @@ -736,7 +824,10 @@ class MessageBox extends Component { } const { - editing, replying, message: { id: messageTmid }, replyCancel + editing, + replying, + message: { id: messageTmid }, + replyCancel } = this.props; // Slash command @@ -745,9 +836,7 @@ class MessageBox extends Component { const commandsCollection = db.get('slash_commands'); const command = message.replace(/ .*/, '').slice(1); const likeString = sanitizeLikeString(command); - const slashCommand = await commandsCollection.query( - Q.where('id', Q.like(`${ likeString }%`)) - ).fetch(); + const slashCommand = await commandsCollection.query(Q.where('id', Q.like(`${likeString}%`))).fetch(); if (slashCommand.length > 0) { logEvent(events.COMMAND_RUN); try { @@ -767,60 +856,66 @@ class MessageBox extends Component { // Edit if (editing) { const { message: editingMessage, editRequest } = this.props; - const { id, subscription: { id: rid } } = editingMessage; + const { + id, + // @ts-ignore + subscription: { id: rid } + } = editingMessage; editRequest({ id, msg: message, rid }); - // Reply + // Reply } else if (replying) { - const { - message: replyingMessage, threadsEnabled, replyWithMention - } = this.props; + const { message: replyingMessage, threadsEnabled, replyWithMention } = this.props; // Thread if (threadsEnabled && replyWithMention) { onSubmit(message, replyingMessage.id, tshow); - // Legacy reply or quote (quote is a reply without mention) + // Legacy reply or quote (quote is a reply without mention) } else { const { user, roomType } = this.props; const permalink = await this.getPermalink(replyingMessage); - let msg = `[ ](${ permalink }) `; + let msg = `[ ](${permalink}) `; // if original message wasn't sent by current user and neither from a direct room if (user.username !== replyingMessage.u.username && roomType !== 'd' && replyWithMention) { - msg += `@${ replyingMessage.u.username } `; + msg += `@${replyingMessage.u.username} `; } - msg = `${ msg } ${ message }`; + msg = `${msg} ${message}`; onSubmit(msg); } replyCancel(); - // Normal message + // Normal message } else { + // @ts-ignore onSubmit(message, undefined, tshow); } - } + }; - updateMentions = (keyword, type) => { + updateMentions = (keyword: any, type: string) => { if (type === MENTIONS_TRACKING_TYPE_USERS) { this.getUsers(keyword); } else if (type === MENTIONS_TRACKING_TYPE_EMOJIS) { this.getEmojis(keyword); } else if (type === MENTIONS_TRACKING_TYPE_COMMANDS) { this.getSlashCommands(keyword); + } else if (type === MENTIONS_TRACKING_TYPE_CANNED) { + this.getCannedResponses(keyword); } else { this.getRooms(keyword); } - } + }; - identifyMentionKeyword = (keyword, type) => { + identifyMentionKeyword = (keyword: any, type: string) => { this.setState({ showEmojiKeyboard: false, - trackingType: type + trackingType: type, + mentionLoading: true }); this.updateMentions(keyword, type); - } + }; stopTrackingMention = () => { const { trackingType, showCommandPreview } = this.state; @@ -833,9 +928,9 @@ class MessageBox extends Component { commandPreview: [], showCommandPreview: false }); - } + }; - handleCommands = ({ event }) => { + handleCommands = ({ event }: { event: any }) => { if (handleCommandTyping(event)) { if (this.focused) { Keyboard.dismiss(); @@ -848,9 +943,9 @@ class MessageBox extends Component { } else if (handleCommandShowUpload(event)) { this.showMessageBoxActions(); } - } + }; - onPressSendToChannel = () => this.setState(({ tshow }) => ({ tshow: !tshow })) + onPressSendToChannel = () => this.setState(({ tshow }) => ({ tshow: !tshow })); renderSendToChannel = () => { const { tshow } = this.state; @@ -863,45 +958,56 @@ class MessageBox extends Component { + testID='messagebox-send-to-channel'> - {I18n.t('Messagebox_Send_to_channel')} + + {I18n.t('Messagebox_Send_to_channel')} + ); - } + }; renderContent = () => { + const { recording, showEmojiKeyboard, showSend, mentions, trackingType, commandPreview, showCommandPreview, mentionLoading } = + this.state; const { - recording, showEmojiKeyboard, showSend, mentions, trackingType, commandPreview, showCommandPreview - } = this.state; - const { - editing, message, replying, replyCancel, user, getCustomEmoji, theme, Message_AudioRecorderEnabled, children, isActionsEnabled, tmid + editing, + message, + replying, + replyCancel, + user, + getCustomEmoji, + theme, + Message_AudioRecorderEnabled, + children, + isActionsEnabled, + tmid } = this.props; - const isAndroidTablet = isTablet && isAndroid ? { - multiline: false, - onSubmitEditing: this.submit, - returnKeyType: 'send' - } : {}; + const isAndroidTablet = + isTablet && isAndroid + ? { + multiline: false, + onSubmitEditing: this.submit, + returnKeyType: 'send' + } + : {}; - const recordAudio = showSend || !Message_AudioRecorderEnabled ? null : ( - - ); + const recordAudio = + showSend || !Message_AudioRecorderEnabled ? null : ( + + ); const commandsPreviewAndMentions = !recording ? ( <> - + ) : null; const replyPreview = !recording ? ( this.component = component} + ref={component => (this.component = component)} style={[styles.textBoxInput, { color: themes[theme].bodyText }]} + // @ts-ignore returnKeyType='default' keyboardType='twitter' blurOnSubmit={false} @@ -936,7 +1043,7 @@ class MessageBox extends Component { underlineColorAndroid='transparent' defaultValue='' multiline - testID={`messagebox-input${ tmid ? '-thread' : '' }`} + testID={`messagebox-input${tmid ? '-thread' : ''}`} theme={theme} {...isAndroidTablet} /> @@ -961,8 +1068,7 @@ class MessageBox extends Component { { backgroundColor: themes[theme].messageboxBackground }, !recording && editing && { backgroundColor: themes[theme].chatComponentBackground } ]} - testID='messagebox' - > + testID='messagebox'> {textInputAndButtons} {recordAudio} @@ -971,32 +1077,29 @@ class MessageBox extends Component { {children} ); - } + }; render() { - console.count(`${ this.constructor.name }.render calls`); + console.count(`${this.constructor.name}.render calls`); const { showEmojiKeyboard } = this.state; - const { - user, baseUrl, theme, iOSScrollBehavior - } = this.props; + const { user, baseUrl, theme, iOSScrollBehavior } = this.props; return ( + onPressCommandPreview: this.onPressCommandPreview, + onPressNoMatchCanned: this.onPressNoMatchCanned + }}> this.tracking = ref} + ref={(ref: any) => (this.tracking = ref)} renderContent={this.renderContent} kbInputRef={this.component} kbComponent={showEmojiKeyboard ? 'EmojiKeyboard' : null} onKeyboardResigned={this.onKeyboardResigned} onItemSelected={this.onEmojiSelected} trackInteractive - // revealKeyboardInteractive requiresSameParentToManageScrollView addBottomView bottomViewColor={themes[theme].messageboxBackground} @@ -1007,7 +1110,7 @@ class MessageBox extends Component { } } -const mapStateToProps = state => ({ +const mapStateToProps = (state: any) => ({ isMasterDetail: state.app.isMasterDetail, baseUrl: state.server.server, threadsEnabled: state.settings.Threads_enabled, @@ -1017,8 +1120,8 @@ const mapStateToProps = state => ({ Message_AudioRecorderEnabled: state.settings.Message_AudioRecorderEnabled }); -const dispatchToProps = ({ - typing: (rid, status) => userTypingAction(rid, status) -}); - +const dispatchToProps = { + typing: (rid: any, status: any) => userTypingAction(rid, status) +}; +// @ts-ignore export default connect(mapStateToProps, dispatchToProps, null, { forwardRef: true })(withActionSheet(MessageBox)); diff --git a/app/containers/MessageBox/styles.js b/app/containers/MessageBox/styles.ts similarity index 76% rename from app/containers/MessageBox/styles.js rename to app/containers/MessageBox/styles.ts index 9c5a2ff52..6f4599090 100644 --- a/app/containers/MessageBox/styles.js +++ b/app/containers/MessageBox/styles.ts @@ -36,6 +36,30 @@ export default StyleSheet.create({ width: 60, height: 48 }, + wrapMentionHeaderList: { + height: MENTION_HEIGHT, + justifyContent: 'center' + }, + wrapMentionHeaderListRow: { + height: MENTION_HEIGHT, + flexDirection: 'row', + alignItems: 'center', + paddingHorizontal: 12 + }, + loadingPaddingHeader: { + paddingRight: 12 + }, + mentionHeaderList: { + fontSize: 14, + ...sharedStyles.textMedium + }, + mentionHeaderListNoMatchFound: { + fontSize: 14, + ...sharedStyles.textRegular + }, + mentionNoMatchHeader: { + justifyContent: 'space-between' + }, mentionList: { maxHeight: MENTION_HEIGHT * 4 }, @@ -67,6 +91,18 @@ export default StyleSheet.create({ fontSize: 14, ...sharedStyles.textRegular }, + cannedMentionText: { + flex: 1, + fontSize: 14, + paddingRight: 12, + ...sharedStyles.textRegular + }, + cannedItem: { + fontSize: 14, + ...sharedStyles.textBold, + paddingLeft: 12, + paddingRight: 8 + }, emojiKeyboardContainer: { flex: 1, borderTopWidth: StyleSheet.hairlineWidth @@ -103,7 +139,8 @@ export default StyleSheet.create({ flex: 1, justifyContent: 'space-between' }, - recordingCancelText: { + recordingDurationText: { + width: 60, fontSize: 16, ...sharedStyles.textRegular }, diff --git a/app/containers/MessageErrorActions.js b/app/containers/MessageErrorActions.tsx similarity index 78% rename from app/containers/MessageErrorActions.js rename to app/containers/MessageErrorActions.tsx index 1711e7df7..6f56e54db 100644 --- a/app/containers/MessageErrorActions.js +++ b/app/containers/MessageErrorActions.tsx @@ -1,5 +1,4 @@ -import { useImperativeHandle, forwardRef } from 'react'; -import PropTypes from 'prop-types'; +import { forwardRef, useImperativeHandle } from 'react'; import RocketChat from '../lib/rocketchat'; import database from '../lib/database'; @@ -8,17 +7,17 @@ import { useActionSheet } from './ActionSheet'; import I18n from '../i18n'; import log from '../utils/log'; -const MessageErrorActions = forwardRef(({ tmid }, ref) => { - const { showActionSheet } = useActionSheet(); +const MessageErrorActions = forwardRef(({ tmid }: any, ref): any => { + const { showActionSheet }: any = useActionSheet(); - const handleResend = protectedFunction(async(message) => { + const handleResend = protectedFunction(async (message: any) => { await RocketChat.resendMessage(message, tmid); }); - const handleDelete = async(message) => { + const handleDelete = async (message: any) => { try { const db = database.active; - const deleteBatch = []; + const deleteBatch: any = []; const msgCollection = db.get('messages'); const threadCollection = db.get('threads'); @@ -39,7 +38,7 @@ const MessageErrorActions = forwardRef(({ tmid }, ref) => { const msg = await msgCollection.find(tmid); if (msg.tcount <= 1) { deleteBatch.push( - msg.prepareUpdate((m) => { + msg.prepareUpdate((m: any) => { m.tcount = null; m.tlm = null; }) @@ -54,7 +53,7 @@ const MessageErrorActions = forwardRef(({ tmid }, ref) => { } } else { deleteBatch.push( - msg.prepareUpdate((m) => { + msg.prepareUpdate((m: any) => { m.tcount -= 1; }) ); @@ -63,7 +62,7 @@ const MessageErrorActions = forwardRef(({ tmid }, ref) => { // Do nothing: message not found } } - await db.action(async() => { + await db.action(async () => { await db.batch(...deleteBatch); }); } catch (e) { @@ -71,7 +70,7 @@ const MessageErrorActions = forwardRef(({ tmid }, ref) => { } }; - const showMessageErrorActions = (message) => { + const showMessageErrorActions = (message: any) => { showActionSheet({ options: [ { @@ -96,8 +95,5 @@ const MessageErrorActions = forwardRef(({ tmid }, ref) => { return null; }); -MessageErrorActions.propTypes = { - tmid: PropTypes.string -}; export default MessageErrorActions; diff --git a/app/containers/OrSeparator.js b/app/containers/OrSeparator.tsx similarity index 70% rename from app/containers/OrSeparator.js rename to app/containers/OrSeparator.tsx index 665c14538..5be1ee883 100644 --- a/app/containers/OrSeparator.js +++ b/app/containers/OrSeparator.tsx @@ -1,6 +1,5 @@ import React from 'react'; -import { View, StyleSheet, Text } from 'react-native'; -import PropTypes from 'prop-types'; +import { StyleSheet, Text, View } from 'react-native'; import I18n from '../i18n'; import sharedStyles from '../views/Styles'; @@ -24,20 +23,20 @@ const styles = StyleSheet.create({ } }); -const OrSeparator = React.memo(({ theme }) => { +interface IOrSeparator { + theme: string; +} + +const OrSeparator = React.memo(({ theme }: IOrSeparator) => { const line = { backgroundColor: themes[theme].borderColor }; const text = { color: themes[theme].auxiliaryText }; return ( - {I18n.t('OR')} + {I18n.t('OR')} ); }); -OrSeparator.propTypes = { - theme: PropTypes.string -}; - export default OrSeparator; diff --git a/app/containers/Passcode/Base/Button.js b/app/containers/Passcode/Base/Button.js deleted file mode 100644 index 7826ed5fe..000000000 --- a/app/containers/Passcode/Base/Button.js +++ /dev/null @@ -1,47 +0,0 @@ -import React from 'react'; -import { Text } from 'react-native'; -import PropTypes from 'prop-types'; - -import styles from './styles'; -import { themes } from '../../../constants/colors'; -import Touch from '../../../utils/touch'; -import { CustomIcon } from '../../../lib/Icons'; - -const Button = React.memo(({ - text, disabled, theme, onPress, icon -}) => { - const press = () => onPress && onPress(text); - - return ( - - { - icon - ? ( - - ) - : ( - - {text} - - ) - } - - ); -}); - -Button.propTypes = { - text: PropTypes.string, - icon: PropTypes.string, - theme: PropTypes.string, - disabled: PropTypes.bool, - onPress: PropTypes.func -}; - -export default Button; diff --git a/app/containers/Passcode/Base/Button.tsx b/app/containers/Passcode/Base/Button.tsx new file mode 100644 index 000000000..f7e6c1a9a --- /dev/null +++ b/app/containers/Passcode/Base/Button.tsx @@ -0,0 +1,37 @@ +import React from 'react'; +import { Text } from 'react-native'; + +import styles from './styles'; +import { themes } from '../../../constants/colors'; +import Touch from '../../../utils/touch'; +import { CustomIcon } from '../../../lib/Icons'; + +interface IPasscodeButton { + text: string; + icon: string; + theme: string; + disabled: boolean; + onPress: Function; +} + +const Button = React.memo(({ text, disabled, theme, onPress, icon }: Partial) => { + const press = () => onPress && onPress(text!); + + return ( + + {icon ? ( + + ) : ( + {text} + )} + + ); +}); + +export default Button; diff --git a/app/containers/Passcode/Base/Dots.js b/app/containers/Passcode/Base/Dots.tsx similarity index 78% rename from app/containers/Passcode/Base/Dots.js rename to app/containers/Passcode/Base/Dots.tsx index 9d9f4a910..75ee4bc76 100644 --- a/app/containers/Passcode/Base/Dots.js +++ b/app/containers/Passcode/Base/Dots.tsx @@ -1,7 +1,6 @@ import React from 'react'; import { View } from 'react-native'; import range from 'lodash/range'; -import PropTypes from 'prop-types'; import styles from './styles'; import { themes } from '../../../constants/colors'; @@ -9,10 +8,16 @@ import { themes } from '../../../constants/colors'; const SIZE_EMPTY = 12; const SIZE_FULL = 16; -const Dots = React.memo(({ passcode, theme, length }) => ( +interface IPasscodeDots { + passcode: string; + theme: string; + length: number; +} + +const Dots = React.memo(({ passcode, theme, length }: IPasscodeDots) => ( - {range(length).map((val) => { - const lengthSup = (passcode.length >= val + 1); + {range(length).map(val => { + const lengthSup = passcode.length >= val + 1; const height = lengthSup ? SIZE_FULL : SIZE_EMPTY; const width = lengthSup ? SIZE_FULL : SIZE_EMPTY; let backgroundColor = ''; @@ -42,10 +47,4 @@ const Dots = React.memo(({ passcode, theme, length }) => ( )); -Dots.propTypes = { - passcode: PropTypes.string, - theme: PropTypes.string, - length: PropTypes.string -}; - export default Dots; diff --git a/app/containers/Passcode/Base/LockIcon.js b/app/containers/Passcode/Base/LockIcon.tsx similarity index 76% rename from app/containers/Passcode/Base/LockIcon.js rename to app/containers/Passcode/Base/LockIcon.tsx index 133485cba..2015d7cb9 100644 --- a/app/containers/Passcode/Base/LockIcon.js +++ b/app/containers/Passcode/Base/LockIcon.tsx @@ -1,13 +1,12 @@ import React from 'react'; import { View } from 'react-native'; import { Row } from 'react-native-easy-grid'; -import PropTypes from 'prop-types'; import styles from './styles'; import { themes } from '../../../constants/colors'; import { CustomIcon } from '../../../lib/Icons'; -const LockIcon = React.memo(({ theme }) => ( +const LockIcon = React.memo(({ theme }: { theme: string }) => ( @@ -15,8 +14,4 @@ const LockIcon = React.memo(({ theme }) => ( )); -LockIcon.propTypes = { - theme: PropTypes.string -}; - export default LockIcon; diff --git a/app/containers/Passcode/Base/Locked.js b/app/containers/Passcode/Base/Locked.tsx similarity index 69% rename from app/containers/Passcode/Base/Locked.js rename to app/containers/Passcode/Base/Locked.tsx index 8371c7287..7cefeed68 100644 --- a/app/containers/Passcode/Base/Locked.js +++ b/app/containers/Passcode/Base/Locked.tsx @@ -1,18 +1,28 @@ import React, { useEffect, useState } from 'react'; -import PropTypes from 'prop-types'; import { Grid } from 'react-native-easy-grid'; import { themes } from '../../../constants/colors'; import { resetAttempts } from '../../../utils/localAuthentication'; import { TYPE } from '../constants'; -import { getLockedUntil, getDiff } from '../utils'; +import { getDiff, getLockedUntil } from '../utils'; import I18n from '../../../i18n'; import styles from './styles'; import Title from './Title'; import Subtitle from './Subtitle'; import LockIcon from './LockIcon'; -const Timer = React.memo(({ time, theme, setStatus }) => { +interface IPasscodeTimer { + time: string; + theme: string; + setStatus: Function; +} + +interface IPasscodeLocked { + theme: string; + setStatus: Function; +} + +const Timer = React.memo(({ time, theme, setStatus }: IPasscodeTimer) => { const calcTimeLeft = () => { const diff = getDiff(time); if (diff > 0) { @@ -20,7 +30,7 @@ const Timer = React.memo(({ time, theme, setStatus }) => { } }; - const [timeLeft, setTimeLeft] = useState(calcTimeLeft()); + const [timeLeft, setTimeLeft] = useState(calcTimeLeft()); useEffect(() => { setTimeout(() => { @@ -39,10 +49,10 @@ const Timer = React.memo(({ time, theme, setStatus }) => { return ; }); -const Locked = React.memo(({ theme, setStatus }) => { - const [lockedUntil, setLockedUntil] = useState(null); +const Locked = React.memo(({ theme, setStatus }: IPasscodeLocked) => { + const [lockedUntil, setLockedUntil] = useState(null); - const readItemFromStorage = async() => { + const readItemFromStorage = async () => { const l = await getLockedUntil(); setLockedUntil(l); }; @@ -52,7 +62,7 @@ const Locked = React.memo(({ theme, setStatus }) => { }, []); return ( - + <Timer theme={theme} time={lockedUntil} setStatus={setStatus} /> @@ -60,15 +70,4 @@ const Locked = React.memo(({ theme, setStatus }) => { ); }); -Locked.propTypes = { - theme: PropTypes.string, - setStatus: PropTypes.func -}; - -Timer.propTypes = { - time: PropTypes.string, - theme: PropTypes.string, - setStatus: PropTypes.func -}; - export default Locked; diff --git a/app/containers/Passcode/Base/Subtitle.js b/app/containers/Passcode/Base/Subtitle.tsx similarity index 64% rename from app/containers/Passcode/Base/Subtitle.js rename to app/containers/Passcode/Base/Subtitle.tsx index 2ac69fee7..76a5a7405 100644 --- a/app/containers/Passcode/Base/Subtitle.js +++ b/app/containers/Passcode/Base/Subtitle.tsx @@ -1,12 +1,16 @@ import React from 'react'; -import { View, Text } from 'react-native'; +import { Text, View } from 'react-native'; import { Row } from 'react-native-easy-grid'; -import PropTypes from 'prop-types'; import styles from './styles'; import { themes } from '../../../constants/colors'; -const Subtitle = React.memo(({ text, theme }) => ( +interface IPasscodeSubtitle { + text: string; + theme: string; +} + +const Subtitle = React.memo(({ text, theme }: IPasscodeSubtitle) => ( <Row style={styles.row}> <View style={styles.subtitleView}> <Text style={[styles.textSubtitle, { color: themes[theme].passcodeSecondary }]}>{text}</Text> @@ -14,9 +18,4 @@ const Subtitle = React.memo(({ text, theme }) => ( </Row> )); -Subtitle.propTypes = { - text: PropTypes.string, - theme: PropTypes.string -}; - export default Subtitle; diff --git a/app/containers/Passcode/Base/Title.js b/app/containers/Passcode/Base/Title.tsx similarity index 64% rename from app/containers/Passcode/Base/Title.js rename to app/containers/Passcode/Base/Title.tsx index 0aa96d757..0c54e0358 100644 --- a/app/containers/Passcode/Base/Title.js +++ b/app/containers/Passcode/Base/Title.tsx @@ -1,12 +1,16 @@ import React from 'react'; -import { View, Text } from 'react-native'; +import { Text, View } from 'react-native'; import { Row } from 'react-native-easy-grid'; -import PropTypes from 'prop-types'; import styles from './styles'; import { themes } from '../../../constants/colors'; -const Title = React.memo(({ text, theme }) => ( +interface IPasscodeTitle { + text: string; + theme: string; +} + +const Title = React.memo(({ text, theme }: IPasscodeTitle) => ( <Row style={styles.row}> <View style={styles.titleView}> <Text style={[styles.textTitle, { color: themes[theme].passcodePrimary }]}>{text}</Text> @@ -14,9 +18,4 @@ const Title = React.memo(({ text, theme }) => ( </Row> )); -Title.propTypes = { - text: PropTypes.string, - theme: PropTypes.string -}; - export default Title; diff --git a/app/containers/Passcode/Base/index.js b/app/containers/Passcode/Base/index.js deleted file mode 100644 index 678036975..000000000 --- a/app/containers/Passcode/Base/index.js +++ /dev/null @@ -1,139 +0,0 @@ -import React, { - useState, forwardRef, useImperativeHandle, useRef -} from 'react'; -import { Col, Row, Grid } from 'react-native-easy-grid'; -import range from 'lodash/range'; -import PropTypes from 'prop-types'; -import * as Animatable from 'react-native-animatable'; -import * as Haptics from 'expo-haptics'; - -import styles from './styles'; -import Button from './Button'; -import Dots from './Dots'; -import { TYPE } from '../constants'; -import { themes } from '../../../constants/colors'; -import { PASSCODE_LENGTH } from '../../../constants/localAuthentication'; -import LockIcon from './LockIcon'; -import Title from './Title'; -import Subtitle from './Subtitle'; - -const Base = forwardRef(({ - theme, type, onEndProcess, previousPasscode, title, subtitle, onError, showBiometry, onBiometryPress -}, ref) => { - const rootRef = useRef(); - const dotsRef = useRef(); - const [passcode, setPasscode] = useState(''); - - const clearPasscode = () => setPasscode(''); - - const wrongPasscode = () => { - clearPasscode(); - dotsRef?.current?.shake(500); - Haptics.notificationAsync(Haptics.NotificationFeedbackType.Error); - }; - - const animate = (animation, duration = 500) => { - rootRef?.current?.[animation](duration); - }; - - const onPressNumber = text => setPasscode((p) => { - const currentPasscode = p + text; - if (currentPasscode?.length === PASSCODE_LENGTH) { - switch (type) { - case TYPE.CHOOSE: - onEndProcess(currentPasscode); - break; - case TYPE.CONFIRM: - if (currentPasscode !== previousPasscode) { - onError(); - } else { - onEndProcess(currentPasscode); - } - break; - case TYPE.ENTER: - onEndProcess(currentPasscode); - break; - default: - break; - } - } - return currentPasscode; - }); - - const onPressDelete = () => setPasscode((p) => { - if (p?.length > 0) { - const newPasscode = p.slice(0, -1); - return newPasscode; - } - return ''; - }); - - useImperativeHandle(ref, () => ({ - wrongPasscode, animate, clearPasscode - })); - - return ( - <Animatable.View ref={rootRef} style={styles.container}> - <Grid style={[styles.grid, { backgroundColor: themes[theme].passcodeBackground }]}> - <LockIcon theme={theme} /> - <Title text={title} theme={theme} /> - <Subtitle text={subtitle} theme={theme} /> - <Row style={styles.row}> - <Animatable.View ref={dotsRef}> - <Dots passcode={passcode} theme={theme} length={PASSCODE_LENGTH} /> - </Animatable.View> - </Row> - <Row style={[styles.row, styles.buttonRow]}> - {range(1, 4).map(i => ( - <Col key={i} style={styles.colButton}> - <Button text={i} theme={theme} onPress={onPressNumber} /> - </Col> - ))} - </Row> - <Row style={[styles.row, styles.buttonRow]}> - {range(4, 7).map(i => ( - <Col key={i} style={styles.colButton}> - <Button text={i} theme={theme} onPress={onPressNumber} /> - </Col> - ))} - </Row> - <Row style={[styles.row, styles.buttonRow]}> - {range(7, 10).map(i => ( - <Col key={i} style={styles.colButton}> - <Button text={i} theme={theme} onPress={onPressNumber} /> - </Col> - ))} - </Row> - <Row style={[styles.row, styles.buttonRow]}> - {showBiometry - ? ( - <Col style={styles.colButton}> - <Button icon='fingerprint' theme={theme} onPress={onBiometryPress} /> - </Col> - ) - : <Col style={styles.colButton} />} - <Col style={styles.colButton}> - <Button text='0' theme={theme} onPress={onPressNumber} /> - </Col> - <Col style={styles.colButton}> - <Button icon='backspace' theme={theme} onPress={onPressDelete} /> - </Col> - </Row> - </Grid> - </Animatable.View> - ); -}); - -Base.propTypes = { - theme: PropTypes.string, - type: PropTypes.string, - previousPasscode: PropTypes.string, - title: PropTypes.string, - subtitle: PropTypes.string, - showBiometry: PropTypes.string, - onEndProcess: PropTypes.func, - onError: PropTypes.func, - onBiometryPress: PropTypes.func -}; - -export default Base; diff --git a/app/containers/Passcode/Base/index.tsx b/app/containers/Passcode/Base/index.tsx new file mode 100644 index 000000000..dd1d90e8d --- /dev/null +++ b/app/containers/Passcode/Base/index.tsx @@ -0,0 +1,143 @@ +import React, { forwardRef, useImperativeHandle, useRef, useState } from 'react'; +import { Col, Grid, Row } from 'react-native-easy-grid'; +import range from 'lodash/range'; +import * as Animatable from 'react-native-animatable'; +import * as Haptics from 'expo-haptics'; + +import styles from './styles'; +import Button from './Button'; +import Dots from './Dots'; +import { TYPE } from '../constants'; +import { themes } from '../../../constants/colors'; +import { PASSCODE_LENGTH } from '../../../constants/localAuthentication'; +import LockIcon from './LockIcon'; +import Title from './Title'; +import Subtitle from './Subtitle'; + +interface IPasscodeBase { + theme: string; + type: string; + previousPasscode?: string; + title: string; + subtitle?: string; + showBiometry?: string; + onEndProcess: Function; + onError?: Function; + onBiometryPress?(): void; +} + +const Base = forwardRef( + ( + { theme, type, onEndProcess, previousPasscode, title, subtitle, onError, showBiometry, onBiometryPress }: IPasscodeBase, + ref + ) => { + const rootRef = useRef<any>(); + const dotsRef = useRef<any>(); + const [passcode, setPasscode] = useState(''); + + const clearPasscode = () => setPasscode(''); + + const wrongPasscode = () => { + clearPasscode(); + dotsRef?.current?.shake(500); + Haptics.notificationAsync(Haptics.NotificationFeedbackType.Error); + }; + + const animate = (animation: string, duration = 500) => { + rootRef?.current?.[animation](duration); + }; + + const onPressNumber = (text: string) => + setPasscode(p => { + const currentPasscode = p + text; + if (currentPasscode?.length === PASSCODE_LENGTH) { + switch (type) { + case TYPE.CHOOSE: + onEndProcess(currentPasscode); + break; + case TYPE.CONFIRM: + if (currentPasscode !== previousPasscode) { + onError?.(); + } else { + onEndProcess(currentPasscode); + } + break; + case TYPE.ENTER: + onEndProcess(currentPasscode); + break; + default: + break; + } + } + return currentPasscode; + }); + + const onPressDelete = () => + setPasscode(p => { + if (p?.length > 0) { + const newPasscode = p.slice(0, -1); + return newPasscode; + } + return ''; + }); + + useImperativeHandle(ref, () => ({ + wrongPasscode, + animate, + clearPasscode + })); + + return ( + <Animatable.View ref={rootRef} style={styles.container}> + <Grid style={[styles.grid, { backgroundColor: themes[theme].passcodeBackground }]}> + <LockIcon theme={theme} /> + <Title text={title} theme={theme} /> + <Subtitle text={subtitle!} theme={theme} /> + <Row style={styles.row}> + <Animatable.View ref={dotsRef}> + <Dots passcode={passcode} theme={theme} length={PASSCODE_LENGTH} /> + </Animatable.View> + </Row> + <Row style={[styles.row, styles.buttonRow]}> + {range(1, 4).map((i: any) => ( + <Col key={i} style={styles.colButton}> + <Button text={i} theme={theme} onPress={onPressNumber} /> + </Col> + ))} + </Row> + <Row style={[styles.row, styles.buttonRow]}> + {range(4, 7).map((i: any) => ( + <Col key={i} style={styles.colButton}> + <Button text={i} theme={theme} onPress={onPressNumber} /> + </Col> + ))} + </Row> + <Row style={[styles.row, styles.buttonRow]}> + {range(7, 10).map((i: any) => ( + <Col key={i} style={styles.colButton}> + <Button text={i} theme={theme} onPress={onPressNumber} /> + </Col> + ))} + </Row> + <Row style={[styles.row, styles.buttonRow]}> + {showBiometry ? ( + <Col style={styles.colButton}> + <Button icon='fingerprint' theme={theme} onPress={onBiometryPress} /> + </Col> + ) : ( + <Col style={styles.colButton} /> + )} + <Col style={styles.colButton}> + <Button text='0' theme={theme} onPress={onPressNumber} /> + </Col> + <Col style={styles.colButton}> + <Button icon='backspace' theme={theme} onPress={onPressDelete} /> + </Col> + </Row> + </Grid> + </Animatable.View> + ); + } +); + +export default Base; diff --git a/app/containers/Passcode/Base/styles.js b/app/containers/Passcode/Base/styles.ts similarity index 100% rename from app/containers/Passcode/Base/styles.js rename to app/containers/Passcode/Base/styles.ts diff --git a/app/containers/Passcode/PasscodeChoose.js b/app/containers/Passcode/PasscodeChoose.tsx similarity index 72% rename from app/containers/Passcode/PasscodeChoose.js rename to app/containers/Passcode/PasscodeChoose.tsx index cabc71978..c28104280 100644 --- a/app/containers/Passcode/PasscodeChoose.js +++ b/app/containers/Passcode/PasscodeChoose.tsx @@ -1,5 +1,4 @@ -import React, { useState, useRef } from 'react'; -import PropTypes from 'prop-types'; +import React, { useRef, useState } from 'react'; import * as Haptics from 'expo-haptics'; import { gestureHandlerRootHOC } from 'react-native-gesture-handler'; @@ -7,14 +6,20 @@ import Base from './Base'; import { TYPE } from './constants'; import I18n from '../../i18n'; -const PasscodeChoose = ({ theme, finishProcess, force = false }) => { - const chooseRef = useRef(null); - const confirmRef = useRef(null); +interface IPasscodeChoose { + theme: string; + force: boolean; + finishProcess: Function; +} + +const PasscodeChoose = ({ theme, finishProcess, force = false }: IPasscodeChoose) => { + const chooseRef = useRef<any>(null); + const confirmRef = useRef<any>(null); const [subtitle, setSubtitle] = useState(null); const [status, setStatus] = useState(TYPE.CHOOSE); - const [previousPasscode, setPreviouPasscode] = useState(null); + const [previousPasscode, setPreviouPasscode] = useState<any>(null); - const firstStep = (p) => { + const firstStep = (p: any) => { setTimeout(() => { setStatus(TYPE.CONFIRM); setPreviouPasscode(p); @@ -22,7 +27,7 @@ const PasscodeChoose = ({ theme, finishProcess, force = false }) => { }, 200); }; - const changePasscode = p => finishProcess && finishProcess(p); + const changePasscode = (p: string) => finishProcess && finishProcess(p); const onError = () => { setTimeout(() => { @@ -60,10 +65,4 @@ const PasscodeChoose = ({ theme, finishProcess, force = false }) => { ); }; -PasscodeChoose.propTypes = { - theme: PropTypes.string, - force: PropTypes.bool, - finishProcess: PropTypes.func -}; - export default gestureHandlerRootHOC(PasscodeChoose); diff --git a/app/containers/Passcode/PasscodeEnter.js b/app/containers/Passcode/PasscodeEnter.tsx similarity index 75% rename from app/containers/Passcode/PasscodeEnter.js rename to app/containers/Passcode/PasscodeEnter.tsx index ebd22f363..cc284b24a 100644 --- a/app/containers/Passcode/PasscodeEnter.js +++ b/app/containers/Passcode/PasscodeEnter.tsx @@ -1,6 +1,5 @@ import React, { useEffect, useRef, useState } from 'react'; import { useAsyncStorage } from '@react-native-community/async-storage'; -import PropTypes from 'prop-types'; import { gestureHandlerRootHOC } from 'react-native-gesture-handler'; import * as Haptics from 'expo-haptics'; import { sha256 } from 'js-sha256'; @@ -8,29 +7,33 @@ import { sha256 } from 'js-sha256'; import Base from './Base'; import Locked from './Base/Locked'; import { TYPE } from './constants'; -import { - ATTEMPTS_KEY, LOCKED_OUT_TIMER_KEY, PASSCODE_KEY, MAX_ATTEMPTS -} from '../../constants/localAuthentication'; -import { resetAttempts, biometryAuth } from '../../utils/localAuthentication'; -import { getLockedUntil, getDiff } from './utils'; +import { ATTEMPTS_KEY, LOCKED_OUT_TIMER_KEY, MAX_ATTEMPTS, PASSCODE_KEY } from '../../constants/localAuthentication'; +import { biometryAuth, resetAttempts } from '../../utils/localAuthentication'; +import { getDiff, getLockedUntil } from './utils'; import UserPreferences from '../../lib/userPreferences'; import I18n from '../../i18n'; -const PasscodeEnter = ({ theme, hasBiometry, finishProcess }) => { +interface IPasscodePasscodeEnter { + theme: string; + hasBiometry: string; + finishProcess: Function; +} + +const PasscodeEnter = ({ theme, hasBiometry, finishProcess }: IPasscodePasscodeEnter) => { const ref = useRef(null); - let attempts = 0; - let lockedUntil = false; + let attempts: any = 0; + let lockedUntil: any = false; const [passcode, setPasscode] = useState(null); const [status, setStatus] = useState(null); const { getItem: getAttempts, setItem: setAttempts } = useAsyncStorage(ATTEMPTS_KEY); const { setItem: setLockedUntil } = useAsyncStorage(LOCKED_OUT_TIMER_KEY); - const fetchPasscode = async() => { - const p = await UserPreferences.getStringAsync(PASSCODE_KEY); + const fetchPasscode = async () => { + const p: any = await UserPreferences.getStringAsync(PASSCODE_KEY); setPasscode(p); }; - const biometry = async() => { + const biometry = async () => { if (hasBiometry && status === TYPE.ENTER) { const result = await biometryAuth(); if (result?.success) { @@ -39,7 +42,7 @@ const PasscodeEnter = ({ theme, hasBiometry, finishProcess }) => { } }; - const readStorage = async() => { + const readStorage = async () => { lockedUntil = await getLockedUntil(); if (lockedUntil) { const diff = getDiff(lockedUntil); @@ -61,7 +64,7 @@ const PasscodeEnter = ({ theme, hasBiometry, finishProcess }) => { readStorage(); }, [status]); - const onEndProcess = (p) => { + const onEndProcess = (p: any) => { setTimeout(() => { if (sha256(p) === passcode) { finishProcess(); @@ -72,6 +75,7 @@ const PasscodeEnter = ({ theme, hasBiometry, finishProcess }) => { setLockedUntil(new Date().toISOString()); Haptics.notificationAsync(Haptics.NotificationFeedbackType.Error); } else { + // @ts-ignore ref.current.wrongPasscode(); setAttempts(attempts?.toString()); Haptics.notificationAsync(Haptics.NotificationFeedbackType.Warning); @@ -97,10 +101,4 @@ const PasscodeEnter = ({ theme, hasBiometry, finishProcess }) => { ); }; -PasscodeEnter.propTypes = { - theme: PropTypes.string, - hasBiometry: PropTypes.string, - finishProcess: PropTypes.func -}; - export default gestureHandlerRootHOC(PasscodeEnter); diff --git a/app/containers/Passcode/constants.js b/app/containers/Passcode/constants.ts similarity index 74% rename from app/containers/Passcode/constants.js rename to app/containers/Passcode/constants.ts index d284ee241..ae268799a 100644 --- a/app/containers/Passcode/constants.js +++ b/app/containers/Passcode/constants.ts @@ -1,4 +1,4 @@ -export const TYPE = { +export const TYPE: any = { CHOOSE: 'choose', CONFIRM: 'confirm', ENTER: 'enter', diff --git a/app/containers/Passcode/index.js b/app/containers/Passcode/index.ts similarity index 100% rename from app/containers/Passcode/index.js rename to app/containers/Passcode/index.ts diff --git a/app/containers/Passcode/utils.js b/app/containers/Passcode/utils.ts similarity index 71% rename from app/containers/Passcode/utils.js rename to app/containers/Passcode/utils.ts index bbb14eea7..f497e95a4 100644 --- a/app/containers/Passcode/utils.js +++ b/app/containers/Passcode/utils.ts @@ -3,12 +3,12 @@ import moment from 'moment'; import { LOCKED_OUT_TIMER_KEY, TIME_TO_LOCK } from '../../constants/localAuthentication'; -export const getLockedUntil = async() => { - const t = await AsyncStorage.getItem(LOCKED_OUT_TIMER_KEY); +export const getLockedUntil = async () => { + const t: any = await AsyncStorage.getItem(LOCKED_OUT_TIMER_KEY); if (t) { return moment(t).add(TIME_TO_LOCK); } return null; }; - +// @ts-ignore export const getDiff = t => new Date(t) - new Date(); diff --git a/app/containers/ReactionsModal.js b/app/containers/ReactionsModal.tsx similarity index 58% rename from app/containers/ReactionsModal.js rename to app/containers/ReactionsModal.tsx index f4f1889d9..9cd2ff0ef 100644 --- a/app/containers/ReactionsModal.js +++ b/app/containers/ReactionsModal.tsx @@ -1,8 +1,5 @@ import React from 'react'; -import { - View, Text, FlatList, StyleSheet -} from 'react-native'; -import PropTypes from 'prop-types'; +import { FlatList, StyleSheet, Text, View } from 'react-native'; import Modal from 'react-native-modal'; import Touchable from 'react-native-platform-touchable'; @@ -62,16 +59,41 @@ const styles = StyleSheet.create({ const standardEmojiStyle = { fontSize: 20 }; const customEmojiStyle = { width: 20, height: 20 }; -const Item = React.memo(({ - item, user, baseUrl, getCustomEmoji, theme -}) => { +interface IItem { + item: { + usernames: any; + emoji: string; + }; + user?: { username: any }; + baseUrl?: string; + getCustomEmoji?: Function; + theme?: string; +} + +interface IModalContent { + message?: { + reactions: any; + }; + onClose: Function; + theme: string; +} + +interface IReactionsModal { + isVisible: boolean; + onClose(): void; + theme: string; +} + +const Item = React.memo(({ item, user, baseUrl, getCustomEmoji, theme }: IItem) => { const count = item.usernames.length; - let usernames = item.usernames.slice(0, 3) - .map(username => (username === user.username ? I18n.t('you') : username)).join(', '); + let usernames = item.usernames + .slice(0, 3) + .map((username: any) => (username === user?.username ? I18n.t('you') : username)) + .join(', '); if (count > 3) { - usernames = `${ usernames } ${ I18n.t('and_more') } ${ count - 3 }`; + usernames = `${usernames} ${I18n.t('and_more')} ${count - 3}`; } else { - usernames = usernames.replace(/,(?=[^,]*$)/, ` ${ I18n.t('and') }`); + usernames = usernames.replace(/,(?=[^,]*$)/, ` ${I18n.t('and')}`); } return ( <View style={styles.itemContainer}> @@ -80,33 +102,27 @@ const Item = React.memo(({ content={item.emoji} standardEmojiStyle={standardEmojiStyle} customEmojiStyle={customEmojiStyle} - baseUrl={baseUrl} - getCustomEmoji={getCustomEmoji} + baseUrl={baseUrl!} + getCustomEmoji={getCustomEmoji!} /> </View> <View style={styles.peopleItemContainer}> - <Text style={[styles.reactCount, { color: themes[theme].buttonText }]}> + <Text style={[styles.reactCount, { color: themes[theme!].buttonText }]}> {count === 1 ? I18n.t('1_person_reacted') : I18n.t('N_people_reacted', { n: count })} </Text> - <Text style={[styles.peopleReacted, { color: themes[theme].buttonText }]}>{ usernames }</Text> + <Text style={[styles.peopleReacted, { color: themes[theme!].buttonText }]}>{usernames}</Text> </View> </View> ); }); -const ModalContent = React.memo(({ - message, onClose, ...props -}) => { +const ModalContent = React.memo(({ message, onClose, ...props }: IModalContent) => { if (message && message.reactions) { return ( <SafeAreaView style={styles.safeArea}> <Touchable onPress={onClose}> <View style={styles.titleContainer}> - <CustomIcon - style={[styles.closeButton, { color: themes[props.theme].buttonText }]} - name='close' - size={20} - /> + <CustomIcon style={[styles.closeButton, { color: themes[props.theme].buttonText }]} name='close' size={20} /> <Text style={[styles.title, { color: themes[props.theme].buttonText }]}>{I18n.t('Reactions')}</Text> </View> </Touchable> @@ -122,42 +138,23 @@ const ModalContent = React.memo(({ return null; }); -const ReactionsModal = React.memo(({ - isVisible, onClose, theme, ...props -}) => ( - <Modal - isVisible={isVisible} - onBackdropPress={onClose} - onBackButtonPress={onClose} - backdropOpacity={0.8} - onSwipeComplete={onClose} - swipeDirection={['up', 'left', 'right', 'down']} - > - <ModalContent onClose={onClose} theme={theme} {...props} /> - </Modal> -), (prevProps, nextProps) => prevProps.isVisible === nextProps.isVisible && prevProps.theme === nextProps.theme); +const ReactionsModal = React.memo( + ({ isVisible, onClose, theme, ...props }: IReactionsModal) => ( + <Modal + isVisible={isVisible} + onBackdropPress={onClose} + onBackButtonPress={onClose} + backdropOpacity={0.8} + onSwipeComplete={onClose} + swipeDirection={['up', 'left', 'right', 'down']}> + <ModalContent onClose={onClose} theme={theme} {...props} /> + </Modal> + ), + (prevProps, nextProps) => prevProps.isVisible === nextProps.isVisible && prevProps.theme === nextProps.theme +); -ReactionsModal.propTypes = { - isVisible: PropTypes.bool, - onClose: PropTypes.func, - theme: PropTypes.string -}; ReactionsModal.displayName = 'ReactionsModal'; - -ModalContent.propTypes = { - message: PropTypes.object, - onClose: PropTypes.func, - theme: PropTypes.string -}; ModalContent.displayName = 'ReactionsModalContent'; - -Item.propTypes = { - item: PropTypes.object, - user: PropTypes.object, - baseUrl: PropTypes.string, - getCustomEmoji: PropTypes.func, - theme: PropTypes.string -}; Item.displayName = 'ReactionsModalItem'; export default withTheme(ReactionsModal); diff --git a/app/containers/RoomHeader/RoomHeader.js b/app/containers/RoomHeader/RoomHeader.js deleted file mode 100644 index 0f1ee472e..000000000 --- a/app/containers/RoomHeader/RoomHeader.js +++ /dev/null @@ -1,207 +0,0 @@ -import React, { useCallback } from 'react'; -import PropTypes from 'prop-types'; -import { - View, Text, StyleSheet, TouchableOpacity -} from 'react-native'; - -import I18n from '../../i18n'; -import sharedStyles from '../../views/Styles'; -import { themes } from '../../constants/colors'; -import Markdown from '../markdown'; -import RoomTypeIcon from '../RoomTypeIcon'; -import { withTheme } from '../../theme'; - -const HIT_SLOP = { - top: 5, right: 5, bottom: 5, left: 5 -}; -const TITLE_SIZE = 16; -const SUBTITLE_SIZE = 12; - -const getSubTitleSize = scale => SUBTITLE_SIZE * scale; - -const styles = StyleSheet.create({ - container: { - flex: 1, - justifyContent: 'center' - }, - titleContainer: { - alignItems: 'center', - flexDirection: 'row' - }, - title: { - flexShrink: 1, - ...sharedStyles.textSemibold - }, - subtitle: { - flexShrink: 1, - ...sharedStyles.textRegular - }, - typingUsers: { - ...sharedStyles.textSemibold - } -}); - -const SubTitle = React.memo(({ - usersTyping, subtitle, renderFunc, theme, scale -}) => { - const fontSize = getSubTitleSize(scale); - // typing - if (usersTyping.length) { - let usersText; - if (usersTyping.length === 2) { - usersText = usersTyping.join(` ${ I18n.t('and') } `); - } else { - usersText = usersTyping.join(', '); - } - return ( - <Text style={[styles.subtitle, { fontSize, color: themes[theme].auxiliaryText }]} numberOfLines={1}> - <Text style={styles.typingUsers}>{usersText} </Text> - { usersTyping.length > 1 ? I18n.t('are_typing') : I18n.t('is_typing') }... - </Text> - ); - } - - // renderFunc - if (renderFunc) { - return renderFunc(); - } - - // subtitle - if (subtitle) { - return ( - <Markdown - preview - msg={subtitle} - style={[styles.subtitle, { fontSize, color: themes[theme].auxiliaryText }]} - numberOfLines={1} - theme={theme} - /> - ); - } - - return null; -}); - -SubTitle.propTypes = { - usersTyping: PropTypes.array, - theme: PropTypes.string, - subtitle: PropTypes.string, - renderFunc: PropTypes.func, - scale: PropTypes.number -}; - -const HeaderTitle = React.memo(({ - title, tmid, prid, scale, theme, testID -}) => { - const titleStyle = { fontSize: TITLE_SIZE * scale, color: themes[theme].headerTitleColor }; - if (!tmid && !prid) { - return ( - <Text - style={[styles.title, titleStyle]} - numberOfLines={1} - testID={testID} - > - {title} - </Text> - ); - } - - return ( - <Markdown - preview - msg={title} - style={[styles.title, titleStyle]} - numberOfLines={1} - theme={theme} - testID={testID} - /> - ); -}); - -HeaderTitle.propTypes = { - title: PropTypes.string, - tmid: PropTypes.string, - prid: PropTypes.string, - scale: PropTypes.number, - theme: PropTypes.string, - testID: PropTypes.string -}; - -const Header = React.memo(({ - title, subtitle, parentTitle, type, status, usersTyping, width, height, prid, tmid, onPress, theme, isGroupChat, teamMain, testID -}) => { - const portrait = height > width; - let scale = 1; - - if (!portrait && !tmid) { - if (usersTyping.length > 0 || subtitle) { - scale = 0.8; - } - } - - let renderFunc; - if (tmid) { - renderFunc = () => ( - <View style={styles.titleContainer}> - <RoomTypeIcon type={prid ? 'discussion' : type} isGroupChat={isGroupChat} status={status} teamMain={teamMain} /> - <Text style={[styles.subtitle, { color: themes[theme].auxiliaryText }]} numberOfLines={1}>{parentTitle}</Text> - </View> - ); - } - - const handleOnPress = useCallback(() => onPress(), []); - - return ( - <TouchableOpacity - testID='room-header' - accessibilityLabel={title} - onPress={handleOnPress} - style={styles.container} - disabled={tmid} - hitSlop={HIT_SLOP} - > - <View style={styles.titleContainer}> - {tmid ? null : <RoomTypeIcon type={prid ? 'discussion' : type} isGroupChat={isGroupChat} status={status} teamMain={teamMain} />} - <HeaderTitle - title={title} - tmid={tmid} - prid={prid} - scale={scale} - theme={theme} - testID={testID} - /> - </View> - <SubTitle - usersTyping={tmid ? [] : usersTyping} - subtitle={subtitle} - theme={theme} - renderFunc={renderFunc} - scale={scale} - /> - </TouchableOpacity> - ); -}); - -Header.propTypes = { - title: PropTypes.string.isRequired, - subtitle: PropTypes.string, - type: PropTypes.string.isRequired, - width: PropTypes.number.isRequired, - height: PropTypes.number.isRequired, - prid: PropTypes.string, - tmid: PropTypes.string, - teamMain: PropTypes.bool, - status: PropTypes.string, - theme: PropTypes.string, - usersTyping: PropTypes.array, - isGroupChat: PropTypes.bool, - parentTitle: PropTypes.string, - onPress: PropTypes.func, - testID: PropTypes.string -}; - -Header.defaultProps = { - usersTyping: [] -}; - -export default withTheme(Header); diff --git a/app/containers/RoomHeader/RoomHeader.stories.js b/app/containers/RoomHeader/RoomHeader.stories.js index bfea5e272..316efca24 100644 --- a/app/containers/RoomHeader/RoomHeader.stories.js +++ b/app/containers/RoomHeader/RoomHeader.stories.js @@ -1,30 +1,32 @@ /* eslint-disable import/no-extraneous-dependencies, import/no-unresolved, import/extensions, react/prop-types, react/destructuring-assignment */ import React from 'react'; -import { View, Dimensions } from 'react-native'; +import { Dimensions, View } from 'react-native'; import { storiesOf } from '@storybook/react-native'; -import RoomHeaderComponent from './RoomHeader'; import Header from '../Header'; import { longText } from '../../../storybook/utils'; import { ThemeContext } from '../../theme'; +import RoomHeaderComponent from './RoomHeader'; const stories = storiesOf('RoomHeader', module); // TODO: refactor after react-navigation v6 const HeaderExample = ({ title }) => ( - <Header - headerTitle={() => ( - <View style={{ flex: 1, paddingHorizontal: 12 }}> - {title()} - </View> - )} - /> + <Header headerTitle={() => <View style={{ flex: 1, paddingHorizontal: 12 }}>{title()}</View>} /> ); const { width, height } = Dimensions.get('window'); const RoomHeader = ({ ...props }) => ( - <RoomHeaderComponent width={width} height={height} title='title' type='p' testID={props.title} onPress={() => alert('header pressed!')} {...props} /> + <RoomHeaderComponent + width={width} + height={height} + title='title' + type='p' + testID={props.title} + onPress={() => alert('header pressed!')} + {...props} + /> ); stories.add('title and subtitle', () => ( @@ -78,9 +80,7 @@ stories.add('thread', () => ( )); const ThemeStory = ({ theme }) => ( - <ThemeContext.Provider - value={{ theme }} - > + <ThemeContext.Provider value={{ theme }}> <HeaderExample title={() => <RoomHeader subtitle='subtitle' />} /> </ThemeContext.Provider> ); diff --git a/app/containers/RoomHeader/RoomHeader.tsx b/app/containers/RoomHeader/RoomHeader.tsx new file mode 100644 index 000000000..0ca3bbf5a --- /dev/null +++ b/app/containers/RoomHeader/RoomHeader.tsx @@ -0,0 +1,197 @@ +import React, { useCallback } from 'react'; +import { StyleSheet, Text, TouchableOpacity, View } from 'react-native'; + +import I18n from '../../i18n'; +import sharedStyles from '../../views/Styles'; +import { themes } from '../../constants/colors'; +import Markdown from '../markdown'; +import RoomTypeIcon from '../RoomTypeIcon'; +import { withTheme } from '../../theme'; + +const HIT_SLOP = { + top: 5, + right: 5, + bottom: 5, + left: 5 +}; +const TITLE_SIZE = 16; +const SUBTITLE_SIZE = 12; + +const getSubTitleSize = (scale: number) => SUBTITLE_SIZE * scale; + +const styles = StyleSheet.create({ + container: { + flex: 1, + justifyContent: 'center' + }, + titleContainer: { + alignItems: 'center', + flexDirection: 'row' + }, + title: { + flexShrink: 1, + ...sharedStyles.textSemibold + }, + subtitle: { + flexShrink: 1, + ...sharedStyles.textRegular + }, + typingUsers: { + ...sharedStyles.textSemibold + } +}); + +type TRoomHeaderSubTitle = { + usersTyping: []; + theme: string; + subtitle: string; + renderFunc: any; + scale: number; +}; + +type TRoomHeaderHeaderTitle = { + title: string; + tmid: string; + prid: string; + scale: number; + theme: string; + testID: string; +}; + +interface IRoomHeader { + title: string; + subtitle: string; + type: string; + width: number; + height: number; + prid: string; + tmid: string; + teamMain: boolean; + status: string; + theme: string; + usersTyping: []; + isGroupChat: boolean; + parentTitle: string; + onPress: Function; + testID: string; +} + +const SubTitle = React.memo(({ usersTyping, subtitle, renderFunc, theme, scale }: TRoomHeaderSubTitle) => { + const fontSize = getSubTitleSize(scale); + // typing + if (usersTyping.length) { + let usersText; + if (usersTyping.length === 2) { + usersText = usersTyping.join(` ${I18n.t('and')} `); + } else { + usersText = usersTyping.join(', '); + } + return ( + <Text style={[styles.subtitle, { fontSize, color: themes[theme].auxiliaryText }]} numberOfLines={1}> + <Text style={styles.typingUsers}>{usersText} </Text> + {usersTyping.length > 1 ? I18n.t('are_typing') : I18n.t('is_typing')}... + </Text> + ); + } + + // renderFunc + if (renderFunc) { + return renderFunc(); + } + + // subtitle + if (subtitle) { + return ( + // @ts-ignore + <Markdown + preview + msg={subtitle} + style={[styles.subtitle, { fontSize, color: themes[theme].auxiliaryText }]} + numberOfLines={1} + theme={theme} + /> + ); + } + + return null; +}); + +const HeaderTitle = React.memo(({ title, tmid, prid, scale, theme, testID }: TRoomHeaderHeaderTitle) => { + const titleStyle = { fontSize: TITLE_SIZE * scale, color: themes[theme].headerTitleColor }; + if (!tmid && !prid) { + return ( + <Text style={[styles.title, titleStyle]} numberOfLines={1} testID={testID}> + {title} + </Text> + ); + } + + return ( + // @ts-ignore + <Markdown preview msg={title} style={[styles.title, titleStyle]} numberOfLines={1} theme={theme} testID={testID} /> + ); +}); + +const Header = React.memo( + ({ + title, + subtitle, + parentTitle, + type, + status, + width, + height, + prid, + tmid, + onPress, + theme, + isGroupChat, + teamMain, + testID, + usersTyping = [] + }: IRoomHeader) => { + const portrait = height > width; + let scale = 1; + + if (!portrait && !tmid) { + if (usersTyping.length > 0 || subtitle) { + scale = 0.8; + } + } + + let renderFunc; + if (tmid) { + renderFunc = () => ( + <View style={styles.titleContainer}> + <RoomTypeIcon type={prid ? 'discussion' : type} isGroupChat={isGroupChat} status={status} teamMain={teamMain} /> + <Text style={[styles.subtitle, { color: themes[theme].auxiliaryText }]} numberOfLines={1}> + {parentTitle} + </Text> + </View> + ); + } + + const handleOnPress = useCallback(() => onPress(), []); + + return ( + <TouchableOpacity + testID='room-header' + accessibilityLabel={title} + onPress={handleOnPress} + style={styles.container} + // @ts-ignore + disabled={tmid} + hitSlop={HIT_SLOP}> + <View style={styles.titleContainer}> + {tmid ? null : ( + <RoomTypeIcon type={prid ? 'discussion' : type} isGroupChat={isGroupChat} status={status} teamMain={teamMain} /> + )} + <HeaderTitle title={title} tmid={tmid} prid={prid} scale={scale} theme={theme} testID={testID} /> + </View> + <SubTitle usersTyping={tmid ? [] : usersTyping} subtitle={subtitle} theme={theme} renderFunc={renderFunc} scale={scale} /> + </TouchableOpacity> + ); + } +); + +export default withTheme(Header); diff --git a/app/containers/RoomHeader/index.js b/app/containers/RoomHeader/index.tsx similarity index 72% rename from app/containers/RoomHeader/index.js rename to app/containers/RoomHeader/index.tsx index 7d4d22de8..5b06fb898 100644 --- a/app/containers/RoomHeader/index.js +++ b/app/containers/RoomHeader/index.tsx @@ -1,5 +1,4 @@ import React, { Component } from 'react'; -import PropTypes from 'prop-types'; import { connect } from 'react-redux'; import { dequal } from 'dequal'; @@ -7,33 +6,32 @@ import RoomHeader from './RoomHeader'; import { withDimensions } from '../../dimensions'; import I18n from '../../i18n'; -class RoomHeaderContainer extends Component { - static propTypes = { - title: PropTypes.string, - subtitle: PropTypes.string, - type: PropTypes.string, - prid: PropTypes.string, - tmid: PropTypes.string, - teamMain: PropTypes.bool, - usersTyping: PropTypes.string, - status: PropTypes.string, - statusText: PropTypes.string, - connecting: PropTypes.bool, - connected: PropTypes.bool, - roomUserId: PropTypes.string, - widthOffset: PropTypes.number, - onPress: PropTypes.func, - width: PropTypes.number, - height: PropTypes.number, - parentTitle: PropTypes.string, - isGroupChat: PropTypes.bool, - testID: PropTypes.string - }; +interface IRoomHeaderContainerProps { + title: string; + subtitle: string; + type: string; + prid: string; + tmid: string; + teamMain: boolean; + usersTyping: string; + status: string; + statusText: string; + connecting: boolean; + connected: boolean; + roomUserId: string; + widthOffset: number; + onPress(): void; + width: number; + height: number; + parentTitle: string; + isGroupChat: boolean; + testID: string; +} - shouldComponentUpdate(nextProps) { - const { - type, title, subtitle, status, statusText, connecting, connected, onPress, usersTyping, width, height, teamMain - } = this.props; +class RoomHeaderContainer extends Component<IRoomHeaderContainerProps, any> { + shouldComponentUpdate(nextProps: IRoomHeaderContainerProps) { + const { type, title, subtitle, status, statusText, connecting, connected, onPress, usersTyping, width, height, teamMain } = + this.props; if (nextProps.type !== type) { return true; } @@ -129,12 +127,10 @@ class RoomHeaderContainer extends Component { } } -const mapStateToProps = (state, ownProps) => { +const mapStateToProps = (state: any, ownProps: any) => { let statusText; let status = 'offline'; - const { - roomUserId, type, visitor = {}, tmid - } = ownProps; + const { roomUserId, type, visitor = {}, tmid } = ownProps; if (state.meteor.connected) { if ((type === 'd' || (tmid && roomUserId)) && state.activeUsers[roomUserId]) { diff --git a/app/containers/RoomTypeIcon.js b/app/containers/RoomTypeIcon.tsx similarity index 62% rename from app/containers/RoomTypeIcon.js rename to app/containers/RoomTypeIcon.tsx index 7c0e32c13..b4ac871e6 100644 --- a/app/containers/RoomTypeIcon.js +++ b/app/containers/RoomTypeIcon.tsx @@ -1,6 +1,6 @@ import React from 'react'; import { StyleSheet } from 'react-native'; -import PropTypes from 'prop-types'; + import { CustomIcon } from '../lib/Icons'; import { STATUS_COLORS, themes } from '../constants/colors'; import Status from './Status/Status'; @@ -12,19 +12,23 @@ const styles = StyleSheet.create({ } }); -const RoomTypeIcon = React.memo(({ - type, size, isGroupChat, status, style, theme, teamMain -}) => { +interface IRoomTypeIcon { + theme: string; + type: string; + isGroupChat: boolean; + teamMain: boolean; + status: string; + size: number; + style: any; +} + +const RoomTypeIcon = React.memo(({ type, isGroupChat, status, style, theme, teamMain, size = 16 }: IRoomTypeIcon) => { if (!type) { return null; } const color = themes[theme].titleText; - const iconStyle = [ - styles.icon, - { color }, - style - ]; + const iconStyle = [styles.icon, { color }, style]; if (type === 'd' && !isGroupChat) { return <Status style={[iconStyle, { color: STATUS_COLORS[status] ?? STATUS_COLORS.offline }]} size={size} status={status} />; @@ -33,7 +37,7 @@ const RoomTypeIcon = React.memo(({ // TODO: move this to a separate function let icon = 'channel-private'; if (teamMain) { - icon = `teams${ type === 'p' ? '-private' : '' }`; + icon = `teams${type === 'p' ? '-private' : ''}`; } else if (type === 'discussion') { icon = 'discussions'; } else if (type === 'c') { @@ -48,27 +52,7 @@ const RoomTypeIcon = React.memo(({ icon = 'omnichannel'; } - return ( - <CustomIcon - name={icon} - size={size} - style={iconStyle} - /> - ); + return <CustomIcon name={icon} size={size} style={iconStyle} />; }); -RoomTypeIcon.propTypes = { - theme: PropTypes.string, - type: PropTypes.string, - isGroupChat: PropTypes.bool, - teamMain: PropTypes.bool, - status: PropTypes.string, - size: PropTypes.number, - style: PropTypes.object -}; - -RoomTypeIcon.defaultProps = { - size: 16 -}; - export default withTheme(RoomTypeIcon); diff --git a/app/containers/SafeAreaView.js b/app/containers/SafeAreaView.tsx similarity index 63% rename from app/containers/SafeAreaView.js rename to app/containers/SafeAreaView.tsx index 290757930..7daa3347e 100644 --- a/app/containers/SafeAreaView.js +++ b/app/containers/SafeAreaView.tsx @@ -1,7 +1,7 @@ import React from 'react'; import { StyleSheet } from 'react-native'; -import PropTypes from 'prop-types'; import { SafeAreaView as SafeAreaContext } from 'react-native-safe-area-context'; + import { themes } from '../constants/colors'; import { withTheme } from '../theme'; @@ -11,25 +11,22 @@ const styles = StyleSheet.create({ } }); -const SafeAreaView = React.memo(({ - style, children, testID, theme, vertical = true, ...props -}) => ( +interface ISafeAreaView { + testID: string; + theme: string; + vertical: boolean; + style: object; + children: JSX.Element; +} + +const SafeAreaView = React.memo(({ style, children, testID, theme, vertical = true, ...props }: ISafeAreaView) => ( <SafeAreaContext style={[styles.view, { backgroundColor: themes[theme].auxiliaryBackground }, style]} edges={vertical ? ['right', 'left'] : undefined} testID={testID} - {...props} - > + {...props}> {children} </SafeAreaContext> )); -SafeAreaView.propTypes = { - testID: PropTypes.string, - theme: PropTypes.string, - vertical: PropTypes.bool, - style: PropTypes.object, - children: PropTypes.element -}; - export default withTheme(SafeAreaView); diff --git a/app/containers/SearchBox.js b/app/containers/SearchBox.tsx similarity index 77% rename from app/containers/SearchBox.js rename to app/containers/SearchBox.tsx index 75ed7f080..4a08c91ce 100644 --- a/app/containers/SearchBox.js +++ b/app/containers/SearchBox.tsx @@ -1,6 +1,5 @@ import React from 'react'; -import { View, StyleSheet, Text } from 'react-native'; -import PropTypes from 'prop-types'; +import { StyleSheet, Text, View } from 'react-native'; import Touchable from 'react-native-platform-touchable'; import TextInput from '../presentation/TextInput'; @@ -45,21 +44,37 @@ const styles = StyleSheet.create({ } }); -const CancelButton = (onCancelPress, theme) => ( +interface ISearchBox { + onChangeText: () => void; + onSubmitEditing: () => void; + hasCancel: boolean; + onCancelPress: Function; + theme: string; + inputRef: any; + testID?: string; +} + +const CancelButton = (onCancelPress: Function, theme: string) => ( <Touchable onPress={onCancelPress} style={styles.cancel}> <Text style={[styles.cancelText, { color: themes[theme].headerTintColor }]}>{I18n.t('Cancel')}</Text> </Touchable> ); const SearchBox = ({ - onChangeText, onSubmitEditing, testID, hasCancel, onCancelPress, inputRef, theme, ...props -}) => ( + onChangeText, + onSubmitEditing, + testID, + hasCancel, + onCancelPress, + inputRef, + theme, + ...props +}: ISearchBox) => ( <View style={[ styles.container, { backgroundColor: isIOS ? themes[theme].headerBackground : themes[theme].headerSecondaryBackground } - ]} - > + ]}> <View style={[styles.searchBox, { backgroundColor: themes[theme].searchboxBackground }]}> <CustomIcon name='search' size={14} color={themes[theme].auxiliaryText} /> <TextInput @@ -79,18 +94,8 @@ const SearchBox = ({ {...props} /> </View> - { hasCancel ? CancelButton(onCancelPress, theme) : null } + {hasCancel ? CancelButton(onCancelPress, theme) : null} </View> ); -SearchBox.propTypes = { - onChangeText: PropTypes.func.isRequired, - onSubmitEditing: PropTypes.func, - hasCancel: PropTypes.bool, - onCancelPress: PropTypes.func, - theme: PropTypes.string, - inputRef: PropTypes.func, - testID: PropTypes.string -}; - export default withTheme(SearchBox); diff --git a/app/views/ThreadMessagesView/SearchHeader.js b/app/containers/SearchHeader.js similarity index 78% rename from app/views/ThreadMessagesView/SearchHeader.js rename to app/containers/SearchHeader.js index ae8c96e84..e231d0748 100644 --- a/app/views/ThreadMessagesView/SearchHeader.js +++ b/app/containers/SearchHeader.js @@ -2,12 +2,12 @@ import React from 'react'; import { StyleSheet, View } from 'react-native'; import PropTypes from 'prop-types'; -import { withTheme } from '../../theme'; -import sharedStyles from '../Styles'; -import { themes } from '../../constants/colors'; -import TextInput from '../../presentation/TextInput'; -import { isTablet, isIOS } from '../../utils/deviceInfo'; -import { useOrientation } from '../../dimensions'; +import { withTheme } from '../theme'; +import sharedStyles from '../views/Styles'; +import { themes } from '../constants/colors'; +import TextInput from '../presentation/TextInput'; +import { isIOS, isTablet } from '../utils/deviceInfo'; +import { useOrientation } from '../dimensions'; const styles = StyleSheet.create({ container: { diff --git a/app/containers/Status/Status.js b/app/containers/Status/Status.tsx similarity index 52% rename from app/containers/Status/Status.js rename to app/containers/Status/Status.tsx index 991402b35..a5ff29d39 100644 --- a/app/containers/Status/Status.js +++ b/app/containers/Status/Status.tsx @@ -1,17 +1,26 @@ import React from 'react'; -import PropTypes from 'prop-types'; + import { CustomIcon } from '../../lib/Icons'; import { STATUS_COLORS } from '../../constants/colors'; -const Status = React.memo(({ - status, size, style, ...props -}) => { - const name = `status-${ status }`; +interface IStatus { + status: string; + size: number; + style: any; +} + +const Status = React.memo(({ style, status = 'offline', size = 32, ...props }: IStatus) => { + const name = `status-${status}`; const isNameValid = CustomIcon.hasIcon(name); const iconName = isNameValid ? name : 'status-offline'; - const calculatedStyle = [{ - width: size, height: size, textAlignVertical: 'center' - }, style]; + const calculatedStyle = [ + { + width: size, + height: size, + textAlignVertical: 'center' + }, + style + ]; return ( <CustomIcon @@ -24,14 +33,4 @@ const Status = React.memo(({ ); }); -Status.propTypes = { - status: PropTypes.string, - size: PropTypes.number, - style: PropTypes.any -}; -Status.defaultProps = { - status: 'offline', - size: 32 -}; - export default Status; diff --git a/app/containers/Status/index.js b/app/containers/Status/index.js deleted file mode 100644 index 7b389da2c..000000000 --- a/app/containers/Status/index.js +++ /dev/null @@ -1,19 +0,0 @@ -import React, { memo } from 'react'; -import PropTypes from 'prop-types'; -import { connect } from 'react-redux'; - -import Status from './Status'; - -const StatusContainer = memo(({ style, size = 32, status }) => <Status size={size} style={style} status={status} />); - -StatusContainer.propTypes = { - style: PropTypes.any, - size: PropTypes.number, - status: PropTypes.string -}; - -const mapStateToProps = (state, ownProps) => ({ - status: state.meteor.connected ? (state.activeUsers[ownProps.id] && state.activeUsers[ownProps.id].status) : 'loading' -}); - -export default connect(mapStateToProps)(StatusContainer); diff --git a/app/containers/Status/index.tsx b/app/containers/Status/index.tsx new file mode 100644 index 000000000..f47ca31c1 --- /dev/null +++ b/app/containers/Status/index.tsx @@ -0,0 +1,20 @@ +import React, { memo } from 'react'; +import { connect } from 'react-redux'; + +import Status from './Status'; + +interface IStatusContainer { + style: any; + size: number; + status: string; +} + +const StatusContainer = memo(({ style, size = 32, status }: IStatusContainer) => ( + <Status size={size} style={style} status={status} /> +)); + +const mapStateToProps = (state: any, ownProps: any) => ({ + status: state.meteor.connected ? state.activeUsers[ownProps.id] && state.activeUsers[ownProps.id].status : 'loading' +}); + +export default connect(mapStateToProps)(StatusContainer); diff --git a/app/containers/StatusBar.js b/app/containers/StatusBar.tsx similarity index 75% rename from app/containers/StatusBar.js rename to app/containers/StatusBar.tsx index ad4264d51..da115f4ed 100644 --- a/app/containers/StatusBar.js +++ b/app/containers/StatusBar.tsx @@ -1,11 +1,16 @@ import React from 'react'; import { StatusBar as StatusBarRN } from 'react-native'; -import PropTypes from 'prop-types'; import { themes } from '../constants/colors'; import { withTheme } from '../theme'; -const StatusBar = React.memo(({ theme, barStyle, backgroundColor }) => { +interface IStatusBar { + theme: string; + barStyle: any; + backgroundColor: string; +} + +const StatusBar = React.memo(({ theme, barStyle, backgroundColor }: IStatusBar) => { if (!barStyle) { barStyle = 'light-content'; if (theme === 'light') { @@ -15,10 +20,4 @@ const StatusBar = React.memo(({ theme, barStyle, backgroundColor }) => { return <StatusBarRN backgroundColor={backgroundColor ?? themes[theme].headerBackground} barStyle={barStyle} animated />; }); -StatusBar.propTypes = { - theme: PropTypes.string, - barStyle: PropTypes.string, - backgroundColor: PropTypes.string -}; - export default withTheme(StatusBar); diff --git a/app/containers/TextInput.stories.js b/app/containers/TextInput.stories.js index 6c802cc30..d0e6c6044 100644 --- a/app/containers/TextInput.stories.js +++ b/app/containers/TextInput.stories.js @@ -20,24 +20,12 @@ const item = { const theme = 'light'; - stories.add('Short and Long Text', () => ( <> <View style={styles.paddingHorizontal}> - <TextInput - label='Short Text' - placeholder='placeholder' - value={item.name} - theme={theme} - /> + <TextInput label='Short Text' placeholder='placeholder' value={item.name} theme={theme} /> - <TextInput - label='Long Text' - placeholder='placeholder' - value={item.longText} - theme={theme} - /> + <TextInput label='Long Text' placeholder='placeholder' value={item.longText} theme={theme} /> </View> </> )); - diff --git a/app/containers/TextInput.js b/app/containers/TextInput.tsx similarity index 74% rename from app/containers/TextInput.js rename to app/containers/TextInput.tsx index 9bb62de33..ae11dcc17 100644 --- a/app/containers/TextInput.js +++ b/app/containers/TextInput.tsx @@ -1,6 +1,5 @@ import React from 'react'; -import { View, StyleSheet, Text } from 'react-native'; -import PropTypes from 'prop-types'; +import { StyleSheet, Text, View } from 'react-native'; import { BorderlessButton } from 'react-native-gesture-handler'; import sharedStyles from '../views/Styles'; @@ -51,40 +50,42 @@ const styles = StyleSheet.create({ } }); +interface IRCTextInputProps { + label: string; + error: { + error: any; + reason: any; + }; + loading: boolean; + secureTextEntry: boolean; + containerStyle: any; + inputStyle: object; + inputRef: any; + testID: string; + iconLeft: string; + iconRight: string; + placeholder: string; + left: JSX.Element; + onIconRightPress(): void; + theme: string; +} -export default class RCTextInput extends React.PureComponent { - static propTypes = { - label: PropTypes.string, - error: PropTypes.object, - loading: PropTypes.bool, - secureTextEntry: PropTypes.bool, - containerStyle: PropTypes.any, - inputStyle: PropTypes.object, - inputRef: PropTypes.func, - testID: PropTypes.string, - iconLeft: PropTypes.string, - iconRight: PropTypes.string, - placeholder: PropTypes.string, - left: PropTypes.element, - onIconRightPress: PropTypes.func, - theme: PropTypes.string - } - +export default class RCTextInput extends React.PureComponent<IRCTextInputProps, any> { static defaultProps = { error: {}, theme: 'light' - } + }; state = { showPassword: false - } + }; get iconLeft() { const { testID, iconLeft, theme } = this.props; return ( <CustomIcon name={iconLeft} - testID={testID ? `${ testID }-icon-left` : null} + testID={testID ? `${testID}-icon-left` : null} style={[styles.iconContainer, styles.iconLeft, { color: themes[theme].bodyText }]} size={20} /> @@ -95,11 +96,7 @@ export default class RCTextInput extends React.PureComponent { const { iconRight, onIconRightPress, theme } = this.props; return ( <BorderlessButton onPress={onIconRightPress} style={[styles.iconContainer, styles.iconRight]}> - <CustomIcon - name={iconRight} - style={{ color: themes[theme].bodyText }} - size={20} - /> + <CustomIcon name={iconRight} style={{ color: themes[theme].bodyText }} size={20} /> </BorderlessButton> ); } @@ -111,7 +108,7 @@ export default class RCTextInput extends React.PureComponent { <BorderlessButton onPress={this.tooglePassword} style={[styles.iconContainer, styles.iconRight]}> <CustomIcon name={showPassword ? 'unread-on-top' : 'unread-on-top-disabled'} - testID={testID ? `${ testID }-icon-right` : null} + testID={testID ? `${testID}-icon-right` : null} style={{ color: themes[theme].auxiliaryText }} size={20} /> @@ -121,17 +118,31 @@ export default class RCTextInput extends React.PureComponent { get loading() { const { theme } = this.props; + // @ts-ignore return <ActivityIndicator style={[styles.iconContainer, styles.iconRight, { color: themes[theme].bodyText }]} />; } tooglePassword = () => { - this.setState(prevState => ({ showPassword: !prevState.showPassword })); - } + this.setState((prevState: any) => ({ showPassword: !prevState.showPassword })); + }; render() { const { showPassword } = this.state; const { - label, left, error, loading, secureTextEntry, containerStyle, inputRef, iconLeft, iconRight, inputStyle, testID, placeholder, theme, ...inputProps + label, + left, + error, + loading, + secureTextEntry, + containerStyle, + inputRef, + iconLeft, + iconRight, + inputStyle, + testID, + placeholder, + theme, + ...inputProps } = this.props; const { dangerColor } = themes[theme]; return ( @@ -139,18 +150,15 @@ export default class RCTextInput extends React.PureComponent { {label ? ( <Text contentDescription={null} + // @ts-ignore accessibilityLabel={null} - style={[ - styles.label, - { color: themes[theme].titleText }, - error.error && { color: dangerColor } - ]} - > + style={[styles.label, { color: themes[theme].titleText }, error.error && { color: dangerColor }]}> {label} </Text> ) : null} <View style={styles.wrap}> <TextInput + /* @ts-ignore*/ style={[ styles.input, iconLeft && styles.inputIconLeft, @@ -167,6 +175,7 @@ export default class RCTextInput extends React.PureComponent { inputStyle ]} ref={inputRef} + /* @ts-ignore*/ autoCorrect={false} autoCapitalize='none' underlineColorAndroid='transparent' @@ -174,6 +183,7 @@ export default class RCTextInput extends React.PureComponent { testID={testID} accessibilityLabel={placeholder} placeholder={placeholder} + /* @ts-ignore*/ contentDescription={placeholder} theme={theme} {...inputProps} diff --git a/app/containers/ThreadDetails.js b/app/containers/ThreadDetails.tsx similarity index 76% rename from app/containers/ThreadDetails.js rename to app/containers/ThreadDetails.tsx index e567a4cf8..86f670f00 100644 --- a/app/containers/ThreadDetails.js +++ b/app/containers/ThreadDetails.tsx @@ -1,6 +1,5 @@ import React from 'react'; -import PropTypes from 'prop-types'; -import { View, Text, StyleSheet } from 'react-native'; +import { StyleSheet, Text, View } from 'react-native'; import Touchable from 'react-native-platform-touchable'; import { CustomIcon } from '../lib/Icons'; @@ -40,14 +39,22 @@ const styles = StyleSheet.create({ } }); -const ThreadDetails = ({ - item, - user, - badgeColor, - toggleFollowThread, - style, - theme -}) => { +interface IThreadDetails { + item: { + tcount: number | string; + replies: any; + id: string; + }; + user: { + id: string; + }; + badgeColor: string; + toggleFollowThread: Function; + style: object; + theme: string; +} + +const ThreadDetails = ({ item, user, badgeColor, toggleFollowThread, style, theme }: IThreadDetails) => { let { tcount } = item; if (tcount >= 1000) { tcount = '+999'; @@ -62,24 +69,28 @@ const ThreadDetails = ({ replies = '+99'; } - const isFollowing = item.replies?.find(u => u === user?.id); + const isFollowing = item.replies?.find((u: any) => u === user?.id); return ( <View style={[styles.container, style]}> <View style={styles.detailsContainer}> <View style={styles.detailContainer}> <CustomIcon name='threads' size={24} color={themes[theme].auxiliaryText} /> - <Text style={[styles.detailText, { color: themes[theme].auxiliaryText }]} numberOfLines={1}>{tcount}</Text> + <Text style={[styles.detailText, { color: themes[theme].auxiliaryText }]} numberOfLines={1}> + {tcount} + </Text> </View> <View style={styles.detailContainer}> <CustomIcon name='user' size={24} color={themes[theme].auxiliaryText} /> - <Text style={[styles.detailText, { color: themes[theme].auxiliaryText }]} numberOfLines={1}>{replies}</Text> + <Text style={[styles.detailText, { color: themes[theme].auxiliaryText }]} numberOfLines={1}> + {replies} + </Text> </View> </View> <View style={styles.badgeContainer}> - {badgeColor ? <View style={[styles.badge, { backgroundColor: badgeColor }]} /> : null } + {badgeColor ? <View style={[styles.badge, { backgroundColor: badgeColor }]} /> : null} <Touchable onPress={() => toggleFollowThread?.(isFollowing, item.id)}> <CustomIcon size={24} @@ -91,13 +102,5 @@ const ThreadDetails = ({ </View> ); }; -ThreadDetails.propTypes = { - item: PropTypes.object, - user: PropTypes.object, - badgeColor: PropTypes.string, - toggleFollowThread: PropTypes.func, - style: PropTypes.object, - theme: PropTypes.string -}; export default withTheme(ThreadDetails); diff --git a/app/containers/Toast.js b/app/containers/Toast.tsx similarity index 80% rename from app/containers/Toast.js rename to app/containers/Toast.tsx index 4b982fb49..79fcc8272 100644 --- a/app/containers/Toast.js +++ b/app/containers/Toast.tsx @@ -1,7 +1,6 @@ import React from 'react'; import { StyleSheet } from 'react-native'; import EasyToast from 'react-native-easy-toast'; -import PropTypes from 'prop-types'; import { themes } from '../constants/colors'; import sharedStyles from '../views/Styles'; @@ -22,16 +21,20 @@ const styles = StyleSheet.create({ export const LISTENER = 'Toast'; -class Toast extends React.Component { - static propTypes = { - theme: PropTypes.string - } +interface IToastProps { + theme: string; +} + +class Toast extends React.Component<IToastProps, any> { + private listener: any; + + private toast: any; componentDidMount() { this.listener = EventEmitter.addEventListener(LISTENER, this.showToast); } - shouldComponentUpdate(nextProps) { + shouldComponentUpdate(nextProps: any) { const { theme } = this.props; if (nextProps.theme !== theme) { return true; @@ -43,19 +46,20 @@ class Toast extends React.Component { EventEmitter.removeListener(LISTENER, this.listener); } - getToastRef = toast => this.toast = toast; + getToastRef = (toast: any) => (this.toast = toast); - showToast = ({ message }) => { + showToast = ({ message }: any) => { if (this.toast && this.toast.show) { this.toast.show(message, 1000); } - } + }; render() { const { theme } = this.props; return ( <EasyToast ref={this.getToastRef} + // @ts-ignore position='center' style={[styles.toast, { backgroundColor: themes[theme].toastBackground }]} textStyle={[styles.text, { color: themes[theme].buttonText }]} diff --git a/app/containers/TwoFactor/index.js b/app/containers/TwoFactor/index.tsx similarity index 77% rename from app/containers/TwoFactor/index.js rename to app/containers/TwoFactor/index.tsx index deb5c25db..dd2686ddc 100644 --- a/app/containers/TwoFactor/index.js +++ b/app/containers/TwoFactor/index.tsx @@ -1,7 +1,6 @@ import React, { useEffect, useState } from 'react'; -import { View, Text, InteractionManager } from 'react-native'; +import { InteractionManager, Text, View } from 'react-native'; import isEmpty from 'lodash/isEmpty'; -import PropTypes from 'prop-types'; import { sha256 } from 'js-sha256'; import Modal from 'react-native-modal'; import useDeepCompareEffect from 'use-deep-compare-effect'; @@ -19,7 +18,12 @@ import styles from './styles'; export const TWO_FACTOR = 'TWO_FACTOR'; -const methods = { +interface ITwoFactor { + theme: string; + isMasterDetail: boolean; +} + +const methods: any = { totp: { text: 'Open_your_authentication_app_and_enter_the_code', keyboardType: 'numeric' @@ -36,10 +40,10 @@ const methods = { } }; -const TwoFactor = React.memo(({ theme, isMasterDetail }) => { +const TwoFactor = React.memo(({ theme, isMasterDetail }: ITwoFactor) => { const [visible, setVisible] = useState(false); - const [data, setData] = useState({}); - const [code, setCode] = useState(''); + const [data, setData] = useState<any>({}); + const [code, setCode] = useState<any>(''); const method = methods[data.method]; const isEmail = data.method === 'email'; @@ -55,7 +59,7 @@ const TwoFactor = React.memo(({ theme, isMasterDetail }) => { } }, [data]); - const showTwoFactor = args => setData(args); + const showTwoFactor = (args: any) => setData(args); useEffect(() => { const listener = EventEmitter.addEventListener(TWO_FACTOR, showTwoFactor); @@ -86,20 +90,26 @@ const TwoFactor = React.memo(({ theme, isMasterDetail }) => { const color = themes[theme].titleText; return ( <Modal + // @ts-ignore transparent avoidKeyboard useNativeDriver isVisible={visible} - hideModalContentWhileAnimating - > + hideModalContentWhileAnimating> <View style={styles.container} testID='two-factor'> - <View style={[styles.content, isMasterDetail && [sharedStyles.modalFormSheet, styles.tablet], { backgroundColor: themes[theme].backgroundColor }]}> + <View + style={[ + styles.content, + isMasterDetail && [sharedStyles.modalFormSheet, styles.tablet], + { backgroundColor: themes[theme].backgroundColor } + ]}> <Text style={[styles.title, { color }]}>{I18n.t(method?.title || 'Two_Factor_Authentication')}</Text> {method?.text ? <Text style={[styles.subtitle, { color }]}>{I18n.t(method.text)}</Text> : null} <TextInput + /* @ts-ignore*/ value={code} theme={theme} - inputRef={e => InteractionManager.runAfterInteractions(() => e?.getNativeRef()?.focus())} + inputRef={(e: any) => InteractionManager.runAfterInteractions(() => e?.getNativeRef()?.focus())} returnKeyType='send' autoCapitalize='none' onChangeText={setCode} @@ -109,7 +119,11 @@ const TwoFactor = React.memo(({ theme, isMasterDetail }) => { error={data.invalid && { error: 'totp-invalid', reason: I18n.t('Code_or_password_invalid') }} testID='two-factor-input' /> - {isEmail && <Text style={[styles.sendEmail, { color }]} onPress={sendEmail}>{I18n.t('Send_me_the_code_again')}</Text>} + {isEmail && ( + <Text style={[styles.sendEmail, { color }]} onPress={sendEmail}> + {I18n.t('Send_me_the_code_again')} + </Text> + )} <View style={styles.buttonContainer}> <Button title={I18n.t('Cancel')} @@ -133,12 +147,8 @@ const TwoFactor = React.memo(({ theme, isMasterDetail }) => { </Modal> ); }); -TwoFactor.propTypes = { - theme: PropTypes.string, - isMasterDetail: PropTypes.bool -}; -const mapStateToProps = state => ({ +const mapStateToProps = (state: any) => ({ isMasterDetail: state.app.isMasterDetail }); diff --git a/app/containers/TwoFactor/styles.js b/app/containers/TwoFactor/styles.ts similarity index 100% rename from app/containers/TwoFactor/styles.js rename to app/containers/TwoFactor/styles.ts diff --git a/app/containers/UIKit/Actions.js b/app/containers/UIKit/Actions.js deleted file mode 100644 index 56b781fd2..000000000 --- a/app/containers/UIKit/Actions.js +++ /dev/null @@ -1,31 +0,0 @@ -import React, { useState } from 'react'; -import PropTypes from 'prop-types'; -import { BLOCK_CONTEXT } from '@rocket.chat/ui-kit'; - -import Button from '../Button'; -import I18n from '../../i18n'; - -export const Actions = ({ - blockId, appId, elements, parser, theme -}) => { - const [showMoreVisible, setShowMoreVisible] = useState(() => elements.length > 5); - const renderedElements = showMoreVisible ? elements.slice(0, 5) : elements; - - const Elements = () => renderedElements - .map(element => parser.renderActions({ blockId, appId, ...element }, BLOCK_CONTEXT.ACTION, parser)); - - return ( - <> - <Elements /> - {showMoreVisible && (<Button theme={theme} title={I18n.t('Show_more')} onPress={() => setShowMoreVisible(false)} />)} - </> - ); -}; - -Actions.propTypes = { - blockId: PropTypes.string, - appId: PropTypes.string, - elements: PropTypes.array, - parser: PropTypes.object, - theme: PropTypes.string -}; diff --git a/app/containers/UIKit/Actions.tsx b/app/containers/UIKit/Actions.tsx new file mode 100644 index 000000000..1d5ba9840 --- /dev/null +++ b/app/containers/UIKit/Actions.tsx @@ -0,0 +1,29 @@ +import React, { useState } from 'react'; +import { BLOCK_CONTEXT } from '@rocket.chat/ui-kit'; + +import Button from '../Button'; +import I18n from '../../i18n'; + +interface IActions { + blockId: string; + appId: string; + elements: any[]; + parser: any; + theme: string; +} + +export const Actions = ({ blockId, appId, elements, parser, theme }: IActions) => { + const [showMoreVisible, setShowMoreVisible] = useState(() => elements.length > 5); + const renderedElements = showMoreVisible ? elements.slice(0, 5) : elements; + + const Elements = () => + renderedElements.map((element: any) => parser.renderActions({ blockId, appId, ...element }, BLOCK_CONTEXT.ACTION, parser)); + + return ( + <> + {/* @ts-ignore*/} + <Elements /> + {showMoreVisible && <Button theme={theme} title={I18n.t('Show_more')} onPress={() => setShowMoreVisible(false)} />} + </> + ); +}; diff --git a/app/containers/UIKit/Context.js b/app/containers/UIKit/Context.tsx similarity index 64% rename from app/containers/UIKit/Context.js rename to app/containers/UIKit/Context.tsx index 78dcecc9d..11f8e492e 100644 --- a/app/containers/UIKit/Context.js +++ b/app/containers/UIKit/Context.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { View, StyleSheet } from 'react-native'; +import { StyleSheet, View } from 'react-native'; import PropTypes from 'prop-types'; import { BLOCK_CONTEXT } from '@rocket.chat/ui-kit'; @@ -11,11 +11,12 @@ const styles = StyleSheet.create({ } }); -export const Context = ({ elements, parser }) => ( +export const Context = ({ elements, parser }: any) => ( <View style={styles.container}> - {elements.map(element => parser.renderContext(element, BLOCK_CONTEXT.CONTEXT, parser))} + {elements.map((element: any) => parser.renderContext(element, BLOCK_CONTEXT.CONTEXT, parser))} </View> ); + Context.propTypes = { elements: PropTypes.array, parser: PropTypes.object diff --git a/app/containers/UIKit/DatePicker.js b/app/containers/UIKit/DatePicker.tsx similarity index 60% rename from app/containers/UIKit/DatePicker.js rename to app/containers/UIKit/DatePicker.tsx index e8ef94635..25ada0700 100644 --- a/app/containers/UIKit/DatePicker.js +++ b/app/containers/UIKit/DatePicker.tsx @@ -1,6 +1,5 @@ import React, { useState } from 'react'; -import { View, StyleSheet, Text } from 'react-native'; -import PropTypes from 'prop-types'; +import { StyleSheet, Text, View } from 'react-native'; import DateTimePicker from '@react-native-community/datetimepicker'; import Touchable from 'react-native-platform-touchable'; import { BLOCK_CONTEXT } from '@rocket.chat/ui-kit'; @@ -9,7 +8,6 @@ import moment from 'moment'; import Button from '../Button'; import { textParser } from './utils'; import { themes } from '../../constants/colors'; - import sharedStyles from '../../views/Styles'; import { CustomIcon } from '../../lib/Icons'; import { isAndroid } from '../../utils/deviceInfo'; @@ -37,14 +35,26 @@ const styles = StyleSheet.create({ } }); -export const DatePicker = ({ - element, language, action, context, theme, loading, value, error -}) => { +interface IDatePicker { + element: { + initial_date: any; + placeholder: string; + }; + language: string; + action: Function; + context: number; + loading: boolean; + theme: string; + value: string; + error: string; +} + +export const DatePicker = ({ element, language, action, context, theme, loading, value, error }: IDatePicker) => { const [show, onShow] = useState(false); const { initial_date, placeholder } = element; const [currentDate, onChangeDate] = useState(new Date(initial_date || value)); - const onChange = ({ nativeEvent: { timestamp } }, date) => { + const onChange = ({ nativeEvent: { timestamp } }: any, date: any) => { const newDate = date || new Date(timestamp); onChangeDate(newDate); action({ value: moment(newDate).format('YYYY-MM-DD') }); @@ -53,49 +63,35 @@ export const DatePicker = ({ } }; - let button = ( - <Button - title={textParser([placeholder])} - onPress={() => onShow(!show)} - loading={loading} - theme={theme} - /> - ); + let button = <Button title={textParser([placeholder])} onPress={() => onShow(!show)} loading={loading} theme={theme} />; if (context === BLOCK_CONTEXT.FORM) { button = ( <Touchable onPress={() => onShow(!show)} style={{ backgroundColor: themes[theme].backgroundColor }} - background={Touchable.Ripple(themes[theme].bannerBackground)} - > + background={Touchable.Ripple(themes[theme].bannerBackground)}> <View style={[styles.input, { borderColor: error ? themes[theme].dangerColor : themes[theme].separatorColor }]}> - <Text - style={[ - styles.inputText, - { color: error ? themes[theme].dangerColor : themes[theme].titleText } - ]} - > + <Text style={[styles.inputText, { color: error ? themes[theme].dangerColor : themes[theme].titleText }]}> {currentDate.toLocaleDateString(language)} </Text> - { - loading - ? <ActivityIndicator style={[styles.loading, styles.icon]} /> - : <CustomIcon name='calendar' size={20} color={error ? themes[theme].dangerColor : themes[theme].auxiliaryText} style={styles.icon} /> - } + {loading ? ( + <ActivityIndicator style={[styles.loading, styles.icon]} /> + ) : ( + <CustomIcon + name='calendar' + size={20} + color={error ? themes[theme].dangerColor : themes[theme].auxiliaryText} + style={styles.icon} + /> + )} </View> </Touchable> ); } const content = show ? ( - <DateTimePicker - mode='date' - display='default' - value={currentDate} - onChange={onChange} - textColor={themes[theme].titleText} - /> + <DateTimePicker mode='date' display='default' value={currentDate} onChange={onChange} textColor={themes[theme].titleText} /> ) : null; return ( @@ -105,13 +101,3 @@ export const DatePicker = ({ </> ); }; -DatePicker.propTypes = { - element: PropTypes.object, - language: PropTypes.string, - action: PropTypes.func, - context: PropTypes.number, - loading: PropTypes.bool, - theme: PropTypes.string, - value: PropTypes.string, - error: PropTypes.string -}; diff --git a/app/containers/UIKit/Divider.js b/app/containers/UIKit/Divider.tsx similarity index 100% rename from app/containers/UIKit/Divider.js rename to app/containers/UIKit/Divider.tsx diff --git a/app/containers/UIKit/Image.js b/app/containers/UIKit/Image.js deleted file mode 100644 index dd7eaa944..000000000 --- a/app/containers/UIKit/Image.js +++ /dev/null @@ -1,61 +0,0 @@ -import React from 'react'; -import { View, StyleSheet } from 'react-native'; -import FastImage from '@rocket.chat/react-native-fast-image'; -import PropTypes from 'prop-types'; -import { BLOCK_CONTEXT } from '@rocket.chat/ui-kit'; - -import ImageContainer from '../message/Image'; -import Navigation from '../../lib/Navigation'; - -const styles = StyleSheet.create({ - image: { - borderRadius: 2 - }, - mediaContext: { - marginRight: 8 - } -}); - -const ThumbContext = args => <View style={styles.mediaContext}><Thumb size={20} {...args} /></View>; - -export const Thumb = ({ element, size = 88 }) => ( - <FastImage - style={[{ width: size, height: size }, styles.image]} - source={{ uri: element.imageUrl }} - /> -); -Thumb.propTypes = { - element: PropTypes.object, - size: PropTypes.number -}; - -export const Media = ({ element, theme }) => { - const showAttachment = attachment => Navigation.navigate('AttachmentView', { attachment }); - const { imageUrl } = element; - - return ( - <ImageContainer - file={{ image_url: imageUrl }} - imageUrl={imageUrl} - showAttachment={showAttachment} - theme={theme} - /> - ); -}; -Media.propTypes = { - element: PropTypes.object, - theme: PropTypes.string -}; - -const genericImage = (element, context, theme) => { - switch (context) { - case BLOCK_CONTEXT.SECTION: - return <Thumb element={element} />; - case BLOCK_CONTEXT.CONTEXT: - return <ThumbContext element={element} />; - default: - return <Media element={element} theme={theme} />; - } -}; - -export const Image = ({ element, context, theme }) => genericImage(element, context, theme); diff --git a/app/containers/UIKit/Image.tsx b/app/containers/UIKit/Image.tsx new file mode 100644 index 000000000..7b8dbf0ff --- /dev/null +++ b/app/containers/UIKit/Image.tsx @@ -0,0 +1,66 @@ +import React from 'react'; +import { StyleSheet, View } from 'react-native'; +import FastImage from '@rocket.chat/react-native-fast-image'; +import { BLOCK_CONTEXT } from '@rocket.chat/ui-kit'; + +import ImageContainer from '../message/Image'; +import Navigation from '../../lib/Navigation'; + +const styles = StyleSheet.create({ + image: { + borderRadius: 2 + }, + mediaContext: { + marginRight: 8 + } +}); + +interface IThumb { + element: { + imageUrl: string; + }; + size?: number; +} + +interface IMedia { + element: { + imageUrl: string; + }; + theme: string; +} + +interface IImage { + element: any; + context: any; + theme: string; +} + +const ThumbContext = (args: any) => ( + <View style={styles.mediaContext}> + <Thumb size={20} {...args} /> + </View> +); + +export const Thumb = ({ element, size = 88 }: IThumb) => ( + <FastImage style={[{ width: size, height: size }, styles.image]} source={{ uri: element.imageUrl }} /> +); + +export const Media = ({ element, theme }: IMedia) => { + const showAttachment = (attachment: any) => Navigation.navigate('AttachmentView', { attachment }); + const { imageUrl } = element; + // @ts-ignore + return <ImageContainer file={{ image_url: imageUrl }} imageUrl={imageUrl} showAttachment={showAttachment} theme={theme} />; +}; + +const genericImage = (element: any, context: any, theme: string) => { + switch (context) { + case BLOCK_CONTEXT.SECTION: + return <Thumb element={element} />; + case BLOCK_CONTEXT.CONTEXT: + return <ThumbContext element={element} />; + default: + return <Media element={element} theme={theme} />; + } +}; + +export const Image = ({ element, context, theme }: IImage) => genericImage(element, context, theme); diff --git a/app/containers/UIKit/Input.js b/app/containers/UIKit/Input.tsx similarity index 69% rename from app/containers/UIKit/Input.js rename to app/containers/UIKit/Input.tsx index 22a7d6846..3ecd8b58c 100644 --- a/app/containers/UIKit/Input.js +++ b/app/containers/UIKit/Input.tsx @@ -1,6 +1,5 @@ import React from 'react'; import { StyleSheet, Text, View } from 'react-native'; -import PropTypes from 'prop-types'; import { BLOCK_CONTEXT } from '@rocket.chat/ui-kit'; import sharedStyles from '../../views/Styles'; @@ -32,24 +31,24 @@ const styles = StyleSheet.create({ } }); -export const Input = ({ - element, parser, label, description, error, hint, theme -}) => ( +interface IInput { + element: object; + parser: any; + label: string; + description: string; + error: string; + hint: string; + theme: string; +} + +export const Input = ({ element, parser, label, description, error, hint, theme }: IInput) => ( <View style={styles.container}> - {label ? <Text style={[styles.label, { color: error ? themes[theme].dangerColor : themes[theme].titleText }]}>{label}</Text> : null} + {label ? ( + <Text style={[styles.label, { color: error ? themes[theme].dangerColor : themes[theme].titleText }]}>{label}</Text> + ) : null} {description ? <Text style={[styles.description, { color: themes[theme].auxiliaryText }]}>{description}</Text> : null} {parser.renderInputs({ ...element }, BLOCK_CONTEXT.FORM, parser)} {error ? <Text style={[styles.error, { color: themes[theme].dangerColor }]}>{error}</Text> : null} {hint ? <Text style={[styles.hint, { color: themes[theme].auxiliaryText }]}>{hint}</Text> : null} </View> ); - -Input.propTypes = { - element: PropTypes.object, - parser: PropTypes.object, - label: PropTypes.string, - description: PropTypes.string, - error: PropTypes.string, - hint: PropTypes.string, - theme: PropTypes.string -}; diff --git a/app/containers/UIKit/MessageBlock.js b/app/containers/UIKit/MessageBlock.js deleted file mode 100644 index 2a1c08b3e..000000000 --- a/app/containers/UIKit/MessageBlock.js +++ /dev/null @@ -1,27 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; - -import { UiKitMessage, UiKitModal } from './index'; -import { KitContext } from './utils'; - -export const messageBlockWithContext = context => props => ( - <KitContext.Provider value={context}> - <MessageBlock {...props} /> - </KitContext.Provider> -); - -const MessageBlock = ({ blocks }) => UiKitMessage(blocks); -MessageBlock.propTypes = { - blocks: PropTypes.any -}; - -export const modalBlockWithContext = context => data => ( - <KitContext.Provider value={{ ...context, ...data }}> - <ModalBlock {...data} /> - </KitContext.Provider> -); - -const ModalBlock = ({ blocks }) => UiKitModal(blocks); -ModalBlock.propTypes = { - blocks: PropTypes.any -}; diff --git a/app/containers/UIKit/MessageBlock.tsx b/app/containers/UIKit/MessageBlock.tsx new file mode 100644 index 000000000..7ccc01db0 --- /dev/null +++ b/app/containers/UIKit/MessageBlock.tsx @@ -0,0 +1,22 @@ +import React from 'react'; + +import { UiKitMessage, UiKitModal } from './index'; +import { KitContext } from './utils'; + +export const messageBlockWithContext = (context: any) => (props: any) => + ( + <KitContext.Provider value={context}> + <MessageBlock {...props} /> + </KitContext.Provider> + ); + +const MessageBlock = ({ blocks }: any) => UiKitMessage(blocks); + +export const modalBlockWithContext = (context: any) => (data: any) => + ( + <KitContext.Provider value={{ ...context, ...data }}> + <ModalBlock {...data} /> + </KitContext.Provider> + ); + +const ModalBlock = ({ blocks }: any) => UiKitModal(blocks); diff --git a/app/containers/UIKit/MultiSelect/Chips.js b/app/containers/UIKit/MultiSelect/Chips.tsx similarity index 55% rename from app/containers/UIKit/MultiSelect/Chips.js rename to app/containers/UIKit/MultiSelect/Chips.tsx index 47827141d..7c11b8aeb 100644 --- a/app/containers/UIKit/MultiSelect/Chips.js +++ b/app/containers/UIKit/MultiSelect/Chips.tsx @@ -1,52 +1,56 @@ import React from 'react'; import { Text, View } from 'react-native'; -import PropTypes from 'prop-types'; import Touchable from 'react-native-platform-touchable'; import FastImage from '@rocket.chat/react-native-fast-image'; import { themes } from '../../../constants/colors'; import { textParser } from '../utils'; import { CustomIcon } from '../../../lib/Icons'; - import styles from './styles'; -const keyExtractor = item => item.value.toString(); +interface IChip { + item: { + value: string; + imageUrl: string; + text: string; + }; + onSelect: Function; + style?: object; + theme: string; +} -const Chip = ({ - item, onSelect, style, theme -}) => ( +interface IChips { + items: []; + onSelect: Function; + style?: object; + theme: string; +} + +const keyExtractor = (item: any) => item.value.toString(); + +const Chip = ({ item, onSelect, style, theme }: IChip) => ( <Touchable key={item.value} onPress={() => onSelect(item)} style={[styles.chip, { backgroundColor: themes[theme].auxiliaryBackground }, style]} - background={Touchable.Ripple(themes[theme].bannerBackground)} - > + background={Touchable.Ripple(themes[theme].bannerBackground)}> <> {item.imageUrl ? <FastImage style={styles.chipImage} source={{ uri: item.imageUrl }} /> : null} - <Text numberOfLines={1} 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='close' size={16} color={themes[theme].auxiliaryText} /> </> </Touchable> ); -Chip.propTypes = { - item: PropTypes.object, - onSelect: PropTypes.func, - style: PropTypes.object, - theme: PropTypes.string -}; +Chip.propTypes = {}; -const Chips = ({ - items, onSelect, style, theme -}) => ( +const Chips = ({ items, onSelect, style, theme }: IChips) => ( <View style={styles.chips}> - {items.map(item => <Chip key={keyExtractor(item)} item={item} onSelect={onSelect} style={style} theme={theme} />)} + {items.map(item => ( + <Chip key={keyExtractor(item)} item={item} onSelect={onSelect} style={style} theme={theme} /> + ))} </View> ); -Chips.propTypes = { - items: PropTypes.array, - onSelect: PropTypes.func, - style: PropTypes.object, - theme: PropTypes.string -}; export default Chips; diff --git a/app/containers/UIKit/MultiSelect/Input.js b/app/containers/UIKit/MultiSelect/Input.tsx similarity index 52% rename from app/containers/UIKit/MultiSelect/Input.js rename to app/containers/UIKit/MultiSelect/Input.tsx index ff8647a61..e03837a2b 100644 --- a/app/containers/UIKit/MultiSelect/Input.js +++ b/app/containers/UIKit/MultiSelect/Input.tsx @@ -1,6 +1,5 @@ import React from 'react'; -import { View, Text } from 'react-native'; -import PropTypes from 'prop-types'; +import { Text, View } from 'react-native'; import Touchable from 'react-native-platform-touchable'; import { CustomIcon } from '../../../lib/Icons'; @@ -8,33 +7,32 @@ import { themes } from '../../../constants/colors'; import ActivityIndicator from '../../ActivityIndicator'; import styles from './styles'; -const Input = ({ - children, onPress, theme, loading, inputStyle, placeholder, disabled -}) => ( +interface IInput { + children: JSX.Element; + onPress: Function; + theme: string; + inputStyle: object; + disabled?: boolean | object; + placeholder?: string; + loading?: boolean; + innerInputStyle?: object; +} + +const Input = ({ children, onPress, theme, loading, inputStyle, placeholder, disabled, innerInputStyle }: IInput) => ( <Touchable onPress={onPress} style={[{ backgroundColor: themes[theme].backgroundColor }, inputStyle]} background={Touchable.Ripple(themes[theme].bannerBackground)} - disabled={disabled} - > - <View style={[styles.input, { borderColor: themes[theme].separatorColor }]}> + disabled={disabled}> + <View style={[styles.input, { borderColor: themes[theme].separatorColor }, innerInputStyle]}> {placeholder ? <Text style={[styles.pickerText, { color: themes[theme].auxiliaryText }]}>{placeholder}</Text> : children} - { - loading - ? <ActivityIndicator style={[styles.loading, styles.icon]} /> - : <CustomIcon name='chevron-down' size={22} color={themes[theme].auxiliaryText} style={styles.icon} /> - } + {loading ? ( + <ActivityIndicator style={[styles.loading, styles.icon]} /> + ) : ( + <CustomIcon name='chevron-down' size={22} color={themes[theme].auxiliaryText} style={styles.icon} /> + )} </View> </Touchable> ); -Input.propTypes = { - children: PropTypes.node, - onPress: PropTypes.func, - theme: PropTypes.string, - inputStyle: PropTypes.object, - disabled: PropTypes.bool, - placeholder: PropTypes.string, - loading: PropTypes.bool -}; export default Input; diff --git a/app/containers/UIKit/MultiSelect/Items.js b/app/containers/UIKit/MultiSelect/Items.tsx similarity index 59% rename from app/containers/UIKit/MultiSelect/Items.js rename to app/containers/UIKit/MultiSelect/Items.tsx index c2f784d03..4c81bbe2c 100644 --- a/app/containers/UIKit/MultiSelect/Items.js +++ b/app/containers/UIKit/MultiSelect/Items.tsx @@ -1,6 +1,5 @@ import React from 'react'; -import { Text, FlatList } from 'react-native'; -import PropTypes from 'prop-types'; +import { FlatList, Text } from 'react-native'; import Touchable from 'react-native-platform-touchable'; import FastImage from '@rocket.chat/react-native-fast-image'; @@ -8,26 +7,37 @@ import Check from '../../Check'; import * as List from '../../List'; import { textParser } from '../utils'; import { themes } from '../../../constants/colors'; - import styles from './styles'; -const keyExtractor = item => item.value.toString(); +interface IItem { + item: { + value: { name: string }; + text: { text: string }; + imageUrl: string; + }; + selected: any; + onSelect: Function; + theme: string; +} + +interface IItems { + items: []; + selected: []; + onSelect: Function; + theme: string; +} + +const keyExtractor = (item: any) => item.value.toString(); // RectButton doesn't work on modal (Android) -const Item = ({ - item, selected, onSelect, theme -}) => { +const Item = ({ item, selected, onSelect, theme }: IItem) => { const itemName = item.value.name || item.text.text.toLowerCase(); return ( <Touchable - testID={`multi-select-item-${ itemName }`} + testID={`multi-select-item-${itemName}`} key={item} onPress={() => onSelect(item)} - style={[ - styles.item, - { backgroundColor: themes[theme].backgroundColor } - ]} - > + style={[styles.item, { backgroundColor: themes[theme].backgroundColor }]}> <> {item.imageUrl ? <FastImage style={styles.itemImage} source={{ uri: item.imageUrl }} /> : null} <Text style={{ color: themes[theme].titleText }}>{textParser([item.text])}</Text> @@ -36,16 +46,8 @@ const Item = ({ </Touchable> ); }; -Item.propTypes = { - item: PropTypes.object, - selected: PropTypes.number, - onSelect: PropTypes.func, - theme: PropTypes.string -}; -const Items = ({ - items, selected, onSelect, theme -}) => ( +const Items = ({ items, selected, onSelect, theme }: IItems) => ( <FlatList data={items} style={[styles.items, { backgroundColor: themes[theme].backgroundColor }]} @@ -53,14 +55,10 @@ const Items = ({ keyboardShouldPersistTaps='always' ItemSeparatorComponent={List.Separator} keyExtractor={keyExtractor} - renderItem={({ item }) => <Item item={item} onSelect={onSelect} theme={theme} selected={selected.find(s => s === item.value)} />} + renderItem={({ item }) => ( + <Item item={item} onSelect={onSelect} theme={theme} selected={selected.find(s => s === item.value)} /> + )} /> ); -Items.propTypes = { - items: PropTypes.array, - selected: PropTypes.array, - onSelect: PropTypes.func, - theme: PropTypes.string -}; export default Items; diff --git a/app/containers/UIKit/MultiSelect/index.js b/app/containers/UIKit/MultiSelect/index.js deleted file mode 100644 index d1e42aaa5..000000000 --- a/app/containers/UIKit/MultiSelect/index.js +++ /dev/null @@ -1,205 +0,0 @@ -import React, { useState, useEffect } from 'react'; -import { - View, Text, TouchableWithoutFeedback, Modal, KeyboardAvoidingView, Animated, Easing, StyleSheet -} from 'react-native'; -import PropTypes from 'prop-types'; -import { BLOCK_CONTEXT } from '@rocket.chat/ui-kit'; - -import Button from '../../Button'; -import TextInput from '../../TextInput'; - -import { textParser } from '../utils'; -import { themes } from '../../../constants/colors'; -import I18n from '../../../i18n'; -import { isIOS } from '../../../utils/deviceInfo'; - -import Chips from './Chips'; -import Items from './Items'; -import Input from './Input'; - -import styles from './styles'; - -const ANIMATION_DURATION = 200; -const ANIMATION_PROPS = { - duration: ANIMATION_DURATION, - easing: Easing.inOut(Easing.quad), - useNativeDriver: true -}; -const animatedValue = new Animated.Value(0); - -const behavior = isIOS ? 'padding' : null; - -export const MultiSelect = React.memo(({ - options = [], - onChange, - placeholder = { text: 'Search' }, - context, - loading, - value: values, - multiselect = false, - onSearch, - onClose, - disabled, - inputStyle, - theme -}) => { - const [selected, select] = useState(Array.isArray(values) ? values : []); - const [open, setOpen] = useState(false); - const [search, onSearchChange] = useState(''); - const [currentValue, setCurrentValue] = useState(''); - const [showContent, setShowContent] = useState(false); - - useEffect(() => { - if (Array.isArray(values)) { - select(values); - } - }, [values]); - - useEffect(() => { - setOpen(showContent); - }, [showContent]); - - useEffect(() => { - if (values && values.length && !multiselect) { - setCurrentValue(values[0].text); - } - }, []); - - const onShow = () => { - Animated.timing( - animatedValue, - { - toValue: 1, - ...ANIMATION_PROPS - } - ).start(); - setShowContent(true); - }; - - const onHide = () => { - onClose(); - Animated.timing( - animatedValue, - { - toValue: 0, - ...ANIMATION_PROPS - } - ).start(() => setShowContent(false)); - }; - - const onSelect = (item) => { - const { value, text: { text } } = item; - if (multiselect) { - let newSelect = []; - if (!selected.includes(value)) { - newSelect = [...selected, value]; - } else { - newSelect = selected.filter(s => s !== value); - } - select(newSelect); - onChange({ value: newSelect }); - } else { - onChange({ value }); - setCurrentValue(text); - onHide(); - } - }; - - const renderContent = () => { - const items = onSearch ? options : options.filter(option => textParser([option.text]).toLowerCase().includes(search.toLowerCase())); - - return ( - <View style={[styles.modal, { backgroundColor: themes[theme].backgroundColor }]}> - <View style={[styles.content, { backgroundColor: themes[theme].backgroundColor }]}> - <TextInput - testID='multi-select-search' - onChangeText={onSearch || onSearchChange} - placeholder={I18n.t('Search')} - theme={theme} - /> - <Items items={items} selected={selected} onSelect={onSelect} theme={theme} /> - </View> - </View> - ); - }; - - const translateY = animatedValue.interpolate({ - inputRange: [0, 1], - outputRange: [600, 0] - }); - - let button = multiselect ? ( - <Button - title={`${ selected.length } selecteds`} - onPress={onShow} - loading={loading} - theme={theme} - /> - ) : ( - <Input - onPress={onShow} - theme={theme} - loading={loading} - disabled={disabled} - inputStyle={inputStyle} - > - <Text style={[styles.pickerText, { color: currentValue ? themes[theme].titleText : themes[theme].auxiliaryText }]}>{currentValue || placeholder.text}</Text> - </Input> - ); - - if (context === BLOCK_CONTEXT.FORM) { - const items = options.filter(option => selected.includes(option.value)); - button = ( - <Input - onPress={onShow} - theme={theme} - loading={loading} - disabled={disabled} - inputStyle={inputStyle} - > - {items.length ? <Chips items={items} onSelect={item => (disabled ? {} : onSelect(item))} theme={theme} /> : <Text style={[styles.pickerText, { color: themes[theme].auxiliaryText }]}>{placeholder.text}</Text>} - </Input> - ); - } - - return ( - <> - <Modal - animationType='fade' - transparent - visible={open} - onRequestClose={onHide} - onShow={onShow} - > - <TouchableWithoutFeedback onPress={onHide}> - <View style={styles.container}> - <View style={{ ...StyleSheet.absoluteFill, opacity: themes[theme].backdropOpacity, backgroundColor: themes[theme].backdropColor }} /> - <KeyboardAvoidingView style={styles.keyboardView} behavior={behavior}> - <Animated.View style={[styles.animatedContent, { transform: [{ translateY }] }]}> - {showContent ? renderContent() : null} - </Animated.View> - </KeyboardAvoidingView> - </View> - </TouchableWithoutFeedback> - </Modal> - {button} - </> - ); -}); -MultiSelect.propTypes = { - options: PropTypes.array, - onChange: PropTypes.func, - placeholder: PropTypes.object, - context: PropTypes.number, - loading: PropTypes.bool, - multiselect: PropTypes.bool, - onSearch: PropTypes.func, - onClose: PropTypes.func, - inputStyle: PropTypes.object, - value: PropTypes.array, - disabled: PropTypes.bool, - theme: PropTypes.string -}; -MultiSelect.defaultProps = { - onClose: () => {} -}; diff --git a/app/containers/UIKit/MultiSelect/index.tsx b/app/containers/UIKit/MultiSelect/index.tsx new file mode 100644 index 000000000..d50dede31 --- /dev/null +++ b/app/containers/UIKit/MultiSelect/index.tsx @@ -0,0 +1,207 @@ +import React, { useEffect, useState } from 'react'; +import { Animated, Easing, KeyboardAvoidingView, Modal, StyleSheet, Text, TouchableWithoutFeedback, View } from 'react-native'; +import { BLOCK_CONTEXT } from '@rocket.chat/ui-kit'; + +import Button from '../../Button'; +import TextInput from '../../TextInput'; +import { textParser } from '../utils'; +import { themes } from '../../../constants/colors'; +import I18n from '../../../i18n'; +import { isIOS } from '../../../utils/deviceInfo'; +import Chips from './Chips'; +import Items from './Items'; +import Input from './Input'; +import styles from './styles'; + +interface IMultiSelect { + options: any[]; + onChange: Function; + placeholder: { + text: string; + }; + context?: number; + loading?: boolean; + multiselect?: boolean; + onSearch: Function; + onClose: Function; + inputStyle: object; + value?: any[]; + disabled?: boolean | object; + theme: string; + innerInputStyle?: object; +} + +const ANIMATION_DURATION = 200; +const ANIMATION_PROPS = { + duration: ANIMATION_DURATION, + easing: Easing.inOut(Easing.quad), + useNativeDriver: true +}; +const animatedValue = new Animated.Value(0); + +const behavior = isIOS ? 'padding' : null; + +export const MultiSelect = React.memo( + ({ + options = [], + onChange, + placeholder = { text: 'Search' }, + context, + loading, + value: values, + multiselect = false, + onSearch, + onClose = () => {}, + disabled, + inputStyle, + theme, + innerInputStyle + }: IMultiSelect) => { + const [selected, select] = useState<any>(Array.isArray(values) ? values : []); + const [open, setOpen] = useState(false); + const [search, onSearchChange] = useState(''); + const [currentValue, setCurrentValue] = useState(''); + const [showContent, setShowContent] = useState(false); + + useEffect(() => { + if (Array.isArray(values)) { + select(values); + } + }, [values]); + + useEffect(() => { + setOpen(showContent); + }, [showContent]); + + useEffect(() => { + if (values && values.length && !multiselect) { + setCurrentValue(values[0].text); + } + }, []); + + const onShow = () => { + Animated.timing(animatedValue, { + toValue: 1, + ...ANIMATION_PROPS + }).start(); + setShowContent(true); + }; + + const onHide = () => { + onClose(); + Animated.timing(animatedValue, { + toValue: 0, + ...ANIMATION_PROPS + }).start(() => setShowContent(false)); + }; + + const onSelect = (item: any) => { + const { + value, + text: { text } + } = item; + if (multiselect) { + let newSelect = []; + if (!selected.includes(value)) { + newSelect = [...selected, value]; + } else { + newSelect = selected.filter((s: any) => s !== value); + } + select(newSelect); + onChange({ value: newSelect }); + } else { + onChange({ value }); + setCurrentValue(text); + onHide(); + } + }; + + const renderContent = () => { + const items: any = onSearch + ? options + : options.filter((option: any) => textParser([option.text]).toLowerCase().includes(search.toLowerCase())); + + return ( + <View style={[styles.modal, { backgroundColor: themes[theme].backgroundColor }]}> + <View style={[styles.content, { backgroundColor: themes[theme].backgroundColor }]}> + <TextInput + testID='multi-select-search' + /* @ts-ignore*/ + onChangeText={onSearch || onSearchChange} + placeholder={I18n.t('Search')} + theme={theme} + /> + <Items items={items} selected={selected} onSelect={onSelect} theme={theme} /> + </View> + </View> + ); + }; + + const translateY = animatedValue.interpolate({ + inputRange: [0, 1], + outputRange: [600, 0] + }); + + let button = multiselect ? ( + <Button title={`${selected.length} selecteds`} onPress={onShow} loading={loading} theme={theme} /> + ) : ( + <Input + onPress={onShow} + theme={theme} + loading={loading} + disabled={disabled} + inputStyle={inputStyle} + innerInputStyle={innerInputStyle}> + <Text style={[styles.pickerText, { color: currentValue ? themes[theme].titleText : themes[theme].auxiliaryText }]}> + {currentValue || placeholder.text} + </Text> + </Input> + ); + + if (context === BLOCK_CONTEXT.FORM) { + const items: any = options.filter((option: any) => selected.includes(option.value)); + button = ( + <Input + onPress={onShow} + theme={theme} + loading={loading} + disabled={disabled} + inputStyle={inputStyle} + innerInputStyle={innerInputStyle}> + {items.length ? ( + <Chips items={items} onSelect={(item: any) => (disabled ? {} : onSelect(item))} theme={theme} /> + ) : ( + <Text style={[styles.pickerText, { color: themes[theme].auxiliaryText }]}>{placeholder.text}</Text> + )} + </Input> + ); + } + + return ( + <> + <Modal animationType='fade' transparent visible={open} onRequestClose={onHide} onShow={onShow}> + <TouchableWithoutFeedback onPress={onHide}> + <View style={styles.container}> + <View + style={[ + StyleSheet.absoluteFill, + { + opacity: themes[theme].backdropOpacity, + backgroundColor: themes[theme].backdropColor + } + ]} + /> + {/* @ts-ignore*/} + <KeyboardAvoidingView style={styles.keyboardView} behavior={behavior}> + <Animated.View style={[styles.animatedContent, { transform: [{ translateY }] }]}> + {showContent ? renderContent() : null} + </Animated.View> + </KeyboardAvoidingView> + </View> + </TouchableWithoutFeedback> + </Modal> + {button} + </> + ); + } +); diff --git a/app/containers/UIKit/MultiSelect/styles.js b/app/containers/UIKit/MultiSelect/styles.ts similarity index 97% rename from app/containers/UIKit/MultiSelect/styles.js rename to app/containers/UIKit/MultiSelect/styles.ts index 8bc2c2124..9954d8da0 100644 --- a/app/containers/UIKit/MultiSelect/styles.js +++ b/app/containers/UIKit/MultiSelect/styles.ts @@ -2,7 +2,7 @@ import { StyleSheet } from 'react-native'; import sharedStyles from '../../../views/Styles'; -export default StyleSheet.create({ +export default StyleSheet.create<any>({ container: { flex: 1, alignItems: 'center', diff --git a/app/containers/UIKit/Overflow.js b/app/containers/UIKit/Overflow.tsx similarity index 56% rename from app/containers/UIKit/Overflow.js rename to app/containers/UIKit/Overflow.tsx index 73104e172..391a3af17 100644 --- a/app/containers/UIKit/Overflow.js +++ b/app/containers/UIKit/Overflow.tsx @@ -1,6 +1,5 @@ import React, { useState } from 'react'; -import { Text, FlatList, StyleSheet } from 'react-native'; -import PropTypes from 'prop-types'; +import { FlatList, StyleSheet, Text } from 'react-native'; import Popover from 'react-native-popover-view'; import Touchable from 'react-native-platform-touchable'; @@ -10,7 +9,33 @@ import { themes } from '../../constants/colors'; import { BUTTON_HIT_SLOP } from '../message/utils'; import * as List from '../List'; -const keyExtractor = item => item.value; +interface IOption { + option: { + text: string; + value: string; + }; + onOptionPress: Function; + parser: any; + theme: string; +} + +interface IOptions { + options: []; + onOptionPress: Function; + parser: object; + theme: string; +} + +interface IOverflow { + element: any; + action: Function; + loading: boolean; + parser: object; + theme: string; + context: any; +} + +const keyExtractor = (item: any) => item.value; const styles = StyleSheet.create({ menu: { @@ -25,27 +50,16 @@ const styles = StyleSheet.create({ } }); -const Option = ({ - option: { text, value }, onOptionPress, parser, theme -}) => ( +const Option = ({ option: { text, value }, onOptionPress, parser, theme }: IOption) => ( <Touchable onPress={() => onOptionPress({ value })} background={Touchable.Ripple(themes[theme].bannerBackground)} - style={styles.option} - > + style={styles.option}> <Text>{parser.text(text)}</Text> </Touchable> ); -Option.propTypes = { - option: PropTypes.object, - onOptionPress: PropTypes.func, - parser: PropTypes.object, - theme: PropTypes.string -}; -const Options = ({ - options, onOptionPress, parser, theme -}) => ( +const Options = ({ options, onOptionPress, parser, theme }: IOptions) => ( <FlatList data={options} renderItem={({ item }) => <Option option={item} onOptionPress={onOptionPress} parser={parser} theme={theme} />} @@ -53,22 +67,14 @@ const Options = ({ ItemSeparatorComponent={List.Separator} /> ); -Options.propTypes = { - options: PropTypes.array, - onOptionPress: PropTypes.func, - parser: PropTypes.object, - theme: PropTypes.string -}; const touchable = {}; -export const Overflow = ({ - element, loading, action, parser, theme -}) => { +export const Overflow = ({ element, loading, action, parser, theme }: IOverflow) => { const { options, blockId } = element; const [show, onShow] = useState(false); - const onOptionPress = ({ value }) => { + const onOptionPress = ({ value }: any) => { onShow(false); action({ value }); }; @@ -76,28 +82,25 @@ export const Overflow = ({ return ( <> <Touchable - ref={ref => touchable[blockId] = ref} + /* @ts-ignore*/ + ref={ref => (touchable[blockId] = ref)} background={Touchable.Ripple(themes[theme].bannerBackground)} onPress={() => onShow(!show)} hitSlop={BUTTON_HIT_SLOP} - style={styles.menu} - > - {!loading ? <CustomIcon size={18} name='kebab' color={themes[theme].bodyText} /> : <ActivityIndicator style={styles.loading} theme={theme} />} + style={styles.menu}> + {!loading ? ( + <CustomIcon size={18} name='kebab' color={themes[theme].bodyText} /> + ) : ( + <ActivityIndicator style={styles.loading} theme={theme} /> + )} </Touchable> <Popover isVisible={show} + /* @ts-ignore*/ fromView={touchable[blockId]} - onRequestClose={() => onShow(false)} - > + onRequestClose={() => onShow(false)}> <Options options={options} onOptionPress={onOptionPress} parser={parser} theme={theme} /> </Popover> </> ); }; -Overflow.propTypes = { - element: PropTypes.any, - action: PropTypes.func, - loading: PropTypes.bool, - parser: PropTypes.object, - theme: PropTypes.string -}; diff --git a/app/containers/UIKit/Section.js b/app/containers/UIKit/Section.js deleted file mode 100644 index 7e509d776..000000000 --- a/app/containers/UIKit/Section.js +++ /dev/null @@ -1,65 +0,0 @@ -import React from 'react'; -import { View, Text, StyleSheet } from 'react-native'; -import PropTypes from 'prop-types'; -import { BLOCK_CONTEXT } from '@rocket.chat/ui-kit'; - -import { themes } from '../../constants/colors'; - -const styles = StyleSheet.create({ - content: { - marginBottom: 8 - }, - row: { - flexDirection: 'row' - }, - column: { - justifyContent: 'center' - }, - text: { - flex: 1, - padding: 4 - }, - field: { - marginVertical: 6 - } -}); - -const Accessory = ({ - blockId, appId, element, parser -}) => parser.renderAccessories( - { blockId, appId, ...element }, - BLOCK_CONTEXT.SECTION, - parser -); - -const Fields = ({ fields, parser, theme }) => fields.map(field => ( - <Text style={[styles.text, styles.field, { color: themes[theme].bodyText }]}> - {parser.text(field)} - </Text> -)); - -const accessoriesRight = ['image', 'overflow']; - -export const Section = ({ - blockId, appId, text, fields, accessory, parser, theme -}) => ( - <View - style={[ - styles.content, - accessory && accessoriesRight.includes(accessory.type) ? styles.row : styles.column - ]} - > - {text ? <View style={styles.text}>{parser.text(text)}</View> : null} - {fields ? <Fields fields={fields} theme={theme} parser={parser} /> : null} - {accessory ? <Accessory element={{ blockId, appId, ...accessory }} parser={parser} /> : null} - </View> -); -Section.propTypes = { - blockId: PropTypes.string, - appId: PropTypes.string, - text: PropTypes.object, - fields: PropTypes.array, - accessory: PropTypes.any, - theme: PropTypes.string, - parser: PropTypes.object -}; diff --git a/app/containers/UIKit/Section.tsx b/app/containers/UIKit/Section.tsx new file mode 100644 index 000000000..a64d2c7a1 --- /dev/null +++ b/app/containers/UIKit/Section.tsx @@ -0,0 +1,65 @@ +import React from 'react'; +import { StyleSheet, Text, View } from 'react-native'; +import { BLOCK_CONTEXT } from '@rocket.chat/ui-kit'; + +import { themes } from '../../constants/colors'; + +const styles = StyleSheet.create({ + content: { + marginBottom: 8 + }, + row: { + flexDirection: 'row' + }, + column: { + justifyContent: 'center' + }, + text: { + flex: 1, + padding: 4 + }, + field: { + marginVertical: 6 + } +}); + +interface IAccessory { + blockId?: string; + appId?: string; + element: any; + parser: any; +} + +interface IFields { + fields: any; + parser: any; + theme: string; +} + +interface ISection { + blockId: string; + appId: string; + text: object; + fields: []; + accessory: any; + theme: string; + parser: any; +} + +const Accessory = ({ blockId, appId, element, parser }: IAccessory) => + parser.renderAccessories({ blockId, appId, ...element }, BLOCK_CONTEXT.SECTION, parser); + +const Fields = ({ fields, parser, theme }: IFields) => + fields.map((field: any) => ( + <Text style={[styles.text, styles.field, { color: themes[theme].bodyText }]}>{parser.text(field)}</Text> + )); + +const accessoriesRight = ['image', 'overflow']; + +export const Section = ({ blockId, appId, text, fields, accessory, parser, theme }: ISection) => ( + <View style={[styles.content, accessory && accessoriesRight.includes(accessory.type) ? styles.row : styles.column]}> + {text ? <View style={styles.text}>{parser.text(text)}</View> : null} + {fields ? <Fields fields={fields} theme={theme} parser={parser} /> : null} + {accessory ? <Accessory element={{ blockId, appId, ...accessory }} parser={parser} /> : null} + </View> +); diff --git a/app/containers/UIKit/Select.js b/app/containers/UIKit/Select.tsx similarity index 68% rename from app/containers/UIKit/Select.js rename to app/containers/UIKit/Select.tsx index 08fe82080..64951ea40 100644 --- a/app/containers/UIKit/Select.js +++ b/app/containers/UIKit/Select.tsx @@ -1,6 +1,5 @@ import React, { useState } from 'react'; import { StyleSheet } from 'react-native'; -import PropTypes from 'prop-types'; import RNPickerSelect from 'react-native-picker-select'; import sharedStyles from '../../views/Styles'; @@ -34,15 +33,20 @@ const styles = StyleSheet.create({ } }); -export const Select = ({ - options = [], - placeholder, - onChange, - loading, - disabled, - value: initialValue, - theme -}) => { +interface ISelect { + options: { + text: string; + value: string; + }[]; + placeholder: string; + onChange: Function; + loading: boolean; + disabled: boolean; + value: []; + theme: string; +} + +export const Select = ({ options = [], placeholder, onChange, loading, disabled, value: initialValue, theme }: ISelect) => { const [selected, setSelected] = useState(!Array.isArray(initialValue) && initialValue); const items = options.map(option => ({ label: textParser([option.text]), value: option.value })); const pickerStyle = { @@ -52,11 +56,12 @@ export const Select = ({ backgroundColor: themes[theme].backgroundColor }; - const Icon = () => ( - loading - ? <ActivityIndicator style={styles.loading} /> - : <CustomIcon size={22} name='chevron-down' style={isAndroid && styles.icon} color={themes[theme].auxiliaryText} /> - ); + const Icon = () => + loading ? ( + <ActivityIndicator style={styles.loading} /> + ) : ( + <CustomIcon size={22} name='chevron-down' style={isAndroid && styles.icon} color={themes[theme].auxiliaryText} /> + ); return ( <RNPickerSelect @@ -65,7 +70,7 @@ export const Select = ({ useNativeAndroidPickerStyle={false} value={selected} disabled={disabled} - onValueChange={(value) => { + onValueChange={value => { onChange({ value }); setSelected(value); }} @@ -74,16 +79,10 @@ export const Select = ({ inputAndroidContainer: pickerStyle }} Icon={Icon} - textInputProps={{ style: { ...styles.pickerText, color: selected ? themes[theme].titleText : themes[theme].auxiliaryText } }} + textInputProps={{ + // @ts-ignore + style: { ...styles.pickerText, color: selected ? themes[theme].titleText : themes[theme].auxiliaryText } + }} /> ); }; -Select.propTypes = { - options: PropTypes.array, - placeholder: PropTypes.string, - onChange: PropTypes.func, - loading: PropTypes.bool, - disabled: PropTypes.bool, - value: PropTypes.array, - theme: PropTypes.string -}; diff --git a/app/containers/UIKit/index.js b/app/containers/UIKit/index.tsx similarity index 56% rename from app/containers/UIKit/index.js rename to app/containers/UIKit/index.tsx index 84cac8861..3f3415509 100644 --- a/app/containers/UIKit/index.js +++ b/app/containers/UIKit/index.tsx @@ -1,22 +1,14 @@ /* eslint-disable class-methods-use-this */ import React, { useContext } from 'react'; import { StyleSheet, Text } from 'react-native'; -import { - uiKitMessage, - UiKitParserMessage, - uiKitModal, - UiKitParserModal, - BLOCK_CONTEXT -} from '@rocket.chat/ui-kit'; +import { BLOCK_CONTEXT, UiKitParserMessage, UiKitParserModal, uiKitMessage, uiKitModal } from '@rocket.chat/ui-kit'; import Markdown from '../markdown'; import Button from '../Button'; import TextInput from '../TextInput'; - -import { useBlockContext, textParser } from './utils'; +import { textParser, useBlockContext } from './utils'; import { themes } from '../../constants/colors'; import sharedStyles from '../../views/Styles'; - import { Divider } from './Divider'; import { Section } from './Section'; import { Actions } from './Actions'; @@ -50,28 +42,22 @@ const styles = StyleSheet.create({ const plainText = ({ text } = { text: '' }) => text; class MessageParser extends UiKitParserMessage { - text({ text, type } = { text: '' }, context) { - const { theme } = useContext(ThemeContext); + text({ text, type }: any = { text: '' }, context: any) { + const { theme }: any = useContext(ThemeContext); if (type !== 'mrkdwn') { return <Text style={[styles.text, { color: themes[theme].bodyText }]}>{text}</Text>; } const isContext = context === BLOCK_CONTEXT.CONTEXT; return ( - <Markdown - msg={text} - theme={theme} - style={[isContext && { color: themes[theme].auxiliaryText }]} - preview={isContext} - /> + // @ts-ignore + <Markdown msg={text} theme={theme} style={[isContext && { color: themes[theme].auxiliaryText }]} preview={isContext} /> ); } - button(element, context) { - const { - text, value, actionId, style - } = element; - const [{ loading }, action] = useBlockContext(element, context); + button(element: any, context: any) { + const { text, value, actionId, style } = element; + const [{ loading }, action]: any = useBlockContext(element, context); const { theme } = useContext(ThemeContext); return ( <Button @@ -87,40 +73,30 @@ class MessageParser extends UiKitParserMessage { } divider() { - const { theme } = useContext(ThemeContext); + const { theme }: any = useContext(ThemeContext); + // @ts-ignore return <Divider theme={theme} />; } - section(args) { + section(args: any) { const { theme } = useContext(ThemeContext); return <Section {...args} theme={theme} parser={this} />; } - actions(args) { + actions(args: any) { const { theme } = useContext(ThemeContext); return <Actions {...args} theme={theme} parser={this} />; } - overflow(element, context) { - const [{ loading }, action] = useBlockContext(element, context); - const { theme } = useContext(ThemeContext); - return ( - <Overflow - element={element} - context={context} - loading={loading} - action={action} - theme={theme} - parser={this} - /> - ); + overflow(element: any, context: any) { + const [{ loading }, action]: any = useBlockContext(element, context); + const { theme }: any = useContext(ThemeContext); + return <Overflow element={element} context={context} loading={loading} action={action} theme={theme} parser={this} />; } - datePicker(element, context) { - const [{ - loading, value, error, language - }, action] = useBlockContext(element, context); - const { theme } = useContext(ThemeContext); + datePicker(element: any, context: any) { + const [{ loading, value, error, language }, action]: any = useBlockContext(element, context); + const { theme }: any = useContext(ThemeContext); return ( <DatePicker element={element} @@ -135,75 +111,48 @@ class MessageParser extends UiKitParserMessage { ); } - image(element, context) { - const { theme } = useContext(ThemeContext); + image(element: any, context: any) { + const { theme }: any = useContext(ThemeContext); return <Image element={element} theme={theme} context={context} />; } - context(args) { + context(args: any) { const { theme } = useContext(ThemeContext); return <Context {...args} theme={theme} parser={this} />; } - multiStaticSelect(element, context) { - const [{ loading, value }, action] = useBlockContext(element, context); + multiStaticSelect(element: any, context: any) { + const [{ loading, value }, action]: any = useBlockContext(element, context); const { theme } = useContext(ThemeContext); return ( - <MultiSelect - {...element} - theme={theme} - value={value} - onChange={action} - context={context} - loading={loading} - multiselect - /> + <MultiSelect {...element} theme={theme} value={value} onChange={action} context={context} loading={loading} multiselect /> ); } - staticSelect(element, context) { - const [{ loading, value }, action] = useBlockContext(element, context); + staticSelect(element: any, context: any) { + const [{ loading, value }, action]: any = useBlockContext(element, context); const { theme } = useContext(ThemeContext); - return ( - <Select - {...element} - theme={theme} - value={value} - onChange={action} - loading={loading} - /> - ); + return <Select {...element} theme={theme} value={value} onChange={action} loading={loading} />; } - selectInput(element, context) { - const [{ loading, value }, action] = useBlockContext(element, context); + selectInput(element: any, context: any) { + const [{ loading, value }, action]: any = useBlockContext(element, context); const { theme } = useContext(ThemeContext); - return ( - <MultiSelect - {...element} - theme={theme} - value={value} - onChange={action} - context={context} - loading={loading} - /> - ); + return <MultiSelect {...element} theme={theme} value={value} onChange={action} context={context} loading={loading} />; } } class ModalParser extends UiKitParserModal { constructor() { super(); - Object.getOwnPropertyNames(MessageParser.prototype).forEach((method) => { + Object.getOwnPropertyNames(MessageParser.prototype).forEach(method => { ModalParser.prototype[method] = ModalParser.prototype[method] || MessageParser.prototype[method]; }); } - input({ - element, blockId, appId, label, description, hint - }, context) { - const [{ error }] = useBlockContext({ ...element, appId, blockId }, context); - const { theme } = useContext(ThemeContext); + input({ element, blockId, appId, label, description, hint }: any, context: any) { + const [{ error }]: any = useBlockContext({ ...element, appId, blockId }, context); + const { theme }: any = useContext(ThemeContext); return ( <Input parser={this} @@ -217,26 +166,28 @@ class ModalParser extends UiKitParserModal { ); } - image(element, context) { - const { theme } = useContext(ThemeContext); + image(element: any, context: any) { + const { theme }: any = useContext(ThemeContext); return <Image element={element} theme={theme} context={context} />; } - plainInput(element, context) { - const [{ loading, value, error }, action] = useBlockContext(element, context); + plainInput(element: any, context: any) { + const [{ loading, value, error }, action]: any = useBlockContext(element, context); const { theme } = useContext(ThemeContext); const { multiline, actionId, placeholder } = element; return ( + // @ts-ignore <TextInput id={actionId} placeholder={plainText(placeholder)} onInput={action} multiline={multiline} loading={loading} - onChangeText={text => action({ value: text })} + onChangeText={(text: any) => action({ value: text })} inputStyle={multiline && styles.multiline} containerStyle={styles.input} value={value} + // @ts-ignore error={{ error }} theme={theme} /> @@ -250,4 +201,4 @@ export const modalParser = new ModalParser(); export const UiKitMessage = uiKitMessage(messageParser); export const UiKitModal = uiKitModal(modalParser); -export const UiKitComponent = ({ render, blocks }) => render(blocks); +export const UiKitComponent = ({ render, blocks }: any) => render(blocks); diff --git a/app/containers/UIKit/utils.js b/app/containers/UIKit/utils.js deleted file mode 100644 index 80b23399a..000000000 --- a/app/containers/UIKit/utils.js +++ /dev/null @@ -1,63 +0,0 @@ -/* eslint-disable no-shadow */ -import React, { useContext, useState } from 'react'; -import { BLOCK_CONTEXT } from '@rocket.chat/ui-kit'; - -export const textParser = ([{ text }]) => text; - -export const defaultContext = { - action: (...args) => console.log(args), - state: console.log, - appId: '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz', - errors: {} -}; - -export const KitContext = React.createContext(defaultContext); - -export const useBlockContext = ({ - blockId, actionId, appId, initialValue -}, context) => { - const { - action, appId: appIdFromContext, viewId, state, language, errors, values = {} - } = useContext(KitContext); - const { value = initialValue } = values[actionId] || {}; - const [loading, setLoading] = useState(false); - - const error = errors && actionId && errors[actionId]; - - if ([BLOCK_CONTEXT.SECTION, BLOCK_CONTEXT.ACTION].includes(context)) { - return [{ - loading, setLoading, error, value, language - }, async({ value }) => { - setLoading(true); - try { - await action({ - blockId, - appId: appId || appIdFromContext, - actionId, - value, - viewId - }); - } catch (e) { - // do nothing - } - setLoading(false); - }]; - } - - return [{ - loading, setLoading, value, error, language - }, async({ value }) => { - setLoading(true); - try { - await state({ - blockId, - appId, - actionId, - value - }); - } catch (e) { - // do nothing - } - setLoading(false); - }]; -}; diff --git a/app/containers/UIKit/utils.ts b/app/containers/UIKit/utils.ts new file mode 100644 index 000000000..b64aea9cc --- /dev/null +++ b/app/containers/UIKit/utils.ts @@ -0,0 +1,73 @@ +/* eslint-disable no-shadow */ +import React, { useContext, useState } from 'react'; +import { BLOCK_CONTEXT } from '@rocket.chat/ui-kit'; + +export const textParser = ([{ text }]: any) => text; + +export const defaultContext: any = { + action: (...args: any) => console.log(args), + state: console.log, + appId: '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz', + errors: {} +}; + +export const KitContext = React.createContext(defaultContext); + +export const useBlockContext = ({ blockId, actionId, appId, initialValue }: any, context: any) => { + const { action, appId: appIdFromContext, viewId, state, language, errors, values = {} } = useContext(KitContext); + const { value = initialValue } = values[actionId] || {}; + const [loading, setLoading] = useState(false); + + const error = errors && actionId && errors[actionId]; + + if ([BLOCK_CONTEXT.SECTION, BLOCK_CONTEXT.ACTION].includes(context)) { + return [ + { + loading, + setLoading, + error, + value, + language + }, + async ({ value }: any) => { + setLoading(true); + try { + await action({ + blockId, + appId: appId || appIdFromContext, + actionId, + value, + viewId + }); + } catch (e) { + // do nothing + } + setLoading(false); + } + ]; + } + + return [ + { + loading, + setLoading, + value, + error, + language + }, + async ({ value }: any) => { + setLoading(true); + try { + await state({ + blockId, + appId, + actionId, + value + }); + } catch (e) { + // do nothing + } + setLoading(false); + } + ]; +}; diff --git a/app/containers/markdown/AtMention.js b/app/containers/markdown/AtMention.tsx similarity index 52% rename from app/containers/markdown/AtMention.js rename to app/containers/markdown/AtMention.tsx index 06be56806..ab6354994 100644 --- a/app/containers/markdown/AtMention.js +++ b/app/containers/markdown/AtMention.tsx @@ -1,15 +1,21 @@ import React from 'react'; -import PropTypes from 'prop-types'; import { Text } from 'react-native'; import { themes } from '../../constants/colors'; - import styles from './styles'; -import { logEvent, events } from '../../utils/log'; +import { events, logEvent } from '../../utils/log'; -const AtMention = React.memo(({ - mention, mentions, username, navToRoomInfo, style = [], useRealName, theme -}) => { +interface IAtMention { + mention: string; + username: string; + navToRoomInfo: Function; + style: any; + useRealName: boolean; + theme: string; + mentions: any; +} + +const AtMention = React.memo(({ mention, mentions, username, navToRoomInfo, style = [], useRealName, theme }: IAtMention) => { if (mention === 'all' || mention === 'here') { return ( <Text @@ -19,8 +25,8 @@ const AtMention = React.memo(({ color: themes[theme].mentionGroupColor }, ...style - ]} - >{mention} + ]}> + {mention} </Text> ); } @@ -36,7 +42,7 @@ const AtMention = React.memo(({ }; } - const user = mentions?.find?.(m => m && m.username === mention); + const user = mentions?.find?.((m: any) => m && m.username === mention); const handlePress = () => { logEvent(events.ROOM_MENTION_GO_USER_INFO); @@ -49,30 +55,13 @@ const AtMention = React.memo(({ if (user) { return ( - <Text - style={[styles.mention, mentionStyle, ...style]} - onPress={handlePress} - > + <Text style={[styles.mention, mentionStyle, ...style]} onPress={handlePress}> {useRealName && user.name ? user.name : user.username} </Text> ); } - return ( - <Text style={[styles.text, { color: themes[theme].bodyText }, ...style]}> - {`@${ mention }`} - </Text> - ); + return <Text style={[styles.text, { color: themes[theme].bodyText }, ...style]}>{`@${mention}`}</Text>; }); -AtMention.propTypes = { - mention: PropTypes.string, - username: PropTypes.string, - navToRoomInfo: PropTypes.func, - style: PropTypes.array, - useRealName: PropTypes.bool, - theme: PropTypes.string, - mentions: PropTypes.oneOfType([PropTypes.array, PropTypes.object]) -}; - export default AtMention; diff --git a/app/containers/markdown/BlockQuote.js b/app/containers/markdown/BlockQuote.tsx similarity index 54% rename from app/containers/markdown/BlockQuote.js rename to app/containers/markdown/BlockQuote.tsx index 98c929144..e2da2c1fa 100644 --- a/app/containers/markdown/BlockQuote.js +++ b/app/containers/markdown/BlockQuote.tsx @@ -1,23 +1,19 @@ import React from 'react'; -import PropTypes from 'prop-types'; import { View } from 'react-native'; import { themes } from '../../constants/colors'; - import styles from './styles'; -const BlockQuote = React.memo(({ children, theme }) => ( +interface IBlockQuote { + children: JSX.Element; + theme: string; +} + +const BlockQuote = React.memo(({ children, theme }: IBlockQuote) => ( <View style={styles.container}> <View style={[styles.quote, { backgroundColor: themes[theme].borderColor }]} /> - <View style={styles.childContainer}> - {children} - </View> + <View style={styles.childContainer}>{children}</View> </View> )); -BlockQuote.propTypes = { - children: PropTypes.node.isRequired, - theme: PropTypes.string -}; - export default BlockQuote; diff --git a/app/containers/markdown/Emoji.js b/app/containers/markdown/Emoji.js deleted file mode 100644 index 800db1735..000000000 --- a/app/containers/markdown/Emoji.js +++ /dev/null @@ -1,51 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import { Text } from 'react-native'; - -import shortnameToUnicode from '../../utils/shortnameToUnicode'; -import CustomEmoji from '../EmojiPicker/CustomEmoji'; -import { themes } from '../../constants/colors'; - -import styles from './styles'; - -const Emoji = React.memo(({ - literal, isMessageContainsOnlyEmoji, getCustomEmoji, baseUrl, customEmojis = true, style = {}, theme -}) => { - const emojiUnicode = shortnameToUnicode(literal); - const emoji = getCustomEmoji && getCustomEmoji(literal.replace(/:/g, '')); - if (emoji && customEmojis) { - return ( - <CustomEmoji - baseUrl={baseUrl} - style={[ - isMessageContainsOnlyEmoji ? styles.customEmojiBig : styles.customEmoji, - style - ]} - emoji={emoji} - /> - ); - } - return ( - <Text - style={[ - { color: themes[theme].bodyText }, - isMessageContainsOnlyEmoji ? styles.textBig : styles.text, - style - ]} - > - {emojiUnicode} - </Text> - ); -}); - -Emoji.propTypes = { - literal: PropTypes.string, - isMessageContainsOnlyEmoji: PropTypes.bool, - getCustomEmoji: PropTypes.func, - baseUrl: PropTypes.string, - customEmojis: PropTypes.bool, - style: PropTypes.object, - theme: PropTypes.string -}; - -export default Emoji; diff --git a/app/containers/markdown/Emoji.tsx b/app/containers/markdown/Emoji.tsx new file mode 100644 index 000000000..bb348268a --- /dev/null +++ b/app/containers/markdown/Emoji.tsx @@ -0,0 +1,42 @@ +import React from 'react'; +import { Text } from 'react-native'; + +import shortnameToUnicode from '../../utils/shortnameToUnicode'; +import CustomEmoji from '../EmojiPicker/CustomEmoji'; +import { themes } from '../../constants/colors'; +import styles from './styles'; + +interface IEmoji { + literal: string; + isMessageContainsOnlyEmoji: boolean; + getCustomEmoji?: Function; + baseUrl: string; + customEmojis?: any; + style: object; + theme?: string; + onEmojiSelected?: Function; + tabEmojiStyle?: object; +} + +const Emoji = React.memo( + ({ literal, isMessageContainsOnlyEmoji, getCustomEmoji, baseUrl, customEmojis = true, style = {}, theme }: IEmoji) => { + const emojiUnicode = shortnameToUnicode(literal); + const emoji: any = getCustomEmoji && getCustomEmoji(literal.replace(/:/g, '')); + if (emoji && customEmojis) { + return ( + <CustomEmoji + baseUrl={baseUrl} + style={[isMessageContainsOnlyEmoji ? styles.customEmojiBig : styles.customEmoji, style]} + emoji={emoji} + /> + ); + } + return ( + <Text style={[{ color: themes[theme!].bodyText }, isMessageContainsOnlyEmoji ? styles.textBig : styles.text, style]}> + {emojiUnicode} + </Text> + ); + } +); + +export default Emoji; diff --git a/app/containers/markdown/Hashtag.js b/app/containers/markdown/Hashtag.tsx similarity index 53% rename from app/containers/markdown/Hashtag.js rename to app/containers/markdown/Hashtag.tsx index 8d9672e40..872b8782a 100644 --- a/app/containers/markdown/Hashtag.js +++ b/app/containers/markdown/Hashtag.tsx @@ -1,14 +1,21 @@ -import PropTypes from 'prop-types'; import React from 'react'; import { Text } from 'react-native'; import { themes } from '../../constants/colors'; - import styles from './styles'; -const Hashtag = React.memo(({ - hashtag, channels, navToRoomInfo, style = [], theme -}) => { +interface IHashtag { + hashtag: string; + navToRoomInfo: Function; + style: []; + theme: string; + channels: { + name: string; + _id: number; + }[]; +} + +const Hashtag = React.memo(({ hashtag, channels, navToRoomInfo, style = [], theme }: IHashtag) => { const handlePress = () => { const index = channels.findIndex(channel => channel.name === hashtag); const navParam = { @@ -26,26 +33,14 @@ const Hashtag = React.memo(({ { color: themes[theme].mentionOtherColor }, - ...style]} - onPress={handlePress} - > - {`#${ hashtag }`} + ...style + ]} + onPress={handlePress}> + {`#${hashtag}`} </Text> ); } - return ( - <Text style={[styles.text, { color: themes[theme].bodyText }, ...style]}> - {`#${ hashtag }`} - </Text> - ); + return <Text style={[styles.text, { color: themes[theme].bodyText }, ...style]}>{`#${hashtag}`}</Text>; }); -Hashtag.propTypes = { - hashtag: PropTypes.string, - navToRoomInfo: PropTypes.func, - style: PropTypes.array, - theme: PropTypes.string, - channels: PropTypes.oneOfType([PropTypes.array, PropTypes.object]) -}; - export default Hashtag; diff --git a/app/containers/markdown/Link.js b/app/containers/markdown/Link.tsx similarity index 61% rename from app/containers/markdown/Link.js rename to app/containers/markdown/Link.tsx index 615b66128..d2aca85f6 100644 --- a/app/containers/markdown/Link.js +++ b/app/containers/markdown/Link.tsx @@ -1,6 +1,5 @@ import React from 'react'; -import PropTypes from 'prop-types'; -import { Text, Clipboard } from 'react-native'; +import { Clipboard, Text } from 'react-native'; import styles from './styles'; import { themes } from '../../constants/colors'; @@ -9,9 +8,14 @@ import EventEmitter from '../../utils/events'; import I18n from '../../i18n'; import openLink from '../../utils/openLink'; -const Link = React.memo(({ - children, link, theme, onLinkPress -}) => { +interface ILink { + children: JSX.Element; + link: string; + theme: string; + onLinkPress: Function; +} + +const Link = React.memo(({ children, link, theme, onLinkPress }: ILink) => { const handlePress = () => { if (!link) { return; @@ -30,21 +34,10 @@ const Link = React.memo(({ // if you have a [](https://rocket.chat) render https://rocket.chat return ( - <Text - onPress={handlePress} - onLongPress={onLongPress} - style={{ ...styles.link, color: themes[theme].actionTintColor }} - > - { childLength !== 0 ? children : link } + <Text onPress={handlePress} onLongPress={onLongPress} style={{ ...styles.link, color: themes[theme].actionTintColor }}> + {childLength !== 0 ? children : link} </Text> ); }); -Link.propTypes = { - children: PropTypes.node, - link: PropTypes.string, - theme: PropTypes.string, - onLinkPress: PropTypes.func -}; - export default Link; diff --git a/app/containers/markdown/List.js b/app/containers/markdown/List.js deleted file mode 100644 index f934a878e..000000000 --- a/app/containers/markdown/List.js +++ /dev/null @@ -1,46 +0,0 @@ -import PropTypes from 'prop-types'; -import React from 'react'; - -const List = React.memo(({ - children, ordered, start, tight, numberOfLines = 0 -}) => { - let bulletWidth = 15; - - if (ordered) { - const lastNumber = (start + children.length) - 1; - bulletWidth = (9 * lastNumber.toString().length) + 7; - } - - let items = React.Children.toArray(children); - - if (numberOfLines) { - items = items.slice(0, numberOfLines); - } - - const _children = items.map((child, index) => React.cloneElement(child, { - bulletWidth, - ordered, - tight, - index: start + index - })); - - return ( - <> - {_children} - </> - ); -}); - -List.propTypes = { - children: PropTypes.node, - ordered: PropTypes.bool, - start: PropTypes.number, - tight: PropTypes.bool, - numberOfLines: PropTypes.number -}; - -List.defaultProps = { - start: 1 -}; - -export default List; diff --git a/app/containers/markdown/List.tsx b/app/containers/markdown/List.tsx new file mode 100644 index 000000000..70a79418b --- /dev/null +++ b/app/containers/markdown/List.tsx @@ -0,0 +1,38 @@ +import React from 'react'; + +interface IList { + children: JSX.Element; + ordered: boolean; + start: number; + tight: boolean; + numberOfLines: number; +} + +const List = React.memo(({ children, ordered, tight, start = 1, numberOfLines = 0 }: IList) => { + let bulletWidth = 15; + + if (ordered) { + // @ts-ignore + const lastNumber = start + children.length - 1; + bulletWidth = 9 * lastNumber.toString().length + 7; + } + + let items = React.Children.toArray(children); + + if (numberOfLines) { + items = items.slice(0, numberOfLines); + } + + const _children = items.map((child: any, index: number) => + React.cloneElement(child, { + bulletWidth, + ordered, + tight, + index: start + index + }) + ); + + return <>{_children}</>; +}); + +export default List; diff --git a/app/containers/markdown/ListItem.js b/app/containers/markdown/ListItem.js deleted file mode 100644 index 9b14569e0..000000000 --- a/app/containers/markdown/ListItem.js +++ /dev/null @@ -1,63 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import { - StyleSheet, - Text, - View -} from 'react-native'; - -import { themes } from '../../constants/colors'; - -const style = StyleSheet.create({ - container: { - flexDirection: 'row', - alignItems: 'flex-start' - }, - bullet: { - alignItems: 'flex-end', - marginRight: 5 - }, - contents: { - flex: 1 - } -}); - -const ListItem = React.memo(({ - children, level, bulletWidth, continue: _continue, ordered, index, theme -}) => { - let bullet; - if (_continue) { - bullet = ''; - } else if (ordered) { - bullet = `${ index }.`; - } else if (level % 2 === 0) { - bullet = '◦'; - } else { - bullet = '•'; - } - - return ( - <View style={style.container}> - <View style={[{ width: bulletWidth }, style.bullet]}> - <Text style={{ color: themes[theme].bodyText }}> - {bullet} - </Text> - </View> - <View style={style.contents}> - {children} - </View> - </View> - ); -}); - -ListItem.propTypes = { - children: PropTypes.node, - bulletWidth: PropTypes.number, - level: PropTypes.number, - ordered: PropTypes.bool, - continue: PropTypes.bool, - theme: PropTypes.string, - index: PropTypes.number -}; - -export default ListItem; diff --git a/app/containers/markdown/ListItem.tsx b/app/containers/markdown/ListItem.tsx new file mode 100644 index 000000000..0e323df77 --- /dev/null +++ b/app/containers/markdown/ListItem.tsx @@ -0,0 +1,52 @@ +import React from 'react'; +import { StyleSheet, Text, View } from 'react-native'; + +import { themes } from '../../constants/colors'; + +const style = StyleSheet.create({ + container: { + flexDirection: 'row', + alignItems: 'flex-start' + }, + bullet: { + alignItems: 'flex-end', + marginRight: 5 + }, + contents: { + flex: 1 + } +}); + +interface IListItem { + children: JSX.Element; + bulletWidth: number; + level: number; + ordered: boolean; + continue: boolean; + theme: string; + index: number; +} + +const ListItem = React.memo(({ children, level, bulletWidth, continue: _continue, ordered, index, theme }: IListItem) => { + let bullet; + if (_continue) { + bullet = ''; + } else if (ordered) { + bullet = `${index}.`; + } else if (level % 2 === 0) { + bullet = '◦'; + } else { + bullet = '•'; + } + + return ( + <View style={style.container}> + <View style={[{ width: bulletWidth }, style.bullet]}> + <Text style={{ color: themes[theme].bodyText }}>{bullet}</Text> + </View> + <View style={style.contents}>{children}</View> + </View> + ); +}); + +export default ListItem; diff --git a/app/containers/markdown/Table.js b/app/containers/markdown/Table.tsx similarity index 66% rename from app/containers/markdown/Table.js rename to app/containers/markdown/Table.tsx index b1bb5a309..f7c742b6e 100644 --- a/app/containers/markdown/Table.js +++ b/app/containers/markdown/Table.tsx @@ -1,11 +1,5 @@ -import { PropTypes } from 'prop-types'; import React from 'react'; -import { - ScrollView, - TouchableOpacity, - View, - Text -} from 'react-native'; +import { ScrollView, Text, TouchableOpacity, View } from 'react-native'; import { CELL_WIDTH } from './TableCell'; import styles from './styles'; @@ -13,11 +7,15 @@ import Navigation from '../../lib/Navigation'; import I18n from '../../i18n'; import { themes } from '../../constants/colors'; +interface ITable { + children: JSX.Element; + numColumns: number; + theme: string; +} + const MAX_HEIGHT = 300; -const Table = React.memo(({ - children, numColumns, theme -}) => { +const Table = React.memo(({ children, numColumns, theme }: ITable) => { const getTableWidth = () => numColumns * CELL_WIDTH; const renderRows = (drawExtraBorders = true) => { @@ -26,16 +24,12 @@ const Table = React.memo(({ tableStyle.push(styles.tableExtraBorders); } - const rows = React.Children.toArray(children); + const rows: any = React.Children.toArray(children); rows[rows.length - 1] = React.cloneElement(rows[rows.length - 1], { isLastRow: true }); - return ( - <View style={tableStyle}> - {rows} - </View> - ); + return <View style={tableStyle}>{rows}</View>; }; const onPress = () => Navigation.navigate('MarkdownTableView', { renderRows, tableWidth: getTableWidth() }); @@ -46,8 +40,10 @@ const Table = React.memo(({ contentContainerStyle={{ width: getTableWidth() }} scrollEnabled={false} showsVerticalScrollIndicator={false} - style={[styles.containerTable, { maxWidth: getTableWidth(), maxHeight: MAX_HEIGHT, borderColor: themes[theme].borderColor }]} - > + style={[ + styles.containerTable, + { maxWidth: getTableWidth(), maxHeight: MAX_HEIGHT, borderColor: themes[theme].borderColor } + ]}> {renderRows(false)} </ScrollView> <Text style={[styles.textInfo, { color: themes[theme].auxiliaryText }]}>{I18n.t('Full_table')}</Text> @@ -55,10 +51,4 @@ const Table = React.memo(({ ); }); -Table.propTypes = { - children: PropTypes.node.isRequired, - numColumns: PropTypes.number.isRequired, - theme: PropTypes.string -}; - export default Table; diff --git a/app/containers/markdown/TableCell.js b/app/containers/markdown/TableCell.tsx similarity index 68% rename from app/containers/markdown/TableCell.js rename to app/containers/markdown/TableCell.tsx index 68c1e2f56..09e5c4fc3 100644 --- a/app/containers/markdown/TableCell.js +++ b/app/containers/markdown/TableCell.tsx @@ -1,16 +1,19 @@ -import PropTypes from 'prop-types'; import React from 'react'; import { Text, View } from 'react-native'; import { themes } from '../../constants/colors'; - import styles from './styles'; +interface ITableCell { + align: '' | 'left' | 'center' | 'right'; + children: JSX.Element; + isLastCell: boolean; + theme: string; +} + export const CELL_WIDTH = 100; -const TableCell = React.memo(({ - isLastCell, align, children, theme -}) => { +const TableCell = React.memo(({ isLastCell, align, children, theme }: ITableCell) => { const cellStyle = [styles.cell, { borderColor: themes[theme].borderColor }]; if (!isLastCell) { cellStyle.push(styles.cellRightBorder); @@ -25,18 +28,9 @@ const TableCell = React.memo(({ return ( <View style={[...cellStyle, { width: CELL_WIDTH }]}> - <Text style={[textStyle, { color: themes[theme].bodyText }]}> - {children} - </Text> + <Text style={[textStyle, { color: themes[theme].bodyText }]}>{children}</Text> </View> ); }); -TableCell.propTypes = { - align: PropTypes.oneOf(['', 'left', 'center', 'right']), - children: PropTypes.node, - isLastCell: PropTypes.bool, - theme: PropTypes.string -}; - export default TableCell; diff --git a/app/containers/markdown/TableRow.js b/app/containers/markdown/TableRow.tsx similarity index 63% rename from app/containers/markdown/TableRow.js rename to app/containers/markdown/TableRow.tsx index e1d0c54ee..730c0b71e 100644 --- a/app/containers/markdown/TableRow.js +++ b/app/containers/markdown/TableRow.tsx @@ -1,20 +1,22 @@ -import PropTypes from 'prop-types'; import React from 'react'; import { View } from 'react-native'; import { themes } from '../../constants/colors'; - import styles from './styles'; -const TableRow = React.memo(({ - isLastRow, children: _children, theme -}) => { +interface ITableRow { + children: JSX.Element; + isLastRow: boolean; + theme: string; +} + +const TableRow = React.memo(({ isLastRow, children: _children, theme }: ITableRow) => { const rowStyle = [styles.row, { borderColor: themes[theme].borderColor }]; if (!isLastRow) { rowStyle.push(styles.rowBottomBorder); } - const children = React.Children.toArray(_children); + const children: any = React.Children.toArray(_children); children[children.length - 1] = React.cloneElement(children[children.length - 1], { isLastCell: true }); @@ -22,10 +24,4 @@ const TableRow = React.memo(({ return <View style={rowStyle}>{children}</View>; }); -TableRow.propTypes = { - children: PropTypes.node, - isLastRow: PropTypes.bool, - theme: PropTypes.string -}; - export default TableRow; diff --git a/app/containers/markdown/index.js b/app/containers/markdown/index.tsx similarity index 53% rename from app/containers/markdown/index.js rename to app/containers/markdown/index.tsx index bc2fdba73..d3ce8453e 100644 --- a/app/containers/markdown/index.js +++ b/app/containers/markdown/index.tsx @@ -1,14 +1,12 @@ import React, { PureComponent } from 'react'; -import { Text, Image } from 'react-native'; -import { Parser, Node } from 'commonmark'; +import { Image, Text } from 'react-native'; +import { Node, Parser } 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'; import { themes } from '../../constants/colors'; - import MarkdownLink from './Link'; import MarkdownList from './List'; import MarkdownListItem from './ListItem'; @@ -20,15 +18,42 @@ import MarkdownTable from './Table'; import MarkdownTableRow from './TableRow'; import MarkdownTableCell from './TableCell'; import mergeTextNodes from './mergeTextNodes'; - import styles from './styles'; import { isValidURL } from '../../utils/url'; +interface IMarkdownProps { + msg: string; + getCustomEmoji: Function; + baseUrl: string; + username: string; + tmid: string; + isEdited: boolean; + numberOfLines: number; + customEmojis: boolean; + useRealName: boolean; + channels: { + name: string; + _id: number; + }[]; + mentions: object[]; + navToRoomInfo: Function; + preview: boolean; + theme: string; + testID: string; + style: any; + onLinkPress: Function; +} + +type TLiteral = { + literal: string; +}; + // Support <http://link|Text> -const formatText = text => text.replace( - new RegExp('(?:<|<)((?:https|http):\\/\\/[^\\|]+)\\|(.+?)(?=>|>)(?:>|>)', 'gm'), - (match, url, title) => `[${ title }](${ url })` -); +const formatText = (text: string) => + text.replace( + new RegExp('(?:<|<)((?:https|http):\\/\\/[^\\|]+)\\|(.+?)(?=>|>)(?:>|>)', 'gm'), + (match, url, title) => `[${title}](${url})` + ); const emojiRanges = [ '\u00a9|\u00ae|[\u2000-\u3300]|\ud83c[\ud000-\udfff]|\ud83d[\ud000-\udfff]|\ud83e[\ud000-\udfff]', // unicode emoji from https://www.regextester.com/106421 @@ -36,18 +61,18 @@ const emojiRanges = [ ' |\n' // allow spaces and line breaks ].join('|'); -const removeSpaces = str => str && str.replace(/\s/g, ''); +const removeSpaces = (str: string) => str && str.replace(/\s/g, ''); -const removeAllEmoji = str => str.replace(new RegExp(emojiRanges, 'g'), ''); +const removeAllEmoji = (str: string) => str.replace(new RegExp(emojiRanges, 'g'), ''); -const isOnlyEmoji = (str) => { +const isOnlyEmoji = (str: string) => { str = removeSpaces(str); return !removeAllEmoji(str).length; }; -const removeOneEmoji = str => str.replace(new RegExp(emojiRanges), ''); +const removeOneEmoji = (str: string) => str.replace(new RegExp(emojiRanges), ''); -const emojiCount = (str) => { +const emojiCount = (str: string) => { str = removeSpaces(str); let oldLength = 0; let counter = 0; @@ -65,71 +90,56 @@ const emojiCount = (str) => { const parser = new Parser(); -class Markdown extends PureComponent { - static propTypes = { - msg: PropTypes.string, - getCustomEmoji: PropTypes.func, - baseUrl: PropTypes.string, - username: PropTypes.string, - tmid: PropTypes.string, - isEdited: PropTypes.bool, - numberOfLines: PropTypes.number, - 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, - onLinkPress: PropTypes.func - }; +class Markdown extends PureComponent<IMarkdownProps, any> { + private renderer: any; - constructor(props) { + private isMessageContainsOnlyEmoji!: boolean; + + constructor(props: IMarkdownProps) { super(props); this.renderer = this.createRenderer(); } - createRenderer = () => new Renderer({ - renderers: { - text: this.renderText, + createRenderer = () => + new Renderer({ + renderers: { + text: this.renderText, - emph: Renderer.forwardChildren, - strong: Renderer.forwardChildren, - del: Renderer.forwardChildren, - code: this.renderCodeInline, - link: this.renderLink, - image: this.renderImage, - atMention: this.renderAtMention, - emoji: this.renderEmoji, - hashtag: this.renderHashtag, + emph: Renderer.forwardChildren, + strong: Renderer.forwardChildren, + del: Renderer.forwardChildren, + code: this.renderCodeInline, + link: this.renderLink, + image: this.renderImage, + atMention: this.renderAtMention, + emoji: this.renderEmoji, + hashtag: this.renderHashtag, - paragraph: this.renderParagraph, - heading: this.renderHeading, - codeBlock: this.renderCodeBlock, - blockQuote: this.renderBlockQuote, + paragraph: this.renderParagraph, + heading: this.renderHeading, + codeBlock: this.renderCodeBlock, + blockQuote: this.renderBlockQuote, - list: this.renderList, - item: this.renderListItem, + list: this.renderList, + item: this.renderListItem, - hardBreak: this.renderBreak, - thematicBreak: this.renderBreak, - softBreak: this.renderBreak, + hardBreak: this.renderBreak, + thematicBreak: this.renderBreak, + softBreak: this.renderBreak, - htmlBlock: this.renderText, - htmlInline: this.renderText, + htmlBlock: this.renderText, + htmlInline: this.renderText, - table: this.renderTable, - table_row: this.renderTableRow, - table_cell: this.renderTableCell, + table: this.renderTable, + table_row: this.renderTableRow, + table_cell: this.renderTableCell, - editedIndicator: this.renderEditedIndicator - }, - renderParagraphsInLists: true - }); + editedIndicator: this.renderEditedIndicator + }, + renderParagraphsInLists: true + }); - editedMessage = (ast) => { + editedMessage = (ast: any) => { const { isEdited } = this.props; if (isEdited) { const editIndicatorNode = new Node('edited_indicator'); @@ -144,26 +154,17 @@ class Markdown extends PureComponent { } }; - renderText = ({ context, literal }) => { - const { - numberOfLines, style = [] - } = this.props; - const defaultStyle = [ - this.isMessageContainsOnlyEmoji ? styles.textBig : {}, - ...context.map(type => styles[type]) - ]; + renderText = ({ context, literal }: { context: []; literal: string }) => { + const { numberOfLines, style = [] } = this.props; + const defaultStyle = [this.isMessageContainsOnlyEmoji ? styles.textBig : {}, ...context.map(type => styles[type])]; return ( - <Text - accessibilityLabel={literal} - style={[styles.text, defaultStyle, ...style]} - numberOfLines={numberOfLines} - > + <Text accessibilityLabel={literal} style={[styles.text, defaultStyle, ...style]} numberOfLines={numberOfLines}> {literal} </Text> ); - } + }; - renderCodeInline = ({ literal }) => { + renderCodeInline = ({ literal }: TLiteral) => { const { theme, style = [] } = this.props; return ( <Text @@ -175,14 +176,13 @@ class Markdown extends PureComponent { borderColor: themes[theme].bannerBackground }, ...style - ]} - > + ]}> {literal} </Text> ); }; - renderCodeBlock = ({ literal }) => { + renderCodeBlock = ({ literal }: TLiteral) => { const { theme, style = [] } = this.props; return ( <Text @@ -194,8 +194,7 @@ class Markdown extends PureComponent { borderColor: themes[theme].bannerBackground }, ...style - ]} - > + ]}> {literal} </Text> ); @@ -204,9 +203,9 @@ class Markdown extends PureComponent { renderBreak = () => { const { tmid } = this.props; return <Text>{tmid ? ' ' : '\n'}</Text>; - } + }; - renderParagraph = ({ children }) => { + renderParagraph = ({ children }: any) => { const { numberOfLines, style, theme } = this.props; if (!children || children.length === 0) { return null; @@ -218,38 +217,22 @@ class Markdown extends PureComponent { ); }; - renderLink = ({ children, href }) => { + renderLink = ({ children, href }: any) => { const { theme, onLinkPress } = this.props; return ( - <MarkdownLink - link={href} - theme={theme} - onLinkPress={onLinkPress} - > + <MarkdownLink link={href} theme={theme} onLinkPress={onLinkPress}> {children} </MarkdownLink> ); - } + }; - renderHashtag = ({ hashtag }) => { - const { - channels, navToRoomInfo, style, theme - } = this.props; - return ( - <MarkdownHashtag - hashtag={hashtag} - channels={channels} - navToRoomInfo={navToRoomInfo} - theme={theme} - style={style} - /> - ); - } + renderHashtag = ({ hashtag }: { hashtag: string }) => { + const { channels, navToRoomInfo, style, theme } = this.props; + return <MarkdownHashtag hashtag={hashtag} channels={channels} navToRoomInfo={navToRoomInfo} theme={theme} style={style} />; + }; - renderAtMention = ({ mentionName }) => { - const { - username, mentions, navToRoomInfo, useRealName, style, theme - } = this.props; + renderAtMention = ({ mentionName }: { mentionName: string }) => { + const { username, mentions, navToRoomInfo, useRealName, style, theme } = this.props; return ( <MarkdownAtMention mentions={mentions} @@ -261,12 +244,10 @@ class Markdown extends PureComponent { style={style} /> ); - } + }; - renderEmoji = ({ literal }) => { - const { - getCustomEmoji, baseUrl, customEmojis, style, theme - } = this.props; + renderEmoji = ({ literal }: TLiteral) => { + const { getCustomEmoji, baseUrl, customEmojis, style, theme } = this.props; return ( <MarkdownEmoji literal={literal} @@ -278,29 +259,24 @@ class Markdown extends PureComponent { theme={theme} /> ); - } + }; - renderImage = ({ src }) => { + renderImage = ({ src }: { src: string }) => { if (!isValidURL(src)) { return null; } - return ( - <Image - style={styles.inlineImage} - source={{ uri: encodeURI(src) }} - /> - ); - } + return <Image style={styles.inlineImage} source={{ uri: encodeURI(src) }} />; + }; renderEditedIndicator = () => { const { theme } = this.props; return <Text style={[styles.edited, { color: themes[theme].auxiliaryText }]}> ({I18n.t('edited')})</Text>; - } + }; - renderHeading = ({ children, level }) => { + renderHeading = ({ children, level }: any) => { const { numberOfLines, theme } = this.props; - const textStyle = styles[`heading${ level }Text`]; + const textStyle = styles[`heading${level}Text`]; return ( <Text numberOfLines={numberOfLines} style={[textStyle, { color: themes[theme].bodyText }]}> {children} @@ -308,71 +284,52 @@ class Markdown extends PureComponent { ); }; - renderList = ({ - children, start, tight, type - }) => { + renderList = ({ children, start, tight, type }: any) => { const { numberOfLines } = this.props; return ( - <MarkdownList - ordered={type !== 'bullet'} - start={start} - tight={tight} - numberOfLines={numberOfLines} - > + <MarkdownList ordered={type !== 'bullet'} start={start} tight={tight} numberOfLines={numberOfLines}> {children} </MarkdownList> ); }; - renderListItem = ({ - children, context, ...otherProps - }) => { + renderListItem = ({ children, context, ...otherProps }: any) => { const { theme } = this.props; - const level = context.filter(type => type === 'list').length; + const level = context.filter((type: string) => type === 'list').length; return ( - <MarkdownListItem - level={level} - theme={theme} - {...otherProps} - > + <MarkdownListItem level={level} theme={theme} {...otherProps}> {children} </MarkdownListItem> ); }; - renderBlockQuote = ({ children }) => { + renderBlockQuote = ({ children }: { children: JSX.Element }) => { const { theme } = this.props; - return ( - <MarkdownBlockQuote theme={theme}> - {children} - </MarkdownBlockQuote> - ); - } + return <MarkdownBlockQuote theme={theme}>{children}</MarkdownBlockQuote>; + }; - renderTable = ({ children, numColumns }) => { + renderTable = ({ children, numColumns }: { children: JSX.Element; numColumns: number }) => { const { theme } = this.props; return ( <MarkdownTable numColumns={numColumns} theme={theme}> {children} </MarkdownTable> ); - } + }; - renderTableRow = (args) => { + renderTableRow = (args: any) => { const { theme } = this.props; return <MarkdownTableRow {...args} theme={theme} />; - } + }; - renderTableCell = (args) => { + renderTableCell = (args: any) => { const { theme } = this.props; return <MarkdownTableCell {...args} theme={theme} />; - } + }; render() { - const { - msg, numberOfLines, preview = false, theme, style = [], testID - } = this.props; + const { msg, numberOfLines, preview = false, theme, style = [], testID } = this.props; if (!msg) { return null; @@ -391,7 +348,11 @@ class Markdown extends PureComponent { m = removeMarkdown(m); m = m.replace(/\n+/g, ' '); return ( - <Text accessibilityLabel={m} style={[styles.text, { color: themes[theme].bodyText }, ...style]} numberOfLines={numberOfLines} testID={testID}> + <Text + accessibilityLabel={m} + style={[styles.text, { color: themes[theme].bodyText }, ...style]} + numberOfLines={numberOfLines} + testID={testID}> {m} </Text> ); diff --git a/app/containers/markdown/mergeTextNodes.js b/app/containers/markdown/mergeTextNodes.ts similarity index 89% rename from app/containers/markdown/mergeTextNodes.js rename to app/containers/markdown/mergeTextNodes.ts index 768395236..93302299e 100644 --- a/app/containers/markdown/mergeTextNodes.js +++ b/app/containers/markdown/mergeTextNodes.ts @@ -1,11 +1,11 @@ // 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) { +export default function mergeTextNodes(ast: any) { // 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()) { + while ((event = walker.next())) { const { entering, node } = event; const { type } = node; if (entering && type === 'text') { diff --git a/app/containers/markdown/styles.js b/app/containers/markdown/styles.ts similarity index 96% rename from app/containers/markdown/styles.js rename to app/containers/markdown/styles.ts index f78ad3c3a..d7eef0502 100644 --- a/app/containers/markdown/styles.js +++ b/app/containers/markdown/styles.ts @@ -1,4 +1,4 @@ -import { StyleSheet, Platform } from 'react-native'; +import { Platform, StyleSheet } from 'react-native'; import sharedStyles from '../../views/Styles'; @@ -7,7 +7,7 @@ const codeFontFamily = Platform.select({ android: { fontFamily: 'monospace' } }); -export default StyleSheet.create({ +export default StyleSheet.create<any>({ container: { alignItems: 'flex-start', flexDirection: 'row' diff --git a/app/containers/message/Attachments.js b/app/containers/message/Attachments.js deleted file mode 100644 index 0600e6d69..000000000 --- a/app/containers/message/Attachments.js +++ /dev/null @@ -1,76 +0,0 @@ -import React, { useContext } from 'react'; -import { dequal } from 'dequal'; -import PropTypes from 'prop-types'; -import { Text } from 'react-native'; - -import Image from './Image'; -import Audio from './Audio'; -import Video from './Video'; -import Reply from './Reply'; -import Button from '../Button'; -import styles from './styles'; -import MessageContext from './Context'; - -const AttachedActions = ({ - attachment, theme -}) => { - const { onAnswerButtonPress } = useContext(MessageContext); - - const attachedButtons = attachment.actions.map((element) => { - if (element.type === 'button') { - return <Button theme={theme} onPress={() => onAnswerButtonPress(element.msg)} title={element.text} />; - } else { - return null; - } - }); - return ( - <> - <Text style={styles.text}>{attachment.text}</Text> - {attachedButtons} - </> - ); -}; - -const Attachments = React.memo(({ - attachments, timeFormat, showAttachment, getCustomEmoji, theme -}) => { - if (!attachments || attachments.length === 0) { - return null; - } - - return attachments.map((file, index) => { - if (file.image_url) { - return <Image key={file.image_url} file={file} showAttachment={showAttachment} getCustomEmoji={getCustomEmoji} theme={theme} />; - } - if (file.audio_url) { - return <Audio key={file.audio_url} file={file} getCustomEmoji={getCustomEmoji} theme={theme} />; - } - if (file.video_url) { - return <Video key={file.video_url} file={file} showAttachment={showAttachment} getCustomEmoji={getCustomEmoji} theme={theme} />; - } - if (file.actions && file.actions.length > 0) { - return <AttachedActions attachment={file} theme={theme} />; - } - - // eslint-disable-next-line react/no-array-index-key - return <Reply key={index} index={index} attachment={file} timeFormat={timeFormat} getCustomEmoji={getCustomEmoji} theme={theme} />; - }); -}, (prevProps, nextProps) => dequal(prevProps.attachments, nextProps.attachments) && prevProps.theme === nextProps.theme); - -Attachments.propTypes = { - attachments: PropTypes.array, - timeFormat: PropTypes.string, - showAttachment: PropTypes.func, - getCustomEmoji: PropTypes.func, - theme: PropTypes.string -}; -Attachments.displayName = 'MessageAttachments'; -AttachedActions.propTypes = { - attachment: PropTypes.shape({ - actions: PropTypes.array, - text: PropTypes.string - }), - theme: PropTypes.string -}; - -export default Attachments; diff --git a/app/containers/message/Attachments.tsx b/app/containers/message/Attachments.tsx new file mode 100644 index 000000000..8ca931836 --- /dev/null +++ b/app/containers/message/Attachments.tsx @@ -0,0 +1,72 @@ +import React, { useContext } from 'react'; +import { dequal } from 'dequal'; +import { Text } from 'react-native'; + +import { IMessageAttachments, IMessageAttachedActions } from './interfaces'; +import Image from './Image'; +import Audio from './Audio'; +import Video from './Video'; +import Reply from './Reply'; +import Button from '../Button'; +import styles from './styles'; +import MessageContext from './Context'; + +const AttachedActions = ({ attachment, theme }: IMessageAttachedActions) => { + const { onAnswerButtonPress } = useContext(MessageContext); + + const attachedButtons = attachment.actions.map((element: { type: string; msg: string; text: string }) => { + if (element.type === 'button') { + return <Button theme={theme} onPress={() => onAnswerButtonPress(element.msg)} title={element.text} />; + } + return null; + }); + return ( + <> + <Text style={styles.text}>{attachment.text}</Text> + {attachedButtons} + </> + ); +}; + +const Attachments = React.memo( + ({ attachments, timeFormat, showAttachment, getCustomEmoji, theme }: IMessageAttachments) => { + if (!attachments || attachments.length === 0) { + return null; + } + + return attachments.map((file: any, index: number) => { + if (file.image_url) { + return ( + <Image key={file.image_url} file={file} showAttachment={showAttachment} getCustomEmoji={getCustomEmoji} theme={theme} /> + ); + } + if (file.audio_url) { + return <Audio key={file.audio_url} file={file} getCustomEmoji={getCustomEmoji} theme={theme} />; + } + if (file.video_url) { + return ( + <Video key={file.video_url} file={file} showAttachment={showAttachment} getCustomEmoji={getCustomEmoji} theme={theme} /> + ); + } + if (file.actions && file.actions.length > 0) { + return <AttachedActions attachment={file} theme={theme} />; + } + + return ( + <Reply + key={index} + index={index} + attachment={file} + timeFormat={timeFormat} + getCustomEmoji={getCustomEmoji} + theme={theme} + /> + ); + }); + }, + (prevProps, nextProps) => dequal(prevProps.attachments, nextProps.attachments) && prevProps.theme === nextProps.theme +); + +Attachments.displayName = 'MessageAttachments'; + +export default Attachments; diff --git a/app/containers/message/Audio.js b/app/containers/message/Audio.tsx similarity index 74% rename from app/containers/message/Audio.js rename to app/containers/message/Audio.tsx index e22dff408..958ae8ee8 100644 --- a/app/containers/message/Audio.js +++ b/app/containers/message/Audio.tsx @@ -1,8 +1,5 @@ import React from 'react'; -import PropTypes from 'prop-types'; -import { - View, StyleSheet, Text, Easing -} from 'react-native'; +import { Easing, StyleSheet, Text, View } from 'react-native'; import { Audio } from 'expo-av'; import Slider from '@react-native-community/slider'; import moment from 'moment'; @@ -19,10 +16,34 @@ import MessageContext from './Context'; import ActivityIndicator from '../ActivityIndicator'; import { withDimensions } from '../../dimensions'; +interface IButton { + loading: boolean; + paused: boolean; + theme: string; + onPress: Function; +} + +interface IMessageAudioProps { + file: { + audio_url: string; + description: string; + }; + theme: string; + getCustomEmoji: Function; + scale: number; +} + +interface IMessageAudioState { + loading: boolean; + currentTime: number; + duration: number; + paused: boolean; +} + const mode = { allowsRecordingIOS: false, playsInSilentModeIOS: true, - staysActiveInBackground: false, + staysActiveInBackground: true, shouldDuckAndroid: true, playThroughEarpieceAndroid: false, interruptionModeIOS: Audio.INTERRUPTION_MODE_IOS_DO_NOT_MIX, @@ -57,52 +78,38 @@ const styles = StyleSheet.create({ } }); -const formatTime = seconds => moment.utc(seconds * 1000).format('mm:ss'); -const BUTTON_HIT_SLOP = { - top: 12, right: 12, bottom: 12, left: 12 -}; +const formatTime = (seconds: number) => moment.utc(seconds * 1000).format('mm:ss'); + +const BUTTON_HIT_SLOP = { top: 12, right: 12, bottom: 12, left: 12 }; + const sliderAnimationConfig = { duration: 250, easing: Easing.linear, delay: 0 }; -const Button = React.memo(({ - loading, paused, onPress, theme -}) => ( +const Button = React.memo(({ loading, paused, onPress, theme }: IButton) => ( <Touchable style={styles.playPauseButton} onPress={onPress} hitSlop={BUTTON_HIT_SLOP} - background={Touchable.SelectableBackgroundBorderless()} - > - { - loading - ? <ActivityIndicator style={[styles.playPauseButton, styles.audioLoading]} theme={theme} /> - : <CustomIcon name={paused ? 'play-filled' : 'pause-filled'} size={36} color={themes[theme].tintColor} /> - } + background={Touchable.SelectableBackgroundBorderless()}> + {loading ? ( + <ActivityIndicator style={[styles.playPauseButton, styles.audioLoading]} theme={theme} /> + ) : ( + <CustomIcon name={paused ? 'play-filled' : 'pause-filled'} size={36} color={themes[theme].tintColor} /> + )} </Touchable> )); -Button.propTypes = { - loading: PropTypes.bool, - paused: PropTypes.bool, - theme: PropTypes.string, - onPress: PropTypes.func -}; Button.displayName = 'MessageAudioButton'; -class MessageAudio extends React.Component { +class MessageAudio extends React.Component<IMessageAudioProps, IMessageAudioState> { static contextType = MessageContext; - static propTypes = { - file: PropTypes.object.isRequired, - theme: PropTypes.string, - getCustomEmoji: PropTypes.func, - scale: PropTypes.number - } + private sound: any; - constructor(props) { + constructor(props: IMessageAudioProps) { super(props); this.state = { loading: false, @@ -121,22 +128,20 @@ class MessageAudio extends React.Component { let url = file.audio_url; if (!url.startsWith('http')) { - url = `${ baseUrl }${ file.audio_url }`; + url = `${baseUrl}${file.audio_url}`; } this.setState({ loading: true }); try { - await this.sound.loadAsync({ uri: `${ url }?rc_uid=${ user.id }&rc_token=${ user.token }` }); + await this.sound.loadAsync({ uri: `${url}?rc_uid=${user.id}&rc_token=${user.token}` }); } catch { // Do nothing } this.setState({ loading: false }); } - shouldComponentUpdate(nextProps, nextState) { - const { - currentTime, duration, paused, loading - } = this.state; + shouldComponentUpdate(nextProps: any, nextState: any) { + const { currentTime, duration, paused, loading } = this.state; const { file, theme } = this.props; if (nextProps.theme !== theme) { return true; @@ -176,28 +181,28 @@ class MessageAudio extends React.Component { } } - onPlaybackStatusUpdate = (status) => { + onPlaybackStatusUpdate = (status: any) => { if (status) { this.onLoad(status); this.onProgress(status); this.onEnd(status); } - } + }; - onLoad = (data) => { + onLoad = (data: any) => { const duration = data.durationMillis / 1000; this.setState({ duration: duration > 0 ? duration : 0 }); - } + }; - onProgress = (data) => { + onProgress = (data: any) => { const { duration } = this.state; const currentTime = data.positionMillis / 1000; if (currentTime <= duration) { this.setState({ currentTime }); } - } + }; - onEnd = async(data) => { + onEnd = async (data: any) => { if (data.didJustFinish) { try { await this.sound.stopAsync(); @@ -206,7 +211,7 @@ class MessageAudio extends React.Component { // do nothing } } - } + }; get duration() { const { currentTime, duration } = this.state; @@ -216,9 +221,9 @@ class MessageAudio extends React.Component { togglePlayPause = () => { const { paused } = this.state; this.setState({ paused: !paused }, this.playPause); - } + }; - playPause = async() => { + playPause = async () => { const { paused } = this.state; try { if (paused) { @@ -230,24 +235,20 @@ class MessageAudio extends React.Component { } catch { // Do nothing } - } + }; - onValueChange = async(value) => { + onValueChange = async (value: any) => { try { this.setState({ currentTime: value }); await this.sound.setPositionAsync(value * 1000); } catch { // Do nothing } - } + }; render() { - const { - loading, paused, currentTime, duration - } = this.state; - const { - file, getCustomEmoji, theme, scale - } = this.props; + const { loading, paused, currentTime, duration } = this.state; + const { file, getCustomEmoji, theme, scale } = this.props; const { description } = file; const { baseUrl, user } = this.context; @@ -261,8 +262,7 @@ class MessageAudio extends React.Component { style={[ styles.audioContainer, { backgroundColor: themes[theme].chatComponentBackground, borderColor: themes[theme].borderColor } - ]} - > + ]}> <Button loading={loading} paused={paused} onPress={this.togglePlayPause} theme={theme} /> <Slider style={styles.slider} @@ -275,10 +275,12 @@ class MessageAudio extends React.Component { minimumTrackTintColor={themes[theme].tintColor} maximumTrackTintColor={themes[theme].auxiliaryText} onValueChange={this.onValueChange} + /* @ts-ignore*/ thumbImage={isIOS && { uri: 'audio_thumb', scale }} /> <Text style={[styles.duration, { color: themes[theme].auxiliaryText }]}>{this.duration}</Text> </View> + {/* @ts-ignore*/} <Markdown msg={description} baseUrl={baseUrl} username={user.username} getCustomEmoji={getCustomEmoji} theme={theme} /> </> ); diff --git a/app/containers/message/Blocks.js b/app/containers/message/Blocks.ts similarity index 59% rename from app/containers/message/Blocks.js rename to app/containers/message/Blocks.ts index ba74ebe20..ef2f422c8 100644 --- a/app/containers/message/Blocks.js +++ b/app/containers/message/Blocks.ts @@ -1,15 +1,14 @@ import React from 'react'; -import PropTypes from 'prop-types'; -import { messageBlockWithContext } from '../UIKit/MessageBlock'; -const Blocks = React.memo(({ - blocks, id: mid, rid, blockAction -}) => { +import { messageBlockWithContext } from '../UIKit/MessageBlock'; +import { IMessageBlocks } from './interfaces'; + +const Blocks = React.memo(({ blocks, id: mid, rid, blockAction }: IMessageBlocks) => { if (blocks && blocks.length > 0) { const appId = blocks[0]?.appId || ''; return React.createElement( messageBlockWithContext({ - action: async({ actionId, value, blockId }) => { + action: async ({ actionId, value, blockId }: any) => { await blockAction({ actionId, appId, @@ -21,18 +20,13 @@ const Blocks = React.memo(({ }, appId, rid - }), { blocks } + }), + { blocks } ); } return null; }); -Blocks.propTypes = { - blocks: PropTypes.array, - id: PropTypes.string, - rid: PropTypes.string, - blockAction: PropTypes.func -}; Blocks.displayName = 'MessageBlocks'; export default Blocks; diff --git a/app/containers/message/Broadcast.js b/app/containers/message/Broadcast.tsx similarity index 78% rename from app/containers/message/Broadcast.js rename to app/containers/message/Broadcast.tsx index 58de1d0dc..56bd0da7c 100644 --- a/app/containers/message/Broadcast.js +++ b/app/containers/message/Broadcast.tsx @@ -1,6 +1,5 @@ import React, { useContext } from 'react'; -import { View, Text } from 'react-native'; -import PropTypes from 'prop-types'; +import { Text, View } from 'react-native'; import Touchable from './Touchable'; import { CustomIcon } from '../../lib/Icons'; @@ -9,10 +8,9 @@ import { BUTTON_HIT_SLOP } from './utils'; import I18n from '../../i18n'; import { themes } from '../../constants/colors'; import MessageContext from './Context'; +import { IMessageBroadcast } from './interfaces'; -const Broadcast = React.memo(({ - author, broadcast, theme -}) => { +const Broadcast = React.memo(({ author, broadcast, theme }: IMessageBroadcast) => { const { user, replyBroadcast } = useContext(MessageContext); const isOwn = author._id === user.id; if (broadcast && !isOwn) { @@ -23,8 +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' - > + testID='message-broadcast-reply'> <> <CustomIcon name='arrow-back' size={20} style={styles.buttonIcon} color={themes[theme].buttonText} /> <Text style={[styles.buttonText, { color: themes[theme].buttonText }]}>{I18n.t('Reply')}</Text> @@ -36,11 +33,6 @@ const Broadcast = React.memo(({ return null; }); -Broadcast.propTypes = { - author: PropTypes.object, - broadcast: PropTypes.bool, - theme: PropTypes.string -}; Broadcast.displayName = 'MessageBroadcast'; export default Broadcast; diff --git a/app/containers/message/CallButton.js b/app/containers/message/CallButton.tsx similarity index 76% rename from app/containers/message/CallButton.js rename to app/containers/message/CallButton.tsx index 4c701a6c8..7e908bf5f 100644 --- a/app/containers/message/CallButton.js +++ b/app/containers/message/CallButton.tsx @@ -1,6 +1,5 @@ import React from 'react'; -import { View, Text } from 'react-native'; -import PropTypes from 'prop-types'; +import { Text, View } from 'react-native'; import Touchable from './Touchable'; import { BUTTON_HIT_SLOP } from './utils'; @@ -8,17 +7,15 @@ import styles from './styles'; import I18n from '../../i18n'; import { CustomIcon } from '../../lib/Icons'; import { themes } from '../../constants/colors'; +import { IMessageCallButton } from './interfaces'; -const CallButton = React.memo(({ - theme, callJitsi -}) => ( +const CallButton = React.memo(({ theme, callJitsi }: IMessageCallButton) => ( <View style={styles.buttonContainer}> <Touchable onPress={callJitsi} background={Touchable.Ripple(themes[theme].bannerBackground)} style={[styles.button, { backgroundColor: themes[theme].tintColor }]} - hitSlop={BUTTON_HIT_SLOP} - > + hitSlop={BUTTON_HIT_SLOP}> <> <CustomIcon name='camera' size={16} style={styles.buttonIcon} color={themes[theme].buttonText} /> <Text style={[styles.buttonText, { color: themes[theme].buttonText }]}>{I18n.t('Click_to_join')}</Text> @@ -27,10 +24,6 @@ const CallButton = React.memo(({ </View> )); -CallButton.propTypes = { - theme: PropTypes.string, - callJitsi: PropTypes.func -}; CallButton.displayName = 'CallButton'; export default CallButton; diff --git a/app/containers/message/Content.js b/app/containers/message/Content.js deleted file mode 100644 index 90af48acd..000000000 --- a/app/containers/message/Content.js +++ /dev/null @@ -1,140 +0,0 @@ -import React, { useContext } from 'react'; -import { Text, View } from 'react-native'; -import PropTypes from 'prop-types'; -import { dequal } from 'dequal'; - -import I18n from '../../i18n'; -import styles from './styles'; -import Markdown from '../markdown'; -import User from './User'; -import { getInfoMessage, SYSTEM_MESSAGE_TYPES_WITH_AUTHOR_NAME } from './utils'; -import { themes } from '../../constants/colors'; -import MessageContext from './Context'; -import Encrypted from './Encrypted'; -import { E2E_MESSAGE_TYPE } from '../../lib/encryption/constants'; - -const Content = React.memo((props) => { - if (props.isInfo) { - const infoMessage = getInfoMessage({ ...props }); - - const renderMessageContent = ( - <Text - style={[styles.textInfo, { color: themes[props.theme].auxiliaryText }]} - accessibilityLabel={infoMessage} - > - {infoMessage} - </Text> - ); - - if (SYSTEM_MESSAGE_TYPES_WITH_AUTHOR_NAME.includes(props.type)) { - return ( - <Text> - <User {...props} /> {renderMessageContent} - </Text> - ); - } - - return renderMessageContent; - } - - const isPreview = props.tmid && !props.isThreadRoom; - let content = null; - - if (props.tmid && !props.msg) { - content = <Text style={[styles.text, { color: themes[props.theme].bodyText }]}>{I18n.t('Sent_an_attachment')}</Text>; - } else if (props.isEncrypted) { - content = <Text style={[styles.textInfo, { color: themes[props.theme].auxiliaryText }]}>{I18n.t('Encrypted_message')}</Text>; - } else { - const { baseUrl, user, onLinkPress } = useContext(MessageContext); - content = ( - <Markdown - msg={props.msg} - baseUrl={baseUrl} - getCustomEmoji={props.getCustomEmoji} - username={user.username} - isEdited={props.isEdited} - numberOfLines={isPreview ? 1 : 0} - preview={isPreview} - channels={props.channels} - mentions={props.mentions} - navToRoomInfo={props.navToRoomInfo} - tmid={props.tmid} - useRealName={props.useRealName} - theme={props.theme} - onLinkPress={onLinkPress} - /> - ); - } - - // If this is a encrypted message and is not a preview - if (props.type === E2E_MESSAGE_TYPE && !isPreview) { - content = ( - <View style={styles.flex}> - <View style={styles.contentContainer}> - {content} - </View> - <Encrypted - type={props.type} - theme={props.theme} - /> - </View> - ); - } - - if (props.isIgnored) { - content = <Text style={[styles.textInfo, { color: themes[props.theme].auxiliaryText }]}>{I18n.t('Message_Ignored')}</Text>; - } - - return ( - <View style={props.isTemp && styles.temp}> - {content} - </View> - ); -}, (prevProps, nextProps) => { - if (prevProps.isTemp !== nextProps.isTemp) { - return false; - } - if (prevProps.msg !== nextProps.msg) { - return false; - } - if (prevProps.type !== nextProps.type) { - return false; - } - if (prevProps.theme !== nextProps.theme) { - return false; - } - if (prevProps.isEncrypted !== nextProps.isEncrypted) { - return false; - } - if (prevProps.isIgnored !== nextProps.isIgnored) { - return false; - } - if (!dequal(prevProps.mentions, nextProps.mentions)) { - return false; - } - if (!dequal(prevProps.channels, nextProps.channels)) { - return false; - } - return true; -}); - -Content.propTypes = { - isTemp: PropTypes.bool, - isInfo: PropTypes.bool, - tmid: PropTypes.string, - isThreadRoom: PropTypes.bool, - msg: PropTypes.string, - theme: PropTypes.string, - isEdited: PropTypes.bool, - isEncrypted: PropTypes.bool, - getCustomEmoji: PropTypes.func, - channels: PropTypes.oneOfType([PropTypes.array, PropTypes.object]), - mentions: PropTypes.oneOfType([PropTypes.array, PropTypes.object]), - navToRoomInfo: PropTypes.func, - useRealName: PropTypes.bool, - isIgnored: PropTypes.bool, - type: PropTypes.string -}; -Content.displayName = 'MessageContent'; - -export default Content; diff --git a/app/containers/message/Content.tsx b/app/containers/message/Content.tsx new file mode 100644 index 000000000..ee3b8c931 --- /dev/null +++ b/app/containers/message/Content.tsx @@ -0,0 +1,118 @@ +import React, { useContext } from 'react'; +import { Text, View } from 'react-native'; +import { dequal } from 'dequal'; + +import I18n from '../../i18n'; +import styles from './styles'; +import Markdown from '../markdown'; +import User from './User'; +import { SYSTEM_MESSAGE_TYPES_WITH_AUTHOR_NAME, getInfoMessage } from './utils'; +import { themes } from '../../constants/colors'; +import MessageContext from './Context'; +import Encrypted from './Encrypted'; +import { E2E_MESSAGE_TYPE } from '../../lib/encryption/constants'; +import { IMessageContent } from './interfaces'; + +const Content = React.memo( + (props: IMessageContent) => { + if (props.isInfo) { + // @ts-ignore + const infoMessage = getInfoMessage({ ...props }); + + const renderMessageContent = ( + <Text style={[styles.textInfo, { color: themes[props.theme].auxiliaryText }]} accessibilityLabel={infoMessage}> + {infoMessage} + </Text> + ); + + if (SYSTEM_MESSAGE_TYPES_WITH_AUTHOR_NAME.includes(props.type)) { + return ( + <Text> + <User {...props} /> {renderMessageContent} + </Text> + ); + } + + return renderMessageContent; + } + + const isPreview: any = props.tmid && !props.isThreadRoom; + let content = null; + + if (props.tmid && !props.msg) { + content = <Text style={[styles.text, { color: themes[props.theme].bodyText }]}>{I18n.t('Sent_an_attachment')}</Text>; + } else if (props.isEncrypted) { + content = ( + <Text style={[styles.textInfo, { color: themes[props.theme].auxiliaryText }]}>{I18n.t('Encrypted_message')}</Text> + ); + } else { + const { baseUrl, user, onLinkPress } = useContext(MessageContext); + content = ( + // @ts-ignore + <Markdown + msg={props.msg} + baseUrl={baseUrl} + getCustomEmoji={props.getCustomEmoji} + username={user.username} + isEdited={props.isEdited} + numberOfLines={isPreview ? 1 : 0} + preview={isPreview} + channels={props.channels} + mentions={props.mentions} + navToRoomInfo={props.navToRoomInfo} + tmid={props.tmid} + useRealName={props.useRealName} + theme={props.theme} + onLinkPress={onLinkPress} + /> + ); + } + + // If this is a encrypted message and is not a preview + if (props.type === E2E_MESSAGE_TYPE && !isPreview) { + content = ( + <View style={styles.flex}> + <View style={styles.contentContainer}>{content}</View> + <Encrypted type={props.type} theme={props.theme} /> + </View> + ); + } + + if (props.isIgnored) { + content = <Text style={[styles.textInfo, { color: themes[props.theme].auxiliaryText }]}>{I18n.t('Message_Ignored')}</Text>; + } + + return <View style={props.isTemp && styles.temp}>{content}</View>; + }, + (prevProps, nextProps) => { + if (prevProps.isTemp !== nextProps.isTemp) { + return false; + } + if (prevProps.msg !== nextProps.msg) { + return false; + } + if (prevProps.type !== nextProps.type) { + return false; + } + if (prevProps.theme !== nextProps.theme) { + return false; + } + if (prevProps.isEncrypted !== nextProps.isEncrypted) { + return false; + } + if (prevProps.isIgnored !== nextProps.isIgnored) { + return false; + } + if (!dequal(prevProps.mentions, nextProps.mentions)) { + return false; + } + if (!dequal(prevProps.channels, nextProps.channels)) { + return false; + } + return true; + } +); + +Content.displayName = 'MessageContent'; + +export default Content; diff --git a/app/containers/message/Context.js b/app/containers/message/Context.js deleted file mode 100644 index 407904b4e..000000000 --- a/app/containers/message/Context.js +++ /dev/null @@ -1,4 +0,0 @@ -import React from 'react'; - -const MessageContext = React.createContext(); -export default MessageContext; diff --git a/app/containers/message/Context.ts b/app/containers/message/Context.ts new file mode 100644 index 000000000..eee35a6c7 --- /dev/null +++ b/app/containers/message/Context.ts @@ -0,0 +1,5 @@ +import React from 'react'; + +// @ts-ignore +const MessageContext = React.createContext<any>(); +export default MessageContext; diff --git a/app/containers/message/Discussion.js b/app/containers/message/Discussion.js deleted file mode 100644 index 86650f38c..000000000 --- a/app/containers/message/Discussion.js +++ /dev/null @@ -1,68 +0,0 @@ -import React, { useContext } from 'react'; -import { View, Text } from 'react-native'; -import PropTypes from 'prop-types'; - -import Touchable from './Touchable'; -import { formatMessageCount, BUTTON_HIT_SLOP } from './utils'; -import styles from './styles'; -import I18n from '../../i18n'; -import { CustomIcon } from '../../lib/Icons'; -import { DISCUSSION } from './constants'; -import { themes } from '../../constants/colors'; -import MessageContext from './Context'; -import { formatDateThreads } from '../../utils/room'; - -const Discussion = React.memo(({ - msg, dcount, dlm, theme -}) => { - let time; - if (dlm) { - time = formatDateThreads(dlm); - } - const buttonText = formatMessageCount(dcount, DISCUSSION); - const { onDiscussionPress } = useContext(MessageContext); - return ( - <> - <Text style={[styles.startedDiscussion, { color: themes[theme].auxiliaryText }]}>{I18n.t('Started_discussion')}</Text> - <Text style={[styles.text, { color: themes[theme].bodyText }]}>{msg}</Text> - <View style={styles.buttonContainer}> - <Touchable - onPress={onDiscussionPress} - background={Touchable.Ripple(themes[theme].bannerBackground)} - style={[styles.button, { backgroundColor: themes[theme].tintColor }]} - hitSlop={BUTTON_HIT_SLOP} - > - <> - <CustomIcon name='discussions' size={16} style={styles.buttonIcon} color={themes[theme].buttonText} /> - <Text style={[styles.buttonText, { color: themes[theme].buttonText }]}>{buttonText}</Text> - </> - </Touchable> - <Text style={[styles.time, { color: themes[theme].auxiliaryText }]}>{time}</Text> - </View> - </> - ); -}, (prevProps, nextProps) => { - if (prevProps.msg !== nextProps.msg) { - return false; - } - if (prevProps.dcount !== nextProps.dcount) { - return false; - } - if (prevProps.dlm !== nextProps.dlm) { - return false; - } - if (prevProps.theme !== nextProps.theme) { - return false; - } - return true; -}); - -Discussion.propTypes = { - msg: PropTypes.string, - dcount: PropTypes.number, - dlm: PropTypes.string, - theme: PropTypes.string -}; -Discussion.displayName = 'MessageDiscussion'; - -export default Discussion; diff --git a/app/containers/message/Discussion.tsx b/app/containers/message/Discussion.tsx new file mode 100644 index 000000000..2f9a782fc --- /dev/null +++ b/app/containers/message/Discussion.tsx @@ -0,0 +1,62 @@ +import React, { useContext } from 'react'; +import { Text, View } from 'react-native'; + +import Touchable from './Touchable'; +import { BUTTON_HIT_SLOP, formatMessageCount } from './utils'; +import styles from './styles'; +import I18n from '../../i18n'; +import { CustomIcon } from '../../lib/Icons'; +import { DISCUSSION } from './constants'; +import { themes } from '../../constants/colors'; +import MessageContext from './Context'; +import { formatDateThreads } from '../../utils/room'; +import { IMessageDiscussion } from './interfaces'; + +const Discussion = React.memo( + ({ msg, dcount, dlm, theme }: IMessageDiscussion) => { + let time; + if (dlm) { + time = formatDateThreads(dlm); + } + const buttonText = formatMessageCount(dcount, DISCUSSION); + const { onDiscussionPress } = useContext(MessageContext); + return ( + <> + <Text style={[styles.startedDiscussion, { color: themes[theme].auxiliaryText }]}>{I18n.t('Started_discussion')}</Text> + <Text style={[styles.text, { color: themes[theme].bodyText }]}>{msg}</Text> + <View style={styles.buttonContainer}> + <Touchable + onPress={onDiscussionPress} + background={Touchable.Ripple(themes[theme].bannerBackground)} + style={[styles.button, { backgroundColor: themes[theme].tintColor }]} + hitSlop={BUTTON_HIT_SLOP}> + <> + <CustomIcon name='discussions' size={16} style={styles.buttonIcon} color={themes[theme].buttonText} /> + <Text style={[styles.buttonText, { color: themes[theme].buttonText }]}>{buttonText}</Text> + </> + </Touchable> + <Text style={[styles.time, { color: themes[theme].auxiliaryText }]}>{time}</Text> + </View> + </> + ); + }, + (prevProps, nextProps) => { + if (prevProps.msg !== nextProps.msg) { + return false; + } + if (prevProps.dcount !== nextProps.dcount) { + return false; + } + if (prevProps.dlm !== nextProps.dlm) { + return false; + } + if (prevProps.theme !== nextProps.theme) { + return false; + } + return true; + } +); + +Discussion.displayName = 'MessageDiscussion'; + +export default Discussion; diff --git a/app/containers/message/Emoji.js b/app/containers/message/Emoji.js deleted file mode 100644 index e8b817da0..000000000 --- a/app/containers/message/Emoji.js +++ /dev/null @@ -1,28 +0,0 @@ -import React from 'react'; -import { Text } from 'react-native'; -import PropTypes from 'prop-types'; - -import shortnameToUnicode from '../../utils/shortnameToUnicode'; -import CustomEmoji from '../EmojiPicker/CustomEmoji'; - -const Emoji = React.memo(({ - content, baseUrl, standardEmojiStyle, customEmojiStyle, getCustomEmoji -}) => { - const parsedContent = content.replace(/^:|:$/g, ''); - const emoji = getCustomEmoji(parsedContent); - if (emoji) { - return <CustomEmoji key={content} baseUrl={baseUrl} style={customEmojiStyle} emoji={emoji} />; - } - return <Text style={standardEmojiStyle}>{ shortnameToUnicode(content) }</Text>; -}, () => true); - -Emoji.propTypes = { - content: PropTypes.string, - baseUrl: PropTypes.string, - standardEmojiStyle: PropTypes.object, - customEmojiStyle: PropTypes.object, - getCustomEmoji: PropTypes.func -}; -Emoji.displayName = 'MessageEmoji'; - -export default Emoji; diff --git a/app/containers/message/Emoji.tsx b/app/containers/message/Emoji.tsx new file mode 100644 index 000000000..0481c5926 --- /dev/null +++ b/app/containers/message/Emoji.tsx @@ -0,0 +1,22 @@ +import React from 'react'; +import { Text } from 'react-native'; + +import shortnameToUnicode from '../../utils/shortnameToUnicode'; +import CustomEmoji from '../EmojiPicker/CustomEmoji'; +import { IMessageEmoji } from './interfaces'; + +const Emoji = React.memo( + ({ content, baseUrl, standardEmojiStyle, customEmojiStyle, getCustomEmoji }: IMessageEmoji) => { + const parsedContent = content.replace(/^:|:$/g, ''); + const emoji = getCustomEmoji(parsedContent); + if (emoji) { + return <CustomEmoji key={content} baseUrl={baseUrl} style={customEmojiStyle} emoji={emoji} />; + } + return <Text style={standardEmojiStyle}>{shortnameToUnicode(content)}</Text>; + }, + () => true +); + +Emoji.displayName = 'MessageEmoji'; + +export default Emoji; diff --git a/app/containers/message/Encrypted.js b/app/containers/message/Encrypted.tsx similarity index 80% rename from app/containers/message/Encrypted.js rename to app/containers/message/Encrypted.tsx index 38d3bf16b..1c3ac0801 100644 --- a/app/containers/message/Encrypted.js +++ b/app/containers/message/Encrypted.tsx @@ -1,5 +1,4 @@ import React, { useContext } from 'react'; -import PropTypes from 'prop-types'; import Touchable from './Touchable'; import { E2E_MESSAGE_TYPE } from '../../lib/encryption/constants'; @@ -9,7 +8,12 @@ import { BUTTON_HIT_SLOP } from './utils'; import MessageContext from './Context'; import styles from './styles'; -const Encrypted = React.memo(({ type, theme }) => { +interface IMessageEncrypted { + type: string; + theme: string; +} + +const Encrypted = React.memo(({ type, theme }: IMessageEncrypted) => { if (type !== E2E_MESSAGE_TYPE) { return null; } @@ -21,9 +25,5 @@ const Encrypted = React.memo(({ type, theme }) => { </Touchable> ); }); -Encrypted.propTypes = { - type: PropTypes.string, - theme: PropTypes.string -}; export default Encrypted; diff --git a/app/containers/message/Image.js b/app/containers/message/Image.js deleted file mode 100644 index 6f466b3b3..000000000 --- a/app/containers/message/Image.js +++ /dev/null @@ -1,93 +0,0 @@ -import React, { useContext } from 'react'; -import { View } from 'react-native'; -import PropTypes from 'prop-types'; -import FastImage from '@rocket.chat/react-native-fast-image'; -import { dequal } from 'dequal'; -import { createImageProgress } from 'react-native-image-progress'; -import * as Progress from 'react-native-progress'; - -import Touchable from './Touchable'; -import Markdown from '../markdown'; -import styles from './styles'; -import { formatAttachmentUrl } from '../../lib/utils'; -import { themes } from '../../constants/colors'; -import MessageContext from './Context'; - -const ImageProgress = createImageProgress(FastImage); - -const Button = React.memo(({ - children, onPress, theme -}) => ( - <Touchable - onPress={onPress} - style={styles.imageContainer} - background={Touchable.Ripple(themes[theme].bannerBackground)} - > - {children} - </Touchable> -)); - -export const MessageImage = React.memo(({ img, theme }) => ( - <ImageProgress - style={[styles.image, { borderColor: themes[theme].borderColor }]} - source={{ uri: encodeURI(img) }} - resizeMode={FastImage.resizeMode.cover} - indicator={Progress.Pie} - indicatorProps={{ - color: themes[theme].actionTintColor - }} - /> -)); - -const ImageContainer = React.memo(({ - file, imageUrl, showAttachment, getCustomEmoji, theme -}) => { - const { baseUrl, user } = useContext(MessageContext); - const img = imageUrl || formatAttachmentUrl(file.image_url, user.id, user.token, baseUrl); - if (!img) { - return null; - } - - const onPress = () => showAttachment(file); - - if (file.description) { - return ( - <Button theme={theme} onPress={onPress}> - <View> - <MessageImage img={img} theme={theme} /> - <Markdown msg={file.description} baseUrl={baseUrl} username={user.username} getCustomEmoji={getCustomEmoji} theme={theme} /> - </View> - </Button> - ); - } - - return ( - <Button theme={theme} onPress={onPress}> - <MessageImage img={img} theme={theme} /> - </Button> - ); -}, (prevProps, nextProps) => dequal(prevProps.file, nextProps.file) && prevProps.theme === nextProps.theme); - -ImageContainer.propTypes = { - file: PropTypes.object, - imageUrl: PropTypes.string, - showAttachment: PropTypes.func, - theme: PropTypes.string, - getCustomEmoji: PropTypes.func -}; -ImageContainer.displayName = 'MessageImageContainer'; - -MessageImage.propTypes = { - img: PropTypes.string, - theme: PropTypes.string -}; -ImageContainer.displayName = 'MessageImage'; - -Button.propTypes = { - children: PropTypes.node, - onPress: PropTypes.func, - theme: PropTypes.string -}; -ImageContainer.displayName = 'MessageButton'; - -export default ImageContainer; diff --git a/app/containers/message/Image.tsx b/app/containers/message/Image.tsx new file mode 100644 index 000000000..a4d29c5af --- /dev/null +++ b/app/containers/message/Image.tsx @@ -0,0 +1,94 @@ +import React, { useContext } from 'react'; +import { View } from 'react-native'; +import FastImage from '@rocket.chat/react-native-fast-image'; +import { dequal } from 'dequal'; +import { createImageProgress } from 'react-native-image-progress'; +import * as Progress from 'react-native-progress'; + +import Touchable from './Touchable'; +import Markdown from '../markdown'; +import styles from './styles'; +import { formatAttachmentUrl } from '../../lib/utils'; +import { themes } from '../../constants/colors'; +import MessageContext from './Context'; + +type TMessageButton = { + children: JSX.Element; + onPress: Function; + theme: string; +}; + +type TMessageImage = { + img: string; + theme: string; +}; + +interface IMessageImage { + file: { image_url: string; description?: string }; + imageUrl?: string; + showAttachment: Function; + theme: string; + getCustomEmoji: Function; +} + +const ImageProgress = createImageProgress(FastImage); + +const Button = React.memo(({ children, onPress, theme }: TMessageButton) => ( + <Touchable onPress={onPress} style={styles.imageContainer} background={Touchable.Ripple(themes[theme].bannerBackground)}> + {children} + </Touchable> +)); + +export const MessageImage = React.memo(({ img, theme }: TMessageImage) => ( + <ImageProgress + style={[styles.image, { borderColor: themes[theme].borderColor }]} + source={{ uri: encodeURI(img) }} + resizeMode={FastImage.resizeMode.cover} + indicator={Progress.Pie} + indicatorProps={{ + color: themes[theme].actionTintColor + }} + /> +)); + +const ImageContainer = React.memo( + ({ file, imageUrl, showAttachment, getCustomEmoji, theme }: IMessageImage) => { + const { baseUrl, user } = useContext(MessageContext); + const img = imageUrl || formatAttachmentUrl(file.image_url, user.id, user.token, baseUrl); + if (!img) { + return null; + } + + const onPress = () => showAttachment(file); + + if (file.description) { + return ( + <Button theme={theme} onPress={onPress}> + <View> + <MessageImage img={img} theme={theme} /> + {/* @ts-ignore */} + <Markdown + msg={file.description} + baseUrl={baseUrl} + username={user.username} + getCustomEmoji={getCustomEmoji} + theme={theme} + /> + </View> + </Button> + ); + } + + return ( + <Button theme={theme} onPress={onPress}> + <MessageImage img={img} theme={theme} /> + </Button> + ); + }, + (prevProps, nextProps) => dequal(prevProps.file, nextProps.file) && prevProps.theme === nextProps.theme +); + +ImageContainer.displayName = 'MessageImageContainer'; +MessageImage.displayName = 'MessageImage'; + +export default ImageContainer; diff --git a/app/containers/message/Message.js b/app/containers/message/Message.tsx similarity index 67% rename from app/containers/message/Message.js rename to app/containers/message/Message.tsx index 4bc03c000..0f24fd879 100644 --- a/app/containers/message/Message.js +++ b/app/containers/message/Message.tsx @@ -1,10 +1,8 @@ import React, { useContext } from 'react'; -import PropTypes from 'prop-types'; import { View } from 'react-native'; import Touchable from 'react-native-platform-touchable'; import MessageContext from './Context'; - import User from './User'; import styles from './styles'; import RepliedThread from './RepliedThread'; @@ -20,8 +18,9 @@ import Content from './Content'; import ReadReceipt from './ReadReceipt'; import CallButton from './CallButton'; import { themes } from '../../constants/colors'; +import { IMessage, IMessageInner, IMessageTouchable } from './interfaces'; -const MessageInner = React.memo((props) => { +const MessageInner = React.memo((props: IMessageInner) => { if (props.type === 'discussion-created') { return ( <> @@ -63,20 +62,16 @@ const MessageInner = React.memo((props) => { }); MessageInner.displayName = 'MessageInner'; -const Message = React.memo((props) => { +const Message = React.memo((props: IMessage) => { if (props.isThreadReply || props.isThreadSequential || props.isInfo || props.isIgnored) { const thread = props.isThreadReply ? <RepliedThread {...props} /> : null; return ( <View style={[styles.container, props.style]}> {thread} <View style={styles.flex}> + {/* @ts-ignore */} <MessageAvatar small {...props} /> - <View - style={[ - styles.messageContent, - props.isHeader && styles.messageContentWithHeader - ]} - > + <View style={[styles.messageContent, props.isHeader && styles.messageContentWithHeader]}> <Content {...props} /> </View> </View> @@ -87,27 +82,19 @@ const Message = React.memo((props) => { return ( <View style={[styles.container, props.style]}> <View style={styles.flex}> + {/* @ts-ignore */} <MessageAvatar {...props} /> - <View - style={[ - styles.messageContent, - props.isHeader && styles.messageContentWithHeader - ]} - > + <View style={[styles.messageContent, props.isHeader && styles.messageContentWithHeader]}> <MessageInner {...props} /> </View> - <ReadReceipt - isReadReceiptEnabled={props.isReadReceiptEnabled} - unread={props.unread} - theme={props.theme} - /> + <ReadReceipt isReadReceiptEnabled={props.isReadReceiptEnabled} unread={props.unread} theme={props.theme} /> </View> </View> ); }); Message.displayName = 'Message'; -const MessageTouchable = React.memo((props) => { +const MessageTouchable = React.memo((props: IMessageTouchable & IMessage) => { if (props.hasError) { return ( <View> @@ -121,44 +108,14 @@ const MessageTouchable = React.memo((props) => { onLongPress={onLongPress} onPress={onPress} disabled={(props.isInfo && !props.isThreadReply) || props.archived || props.isTemp} - style={{ backgroundColor: props.highlighted ? themes[props.theme].headerBackground : null }} - > + style={{ backgroundColor: props.highlighted ? themes[props.theme].headerBackground : null }}> <View> <Message {...props} /> </View> </Touchable> ); }); + MessageTouchable.displayName = 'MessageTouchable'; -MessageTouchable.propTypes = { - hasError: PropTypes.bool, - isInfo: PropTypes.bool, - isThreadReply: PropTypes.bool, - isTemp: PropTypes.bool, - archived: PropTypes.bool, - highlighted: PropTypes.bool, - theme: PropTypes.string -}; - -Message.propTypes = { - isThreadReply: PropTypes.bool, - isThreadSequential: PropTypes.bool, - isInfo: PropTypes.bool, - isTemp: PropTypes.bool, - isHeader: PropTypes.bool, - hasError: PropTypes.bool, - style: PropTypes.any, - onLongPress: PropTypes.func, - isReadReceiptEnabled: PropTypes.bool, - unread: PropTypes.bool, - theme: PropTypes.string, - isIgnored: PropTypes.bool -}; - -MessageInner.propTypes = { - type: PropTypes.string, - blocks: PropTypes.array -}; - export default MessageTouchable; diff --git a/app/containers/message/MessageAvatar.js b/app/containers/message/MessageAvatar.js deleted file mode 100644 index 7fcdffb55..000000000 --- a/app/containers/message/MessageAvatar.js +++ /dev/null @@ -1,46 +0,0 @@ -import React, { useContext } from 'react'; -import PropTypes from 'prop-types'; - -import Avatar from '../Avatar'; -import styles from './styles'; -import MessageContext from './Context'; - -const MessageAvatar = React.memo(({ - isHeader, avatar, author, small, navToRoomInfo, emoji, getCustomEmoji, theme -}) => { - const { user } = useContext(MessageContext); - if (isHeader && author) { - const navParam = { - t: 'd', - rid: author._id - }; - return ( - <Avatar - style={small ? styles.avatarSmall : styles.avatar} - text={avatar ? '' : author.username} - size={small ? 20 : 36} - borderRadius={small ? 2 : 4} - onPress={author._id === user.id ? undefined : () => navToRoomInfo(navParam)} - getCustomEmoji={getCustomEmoji} - avatar={avatar} - emoji={emoji} - theme={theme} - /> - ); - } - return null; -}); - -MessageAvatar.propTypes = { - isHeader: PropTypes.bool, - avatar: PropTypes.string, - emoji: PropTypes.string, - author: PropTypes.obj, - small: PropTypes.bool, - navToRoomInfo: PropTypes.func, - getCustomEmoji: PropTypes.func, - theme: PropTypes.string -}; -MessageAvatar.displayName = 'MessageAvatar'; - -export default MessageAvatar; diff --git a/app/containers/message/MessageAvatar.tsx b/app/containers/message/MessageAvatar.tsx new file mode 100644 index 000000000..3f330d819 --- /dev/null +++ b/app/containers/message/MessageAvatar.tsx @@ -0,0 +1,36 @@ +import React, { useContext } from 'react'; + +import Avatar from '../Avatar'; +import styles from './styles'; +import MessageContext from './Context'; +import { IMessageAvatar } from './interfaces'; + +const MessageAvatar = React.memo( + ({ isHeader, avatar, author, small, navToRoomInfo, emoji, getCustomEmoji, theme }: IMessageAvatar) => { + const { user } = useContext(MessageContext); + if (isHeader && author) { + const navParam = { + t: 'd', + rid: author._id + }; + return ( + <Avatar + style={small ? styles.avatarSmall : styles.avatar} + text={avatar ? '' : author.username} + size={small ? 20 : 36} + borderRadius={small ? 2 : 4} + onPress={author._id === user.id ? undefined : () => navToRoomInfo(navParam)} + getCustomEmoji={getCustomEmoji} + avatar={avatar} + emoji={emoji} + theme={theme} + /> + ); + } + return null; + } +); + +MessageAvatar.displayName = 'MessageAvatar'; + +export default MessageAvatar; diff --git a/app/containers/message/MessageError.js b/app/containers/message/MessageError.js deleted file mode 100644 index b3e3969a1..000000000 --- a/app/containers/message/MessageError.js +++ /dev/null @@ -1,29 +0,0 @@ -import React, { useContext } from 'react'; -import PropTypes from 'prop-types'; - -import Touchable from './Touchable'; -import { CustomIcon } from '../../lib/Icons'; -import styles from './styles'; -import { BUTTON_HIT_SLOP } from './utils'; -import { themes } from '../../constants/colors'; -import MessageContext from './Context'; - -const MessageError = React.memo(({ hasError, theme }) => { - if (!hasError) { - return null; - } - const { onErrorPress } = useContext(MessageContext); - return ( - <Touchable onPress={onErrorPress} style={styles.errorButton} hitSlop={BUTTON_HIT_SLOP}> - <CustomIcon name='warning' color={themes[theme].dangerColor} size={18} /> - </Touchable> - ); -}, (prevProps, nextProps) => prevProps.hasError === nextProps.hasError && prevProps.theme === nextProps.theme); - -MessageError.propTypes = { - hasError: PropTypes.bool, - theme: PropTypes.string -}; -MessageError.displayName = 'MessageError'; - -export default MessageError; diff --git a/app/containers/message/MessageError.tsx b/app/containers/message/MessageError.tsx new file mode 100644 index 000000000..5405f955d --- /dev/null +++ b/app/containers/message/MessageError.tsx @@ -0,0 +1,32 @@ +import React, { useContext } from 'react'; + +import Touchable from './Touchable'; +import { CustomIcon } from '../../lib/Icons'; +import styles from './styles'; +import { BUTTON_HIT_SLOP } from './utils'; +import { themes } from '../../constants/colors'; +import MessageContext from './Context'; + +interface IMessageError { + hasError: boolean; + theme: string; +} + +const MessageError = React.memo( + ({ hasError, theme }: IMessageError) => { + if (!hasError) { + return null; + } + const { onErrorPress } = useContext(MessageContext); + return ( + <Touchable onPress={onErrorPress} style={styles.errorButton} hitSlop={BUTTON_HIT_SLOP}> + <CustomIcon name='warning' color={themes[theme].dangerColor} size={18} /> + </Touchable> + ); + }, + (prevProps, nextProps) => prevProps.hasError === nextProps.hasError && prevProps.theme === nextProps.theme +); + +MessageError.displayName = 'MessageError'; + +export default MessageError; diff --git a/app/containers/message/Reactions.js b/app/containers/message/Reactions.tsx similarity index 60% rename from app/containers/message/Reactions.js rename to app/containers/message/Reactions.tsx index 7502057f6..823c4637d 100644 --- a/app/containers/message/Reactions.js +++ b/app/containers/message/Reactions.tsx @@ -1,6 +1,5 @@ import React, { useContext } from 'react'; -import { View, Text } from 'react-native'; -import PropTypes from 'prop-types'; +import { Text, View } from 'react-native'; import Touchable from './Touchable'; import { CustomIcon } from '../../lib/Icons'; @@ -11,7 +10,26 @@ import { themes } from '../../constants/colors'; import { withTheme } from '../../theme'; import MessageContext from './Context'; -const AddReaction = React.memo(({ theme }) => { +interface IMessageAddReaction { + theme: string; +} + +interface IMessageReaction { + reaction: { + usernames: []; + emoji: object; + }; + getCustomEmoji: Function; + theme: string; +} + +interface IMessageReactions { + reactions: object[]; + getCustomEmoji: Function; + theme: string; +} + +const AddReaction = React.memo(({ theme }: IMessageAddReaction) => { const { reactionInit } = useContext(MessageContext); return ( <Touchable @@ -20,8 +38,7 @@ const AddReaction = React.memo(({ theme }) => { testID='message-add-reaction' style={[styles.reactionButton, { backgroundColor: themes[theme].backgroundColor }]} background={Touchable.Ripple(themes[theme].bannerBackground)} - hitSlop={BUTTON_HIT_SLOP} - > + hitSlop={BUTTON_HIT_SLOP}> <View style={[styles.reactionContainer, { borderColor: themes[theme].borderColor }]}> <CustomIcon name='reaction-add' size={21} color={themes[theme].tintColor} /> </View> @@ -29,23 +46,21 @@ const AddReaction = React.memo(({ theme }) => { ); }); -const Reaction = React.memo(({ - reaction, getCustomEmoji, theme -}) => { - const { - onReactionPress, onReactionLongPress, baseUrl, user - } = useContext(MessageContext); - const reacted = reaction.usernames.findIndex(item => item === user.username) !== -1; +const Reaction = React.memo(({ reaction, getCustomEmoji, theme }: IMessageReaction) => { + const { onReactionPress, onReactionLongPress, baseUrl, user } = useContext(MessageContext); + const reacted = reaction.usernames.findIndex((item: IMessageReaction) => item === user.username) !== -1; return ( <Touchable onPress={() => onReactionPress(reaction.emoji)} onLongPress={onReactionLongPress} key={reaction.emoji} - testID={`message-reaction-${ reaction.emoji }`} - style={[styles.reactionButton, { backgroundColor: reacted ? themes[theme].bannerBackground : themes[theme].backgroundColor }]} + testID={`message-reaction-${reaction.emoji}`} + style={[ + styles.reactionButton, + { backgroundColor: reacted ? themes[theme].bannerBackground : themes[theme].backgroundColor } + ]} background={Touchable.Ripple(themes[theme].bannerBackground)} - hitSlop={BUTTON_HIT_SLOP} - > + hitSlop={BUTTON_HIT_SLOP}> <View style={[styles.reactionContainer, { borderColor: reacted ? themes[theme].tintColor : themes[theme].borderColor }]}> <Emoji content={reaction.emoji} @@ -54,50 +69,28 @@ const Reaction = React.memo(({ baseUrl={baseUrl} getCustomEmoji={getCustomEmoji} /> - <Text style={[styles.reactionCount, { color: themes[theme].tintColor }]}>{ reaction.usernames.length }</Text> + <Text style={[styles.reactionCount, { color: themes[theme].tintColor }]}>{reaction.usernames.length}</Text> </View> </Touchable> ); }); -const Reactions = React.memo(({ - reactions, getCustomEmoji, theme -}) => { +const Reactions = React.memo(({ reactions, getCustomEmoji, theme }: IMessageReactions) => { if (!Array.isArray(reactions) || reactions.length === 0) { return null; } return ( <View style={styles.reactionsContainer}> - {reactions.map(reaction => ( - <Reaction - key={reaction.emoji} - reaction={reaction} - getCustomEmoji={getCustomEmoji} - theme={theme} - /> + {reactions.map((reaction: any) => ( + <Reaction key={reaction.emoji} reaction={reaction} getCustomEmoji={getCustomEmoji} theme={theme} /> ))} <AddReaction theme={theme} /> </View> ); }); -Reaction.propTypes = { - reaction: PropTypes.object, - getCustomEmoji: PropTypes.func, - theme: PropTypes.string -}; Reaction.displayName = 'MessageReaction'; - -Reactions.propTypes = { - reactions: PropTypes.oneOfType([PropTypes.array, PropTypes.object]), - getCustomEmoji: PropTypes.func, - theme: PropTypes.string -}; Reactions.displayName = 'MessageReactions'; - -AddReaction.propTypes = { - theme: PropTypes.string -}; AddReaction.displayName = 'MessageAddReaction'; export default withTheme(Reactions); diff --git a/app/containers/message/ReadReceipt.js b/app/containers/message/ReadReceipt.tsx similarity index 74% rename from app/containers/message/ReadReceipt.js rename to app/containers/message/ReadReceipt.tsx index 5ca392f6d..8a5298eea 100644 --- a/app/containers/message/ReadReceipt.js +++ b/app/containers/message/ReadReceipt.tsx @@ -1,11 +1,16 @@ import React from 'react'; -import PropTypes from 'prop-types'; import { themes } from '../../constants/colors'; import { CustomIcon } from '../../lib/Icons'; import styles from './styles'; -const ReadReceipt = React.memo(({ isReadReceiptEnabled, unread, theme }) => { +interface IMessageReadReceipt { + isReadReceiptEnabled: boolean; + unread: boolean; + theme: string; +} + +const ReadReceipt = React.memo(({ isReadReceiptEnabled, unread, theme }: IMessageReadReceipt) => { if (isReadReceiptEnabled && !unread && unread !== null) { return <CustomIcon name='check' color={themes[theme].tintColor} size={15} style={styles.readReceipt} />; } @@ -13,10 +18,4 @@ const ReadReceipt = React.memo(({ isReadReceiptEnabled, unread, theme }) => { }); ReadReceipt.displayName = 'MessageReadReceipt'; -ReadReceipt.propTypes = { - isReadReceiptEnabled: PropTypes.bool, - unread: PropTypes.bool, - theme: PropTypes.bool -}; - export default ReadReceipt; diff --git a/app/containers/message/RepliedThread.js b/app/containers/message/RepliedThread.tsx similarity index 68% rename from app/containers/message/RepliedThread.js rename to app/containers/message/RepliedThread.tsx index 46be5b1f6..454ed852b 100644 --- a/app/containers/message/RepliedThread.js +++ b/app/containers/message/RepliedThread.tsx @@ -1,22 +1,20 @@ import React, { memo, useEffect, useState } from 'react'; import { View } from 'react-native'; -import PropTypes from 'prop-types'; import { CustomIcon } from '../../lib/Icons'; import styles from './styles'; import { themes } from '../../constants/colors'; import I18n from '../../i18n'; import Markdown from '../markdown'; +import { IMessageRepliedThread } from './interfaces'; -const RepliedThread = memo(({ - tmid, tmsg, isHeader, fetchThreadName, id, isEncrypted, theme -}) => { +const RepliedThread = memo(({ tmid, tmsg, isHeader, fetchThreadName, id, isEncrypted, theme }: IMessageRepliedThread) => { if (!tmid || !isHeader) { return null; } const [msg, setMsg] = useState(isEncrypted ? I18n.t('Encrypted_message') : tmsg); - const fetch = async() => { + const fetch = async () => { const threadName = await fetchThreadName(tmid, id); setMsg(threadName); }; @@ -32,8 +30,9 @@ const RepliedThread = memo(({ } return ( - <View style={styles.repliedThread} testID={`message-thread-replied-on-${ msg }`}> + <View style={styles.repliedThread} testID={`message-thread-replied-on-${msg}`}> <CustomIcon name='threads' size={20} style={styles.repliedThreadIcon} color={themes[theme].tintColor} /> + {/* @ts-ignore*/} <Markdown msg={msg} theme={theme} @@ -42,25 +41,12 @@ const RepliedThread = memo(({ numberOfLines={1} /> <View style={styles.repliedThreadDisclosure}> - <CustomIcon - name='chevron-right' - color={themes[theme].auxiliaryText} - size={20} - /> + <CustomIcon name='chevron-right' color={themes[theme].auxiliaryText} size={20} /> </View> </View> ); }); -RepliedThread.propTypes = { - tmid: PropTypes.string, - tmsg: PropTypes.string, - id: PropTypes.string, - isHeader: PropTypes.bool, - theme: PropTypes.string, - fetchThreadName: PropTypes.func, - isEncrypted: PropTypes.bool -}; RepliedThread.displayName = 'MessageRepliedThread'; export default RepliedThread; diff --git a/app/containers/message/Reply.js b/app/containers/message/Reply.js deleted file mode 100644 index 6faaf5109..000000000 --- a/app/containers/message/Reply.js +++ /dev/null @@ -1,274 +0,0 @@ -import React, { useContext } from 'react'; -import { View, Text, StyleSheet } from 'react-native'; -import PropTypes from 'prop-types'; -import moment from 'moment'; -import { transparentize } from 'color2k'; -import { dequal } from 'dequal'; -import FastImage from '@rocket.chat/react-native-fast-image'; - -import Touchable from './Touchable'; -import Markdown from '../markdown'; -import openLink from '../../utils/openLink'; -import sharedStyles from '../../views/Styles'; -import { themes } from '../../constants/colors'; -import MessageContext from './Context'; - -const styles = StyleSheet.create({ - button: { - flex: 1, - flexDirection: 'row', - alignItems: 'center', - marginTop: 6, - alignSelf: 'flex-start', - borderWidth: 1, - borderRadius: 4 - }, - attachmentContainer: { - flex: 1, - borderRadius: 4, - flexDirection: 'column', - padding: 15 - }, - authorContainer: { - flex: 1, - flexDirection: 'row', - alignItems: 'center' - }, - author: { - flex: 1, - fontSize: 16, - ...sharedStyles.textMedium - }, - time: { - fontSize: 12, - marginLeft: 10, - ...sharedStyles.textRegular, - fontWeight: '300' - }, - fieldsContainer: { - flex: 1, - flexWrap: 'wrap', - flexDirection: 'row' - }, - fieldContainer: { - flexDirection: 'column', - padding: 10 - }, - fieldTitle: { - fontSize: 14, - ...sharedStyles.textSemibold - }, - fieldValue: { - fontSize: 14, - ...sharedStyles.textRegular - }, - marginTop: { - marginTop: 4 - }, - marginBottom: { - marginBottom: 4 - }, - image: { - width: null, - height: 200, - flex: 1, - borderTopLeftRadius: 4, - borderTopRightRadius: 4, - marginBottom: 1 - }, - title: { - flex: 1, - fontSize: 16, - marginBottom: 3, - ...sharedStyles.textMedium - } -}); - -const Title = React.memo(({ attachment, timeFormat, theme }) => { - const time = attachment.message_link && attachment.ts ? moment(attachment.ts).format(timeFormat) : null; - return ( - <View style={styles.authorContainer}> - {attachment.author_name ? <Text style={[styles.author, { color: themes[theme].bodyText }]}>{attachment.author_name}</Text> : null} - {attachment.title ? <Text style={[styles.title, { color: themes[theme].bodyText }]}>{attachment.title}</Text> : null} - {time ? <Text style={[styles.time, { color: themes[theme].auxiliaryText }]}>{ time }</Text> : null} - </View> - ); -}); - -const Description = React.memo(({ - attachment, getCustomEmoji, theme -}) => { - const text = attachment.text || attachment.title; - if (!text) { - return null; - } - const { baseUrl, user } = useContext(MessageContext); - return ( - <Markdown - msg={text} - baseUrl={baseUrl} - username={user.username} - getCustomEmoji={getCustomEmoji} - theme={theme} - /> - ); -}, (prevProps, nextProps) => { - if (prevProps.attachment.text !== nextProps.attachment.text) { - return false; - } - if (prevProps.attachment.title !== nextProps.attachment.title) { - return false; - } - if (prevProps.theme !== nextProps.theme) { - return false; - } - return true; -}); - -const UrlImage = React.memo(({ image }) => { - if (!image) { - return null; - } - const { baseUrl, user } = useContext(MessageContext); - image = image.includes('http') ? image : `${ baseUrl }/${ image }?rc_uid=${ user.id }&rc_token=${ user.token }`; - return <FastImage source={{ uri: image }} style={styles.image} resizeMode={FastImage.resizeMode.cover} />; -}, (prevProps, nextProps) => prevProps.image === nextProps.image); - -const Fields = React.memo(({ attachment, theme, getCustomEmoji }) => { - if (!attachment.fields) { - return null; - } - - const { baseUrl, user } = useContext(MessageContext); - return ( - <View style={styles.fieldsContainer}> - {attachment.fields.map(field => ( - <View key={field.title} style={[styles.fieldContainer, { width: field.short ? '50%' : '100%' }]}> - <Text style={[styles.fieldTitle, { color: themes[theme].bodyText }]}>{field.title}</Text> - <Markdown - msg={field.value} - baseUrl={baseUrl} - username={user.username} - getCustomEmoji={getCustomEmoji} - theme={theme} - /> - </View> - ))} - </View> - ); -}, (prevProps, nextProps) => dequal(prevProps.attachment.fields, nextProps.attachment.fields) && prevProps.theme === nextProps.theme); - -const Reply = React.memo(({ - attachment, timeFormat, index, getCustomEmoji, theme -}) => { - if (!attachment) { - return null; - } - const { baseUrl, user, jumpToMessage } = useContext(MessageContext); - - const onPress = () => { - let url = attachment.title_link || attachment.author_link; - if (attachment.message_link) { - return jumpToMessage(attachment.message_link); - } - if (!url) { - return; - } - if (attachment.type === 'file') { - if (!url.startsWith('http')) { - url = `${ baseUrl }${ url }`; - } - url = `${ url }?rc_uid=${ user.id }&rc_token=${ user.token }`; - } - openLink(url, theme); - }; - - let { borderColor, chatComponentBackground: backgroundColor } = themes[theme]; - try { - if (attachment.color) { - backgroundColor = transparentize(attachment.color, 0.80); - borderColor = attachment.color; - } - } catch (e) { - // fallback to default - } - - return ( - <> - <Touchable - onPress={onPress} - style={[ - styles.button, - index > 0 && styles.marginTop, - attachment.description && styles.marginBottom, - { - backgroundColor, borderColor - } - ]} - background={Touchable.Ripple(themes[theme].bannerBackground)} - > - <View style={styles.attachmentContainer}> - <Title - attachment={attachment} - timeFormat={timeFormat} - theme={theme} - /> - <UrlImage image={attachment.thumb_url} /> - <Description - attachment={attachment} - getCustomEmoji={getCustomEmoji} - theme={theme} - /> - <Fields - attachment={attachment} - getCustomEmoji={getCustomEmoji} - theme={theme} - /> - </View> - </Touchable> - <Markdown - msg={attachment.description} - baseUrl={baseUrl} - username={user.username} - getCustomEmoji={getCustomEmoji} - theme={theme} - /> - </> - ); -}, (prevProps, nextProps) => dequal(prevProps.attachment, nextProps.attachment) && prevProps.theme === nextProps.theme); - -Reply.propTypes = { - attachment: PropTypes.object, - timeFormat: PropTypes.string, - index: PropTypes.number, - theme: PropTypes.string, - getCustomEmoji: PropTypes.func -}; -Reply.displayName = 'MessageReply'; - -UrlImage.propTypes = { - image: PropTypes.string -}; - -Title.propTypes = { - attachment: PropTypes.object, - timeFormat: PropTypes.string, - theme: PropTypes.string -}; -Title.displayName = 'MessageReplyTitle'; - -Description.propTypes = { - attachment: PropTypes.object, - getCustomEmoji: PropTypes.func, - theme: PropTypes.string -}; -Description.displayName = 'MessageReplyDescription'; - -Fields.propTypes = { - attachment: PropTypes.object, - theme: PropTypes.string, - getCustomEmoji: PropTypes.func -}; -Fields.displayName = 'MessageReplyFields'; - -export default Reply; diff --git a/app/containers/message/Reply.tsx b/app/containers/message/Reply.tsx new file mode 100644 index 000000000..5f4ca7657 --- /dev/null +++ b/app/containers/message/Reply.tsx @@ -0,0 +1,284 @@ +import React, { useContext } from 'react'; +import { StyleSheet, Text, View } from 'react-native'; +import moment from 'moment'; +import { transparentize } from 'color2k'; +import { dequal } from 'dequal'; +import FastImage from '@rocket.chat/react-native-fast-image'; + +import Touchable from './Touchable'; +import Markdown from '../markdown'; +import openLink from '../../utils/openLink'; +import sharedStyles from '../../views/Styles'; +import { themes } from '../../constants/colors'; +import MessageContext from './Context'; + +const styles = StyleSheet.create({ + button: { + flex: 1, + flexDirection: 'row', + alignItems: 'center', + marginTop: 6, + alignSelf: 'flex-start', + borderWidth: 1, + borderRadius: 4 + }, + attachmentContainer: { + flex: 1, + borderRadius: 4, + flexDirection: 'column', + padding: 15 + }, + authorContainer: { + flex: 1, + flexDirection: 'row', + alignItems: 'center' + }, + author: { + flex: 1, + fontSize: 16, + ...sharedStyles.textMedium + }, + time: { + fontSize: 12, + marginLeft: 10, + ...sharedStyles.textRegular, + fontWeight: '300' + }, + fieldsContainer: { + flex: 1, + flexWrap: 'wrap', + flexDirection: 'row' + }, + fieldContainer: { + flexDirection: 'column', + padding: 10 + }, + fieldTitle: { + fontSize: 14, + ...sharedStyles.textSemibold + }, + fieldValue: { + fontSize: 14, + ...sharedStyles.textRegular + }, + marginTop: { + marginTop: 4 + }, + marginBottom: { + marginBottom: 4 + }, + image: { + // @ts-ignore + width: null, + height: 200, + flex: 1, + borderTopLeftRadius: 4, + borderTopRightRadius: 4, + marginBottom: 1 + }, + title: { + flex: 1, + fontSize: 16, + marginBottom: 3, + ...sharedStyles.textMedium + } +}); + +interface IMessageReplyAttachment { + author_name: string; + message_link: string; + ts: string; + text: string; + title: string; + short: boolean; + value: string; + title_link: string; + author_link: string; + type: string; + color: string; + description: string; + fields: IMessageReplyAttachment[]; + thumb_url: string; +} + +interface IMessageTitle { + attachment: Partial<IMessageReplyAttachment>; + timeFormat: string; + theme: string; +} + +interface IMessageDescription { + attachment: Partial<IMessageReplyAttachment>; + getCustomEmoji: Function; + theme: string; +} + +interface IMessageFields { + attachment: Partial<IMessageReplyAttachment>; + theme: string; + getCustomEmoji: Function; +} + +interface IMessageReply { + attachment: Partial<IMessageReplyAttachment>; + timeFormat: string; + index: number; + theme: string; + getCustomEmoji: Function; +} + +const Title = React.memo(({ attachment, timeFormat, theme }: IMessageTitle) => { + const time = attachment.message_link && attachment.ts ? moment(attachment.ts).format(timeFormat) : null; + return ( + <View style={styles.authorContainer}> + {attachment.author_name ? ( + <Text style={[styles.author, { color: themes[theme].bodyText }]}>{attachment.author_name}</Text> + ) : null} + {attachment.title ? <Text style={[styles.title, { color: themes[theme].bodyText }]}>{attachment.title}</Text> : null} + {time ? <Text style={[styles.time, { color: themes[theme].auxiliaryText }]}>{time}</Text> : null} + </View> + ); +}); + +const Description = React.memo( + ({ attachment, getCustomEmoji, theme }: IMessageDescription) => { + const text = attachment.text || attachment.title; + if (!text) { + return null; + } + const { baseUrl, user } = useContext(MessageContext); + return ( + // @ts-ignore + <Markdown msg={text} baseUrl={baseUrl} username={user.username} getCustomEmoji={getCustomEmoji} theme={theme} /> + ); + }, + (prevProps, nextProps) => { + if (prevProps.attachment.text !== nextProps.attachment.text) { + return false; + } + if (prevProps.attachment.title !== nextProps.attachment.title) { + return false; + } + if (prevProps.theme !== nextProps.theme) { + return false; + } + return true; + } +); + +const UrlImage = React.memo( + ({ image }: any) => { + if (!image) { + return null; + } + const { baseUrl, user } = useContext(MessageContext); + image = image.includes('http') ? image : `${baseUrl}/${image}?rc_uid=${user.id}&rc_token=${user.token}`; + return <FastImage source={{ uri: image }} style={styles.image} resizeMode={FastImage.resizeMode.cover} />; + }, + (prevProps, nextProps) => prevProps.image === nextProps.image +); + +const Fields = React.memo( + ({ attachment, theme, getCustomEmoji }: IMessageFields) => { + if (!attachment.fields) { + return null; + } + + const { baseUrl, user } = useContext(MessageContext); + return ( + <View style={styles.fieldsContainer}> + {attachment.fields.map(field => ( + <View key={field.title} style={[styles.fieldContainer, { width: field.short ? '50%' : '100%' }]}> + <Text style={[styles.fieldTitle, { color: themes[theme].bodyText }]}>{field.title}</Text> + {/* @ts-ignore*/} + <Markdown + msg={field.value} + baseUrl={baseUrl} + username={user.username} + getCustomEmoji={getCustomEmoji} + theme={theme} + /> + </View> + ))} + </View> + ); + }, + (prevProps, nextProps) => + dequal(prevProps.attachment.fields, nextProps.attachment.fields) && prevProps.theme === nextProps.theme +); + +const Reply = React.memo( + ({ attachment, timeFormat, index, getCustomEmoji, theme }: IMessageReply) => { + if (!attachment) { + return null; + } + const { baseUrl, user, jumpToMessage } = useContext(MessageContext); + + const onPress = () => { + let url = attachment.title_link || attachment.author_link; + if (attachment.message_link) { + return jumpToMessage(attachment.message_link); + } + if (!url) { + return; + } + if (attachment.type === 'file') { + if (!url.startsWith('http')) { + url = `${baseUrl}${url}`; + } + url = `${url}?rc_uid=${user.id}&rc_token=${user.token}`; + } + openLink(url, theme); + }; + + let { borderColor, chatComponentBackground: backgroundColor } = themes[theme]; + try { + if (attachment.color) { + backgroundColor = transparentize(attachment.color, 0.8); + borderColor = attachment.color; + } + } catch (e) { + // fallback to default + } + + return ( + <> + <Touchable + onPress={onPress} + style={[ + styles.button, + index > 0 && styles.marginTop, + attachment.description && styles.marginBottom, + { + backgroundColor, + borderColor + } + ]} + background={Touchable.Ripple(themes[theme].bannerBackground)}> + <View style={styles.attachmentContainer}> + <Title attachment={attachment} timeFormat={timeFormat} theme={theme} /> + <UrlImage image={attachment.thumb_url} /> + <Description attachment={attachment} getCustomEmoji={getCustomEmoji} theme={theme} /> + <Fields attachment={attachment} getCustomEmoji={getCustomEmoji} theme={theme} /> + </View> + </Touchable> + {/* @ts-ignore*/} + <Markdown + msg={attachment.description!} + baseUrl={baseUrl} + username={user.username} + getCustomEmoji={getCustomEmoji} + theme={theme} + /> + </> + ); + }, + (prevProps, nextProps) => dequal(prevProps.attachment, nextProps.attachment) && prevProps.theme === nextProps.theme +); + +Reply.displayName = 'MessageReply'; +Title.displayName = 'MessageReplyTitle'; +Description.displayName = 'MessageReplyDescription'; +Fields.displayName = 'MessageReplyFields'; + +export default Reply; diff --git a/app/containers/message/Thread.js b/app/containers/message/Thread.js deleted file mode 100644 index 47d9c8692..000000000 --- a/app/containers/message/Thread.js +++ /dev/null @@ -1,63 +0,0 @@ -import React, { useContext } from 'react'; -import { View, Text } from 'react-native'; -import PropTypes from 'prop-types'; - -import styles from './styles'; -import { themes } from '../../constants/colors'; -import MessageContext from './Context'; -import ThreadDetails from '../ThreadDetails'; -import I18n from '../../i18n'; - -const Thread = React.memo(({ - msg, tcount, tlm, isThreadRoom, theme, id -}) => { - if (!tlm || isThreadRoom || tcount === 0) { - return null; - } - - const { - threadBadgeColor, toggleFollowThread, user, replies - } = useContext(MessageContext); - return ( - <View style={styles.buttonContainer}> - <View - style={[styles.button, { backgroundColor: themes[theme].tintColor }]} - testID={`message-thread-button-${ msg }`} - > - <Text style={[styles.buttonText, { color: themes[theme].buttonText }]}>{I18n.t('Reply')}</Text> - </View> - <ThreadDetails - item={{ - tcount, - replies, - tlm, - id - }} - user={user} - badgeColor={threadBadgeColor} - toggleFollowThread={toggleFollowThread} - style={styles.threadDetails} - /> - </View> - ); -}, (prevProps, nextProps) => { - if (prevProps.tcount !== nextProps.tcount) { - return false; - } - if (prevProps.theme !== nextProps.theme) { - return false; - } - return true; -}); - -Thread.propTypes = { - msg: PropTypes.string, - tcount: PropTypes.string, - theme: PropTypes.string, - tlm: PropTypes.string, - isThreadRoom: PropTypes.bool, - id: PropTypes.string -}; -Thread.displayName = 'MessageThread'; - -export default Thread; diff --git a/app/containers/message/Thread.tsx b/app/containers/message/Thread.tsx new file mode 100644 index 000000000..16ed35a4b --- /dev/null +++ b/app/containers/message/Thread.tsx @@ -0,0 +1,51 @@ +import React, { useContext } from 'react'; +import { Text, View } from 'react-native'; + +import styles from './styles'; +import { themes } from '../../constants/colors'; +import MessageContext from './Context'; +import ThreadDetails from '../ThreadDetails'; +import I18n from '../../i18n'; +import { IMessageThread } from './interfaces'; + +const Thread = React.memo( + ({ msg, tcount, tlm, isThreadRoom, theme, id }: IMessageThread) => { + if (!tlm || isThreadRoom || tcount === 0) { + return null; + } + + const { threadBadgeColor, toggleFollowThread, user, replies } = useContext(MessageContext); + return ( + <View style={styles.buttonContainer}> + <View style={[styles.button, { backgroundColor: themes[theme].tintColor }]} testID={`message-thread-button-${msg}`}> + <Text style={[styles.buttonText, { color: themes[theme].buttonText }]}>{I18n.t('Reply')}</Text> + </View> + <ThreadDetails + item={{ + tcount, + replies, + tlm, + id + }} + user={user} + badgeColor={threadBadgeColor} + toggleFollowThread={toggleFollowThread} + style={styles.threadDetails} + /> + </View> + ); + }, + (prevProps, nextProps) => { + if (prevProps.tcount !== nextProps.tcount) { + return false; + } + if (prevProps.theme !== nextProps.theme) { + return false; + } + return true; + } +); + +Thread.displayName = 'MessageThread'; + +export default Thread; diff --git a/app/containers/message/Touchable.js b/app/containers/message/Touchable.tsx similarity index 57% rename from app/containers/message/Touchable.js rename to app/containers/message/Touchable.tsx index edd2d63e5..6bab16bec 100644 --- a/app/containers/message/Touchable.js +++ b/app/containers/message/Touchable.tsx @@ -1,25 +1,20 @@ import React, { useContext } from 'react'; import Touchable from 'react-native-platform-touchable'; -import PropTypes from 'prop-types'; import MessageContext from './Context'; -const RCTouchable = React.memo(({ children, ...props }) => { +const RCTouchable: any = React.memo(({ children, ...props }: any) => { const { onLongPress } = useContext(MessageContext); return ( - <Touchable - onLongPress={onLongPress} - {...props} - > + <Touchable onLongPress={onLongPress} {...props}> {children} </Touchable> ); }); -RCTouchable.propTypes = { - children: PropTypes.node -}; -RCTouchable.Ripple = (...args) => Touchable.Ripple(...args); + +// @ts-ignore +RCTouchable.Ripple = (...args: any[]) => Touchable.Ripple(...args); RCTouchable.SelectableBackgroundBorderless = () => Touchable.SelectableBackgroundBorderless(); export default RCTouchable; diff --git a/app/containers/message/Urls.js b/app/containers/message/Urls.js deleted file mode 100644 index b82d029af..000000000 --- a/app/containers/message/Urls.js +++ /dev/null @@ -1,152 +0,0 @@ -import React, { useContext } from 'react'; -import { - View, Text, StyleSheet, Clipboard -} from 'react-native'; -import PropTypes from 'prop-types'; -import FastImage from '@rocket.chat/react-native-fast-image'; -import { dequal } from 'dequal'; - -import Touchable from './Touchable'; -import openLink from '../../utils/openLink'; -import sharedStyles from '../../views/Styles'; -import { themes } from '../../constants/colors'; -import { withTheme } from '../../theme'; -import { LISTENER } from '../Toast'; -import EventEmitter from '../../utils/events'; -import I18n from '../../i18n'; -import MessageContext from './Context'; - -const styles = StyleSheet.create({ - button: { - marginTop: 6 - }, - container: { - flex: 1, - flexDirection: 'column', - borderRadius: 4, - borderWidth: 1 - }, - textContainer: { - flex: 1, - flexDirection: 'column', - padding: 15, - justifyContent: 'flex-start', - alignItems: 'flex-start' - }, - title: { - fontSize: 16, - ...sharedStyles.textMedium - }, - description: { - fontSize: 16, - ...sharedStyles.textRegular - }, - marginTop: { - marginTop: 4 - }, - image: { - width: '100%', - height: 150, - borderTopLeftRadius: 4, - borderTopRightRadius: 4 - } -}); - -const UrlImage = React.memo(({ image }) => { - if (!image) { - return null; - } - const { baseUrl, user } = useContext(MessageContext); - image = image.includes('http') ? image : `${ baseUrl }/${ image }?rc_uid=${ user.id }&rc_token=${ user.token }`; - return <FastImage source={{ uri: image }} style={styles.image} resizeMode={FastImage.resizeMode.cover} />; -}, (prevProps, nextProps) => prevProps.image === nextProps.image); - -const UrlContent = React.memo(({ title, description, theme }) => ( - <View style={styles.textContainer}> - {title ? <Text style={[styles.title, { color: themes[theme].tintColor }]} numberOfLines={2}>{title}</Text> : null} - {description ? <Text style={[styles.description, { color: themes[theme].auxiliaryText }]} numberOfLines={2}>{description}</Text> : null} - </View> -), (prevProps, nextProps) => { - if (prevProps.title !== nextProps.title) { - return false; - } - if (prevProps.description !== nextProps.description) { - return false; - } - if (prevProps.theme !== nextProps.theme) { - return false; - } - return true; -}); - -const Url = React.memo(({ url, index, theme }) => { - if (!url || url?.ignoreParse) { - return null; - } - - const onPress = () => openLink(url.url, theme); - - const onLongPress = () => { - Clipboard.setString(url.url); - EventEmitter.emit(LISTENER, { message: I18n.t('Copied_to_clipboard') }); - }; - - return ( - <Touchable - onPress={onPress} - onLongPress={onLongPress} - style={[ - styles.button, - index > 0 && styles.marginTop, - styles.container, - { - backgroundColor: themes[theme].chatComponentBackground, - borderColor: themes[theme].borderColor - } - ]} - background={Touchable.Ripple(themes[theme].bannerBackground)} - > - <> - <UrlImage image={url.image} /> - <UrlContent title={url.title} description={url.description} theme={theme} /> - </> - </Touchable> - ); -}, (oldProps, newProps) => dequal(oldProps.url, newProps.url) && oldProps.theme === newProps.theme); - -const Urls = React.memo(({ urls, theme }) => { - if (!urls || urls.length === 0) { - return null; - } - - return urls.map((url, index) => ( - <Url url={url} key={url.url} index={index} theme={theme} /> - )); -}, (oldProps, newProps) => dequal(oldProps.urls, newProps.urls) && oldProps.theme === newProps.theme); - -UrlImage.propTypes = { - image: PropTypes.string -}; -UrlImage.displayName = 'MessageUrlImage'; - -UrlContent.propTypes = { - title: PropTypes.string, - description: PropTypes.string, - theme: PropTypes.string -}; -UrlContent.displayName = 'MessageUrlContent'; - -Url.propTypes = { - url: PropTypes.object.isRequired, - index: PropTypes.number, - theme: PropTypes.string -}; -Url.displayName = 'MessageUrl'; - -Urls.propTypes = { - urls: PropTypes.array, - theme: PropTypes.string -}; -Urls.displayName = 'MessageUrls'; - -export default withTheme(Urls); diff --git a/app/containers/message/Urls.tsx b/app/containers/message/Urls.tsx new file mode 100644 index 000000000..f847fa146 --- /dev/null +++ b/app/containers/message/Urls.tsx @@ -0,0 +1,169 @@ +import React, { useContext } from 'react'; +import { Clipboard, StyleSheet, Text, View } from 'react-native'; +import FastImage from '@rocket.chat/react-native-fast-image'; +import { dequal } from 'dequal'; + +import Touchable from './Touchable'; +import openLink from '../../utils/openLink'; +import sharedStyles from '../../views/Styles'; +import { themes } from '../../constants/colors'; +import { withTheme } from '../../theme'; +import { LISTENER } from '../Toast'; +import EventEmitter from '../../utils/events'; +import I18n from '../../i18n'; +import MessageContext from './Context'; + +const styles = StyleSheet.create({ + button: { + marginTop: 6 + }, + container: { + flex: 1, + flexDirection: 'column', + borderRadius: 4, + borderWidth: 1 + }, + textContainer: { + flex: 1, + flexDirection: 'column', + padding: 15, + justifyContent: 'flex-start', + alignItems: 'flex-start' + }, + title: { + fontSize: 16, + ...sharedStyles.textMedium + }, + description: { + fontSize: 16, + ...sharedStyles.textRegular + }, + marginTop: { + marginTop: 4 + }, + image: { + width: '100%', + height: 150, + borderTopLeftRadius: 4, + borderTopRightRadius: 4 + } +}); + +interface IMessageUrlContent { + title: string; + description: string; + theme: string; +} + +interface IMessageUrl { + url: { + ignoreParse: boolean; + url: string; + image: string; + title: string; + description: string; + }; + index: number; + theme: string; +} + +interface IMessageUrls { + urls: any; + theme: string; +} + +const UrlImage = React.memo( + ({ image }: { image: string }) => { + if (!image) { + return null; + } + const { baseUrl, user } = useContext(MessageContext); + image = image.includes('http') ? image : `${baseUrl}/${image}?rc_uid=${user.id}&rc_token=${user.token}`; + return <FastImage source={{ uri: image }} style={styles.image} resizeMode={FastImage.resizeMode.cover} />; + }, + (prevProps, nextProps) => prevProps.image === nextProps.image +); + +const UrlContent = React.memo( + ({ title, description, theme }: IMessageUrlContent) => ( + <View style={styles.textContainer}> + {title ? ( + <Text style={[styles.title, { color: themes[theme].tintColor }]} numberOfLines={2}> + {title} + </Text> + ) : null} + {description ? ( + <Text style={[styles.description, { color: themes[theme].auxiliaryText }]} numberOfLines={2}> + {description} + </Text> + ) : null} + </View> + ), + (prevProps, nextProps) => { + if (prevProps.title !== nextProps.title) { + return false; + } + if (prevProps.description !== nextProps.description) { + return false; + } + if (prevProps.theme !== nextProps.theme) { + return false; + } + return true; + } +); + +const Url = React.memo( + ({ url, index, theme }: IMessageUrl) => { + if (!url || url?.ignoreParse) { + return null; + } + + const onPress = () => openLink(url.url, theme); + + const onLongPress = () => { + Clipboard.setString(url.url); + EventEmitter.emit(LISTENER, { message: I18n.t('Copied_to_clipboard') }); + }; + + return ( + <Touchable + onPress={onPress} + onLongPress={onLongPress} + style={[ + styles.button, + index > 0 && styles.marginTop, + styles.container, + { + backgroundColor: themes[theme].chatComponentBackground, + borderColor: themes[theme].borderColor + } + ]} + background={Touchable.Ripple(themes[theme].bannerBackground)}> + <> + <UrlImage image={url.image} /> + <UrlContent title={url.title} description={url.description} theme={theme} /> + </> + </Touchable> + ); + }, + (oldProps, newProps) => dequal(oldProps.url, newProps.url) && oldProps.theme === newProps.theme +); + +const Urls = React.memo( + ({ urls, theme }: IMessageUrls) => { + if (!urls || urls.length === 0) { + return null; + } + + return urls.map((url: any, index: number) => <Url url={url} key={url.url} index={index} theme={theme} />); + }, + (oldProps, newProps) => dequal(oldProps.urls, newProps.urls) && oldProps.theme === newProps.theme +); + +UrlImage.displayName = 'MessageUrlImage'; +UrlContent.displayName = 'MessageUrlContent'; +Url.displayName = 'MessageUrl'; +Urls.displayName = 'MessageUrls'; + +export default withTheme(Urls); diff --git a/app/containers/message/User.js b/app/containers/message/User.js deleted file mode 100644 index 98b449849..000000000 --- a/app/containers/message/User.js +++ /dev/null @@ -1,111 +0,0 @@ -import React, { useContext } from 'react'; -import PropTypes from 'prop-types'; -import { - View, Text, StyleSheet, TouchableOpacity -} from 'react-native'; -import moment from 'moment'; - -import { themes } from '../../constants/colors'; -import { withTheme } from '../../theme'; - -import MessageError from './MessageError'; -import sharedStyles from '../../views/Styles'; -import messageStyles from './styles'; -import MessageContext from './Context'; -import { SYSTEM_MESSAGE_TYPES_WITH_AUTHOR_NAME } from './utils'; - -const styles = StyleSheet.create({ - container: { - flex: 1, - flexDirection: 'row', - justifyContent: 'space-between', - alignItems: 'center' - }, - username: { - fontSize: 16, - lineHeight: 22, - ...sharedStyles.textMedium - }, - usernameInfoMessage: { - fontSize: 16, - ...sharedStyles.textMedium - }, - titleContainer: { - flexShrink: 1, - flexDirection: 'row', - alignItems: 'center' - }, - alias: { - fontSize: 14, - ...sharedStyles.textRegular - } -}); - -const User = React.memo(({ - isHeader, useRealName, author, alias, ts, timeFormat, hasError, theme, navToRoomInfo, type, ...props -}) => { - if (isHeader || hasError) { - const navParam = { - t: 'd', - rid: author._id - }; - const { user } = useContext(MessageContext); - const username = (useRealName && author.name) || author.username; - const aliasUsername = alias ? (<Text style={[styles.alias, { color: themes[theme].auxiliaryText }]}> @{username}</Text>) : null; - const time = moment(ts).format(timeFormat); - const onUserPress = () => navToRoomInfo(navParam); - const isDisabled = author._id === user.id; - - const textContent = ( - <> - {alias || username} - {aliasUsername} - </> - ); - - if (SYSTEM_MESSAGE_TYPES_WITH_AUTHOR_NAME.includes(type)) { - return ( - <Text - style={[styles.usernameInfoMessage, { color: themes[theme].titleText }]} - onPress={onUserPress} - disabled={isDisabled} - > - {textContent} - </Text> - ); - } - - return ( - <View style={styles.container}> - <TouchableOpacity - style={styles.titleContainer} - onPress={onUserPress} - disabled={isDisabled} - > - <Text style={[styles.username, { color: themes[theme].titleText }]} numberOfLines={1}> - {textContent} - </Text> - </TouchableOpacity> - <Text style={[messageStyles.time, { color: themes[theme].auxiliaryText }]}>{time}</Text> - { hasError && <MessageError hasError={hasError} theme={theme} {...props} /> } - </View> - ); - } - return null; -}); - -User.propTypes = { - isHeader: PropTypes.bool, - hasError: PropTypes.bool, - useRealName: PropTypes.bool, - author: PropTypes.object, - alias: PropTypes.string, - ts: PropTypes.instanceOf(Date), - timeFormat: PropTypes.string, - theme: PropTypes.string, - navToRoomInfo: PropTypes.func, - type: PropTypes.string -}; -User.displayName = 'MessageUser'; - -export default withTheme(User); diff --git a/app/containers/message/User.tsx b/app/containers/message/User.tsx new file mode 100644 index 000000000..73adf316e --- /dev/null +++ b/app/containers/message/User.tsx @@ -0,0 +1,110 @@ +import React, { useContext } from 'react'; +import { StyleSheet, Text, TouchableOpacity, View } from 'react-native'; +import moment from 'moment'; + +import { themes } from '../../constants/colors'; +import { withTheme } from '../../theme'; +import MessageError from './MessageError'; +import sharedStyles from '../../views/Styles'; +import messageStyles from './styles'; +import MessageContext from './Context'; +import { SYSTEM_MESSAGE_TYPES_WITH_AUTHOR_NAME } from './utils'; + +const styles = StyleSheet.create({ + container: { + flex: 1, + flexDirection: 'row', + justifyContent: 'space-between', + alignItems: 'center' + }, + username: { + fontSize: 16, + lineHeight: 22, + ...sharedStyles.textMedium + }, + usernameInfoMessage: { + fontSize: 16, + ...sharedStyles.textMedium + }, + titleContainer: { + flexShrink: 1, + flexDirection: 'row', + alignItems: 'center' + }, + alias: { + fontSize: 14, + ...sharedStyles.textRegular + } +}); + +interface IMessageUser { + isHeader: boolean; + hasError: boolean; + useRealName: boolean; + author: { + _id: string; + name: string; + username: string; + }; + alias: string; + ts: Date; + timeFormat: string; + theme: string; + navToRoomInfo: Function; + type: string; +} + +const User = React.memo( + ({ isHeader, useRealName, author, alias, ts, timeFormat, hasError, theme, navToRoomInfo, type, ...props }: IMessageUser) => { + if (isHeader || hasError) { + const navParam = { + t: 'd', + rid: author._id + }; + const { user } = useContext(MessageContext); + const username = (useRealName && author.name) || author.username; + const aliasUsername = alias ? ( + <Text style={[styles.alias, { color: themes[theme].auxiliaryText }]}> @{username}</Text> + ) : null; + const time = moment(ts).format(timeFormat); + const onUserPress = () => navToRoomInfo(navParam); + const isDisabled = author._id === user.id; + + const textContent = ( + <> + {alias || username} + {aliasUsername} + </> + ); + + if (SYSTEM_MESSAGE_TYPES_WITH_AUTHOR_NAME.includes(type)) { + return ( + <Text + style={[styles.usernameInfoMessage, { color: themes[theme].titleText }]} + onPress={onUserPress} + // @ts-ignore + disabled={isDisabled}> + {textContent} + </Text> + ); + } + + return ( + <View style={styles.container}> + <TouchableOpacity style={styles.titleContainer} onPress={onUserPress} disabled={isDisabled}> + <Text style={[styles.username, { color: themes[theme].titleText }]} numberOfLines={1}> + {textContent} + </Text> + </TouchableOpacity> + <Text style={[messageStyles.time, { color: themes[theme].auxiliaryText }]}>{time}</Text> + {hasError && <MessageError hasError={hasError} theme={theme} {...props} />} + </View> + ); + } + return null; + } +); + +User.displayName = 'MessageUser'; + +export default withTheme(User); diff --git a/app/containers/message/Video.js b/app/containers/message/Video.js deleted file mode 100644 index fed0c67f2..000000000 --- a/app/containers/message/Video.js +++ /dev/null @@ -1,69 +0,0 @@ -import React, { useContext } from 'react'; -import PropTypes from 'prop-types'; -import { StyleSheet } from 'react-native'; -import { dequal } from 'dequal'; - -import Touchable from './Touchable'; -import Markdown from '../markdown'; -import openLink from '../../utils/openLink'; -import { isIOS } from '../../utils/deviceInfo'; -import { CustomIcon } from '../../lib/Icons'; -import { formatAttachmentUrl } from '../../lib/utils'; -import { themes } from '../../constants/colors'; -import MessageContext from './Context'; - -const SUPPORTED_TYPES = ['video/quicktime', 'video/mp4', ...(isIOS ? [] : ['video/3gp', 'video/mkv'])]; -const isTypeSupported = type => SUPPORTED_TYPES.indexOf(type) !== -1; - -const styles = StyleSheet.create({ - button: { - flex: 1, - borderRadius: 4, - height: 150, - marginBottom: 6, - alignItems: 'center', - justifyContent: 'center' - } -}); - -const Video = React.memo(({ - file, showAttachment, getCustomEmoji, theme -}) => { - const { baseUrl, user } = useContext(MessageContext); - if (!baseUrl) { - return null; - } - const onPress = () => { - if (isTypeSupported(file.video_type)) { - return showAttachment(file); - } - const uri = formatAttachmentUrl(file.video_url, user.id, user.token, baseUrl); - openLink(uri, theme); - }; - - return ( - <> - <Touchable - onPress={onPress} - style={[styles.button, { backgroundColor: themes[theme].videoBackground }]} - background={Touchable.Ripple(themes[theme].bannerBackground)} - > - <CustomIcon - name='play-filled' - size={54} - color={themes[theme].buttonText} - /> - </Touchable> - <Markdown msg={file.description} baseUrl={baseUrl} username={user.username} getCustomEmoji={getCustomEmoji} theme={theme} /> - </> - ); -}, (prevProps, nextProps) => dequal(prevProps.file, nextProps.file) && prevProps.theme === nextProps.theme); - -Video.propTypes = { - file: PropTypes.object, - showAttachment: PropTypes.func, - getCustomEmoji: PropTypes.func, - theme: PropTypes.string -}; - -export default Video; diff --git a/app/containers/message/Video.tsx b/app/containers/message/Video.tsx new file mode 100644 index 000000000..5a9761412 --- /dev/null +++ b/app/containers/message/Video.tsx @@ -0,0 +1,75 @@ +import React, { useContext } from 'react'; +import { StyleSheet } from 'react-native'; +import { dequal } from 'dequal'; + +import Touchable from './Touchable'; +import Markdown from '../markdown'; +import openLink from '../../utils/openLink'; +import { isIOS } from '../../utils/deviceInfo'; +import { CustomIcon } from '../../lib/Icons'; +import { formatAttachmentUrl } from '../../lib/utils'; +import { themes } from '../../constants/colors'; +import MessageContext from './Context'; + +const SUPPORTED_TYPES = ['video/quicktime', 'video/mp4', ...(isIOS ? [] : ['video/3gp', 'video/mkv'])]; +const isTypeSupported = (type: any) => SUPPORTED_TYPES.indexOf(type) !== -1; + +const styles = StyleSheet.create({ + button: { + flex: 1, + borderRadius: 4, + height: 150, + marginBottom: 6, + alignItems: 'center', + justifyContent: 'center' + } +}); + +interface IMessageVideo { + file: { + video_type: string; + video_url: string; + description: string; + }; + showAttachment: Function; + getCustomEmoji: Function; + theme: string; +} + +const Video = React.memo( + ({ file, showAttachment, getCustomEmoji, theme }: IMessageVideo) => { + const { baseUrl, user } = useContext(MessageContext); + if (!baseUrl) { + return null; + } + const onPress = () => { + if (isTypeSupported(file.video_type)) { + return showAttachment(file); + } + const uri = formatAttachmentUrl(file.video_url, user.id, user.token, baseUrl); + openLink(uri, theme); + }; + + return ( + <> + <Touchable + onPress={onPress} + style={[styles.button, { backgroundColor: themes[theme].videoBackground }]} + background={Touchable.Ripple(themes[theme].bannerBackground)}> + <CustomIcon name='play-filled' size={54} color={themes[theme].buttonText} /> + </Touchable> + {/* @ts-ignore*/} + <Markdown + msg={file.description} + baseUrl={baseUrl} + username={user.username} + getCustomEmoji={getCustomEmoji} + theme={theme} + /> + </> + ); + }, + (prevProps, nextProps) => dequal(prevProps.file, nextProps.file) && prevProps.theme === nextProps.theme +); + +export default Video; diff --git a/app/containers/message/constants.js b/app/containers/message/constants.ts similarity index 100% rename from app/containers/message/constants.js rename to app/containers/message/constants.ts diff --git a/app/containers/message/index.js b/app/containers/message/index.tsx similarity index 72% rename from app/containers/message/index.js rename to app/containers/message/index.tsx index 1583b204a..2847e086e 100644 --- a/app/containers/message/index.js +++ b/app/containers/message/index.tsx @@ -1,5 +1,4 @@ import React from 'react'; -import PropTypes from 'prop-types'; import { Keyboard } from 'react-native'; import Message from './Message'; @@ -11,53 +10,60 @@ import messagesStatus from '../../constants/messagesStatus'; import { withTheme } from '../../theme'; import openLink from '../../utils/openLink'; -class MessageContainer extends React.Component { - static propTypes = { - item: PropTypes.object.isRequired, - user: PropTypes.shape({ - id: PropTypes.string.isRequired, - username: PropTypes.string.isRequired, - token: PropTypes.string.isRequired - }), - rid: PropTypes.string, - timeFormat: PropTypes.string, - style: PropTypes.any, - archived: PropTypes.bool, - broadcast: PropTypes.bool, - previousItem: PropTypes.object, - baseUrl: PropTypes.string, - Message_GroupingPeriod: PropTypes.number, - isReadReceiptEnabled: PropTypes.bool, - isThreadRoom: PropTypes.bool, - useRealName: PropTypes.bool, - autoTranslateRoom: PropTypes.bool, - autoTranslateLanguage: PropTypes.string, - status: PropTypes.number, - isIgnored: PropTypes.bool, - highlighted: PropTypes.bool, - getCustomEmoji: PropTypes.func, - onLongPress: PropTypes.func, - onReactionPress: PropTypes.func, - onEncryptedPress: PropTypes.func, - onDiscussionPress: PropTypes.func, - onThreadPress: PropTypes.func, - onAnswerButtonPress: PropTypes.func, - errorActionsShow: PropTypes.func, - replyBroadcast: PropTypes.func, - reactionInit: PropTypes.func, - fetchThreadName: PropTypes.func, - showAttachment: PropTypes.func, - onReactionLongPress: PropTypes.func, - navToRoomInfo: PropTypes.func, - callJitsi: PropTypes.func, - blockAction: PropTypes.func, - theme: PropTypes.string, - threadBadgeColor: PropTypes.string, - toggleFollowThread: PropTypes.func, - jumpToMessage: PropTypes.func, - onPress: PropTypes.func - } +interface IMessageContainerProps { + item: any; + user: { + id: string; + username: string; + token: string; + }; + rid: string; + timeFormat: string; + style: any; + archived: boolean; + broadcast: boolean; + previousItem: { + ts: any; + u: any; + groupable: any; + id: any; + tmid: any; + status: any; + }; + baseUrl: string; + Message_GroupingPeriod: number; + isReadReceiptEnabled: boolean; + isThreadRoom: boolean; + useRealName: boolean; + autoTranslateRoom: boolean; + autoTranslateLanguage: string; + status: number; + isIgnored: boolean; + highlighted: boolean; + getCustomEmoji(): void; + onLongPress: Function; + onReactionPress: Function; + onEncryptedPress: Function; + onDiscussionPress: Function; + onThreadPress: Function; + errorActionsShow: Function; + replyBroadcast: Function; + reactionInit: Function; + fetchThreadName: Function; + showAttachment: Function; + onReactionLongPress: Function; + navToRoomInfo: Function; + callJitsi: Function; + blockAction: Function; + onAnswerButtonPress: Function; + theme: string; + threadBadgeColor: string; + toggleFollowThread: Function; + jumpToMessage: Function; + onPress: Function; +} +class MessageContainer extends React.Component<IMessageContainerProps, any> { static defaultProps = { getCustomEmoji: () => {}, onLongPress: () => {}, @@ -79,10 +85,12 @@ class MessageContainer extends React.Component { broadcast: false, isIgnored: false, theme: 'light' - } + }; state = { isManualUnignored: false }; + private subscription: any; + componentDidMount() { const { item } = this.props; if (item && item.observe) { @@ -93,11 +101,9 @@ class MessageContainer extends React.Component { } } - shouldComponentUpdate(nextProps, nextState) { + shouldComponentUpdate(nextProps: any, nextState: any) { const { isManualUnignored } = this.state; - const { - theme, threadBadgeColor, isIgnored, highlighted - } = this.props; + const { theme, threadBadgeColor, isIgnored, highlighted } = this.props; if (nextProps.theme !== theme) { return true; } @@ -122,23 +128,27 @@ class MessageContainer extends React.Component { } } - onPress = debounce(() => { - const { onPress } = this.props; - if (this.isIgnored) { - return this.onIgnoredMessagePress(); - } + onPress = debounce( + () => { + const { onPress } = this.props; + if (this.isIgnored) { + return this.onIgnoredMessagePress(); + } - if (onPress) { - return onPress(); - } + if (onPress) { + return onPress(); + } - const { item, isThreadRoom } = this.props; - Keyboard.dismiss(); + const { item, isThreadRoom } = this.props; + Keyboard.dismiss(); - if (((item.tlm || item.tmid) && !isThreadRoom)) { - this.onThreadPress(); - } - }, 300, true); + if ((item.tlm || item.tmid) && !isThreadRoom) { + this.onThreadPress(); + } + }, + 300, + true + ); onLongPress = () => { const { archived, onLongPress, item } = this.props; @@ -148,76 +158,75 @@ class MessageContainer extends React.Component { if (onLongPress) { onLongPress(item); } - } + }; onErrorPress = () => { const { errorActionsShow, item } = this.props; if (errorActionsShow) { errorActionsShow(item); } - } + }; - onReactionPress = (emoji) => { + onReactionPress = (emoji: any) => { const { onReactionPress, item } = this.props; if (onReactionPress) { onReactionPress(emoji, item.id); } - } + }; onReactionLongPress = () => { const { onReactionLongPress, item } = this.props; if (onReactionLongPress) { onReactionLongPress(item); } - } + }; onEncryptedPress = () => { const { onEncryptedPress } = this.props; if (onEncryptedPress) { onEncryptedPress(); } - } + }; onDiscussionPress = () => { const { onDiscussionPress, item } = this.props; if (onDiscussionPress) { onDiscussionPress(item); } - } + }; onThreadPress = () => { const { onThreadPress, item } = this.props; if (onThreadPress) { onThreadPress(item); } - } + }; - onAnswerButtonPress = (msg) => { + onAnswerButtonPress = (msg: string) => { const { onAnswerButtonPress } = this.props; if (onAnswerButtonPress) { onAnswerButtonPress(msg, undefined, false); } - } + }; onIgnoredMessagePress = () => { this.setState({ isManualUnignored: true }); - } + }; get isHeader() { - const { - item, previousItem, broadcast, Message_GroupingPeriod - } = this.props; + const { item, previousItem, broadcast, Message_GroupingPeriod } = this.props; if (this.hasError || (previousItem && previousItem.status === messagesStatus.ERROR)) { return true; } try { - if (previousItem && ( - (previousItem.ts.toDateString() === item.ts.toDateString()) - && (previousItem.u.username === item.u.username) - && !(previousItem.groupable === false || item.groupable === false || broadcast === true) - && (item.ts - previousItem.ts < Message_GroupingPeriod * 1000) - && (previousItem.tmid === item.tmid) - )) { + if ( + previousItem && + previousItem.ts.toDateString() === item.ts.toDateString() && + previousItem.u.username === item.u.username && + !(previousItem.groupable === false || item.groupable === false || broadcast === true) && + item.ts - previousItem.ts < Message_GroupingPeriod * 1000 && + previousItem.tmid === item.tmid + ) { return false; } return true; @@ -227,13 +236,11 @@ class MessageContainer extends React.Component { } get isThreadReply() { - const { - item, previousItem, isThreadRoom - } = this.props; + const { item, previousItem, isThreadRoom } = this.props; if (isThreadRoom) { return false; } - if (previousItem && item.tmid && (previousItem.tmid !== item.tmid) && (previousItem.id !== item.tmid)) { + if (previousItem && item.tmid && previousItem.tmid !== item.tmid && previousItem.id !== item.tmid) { return true; } return false; @@ -279,23 +286,23 @@ class MessageContainer extends React.Component { if (reactionInit) { reactionInit(item); } - } + }; replyBroadcast = () => { const { replyBroadcast, item } = this.props; if (replyBroadcast) { replyBroadcast(item); } - } + }; - onLinkPress = (link) => { + onLinkPress = (link: any) => { const { item, theme, jumpToMessage } = this.props; - const isMessageLink = item?.attachments?.findIndex(att => att?.message_link === link) !== -1; + const isMessageLink = item?.attachments?.findIndex((att: any) => att?.message_link === link) !== -1; if (isMessageLink) { return jumpToMessage(link); } openLink(link, theme); - } + }; render() { const { @@ -380,8 +387,7 @@ class MessageContainer extends React.Component { threadBadgeColor, toggleFollowThread, replies - }} - > + }}> <Message id={id} msg={message} @@ -394,6 +400,7 @@ class MessageContainer extends React.Component { urls={urls} reactions={reactions} alias={alias} + /* @ts-ignore*/ avatar={avatar} emoji={emoji} timeFormat={timeFormat} diff --git a/app/containers/message/interfaces.ts b/app/containers/message/interfaces.ts new file mode 100644 index 000000000..98a0b003f --- /dev/null +++ b/app/containers/message/interfaces.ts @@ -0,0 +1,147 @@ +export interface IMessageAttachments { + attachments: any; + timeFormat: string; + showAttachment: Function; + getCustomEmoji: Function; + theme: string; +} + +export interface IMessageAttachedActions { + attachment: { + actions: []; + text: string; + }; + theme: string; +} + +export interface IMessageAvatar { + isHeader: boolean; + avatar: string; + emoji: string; + author: { + username: string; + _id: string; + }; + small?: boolean; + navToRoomInfo: Function; + getCustomEmoji(): void; + theme: string; +} + +export interface IMessageBlocks { + blocks: any; + id: string; + rid: string; + blockAction: Function; +} + +export interface IMessageBroadcast { + author: { + _id: string; + }; + broadcast: boolean; + theme: string; +} + +export interface IMessageCallButton { + theme: string; + callJitsi: Function; +} + +export interface IMessageContent { + isTemp: boolean; + isInfo: boolean; + tmid: string; + isThreadRoom: boolean; + msg: string; + theme: string; + isEdited: boolean; + isEncrypted: boolean; + getCustomEmoji: Function; + channels: { + name: string; + _id: number; + }[]; + mentions: object[]; + navToRoomInfo: Function; + useRealName: boolean; + isIgnored: boolean; + type: string; +} + +export interface IMessageDiscussion { + msg: string; + dcount: number; + dlm: string; + theme: string; +} + +export interface IMessageEmoji { + content: any; + baseUrl: string; + standardEmojiStyle: object; + customEmojiStyle: object; + getCustomEmoji: Function; +} + +export interface IMessageThread { + msg: string; + tcount: number; + theme: string; + tlm: string; + isThreadRoom: boolean; + id: string; +} + +export interface IMessageTouchable { + hasError: boolean; + isInfo: boolean; + isThreadReply: boolean; + isTemp: boolean; + archived: boolean; + highlighted: boolean; + theme: string; + ts?: any; + urls?: any; + reactions?: any; + alias?: any; + role?: any; + drid?: any; +} + +export interface IMessageRepliedThread { + tmid: string; + tmsg: string; + id: string; + isHeader: boolean; + theme: string; + fetchThreadName: Function; + isEncrypted: boolean; +} + +export interface IMessageInner + extends IMessageDiscussion, + IMessageContent, + IMessageCallButton, + IMessageBlocks, + IMessageThread, + IMessageAttachments, + IMessageBroadcast { + type: string; + blocks: []; +} + +export interface IMessage extends IMessageRepliedThread, IMessageInner { + isThreadReply: boolean; + isThreadSequential: boolean; + isInfo: boolean; + isTemp: boolean; + isHeader: boolean; + hasError: boolean; + style: any; + onLongPress: Function; + isReadReceiptEnabled: boolean; + unread: boolean; + theme: string; + isIgnored: boolean; +} diff --git a/app/containers/message/styles.js b/app/containers/message/styles.ts similarity index 98% rename from app/containers/message/styles.js rename to app/containers/message/styles.ts index dd0dff627..3d4334f7c 100644 --- a/app/containers/message/styles.js +++ b/app/containers/message/styles.ts @@ -3,7 +3,7 @@ import { StyleSheet } from 'react-native'; import sharedStyles from '../../views/Styles'; import { isTablet } from '../../utils/deviceInfo'; -export default StyleSheet.create({ +export default StyleSheet.create<any>({ root: { flexDirection: 'row' }, diff --git a/app/containers/message/utils.js b/app/containers/message/utils.ts similarity index 63% rename from app/containers/message/utils.js rename to app/containers/message/utils.ts index 22f4d90ca..d82fe25f1 100644 --- a/app/containers/message/utils.js +++ b/app/containers/message/utils.ts @@ -1,21 +1,24 @@ import I18n from '../../i18n'; import { DISCUSSION } from './constants'; -export const formatMessageCount = (count, type) => { +export const formatMessageCount = (count: number, type: string) => { const discussion = type === DISCUSSION; let text = discussion ? I18n.t('No_messages_yet') : null; if (count === 1) { - text = `${ count } ${ discussion ? I18n.t('message') : I18n.t('reply') }`; + text = `${count} ${discussion ? I18n.t('message') : I18n.t('reply')}`; } else if (count > 1 && count < 1000) { - text = `${ count } ${ discussion ? I18n.t('messages') : I18n.t('replies') }`; + text = `${count} ${discussion ? I18n.t('messages') : I18n.t('replies')}`; } else if (count > 999) { - text = `+999 ${ discussion ? I18n.t('messages') : I18n.t('replies') }`; + text = `+999 ${discussion ? I18n.t('messages') : I18n.t('replies')}`; } return text; }; export const BUTTON_HIT_SLOP = { - top: 4, right: 4, bottom: 4, left: 4 + top: 4, + right: 4, + bottom: 4, + left: 4 }; export const SYSTEM_MESSAGES = [ @@ -60,63 +63,87 @@ export const SYSTEM_MESSAGE_TYPES_WITH_AUTHOR_NAME = [ SYSTEM_MESSAGE_TYPES.USER_LEFT_CHANNEL ]; -export const getInfoMessage = ({ - type, role, msg, author -}) => { +type TInfoMessage = { + type: string; + role: string; + msg: string; + author: { username: string }; +}; +export const getInfoMessage = ({ type, role, msg, author }: TInfoMessage) => { const { username } = author; if (type === 'rm') { return I18n.t('Message_removed'); - } else if (type === 'uj') { + } + if (type === 'uj') { return I18n.t('Has_joined_the_channel'); - } else if (type === 'ut') { + } + if (type === 'ut') { return I18n.t('Has_joined_the_conversation'); - } else if (type === 'r') { + } + if (type === 'r') { return I18n.t('Room_name_changed', { name: msg, userBy: username }); - } else if (type === 'message_pinned') { + } + if (type === 'message_pinned') { return I18n.t('Message_pinned'); - } else if (type === 'jitsi_call_started') { + } + if (type === 'jitsi_call_started') { return I18n.t('Started_call', { userBy: username }); - } else if (type === 'ul') { + } + if (type === 'ul') { return I18n.t('Has_left_the_channel'); - } else if (type === 'ru') { + } + if (type === 'ru') { return I18n.t('User_removed_by', { userRemoved: msg, userBy: username }); - } else if (type === 'au') { + } + if (type === 'au') { return I18n.t('User_added_by', { userAdded: msg, userBy: username }); - } else if (type === 'user-muted') { + } + if (type === 'user-muted') { return I18n.t('User_muted_by', { userMuted: msg, userBy: username }); - } else if (type === 'user-unmuted') { + } + if (type === 'user-unmuted') { return I18n.t('User_unmuted_by', { userUnmuted: msg, userBy: username }); - } else if (type === 'subscription-role-added') { - return `${ msg } was set ${ role } by ${ username }`; - } else if (type === 'subscription-role-removed') { - return `${ msg } is no longer ${ role } by ${ username }`; - } else if (type === 'room_changed_description') { + } + if (type === 'subscription-role-added') { + return `${msg} was set ${role} by ${username}`; + } + if (type === 'subscription-role-removed') { + return `${msg} is no longer ${role} by ${username}`; + } + if (type === 'room_changed_description') { return I18n.t('Room_changed_description', { description: msg, userBy: username }); - } else if (type === 'room_changed_announcement') { + } + if (type === 'room_changed_announcement') { return I18n.t('Room_changed_announcement', { announcement: msg, userBy: username }); - } else if (type === 'room_changed_topic') { + } + if (type === 'room_changed_topic') { return I18n.t('Room_changed_topic', { topic: msg, userBy: username }); - } else if (type === 'room_changed_privacy') { + } + if (type === 'room_changed_privacy') { return I18n.t('Room_changed_privacy', { type: msg, userBy: username }); - } else if (type === 'room_changed_avatar') { + } + if (type === 'room_changed_avatar') { return I18n.t('Room_changed_avatar', { userBy: username }); - } else if (type === 'message_snippeted') { + } + if (type === 'message_snippeted') { return I18n.t('Created_snippet'); - } else if (type === 'room_e2e_disabled') { + } + if (type === 'room_e2e_disabled') { return I18n.t('This_room_encryption_has_been_disabled_by__username_', { username }); - } else if (type === 'room_e2e_enabled') { + } + if (type === 'room_e2e_enabled') { return I18n.t('This_room_encryption_has_been_enabled_by__username_', { username }); } return ''; }; -export const getMessageTranslation = (message, autoTranslateLanguage) => { +export const getMessageTranslation = (message: { translations: any }, autoTranslateLanguage: string) => { if (!autoTranslateLanguage) { return null; } const { translations } = message; if (translations) { - const translation = translations.find(trans => trans.language === autoTranslateLanguage); + const translation = translations.find((trans: any) => trans.language === autoTranslateLanguage); return translation && translation.value; } return null; diff --git a/app/dimensions.js b/app/dimensions.js deleted file mode 100644 index 50f5c7b9a..000000000 --- a/app/dimensions.js +++ /dev/null @@ -1,26 +0,0 @@ -import React from 'react'; -import { Dimensions } from 'react-native'; -import hoistNonReactStatics from 'hoist-non-react-statics'; - -export const DimensionsContext = React.createContext(Dimensions.get('window')); - -export function withDimensions(Component) { - const DimensionsComponent = props => ( - <DimensionsContext.Consumer> - {contexts => <Component {...props} {...contexts} />} - </DimensionsContext.Consumer> - ); - hoistNonReactStatics(DimensionsComponent, Component); - return DimensionsComponent; -} - -export const useDimensions = () => React.useContext(DimensionsContext); - -export const useOrientation = () => { - const { width, height } = React.useContext(DimensionsContext); - const isPortrait = height > width; - return { - isPortrait, - isLandscape: !isPortrait - }; -}; diff --git a/app/dimensions.tsx b/app/dimensions.tsx new file mode 100644 index 000000000..dc164362a --- /dev/null +++ b/app/dimensions.tsx @@ -0,0 +1,42 @@ +import React from 'react'; +import { Dimensions } from 'react-native'; +import hoistNonReactStatics from 'hoist-non-react-statics'; + +export interface IDimensionsContextProps { + width: number; + height?: number; + scale: number; + fontScale: number; + setDimensions: ({ + width, + height, + scale, + fontScale + }: { + width: number; + height: number; + scale: number; + fontScale: number; + }) => void; +} + +export const DimensionsContext = React.createContext<Partial<IDimensionsContextProps>>(Dimensions.get('window')); + +export function withDimensions(Component: any) { + const DimensionsComponent = (props: any) => ( + <DimensionsContext.Consumer>{contexts => <Component {...props} {...contexts} />}</DimensionsContext.Consumer> + ); + hoistNonReactStatics(DimensionsComponent, Component); + return DimensionsComponent; +} + +export const useDimensions = () => React.useContext(DimensionsContext); + +export const useOrientation = () => { + const { width, height } = React.useContext(DimensionsContext); + const isPortrait = height! > width!; + return { + isPortrait, + isLandscape: !isPortrait + }; +}; diff --git a/app/ee/omnichannel/containers/OmnichannelStatus.js b/app/ee/omnichannel/containers/OmnichannelStatus.js index 148f398cf..82f4e1418 100644 --- a/app/ee/omnichannel/containers/OmnichannelStatus.js +++ b/app/ee/omnichannel/containers/OmnichannelStatus.js @@ -1,18 +1,16 @@ -import React, { memo, useState, useEffect } from 'react'; -import { View, Switch } from 'react-native'; +import React, { memo, useEffect, useState } from 'react'; +import { Switch, View } from 'react-native'; import PropTypes from 'prop-types'; import * as List from '../../../containers/List'; import styles from '../../../views/RoomsListView/styles'; -import { themes, SWITCH_TRACK_COLOR } from '../../../constants/colors'; +import { SWITCH_TRACK_COLOR, themes } from '../../../constants/colors'; import { withTheme } from '../../../theme'; import UnreadBadge from '../../../presentation/UnreadBadge'; import RocketChat from '../../../lib/rocketchat'; -import { isOmnichannelStatusAvailable, changeLivechatStatus } from '../lib'; +import { changeLivechatStatus, isOmnichannelStatusAvailable } from '../lib'; -const OmnichannelStatus = memo(({ - searching, goQueue, theme, queueSize, inquiryEnabled, user -}) => { +const OmnichannelStatus = memo(({ searching, goQueue, theme, queueSize, inquiryEnabled, user }) => { if (searching > 0 || !(RocketChat.isOmnichannelModuleAvailable() && user?.roles?.includes('livechat-agent'))) { return null; } @@ -22,7 +20,7 @@ const OmnichannelStatus = memo(({ setStatus(isOmnichannelStatusAvailable(user)); }, [user.statusLivechat]); - const toggleLivechat = async() => { + const toggleLivechat = async () => { try { setStatus(v => !v); await changeLivechatStatus(); @@ -40,19 +38,8 @@ const OmnichannelStatus = memo(({ onPress={goQueue} right={() => ( <View style={styles.omnichannelRightContainer}> - {inquiryEnabled - ? ( - <UnreadBadge - style={styles.queueIcon} - unread={queueSize} - /> - ) - : null} - <Switch - value={status} - trackColor={SWITCH_TRACK_COLOR} - onValueChange={toggleLivechat} - /> + {inquiryEnabled ? <UnreadBadge style={styles.queueIcon} unread={queueSize} /> : null} + <Switch value={status} trackColor={SWITCH_TRACK_COLOR} onValueChange={toggleLivechat} /> </View> )} /> diff --git a/app/ee/omnichannel/lib/index.js b/app/ee/omnichannel/lib/index.js index 7492675eb..107bc31d0 100644 --- a/app/ee/omnichannel/lib/index.js +++ b/app/ee/omnichannel/lib/index.js @@ -1,6 +1,6 @@ -import subscribeInquiry from './subscriptions/inquiry'; import RocketChat from '../../../lib/rocketchat'; import EventEmitter from '../../../utils/events'; +import subscribeInquiry from './subscriptions/inquiry'; export const isOmnichannelStatusAvailable = user => user?.statusLivechat === 'available'; @@ -22,10 +22,10 @@ class Omnichannel { EventEmitter.addEventListener('INQUIRY_UNSUBSCRIBE', this.unsubscribeInquiry); } - subscribeInquiry = async() => { + subscribeInquiry = async () => { console.log('[RCRN] Subscribing to inquiry'); this.inquirySub = await subscribeInquiry(); - } + }; unsubscribeInquiry = () => { if (this.inquirySub) { @@ -33,7 +33,7 @@ class Omnichannel { this.inquirySub.stop(); this.inquirySub = null; } - } + }; } // eslint-disable-next-line no-unused-vars diff --git a/app/ee/omnichannel/lib/subscriptions/inquiry.js b/app/ee/omnichannel/lib/subscriptions/inquiry.js index 02eafb8a9..00d320828 100644 --- a/app/ee/omnichannel/lib/subscriptions/inquiry.js +++ b/app/ee/omnichannel/lib/subscriptions/inquiry.js @@ -1,12 +1,7 @@ import log from '../../../../utils/log'; import store from '../../../../lib/createStore'; import RocketChat from '../../../../lib/rocketchat'; -import { - inquiryRequest, - inquiryQueueAdd, - inquiryQueueUpdate, - inquiryQueueRemove -} from '../../actions/inquiry'; +import { inquiryQueueAdd, inquiryQueueRemove, inquiryQueueUpdate, inquiryRequest } from '../../actions/inquiry'; const removeListener = listener => listener.stop(); @@ -21,7 +16,7 @@ export default function subscribeInquiry() { store.dispatch(inquiryRequest()); }; - const handleQueueMessageReceived = (ddpMessage) => { + const handleQueueMessageReceived = ddpMessage => { const [{ type, ...sub }] = ddpMessage.fields.args; // added can be ignored, since it is handled by 'changed' event @@ -69,7 +64,7 @@ export default function subscribeInquiry() { try { const { user } = store.getState().login; - RocketChat.getAgentDepartments(user.id).then((result) => { + RocketChat.getAgentDepartments(user.id).then(result => { if (result.success) { const { departments } = result; @@ -78,9 +73,9 @@ export default function subscribeInquiry() { } const departmentIds = departments.map(({ departmentId }) => departmentId); - departmentIds.forEach((departmentId) => { + departmentIds.forEach(departmentId => { // subscribe to all departments of the agent - RocketChat.subscribe(streamTopic, `department/${ departmentId }`).catch(e => console.log(e)); + RocketChat.subscribe(streamTopic, `department/${departmentId}`).catch(e => console.log(e)); }); } }); diff --git a/app/ee/omnichannel/reducers/inquiry.js b/app/ee/omnichannel/reducers/inquiry.js index 156c9633d..8c031d55d 100644 --- a/app/ee/omnichannel/reducers/inquiry.js +++ b/app/ee/omnichannel/reducers/inquiry.js @@ -31,7 +31,7 @@ export default function inquiry(state = initialState, action) { case INQUIRY.QUEUE_UPDATE: return { ...state, - queued: state.queued.map((item) => { + queued: state.queued.map(item => { if (item._id === action.inquiry._id) { return action.inquiry; } diff --git a/app/ee/omnichannel/sagas/inquiry.js b/app/ee/omnichannel/sagas/inquiry.js index 6140e2f65..b9bec6b41 100644 --- a/app/ee/omnichannel/sagas/inquiry.js +++ b/app/ee/omnichannel/sagas/inquiry.js @@ -1,10 +1,10 @@ -import { put, takeLatest, select } from 'redux-saga/effects'; +import { put, select, takeLatest } from 'redux-saga/effects'; import * as types from '../../../actions/actionsTypes'; import RocketChat from '../../../lib/rocketchat'; import EventEmitter from '../../../utils/events'; -import { inquirySuccess, inquiryFailure, inquirySetEnabled } from '../actions/inquiry'; -import { isOmnichannelStatusAvailable, getInquiriesQueued } from '../lib'; +import { inquiryFailure, inquirySetEnabled, inquirySuccess } from '../actions/inquiry'; +import { getInquiriesQueued, isOmnichannelStatusAvailable } from '../lib'; const handleRequest = function* handleRequest() { try { diff --git a/app/ee/omnichannel/selectors/inquiry.js b/app/ee/omnichannel/selectors/inquiry.js index baeb09ef8..ada81fd9e 100644 --- a/app/ee/omnichannel/selectors/inquiry.js +++ b/app/ee/omnichannel/selectors/inquiry.js @@ -2,7 +2,4 @@ import { createSelector } from 'reselect'; const getInquiryQueue = state => state.inquiry.queued; -export const getInquiryQueueSelector = createSelector( - [getInquiryQueue], - queue => queue -); +export const getInquiryQueueSelector = createSelector([getInquiryQueue], queue => queue); diff --git a/app/ee/omnichannel/views/QueueListView.js b/app/ee/omnichannel/views/QueueListView.js index a261d481e..d22613445 100644 --- a/app/ee/omnichannel/views/QueueListView.js +++ b/app/ee/omnichannel/views/QueueListView.js @@ -7,7 +7,7 @@ import { dequal } from 'dequal'; import I18n from '../../../i18n'; import RoomItem, { ROW_HEIGHT } from '../../../presentation/RoomItem'; import { MAX_SIDEBAR_WIDTH } from '../../../constants/tablet'; -import { isTablet, isIOS } from '../../../utils/deviceInfo'; +import { isIOS, isTablet } from '../../../utils/deviceInfo'; import { getUserSelector } from '../../../selectors/login'; import { withTheme } from '../../../theme'; import { withDimensions } from '../../../dimensions'; @@ -17,7 +17,7 @@ import StatusBar from '../../../containers/StatusBar'; import { goRoom } from '../../../utils/goRoom'; import * as HeaderButton from '../../../containers/HeaderButton'; import RocketChat from '../../../lib/rocketchat'; -import { logEvent, events } from '../../../utils/log'; +import { events, logEvent } from '../../../utils/log'; import { getInquiryQueueSelector } from '../selectors/inquiry'; const INITIAL_NUM_TO_RENDER = isTablet ? 20 : 12; @@ -37,7 +37,7 @@ class QueueListView extends React.Component { options.headerLeft = () => <HeaderButton.CloseModal navigation={navigation} testID='directory-view-close' />; } return options; - } + }; static propTypes = { user: PropTypes.shape({ @@ -52,7 +52,7 @@ class QueueListView extends React.Component { useRealName: PropTypes.bool, navigation: PropTypes.object, theme: PropTypes.string - } + }; shouldComponentUpdate(nextProps) { const { queued } = this.props; @@ -82,19 +82,15 @@ class QueueListView extends React.Component { }); }; - getRoomTitle = item => RocketChat.getRoomTitle(item) + getRoomTitle = item => RocketChat.getRoomTitle(item); - getRoomAvatar = item => RocketChat.getRoomAvatar(item) + getRoomAvatar = item => RocketChat.getRoomAvatar(item); - getUidDirectMessage = room => RocketChat.getUidDirectMessage(room) + getUidDirectMessage = room => RocketChat.getUidDirectMessage(room); renderItem = ({ item }) => { const { - user: { - id: userId, - username, - token - }, + user: { id: userId, username, token }, server, useRealName, theme, @@ -114,7 +110,7 @@ class QueueListView extends React.Component { token={token} baseUrl={server} onPress={this.onPressItem} - testID={`queue-list-view-item-${ item.name }`} + testID={`queue-list-view-item-${item.name}`} width={isMasterDetail ? MAX_SIDEBAR_WIDTH : width} useRealName={useRealName} getRoomTitle={this.getRoomTitle} @@ -123,7 +119,7 @@ class QueueListView extends React.Component { swipeEnabled={false} /> ); - } + }; render() { const { queued, theme } = this.props; diff --git a/app/emojis.js b/app/emojis.ts similarity index 99% rename from app/emojis.js rename to app/emojis.ts index b5ed98f52..172d15adf 100644 --- a/app/emojis.js +++ b/app/emojis.ts @@ -1,4 +1,4 @@ -export const emojisByCategory = { +export const emojisByCategory: any = { people: [ 'grinning', 'grimacing', diff --git a/app/externalModules.d.ts b/app/externalModules.d.ts new file mode 100644 index 000000000..3f7d7a6d8 --- /dev/null +++ b/app/externalModules.d.ts @@ -0,0 +1,11 @@ +declare module 'rn-extensions-share'; +declare module 'commonmark'; +declare module 'commonmark-react-renderer'; +declare module 'remove-markdown'; +declare module 'react-native-image-progress'; +declare module 'react-native-platform-touchable'; +declare module 'react-native-ui-lib/keyboard'; +declare module '@rocket.chat/ui-kit'; +declare module '@rocket.chat/sdk'; +declare module 'react-native-config-reader'; +declare module 'react-native-keycommands'; diff --git a/app/i18n/index.js b/app/i18n/index.js index ebe430769..aa39cef7c 100644 --- a/app/i18n/index.js +++ b/app/i18n/index.js @@ -14,55 +14,68 @@ export const LANGUAGES = [ label: 'English', value: 'en', file: () => require('./locales/en.json') - }, { + }, + { label: '简体中文', value: 'zh-CN', file: () => require('./locales/zh-CN.json') - }, { + }, + { label: '繁體中文', value: 'zh-TW', file: () => require('./locales/zh-TW.json') - }, { + }, + { label: 'Deutsch', value: 'de', file: () => require('./locales/de.json') - }, { + }, + { label: 'Español (ES)', value: 'es-ES', file: () => require('./locales/es-ES.json') - }, { + }, + { label: 'Français', value: 'fr', file: () => require('./locales/fr.json') - }, { + }, + { label: 'Português (BR)', value: 'pt-BR', file: () => require('./locales/pt-BR.json') - }, { + }, + { label: 'Português (PT)', value: 'pt-PT', file: () => require('./locales/pt-PT.json') - }, { + }, + { label: 'Russian', value: 'ru', file: () => require('./locales/ru.json') - }, { + }, + { label: 'Nederlands', value: 'nl', file: () => require('./locales/nl.json') - }, { + }, + { label: 'Italiano', value: 'it', file: () => require('./locales/it.json') - }, { + }, + { label: '日本語', value: 'ja', file: () => require('./locales/ja.json') - }, { + }, + { label: 'العربية', value: 'ar', file: () => require('./locales/ar.json') - }, { + }, + { label: 'Türkçe', value: 'tr', file: () => require('./locales/tr.json') @@ -74,7 +87,7 @@ const translations = LANGUAGES.reduce((ret, item) => { return ret; }, {}); -export const setLanguage = (l) => { +export const setLanguage = l => { if (!l) { return; } diff --git a/app/i18n/isRTL.js b/app/i18n/isRTL.js index 89cac3ac7..0f145ca14 100644 --- a/app/i18n/isRTL.js +++ b/app/i18n/isRTL.js @@ -1,16 +1,4 @@ // https://github.com/zoontek/react-native-localize/blob/master/src/constants.ts#L5 -const USES_RTL_LAYOUT = [ - 'ar', - 'ckb', - 'fa', - 'he', - 'ks', - 'lrc', - 'mzn', - 'ps', - 'ug', - 'ur', - 'yi' -]; +const USES_RTL_LAYOUT = ['ar', 'ckb', 'fa', 'he', 'ks', 'lrc', 'mzn', 'ps', 'ug', 'ur', 'yi']; export const isRTL = locale => USES_RTL_LAYOUT.includes(locale); diff --git a/app/i18n/locales/ar.json b/app/i18n/locales/ar.json index 8b08c047d..d48c9ccb1 100644 --- a/app/i18n/locales/ar.json +++ b/app/i18n/locales/ar.json @@ -1,656 +1,656 @@ { - "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}}", - "error-avatar-url-handling": "خطأ في معالجة الصورة الرمزية ({{url}}) للمستخدم {{username}}", - "error-cant-invite-for-direct-room": "لا يمكن دعوة المستخدم في الغرفة المباشرة", - "error-could-not-change-email": "تعذر تغيير البريد الإلكتروني", - "error-could-not-change-name": "تعذر تغيير الاسم", - "error-could-not-change-username": "تعذر تغيير اسم المستخدم", - "error-could-not-change-status": "تعذر تغيير الحالة", - "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-save-video": "خطأ عند حفظ الفيديو", - "error-field-unavailable": "{{field}} مستخدم بالفعل :(", - "error-file-too-large": "حجم الملف كبير جداً", - "error-importer-not-defined": "المستورِد معرف بطريقة غير صحيحة، يجب تحديد نوع الإستيراد", - "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": "عنوان البريد اﻹلكتروني غير صالح {{email}}", - "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": "رابط إعادة توجيه غير صحيح", - "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": "عناوين غير صالحة", - "error-invalid-user": "مستخدم غير صالح", - "error-invalid-username": "اسم المستخدم غير صالح", - "error-invalid-webhook-response": "الرد التلقائي من العنوان استجاب برمز مغاير عن 200", - "error-message-deleting-blocked": "حذف الرسالة محظور", - "error-message-editing-blocked": "تعديل الرسالة محظور", - "error-message-size-exceeded": "حجم الرسالة تجاوز الحد المسموح به (Message_MaxAllowedSize)", - "error-missing-unsubscribe-link": "يجب عليك تقديم رابط [unsubscribe]", - "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": "التسجيل مسموح به عبر عنوان الويب السري فقط", - "error-you-are-last-owner": "أنت المالك الأخير. يرجى تعيين مالك جديد قبل مغادرة الغرفة", - "Actions": "الإجراءات", - "activity": "نشاط", - "Activity": "النشاط", - "Add_Reaction": "إضافة تفاعل", - "Add_Server": "إضافة خادم", - "Add_users": "إضافة مستخدمين", - "Admin_Panel": "لوحة الإدارة", - "Agent": "المندوب", - "Alert": "إنذار", - "alert": "إنذار", - "alerts": "الإنذارات", - "All_users_in_the_channel_can_write_new_messages": "يمكن لجميع المستخدمين في القناة كتابة رسائل جديدة", - "A_meaningful_name_for_the_discussion_room": "اسم معبر لغرفة النقاش", - "All": "الكل", - "All_Messages": "كل الرسائل", - "Allow_Reactions": "السماح للتفاعلات", - "Alphabetical": "أبجدي", - "and_more": "وأكثر", - "and": "و", - "announcement": "إعلان", - "Announcement": "إعلان", - "Apply_Your_Certificate": "طبق شهادتك", - "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": "عنوان ويب الصورة الرمزية", - "Away": "غير متواجد", - "Back": "عودة", - "Black": "أسود", - "Block_user": "حظر المستخدم", - "Browser": "المتصفح", - "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": "تم انهاء المكالمة بالفعل !", - "Clear_cookies_alert": "هل تريد حذف جميع ملفات تعريف الإرتباط؟", - "Clear_cookies_desc": "هذا الإجراء سيحذف ملفات تعريف الإرتباط الخاصة بتسجيل الدخول مما يسمح بتسجيل الدخول لحسابات أخرى", - "Clear_cookies_yes": "نعم، مسح ملفات الإرتباط", - "Clear_cookies_no": "لا، احتفظ بملفات تعريف الإرتباط", - "Click_to_join": "انقر للانضمام!", - "Close": "إغلاق", - "Close_emoji_selector": "إغلاق محدد الرموز التعبيرية", - "Closing_chat": "إغلاق المحادثة", - "Change_language_loading": "تغيير اللغة", - "Chat_closed_by_agent": "المندوب أغلق المحادثة", - "Choose": "اختر", - "Choose_from_library": "اختر من المكتبة", - "Choose_file": "اختر ملف", - "Choose_where_you_want_links_be_opened": "اختر المكان الذي تريد فتح الروابط فيه", - "Code": "الرمز", - "Code_or_password_invalid": "الرمز أو كلمة المرور خاطئة", - "Collaborative": "تعاونية", - "Confirm": "تأكيد", - "Connect": "اتصال", - "Connected": "متصل", - "connecting_server": "يتم الاتصال بالخادم", - "Connecting": "جار الاتصال...", - "Contact_us": "تواصل معنا", - "Contact_your_server_admin": "اتصل بمسؤول الخادم", - "Continue_with": "متابعة بـ", - "Copied_to_clipboard": "تم النسخ للحافظة!", - "Copy": "نسخ", - "Conversation": "محادثة", - "Permalink": "رابط ثابت", - "Certificate_password": "الرقم السري للشهادة", - "Clear_cache": "امسح ذاكرة التخزين المؤقتة للخادم", - "Clear_cache_loading": "يتم مسح ذاكرة التخزين", - "Whats_the_password_for_your_certificate": "ماهي كلمة المرور للشهادة؟", - "Create_account": "إنشاء حساب", - "Create_Channel": "إنشاء قناة", - "Create_Direct_Messages": "إنشاء رسالة مباشرة", - "Create_Discussion": "إنشاء نقاش", - "Created_snippet": "إنشاء مقتطف", - "Create_a_new_workspace": "إنشاء مساحة عمل جديدة", - "Create": "إنشاء", - "Custom_Status": "حالة مخصصة", - "Dark": "داكن", - "Dark_level": "مستوى السمة الداكنة", - "Default": "افتراضي", - "Default_browser": "المتصفح الأساسي", - "Delete_Room_Warning": "سيؤدي حذف الغرفة إلى حذف جميع الرسائل المنشورة. لا يمكن التراجع بعد الحذف", - "Department": "القسم", - "delete": "حذف", - "Delete": "حذف", - "DELETE": "حذف", - "deleting_room": "حذف الغرفة", - "description": "وصف", - "Description": "وصف", - "Desktop_Alert_info": "هذه الإشعارات ترسل لسطح المكتب", - "Directory": "مجلد", - "Direct_Messages": "رسالة مباشرة", - "Disable_notifications": "أوقف الإشعارات", - "Discussions": "مناقشات", - "Discussion_Desc": "ساهم بإعطاء نظرة عامة لما يجري. عند إنشاء مناقشة يتم إنشاء قناة فرعية وربطها بالقناة المحددة", - "Discussion_name": "اسم النقاش", - "Done": "تم", - "Dont_Have_An_Account": "ليس لديك حساب؟", - "Do_you_have_an_account": "هل لديك حساب؟", - "Do_you_have_a_certificate": "هل لديك شهادة؟", - "Do_you_really_want_to_key_this_room_question_mark": "هل تريد حقاً {{key}} هذه الغرفة؟", - "E2E_How_It_Works_info1": "يمكنك الآن إنشاء مجموعات خاصة ورسائل مباشرة مشفرة. بإمكانك أيضاً تشفير المجموعات الخاصة والرسائل المباشرة الموجودة مسبقاً", - "E2E_How_It_Works_info2": "التشفير يتم بين الطرفيات بمعنى أن المفتاح سيستخدم لتشفير وفك تشفير رسائلك ولن يتم حفظه في الخادم. لذلك يترتب عليك حفظ كلمة المرور هذه في مكان آمن", - "E2E_How_It_Works_info3": "حين الاستمرار سيتم إنشاء كلمة المرور بين الطرفيات", - "E2E_How_It_Works_info4": "بإمكانك أيضاً إنشاء كلمة مرور جديدة لمفتاح التشفير في أي وقت عند دخولك بكلمة المرور الحالية لمفتاح التشفير", - "edit": "تعديل", - "edited": "معدل", - "Edit": "تعديل", - "Edit_Status": "تعديل الحالة", - "Edit_Invite": "تعديل الدعوة", - "End_to_end_encrypted_room": "غرفة مشفرة بين الطرفيات", - "end_to_end_encryption": "تشفير بين الطرفيات", - "Email_Notification_Mode_All": "لكل إشارة أو رسالة مباشرة", - "Email_Notification_Mode_Disabled": "معطل", - "Email_or_password_field_is_empty": "حقل البريد الإلكتروني أو كلمة المرور فارغ", - "Email": "البريد الإلكتروني", - "email": "البريد الإلكتروني", - "Empty_title": "عنوان فارغ", - "Enable_Auto_Translate": "تمكين الترجمة التلقائية", - "Enable_notifications": "تفعيل الإشعارات", - "Encrypted": "مشفر", - "Encrypted_message": "رسالة مشفرة", - "Enter_Your_E2E_Password": "أدخل كلمة المرور بين الطرفيات", - "Enter_Your_Encryption_Password_desc1": "سيمكنك هذا من الوصول لرسائلك المباشرة والمجموعات الخاصة", - "Enter_Your_Encryption_Password_desc2": "يجب إدخال كلمة المرور لتشفير وفك تشفير الرسائل المرسلة", - "Encryption_error_title": "كلمة المرور المشفرة خاطئة", - "Encryption_error_desc": "تعذر قراءة مفتاح التشفير أثناء الاستيراد", - "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_password_If_this_email_is_registered": "إن كان البريد الإلكتروني مسجلاً، فسنرسل تعليمات إعادة تعيين كلمة المرور الخاصة بك. إذا لم تتلق بريداً إلكترونياً قريباً، فيرجى العودة والمحاولة مرة أخرى", - "Forgot_password": "هل نسيت كلمة المرور؟", - "Forgot_Password": "نسيت كلمة المرور", - "Forward": "إعادة توجيه", - "Forward_Chat": "إعادة توجيه المحادثة", - "Forward_to_department": "إعادة توجيه للقسم", - "Forward_to_user": "إعادة توجيه لمستخدم", - "Full_table": "انقر لرؤية الجدول كاملاً", - "Generate_New_Link": "إنشاء رابط جديد", - "Group_by_favorites": "جمع حسب المفضلة", - "Group_by_type": "جمع حسب النوع", - "Hide": "إخفاء", - "Has_joined_the_channel": "انضم إلى القناة", - "Has_joined_the_conversation": "انضم إلى المحادثة", - "Has_left_the_channel": "غادر القناة", - "Hide_System_Messages": "إخفاء رسائل النظام", - "Hide_type_messages": "إخفاء رسائل \"{{type}}\"", - "How_It_Works": "طريقة العمل", - "Message_HideType_uj": "مستخدم انضم", - "Message_HideType_ul": "مستخدم غادر", - "Message_HideType_ru": "مستخدم حُذف", - "Message_HideType_au": "مستخدم أضيف", - "Message_HideType_mute_unmute": "صوت المستخدم كتم / فتح", - "Message_HideType_r": "اسم الغرفة تغير", - "Message_HideType_ut": "مستخدم انضم للمحادثة", - "Message_HideType_wm": "ترحيب", - "Message_HideType_rm": "رسالة حُذفت", - "Message_HideType_subscription_role_added": "تعيين دور جديد", - "Message_HideType_subscription_role_removed": "دور أصبح غير معرف", - "Message_HideType_room_archived": "غرفة أرشفت", - "Message_HideType_room_unarchived": "غرفة ألغيت أرشفتها", - "I_Saved_My_E2E_Password": "قمت بحفظ كلمة المرور الطرفية", - "IP": " عنوان بروتوكول الإنترنت (الآيبي)", - "In_app": "في التطبيق", - "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": "انضم", - "Join_our_open_workspace": "انضم لمساحة عملنا المفتوحة", - "Join_your_workspace": "انضم لمساحة عملك", - "Just_invited_people_can_access_this_channel": "يمكن للأشخاص المدعوين فقط الوصول إلى هذه القناة", - "Language": "اللغة", - "last_message": "الرسالة الأخيرة", - "Leave_channel": "مغادرة القناة", - "leaving_room": "مغادرة الغرفة", - "Leave": "مغادرة الغرفة", - "leave": "مغادرة", - "Legal": "قانوني", - "Light": "ساطع", - "License": "رخصة", - "Livechat": "محادثة مباشرة", - "Livechat_edit": "تعديل المحادثة المباشرة", - "Login": "تسجيل الدخول", - "Login_error": "تم رفض تسجيل الدخول الرجاء المحاولة مرة أخرى", - "Login_with": "تسجيل الدخول بـ", - "Logging_out": "تسجيل الخروج", - "Logout": "تسجيل الخروج", - "Max_number_of_uses": "أقصى عدد للاستخدام", - "Max_number_of_users_allowed_is_number": "أقصى عدد للمستخدمين {{maxUsers}}", - "members": "أفراد", - "Members": "أفراد", - "Mentioned_Messages": "الرسائل المذكورة", - "mentioned": "مشار", - "Mentions": "الإشارات", - "Message_accessibility": "الرسالة من {{user}} في {{time}}: {{message}}", - "Message_actions": "إجراءات الرسالة", - "Message_pinned": "الرسالة مثبتة", - "Message_removed": "الرسالة حذفت", - "Message_starred": "الرسالة مميزة", - "Message_unstarred": "الرسالة غير مميزة", - "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": "اسم", - "Navigation_history": "تاريخ التصفح", - "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_label_provided": "لا {{label}} معطى", - "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": "تفضيلات الإشعار", - "No_available_agents_to_transfer": "المندوبين غير متوفرين حالياً", - "Offline": "غير متصل", - "Oops": "عفوًا!", - "Omnichannel": "القنوات الموحدة", - "Open_Livechats": "محادثات مباشرة جارية", - "Omnichannel_enable_alert": "أنت غير متاح ", - "Onboarding_description": "مساحة عمل هي مساحة لفريقك ومنظمتك للتعاون. اطلب من المشرف العنوان للانضمام أو أنشئ لفريقك", - "Onboarding_join_workspace": "انضم لمساحة عمل", - "Onboarding_subtitle": "ما بعد بيئة فريق تعاونية", - "Onboarding_title": "مرحبا بك في Rocket.Chat", - "Onboarding_join_open_description": "انضم لمساحة عملنا للتواصل مع فريق Rocket.Chat ومع المجتمع", - "Onboarding_agree_terms": "بالمواصلة أنت توافق على Rocket.Chat", - "Onboarding_less_options": "خيارات أقل", - "Onboarding_more_options": "خيارات أكثر", - "Online": "متصل", - "Only_authorized_users_can_write_new_messages": "يمكن للمستخدمين المصرح لهم فقط كتابة رسائل جديدة", - "Open_emoji_selector": "افتح محدد الرموز التعبيرية", - "Open_Source_Communication": "تواصل مفتوح المصدر", - "Open_your_authentication_app_and_enter_the_code": "افتح تطبيق المصادقة الخاص بك وأدخل الرمز", - "OR": "أو", - "OS": "نظام التشغيل", - "Overwrites_the_server_configuration_and_use_room_config": "يمكن للمستخدمين المصرح لهم فقط كتابة رسائل جديدة", - "Password": "كلمة المرور", - "Parent_channel_or_group": "القناة أو المجموعة الأب", - "Permalink_copied_to_clipboard": "تم نسخ الرابط الثابت إلى الحافظة!", - "Phone": "الهاتف", - "Pin": "ثبت", - "Pinned_Messages": "رسائل مثبتة", - "pinned": "مثبت", - "Pinned": "مثبت", - "Please_add_a_comment": "الرجاء إضافة تعليق", - "Please_enter_your_password": "الرجاء إدخال كلمة المرور", - "Please_wait": "الرجاء الإنتظار", - "Preferences": "التفضيلات", - "Preferences_saved": "تم حفظ التفضيلات", - "Privacy_Policy": "سياسة الخصوصية", - "Private_Channel": "قناة خاصة", - "Private": "خاص", - "Processing": "جار معالجة...", - "Profile_saved_successfully": "تم حفظ الملف الشخصي بنجاح!", - "Profile": "الملف الشخصي", - "Public_Channel": "قناة عامة", - "Public": "عام", - "Push_Notifications_Alert_Info": "يتم إرسال هذه الإشعارات إليك عندما لا يكون التطبيق مفتوحاً", - "Quote": "اقتباس", - "Reactions_are_disabled": "التفاعل معطل", - "Reactions_are_enabled": "التفاعل مفعل", - "Reactions": "التفاعلات", - "Read": "قراءة", - "Read_External_Permission_Message": "يحتاج Rocket.chat للوصول إلى الصور والملفات الموجودة على الجهاز", - "Read_External_Permission": "صلاحية قراءة الوسائط", - "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": "إعادة", - "Return": "العودة", - "Review_app_title": "هل أنت مستمتع بهذا التطبيق؟", - "Review_app_desc": "قم بإعطائنا 5 نجوم {{store}}", - "Review_app_yes": "أكيد!", - "Review_app_no": "لا", - "Review_app_later": "ربما لاحقاً", - "Review_app_unable_store": "لم يتمكن من فتح {{store}}", - "Review_this_app": "تقييم هذا التطبيق", - "Remove": "حذف", - "Roles": "أدوار", - "Room_actions": "إجراءات الغرفة", - "Room_changed_announcement": "تم تغيير إعلان الغرفة إلى: {{announcement}} من قبل {{userBy}}", - "Room_changed_description": "تم تغيير وصف الغرفة إلى: {{description}} من قبل {{userBy}}", - "Room_changed_privacy": "تم تغيير نوع الغرفة إلى: {{type}} من قبل {{userBy}}", - "Room_changed_topic": "تم تغيير موضوع الغرفة إلى: {{topic}} من قبل {{userBy}}", - "Room_Files": "ملفات الغرفة", - "Room_Info_Edit": "تعديل معلومات الغرفة", - "Room_Info": "معلومات الغرفة", - "Room_Members": "أعضاء الغرفة", - "Room_name_changed": "تم تغيير اسم الغرفة إلى: {{name}} من قبل {{userBy}}", - "SAVE": "حفظ", - "Save_Changes": "حفظ التغيرات", - "Save": "حفظ", - "Saved": "تم الحفظ", - "saving_preferences": "حفظ التفضيلات", - "saving_profile": "حفظ الملف الشخصي", - "saving_settings": "حفظ الإعدادات", - "saved_to_gallery": "تم الحفظ في المعرض", - "Save_Your_E2E_Password": "حفظ كلمة المرور بين الطرفين", - "Save_Your_Encryption_Password": "حفظ كلمة المرور المشفرة", - "Save_Your_Encryption_Password_warning": "لا نقوم بتخزين كلمة المرور، الرجاء حفظها لديك في مكان آخر", - "Save_Your_Encryption_Password_info": "لا يمكن إستعادة كلمة المرور في حال فقدانها ولن تستطيع الوصول لرسائلك", - "Search_Messages": "بحث الرسائل", - "Search": "بحث", - "Search_by": "بحث حسب", - "Search_global_users": "بحث عام عن المستخدمين", - "Search_global_users_description": "إذا قمت بالتفعيل، فسيمكنك البحث عن أي مستخدم في شركات أو خوادم أخرى", - "Seconds": "{{second}} ثواني", - "Select_Avatar": "حدد الصورة الرمزية", - "Select_Server": "حدد خادم", - "Select_Users": "حدد مستخدمين", - "Select_a_Channel": "حدد قناة", - "Select_a_Department": "حدد قسم", - "Select_an_option": "حدد خيار", - "Select_a_User": "حدد مستخدم", - "Send": "إرسال", - "Send_audio_message": "إرسال رسالة صوتية", - "Send_crash_report": "إرسال تقرير الأعطال", - "Send_message": "إرسال الرسالة", - "Send_me_the_code_again": "أرسل الرمز مرة أخرى", - "Send_to": "إرسال إلى...", - "Sending_to": "يتم الإرسال إلى", - "Sent_an_attachment": "تم إرسال المرفق", - "Server": "سرفر", - "Servers": "سرفرات", - "Server_version": "نسخة الخادم: {{version}}", - "Set_username_subtitle": "يتم استخدام اسم المستخدم للسماح للآخرين بذكرك في الرسائل", - "Set_custom_status": "حدد حالة خاصة", - "Set_status": "حدد حالة", - "Status_saved_successfully": "تم حفظ الحالة بنجاح!", - "Settings": "الإعدادات", - "Settings_succesfully_changed": "تم تعديل الإعدادات بنجاح!", - "Share": "مشاركة", - "Share_Link": "مشاركة رابط", - "Share_this_app": "مشاركة هذا البرنامج", - "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": "بداية المحادثة", - "Start_a_Discussion": "ابدأ نقاش", - "Started_discussion": "بدأ النقاش", - "Started_call": "أجرى الاتصال {{userBy}}", - "Submit": "إرسال", - "Table": "جدول", - "Tags": "العلامات", - "Take_a_photo": "التقاط صورة", - "Take_a_video": "التقاط فيديو", - "Take_it": "التقط!", - "tap_to_change_status": "انقر لتغيير الحالة", - "Tap_to_view_servers_list": "انقر لعرض قائمة الخوادم", - "Terms_of_Service": " شروط الخدمة ", - "Theme": "سمة", - "The_user_wont_be_able_to_type_in_roomName": "المستخدم لن يتمكن من الكتابة في {{roomName}}", - "The_user_will_be_able_to_type_in_roomName": "المستخدم سيتمكن من الكتابة في {{roomName}}", - "There_was_an_error_while_action": "حدث خطأ أثناء {{action}}!", - "This_room_is_blocked": "هذه الغرفة محظورة", - "This_room_is_read_only": "هذه الغرفة للقراءة فقط", - "Thread": "موضوع", - "Threads": "مواضيع", - "Timezone": "المنطقة الزمنية", - "To": "إلى", - "topic": "عنوان", - "Topic": "عنوان", - "Translate": "ترجمة", - "Try_again": "حاول مجدداً", - "Two_Factor_Authentication": "المصادقة الثنائية", - "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": "رفع الملف؟", - "User": "مستخدم", - "Users": "مستخدمين", - "User_added_by": "مستخدم {{userAdded}} أضيف من قبل {{userBy}}", - "User_Info": "معلومات المستخدم", - "User_has_been_key": "تمت {{key}} المستخدم!", - "User_is_no_longer_role_by_": "تم إزالة الدور {{role}} عن المستخدم {{user}} من قبل {{userBy}}", - "User_muted_by": "المستخدم {{userMuted}} كتم من قبل {{userBy}}", - "User_removed_by": "المستخدم {{userRemoved}} حذف من قبل {{userBy}}", - "User_sent_an_attachment": "{{user}} أرسل مرفقًا", - "User_unmuted_by": "ألغى {{userBy}} الكتم عن {{userUnmuted}}", - "User_was_set_role_by_": "أضيف دور {{role}} للمستخدم {{user}} من قبل {{userBy}}", - "Username_is_empty": "اسم المستخدم فارغ", - "Username": "اسم المستخدم", - "Username_or_email": "اسم المستخدم أو البريد الالكتروني", - "Uses_server_configuration": "يستخدم إعداد الخادم", - "Validating": "يتم التحقق", - "Registration_Succeeded": "تم التسجيل بنجاح", - "Verify": "تحقق", - "Verify_email_title": "تم التسجيل!", - "Verify_email_desc": "لقد أرسلنا إليك بريداً إلكترونياً لتأكيد تسجيلك. إذا لم تتلق البريد الإلكتروني قريباً، فيرجى العودة والمحاولة مرة أخرى", - "Verify_your_email_for_the_code_we_sent": "يرجى تأكيد البريد الإلكتروني عبر الرمز المرسل", - "Video_call": "مكالمة فيديو", - "View_Original": "عرض المحتوى الأصلي", - "Voice_call": "مكالمة صوتية", - "Waiting_for_network": "بانتظار توفر شبكة...", - "Websocket_disabled": "تم تعطيل Websocket لهذا الخادم.\n{{contact}}", - "Welcome": "مرحبا", - "What_are_you_doing_right_now": "ما الذي تفعله حالياً؟", - "Whats_your_2fa": "ما هو رمز التحقق الثنائي؟", - "Without_Servers": "بدون خوادم", - "Workspaces": "مساحات العمل", - "Would_you_like_to_return_the_inquiry": "هل ترغب بالرد على السؤال؟", - "Write_External_Permission_Message": "يحتاج Rocket.Chat للوصول إلى معرض الصور الخاص بك حتى تتمكن من حفظ الصور", - "Write_External_Permission": "إذن معرض", - "Yes": "نعم", - "Yes_action_it": "نعم، {{action}}!", - "Yesterday": "أمس", - "You_are_in_preview_mode": "أنت في وضع المعاينة", - "You_are_offline": "أنت غير متصل", - "You_can_search_using_RegExp_eg": "يمكنك استخدام RegExp. مثال: `/^text$/i`", - "You_colon": "أنت: ", - "you_were_mentioned": "تمت الإشارة إليك", - "You_were_removed_from_channel": "تمت إزالتك من {{channel}}", - "you": "أنت", - "You": "أنت", - "Logged_out_by_server": "لقد تم تسجيل خروجك من قبل الخادم. الرجاد الدخول من جديد", - "You_need_to_access_at_least_one_RocketChat_server_to_share_something": "تحتاج إلى الوصول إلى خادم Rocket.Chat واحد على الأقل لمشاركة شيء ما", - "You_need_to_verifiy_your_email_address_to_get_notications": "يجب تأكيد البريد الإلكتروني حتى تصلك الإشعارات", - "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": "لن تنتهي صلاحية رابط الدعوة الخاص بك", - "Your_workspace": "مساحة عملك", - "Your_password_is": "كلمة المرور الخاصة بك هي", - "Version_no": "النسخة: {{version}}", - "You_will_not_be_able_to_recover_this_message": "لن تتمكن من استعادة هذه الرسالة!", - "You_will_unset_a_certificate_for_this_server": "ستلغي شهادة هذا الخادم", - "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": "الرد على الأحدث", - "Reply_in_Thread": "الرد في موضوع", - "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": "سيؤدي هذا إلى محو جميع بياناتك في وضع عدم الاتصال.", - "This_will_remove_all_data_from_this_server": "هذا الإجراء يحذف جميع البيانات من هذا السيرفر", - "Mark_unread": "علامة غير مقروء", - "Wait_activation_warning": "يحب تفعيل حسابك من المشرف قبل تسجيل الدخول", - "Screen_lock": "قفل الشاشة", - "Local_authentication_biometry_title": "صادق", - "Local_authentication_biometry_fallback": "استخدم كلمة المرور", - "Local_authentication_unlock_option": "افتح القفل بكلمة المرور", - "Local_authentication_change_passcode": "تغيير كلمة المرور", - "Local_authentication_info": "تنويه: يجب حذف التطبيق عند نسيان كلمة المرور", - "Local_authentication_facial_recognition": "التعرف على الوجه", - "Local_authentication_fingerprint": "البصمة", - "Local_authentication_unlock_with_label": "فتح القفل بـ {{label}}", - "Local_authentication_auto_lock_60": "بعد دقيقة", - "Local_authentication_auto_lock_300": "بعد 5 دقائق", - "Local_authentication_auto_lock_900": "بعد 15 دقيقة", - "Local_authentication_auto_lock_1800": "بعد 30 دقيقة", - "Local_authentication_auto_lock_3600": "بعد ساعة", - "Passcode_enter_title": "أدخل كلمة المرور", - "Passcode_choose_title": "اختر كلمة المرور الجديدة", - "Passcode_choose_confirm_title": "تأكيد كلمة المرور الجديدة", - "Passcode_choose_error": "كلمة المرور غير متطابقة. حاول مجدداً", - "Passcode_choose_force_set": "كلمة المرور مطلوبة من المشرف", - "Passcode_app_locked_title": "التطبيق مقفل", - "Passcode_app_locked_subtitle": "حاول مجدداً بعد {{timeLeft}} ثوان", - "After_seconds_set_by_admin": "بعد {{seconds}} ثوان (حددها المدير)", - "Dont_activate": "لا تقم بالتفعيل الآن", - "Queued_chats": "محادثات في قائمى الانتظار", - "Queue_is_empty": "قائمة الانتظار فارغة", - "Logout_from_other_logged_in_locations": "تسجيل الخروج من الأماكن الأخرى", - "You_will_be_logged_out_from_other_locations": "سيتم تسجيل خروج من الأماكن الأخرى", - "Logged_out_of_other_clients_successfully": "تم تسجيل الخروج من الأماكن الأخرى بنجاح", - "Logout_failed": "فشل تسجيل الخروج!", - "Log_analytics_events": "تحليلات سجل الأحداث", - "invalid-room": "غرفة غير صالحة" -} \ No newline at end of file + "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}}", + "error-avatar-url-handling": "خطأ في معالجة الصورة الرمزية ({{url}}) للمستخدم {{username}}", + "error-cant-invite-for-direct-room": "لا يمكن دعوة المستخدم في الغرفة المباشرة", + "error-could-not-change-email": "تعذر تغيير البريد الإلكتروني", + "error-could-not-change-name": "تعذر تغيير الاسم", + "error-could-not-change-username": "تعذر تغيير اسم المستخدم", + "error-could-not-change-status": "تعذر تغيير الحالة", + "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-save-video": "خطأ عند حفظ الفيديو", + "error-field-unavailable": "{{field}} مستخدم بالفعل :(", + "error-file-too-large": "حجم الملف كبير جداً", + "error-importer-not-defined": "المستورِد معرف بطريقة غير صحيحة، يجب تحديد نوع الإستيراد", + "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": "عنوان البريد اﻹلكتروني غير صالح {{email}}", + "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": "رابط إعادة توجيه غير صحيح", + "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": "عناوين غير صالحة", + "error-invalid-user": "مستخدم غير صالح", + "error-invalid-username": "اسم المستخدم غير صالح", + "error-invalid-webhook-response": "الرد التلقائي من العنوان استجاب برمز مغاير عن 200", + "error-message-deleting-blocked": "حذف الرسالة محظور", + "error-message-editing-blocked": "تعديل الرسالة محظور", + "error-message-size-exceeded": "حجم الرسالة تجاوز الحد المسموح به (Message_MaxAllowedSize)", + "error-missing-unsubscribe-link": "يجب عليك تقديم رابط [unsubscribe]", + "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": "التسجيل مسموح به عبر عنوان الويب السري فقط", + "error-you-are-last-owner": "أنت المالك الأخير. يرجى تعيين مالك جديد قبل مغادرة الغرفة", + "Actions": "الإجراءات", + "activity": "نشاط", + "Activity": "النشاط", + "Add_Reaction": "إضافة تفاعل", + "Add_Server": "إضافة خادم", + "Add_users": "إضافة مستخدمين", + "Admin_Panel": "لوحة الإدارة", + "Agent": "المندوب", + "Alert": "إنذار", + "alert": "إنذار", + "alerts": "الإنذارات", + "All_users_in_the_channel_can_write_new_messages": "يمكن لجميع المستخدمين في القناة كتابة رسائل جديدة", + "A_meaningful_name_for_the_discussion_room": "اسم معبر لغرفة النقاش", + "All": "الكل", + "All_Messages": "كل الرسائل", + "Allow_Reactions": "السماح للتفاعلات", + "Alphabetical": "أبجدي", + "and_more": "وأكثر", + "and": "و", + "announcement": "إعلان", + "Announcement": "إعلان", + "Apply_Your_Certificate": "طبق شهادتك", + "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": "عنوان ويب الصورة الرمزية", + "Away": "غير متواجد", + "Back": "عودة", + "Black": "أسود", + "Block_user": "حظر المستخدم", + "Browser": "المتصفح", + "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": "تم انهاء المكالمة بالفعل !", + "Clear_cookies_alert": "هل تريد حذف جميع ملفات تعريف الإرتباط؟", + "Clear_cookies_desc": "هذا الإجراء سيحذف ملفات تعريف الإرتباط الخاصة بتسجيل الدخول مما يسمح بتسجيل الدخول لحسابات أخرى", + "Clear_cookies_yes": "نعم، مسح ملفات الإرتباط", + "Clear_cookies_no": "لا، احتفظ بملفات تعريف الإرتباط", + "Click_to_join": "انقر للانضمام!", + "Close": "إغلاق", + "Close_emoji_selector": "إغلاق محدد الرموز التعبيرية", + "Closing_chat": "إغلاق المحادثة", + "Change_language_loading": "تغيير اللغة", + "Chat_closed_by_agent": "المندوب أغلق المحادثة", + "Choose": "اختر", + "Choose_from_library": "اختر من المكتبة", + "Choose_file": "اختر ملف", + "Choose_where_you_want_links_be_opened": "اختر المكان الذي تريد فتح الروابط فيه", + "Code": "الرمز", + "Code_or_password_invalid": "الرمز أو كلمة المرور خاطئة", + "Collaborative": "تعاونية", + "Confirm": "تأكيد", + "Connect": "اتصال", + "Connected": "متصل", + "connecting_server": "يتم الاتصال بالخادم", + "Connecting": "جار الاتصال...", + "Contact_us": "تواصل معنا", + "Contact_your_server_admin": "اتصل بمسؤول الخادم", + "Continue_with": "متابعة بـ", + "Copied_to_clipboard": "تم النسخ للحافظة!", + "Copy": "نسخ", + "Conversation": "محادثة", + "Permalink": "رابط ثابت", + "Certificate_password": "الرقم السري للشهادة", + "Clear_cache": "امسح ذاكرة التخزين المؤقتة للخادم", + "Clear_cache_loading": "يتم مسح ذاكرة التخزين", + "Whats_the_password_for_your_certificate": "ماهي كلمة المرور للشهادة؟", + "Create_account": "إنشاء حساب", + "Create_Channel": "إنشاء قناة", + "Create_Direct_Messages": "إنشاء رسالة مباشرة", + "Create_Discussion": "إنشاء نقاش", + "Created_snippet": "إنشاء مقتطف", + "Create_a_new_workspace": "إنشاء مساحة عمل جديدة", + "Create": "إنشاء", + "Custom_Status": "حالة مخصصة", + "Dark": "داكن", + "Dark_level": "مستوى السمة الداكنة", + "Default": "افتراضي", + "Default_browser": "المتصفح الأساسي", + "Delete_Room_Warning": "سيؤدي حذف الغرفة إلى حذف جميع الرسائل المنشورة. لا يمكن التراجع بعد الحذف", + "Department": "القسم", + "delete": "حذف", + "Delete": "حذف", + "DELETE": "حذف", + "deleting_room": "حذف الغرفة", + "description": "وصف", + "Description": "وصف", + "Desktop_Alert_info": "هذه الإشعارات ترسل لسطح المكتب", + "Directory": "مجلد", + "Direct_Messages": "رسالة مباشرة", + "Disable_notifications": "أوقف الإشعارات", + "Discussions": "مناقشات", + "Discussion_Desc": "ساهم بإعطاء نظرة عامة لما يجري. عند إنشاء مناقشة يتم إنشاء قناة فرعية وربطها بالقناة المحددة", + "Discussion_name": "اسم النقاش", + "Done": "تم", + "Dont_Have_An_Account": "ليس لديك حساب؟", + "Do_you_have_an_account": "هل لديك حساب؟", + "Do_you_have_a_certificate": "هل لديك شهادة؟", + "Do_you_really_want_to_key_this_room_question_mark": "هل تريد حقاً {{key}} هذه الغرفة؟", + "E2E_How_It_Works_info1": "يمكنك الآن إنشاء مجموعات خاصة ورسائل مباشرة مشفرة. بإمكانك أيضاً تشفير المجموعات الخاصة والرسائل المباشرة الموجودة مسبقاً", + "E2E_How_It_Works_info2": "التشفير يتم بين الطرفيات بمعنى أن المفتاح سيستخدم لتشفير وفك تشفير رسائلك ولن يتم حفظه في الخادم. لذلك يترتب عليك حفظ كلمة المرور هذه في مكان آمن", + "E2E_How_It_Works_info3": "حين الاستمرار سيتم إنشاء كلمة المرور بين الطرفيات", + "E2E_How_It_Works_info4": "بإمكانك أيضاً إنشاء كلمة مرور جديدة لمفتاح التشفير في أي وقت عند دخولك بكلمة المرور الحالية لمفتاح التشفير", + "edit": "تعديل", + "edited": "معدل", + "Edit": "تعديل", + "Edit_Status": "تعديل الحالة", + "Edit_Invite": "تعديل الدعوة", + "End_to_end_encrypted_room": "غرفة مشفرة بين الطرفيات", + "end_to_end_encryption": "تشفير بين الطرفيات", + "Email_Notification_Mode_All": "لكل إشارة أو رسالة مباشرة", + "Email_Notification_Mode_Disabled": "معطل", + "Email_or_password_field_is_empty": "حقل البريد الإلكتروني أو كلمة المرور فارغ", + "Email": "البريد الإلكتروني", + "email": "البريد الإلكتروني", + "Empty_title": "عنوان فارغ", + "Enable_Auto_Translate": "تمكين الترجمة التلقائية", + "Enable_notifications": "تفعيل الإشعارات", + "Encrypted": "مشفر", + "Encrypted_message": "رسالة مشفرة", + "Enter_Your_E2E_Password": "أدخل كلمة المرور بين الطرفيات", + "Enter_Your_Encryption_Password_desc1": "سيمكنك هذا من الوصول لرسائلك المباشرة والمجموعات الخاصة", + "Enter_Your_Encryption_Password_desc2": "يجب إدخال كلمة المرور لتشفير وفك تشفير الرسائل المرسلة", + "Encryption_error_title": "كلمة المرور المشفرة خاطئة", + "Encryption_error_desc": "تعذر قراءة مفتاح التشفير أثناء الاستيراد", + "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_password_If_this_email_is_registered": "إن كان البريد الإلكتروني مسجلاً، فسنرسل تعليمات إعادة تعيين كلمة المرور الخاصة بك. إذا لم تتلق بريداً إلكترونياً قريباً، فيرجى العودة والمحاولة مرة أخرى", + "Forgot_password": "هل نسيت كلمة المرور؟", + "Forgot_Password": "نسيت كلمة المرور", + "Forward": "إعادة توجيه", + "Forward_Chat": "إعادة توجيه المحادثة", + "Forward_to_department": "إعادة توجيه للقسم", + "Forward_to_user": "إعادة توجيه لمستخدم", + "Full_table": "انقر لرؤية الجدول كاملاً", + "Generate_New_Link": "إنشاء رابط جديد", + "Group_by_favorites": "جمع حسب المفضلة", + "Group_by_type": "جمع حسب النوع", + "Hide": "إخفاء", + "Has_joined_the_channel": "انضم إلى القناة", + "Has_joined_the_conversation": "انضم إلى المحادثة", + "Has_left_the_channel": "غادر القناة", + "Hide_System_Messages": "إخفاء رسائل النظام", + "Hide_type_messages": "إخفاء رسائل \"{{type}}\"", + "How_It_Works": "طريقة العمل", + "Message_HideType_uj": "مستخدم انضم", + "Message_HideType_ul": "مستخدم غادر", + "Message_HideType_ru": "مستخدم حُذف", + "Message_HideType_au": "مستخدم أضيف", + "Message_HideType_mute_unmute": "صوت المستخدم كتم / فتح", + "Message_HideType_r": "اسم الغرفة تغير", + "Message_HideType_ut": "مستخدم انضم للمحادثة", + "Message_HideType_wm": "ترحيب", + "Message_HideType_rm": "رسالة حُذفت", + "Message_HideType_subscription_role_added": "تعيين دور جديد", + "Message_HideType_subscription_role_removed": "دور أصبح غير معرف", + "Message_HideType_room_archived": "غرفة أرشفت", + "Message_HideType_room_unarchived": "غرفة ألغيت أرشفتها", + "I_Saved_My_E2E_Password": "قمت بحفظ كلمة المرور الطرفية", + "IP": " عنوان بروتوكول الإنترنت (الآيبي)", + "In_app": "في التطبيق", + "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": "انضم", + "Join_our_open_workspace": "انضم لمساحة عملنا المفتوحة", + "Join_your_workspace": "انضم لمساحة عملك", + "Just_invited_people_can_access_this_channel": "يمكن للأشخاص المدعوين فقط الوصول إلى هذه القناة", + "Language": "اللغة", + "last_message": "الرسالة الأخيرة", + "Leave_channel": "مغادرة القناة", + "leaving_room": "مغادرة الغرفة", + "Leave": "مغادرة الغرفة", + "leave": "مغادرة", + "Legal": "قانوني", + "Light": "ساطع", + "License": "رخصة", + "Livechat": "محادثة مباشرة", + "Livechat_edit": "تعديل المحادثة المباشرة", + "Login": "تسجيل الدخول", + "Login_error": "تم رفض تسجيل الدخول الرجاء المحاولة مرة أخرى", + "Login_with": "تسجيل الدخول بـ", + "Logging_out": "تسجيل الخروج", + "Logout": "تسجيل الخروج", + "Max_number_of_uses": "أقصى عدد للاستخدام", + "Max_number_of_users_allowed_is_number": "أقصى عدد للمستخدمين {{maxUsers}}", + "members": "أفراد", + "Members": "أفراد", + "Mentioned_Messages": "الرسائل المذكورة", + "mentioned": "مشار", + "Mentions": "الإشارات", + "Message_accessibility": "الرسالة من {{user}} في {{time}}: {{message}}", + "Message_actions": "إجراءات الرسالة", + "Message_pinned": "الرسالة مثبتة", + "Message_removed": "الرسالة حذفت", + "Message_starred": "الرسالة مميزة", + "Message_unstarred": "الرسالة غير مميزة", + "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": "اسم", + "Navigation_history": "تاريخ التصفح", + "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_label_provided": "لا {{label}} معطى", + "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": "تفضيلات الإشعار", + "No_available_agents_to_transfer": "المندوبين غير متوفرين حالياً", + "Offline": "غير متصل", + "Oops": "عفوًا!", + "Omnichannel": "القنوات الموحدة", + "Open_Livechats": "محادثات مباشرة جارية", + "Omnichannel_enable_alert": "أنت غير متاح ", + "Onboarding_description": "مساحة عمل هي مساحة لفريقك ومنظمتك للتعاون. اطلب من المشرف العنوان للانضمام أو أنشئ لفريقك", + "Onboarding_join_workspace": "انضم لمساحة عمل", + "Onboarding_subtitle": "ما بعد بيئة فريق تعاونية", + "Onboarding_title": "مرحبا بك في Rocket.Chat", + "Onboarding_join_open_description": "انضم لمساحة عملنا للتواصل مع فريق Rocket.Chat ومع المجتمع", + "Onboarding_agree_terms": "بالمواصلة أنت توافق على Rocket.Chat", + "Onboarding_less_options": "خيارات أقل", + "Onboarding_more_options": "خيارات أكثر", + "Online": "متصل", + "Only_authorized_users_can_write_new_messages": "يمكن للمستخدمين المصرح لهم فقط كتابة رسائل جديدة", + "Open_emoji_selector": "افتح محدد الرموز التعبيرية", + "Open_Source_Communication": "تواصل مفتوح المصدر", + "Open_your_authentication_app_and_enter_the_code": "افتح تطبيق المصادقة الخاص بك وأدخل الرمز", + "OR": "أو", + "OS": "نظام التشغيل", + "Overwrites_the_server_configuration_and_use_room_config": "يمكن للمستخدمين المصرح لهم فقط كتابة رسائل جديدة", + "Password": "كلمة المرور", + "Parent_channel_or_group": "القناة أو المجموعة الأب", + "Permalink_copied_to_clipboard": "تم نسخ الرابط الثابت إلى الحافظة!", + "Phone": "الهاتف", + "Pin": "ثبت", + "Pinned_Messages": "رسائل مثبتة", + "pinned": "مثبت", + "Pinned": "مثبت", + "Please_add_a_comment": "الرجاء إضافة تعليق", + "Please_enter_your_password": "الرجاء إدخال كلمة المرور", + "Please_wait": "الرجاء الإنتظار", + "Preferences": "التفضيلات", + "Preferences_saved": "تم حفظ التفضيلات", + "Privacy_Policy": "سياسة الخصوصية", + "Private_Channel": "قناة خاصة", + "Private": "خاص", + "Processing": "جار معالجة...", + "Profile_saved_successfully": "تم حفظ الملف الشخصي بنجاح!", + "Profile": "الملف الشخصي", + "Public_Channel": "قناة عامة", + "Public": "عام", + "Push_Notifications_Alert_Info": "يتم إرسال هذه الإشعارات إليك عندما لا يكون التطبيق مفتوحاً", + "Quote": "اقتباس", + "Reactions_are_disabled": "التفاعل معطل", + "Reactions_are_enabled": "التفاعل مفعل", + "Reactions": "التفاعلات", + "Read": "قراءة", + "Read_External_Permission_Message": "يحتاج Rocket.chat للوصول إلى الصور والملفات الموجودة على الجهاز", + "Read_External_Permission": "صلاحية قراءة الوسائط", + "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": "إعادة", + "Return": "العودة", + "Review_app_title": "هل أنت مستمتع بهذا التطبيق؟", + "Review_app_desc": "قم بإعطائنا 5 نجوم {{store}}", + "Review_app_yes": "أكيد!", + "Review_app_no": "لا", + "Review_app_later": "ربما لاحقاً", + "Review_app_unable_store": "لم يتمكن من فتح {{store}}", + "Review_this_app": "تقييم هذا التطبيق", + "Remove": "حذف", + "Roles": "أدوار", + "Room_actions": "إجراءات الغرفة", + "Room_changed_announcement": "تم تغيير إعلان الغرفة إلى: {{announcement}} من قبل {{userBy}}", + "Room_changed_description": "تم تغيير وصف الغرفة إلى: {{description}} من قبل {{userBy}}", + "Room_changed_privacy": "تم تغيير نوع الغرفة إلى: {{type}} من قبل {{userBy}}", + "Room_changed_topic": "تم تغيير موضوع الغرفة إلى: {{topic}} من قبل {{userBy}}", + "Room_Files": "ملفات الغرفة", + "Room_Info_Edit": "تعديل معلومات الغرفة", + "Room_Info": "معلومات الغرفة", + "Room_Members": "أعضاء الغرفة", + "Room_name_changed": "تم تغيير اسم الغرفة إلى: {{name}} من قبل {{userBy}}", + "SAVE": "حفظ", + "Save_Changes": "حفظ التغيرات", + "Save": "حفظ", + "Saved": "تم الحفظ", + "saving_preferences": "حفظ التفضيلات", + "saving_profile": "حفظ الملف الشخصي", + "saving_settings": "حفظ الإعدادات", + "saved_to_gallery": "تم الحفظ في المعرض", + "Save_Your_E2E_Password": "حفظ كلمة المرور بين الطرفين", + "Save_Your_Encryption_Password": "حفظ كلمة المرور المشفرة", + "Save_Your_Encryption_Password_warning": "لا نقوم بتخزين كلمة المرور، الرجاء حفظها لديك في مكان آخر", + "Save_Your_Encryption_Password_info": "لا يمكن إستعادة كلمة المرور في حال فقدانها ولن تستطيع الوصول لرسائلك", + "Search_Messages": "بحث الرسائل", + "Search": "بحث", + "Search_by": "بحث حسب", + "Search_global_users": "بحث عام عن المستخدمين", + "Search_global_users_description": "إذا قمت بالتفعيل، فسيمكنك البحث عن أي مستخدم في شركات أو خوادم أخرى", + "Seconds": "{{second}} ثواني", + "Select_Avatar": "حدد الصورة الرمزية", + "Select_Server": "حدد خادم", + "Select_Users": "حدد مستخدمين", + "Select_a_Channel": "حدد قناة", + "Select_a_Department": "حدد قسم", + "Select_an_option": "حدد خيار", + "Select_a_User": "حدد مستخدم", + "Send": "إرسال", + "Send_audio_message": "إرسال رسالة صوتية", + "Send_crash_report": "إرسال تقرير الأعطال", + "Send_message": "إرسال الرسالة", + "Send_me_the_code_again": "أرسل الرمز مرة أخرى", + "Send_to": "إرسال إلى...", + "Sending_to": "يتم الإرسال إلى", + "Sent_an_attachment": "تم إرسال المرفق", + "Server": "سرفر", + "Servers": "سرفرات", + "Server_version": "نسخة الخادم: {{version}}", + "Set_username_subtitle": "يتم استخدام اسم المستخدم للسماح للآخرين بذكرك في الرسائل", + "Set_custom_status": "حدد حالة خاصة", + "Set_status": "حدد حالة", + "Status_saved_successfully": "تم حفظ الحالة بنجاح!", + "Settings": "الإعدادات", + "Settings_succesfully_changed": "تم تعديل الإعدادات بنجاح!", + "Share": "مشاركة", + "Share_Link": "مشاركة رابط", + "Share_this_app": "مشاركة هذا البرنامج", + "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": "بداية المحادثة", + "Start_a_Discussion": "ابدأ نقاش", + "Started_discussion": "بدأ النقاش", + "Started_call": "أجرى الاتصال {{userBy}}", + "Submit": "إرسال", + "Table": "جدول", + "Tags": "العلامات", + "Take_a_photo": "التقاط صورة", + "Take_a_video": "التقاط فيديو", + "Take_it": "التقط!", + "tap_to_change_status": "انقر لتغيير الحالة", + "Tap_to_view_servers_list": "انقر لعرض قائمة الخوادم", + "Terms_of_Service": " شروط الخدمة ", + "Theme": "سمة", + "The_user_wont_be_able_to_type_in_roomName": "المستخدم لن يتمكن من الكتابة في {{roomName}}", + "The_user_will_be_able_to_type_in_roomName": "المستخدم سيتمكن من الكتابة في {{roomName}}", + "There_was_an_error_while_action": "حدث خطأ أثناء {{action}}!", + "This_room_is_blocked": "هذه الغرفة محظورة", + "This_room_is_read_only": "هذه الغرفة للقراءة فقط", + "Thread": "موضوع", + "Threads": "مواضيع", + "Timezone": "المنطقة الزمنية", + "To": "إلى", + "topic": "عنوان", + "Topic": "عنوان", + "Translate": "ترجمة", + "Try_again": "حاول مجدداً", + "Two_Factor_Authentication": "المصادقة الثنائية", + "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": "رفع الملف؟", + "User": "مستخدم", + "Users": "مستخدمين", + "User_added_by": "مستخدم {{userAdded}} أضيف من قبل {{userBy}}", + "User_Info": "معلومات المستخدم", + "User_has_been_key": "تمت {{key}} المستخدم!", + "User_is_no_longer_role_by_": "تم إزالة الدور {{role}} عن المستخدم {{user}} من قبل {{userBy}}", + "User_muted_by": "المستخدم {{userMuted}} كتم من قبل {{userBy}}", + "User_removed_by": "المستخدم {{userRemoved}} حذف من قبل {{userBy}}", + "User_sent_an_attachment": "{{user}} أرسل مرفقًا", + "User_unmuted_by": "ألغى {{userBy}} الكتم عن {{userUnmuted}}", + "User_was_set_role_by_": "أضيف دور {{role}} للمستخدم {{user}} من قبل {{userBy}}", + "Username_is_empty": "اسم المستخدم فارغ", + "Username": "اسم المستخدم", + "Username_or_email": "اسم المستخدم أو البريد الالكتروني", + "Uses_server_configuration": "يستخدم إعداد الخادم", + "Validating": "يتم التحقق", + "Registration_Succeeded": "تم التسجيل بنجاح", + "Verify": "تحقق", + "Verify_email_title": "تم التسجيل!", + "Verify_email_desc": "لقد أرسلنا إليك بريداً إلكترونياً لتأكيد تسجيلك. إذا لم تتلق البريد الإلكتروني قريباً، فيرجى العودة والمحاولة مرة أخرى", + "Verify_your_email_for_the_code_we_sent": "يرجى تأكيد البريد الإلكتروني عبر الرمز المرسل", + "Video_call": "مكالمة فيديو", + "View_Original": "عرض المحتوى الأصلي", + "Voice_call": "مكالمة صوتية", + "Waiting_for_network": "بانتظار توفر شبكة...", + "Websocket_disabled": "تم تعطيل Websocket لهذا الخادم.\n{{contact}}", + "Welcome": "مرحبا", + "What_are_you_doing_right_now": "ما الذي تفعله حالياً؟", + "Whats_your_2fa": "ما هو رمز التحقق الثنائي؟", + "Without_Servers": "بدون خوادم", + "Workspaces": "مساحات العمل", + "Would_you_like_to_return_the_inquiry": "هل ترغب بالرد على السؤال؟", + "Write_External_Permission_Message": "يحتاج Rocket.Chat للوصول إلى معرض الصور الخاص بك حتى تتمكن من حفظ الصور", + "Write_External_Permission": "إذن معرض", + "Yes": "نعم", + "Yes_action_it": "نعم، {{action}}!", + "Yesterday": "أمس", + "You_are_in_preview_mode": "أنت في وضع المعاينة", + "You_are_offline": "أنت غير متصل", + "You_can_search_using_RegExp_eg": "يمكنك استخدام RegExp. مثال: `/^text$/i`", + "You_colon": "أنت: ", + "you_were_mentioned": "تمت الإشارة إليك", + "You_were_removed_from_channel": "تمت إزالتك من {{channel}}", + "you": "أنت", + "You": "أنت", + "Logged_out_by_server": "لقد تم تسجيل خروجك من قبل الخادم. الرجاد الدخول من جديد", + "You_need_to_access_at_least_one_RocketChat_server_to_share_something": "تحتاج إلى الوصول إلى خادم Rocket.Chat واحد على الأقل لمشاركة شيء ما", + "You_need_to_verifiy_your_email_address_to_get_notications": "يجب تأكيد البريد الإلكتروني حتى تصلك الإشعارات", + "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": "لن تنتهي صلاحية رابط الدعوة الخاص بك", + "Your_workspace": "مساحة عملك", + "Your_password_is": "كلمة المرور الخاصة بك هي", + "Version_no": "النسخة: {{version}}", + "You_will_not_be_able_to_recover_this_message": "لن تتمكن من استعادة هذه الرسالة!", + "You_will_unset_a_certificate_for_this_server": "ستلغي شهادة هذا الخادم", + "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": "الرد على الأحدث", + "Reply_in_Thread": "الرد في موضوع", + "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": "سيؤدي هذا إلى محو جميع بياناتك في وضع عدم الاتصال.", + "This_will_remove_all_data_from_this_server": "هذا الإجراء يحذف جميع البيانات من هذا السيرفر", + "Mark_unread": "علامة غير مقروء", + "Wait_activation_warning": "يحب تفعيل حسابك من المشرف قبل تسجيل الدخول", + "Screen_lock": "قفل الشاشة", + "Local_authentication_biometry_title": "صادق", + "Local_authentication_biometry_fallback": "استخدم كلمة المرور", + "Local_authentication_unlock_option": "افتح القفل بكلمة المرور", + "Local_authentication_change_passcode": "تغيير كلمة المرور", + "Local_authentication_info": "تنويه: يجب حذف التطبيق عند نسيان كلمة المرور", + "Local_authentication_facial_recognition": "التعرف على الوجه", + "Local_authentication_fingerprint": "البصمة", + "Local_authentication_unlock_with_label": "فتح القفل بـ {{label}}", + "Local_authentication_auto_lock_60": "بعد دقيقة", + "Local_authentication_auto_lock_300": "بعد 5 دقائق", + "Local_authentication_auto_lock_900": "بعد 15 دقيقة", + "Local_authentication_auto_lock_1800": "بعد 30 دقيقة", + "Local_authentication_auto_lock_3600": "بعد ساعة", + "Passcode_enter_title": "أدخل كلمة المرور", + "Passcode_choose_title": "اختر كلمة المرور الجديدة", + "Passcode_choose_confirm_title": "تأكيد كلمة المرور الجديدة", + "Passcode_choose_error": "كلمة المرور غير متطابقة. حاول مجدداً", + "Passcode_choose_force_set": "كلمة المرور مطلوبة من المشرف", + "Passcode_app_locked_title": "التطبيق مقفل", + "Passcode_app_locked_subtitle": "حاول مجدداً بعد {{timeLeft}} ثوان", + "After_seconds_set_by_admin": "بعد {{seconds}} ثوان (حددها المدير)", + "Dont_activate": "لا تقم بالتفعيل الآن", + "Queued_chats": "محادثات في قائمى الانتظار", + "Queue_is_empty": "قائمة الانتظار فارغة", + "Logout_from_other_logged_in_locations": "تسجيل الخروج من الأماكن الأخرى", + "You_will_be_logged_out_from_other_locations": "سيتم تسجيل خروج من الأماكن الأخرى", + "Logged_out_of_other_clients_successfully": "تم تسجيل الخروج من الأماكن الأخرى بنجاح", + "Logout_failed": "فشل تسجيل الخروج!", + "Log_analytics_events": "تحليلات سجل الأحداث", + "invalid-room": "غرفة غير صالحة" +} diff --git a/app/i18n/locales/de.json b/app/i18n/locales/de.json index c2881a9c6..55b32c1fe 100644 --- a/app/i18n/locales/de.json +++ b/app/i18n/locales/de.json @@ -1,772 +1,776 @@ { - "1_person_reacted": "1 Person hat reagiert", - "1_user": "1 Benutzer", - "error-action-not-allowed": "{{action}} ist nicht erlaubt", - "error-application-not-found": "Anwendung nicht gefunden", - "error-archived-duplicate-name": "Es gibt bereits einen archivierten Kanal mit dem Namen {{room_name}}", - "error-avatar-invalid-url": "Ungültige Avatar-URL: {{url}}", - "error-avatar-url-handling": "Fehler beim Umgang mit der Avatar-Einstellung von einer URL ({{url}}) für {{username}}", - "error-cant-invite-for-direct-room": "Benutzer können nicht zu Räumen eingeladen werden", - "error-could-not-change-email": "E-Mail konnte nicht geändert werden", - "error-could-not-change-name": "Name konnte nicht geändert werden", - "error-could-not-change-username": "Benutzername konnte nicht geändert werden", - "error-could-not-change-status": "Status konnte nicht geändert werden", - "error-delete-protected-role": "Eine geschützte Rolle kann nicht gelöscht werden", - "error-department-not-found": "Abteilung nicht gefunden", - "error-direct-message-file-upload-not-allowed": "Dateifreigabe in direkten Nachrichten nicht zulässig", - "error-duplicate-channel-name": "Ein Kanal mit dem Namen {{room_name}} ist bereits vorhanden", - "error-email-domain-blacklisted": "Die E-Mail-Domain wird auf die schwarze Liste gesetzt", - "error-email-send-failed": "Fehler beim Versuch, eine E-Mail zu senden: {{message}}", - "error-save-image": "Fehler beim Speichern des Bildes", - "error-save-video": "Fehler beim Speichern des Videos", - "error-field-unavailable": "{{field}} wird bereits verwendet :(", - "error-file-too-large": "Datei ist zu groß", - "error-importer-not-defined": "Der Import wurde nicht korrekt definiert, es fehlt die Importklasse.", - "error-input-is-not-a-valid-field": "{{input}} ist kein gültiges {{field}}", - "error-invalid-actionlink": "Ungültiger Aktionslink", - "error-invalid-arguments": "Ungültige Argumente", - "error-invalid-asset": "Ungültiges Asset", - "error-invalid-channel": "Ungültiger Kanal", - "error-invalid-channel-start-with-chars": "Ungültiger Kanal. Beginne mit @ oder #", - "error-invalid-custom-field": "Ungültiges benutzerdefiniertes Feld", - "error-invalid-custom-field-name": "Ungültiger benutzerdefinierter Feldname. Verwende nur Buchstaben, Zahlen, Bindestriche und Unterstriche.", - "error-invalid-date": "Ungültiges Datum angegeben", - "error-invalid-description": "Ungültige Beschreibung", - "error-invalid-domain": "Ungültige Domain", - "error-invalid-email": "Ungültige E-Mail {{email}}", - "error-invalid-email-address": "Ungültige E-Mail-Adresse", - "error-invalid-file-height": "Ungültige Dateihöhe", - "error-invalid-file-type": "Ungültiger Dateityp", - "error-invalid-file-width": "Ungültige Dateibreite", - "error-invalid-from-address": "Du hast eine ungültige FROM-Adresse mitgeteilt.", - "error-invalid-integration": "Ungültige Integration", - "error-invalid-message": "Ungültige Nachricht", - "error-invalid-method": "Ungültige Methode", - "error-invalid-name": "Ungültiger Name", - "error-invalid-password": "Ungültiges Passwort", - "error-invalid-redirectUri": "Ungültige Weiterleitung", - "error-invalid-role": "Ungültige Rolle", - "error-invalid-room": "Ungültiger Raum", - "error-invalid-room-name": "{{room_name}} ist kein gültiger Raumname", - "error-invalid-room-type": "{{type}} ist kein gültiger Raumtyp.", - "error-invalid-settings": "Ungültige Einstellungen angegeben", - "error-invalid-subscription": "Ungültiges Abonnement", - "error-invalid-token": "Ungültiges Token", - "error-invalid-triggerWords": "Ungültige TriggerWords", - "error-invalid-urls": "Ungültige URLs", - "error-invalid-user": "Ungültiger Benutzer", - "error-invalid-username": "Ungültiger Benutzername", - "error-invalid-webhook-response": "Die Webhook-URL antwortete mit einem anderen Status als 200", - "error-message-deleting-blocked": "Das Löschen von Nachrichten ist gesperrt", - "error-message-editing-blocked": "Die Bearbeitung von Nachrichten ist gesperrt", - "error-message-size-exceeded": "Die Nachrichtengröße überschreitet Message_MaxAllowedSize", - "error-missing-unsubscribe-link": "Du musst den Link [abbestellen] angeben.", - "error-no-owner-channel": "Dieser Raum gehört dir nicht", - "error-no-tokens-for-this-user": "Für diesen Benutzer gibt es keine Token", - "error-not-allowed": "Nicht erlaubt", - "error-not-authorized": "Nicht berechtigt", - "error-push-disabled": "Push ist deaktiviert", - "error-remove-last-owner": "Dies ist der letzte Besitzer. Bitte lege einen neuen Besitzer fest, bevor du diesen entfernst.", - "error-role-in-use": "Rolle kann nicht gelöscht werden, da sie gerade verwendet wird", - "error-role-name-required": "Der Rollenname ist erforderlich", - "error-the-field-is-required": "Das Feld {{field}} ist erforderlich.", - "error-too-many-requests": "Fehler, zu viele Anfragen. Du musst {{seconds}} Sekunden warten, bevor du es erneut versuchst.", - "error-user-is-not-activated": "Benutzer ist nicht aktiviert", - "error-user-has-no-roles": "Benutzer hat keine Rollen", - "error-user-limit-exceeded": "Die Anzahl der Benutzer, die du zu #channel_name einladen möchtest, überschreitet die vom Administrator festgelegte Grenze", - "error-user-not-in-room": "Benutzer ist nicht in diesem Raum", - "error-user-registration-custom-field": "error-user-registration-custom-field", - "error-user-registration-disabled": "Die Benutzerregistrierung ist deaktiviert", - "error-user-registration-secret": "Die Benutzerregistrierung ist nur über eine geheime URL möglich", - "error-you-are-last-owner": "Du bist der letzte Besitzer. Bitte setze einen neuen Besitzer, bevor du den Raum verlässt.", - "error-status-not-allowed": "Unsichtbar-Status ist deaktiviert", - "Actions": "Aktionen", - "activity": "Aktivität", - "Activity": "Aktivität", - "Add_Reaction": "Reaktion hinzufügen", - "Add_Server": "Server hinzufügen", - "Add_users": "Benutzer hinzufügen", - "Admin_Panel": "Admin-Panel", - "Agent": "Agent", - "Alert": "Benachrichtigung", - "alert": "Benachrichtigung", - "alerts": "Benachrichtigungen", - "All_users_in_the_channel_can_write_new_messages": "Alle Benutzer im Kanal können neue Nachrichten schreiben", - "All_users_in_the_team_can_write_new_messages": "Alle Mitglieder eines Teams können neue Nachrichten schreiben", - "A_meaningful_name_for_the_discussion_room": "Ein aussagekräftiger Name für den Diskussionsraum", - "All": "alle", - "All_Messages": "Alle Nachrichten", - "Allow_Reactions": "Reaktionen zulassen", - "Alphabetical": "Alphabetisch", - "and_more": "und mehr", - "and": "und", - "announcement": "Ankündigung", - "Announcement": "Ankündigung", - "Apply_Your_Certificate": "Wende dein Zertifikat an", - "ARCHIVE": "ARCHIV", - "archive": "Archiv", - "are_typing": "tippen", - "Are_you_sure_question_mark": "Bist du sicher?", - "Are_you_sure_you_want_to_leave_the_room": "Möchtest du den Raum wirklich verlassen {{room}}?", - "Audio": "Audio", - "Authenticating": "Authentifizierung", - "Automatic": "Automatisch", - "Auto_Translate": "Automatische Übersetzung", - "Avatar_changed_successfully": "Avatar erfolgreich geändert!", - "Avatar_Url": "Avatar-URL", - "Away": "Abwesend", - "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": "Broadcast-Kanal", - "Busy": "Beschäftigt", - "By_proceeding_you_are_agreeing": "Indem du fortfährst, stimmst du zu unserem", - "Cancel_editing": "Bearbeitung abbrechen", - "Cancel_recording": "Aufnahme abbrechen", - "Cancel": "Abbrechen", - "changing_avatar": "Avatar wechseln", - "creating_channel": "Kanal erstellen", - "creating_invite": "Einladung erstellen", - "Channel_Name": "Kanal Name", - "Channels": "Kanäle", - "Chats": "Chats", - "Call_already_ended": "Anruf bereits beendet!", - "Clear_cookies_alert": "Möchtest du alle Cookies löschen?", - "Clear_cookies_desc": "Diese Aktion wird alle Login-Cookies löschen und erlaubt es dir, dich mit einem anderen Konto anzumelden.", - "Clear_cookies_yes": "Ja, Cookies löschen", - "Clear_cookies_no": "Nein, Cookies behalten", - "Click_to_join": "Klicken um beizutreten!", - "Close": "Schließen", - "Close_emoji_selector": "Schließe die Emoji-Auswahl", - "Closing_chat": "Chat schließen", - "Change_language_loading": "Ändere Sprache.", - "Chat_closed_by_agent": "Chat durch den Agenten geschlossen", - "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", - "Code_or_password_invalid": "Code oder Passwort sind falsch", - "Collaborative": "Kollaborativ", - "Confirm": "Bestätigen", - "Connect": "Verbinden", - "Connected": "Verbunden", - "connecting_server": "verbinde zum Server", - "Connecting": "Verbinden ...", - "Contact_us": "Kontaktiere uns", - "Contact_your_server_admin": "Kontaktiere deinen Server-Administrator.", - "Continue_with": "Weitermachen mit", - "Copied_to_clipboard": "In die Zwischenablage kopiert!", - "Copy": "Kopieren", - "Conversation": "Konversationen", - "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 dein Zertifikat?", - "Create_account": "Ein Konto erstellen", - "Create_Channel": "Kanal erstellen", - "Create_Direct_Messages": "Direkt-Nachricht erstellen", - "Create_Discussion": "Diskussion erstellen", - "Created_snippet": "ein Snippet erstellt", - "Create_a_new_workspace": "Erstelle einen neuen Arbeitsbereich", - "Create": "Erstellen", - "Custom_Status": "Eigener Status", - "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.", - "Department": "Abteilung", - "delete": "löschen", - "Delete": "Löschen", - "DELETE": "LÖSCHEN", - "move": "verschieben", - "deleting_room": "lösche Raum", - "description": "Beschreibung", - "Description": "Beschreibung", - "Desktop_Options": "Desktop-Einstellungen", - "Desktop_Notifications": "Desktop-Benachrichtigungen", - "Desktop_Alert_info": "Diese Benachrichtigungen werden auf dem Desktop angezeigt", - "Directory": "Verzeichnis", - "Direct_Messages": "Direktnachrichten", - "Disable_notifications": "Benachrichtigungen deaktiveren", - "Discussions": "Diskussionen", - "Discussion_Desc": "Hilft dir die Übersicht zu behalten! Durch das Erstellen einer Diskussion wird ein Unter-Kanal im ausgewählten Raum erzeugt und beide verknüpft.", - "Discussion_name": "Diskussions-Name", - "Done": "Erledigt", - "Dont_Have_An_Account": "Du hast noch kein Konto?", - "Do_you_have_an_account": "Du hast schon ein Konto?", - "Do_you_have_a_certificate": "Hast du ein Zertifikat?", - "Do_you_really_want_to_key_this_room_question_mark": "Möchtest du diesen Raum wirklich {{key}}?", - "E2E_Encryption": "E2E-Verschlüsselung", - "E2E_How_It_Works_info1": "Du kannst nun verschlüsselte private Gruppen und Direktnachrichten versenden. Du kannst außerdem deine bestehenden privaten Gruppen und Direktnachrichten auf Verschlüsselung umstellen.", - "E2E_How_It_Works_info2": "Dies ist *Ende-zu-Ende-Verschlüsselung*, daher wird der Schlüssel um die Nachrichten zu ver-/entschlüsseln nicht auf dem Server gespeichert. Aus diesem Grund musst du dieses Passwort an einem sicheren Ort speichern, so dass du später bei Bedarf darauf zugreifen kannst.", - "E2E_How_It_Works_info3": "Wenn du fortfährst, wird automatisch ein ein E2E-Passwort erzeugt.", - "E2E_How_It_Works_info4": "Du kannst außerdem jederzeit, in jedem Browser, in dem du das bestehende Passwort eingegeben hast, ein neues Passwort setzen.", - "edit": "bearbeiten", - "edited": "bearbeitet", - "Edit": "Bearbeiten", - "Edit_Status": "Status ändern", - "Edit_Invite": "Einladung bearbeiten", - "End_to_end_encrypted_room": "Ende-zu-Ende-verschlüsselter Raum", - "end_to_end_encryption": "Nicht mehr Ende-zu-Ende verschlüsseln", - "Email_Notification_Mode_All": "Jede Erwähnung/Direktnachricht", - "Email_Notification_Mode_Disabled": "Deaktiviert", - "Email_or_password_field_is_empty": "Das E-Mail- oder Passwortfeld ist leer", - "Email": "E-mail", - "email": "E-mail", - "Empty_title": "leerer Titel", - "Enable_Auto_Translate": "Automatische Übersetzung aktivieren", - "Enable_notifications": "Benachrichtigungen aktivieren", - "Encrypted": "Verschlüsselt", - "Encrypted_message": "Verschlüsselte Nachricht", - "Enter_Your_E2E_Password": "Gib dein Ende-zu-Ende-Passwort ein", - "Enter_Your_Encryption_Password_desc1": "Das erlaubt dir auf deine verschlüsselten privaten Gruppen und Direktnachrichten zuzugreifen.", - "Enter_Your_Encryption_Password_desc2": "Du musst das Passwort zur Ver-/Entschlüsselung an jeder Stelle eingeben, an dem du diesen Chat verwendest.", - "Encryption_error_title": "Dein Verschlüsselungs-Passwort scheint falsch zu sein", - "Encryption_error_desc": "Es war nicht möglich deinen Verschlüsselungs-Key zu importieren.", - "Everyone_can_access_this_channel": "Jeder kann auf diesen Kanal zugreifen", - "Everyone_can_access_this_team": "Jeder kann auf dieses Team zugreifen", - "Error_uploading": "Fehler beim Hochladen", - "Expiration_Days": "läuft ab (Tage)", - "Favorite": "Favorisieren", - "Favorites": "Favoriten", - "Files": "Dateien", - "File_description": "Dateibeschreibung", - "File_name": "Dateiname", - "Finish_recording": "Beende die Aufnahme", - "Following_thread": "Thread folgen", - "For_your_security_you_must_enter_your_current_password_to_continue": "Zu deiner Sicherheit musst du dein aktuelles Passwort eingeben, um fortzufahren", - "Forgot_password_If_this_email_is_registered": "Wenn diese E-Mail registriert ist, senden wir Anweisungen zum Zurücksetzen deines Passworts. Wenn du nicht in Kürze keine E-Mail erhältst, versuche es bitte erneut.", - "Forgot_password": "Passwort vergessen", - "Forgot_Password": "Passwort vergessen", - "Forward": "Weiterleiten", - "Forward_Chat": "Chat weiterleiten", - "Forward_to_department": "Weiterleiten an Abteilung", - "Forward_to_user": "Weiterleiten an Benutzer", - "Full_table": "Klicken um die ganze Tabelle anzuzeigen", - "Generate_New_Link": "Neuen Link erstellen", - "Group_by_favorites": "Nach Favoriten gruppieren", - "Group_by_type": "Gruppieren nach Typ", - "Hide": "Ausblenden", - "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", - "Hide_System_Messages": "Systemnachrichten verstecken", - "Hide_type_messages": "Verstecke \"{{type}}\"-Nachrichten", - "How_It_Works": "Wie es funktioniert", - "Message_HideType_uj": "Benutzer beigetreten", - "Message_HideType_ul": "Benutzer verlassen", - "Message_HideType_ru": "Benutzer entfernt", - "Message_HideType_au": "Benutzer hinzugefügt", - "Message_HideType_mute_unmute": "Benutzer stummgeschaltet / freigegeben", - "Message_HideType_r": "Raumname geändert", - "Message_HideType_ut": "Benutzer ist der Unterhaltung beigetreten", - "Message_HideType_wm": "Willkommen", - "Message_HideType_rm": "Nachricht entfernt", - "Message_HideType_subscription_role_added": "Rolle wurde gesetzt", - "Message_HideType_subscription_role_removed": "Rolle nicht länger definiert", - "Message_HideType_room_archived": "Raum archiviert", - "Message_HideType_room_unarchived": "Raum nicht mehr archiviert", - "I_Saved_My_E2E_Password": "Ich habe mein Ende-zu-Ende-Passwort gesichert", - "IP": "IP", - "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", - "Invite": "Einladen", - "is_a_valid_RocketChat_instance": "ist eine gültige Rocket.Chat-Instanz", - "is_not_a_valid_RocketChat_instance": "ist keine gültige Rocket.Chat-Instanz", - "is_typing": "schreibt", - "Invalid_or_expired_invite_token": "Ungültiger oder abgelaufener Einladungscode", - "Invalid_server_version": "Der Server, zu dem du dich verbinden möchtest, verwendet eine Version, die von der App nicht mehr unterstützt wird: {{currentVersion}}.\n\nWir benötigen Version {{minVersion}}.", - "Invite_Link": "Einladungs-Link", - "Invite_users": "Benutzer einladen", - "Join": "Beitreten", - "Join_Code": "Beitrittscode", - "Insert_Join_Code": "Beitrittscode eingeben", - "Join_our_open_workspace": "Tritt unserem offenen Arbeitsbereich bei", - "Join_your_workspace": "Tritt deinem Arbeitsbereich bei", - "Just_invited_people_can_access_this_channel": "Nur eingeladene Personen können auf diesen Kanal zugreifen", - "Just_invited_people_can_access_this_team": "Nur eingeladene Personen können auf das Team zugreifen", - "Language": "Sprache", - "last_message": "letzte Nachricht", - "Leave_channel": "Kanal verlassen", - "leaving_room": "Raum verlassen", - "Leave": "Raum verlassen", - "leave": "verlassen", - "Legal": "Rechtliches", - "Light": "Hell", - "License": "Lizenz", - "Livechat": "Live-Chat", - "Livechat_edit": "Live-Chat bearbeiten", - "Login": "Anmeldung", - "Login_error": "Deine Zugangsdaten wurden abgelehnt! Bitte versuche es erneut.", - "Login_with": "Einloggen mit", - "Logging_out": "Abmelden.", - "Logout": "Abmelden", - "Max_number_of_uses": "Maximale Anzahl der Benutzungen", - "Max_number_of_users_allowed_is_number": "Maximale Anzahl von erlaubten Benutzern ist {{maxUsers}}", - "members": "Mitglieder", - "Members": "Mitglieder", - "Mentioned_Messages": "Erwähnte Nachrichten", - "mentioned": "erwähnt", - "Mentions": "Erwähnungen", - "Message_accessibility": "Nachricht von {{user}} um {{time}}: {{message}}", - "Message_actions": "Nachrichtenaktionen", - "Message_pinned": "Eine Nachricht wurde angeheftet", - "Message_removed": "Nachricht entfernt", - "Message_starred": "Nachricht favorisiert", - "Message_unstarred": "Nachricht nicht mehr favorisiert", - "message": "Nachricht", - "messages": "Nachrichten", - "Message": "Nachricht", - "Messages": "Mitteilungen", - "Message_Reported": "Nachricht gemeldet", - "Microphone_Permission_Message": "Rocket.Chat benötigt Zugriff auf das Mikrofon, damit du eine Audionachricht senden kannst.", - "Microphone_Permission": "Mikrofonberechtigung", - "Mute": "Diesem Benutzer das Chatten verbieten", - "muted": "stummgeschaltet", - "My_servers": "Meine Server", - "N_people_reacted": "{{n}} Leute haben reagiert", - "N_users": "{{n}} Benutzer", - "N_channels": "{{n}} Kanäle", - "name": "Name", - "Name": "Name", - "Navigation_history": "Navigations-Verlauf", - "Never": "Niemals", - "New_Message": "Neue Nachricht", - "New_Password": "Neues Kennwort", - "New_Server": "Neuer Server", - "Next": "Nächster", - "No_files": "Keine Dateien", - "No_limit": "Kein Limit", - "No_mentioned_messages": "Keine Nachrichten mit Erwähnungen", - "No_pinned_messages": "Keine angehefteten Nachrichten", - "No_results_found": "Keine Ergebnisse gefunden", - "No_starred_messages": "Keine markierten Nachrichten", - "No_thread_messages": "Keine Threadnachrichten", - "No_label_provided": "Kein(e) {{label}} gesetzt.", - "No_Message": "Keine Nachricht", - "No_messages_yet": "Noch keine Nachrichten", - "No_Reactions": "Keine Reaktionen", - "No_Read_Receipts": "Keine Lesebestätigungen", - "Not_logged": "Nicht protokolliert", - "Not_RC_Server": "Dies ist kein Rocket.Chat-Server.\n{{contact}}", - "Nothing": "Nichts", - "Nothing_to_save": "Nichts zu speichern!", - "Notify_active_in_this_room": "Aktive Benutzer in diesem Raum benachrichtigen", - "Notify_all_in_this_room": "Benachrichtige alle in diesem Raum", - "Notifications": "Benachrichtigungen", - "Notification_Duration": "Benachrichtigungsdauer", - "Notification_Preferences": "Benachrichtigungseinstellungen", - "No_available_agents_to_transfer": "Keine Agenten für den Transfer verfügbar", - "Offline": "Offline", - "Oops": "Hoppla!", - "Omnichannel": "Omnichannel", - "Open_Livechats": "Offene Chats", - "Omnichannel_enable_alert": "Du bist in Omnichannel nicht verfügbar. Möchtest du erreichbar sein?", - "Onboarding_description": "Ein Arbeitsbereich ist der Ort für die Zusammenarbeit deines Teams oder Organisation. Bitte den Admin des Arbeitsbereichs um eine Adresse, um ihm beizutreten, oder erstelle einen Arbeitsbereich für dein Team.", - "Onboarding_join_workspace": "Tritt einem Arbeitsbereich bei", - "Onboarding_subtitle": "Mehr als Team-Zusammenarbeit", - "Onboarding_title": "Willkommen bei Rocket.Chat", - "Onboarding_join_open_description": "Tritt unserem Arbeitsbereich bei um mit dem Rocket.Chat-Team oder der Gemeinschaft zu chatten.", - "Onboarding_agree_terms": "Durch fortfahren stimmst du Rocket.Chats Bedingungen zu", - "Onboarding_less_options": "Weniger Optionen", - "Onboarding_more_options": "Mehr Optionen", - "Online": "Online", - "Only_authorized_users_can_write_new_messages": "Nur autorisierte Benutzer können neue Nachrichten schreiben", - "Open_emoji_selector": "Öffne die Emoji-Auswahl", - "Open_Source_Communication": "Open-Source-Kommunikation", - "Open_your_authentication_app_and_enter_the_code": "Öffne deine Authentifizierungsanwendung und gib den Code ein.", - "OR": "ODER", - "OS": "OS", - "Overwrites_the_server_configuration_and_use_room_config": "Übergeht die Servereinstellungen und nutzt Einstellung für den Raum", - "Password": "Passwort", - "Parent_channel_or_group": "Übergeordneter Kanal oder Gruppe", - "Permalink_copied_to_clipboard": "Permalink in die Zwischenablage kopiert!", - "Phone": "Telefon", - "Pin": "Anheften", - "Pinned_Messages": "Angeheftete Nachrichten", - "pinned": "angeheftet", - "Pinned": "Angeheftet", - "Please_add_a_comment": "Bitte Kommentar hinzufügen", - "Please_enter_your_password": "Gib bitte dein Passwort ein", - "Please_wait": "Bitte warten.", - "Preferences": "Einstellungen", - "Preferences_saved": "Einstellungen gespeichert!", - "Privacy_Policy": " Datenschutzbestimmungen", - "Private_Channel": "Privater Kanal", - "Private": "Privat", - "Processing": "Bearbeite …", - "Profile_saved_successfully": "Profil erfolgreich gespeichert!", - "Profile": "Profil", - "Public_Channel": "Öffentlicher Kanal", - "Public": "Öffentlich", - "Push_Notifications": "Push-Benachrichtigungen", - "Push_Notifications_Alert_Info": "Diese Benachrichtigungen werden dir zugestellt, wenn die App nicht geöffnet ist.", - "Quote": "Zitat", - "Reactions_are_disabled": "Reaktionen sind deaktiviert", - "Reactions_are_enabled": "Reaktionen sind aktiviert", - "Reactions": "Reaktionen", - "Read": "Gelesen", - "Read_External_Permission_Message": "Rocket.Chat benötigt Zugriff auf deine Fotos, Medien und Dateien auf deinem Gerät", - "Read_External_Permission": "Lese-Zugriff auf Medien", - "Read_Only_Channel": "Nur-Lese-Kanal", - "Read_Only": "Schreibgeschützt", - "Read_Receipt": "Lesebestätigung", - "Receive_Group_Mentions": "Erhalte Gruppen-Benachrichtigungen", - "Receive_Group_Mentions_Info": "Empfange @all und @here Erwähnungen", - "Register": "Registrieren", - "Repeat_Password": "Wiederhole das Passwort", - "Replied_on": "Antwortete am:", - "replies": "Antworten", - "reply": "Antworten", - "Reply": "Antworten", - "Report": "Melden", - "Receive_Notification": "Erhalte Benachrichtigungen", - "Receive_notifications_from": "Erhalte Benachrichtigungen von {{name}}", - "Resend": "Erneut senden", - "Reset_password": "Passwort zurücksetzen", - "resetting_password": "Passwort zurücksetzen", - "RESET": "ZURÜCKSETZEN", - "Return": "Zurück", - "Review_app_title": "Gefällt dir diese App?", - "Review_app_desc": "Gib uns 5 Sterne im {{store}}", - "Review_app_yes": "Sicher!", - "Review_app_no": "Nein", - "Review_app_later": "Vielleicht später", - "Review_app_unable_store": "Kann {{store}} nicht öffnen", - "Review_this_app": "App bewerten", - "Remove": "Entfernen", - "remove": "entfernen", - "Roles": "Rollen", - "Room_actions": "Raumaktionen", - "Room_changed_announcement": "Raumansage geändert in: {{announcement}} von {{userBy}}", - "Room_changed_avatar": "Raum-Avatar durch Nutzer {{userBy}} gändert", - "Room_changed_description": "Raumbeschreibung geändert in: {{description}} von {{userBy}}", - "Room_changed_privacy": "Raumtyp geändert in: {{type}} von {{userBy}}", - "Room_changed_topic": "Raumthema geändert in: {{topic}} von {{userBy}}", - "Room_Files": "Raumdateien", - "Room_Info_Edit": "Rauminfo bearbeiten", - "Room_Info": "Rauminfo", - "Room_Members": "Raum-Mitglieder", - "Room_name_changed": "Raumname geändert in {{name}} von {{userBy}}", - "SAVE": "SPEICHERN", - "Save_Changes": "Änderungen speichern", - "Save": "speichern", - "Saved": "gespeichert", - "saving_preferences": "Präferenzen speichern", - "saving_profile": "Profil speichern", - "saving_settings": "Einstellungen speichern", - "saved_to_gallery": "Gespeichert in der Galerie", - "Save_Your_E2E_Password": "Speichere dein Ende-zu-Ende-Passwort", - "Save_Your_Encryption_Password": "Speichere dein Verschlüsselungs-Passwort", - "Save_Your_Encryption_Password_warning": "Dieses Passwort wird nirgends gespeichert, stelle daher sicher, dass du es an einem sicheren Ort aufbewahrst.", - "Save_Your_Encryption_Password_info": "Hinweis: Wenn du dieses Passwort verlierst, gibt es keine Möglichkeit es wieder herzustellen und du wirst nicht mehr auf deine Nachrichten zugreifen können.", - "Search_Messages": "Nachrichten suchen", - "Search": "Suche", - "Search_by": "Suche nach", - "Search_global_users": "Suche nach globalen Benutzern", - "Search_global_users_description": "Wenn aktiviert, kannst du nach Benutzern von anderen Unternehmen oder Servern suchen.", - "Seconds": "{{second}} Sekunden", - "Security_and_privacy": "Sicherheit und Datenschutz", - "Select_Avatar": "Wähle einen Avatar aus", - "Select_Server": "Server auswählen", - "Select_Users": "Benutzer auswählen", - "Select_a_Channel": "Kanal auswählen", - "Select_a_Department": "Abteilung auswählen", - "Select_an_option": "Option auswählen", - "Select_a_User": "Benutzer auswählen", - "Send": "Senden", - "Send_audio_message": "Audio-Nachricht senden", - "Send_crash_report": "Absturzbericht senden", - "Send_message": "Nachricht senden", - "Send_me_the_code_again": "Den Code neu versenden", - "Send_to": "Senden an …", - "Sending_to": "Sende an", - "Sent_an_attachment": "Sende einen Anhang", - "Server": "Server", - "Servers": "Server", - "Server_version": "Server version: {{version}}", - "Set_username_subtitle": "Der Benutzername wird verwendet, damit andere Personen dich in Nachrichten erwähnen können", - "Set_custom_status": "Individuellen Status setzen", - "Set_status": "Status setzen", - "Status_saved_successfully": "Status erfolgreich gesetzt!", - "Settings": "Einstellungen", - "Settings_succesfully_changed": "Einstellungen erfolgreich geändert!", - "Share": "Teilen", - "Share_Link": "Link teilen", - "Share_this_app": "App teilen", - "Show_more": "Mehr anzeigen …", - "Show_Unread_Counter": "Zähler anzeigen", - "Show_Unread_Counter_Info": "Anzahl der ungelesenen Nachrichten anzeigen", - "Sign_in_your_server": "Melde dich bei deinem Server an", - "Sign_Up": "Anmelden", - "Some_field_is_invalid_or_empty": "Ein Feld ist ungültig oder leer", - "Sorting_by": "Sortierung nach {{key}}", - "Sound": "Ton", - "Star_room": "Favorisierter Raum", - "Star": "Favoriten", - "Starred_Messages": "Favorisierte Nachrichten", - "starred": "favorisiert", - "Starred": "Favorisiert", - "Start_of_conversation": "Beginn des Gesprächs", - "Start_a_Discussion": "Beginne eine Diskussion", - "Started_discussion": "Hat eine Diskussion gestartet:", - "Started_call": "Anruf gestartet von {{userBy}}", - "Submit": "einreichen", - "Table": "Tabelle", - "Tags": "Tags", - "Take_a_photo": "Foto aufnehmen", - "Take_a_video": "Video aufnehmen", - "Take_it": "Annehmen!", - "tap_to_change_status": "Tippen um den Status zu ändern", - "Tap_to_view_servers_list": "Tippen, um die Serverliste anzuzeigen", - "Terms_of_Service": " Nutzungsbedingungen", - "Theme": "Erscheinungsbild", - "The_user_wont_be_able_to_type_in_roomName": "Dem Nutzer wird es nicht möglich sein in {{roomName}} zu schreiben", - "The_user_will_be_able_to_type_in_roomName": "Der Nutzer wird in {{roomName}} schreiben können", - "There_was_an_error_while_action": "Während {{action}} ist ein Fehler aufgetreten!", - "This_room_is_blocked": "Dieser Raum ist gesperrt", - "This_room_is_read_only": "Dieser Raum kann nur gelesen werden", - "Thread": "Thread", - "Threads": "Threads", - "Timezone": "Zeitzone", - "To": "An", - "topic": "Thema", - "Topic": "Thema", - "Translate": "Übersetzen", - "Try_again": "Versuche es nochmal", - "Two_Factor_Authentication": "Zwei-Faktor-Authentifizierung", - "Type_the_channel_name_here": "Gib hier den Kanalnamen ein", - "unarchive": "wiederherstellen", - "UNARCHIVE": "WIEDERHERSTELLEN", - "Unblock_user": "Benutzer entsperren", - "Unfavorite": "Nicht mehr favorisieren", - "Unfollowed_thread": "Thread nicht mehr folgen", - "Unmute": "Stummschaltung aufheben", - "unmuted": "Stummschaltung aufgehoben", - "Unpin": "Nachricht nicht mehr anheften", - "unread_messages": "ungelesene", - "Unread": "ungelesene", - "Unread_on_top": "Ungelesene oben", - "Unstar": "von Favoriten entfernen", - "Updating": "Aktualisierung …", - "Uploading": "Hochladen", - "Upload_file_question_mark": "Datei hochladen?", - "User": "Benutzer", - "Users": "Benutzer", - "User_added_by": "Benutzer {{userAdded}} hinzugefügt von {{userBy}}", - "User_Info": "Benutzerinfo", - "User_has_been_key": "Benutzer wurde {{key}}", - "User_is_no_longer_role_by_": "{{user}} ist nicht länger {{role}} von {{userBy}}", - "User_muted_by": "Benutzer {{userMuted}} von {{userBy}} stummgeschaltet", - "User_removed_by": "Benutzer {{userRemoved}} wurde von {{userBy}} entfernt", - "User_sent_an_attachment": "{{user}}: eine Datei gesendet", - "User_unmuted_by": "Benutzer {{userUnmuted}} nicht stummgeschaltet von {{userBy}}", - "User_was_set_role_by_": "{{user}} wurde von {{userBy}} {{role}} festgelegt.", - "Username_is_empty": "Der Benutzername ist leer", - "Username": "Benutzername", - "Username_or_email": "Benutzername oder E-Mail-Adresse", - "Uses_server_configuration": "Nutzt Servereinstellungen", - "Validating": "Validierung", - "Registration_Succeeded": "Registrierung erfolgreich!", - "Verify": "Überprüfen", - "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.", - "Verify_your_email_for_the_code_we_sent": "Prüfe deine Mails für den Code, den wir dir eben geschickt haben.", - "Video_call": "Videoanruf", - "View_Original": "Original anzeigen", - "Voice_call": "Sprachanruf", - "Waiting_for_network": "Warte auf das Netzwerk …", - "Websocket_disabled": "Websockets sind auf diesem Server nicht aktiviert.\n{{contact}}", - "Welcome": "Herzlich willkommen", - "What_are_you_doing_right_now": "Was machst du gerade?", - "Whats_your_2fa": "Wie lautet dein 2FA-Code?", - "Without_Servers": "Ohne Server", - "Workspaces": "Arbeitsbereiche", - "Would_you_like_to_return_the_inquiry": "Willst du zur Anfrage zurück?", - "Write_External_Permission_Message": "Rocket.Chat benötigt Zugriff auf deine Galerie um Bilder speichern zu können.", - "Write_External_Permission": "Galerie-Zugriff", - "Yes": "Ja", - "Yes_action_it": "Ja, {{action}}!", - "Yesterday": "Gestern", - "You_are_in_preview_mode": "Du befindest dich im Vorschaumodus", - "You_are_offline": "Du bist offline", - "You_can_search_using_RegExp_eg": "Du kannst mit RegExp suchen. z.B. `/ ^ text $ / i`", - "You_colon": "Du: ", - "you_were_mentioned": "Du wurdest erwähnt", - "You_were_removed_from_channel": "Du wurdest aus {{channel}} entfernt", - "you": "du", - "You": "Du", - "Logged_out_by_server": "Du bist vom Server abgemeldet worden. Bitte melde dich wieder an.", - "You_need_to_access_at_least_one_RocketChat_server_to_share_something": "Du benötigst Zugang zu mindestens einem Rocket.Chat-Server um etwas zu teilen.", - "You_need_to_verifiy_your_email_address_to_get_notications": "Du musst deine Email-Adresse bestätigen um Benachrichtigungen zu erhalten.", - "Your_certificate": "Dein Zertifikat", - "Your_invite_link_will_expire_after__usesLeft__uses": "Dein Einladungs-Link wird nach {{usesLeft}} Benutzungen ablaufen.", - "Your_invite_link_will_expire_on__date__or_after__usesLeft__uses": "Dein Einladungs-Link wird am {{date}} oder nach {{usesLeft}} Benutzungen ablaufen.", - "Your_invite_link_will_expire_on__date__": "Dein Einladungs-Link wird am {{date}} ablaufen.", - "Your_invite_link_will_never_expire": "Dein Einladungs-Link wird niemals ablaufen.", - "Your_workspace": "Dein Arbeitsbereich", - "Your_password_is": "Dein Passwort lautet", - "Version_no": "Version: {{version}}", - "You_will_not_be_able_to_recover_this_message": "Du kannst diese Nachricht nicht wiederherstellen!", - "You_will_unset_a_certificate_for_this_server": "Du entfernst ein Zertifikat für diesen Server", - "Change_Language": "Sprache ändern", - "Crash_report_disclaimer": "Wir verfolgen niemals den Inhalt deiner Chats. Der Crash-Report enthält nur für uns relevante Informationen um das Problem zu erkennen und zu beheben.", - "Type_message": "Nachricht schreiben", - "Room_search": "Raum-Suche", - "Room_selection": "Raum-Auswahl 1...9", - "Next_room": "Nächster Raum", - "Previous_room": "Voriger Raum", - "New_room": "Neuer Raum", - "Upload_room": "Zu einem Raum hochladen", - "Search_messages": "Nachrichten durchsuchen", - "Scroll_messages": "Nachrichten durchblättern", - "Reply_latest": "Auf die letzte Nachricht antworten", - "Reply_in_Thread": "Im Thread antworten", - "Server_selection": "Server-Auswahl", - "Server_selection_numbers": "Server-Auswahl 1...9", - "Add_server": "Server hinzufügen", - "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_remove_all_data_from_this_server": "Dies wird alle Daten von diesem Server löschen.", - "Mark_unread": "Als ungelesen markieren", - "Wait_activation_warning": "Bevor du dich anmelden kannst, muss dein Konto durch einen Administrator freigeschaltet werden.", - "Screen_lock": "Zugriffs-Sperre", - "Local_authentication_biometry_title": "Authentifizieren", - "Local_authentication_biometry_fallback": "Sicherheitscode benutzen", - "Local_authentication_unlock_option": "Entsperren mit Sicherheitscode", - "Local_authentication_change_passcode": "Ändere Sicherheitscode", - "Local_authentication_info": "Anmerkung: Wenn du den Sicherheitscode vergisst, musst du diese App löschen und neu installieren.", - "Local_authentication_facial_recognition": "Gesichtserkennung", - "Local_authentication_fingerprint": "Fingerabdruck", - "Local_authentication_unlock_with_label": "Entsperren mit {{label}}", - "Local_authentication_auto_lock_60": "Nach 1 Minute", - "Local_authentication_auto_lock_300": "Nach 5 Minuten", - "Local_authentication_auto_lock_900": "Nach 15 Minuten", - "Local_authentication_auto_lock_1800": "Nach 30 Minuten", - "Local_authentication_auto_lock_3600": "Nach 1 Stunde", - "Passcode_enter_title": "Gib deinen Sicherheitscode ein", - "Passcode_choose_title": "Setze deinen neuen Sicherheitscode", - "Passcode_choose_confirm_title": "Bestätige deinen neuen Sicherheitscode", - "Passcode_choose_error": "Sicherheitscodes stimmen nicht überein. Probiere es noch einmal.", - "Passcode_choose_force_set": "Sicherheitscode wird vom Admin verlangt", - "Passcode_app_locked_title": "App gesperrt", - "Passcode_app_locked_subtitle": "Versuche es in {{timeLeft}} Sekunden noch einmal.", - "After_seconds_set_by_admin": "Nach {{seconds}} Sekunden (durch den Admin gesetzt)", - "Dont_activate": "Jetzt nicht aktivieren", - "Queued_chats": "Chats in der Warteschlange", - "Queue_is_empty": "Warteschlange leer", - "Logout_from_other_logged_in_locations": "Auf anderen angemeldeten Geräte abmelden", - "You_will_be_logged_out_from_other_locations": "Du wirst auf anderen Geräten abgemeldet.", - "Logged_out_of_other_clients_successfully": "Erfolgreich von anderen Geräten abgemeldet.", - "Logout_failed": "Abmeldung fehlgeschlagen!", - "Log_analytics_events": "Analyse-Ereignisse loggen", - "E2E_encryption_change_password_title": "Verschlüsselungs-Passwort ändern", - "E2E_encryption_change_password_description": "Du kannst nun verschlüsselte private Gruppen und Direktnachrichten versenden. Du kannst außerdem deine bestehenden privaten Gruppen und Direktnachrichten auf Verschlüsselung umstellen. \nDies ist Ende-zu-Ende-Verschlüsselung, daher wird der Schlüssel um die Nachrichten zu ver-/entschlüsseln nicht auf dem Server gespeichert. Aus diesem Grund musst du dieses Passwort an einem sicheren Ort speichern, du wirst es auf anderen Geräten benötigen auf denen du E2E-Verschlüsselung nutzen möchtest.", - "E2E_encryption_change_password_error": "Fehler beim Ändern des E2E-Passworts!", - "E2E_encryption_change_password_success": "E2E-Passwort erfolgreich geändert!", - "E2E_encryption_change_password_message": "Stelle sicher, dass du es noch an einer anderen Stelle gesichert hasst.", - "E2E_encryption_change_password_confirmation": "Ja, ändern", - "E2E_encryption_reset_title": "E2E-Schlüssel zurücksetzen", - "E2E_encryption_reset_description": "Diese Option wird deinen aktuellen E2E-Schlüssel entfernen und dich abmelden. \nWenn du dich wieder anmeldest, wird Rocket.Chat einen neuen Schlüssel erzeugen und deinen Zugang zu allen Kanälen mit einer oder mehreren anwesenden Personen wieder herstellen. \nAufgrund der Funktionsweise von E2E-Verschlüsselung, kann Rocket.Chat nicht deinen Zugang zu Kanälen wieder herstellen, in denen keine andere Person anwesend ist.", - "E2E_encryption_reset_button": "E2E-Schlüssel zurücksetzen", - "E2E_encryption_reset_error": "Fehler beim Zurücksetzen des E2E-Schlüssels!", - "E2E_encryption_reset_message": "Du wirst abgemeldet.", - "E2E_encryption_reset_confirmation": "Ja, zurücksetzen", - "Following": "verfolgte", - "Threads_displaying_all": "zeige alle", - "Threads_displaying_following": "zeige gefolgte", - "Threads_displaying_unread": "Zeige ungelesene", - "No_threads": "Es gibt keine Threads", - "No_threads_following": "Du folgst keinen Threads", - "No_threads_unread": "Es gibt keine ungelesenen Threads", - "Messagebox_Send_to_channel": "an Kanal senden", - "Leader": "Leiter", - "Moderator": "Moderator", - "Owner": "Eigentümer", - "Remove_from_room": "Aus dem Raum entfernen", - "Ignore": "Ignorieren", - "Unignore": "Nicht mehr ignorieren", - "User_has_been_ignored": "Benutzer wurde stumm geschaltet", - "User_has_been_unignored": "Benutzer nicht mehr stumm geschaltet", - "User_has_been_removed_from_s": "Benutzer wurde aus {{s}} entfernt", - "User__username__is_now_a_leader_of__room_name_": "Benutzer {{username}} ist nun Diskussionsleiter von {{room_name}}", - "User__username__is_now_a_moderator_of__room_name_": "Benutzer {{username}} ist nun Moderator von {{room_name}}", - "User__username__is_now_a_owner_of__room_name_": "Benutzer {{username}} ist nun Eigentümer von {{room_name}}", - "User__username__removed_from__room_name__leaders": "Benutzer {{username}} als Diskussionsleiter von {{room_name}} entfernt", - "User__username__removed_from__room_name__moderators": "Benutzer {{username}} als Moderator von {{room_name}} entfernt", - "User__username__removed_from__room_name__owners": "Benutzer {{username}} als Eigentümer von {{room_name}} entfernt", - "The_user_will_be_removed_from_s": "Der Benutzer wird aus {{s}} entfernt", - "Yes_remove_user": "Ja, Benutzer entfernen!", - "Direct_message": "Direktnachricht", - "Message_Ignored": "Nachricht ignoriert. Antippen um sie zu zeigen.", - "Enter_workspace_URL": "Arbeitsbereich-URL", - "Workspace_URL_Example": "z.B. https://rocketchat.deine-firma.de", - "This_room_encryption_has_been_enabled_by__username_": "Die Verschlüsselung dieses Raums wurde von {{username}} aktiviert", - "This_room_encryption_has_been_disabled_by__username_": "Die Verschlüsselung dieses Raums wurde von {{username}} deaktiviert", - "Teams": "Teams", - "No_team_channels_found": "Keine Kanäle gefunden", - "Team_not_found": "Team nicht gefunden", - "Create_Team": "Team erstellen", - "Team_Name": "Team-Name", - "Private_Team": "Privates Team", - "Read_Only_Team": "Nur-Lesen-Team", - "Broadcast_Team": "Broadcast-Team", - "creating_team": "Team erstellen", - "team-name-already-exists": "Ein Team mit diesem Namen existiert bereits", - "Add_Channel_to_Team": "Kanal zum Team hinzufügen", - "Left_The_Team_Successfully": "Das Team erfolgreich verlassen", - "Create_New": "Neu erstellen", - "Add_Existing": "Vorhandenes hinzufügen", - "Add_Existing_Channel": "Vorhandenen Kanal hinzufügen", - "Remove_from_Team": "Aus Team entfernen", - "Auto-join": "Automatischer Beitritt", - "Remove_Team_Room_Warning": "Möchten du diesen Kanal aus dem Team entfernen? Der Kanal wird zurück in den Arbeitsbereich verschoben.", - "Confirmation": "Bestätigung", - "invalid-room": "Ungültiger Raum", - "You_are_leaving_the_team": "Du verlässt das Team '{{team}}'", - "Leave_Team": "Team verlassen", - "Select_Team": "Team auswählen", - "Select_Team_Channels": "Wähle die Kanäle des Teams aus, die du verlassen möchtest.", - "Cannot_leave": "Verlassen nicht möglich", - "Cannot_remove": "Kann nicht entfernt werden", - "Cannot_delete": "Kann nicht gelöscht werden", - "Last_owner_team_room": "Du bist der letzte Eigentümer des Kanals. Wenn du das Team verlässt, bleibt der Kanal innerhalb des Teams aber du verwaltest ihn von außen.", - "last-owner-can-not-be-removed": "Letzter Besitzer kann nicht entfernt werden", - "Remove_User_Teams": "Wähle die Kanäle aus, aus denen der Benutzer entfernt werden soll.", - "Delete_Team": "Team löschen", - "Select_channels_to_delete": "Dies kann nicht rückgängig gemacht werden. Wenn du ein Team löschst, werden alle Chat-Inhalte und und Einstellungen gelöscht.\n\nWähle die Kanäle, die du löschen möchtest. Diejenigen, die du behalten möchtest, werden in deinem Arbeitsbereich verfügbar sein. Beachte, das öffentliche Kanäle öffentlich bleiben und für jeden sichtbar sein werden.", - "You_are_deleting_the_team": "Du löschst dieses Team", - "Removing_user_from_this_team": "Du entfernst {{user}} aus diesem Team", - "Remove_User_Team_Channels": "Wähle die Kanäle aus, aus denen der Benutzer entfernt werden soll.", - "Remove_Member": "Mitglied entfernen", - "leaving_team": "Team verlassen", - "removing_team": "Aus dem Team entfernen", - "moving_channel_to_team": "Kanal zu Team verschieben", - "deleting_team": "Team löschen", - "member-does-not-exist": "Mitglied existiert nicht", - "Convert": "Konvertieren", - "Convert_to_Team": "Zu Team konvertieren", - "Convert_to_Team_Warning": "Dies kann nicht rückgängig gemacht werden. Sobald du einen Kanal in ein Team umgewandelt hast, kannst du ihn nicht mehr zurück in einen Kanal verwandeln.", - "Move_to_Team": "Zu Team hinzufügen", - "Move_Channel_Paragraph": "Das Verschieben eines Kanals innerhalb eines Teams bedeutet, dass dieser Kanal im Kontext des Teams hinzugefügt wird, jedoch haben alle Mitglieder des Kanals, die nicht Mitglied des jeweiligen Teams sind, weiterhin Zugriff auf diesen Kanal, werden aber nicht als Teammitglieder hinzugefügt \n\nDie gesamte Verwaltung des Kanals wird weiterhin von den Eigentümern dieses Kanals vorgenommen.\n\nTeammitglieder und sogar Teameigentümer, die nicht Mitglied dieses Kanals sind, können keinen Zugriff auf den Inhalt des Kanals haben \n\nBitte beachte, dass der Besitzer des Teams in der Lage ist, Mitglieder aus dem Kanal zu entfernen.", - "Move_to_Team_Warning": "Nachdem du die vorherigen Anleitungen zu diesem Verhalten gelesen hast, möchtest du diesen Kanal immer noch in das ausgewählte Team verschieben?", - "Load_More": "Mehr laden", - "Load_Newer": "Neuere laden", - "Load_Older": "Ältere laden", - "Left_The_Room_Successfully": "Raum erfolgreich verlassen", - "Deleted_The_Team_Successfully": "Team erfolgreich gelöscht", - "Deleted_The_Room_Successfully": "Raum erfolgreich gelöscht", - "Convert_to_Channel": "In Kanal umwandeln", - "Converting_Team_To_Channel": "Team in Kanal umwandeln", - "Select_Team_Channels_To_Delete": "Wähle die Kanäle des Teams aus, die du löschen möchtest. Die Kanäle, die du nicht auswählst, werden in den Arbeitsbereich verschoben \n\nBeachte, dass öffentliche Kanäle öffentlich und für alle sichtbar sind.", - "You_are_converting_the_team": "Du wandelst dieses Team in einen Raum um" -} \ No newline at end of file + "1_person_reacted": "1 Person hat reagiert", + "1_user": "1 Benutzer", + "error-action-not-allowed": "{{action}} ist nicht erlaubt", + "error-application-not-found": "Anwendung nicht gefunden", + "error-archived-duplicate-name": "Es gibt bereits einen archivierten Kanal mit dem Namen {{room_name}}", + "error-avatar-invalid-url": "Ungültige Avatar-URL: {{url}}", + "error-avatar-url-handling": "Fehler beim Umgang mit der Avatar-Einstellung von einer URL ({{url}}) für {{username}}", + "error-cant-invite-for-direct-room": "Benutzer können nicht zu Räumen eingeladen werden", + "error-could-not-change-email": "E-Mail konnte nicht geändert werden", + "error-could-not-change-name": "Name konnte nicht geändert werden", + "error-could-not-change-username": "Benutzername konnte nicht geändert werden", + "error-could-not-change-status": "Status konnte nicht geändert werden", + "error-delete-protected-role": "Eine geschützte Rolle kann nicht gelöscht werden", + "error-department-not-found": "Abteilung nicht gefunden", + "error-direct-message-file-upload-not-allowed": "Dateifreigabe in direkten Nachrichten nicht zulässig", + "error-duplicate-channel-name": "Ein Kanal mit dem Namen {{room_name}} ist bereits vorhanden", + "error-email-domain-blacklisted": "Die E-Mail-Domain wird auf die schwarze Liste gesetzt", + "error-email-send-failed": "Fehler beim Versuch, eine E-Mail zu senden: {{message}}", + "error-save-image": "Fehler beim Speichern des Bildes", + "error-save-video": "Fehler beim Speichern des Videos", + "error-field-unavailable": "{{field}} wird bereits verwendet :(", + "error-file-too-large": "Datei ist zu groß", + "error-importer-not-defined": "Der Import wurde nicht korrekt definiert, es fehlt die Importklasse.", + "error-input-is-not-a-valid-field": "{{input}} ist kein gültiges {{field}}", + "error-invalid-actionlink": "Ungültiger Aktionslink", + "error-invalid-arguments": "Ungültige Argumente", + "error-invalid-asset": "Ungültiges Asset", + "error-invalid-channel": "Ungültiger Kanal", + "error-invalid-channel-start-with-chars": "Ungültiger Kanal. Beginne mit @ oder #", + "error-invalid-custom-field": "Ungültiges benutzerdefiniertes Feld", + "error-invalid-custom-field-name": "Ungültiger benutzerdefinierter Feldname. Verwende nur Buchstaben, Zahlen, Bindestriche und Unterstriche.", + "error-invalid-date": "Ungültiges Datum angegeben", + "error-invalid-description": "Ungültige Beschreibung", + "error-invalid-domain": "Ungültige Domain", + "error-invalid-email": "Ungültige E-Mail {{email}}", + "error-invalid-email-address": "Ungültige E-Mail-Adresse", + "error-invalid-file-height": "Ungültige Dateihöhe", + "error-invalid-file-type": "Ungültiger Dateityp", + "error-invalid-file-width": "Ungültige Dateibreite", + "error-invalid-from-address": "Du hast eine ungültige FROM-Adresse mitgeteilt.", + "error-invalid-integration": "Ungültige Integration", + "error-invalid-message": "Ungültige Nachricht", + "error-invalid-method": "Ungültige Methode", + "error-invalid-name": "Ungültiger Name", + "error-invalid-password": "Ungültiges Passwort", + "error-invalid-redirectUri": "Ungültige Weiterleitung", + "error-invalid-role": "Ungültige Rolle", + "error-invalid-room": "Ungültiger Raum", + "error-invalid-room-name": "{{room_name}} ist kein gültiger Raumname", + "error-invalid-room-type": "{{type}} ist kein gültiger Raumtyp.", + "error-invalid-settings": "Ungültige Einstellungen angegeben", + "error-invalid-subscription": "Ungültiges Abonnement", + "error-invalid-token": "Ungültiges Token", + "error-invalid-triggerWords": "Ungültige TriggerWords", + "error-invalid-urls": "Ungültige URLs", + "error-invalid-user": "Ungültiger Benutzer", + "error-invalid-username": "Ungültiger Benutzername", + "error-invalid-webhook-response": "Die Webhook-URL antwortete mit einem anderen Status als 200", + "error-message-deleting-blocked": "Das Löschen von Nachrichten ist gesperrt", + "error-message-editing-blocked": "Die Bearbeitung von Nachrichten ist gesperrt", + "error-message-size-exceeded": "Die Nachrichtengröße überschreitet Message_MaxAllowedSize", + "error-missing-unsubscribe-link": "Du musst den Link [abbestellen] angeben.", + "error-no-owner-channel": "Dieser Raum gehört dir nicht", + "error-no-tokens-for-this-user": "Für diesen Benutzer gibt es keine Token", + "error-not-allowed": "Nicht erlaubt", + "error-not-authorized": "Nicht berechtigt", + "error-push-disabled": "Push ist deaktiviert", + "error-remove-last-owner": "Dies ist der letzte Besitzer. Bitte lege einen neuen Besitzer fest, bevor du diesen entfernst.", + "error-role-in-use": "Rolle kann nicht gelöscht werden, da sie gerade verwendet wird", + "error-role-name-required": "Der Rollenname ist erforderlich", + "error-the-field-is-required": "Das Feld {{field}} ist erforderlich.", + "error-too-many-requests": "Fehler, zu viele Anfragen. Du musst {{seconds}} Sekunden warten, bevor du es erneut versuchst.", + "error-user-is-not-activated": "Benutzer ist nicht aktiviert", + "error-user-has-no-roles": "Benutzer hat keine Rollen", + "error-user-limit-exceeded": "Die Anzahl der Benutzer, die du zu #channel_name einladen möchtest, überschreitet die vom Administrator festgelegte Grenze", + "error-user-not-in-room": "Benutzer ist nicht in diesem Raum", + "error-user-registration-custom-field": "error-user-registration-custom-field", + "error-user-registration-disabled": "Die Benutzerregistrierung ist deaktiviert", + "error-user-registration-secret": "Die Benutzerregistrierung ist nur über eine geheime URL möglich", + "error-you-are-last-owner": "Du bist der letzte Besitzer. Bitte setze einen neuen Besitzer, bevor du den Raum verlässt.", + "error-status-not-allowed": "Unsichtbar-Status ist deaktiviert", + "Actions": "Aktionen", + "activity": "Aktivität", + "Activity": "Aktivität", + "Add_Reaction": "Reaktion hinzufügen", + "Add_Server": "Server hinzufügen", + "Add_users": "Benutzer hinzufügen", + "Admin_Panel": "Admin-Panel", + "Agent": "Agent", + "Alert": "Benachrichtigung", + "alert": "Benachrichtigung", + "alerts": "Benachrichtigungen", + "All_users_in_the_channel_can_write_new_messages": "Alle Benutzer im Kanal können neue Nachrichten schreiben", + "All_users_in_the_team_can_write_new_messages": "Alle Mitglieder eines Teams können neue Nachrichten schreiben", + "A_meaningful_name_for_the_discussion_room": "Ein aussagekräftiger Name für den Diskussionsraum", + "All": "alle", + "All_Messages": "Alle Nachrichten", + "Allow_Reactions": "Reaktionen zulassen", + "Alphabetical": "Alphabetisch", + "and_more": "und mehr", + "and": "und", + "announcement": "Ankündigung", + "Announcement": "Ankündigung", + "Apply_Your_Certificate": "Wende dein Zertifikat an", + "ARCHIVE": "ARCHIV", + "archive": "Archiv", + "are_typing": "tippen", + "Are_you_sure_question_mark": "Bist du sicher?", + "Are_you_sure_you_want_to_leave_the_room": "Möchtest du den Raum wirklich verlassen {{room}}?", + "Audio": "Audio", + "Authenticating": "Authentifizierung", + "Automatic": "Automatisch", + "Auto_Translate": "Automatische Übersetzung", + "Avatar_changed_successfully": "Avatar erfolgreich geändert!", + "Avatar_Url": "Avatar-URL", + "Away": "Abwesend", + "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": "Broadcast-Kanal", + "Busy": "Beschäftigt", + "By_proceeding_you_are_agreeing": "Indem du fortfährst, stimmst du zu unserem", + "Cancel_editing": "Bearbeitung abbrechen", + "Cancel_recording": "Aufnahme abbrechen", + "Cancel": "Abbrechen", + "changing_avatar": "Avatar wechseln", + "creating_channel": "Kanal erstellen", + "creating_invite": "Einladung erstellen", + "Channel_Name": "Kanal Name", + "Channels": "Kanäle", + "Chats": "Chats", + "Call_already_ended": "Anruf bereits beendet!", + "Clear_cookies_alert": "Möchtest du alle Cookies löschen?", + "Clear_cookies_desc": "Diese Aktion wird alle Login-Cookies löschen und erlaubt es dir, dich mit einem anderen Konto anzumelden.", + "Clear_cookies_yes": "Ja, Cookies löschen", + "Clear_cookies_no": "Nein, Cookies behalten", + "Click_to_join": "Klicken um beizutreten!", + "Close": "Schließen", + "Close_emoji_selector": "Schließe die Emoji-Auswahl", + "Closing_chat": "Chat schließen", + "Change_language_loading": "Ändere Sprache.", + "Chat_closed_by_agent": "Chat durch den Agenten geschlossen", + "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", + "Code_or_password_invalid": "Code oder Passwort sind falsch", + "Collaborative": "Kollaborativ", + "Confirm": "Bestätigen", + "Connect": "Verbinden", + "Connected": "Verbunden", + "connecting_server": "verbinde zum Server", + "Connecting": "Verbinden ...", + "Contact_us": "Kontaktiere uns", + "Contact_your_server_admin": "Kontaktiere deinen Server-Administrator.", + "Continue_with": "Weitermachen mit", + "Copied_to_clipboard": "In die Zwischenablage kopiert!", + "Copy": "Kopieren", + "Conversation": "Konversationen", + "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 dein Zertifikat?", + "Create_account": "Ein Konto erstellen", + "Create_Channel": "Kanal erstellen", + "Create_Direct_Messages": "Direkt-Nachricht erstellen", + "Create_Discussion": "Diskussion erstellen", + "Created_snippet": "ein Snippet erstellt", + "Create_a_new_workspace": "Erstelle einen neuen Arbeitsbereich", + "Create": "Erstellen", + "Custom_Status": "Eigener Status", + "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.", + "Department": "Abteilung", + "delete": "löschen", + "Delete": "Löschen", + "DELETE": "LÖSCHEN", + "move": "verschieben", + "deleting_room": "lösche Raum", + "description": "Beschreibung", + "Description": "Beschreibung", + "Desktop_Options": "Desktop-Einstellungen", + "Desktop_Notifications": "Desktop-Benachrichtigungen", + "Desktop_Alert_info": "Diese Benachrichtigungen werden auf dem Desktop angezeigt", + "Directory": "Verzeichnis", + "Direct_Messages": "Direktnachrichten", + "Disable_notifications": "Benachrichtigungen deaktiveren", + "Discussions": "Diskussionen", + "Discussion_Desc": "Hilft dir die Übersicht zu behalten! Durch das Erstellen einer Diskussion wird ein Unter-Kanal im ausgewählten Raum erzeugt und beide verknüpft.", + "Discussion_name": "Diskussions-Name", + "Done": "Erledigt", + "Dont_Have_An_Account": "Du hast noch kein Konto?", + "Do_you_have_an_account": "Du hast schon ein Konto?", + "Do_you_have_a_certificate": "Hast du ein Zertifikat?", + "Do_you_really_want_to_key_this_room_question_mark": "Möchtest du diesen Raum wirklich {{key}}?", + "E2E_Encryption": "E2E-Verschlüsselung", + "E2E_How_It_Works_info1": "Du kannst nun verschlüsselte private Gruppen und Direktnachrichten versenden. Du kannst außerdem deine bestehenden privaten Gruppen und Direktnachrichten auf Verschlüsselung umstellen.", + "E2E_How_It_Works_info2": "Dies ist *Ende-zu-Ende-Verschlüsselung*, daher wird der Schlüssel um die Nachrichten zu ver-/entschlüsseln nicht auf dem Server gespeichert. Aus diesem Grund musst du dieses Passwort an einem sicheren Ort speichern, so dass du später bei Bedarf darauf zugreifen kannst.", + "E2E_How_It_Works_info3": "Wenn du fortfährst, wird automatisch ein ein E2E-Passwort erzeugt.", + "E2E_How_It_Works_info4": "Du kannst außerdem jederzeit, in jedem Browser, in dem du das bestehende Passwort eingegeben hast, ein neues Passwort setzen.", + "edit": "bearbeiten", + "edited": "bearbeitet", + "Edit": "Bearbeiten", + "Edit_Status": "Status ändern", + "Edit_Invite": "Einladung bearbeiten", + "End_to_end_encrypted_room": "Ende-zu-Ende-verschlüsselter Raum", + "end_to_end_encryption": "Nicht mehr Ende-zu-Ende verschlüsseln", + "Email_Notification_Mode_All": "Jede Erwähnung/Direktnachricht", + "Email_Notification_Mode_Disabled": "Deaktiviert", + "Email_or_password_field_is_empty": "Das E-Mail- oder Passwortfeld ist leer", + "Email": "E-mail", + "email": "E-mail", + "Empty_title": "leerer Titel", + "Enable_Auto_Translate": "Automatische Übersetzung aktivieren", + "Enable_notifications": "Benachrichtigungen aktivieren", + "Encrypted": "Verschlüsselt", + "Encrypted_message": "Verschlüsselte Nachricht", + "Enter_Your_E2E_Password": "Gib dein Ende-zu-Ende-Passwort ein", + "Enter_Your_Encryption_Password_desc1": "Das erlaubt dir auf deine verschlüsselten privaten Gruppen und Direktnachrichten zuzugreifen.", + "Enter_Your_Encryption_Password_desc2": "Du musst das Passwort zur Ver-/Entschlüsselung an jeder Stelle eingeben, an dem du diesen Chat verwendest.", + "Encryption_error_title": "Dein Verschlüsselungs-Passwort scheint falsch zu sein", + "Encryption_error_desc": "Es war nicht möglich deinen Verschlüsselungs-Key zu importieren.", + "Everyone_can_access_this_channel": "Jeder kann auf diesen Kanal zugreifen", + "Everyone_can_access_this_team": "Jeder kann auf dieses Team zugreifen", + "Error_uploading": "Fehler beim Hochladen", + "Expiration_Days": "läuft ab (Tage)", + "Favorite": "Favorisieren", + "Favorites": "Favoriten", + "Files": "Dateien", + "File_description": "Dateibeschreibung", + "File_name": "Dateiname", + "Finish_recording": "Beende die Aufnahme", + "Following_thread": "Thread folgen", + "For_your_security_you_must_enter_your_current_password_to_continue": "Zu deiner Sicherheit musst du dein aktuelles Passwort eingeben, um fortzufahren", + "Forgot_password_If_this_email_is_registered": "Wenn diese E-Mail registriert ist, senden wir Anweisungen zum Zurücksetzen deines Passworts. Wenn du nicht in Kürze keine E-Mail erhältst, versuche es bitte erneut.", + "Forgot_password": "Passwort vergessen", + "Forgot_Password": "Passwort vergessen", + "Forward": "Weiterleiten", + "Forward_Chat": "Chat weiterleiten", + "Forward_to_department": "Weiterleiten an Abteilung", + "Forward_to_user": "Weiterleiten an Benutzer", + "Full_table": "Klicken um die ganze Tabelle anzuzeigen", + "Generate_New_Link": "Neuen Link erstellen", + "Group_by_favorites": "Nach Favoriten gruppieren", + "Group_by_type": "Gruppieren nach Typ", + "Hide": "Ausblenden", + "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", + "Hide_System_Messages": "Systemnachrichten verstecken", + "Hide_type_messages": "Verstecke \"{{type}}\"-Nachrichten", + "How_It_Works": "Wie es funktioniert", + "Message_HideType_uj": "Benutzer beigetreten", + "Message_HideType_ul": "Benutzer verlassen", + "Message_HideType_ru": "Benutzer entfernt", + "Message_HideType_au": "Benutzer hinzugefügt", + "Message_HideType_mute_unmute": "Benutzer stummgeschaltet / freigegeben", + "Message_HideType_r": "Raumname geändert", + "Message_HideType_ut": "Benutzer ist der Unterhaltung beigetreten", + "Message_HideType_wm": "Willkommen", + "Message_HideType_rm": "Nachricht entfernt", + "Message_HideType_subscription_role_added": "Rolle wurde gesetzt", + "Message_HideType_subscription_role_removed": "Rolle nicht länger definiert", + "Message_HideType_room_archived": "Raum archiviert", + "Message_HideType_room_unarchived": "Raum nicht mehr archiviert", + "I_Saved_My_E2E_Password": "Ich habe mein Ende-zu-Ende-Passwort gesichert", + "IP": "IP", + "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", + "Invite": "Einladen", + "is_a_valid_RocketChat_instance": "ist eine gültige Rocket.Chat-Instanz", + "is_not_a_valid_RocketChat_instance": "ist keine gültige Rocket.Chat-Instanz", + "is_typing": "schreibt", + "Invalid_or_expired_invite_token": "Ungültiger oder abgelaufener Einladungscode", + "Invalid_server_version": "Der Server, zu dem du dich verbinden möchtest, verwendet eine Version, die von der App nicht mehr unterstützt wird: {{currentVersion}}.\n\nWir benötigen Version {{minVersion}}.", + "Invite_Link": "Einladungs-Link", + "Invite_users": "Benutzer einladen", + "Join": "Beitreten", + "Join_Code": "Beitrittscode", + "Insert_Join_Code": "Beitrittscode eingeben", + "Join_our_open_workspace": "Tritt unserem offenen Arbeitsbereich bei", + "Join_your_workspace": "Tritt deinem Arbeitsbereich bei", + "Just_invited_people_can_access_this_channel": "Nur eingeladene Personen können auf diesen Kanal zugreifen", + "Just_invited_people_can_access_this_team": "Nur eingeladene Personen können auf das Team zugreifen", + "Language": "Sprache", + "last_message": "letzte Nachricht", + "Leave_channel": "Kanal verlassen", + "leaving_room": "Raum verlassen", + "Leave": "Raum verlassen", + "leave": "verlassen", + "Legal": "Rechtliches", + "Light": "Hell", + "License": "Lizenz", + "Livechat": "Live-Chat", + "Livechat_edit": "Live-Chat bearbeiten", + "Login": "Anmeldung", + "Login_error": "Deine Zugangsdaten wurden abgelehnt! Bitte versuche es erneut.", + "Login_with": "Einloggen mit", + "Logging_out": "Abmelden.", + "Logout": "Abmelden", + "Max_number_of_uses": "Maximale Anzahl der Benutzungen", + "Max_number_of_users_allowed_is_number": "Maximale Anzahl von erlaubten Benutzern ist {{maxUsers}}", + "members": "Mitglieder", + "Members": "Mitglieder", + "Mentioned_Messages": "Erwähnte Nachrichten", + "mentioned": "erwähnt", + "Mentions": "Erwähnungen", + "Message_accessibility": "Nachricht von {{user}} um {{time}}: {{message}}", + "Message_actions": "Nachrichtenaktionen", + "Message_pinned": "Eine Nachricht wurde angeheftet", + "Message_removed": "Nachricht entfernt", + "Message_starred": "Nachricht favorisiert", + "Message_unstarred": "Nachricht nicht mehr favorisiert", + "message": "Nachricht", + "messages": "Nachrichten", + "Message": "Nachricht", + "Messages": "Mitteilungen", + "Message_Reported": "Nachricht gemeldet", + "Microphone_Permission_Message": "Rocket.Chat benötigt Zugriff auf das Mikrofon, damit du eine Audionachricht senden kannst.", + "Microphone_Permission": "Mikrofonberechtigung", + "Mute": "Diesem Benutzer das Chatten verbieten", + "muted": "stummgeschaltet", + "My_servers": "Meine Server", + "N_people_reacted": "{{n}} Leute haben reagiert", + "N_users": "{{n}} Benutzer", + "N_channels": "{{n}} Kanäle", + "name": "Name", + "Name": "Name", + "Navigation_history": "Navigations-Verlauf", + "Never": "Niemals", + "New_Message": "Neue Nachricht", + "New_Password": "Neues Kennwort", + "New_Server": "Neuer Server", + "Next": "Nächster", + "No_files": "Keine Dateien", + "No_limit": "Kein Limit", + "No_mentioned_messages": "Keine Nachrichten mit Erwähnungen", + "No_pinned_messages": "Keine angehefteten Nachrichten", + "No_results_found": "Keine Ergebnisse gefunden", + "No_starred_messages": "Keine markierten Nachrichten", + "No_thread_messages": "Keine Threadnachrichten", + "No_label_provided": "Kein(e) {{label}} gesetzt.", + "No_Message": "Keine Nachricht", + "No_messages_yet": "Noch keine Nachrichten", + "No_Reactions": "Keine Reaktionen", + "No_Read_Receipts": "Keine Lesebestätigungen", + "Not_logged": "Nicht protokolliert", + "Not_RC_Server": "Dies ist kein Rocket.Chat-Server.\n{{contact}}", + "Nothing": "Nichts", + "Nothing_to_save": "Nichts zu speichern!", + "Notify_active_in_this_room": "Aktive Benutzer in diesem Raum benachrichtigen", + "Notify_all_in_this_room": "Benachrichtige alle in diesem Raum", + "Notifications": "Benachrichtigungen", + "Notification_Duration": "Benachrichtigungsdauer", + "Notification_Preferences": "Benachrichtigungseinstellungen", + "No_available_agents_to_transfer": "Keine Agenten für den Transfer verfügbar", + "Offline": "Offline", + "Oops": "Hoppla!", + "Omnichannel": "Omnichannel", + "Open_Livechats": "Offene Chats", + "Omnichannel_enable_alert": "Du bist in Omnichannel nicht verfügbar. Möchtest du erreichbar sein?", + "Onboarding_description": "Ein Arbeitsbereich ist der Ort für die Zusammenarbeit deines Teams oder Organisation. Bitte den Admin des Arbeitsbereichs um eine Adresse, um ihm beizutreten, oder erstelle einen Arbeitsbereich für dein Team.", + "Onboarding_join_workspace": "Tritt einem Arbeitsbereich bei", + "Onboarding_subtitle": "Mehr als Team-Zusammenarbeit", + "Onboarding_title": "Willkommen bei Rocket.Chat", + "Onboarding_join_open_description": "Tritt unserem Arbeitsbereich bei um mit dem Rocket.Chat-Team oder der Gemeinschaft zu chatten.", + "Onboarding_agree_terms": "Durch fortfahren stimmst du Rocket.Chats Bedingungen zu", + "Onboarding_less_options": "Weniger Optionen", + "Onboarding_more_options": "Mehr Optionen", + "Online": "Online", + "Only_authorized_users_can_write_new_messages": "Nur autorisierte Benutzer können neue Nachrichten schreiben", + "Open_emoji_selector": "Öffne die Emoji-Auswahl", + "Open_Source_Communication": "Open-Source-Kommunikation", + "Open_your_authentication_app_and_enter_the_code": "Öffne deine Authentifizierungsanwendung und gib den Code ein.", + "OR": "ODER", + "OS": "OS", + "Overwrites_the_server_configuration_and_use_room_config": "Übergeht die Servereinstellungen und nutzt Einstellung für den Raum", + "Password": "Passwort", + "Parent_channel_or_group": "Übergeordneter Kanal oder Gruppe", + "Permalink_copied_to_clipboard": "Permalink in die Zwischenablage kopiert!", + "Phone": "Telefon", + "Pin": "Anheften", + "Pinned_Messages": "Angeheftete Nachrichten", + "pinned": "angeheftet", + "Pinned": "Angeheftet", + "Please_add_a_comment": "Bitte Kommentar hinzufügen", + "Please_enter_your_password": "Gib bitte dein Passwort ein", + "Please_wait": "Bitte warten.", + "Preferences": "Einstellungen", + "Preferences_saved": "Einstellungen gespeichert!", + "Privacy_Policy": " Datenschutzbestimmungen", + "Private_Channel": "Privater Kanal", + "Private": "Privat", + "Processing": "Bearbeite …", + "Profile_saved_successfully": "Profil erfolgreich gespeichert!", + "Profile": "Profil", + "Public_Channel": "Öffentlicher Kanal", + "Public": "Öffentlich", + "Push_Notifications": "Push-Benachrichtigungen", + "Push_Notifications_Alert_Info": "Diese Benachrichtigungen werden dir zugestellt, wenn die App nicht geöffnet ist.", + "Quote": "Zitat", + "Reactions_are_disabled": "Reaktionen sind deaktiviert", + "Reactions_are_enabled": "Reaktionen sind aktiviert", + "Reactions": "Reaktionen", + "Read": "Gelesen", + "Read_External_Permission_Message": "Rocket.Chat benötigt Zugriff auf deine Fotos, Medien und Dateien auf deinem Gerät", + "Read_External_Permission": "Lese-Zugriff auf Medien", + "Read_Only_Channel": "Nur-Lese-Kanal", + "Read_Only": "Schreibgeschützt", + "Read_Receipt": "Lesebestätigung", + "Receive_Group_Mentions": "Erhalte Gruppen-Benachrichtigungen", + "Receive_Group_Mentions_Info": "Empfange @all und @here Erwähnungen", + "Register": "Registrieren", + "Repeat_Password": "Wiederhole das Passwort", + "Replied_on": "Antwortete am:", + "replies": "Antworten", + "reply": "Antworten", + "Reply": "Antworten", + "Report": "Melden", + "Receive_Notification": "Erhalte Benachrichtigungen", + "Receive_notifications_from": "Erhalte Benachrichtigungen von {{name}}", + "Resend": "Erneut senden", + "Reset_password": "Passwort zurücksetzen", + "resetting_password": "Passwort zurücksetzen", + "RESET": "ZURÜCKSETZEN", + "Return": "Zurück", + "Review_app_title": "Gefällt dir diese App?", + "Review_app_desc": "Gib uns 5 Sterne im {{store}}", + "Review_app_yes": "Sicher!", + "Review_app_no": "Nein", + "Review_app_later": "Vielleicht später", + "Review_app_unable_store": "Kann {{store}} nicht öffnen", + "Review_this_app": "App bewerten", + "Remove": "Entfernen", + "remove": "entfernen", + "Roles": "Rollen", + "Room_actions": "Raumaktionen", + "Room_changed_announcement": "Raumansage geändert in: {{announcement}} von {{userBy}}", + "Room_changed_avatar": "Raum-Avatar durch Nutzer {{userBy}} gändert", + "Room_changed_description": "Raumbeschreibung geändert in: {{description}} von {{userBy}}", + "Room_changed_privacy": "Raumtyp geändert in: {{type}} von {{userBy}}", + "Room_changed_topic": "Raumthema geändert in: {{topic}} von {{userBy}}", + "Room_Files": "Raumdateien", + "Room_Info_Edit": "Rauminfo bearbeiten", + "Room_Info": "Rauminfo", + "Room_Members": "Raum-Mitglieder", + "Room_name_changed": "Raumname geändert in {{name}} von {{userBy}}", + "SAVE": "SPEICHERN", + "Save_Changes": "Änderungen speichern", + "Save": "speichern", + "Saved": "gespeichert", + "saving_preferences": "Präferenzen speichern", + "saving_profile": "Profil speichern", + "saving_settings": "Einstellungen speichern", + "saved_to_gallery": "Gespeichert in der Galerie", + "Save_Your_E2E_Password": "Speichere dein Ende-zu-Ende-Passwort", + "Save_Your_Encryption_Password": "Speichere dein Verschlüsselungs-Passwort", + "Save_Your_Encryption_Password_warning": "Dieses Passwort wird nirgends gespeichert, stelle daher sicher, dass du es an einem sicheren Ort aufbewahrst.", + "Save_Your_Encryption_Password_info": "Hinweis: Wenn du dieses Passwort verlierst, gibt es keine Möglichkeit es wieder herzustellen und du wirst nicht mehr auf deine Nachrichten zugreifen können.", + "Search_Messages": "Nachrichten suchen", + "Search": "Suche", + "Search_by": "Suche nach", + "Search_global_users": "Suche nach globalen Benutzern", + "Search_global_users_description": "Wenn aktiviert, kannst du nach Benutzern von anderen Unternehmen oder Servern suchen.", + "Seconds": "{{second}} Sekunden", + "Security_and_privacy": "Sicherheit und Datenschutz", + "Select_Avatar": "Wähle einen Avatar aus", + "Select_Server": "Server auswählen", + "Select_Users": "Benutzer auswählen", + "Select_a_Channel": "Kanal auswählen", + "Select_a_Department": "Abteilung auswählen", + "Select_an_option": "Option auswählen", + "Select_a_User": "Benutzer auswählen", + "Send": "Senden", + "Send_audio_message": "Audio-Nachricht senden", + "Send_crash_report": "Absturzbericht senden", + "Send_message": "Nachricht senden", + "Send_me_the_code_again": "Den Code neu versenden", + "Send_to": "Senden an …", + "Sending_to": "Sende an", + "Sent_an_attachment": "Sende einen Anhang", + "Server": "Server", + "Servers": "Server", + "Server_version": "Server version: {{version}}", + "Set_username_subtitle": "Der Benutzername wird verwendet, damit andere Personen dich in Nachrichten erwähnen können", + "Set_custom_status": "Individuellen Status setzen", + "Set_status": "Status setzen", + "Status_saved_successfully": "Status erfolgreich gesetzt!", + "Settings": "Einstellungen", + "Settings_succesfully_changed": "Einstellungen erfolgreich geändert!", + "Share": "Teilen", + "Share_Link": "Link teilen", + "Share_this_app": "App teilen", + "Show_more": "Mehr anzeigen …", + "Show_Unread_Counter": "Zähler anzeigen", + "Show_Unread_Counter_Info": "Anzahl der ungelesenen Nachrichten anzeigen", + "Sign_in_your_server": "Melde dich bei deinem Server an", + "Sign_Up": "Anmelden", + "Some_field_is_invalid_or_empty": "Ein Feld ist ungültig oder leer", + "Sorting_by": "Sortierung nach {{key}}", + "Sound": "Ton", + "Star_room": "Favorisierter Raum", + "Star": "Favoriten", + "Starred_Messages": "Favorisierte Nachrichten", + "starred": "favorisiert", + "Starred": "Favorisiert", + "Start_of_conversation": "Beginn des Gesprächs", + "Start_a_Discussion": "Beginne eine Diskussion", + "Started_discussion": "Hat eine Diskussion gestartet:", + "Started_call": "Anruf gestartet von {{userBy}}", + "Submit": "einreichen", + "Table": "Tabelle", + "Tags": "Tags", + "Take_a_photo": "Foto aufnehmen", + "Take_a_video": "Video aufnehmen", + "Take_it": "Annehmen!", + "tap_to_change_status": "Tippen um den Status zu ändern", + "Tap_to_view_servers_list": "Tippen, um die Serverliste anzuzeigen", + "Terms_of_Service": " Nutzungsbedingungen", + "Theme": "Erscheinungsbild", + "The_user_wont_be_able_to_type_in_roomName": "Dem Nutzer wird es nicht möglich sein in {{roomName}} zu schreiben", + "The_user_will_be_able_to_type_in_roomName": "Der Nutzer wird in {{roomName}} schreiben können", + "There_was_an_error_while_action": "Während {{action}} ist ein Fehler aufgetreten!", + "This_room_is_blocked": "Dieser Raum ist gesperrt", + "This_room_is_read_only": "Dieser Raum kann nur gelesen werden", + "Thread": "Thread", + "Threads": "Threads", + "Timezone": "Zeitzone", + "To": "An", + "topic": "Thema", + "Topic": "Thema", + "Translate": "Übersetzen", + "Try_again": "Versuche es nochmal", + "Two_Factor_Authentication": "Zwei-Faktor-Authentifizierung", + "Type_the_channel_name_here": "Gib hier den Kanalnamen ein", + "unarchive": "wiederherstellen", + "UNARCHIVE": "WIEDERHERSTELLEN", + "Unblock_user": "Benutzer entsperren", + "Unfavorite": "Nicht mehr favorisieren", + "Unfollowed_thread": "Thread nicht mehr folgen", + "Unmute": "Stummschaltung aufheben", + "unmuted": "Stummschaltung aufgehoben", + "Unpin": "Nachricht nicht mehr anheften", + "unread_messages": "ungelesene", + "Unread": "ungelesene", + "Unread_on_top": "Ungelesene oben", + "Unstar": "von Favoriten entfernen", + "Updating": "Aktualisierung …", + "Uploading": "Hochladen", + "Upload_file_question_mark": "Datei hochladen?", + "User": "Benutzer", + "Users": "Benutzer", + "User_added_by": "Benutzer {{userAdded}} hinzugefügt von {{userBy}}", + "User_Info": "Benutzerinfo", + "User_has_been_key": "Benutzer wurde {{key}}", + "User_is_no_longer_role_by_": "{{user}} ist nicht länger {{role}} von {{userBy}}", + "User_muted_by": "Benutzer {{userMuted}} von {{userBy}} stummgeschaltet", + "User_removed_by": "Benutzer {{userRemoved}} wurde von {{userBy}} entfernt", + "User_sent_an_attachment": "{{user}}: eine Datei gesendet", + "User_unmuted_by": "Benutzer {{userUnmuted}} nicht stummgeschaltet von {{userBy}}", + "User_was_set_role_by_": "{{user}} wurde von {{userBy}} {{role}} festgelegt.", + "Username_is_empty": "Der Benutzername ist leer", + "Username": "Benutzername", + "Username_or_email": "Benutzername oder E-Mail-Adresse", + "Uses_server_configuration": "Nutzt Servereinstellungen", + "Validating": "Validierung", + "Registration_Succeeded": "Registrierung erfolgreich!", + "Verify": "Überprüfen", + "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.", + "Verify_your_email_for_the_code_we_sent": "Prüfe deine Mails für den Code, den wir dir eben geschickt haben.", + "Video_call": "Videoanruf", + "View_Original": "Original anzeigen", + "Voice_call": "Sprachanruf", + "Waiting_for_network": "Warte auf das Netzwerk …", + "Websocket_disabled": "Websockets sind auf diesem Server nicht aktiviert.\n{{contact}}", + "Welcome": "Herzlich willkommen", + "What_are_you_doing_right_now": "Was machst du gerade?", + "Whats_your_2fa": "Wie lautet dein 2FA-Code?", + "Without_Servers": "Ohne Server", + "Workspaces": "Arbeitsbereiche", + "Would_you_like_to_return_the_inquiry": "Willst du zur Anfrage zurück?", + "Write_External_Permission_Message": "Rocket.Chat benötigt Zugriff auf deine Galerie um Bilder speichern zu können.", + "Write_External_Permission": "Galerie-Zugriff", + "Yes": "Ja", + "Yes_action_it": "Ja, {{action}}!", + "Yesterday": "Gestern", + "You_are_in_preview_mode": "Du befindest dich im Vorschaumodus", + "You_are_offline": "Du bist offline", + "You_can_search_using_RegExp_eg": "Du kannst mit RegExp suchen. z.B. `/ ^ text $ / i`", + "You_colon": "Du: ", + "you_were_mentioned": "Du wurdest erwähnt", + "You_were_removed_from_channel": "Du wurdest aus {{channel}} entfernt", + "you": "du", + "You": "Du", + "Logged_out_by_server": "Du bist vom Server abgemeldet worden. Bitte melde dich wieder an.", + "You_need_to_access_at_least_one_RocketChat_server_to_share_something": "Du benötigst Zugang zu mindestens einem Rocket.Chat-Server um etwas zu teilen.", + "You_need_to_verifiy_your_email_address_to_get_notications": "Du musst deine Email-Adresse bestätigen um Benachrichtigungen zu erhalten.", + "Your_certificate": "Dein Zertifikat", + "Your_invite_link_will_expire_after__usesLeft__uses": "Dein Einladungs-Link wird nach {{usesLeft}} Benutzungen ablaufen.", + "Your_invite_link_will_expire_on__date__or_after__usesLeft__uses": "Dein Einladungs-Link wird am {{date}} oder nach {{usesLeft}} Benutzungen ablaufen.", + "Your_invite_link_will_expire_on__date__": "Dein Einladungs-Link wird am {{date}} ablaufen.", + "Your_invite_link_will_never_expire": "Dein Einladungs-Link wird niemals ablaufen.", + "Your_workspace": "Dein Arbeitsbereich", + "Your_password_is": "Dein Passwort lautet", + "Version_no": "Version: {{version}}", + "You_will_not_be_able_to_recover_this_message": "Du kannst diese Nachricht nicht wiederherstellen!", + "You_will_unset_a_certificate_for_this_server": "Du entfernst ein Zertifikat für diesen Server", + "Change_Language": "Sprache ändern", + "Crash_report_disclaimer": "Wir verfolgen niemals den Inhalt deiner Chats. Der Crash-Report enthält nur für uns relevante Informationen um das Problem zu erkennen und zu beheben.", + "Type_message": "Nachricht schreiben", + "Room_search": "Raum-Suche", + "Room_selection": "Raum-Auswahl 1...9", + "Next_room": "Nächster Raum", + "Previous_room": "Voriger Raum", + "New_room": "Neuer Raum", + "Upload_room": "Zu einem Raum hochladen", + "Search_messages": "Nachrichten durchsuchen", + "Scroll_messages": "Nachrichten durchblättern", + "Reply_latest": "Auf die letzte Nachricht antworten", + "Reply_in_Thread": "Im Thread antworten", + "Server_selection": "Server-Auswahl", + "Server_selection_numbers": "Server-Auswahl 1...9", + "Add_server": "Server hinzufügen", + "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_remove_all_data_from_this_server": "Dies wird alle Daten von diesem Server löschen.", + "Mark_unread": "Als ungelesen markieren", + "Wait_activation_warning": "Bevor du dich anmelden kannst, muss dein Konto durch einen Administrator freigeschaltet werden.", + "Screen_lock": "Zugriffs-Sperre", + "Local_authentication_biometry_title": "Authentifizieren", + "Local_authentication_biometry_fallback": "Sicherheitscode benutzen", + "Local_authentication_unlock_option": "Entsperren mit Sicherheitscode", + "Local_authentication_change_passcode": "Ändere Sicherheitscode", + "Local_authentication_info": "Anmerkung: Wenn du den Sicherheitscode vergisst, musst du diese App löschen und neu installieren.", + "Local_authentication_facial_recognition": "Gesichtserkennung", + "Local_authentication_fingerprint": "Fingerabdruck", + "Local_authentication_unlock_with_label": "Entsperren mit {{label}}", + "Local_authentication_auto_lock_60": "Nach 1 Minute", + "Local_authentication_auto_lock_300": "Nach 5 Minuten", + "Local_authentication_auto_lock_900": "Nach 15 Minuten", + "Local_authentication_auto_lock_1800": "Nach 30 Minuten", + "Local_authentication_auto_lock_3600": "Nach 1 Stunde", + "Passcode_enter_title": "Gib deinen Sicherheitscode ein", + "Passcode_choose_title": "Setze deinen neuen Sicherheitscode", + "Passcode_choose_confirm_title": "Bestätige deinen neuen Sicherheitscode", + "Passcode_choose_error": "Sicherheitscodes stimmen nicht überein. Probiere es noch einmal.", + "Passcode_choose_force_set": "Sicherheitscode wird vom Admin verlangt", + "Passcode_app_locked_title": "App gesperrt", + "Passcode_app_locked_subtitle": "Versuche es in {{timeLeft}} Sekunden noch einmal.", + "After_seconds_set_by_admin": "Nach {{seconds}} Sekunden (durch den Admin gesetzt)", + "Dont_activate": "Jetzt nicht aktivieren", + "Queued_chats": "Chats in der Warteschlange", + "Queue_is_empty": "Warteschlange leer", + "Logout_from_other_logged_in_locations": "Auf anderen angemeldeten Geräte abmelden", + "You_will_be_logged_out_from_other_locations": "Du wirst auf anderen Geräten abgemeldet.", + "Logged_out_of_other_clients_successfully": "Erfolgreich von anderen Geräten abgemeldet.", + "Logout_failed": "Abmeldung fehlgeschlagen!", + "Log_analytics_events": "Analyse-Ereignisse loggen", + "E2E_encryption_change_password_title": "Verschlüsselungs-Passwort ändern", + "E2E_encryption_change_password_description": "Du kannst nun verschlüsselte private Gruppen und Direktnachrichten versenden. Du kannst außerdem deine bestehenden privaten Gruppen und Direktnachrichten auf Verschlüsselung umstellen. \nDies ist Ende-zu-Ende-Verschlüsselung, daher wird der Schlüssel um die Nachrichten zu ver-/entschlüsseln nicht auf dem Server gespeichert. Aus diesem Grund musst du dieses Passwort an einem sicheren Ort speichern, du wirst es auf anderen Geräten benötigen auf denen du E2E-Verschlüsselung nutzen möchtest.", + "E2E_encryption_change_password_error": "Fehler beim Ändern des E2E-Passworts!", + "E2E_encryption_change_password_success": "E2E-Passwort erfolgreich geändert!", + "E2E_encryption_change_password_message": "Stelle sicher, dass du es noch an einer anderen Stelle gesichert hasst.", + "E2E_encryption_change_password_confirmation": "Ja, ändern", + "E2E_encryption_reset_title": "E2E-Schlüssel zurücksetzen", + "E2E_encryption_reset_description": "Diese Option wird deinen aktuellen E2E-Schlüssel entfernen und dich abmelden. \nWenn du dich wieder anmeldest, wird Rocket.Chat einen neuen Schlüssel erzeugen und deinen Zugang zu allen Kanälen mit einer oder mehreren anwesenden Personen wieder herstellen. \nAufgrund der Funktionsweise von E2E-Verschlüsselung, kann Rocket.Chat nicht deinen Zugang zu Kanälen wieder herstellen, in denen keine andere Person anwesend ist.", + "E2E_encryption_reset_button": "E2E-Schlüssel zurücksetzen", + "E2E_encryption_reset_error": "Fehler beim Zurücksetzen des E2E-Schlüssels!", + "E2E_encryption_reset_message": "Du wirst abgemeldet.", + "E2E_encryption_reset_confirmation": "Ja, zurücksetzen", + "Following": "verfolgte", + "Threads_displaying_all": "zeige alle", + "Threads_displaying_following": "zeige gefolgte", + "Threads_displaying_unread": "Zeige ungelesene", + "No_threads": "Es gibt keine Threads", + "No_threads_following": "Du folgst keinen Threads", + "No_threads_unread": "Es gibt keine ungelesenen Threads", + "Messagebox_Send_to_channel": "an Kanal senden", + "Leader": "Leiter", + "Moderator": "Moderator", + "Owner": "Eigentümer", + "Remove_from_room": "Aus dem Raum entfernen", + "Ignore": "Ignorieren", + "Unignore": "Nicht mehr ignorieren", + "User_has_been_ignored": "Benutzer wurde stumm geschaltet", + "User_has_been_unignored": "Benutzer nicht mehr stumm geschaltet", + "User_has_been_removed_from_s": "Benutzer wurde aus {{s}} entfernt", + "User__username__is_now_a_leader_of__room_name_": "Benutzer {{username}} ist nun Diskussionsleiter von {{room_name}}", + "User__username__is_now_a_moderator_of__room_name_": "Benutzer {{username}} ist nun Moderator von {{room_name}}", + "User__username__is_now_a_owner_of__room_name_": "Benutzer {{username}} ist nun Eigentümer von {{room_name}}", + "User__username__removed_from__room_name__leaders": "Benutzer {{username}} als Diskussionsleiter von {{room_name}} entfernt", + "User__username__removed_from__room_name__moderators": "Benutzer {{username}} als Moderator von {{room_name}} entfernt", + "User__username__removed_from__room_name__owners": "Benutzer {{username}} als Eigentümer von {{room_name}} entfernt", + "The_user_will_be_removed_from_s": "Der Benutzer wird aus {{s}} entfernt", + "Yes_remove_user": "Ja, Benutzer entfernen!", + "Direct_message": "Direktnachricht", + "Message_Ignored": "Nachricht ignoriert. Antippen um sie zu zeigen.", + "Enter_workspace_URL": "Arbeitsbereich-URL", + "Workspace_URL_Example": "z.B. https://rocketchat.deine-firma.de", + "This_room_encryption_has_been_enabled_by__username_": "Die Verschlüsselung dieses Raums wurde von {{username}} aktiviert", + "This_room_encryption_has_been_disabled_by__username_": "Die Verschlüsselung dieses Raums wurde von {{username}} deaktiviert", + "Teams": "Teams", + "No_team_channels_found": "Keine Kanäle gefunden", + "Team_not_found": "Team nicht gefunden", + "Create_Team": "Team erstellen", + "Team_Name": "Team-Name", + "Private_Team": "Privates Team", + "Read_Only_Team": "Nur-Lesen-Team", + "Broadcast_Team": "Broadcast-Team", + "creating_team": "Team erstellen", + "team-name-already-exists": "Ein Team mit diesem Namen existiert bereits", + "Add_Channel_to_Team": "Kanal zum Team hinzufügen", + "Left_The_Team_Successfully": "Das Team erfolgreich verlassen", + "Create_New": "Neu erstellen", + "Add_Existing": "Vorhandenes hinzufügen", + "Add_Existing_Channel": "Vorhandenen Kanal hinzufügen", + "Remove_from_Team": "Aus Team entfernen", + "Auto-join": "Automatischer Beitritt", + "Remove_Team_Room_Warning": "Möchten du diesen Kanal aus dem Team entfernen? Der Kanal wird zurück in den Arbeitsbereich verschoben.", + "Confirmation": "Bestätigung", + "invalid-room": "Ungültiger Raum", + "You_are_leaving_the_team": "Du verlässt das Team '{{team}}'", + "Leave_Team": "Team verlassen", + "Select_Team": "Team auswählen", + "Select_Team_Channels": "Wähle die Kanäle des Teams aus, die du verlassen möchtest.", + "Cannot_leave": "Verlassen nicht möglich", + "Cannot_remove": "Kann nicht entfernt werden", + "Cannot_delete": "Kann nicht gelöscht werden", + "Last_owner_team_room": "Du bist der letzte Eigentümer des Kanals. Wenn du das Team verlässt, bleibt der Kanal innerhalb des Teams aber du verwaltest ihn von außen.", + "last-owner-can-not-be-removed": "Letzter Besitzer kann nicht entfernt werden", + "Remove_User_Teams": "Wähle die Kanäle aus, aus denen der Benutzer entfernt werden soll.", + "Delete_Team": "Team löschen", + "Select_channels_to_delete": "Dies kann nicht rückgängig gemacht werden. Wenn du ein Team löschst, werden alle Chat-Inhalte und und Einstellungen gelöscht.\n\nWähle die Kanäle, die du löschen möchtest. Diejenigen, die du behalten möchtest, werden in deinem Arbeitsbereich verfügbar sein. Beachte, das öffentliche Kanäle öffentlich bleiben und für jeden sichtbar sein werden.", + "You_are_deleting_the_team": "Du löschst dieses Team", + "Removing_user_from_this_team": "Du entfernst {{user}} aus diesem Team", + "Remove_User_Team_Channels": "Wähle die Kanäle aus, aus denen der Benutzer entfernt werden soll.", + "Remove_Member": "Mitglied entfernen", + "leaving_team": "Team verlassen", + "removing_team": "Aus dem Team entfernen", + "moving_channel_to_team": "Kanal zu Team verschieben", + "deleting_team": "Team löschen", + "member-does-not-exist": "Mitglied existiert nicht", + "Convert": "Konvertieren", + "Convert_to_Team": "Zu Team konvertieren", + "Convert_to_Team_Warning": "Dies kann nicht rückgängig gemacht werden. Sobald du einen Kanal in ein Team umgewandelt hast, kannst du ihn nicht mehr zurück in einen Kanal verwandeln.", + "Move_to_Team": "Zu Team hinzufügen", + "Move_Channel_Paragraph": "Das Verschieben eines Kanals innerhalb eines Teams bedeutet, dass dieser Kanal im Kontext des Teams hinzugefügt wird, jedoch haben alle Mitglieder des Kanals, die nicht Mitglied des jeweiligen Teams sind, weiterhin Zugriff auf diesen Kanal, werden aber nicht als Teammitglieder hinzugefügt \n\nDie gesamte Verwaltung des Kanals wird weiterhin von den Eigentümern dieses Kanals vorgenommen.\n\nTeammitglieder und sogar Teameigentümer, die nicht Mitglied dieses Kanals sind, können keinen Zugriff auf den Inhalt des Kanals haben \n\nBitte beachte, dass der Besitzer des Teams in der Lage ist, Mitglieder aus dem Kanal zu entfernen.", + "Move_to_Team_Warning": "Nachdem du die vorherigen Anleitungen zu diesem Verhalten gelesen hast, möchtest du diesen Kanal immer noch in das ausgewählte Team verschieben?", + "Load_More": "Mehr laden", + "Load_Newer": "Neuere laden", + "Load_Older": "Ältere laden", + "room-name-already-exists": "Raum-Name existiert bereits", + "error-team-creation": "Fehler bei der Erstellung des Teams", + "unauthorized": "Nicht erlaubt", + "Left_The_Room_Successfully": "Raum erfolgreich verlassen", + "Deleted_The_Team_Successfully": "Team erfolgreich gelöscht", + "Deleted_The_Room_Successfully": "Raum erfolgreich gelöscht", + "Convert_to_Channel": "In Kanal umwandeln", + "Converting_Team_To_Channel": "Team in Kanal umwandeln", + "Select_Team_Channels_To_Delete": "Wähle die Kanäle des Teams aus, die du löschen möchtest. Die Kanäle, die du nicht auswählst, werden in den Arbeitsbereich verschoben \n\nBeachte, dass öffentliche Kanäle öffentlich und für alle sichtbar sind.", + "You_are_converting_the_team": "Du wandelst dieses Team in einen Raum um", + "creating_discussion": "erzeuge Diskussion" +} diff --git a/app/i18n/locales/en.json b/app/i18n/locales/en.json index 114478bb6..8d409c1cc 100644 --- a/app/i18n/locales/en.json +++ b/app/i18n/locales/en.json @@ -1,776 +1,785 @@ { - "1_person_reacted": "1 person reacted", - "1_user": "1 user", - "error-action-not-allowed": "{{action}} is not allowed", - "error-application-not-found": "Application not found", - "error-archived-duplicate-name": "There's an archived channel with name {{room_name}}", - "error-avatar-invalid-url": "Invalid avatar URL: {{url}}", - "error-avatar-url-handling": "Error while handling avatar setting from a URL ({{url}}) for {{username}}", - "error-cant-invite-for-direct-room": "Can't invite user to direct rooms", - "error-could-not-change-email": "Could not change email", - "error-could-not-change-name": "Could not change name", - "error-could-not-change-username": "Could not change username", - "error-could-not-change-status": "Could not change status", - "error-delete-protected-role": "Cannot delete a protected role", - "error-department-not-found": "Department not found", - "error-direct-message-file-upload-not-allowed": "File sharing not allowed in direct messages", - "error-duplicate-channel-name": "A channel with name {{room_name}} exists", - "error-email-domain-blacklisted": "The email domain is blacklisted", - "error-email-send-failed": "Error trying to send email: {{message}}", - "error-save-image": "Error while saving image", - "error-save-video": "Error while saving video", - "error-field-unavailable": "{{field}} is already in use :(", - "error-file-too-large": "File is too large", - "error-importer-not-defined": "The importer was not defined correctly, it is missing the Import class.", - "error-input-is-not-a-valid-field": "{{input}} is not a valid {{field}}", - "error-invalid-actionlink": "Invalid action link", - "error-invalid-arguments": "Invalid arguments", - "error-invalid-asset": "Invalid asset", - "error-invalid-channel": "Invalid channel.", - "error-invalid-channel-start-with-chars": "Invalid channel. Start with @ or #", - "error-invalid-custom-field": "Invalid custom field", - "error-invalid-custom-field-name": "Invalid custom field name. Use only letters, numbers, hyphens and underscores.", - "error-invalid-date": "Invalid date provided.", - "error-invalid-description": "Invalid description", - "error-invalid-domain": "Invalid domain", - "error-invalid-email": "Invalid email {{email}}", - "error-invalid-email-address": "Invalid email address", - "error-invalid-file-height": "Invalid file height", - "error-invalid-file-type": "Invalid file type", - "error-invalid-file-width": "Invalid file width", - "error-invalid-from-address": "You informed an invalid FROM address.", - "error-invalid-integration": "Invalid integration", - "error-invalid-message": "Invalid message", - "error-invalid-method": "Invalid method", - "error-invalid-name": "Invalid name", - "error-invalid-password": "Invalid password", - "error-invalid-redirectUri": "Invalid redirectUri", - "error-invalid-role": "Invalid role", - "error-invalid-room": "Invalid room", - "error-invalid-room-name": "{{room_name}} is not a valid room name", - "error-invalid-room-type": "{{type}} is not a valid room type.", - "error-invalid-settings": "Invalid settings provided", - "error-invalid-subscription": "Invalid subscription", - "error-invalid-token": "Invalid token", - "error-invalid-triggerWords": "Invalid triggerWords", - "error-invalid-urls": "Invalid URLs", - "error-invalid-user": "Invalid user", - "error-invalid-username": "Invalid username", - "error-invalid-webhook-response": "The webhook URL responded with a status other than 200", - "error-message-deleting-blocked": "Message deleting is blocked", - "error-message-editing-blocked": "Message editing is blocked", - "error-message-size-exceeded": "Message size exceeds Message_MaxAllowedSize", - "error-missing-unsubscribe-link": "You must provide the [unsubscribe] link.", - "error-no-owner-channel": "You don't own the channel", - "error-no-tokens-for-this-user": "There are no tokens for this user", - "error-not-allowed": "Not allowed", - "error-not-authorized": "Not authorized", - "error-push-disabled": "Push is disabled", - "error-remove-last-owner": "This is the last owner. Please set a new owner before removing this one.", - "error-role-in-use": "Cannot delete role because it's in use", - "error-role-name-required": "Role name is required", - "error-the-field-is-required": "The field {{field}} is required.", - "error-too-many-requests": "Error, too many requests. Please slow down. You must wait {{seconds}} seconds before trying again.", - "error-user-is-not-activated": "User is not activated", - "error-user-has-no-roles": "User has no roles", - "error-user-limit-exceeded": "The number of users you are trying to invite to #channel_name exceeds the limit set by the administrator", - "error-user-not-in-room": "User is not in this room", - "error-user-registration-custom-field": "error-user-registration-custom-field", - "error-user-registration-disabled": "User registration is disabled", - "error-user-registration-secret": "User registration is only allowed via Secret URL", - "error-you-are-last-owner": "You are the last owner. Please set new owner before leaving the room.", - "error-status-not-allowed": "Invisible status is disabled", - "Actions": "Actions", - "activity": "activity", - "Activity": "Activity", - "Add_Reaction": "Add Reaction", - "Add_Server": "Add Server", - "Add_users": "Add users", - "Admin_Panel": "Admin Panel", - "Agent": "Agent", - "Alert": "Alert", - "alert": "alert", - "alerts": "alerts", - "All_users_in_the_channel_can_write_new_messages": "All users in the channel can write new messages", - "All_users_in_the_team_can_write_new_messages": "All users in the team can write new messages", - "A_meaningful_name_for_the_discussion_room": "A meaningful name for the discussion room", - "All": "All", - "All_Messages": "All Messages", - "Allow_Reactions": "Allow Reactions", - "Alphabetical": "Alphabetical", - "and_more": "and more", - "and": "and", - "announcement": "announcement", - "Announcement": "Announcement", - "Apply_Your_Certificate": "Apply Your Certificate", - "ARCHIVE": "ARCHIVE", - "archive": "archive", - "are_typing": "are typing", - "Are_you_sure_question_mark": "Are you sure?", - "Are_you_sure_you_want_to_leave_the_room": "Are you sure you want to leave the room {{room}}?", - "Audio": "Audio", - "Authenticating": "Authenticating", - "Automatic": "Automatic", - "Auto_Translate": "Auto-Translate", - "Avatar_changed_successfully": "Avatar changed successfully!", - "Avatar_Url": "Avatar URL", - "Away": "Away", - "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", - "By_proceeding_you_are_agreeing": "By proceeding you are agreeing to our", - "Cancel_editing": "Cancel editing", - "Cancel_recording": "Cancel recording", - "Cancel": "Cancel", - "changing_avatar": "changing avatar", - "creating_channel": "creating channel", - "creating_invite": "creating invite", - "Channel_Name": "Channel Name", - "Channels": "Channels", - "Chats": "Chats", - "Call_already_ended": "Call already ended!", - "Clear_cookies_alert": "Do you want to clear all cookies?", - "Clear_cookies_desc": "This action will clear all login cookies, allowing you to login into other accounts.", - "Clear_cookies_yes": "Yes, clear cookies", - "Clear_cookies_no": "No, keep cookies", - "Click_to_join": "Click to Join!", - "Close": "Close", - "Close_emoji_selector": "Close emoji selector", - "Closing_chat": "Closing chat", - "Change_language_loading": "Changing language.", - "Chat_closed_by_agent": "Chat closed by agent", - "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", - "Code_or_password_invalid": "Code or password invalid", - "Collaborative": "Collaborative", - "Confirm": "Confirm", - "Connect": "Connect", - "Connected": "Connected", - "connecting_server": "connecting to server", - "Connecting": "Connecting...", - "Contact_us": "Contact us", - "Contact_your_server_admin": "Contact your server admin.", - "Continue_with": "Continue with", - "Copied_to_clipboard": "Copied to clipboard!", - "Copy": "Copy", - "Conversation": "Conversation", - "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", - "Create_Direct_Messages": "Create Direct Messages", - "Create_Discussion": "Create Discussion", - "Created_snippet": "created a snippet", - "Create_a_new_workspace": "Create a new workspace", - "Create": "Create", - "Custom_Status": "Custom Status", - "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.", - "Department": "Department", - "delete": "delete", - "Delete": "Delete", - "DELETE": "DELETE", - "move": "move", - "deleting_room": "deleting room", - "description": "description", - "Description": "Description", - "Desktop_Options": "Desktop Options", - "Desktop_Notifications": "Desktop Notifications", - "Desktop_Alert_info": "These notifications are delivered in desktop", - "Directory": "Directory", - "Direct_Messages": "Direct Messages", - "Disable_notifications": "Disable notifications", - "Discussions": "Discussions", - "Discussion_Desc": "Help keeping an overview about what's going on! By creating a discussion, a sub-channel of the one you selected is created and both are linked.", - "Discussion_name": "Discussion name", - "Done": "Done", - "Dont_Have_An_Account": "Don't you have an account?", - "Do_you_have_an_account": "Do you have an account?", - "Do_you_have_a_certificate": "Do you have a certificate?", - "Do_you_really_want_to_key_this_room_question_mark": "Do you really want to {{key}} this room?", - "E2E_Encryption": "E2E Encryption", - "E2E_How_It_Works_info1": "You can now create encrypted private groups and direct messages. You may also change existing private groups or DMs to encrypted.", - "E2E_How_It_Works_info2": "This is *end to end encryption* so the key to encode/decode your messages and they will not be saved on the server. For that reason *you need to store this password somewhere safe* which you can access later if you may need.", - "E2E_How_It_Works_info3": "If you proceed, it will be auto generated an E2E password.", - "E2E_How_It_Works_info4": "You can also setup a new password for your encryption key any time from any browser you have entered the existing E2E password.", - "edit": "edit", - "edited": "edited", - "Edit": "Edit", - "Edit_Status": "Edit Status", - "Edit_Invite": "Edit Invite", - "End_to_end_encrypted_room": "End to end encrypted room", - "end_to_end_encryption": "end to end encryption", - "Email_Notification_Mode_All": "Every Mention/DM", - "Email_Notification_Mode_Disabled": "Disabled", - "Email_or_password_field_is_empty": "Email or password field is empty", - "Email": "E-mail", - "email": "e-mail", - "Empty_title": "Empty title", - "Enable_Auto_Translate": "Enable Auto-Translate", - "Enable_notifications": "Enable notifications", - "Encrypted": "Encrypted", - "Encrypted_message": "Encrypted message", - "Enter_Your_E2E_Password": "Enter Your E2E Password", - "Enter_Your_Encryption_Password_desc1": "This will allow you to access your encrypted private groups and direct messages.", - "Enter_Your_Encryption_Password_desc2": "You need to enter the password to encode/decode messages every place you use the chat.", - "Encryption_error_title": "Your encryption password seems wrong", - "Encryption_error_desc": "It wasn't possible to decode your encryption key to be imported.", - "Everyone_can_access_this_channel": "Everyone can access this channel", - "Everyone_can_access_this_team": "Everyone can access this team", - "Error_uploading": "Error uploading", - "Expiration_Days": "Expiration (Days)", - "Favorite": "Favorite", - "Favorites": "Favorites", - "Files": "Files", - "File_description": "File description", - "File_name": "File name", - "Finish_recording": "Finish recording", - "Following_thread": "Following thread", - "For_your_security_you_must_enter_your_current_password_to_continue": "For your security, you must enter your current password to continue", - "Forgot_password_If_this_email_is_registered": "If this email is registered, we'll send instructions on how to reset your password. If you do not receive an email shortly, please come back and try again.", - "Forgot_password": "Forgot your password?", - "Forgot_Password": "Forgot Password", - "Forward": "Forward", - "Forward_Chat": "Forward Chat", - "Forward_to_department": "Forward to department", - "Forward_to_user": "Forward to user", - "Full_table": "Click to see full table", - "Generate_New_Link": "Generate New Link", - "Group_by_favorites": "Group favorites", - "Group_by_type": "Group by type", - "Hide": "Hide", - "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", - "How_It_Works": "How It Works", - "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", - "I_Saved_My_E2E_Password": "I Saved My E2E Password", - "IP": "IP", - "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", - "Invite": "Invite", - "is_a_valid_RocketChat_instance": "is a valid Rocket.Chat instance", - "is_not_a_valid_RocketChat_instance": "is not a valid Rocket.Chat instance", - "is_typing": "is typing", - "Invalid_or_expired_invite_token": "Invalid or expired invite token", - "Invalid_server_version": "The server you're trying to connect is using a version that's not supported by the app anymore: {{currentVersion}}.\n\nWe require version {{minVersion}}", - "Invite_Link": "Invite Link", - "Invite_users": "Invite users", - "Join": "Join", - "Join_Code": "Join Code", - "Insert_Join_Code": "Insert Join Code", - "Join_our_open_workspace": "Join our open workspace", - "Join_your_workspace": "Join your workspace", - "Just_invited_people_can_access_this_channel": "Just invited people can access this channel", - "Just_invited_people_can_access_this_team": "Just invited people can access this team", - "Language": "Language", - "last_message": "last message", - "Leave_channel": "Leave channel", - "leaving_room": "leaving room", - "Leave": "Leave", - "leave": "leave", - "Legal": "Legal", - "Light": "Light", - "License": "License", - "Livechat": "Livechat", - "Livechat_edit": "Livechat edit", - "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", - "Max_number_of_users_allowed_is_number": "Max number of users allowed is {{maxUsers}}", - "members": "members", - "Members": "Members", - "Mentioned_Messages": "Mentioned Messages", - "mentioned": "mentioned", - "Mentions": "Mentions", - "Message_accessibility": "Message from {{user}} at {{time}}: {{message}}", - "Message_actions": "Message actions", - "Message_pinned": "Message pinned", - "Message_removed": "Message removed", - "Message_starred": "Message starred", - "Message_unstarred": "Message unstarred", - "message": "message", - "messages": "messages", - "Message": "Message", - "Messages": "Messages", - "Message_Reported": "Message reported", - "Microphone_Permission_Message": "Rocket.Chat needs access to your microphone so you can send audio message.", - "Microphone_Permission": "Microphone Permission", - "Mute": "Mute", - "muted": "muted", - "My_servers": "My servers", - "N_people_reacted": "{{n}} people reacted", - "N_users": "{{n}} users", - "N_channels": "{{n}} channels", - "name": "name", - "Name": "Name", - "Navigation_history": "Navigation history", - "Never": "Never", - "New_Message": "New Message", - "New_Password": "New Password", - "New_Server": "New Server", - "Next": "Next", - "No_files": "No files", - "No_limit": "No limit", - "No_mentioned_messages": "No mentioned messages", - "No_pinned_messages": "No pinned messages", - "No_results_found": "No results found", - "No_starred_messages": "No starred messages", - "No_thread_messages": "No thread messages", - "No_label_provided": "No {{label}} provided.", - "No_Message": "No Message", - "No_messages_yet": "No messages yet", - "No_Reactions": "No Reactions", - "No_Read_Receipts": "No Read Receipts", - "Not_logged": "Not logged", - "Not_RC_Server": "This is not a Rocket.Chat server.\n{{contact}}", - "Nothing": "Nothing", - "Nothing_to_save": "Nothing to save!", - "Notify_active_in_this_room": "Notify active users in this room", - "Notify_all_in_this_room": "Notify all in this room", - "Notifications": "Notifications", - "Notification_Duration": "Notification Duration", - "Notification_Preferences": "Notification Preferences", - "No_available_agents_to_transfer": "No available agents to transfer", - "Offline": "Offline", - "Oops": "Oops!", - "Omnichannel": "Omnichannel", - "Open_Livechats": "Chats in Progress", - "Omnichannel_enable_alert": "You're not available on Omnichannel. Would you like to be available?", - "Onboarding_description": "A workspace is your team or organization’s space to collaborate. Ask the workspace admin for address to join or create one for your team.", - "Onboarding_join_workspace": "Join a workspace", - "Onboarding_subtitle": "Beyond Team Collaboration", - "Onboarding_title": "Welcome to Rocket.Chat", - "Onboarding_join_open_description": "Join our open workspace to chat with the Rocket.Chat team and community.", - "Onboarding_agree_terms": "By continuing, you agree to Rocket.Chat", - "Onboarding_less_options": "Less options", - "Onboarding_more_options": "More options", - "Online": "Online", - "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", - "Open_your_authentication_app_and_enter_the_code": "Open your authentication app and enter the code.", - "OR": "OR", - "OS": "OS", - "Overwrites_the_server_configuration_and_use_room_config": "Overwrites the server configuration and use room config", - "Password": "Password", - "Parent_channel_or_group": "Parent channel or group", - "Permalink_copied_to_clipboard": "Permalink copied to clipboard!", - "Phone": "Phone", - "Pin": "Pin", - "Pinned_Messages": "Pinned Messages", - "pinned": "pinned", - "Pinned": "Pinned", - "Please_add_a_comment": "Please add a comment", - "Please_enter_your_password": "Please enter your password", - "Please_wait": "Please wait.", - "Preferences": "Preferences", - "Preferences_saved": "Preferences saved!", - "Privacy_Policy": " Privacy Policy", - "Private_Channel": "Private Channel", - "Private": "Private", - "Processing": "Processing...", - "Profile_saved_successfully": "Profile saved successfully!", - "Profile": "Profile", - "Public_Channel": "Public Channel", - "Public": "Public", - "Push_Notifications": "Push Notifications", - "Push_Notifications_Alert_Info": "These notifications are delivered to you when the app is not open", - "Quote": "Quote", - "Reactions_are_disabled": "Reactions are disabled", - "Reactions_are_enabled": "Reactions are enabled", - "Reactions": "Reactions", - "Read": "Read", - "Read_External_Permission_Message": "Rocket.Chat needs to access photos, media, and files on your device", - "Read_External_Permission": "Read Media Permission", - "Read_Only_Channel": "Read Only Channel", - "Read_Only": "Read Only", - "Read_Receipt": "Read Receipt", - "Receive_Group_Mentions": "Receive Group Mentions", - "Receive_Group_Mentions_Info": "Receive @all and @here mentions", - "Register": "Register", - "Repeat_Password": "Repeat Password", - "Replied_on": "Replied on:", - "replies": "replies", - "reply": "reply", - "Reply": "Reply", - "Report": "Report", - "Receive_Notification": "Receive Notification", - "Receive_notifications_from": "Receive notifications from {{name}}", - "Resend": "Resend", - "Reset_password": "Reset password", - "resetting_password": "resetting password", - "RESET": "RESET", - "Return": "Return", - "Review_app_title": "Are you enjoying this app?", - "Review_app_desc": "Give us 5 stars on {{store}}", - "Review_app_yes": "Sure!", - "Review_app_no": "No", - "Review_app_later": "Maybe later", - "Review_app_unable_store": "Unable to open {{store}}", - "Review_this_app": "Review this app", - "Remove": "Remove", - "remove": "remove", - "Roles": "Roles", - "Room_actions": "Room actions", - "Room_changed_announcement": "Room announcement changed to: {{announcement}} by {{userBy}}", - "Room_changed_avatar": "Room avatar changed by {{userBy}}", - "Room_changed_description": "Room description changed to: {{description}} by {{userBy}}", - "Room_changed_privacy": "Room type changed to: {{type}} by {{userBy}}", - "Room_changed_topic": "Room topic changed to: {{topic}} by {{userBy}}", - "Room_Files": "Room Files", - "Room_Info_Edit": "Room Info Edit", - "Room_Info": "Room Info", - "Room_Members": "Room Members", - "Room_name_changed": "Room name changed to: {{name}} by {{userBy}}", - "SAVE": "SAVE", - "Save_Changes": "Save Changes", - "Save": "Save", - "Saved": "Saved", - "saving_preferences": "saving preferences", - "saving_profile": "saving profile", - "saving_settings": "saving settings", - "saved_to_gallery": "Saved to gallery", - "Save_Your_E2E_Password": "Save Your E2E Password", - "Save_Your_Encryption_Password": "Save Your Encryption Password", - "Save_Your_Encryption_Password_warning": "This password is not stored anywhere so save it carefully somewhere else.", - "Save_Your_Encryption_Password_info": "Notice that you lose your password, there is no way to recover it and you will lose access to your messages.", - "Search_Messages": "Search Messages", - "Search": "Search", - "Search_by": "Search by", - "Search_global_users": "Search for global users", - "Search_global_users_description": "If you turn-on, you can search for any user from others companies or servers.", - "Seconds": "{{second}} seconds", - "Security_and_privacy": "Security and privacy", - "Select_Avatar": "Select Avatar", - "Select_Server": "Select Server", - "Select_Users": "Select Users", - "Select_a_Channel": "Select a Channel", - "Select_a_Department": "Select a Department", - "Select_an_option": "Select an option", - "Select_a_User": "Select a User", - "Send": "Send", - "Send_audio_message": "Send audio message", - "Send_crash_report": "Send crash report", - "Send_message": "Send message", - "Send_me_the_code_again": "Send me the code again", - "Send_to": "Send to...", - "Sending_to": "Sending to", - "Sent_an_attachment": "Sent an attachment", - "Server": "Server", - "Servers": "Servers", - "Server_version": "Server version: {{version}}", - "Set_username_subtitle": "The username is used to allow others to mention you in messages", - "Set_custom_status": "Set custom status", - "Set_status": "Set status", - "Status_saved_successfully": "Status saved successfully!", - "Settings": "Settings", - "Settings_succesfully_changed": "Settings succesfully changed!", - "Share": "Share", - "Share_Link": "Share Link", - "Share_this_app": "Share this app", - "Show_more": "Show more..", - "Show_Unread_Counter": "Show Unread Counter", - "Show_Unread_Counter_Info": "Unread counter is displayed as a badge on the right of the channel, in the list", - "Sign_in_your_server": "Sign in your server", - "Sign_Up": "Sign Up", - "Some_field_is_invalid_or_empty": "Some field is invalid or empty", - "Sorting_by": "Sorting by {{key}}", - "Sound": "Sound", - "Star_room": "Star room", - "Star": "Star", - "Starred_Messages": "Starred Messages", - "starred": "starred", - "Starred": "Starred", - "Start_of_conversation": "Start of conversation", - "Start_a_Discussion": "Start a Discussion", - "Started_discussion": "Started a discussion:", - "Started_call": "Call started by {{userBy}}", - "Submit": "Submit", - "Table": "Table", - "Tags": "Tags", - "Take_a_photo": "Take a photo", - "Take_a_video": "Take a video", - "Take_it": "Take it!", - "tap_to_change_status": "tap to change status", - "Tap_to_view_servers_list": "Tap to view servers list", - "Terms_of_Service": " Terms of Service ", - "Theme": "Theme", - "The_user_wont_be_able_to_type_in_roomName": "The user won't be able to type in {{roomName}}", - "The_user_will_be_able_to_type_in_roomName": "The user will be able to type in {{roomName}}", - "There_was_an_error_while_action": "There was an error while {{action}}!", - "This_room_is_blocked": "This room is blocked", - "This_room_is_read_only": "This room is read only", - "Thread": "Thread", - "Threads": "Threads", - "Timezone": "Timezone", - "To": "To", - "topic": "topic", - "Topic": "Topic", - "Translate": "Translate", - "Try_again": "Try again", - "Two_Factor_Authentication": "Two-factor Authentication", - "Type_the_channel_name_here": "Type the channel name here", - "unarchive": "unarchive", - "UNARCHIVE": "UNARCHIVE", - "Unblock_user": "Unblock user", - "Unfavorite": "Unfavorite", - "Unfollowed_thread": "Unfollowed thread", - "Unmute": "Unmute", - "unmuted": "unmuted", - "Unpin": "Unpin", - "unread_messages": "unread", - "Unread": "Unread", - "Unread_on_top": "Unread on top", - "Unstar": "Unstar", - "Updating": "Updating...", - "Uploading": "Uploading", - "Upload_file_question_mark": "Upload file?", - "User": "User", - "Users": "Users", - "User_added_by": "User {{userAdded}} added by {{userBy}}", - "User_Info": "User Info", - "User_has_been_key": "User has been {{key}}", - "User_is_no_longer_role_by_": "{{user}} is no longer {{role}} by {{userBy}}", - "User_muted_by": "User {{userMuted}} muted by {{userBy}}", - "User_removed_by": "User {{userRemoved}} removed by {{userBy}}", - "User_sent_an_attachment": "{{user}} sent an attachment", - "User_unmuted_by": "User {{userUnmuted}} unmuted by {{userBy}}", - "User_was_set_role_by_": "{{user}} was set {{role}} by {{userBy}}", - "Username_is_empty": "Username is empty", - "Username": "Username", - "Username_or_email": "Username or email", - "Uses_server_configuration": "Uses server configuration", - "Validating": "Validating", - "Registration_Succeeded": "Registration Succeeded!", - "Verify": "Verify", - "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.", - "Verify_your_email_for_the_code_we_sent": "Verify your email for the code we sent", - "Video_call": "Video call", - "View_Original": "View Original", - "Voice_call": "Voice call", - "Waiting_for_network": "Waiting for network...", - "Websocket_disabled": "Websocket is disabled for this server.\n{{contact}}", - "Welcome": "Welcome", - "What_are_you_doing_right_now": "What are you doing right now?", - "Whats_your_2fa": "What's your 2FA code?", - "Without_Servers": "Without Servers", - "Workspaces": "Workspaces", - "Would_you_like_to_return_the_inquiry": "Would you like to return the inquiry?", - "Write_External_Permission_Message": "Rocket.Chat needs access to your gallery so you can save images.", - "Write_External_Permission": "Gallery Permission", - "Yes": "Yes", - "Yes_action_it": "Yes, {{action}} it!", - "Yesterday": "Yesterday", - "You_are_in_preview_mode": "You are in preview mode", - "You_are_offline": "You are offline", - "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.", - "You_need_to_access_at_least_one_RocketChat_server_to_share_something": "You need to access at least one Rocket.Chat server to share something.", - "You_need_to_verifiy_your_email_address_to_get_notications": "You need to verify your email address to get notifications", - "Your_certificate": "Your Certificate", - "Your_invite_link_will_expire_after__usesLeft__uses": "Your invite link will expire after {{usesLeft}} uses.", - "Your_invite_link_will_expire_on__date__or_after__usesLeft__uses": "Your invite link will expire on {{date}} or after {{usesLeft}} uses.", - "Your_invite_link_will_expire_on__date__": "Your invite link will expire on {{date}}.", - "Your_invite_link_will_never_expire": "Your invite link will never expire.", - "Your_workspace": "Your workspace", - "Your_password_is": "Your password is", - "Version_no": "Version: {{version}}", - "You_will_not_be_able_to_recover_this_message": "You will not be able to recover this message!", - "You_will_unset_a_certificate_for_this_server": "You will unset a certificate for this server", - "Change_Language": "Change Language", - "Crash_report_disclaimer": "We never track the content of your chats. The crash report and analytics events only contains relevant information for us in order to identify and fix issues.", - "Type_message": "Type message", - "Room_search": "Rooms search", - "Room_selection": "Room selection 1...9", - "Next_room": "Next room", - "Previous_room": "Previous room", - "New_room": "New room", - "Upload_room": "Upload to room", - "Search_messages": "Search messages", - "Scroll_messages": "Scroll messages", - "Reply_latest": "Reply to latest", - "Reply_in_Thread": "Reply in Thread", - "Server_selection": "Server selection", - "Server_selection_numbers": "Server selection 1...9", - "Add_server": "Add server", - "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_remove_all_data_from_this_server": "This will remove all data from this server.", - "Mark_unread": "Mark Unread", - "Wait_activation_warning": "Before you can login, your account must be manually activated by an administrator.", - "Screen_lock": "Screen lock", - "Local_authentication_biometry_title": "Authenticate", - "Local_authentication_biometry_fallback": "Use passcode", - "Local_authentication_unlock_option": "Unlock with Passcode", - "Local_authentication_change_passcode": "Change Passcode", - "Local_authentication_info": "Note: if you forget the Passcode, you'll need to delete and reinstall the app.", - "Local_authentication_facial_recognition": "facial recognition", - "Local_authentication_fingerprint": "fingerprint", - "Local_authentication_unlock_with_label": "Unlock with {{label}}", - "Local_authentication_auto_lock_60": "After 1 minute", - "Local_authentication_auto_lock_300": "After 5 minutes", - "Local_authentication_auto_lock_900": "After 15 minutes", - "Local_authentication_auto_lock_1800": "After 30 minutes", - "Local_authentication_auto_lock_3600": "After 1 hour", - "Passcode_enter_title": "Enter your passcode", - "Passcode_choose_title": "Choose your new passcode", - "Passcode_choose_confirm_title": "Confirm your new passcode", - "Passcode_choose_error": "Passcodes don't match. Try again.", - "Passcode_choose_force_set": "Passcode required by admin", - "Passcode_app_locked_title": "App locked", - "Passcode_app_locked_subtitle": "Try again in {{timeLeft}} seconds", - "After_seconds_set_by_admin": "After {{seconds}} seconds (set by admin)", - "Dont_activate": "Don't activate now", - "Queued_chats": "Queued chats", - "Queue_is_empty": "Queue is empty", - "Logout_from_other_logged_in_locations": "Logout from other logged in locations", - "You_will_be_logged_out_from_other_locations": "You'll be logged out from other locations.", - "Logged_out_of_other_clients_successfully": "Logged out of other clients successfully", - "Logout_failed": "Logout failed!", - "Log_analytics_events": "Log analytics events", - "E2E_encryption_change_password_title": "Change Encryption Password", - "E2E_encryption_change_password_description": "You can now create encrypted private groups and direct messages. You may also change existing private groups or DMs to encrypted. \nThis is end to end encryption so the key to encode/decode your messages will not be saved on the server. For that reason you need to store your password somewhere safe. You will be required to enter it on other devices you wish to use e2e encryption on.", - "E2E_encryption_change_password_error": "Error while changing E2E key password!", - "E2E_encryption_change_password_success": "E2E key password changed successfully!", - "E2E_encryption_change_password_message": "Make sure you've saved it carefully somewhere else.", - "E2E_encryption_change_password_confirmation": "Yes, change it", - "E2E_encryption_reset_title": "Reset E2E Key", - "E2E_encryption_reset_description": "This option will remove your current E2E key and log you out. \nWhen you login again, Rocket.Chat will generate you a new key and restore your access to any encrypted room that has one or more members online. \nDue to the nature of the E2E encryption, Rocket.Chat will not be able to restore access to any encrypted room that has no member online.", - "E2E_encryption_reset_button": "Reset E2E Key", - "E2E_encryption_reset_error": "Error while resetting E2E key!", - "E2E_encryption_reset_message": "You're going to be logged out.", - "E2E_encryption_reset_confirmation": "Yes, reset it", - "Following": "Following", - "Threads_displaying_all": "Displaying All", - "Threads_displaying_following": "Displaying Following", - "Threads_displaying_unread": "Displaying Unread", - "No_threads": "There are no threads", - "No_threads_following": "You are not following any threads", - "No_threads_unread": "There are no unread threads", - "Messagebox_Send_to_channel": "Send to channel", - "Leader": "Leader", - "Moderator": "Moderator", - "Owner": "Owner", - "Remove_from_room": "Remove from room", - "Ignore": "Ignore", - "Unignore": "Unignore", - "User_has_been_ignored": "User has been ignored", - "User_has_been_unignored": "User is no longer ignored", - "User_has_been_removed_from_s": "User has been removed from {{s}}", - "User__username__is_now_a_leader_of__room_name_": "User {{username}} is now a leader of {{room_name}}", - "User__username__is_now_a_moderator_of__room_name_": "User {{username}} is now a moderator of {{room_name}}", - "User__username__is_now_a_owner_of__room_name_": "User {{username}} is now a owner of {{room_name}}", - "User__username__removed_from__room_name__leaders": "User {{username}} removed from {{room_name}} leaders", - "User__username__removed_from__room_name__moderators": "User {{username}} removed from {{room_name}} moderators", - "User__username__removed_from__room_name__owners": "User {{username}} removed from {{room_name}} owners", - "The_user_will_be_removed_from_s": "The user will be removed from {{s}}", - "Yes_remove_user": "Yes, remove user!", - "Direct_message": "Direct message", - "Message_Ignored": "Message ignored. Tap to display it.", - "Enter_workspace_URL": "Enter workspace URL", - "Workspace_URL_Example": "Ex. your-company.rocket.chat", - "This_room_encryption_has_been_enabled_by__username_": "This room's encryption has been enabled by {{username}}", - "This_room_encryption_has_been_disabled_by__username_": "This room's encryption has been disabled by {{username}}", - "Teams": "Teams", - "No_team_channels_found": "No channels found", - "Team_not_found": "Team not found", - "Create_Team": "Create Team", - "Team_Name": "Team Name", - "Private_Team": "Private Team", - "Read_Only_Team": "Read Only Team", - "Broadcast_Team": "Broadcast Team", - "creating_team": "creating team", - "team-name-already-exists": "A team with that name already exists", - "Add_Channel_to_Team": "Add Channel to Team", - "Left_The_Team_Successfully": "Left the team successfully", - "Create_New": "Create New", - "Add_Existing": "Add Existing", - "Add_Existing_Channel": "Add Existing Channel", - "Remove_from_Team": "Remove from Team", - "Auto-join": "Auto-join", - "Remove_Team_Room_Warning": "Woud you like to remove this channel from the team? The channel will be moved back to the workspace", - "Confirmation": "Confirmation", - "invalid-room": "Invalid room", - "You_are_leaving_the_team": "You are leaving the team '{{team}}'", - "Leave_Team": "Leave Team", - "Select_Team": "Select Team", - "Select_Team_Channels": "Select the Team's channels you would like to leave.", - "Cannot_leave": "Cannot leave", - "Cannot_remove": "Cannot remove", - "Cannot_delete": "Cannot delete", - "Last_owner_team_room": "You are the last owner of this channel. Once you leave the team, the channel will be kept inside the team but you will be managing it from outside.", - "last-owner-can-not-be-removed": "Last owner cannot be removed", - "Remove_User_Teams": "Select channels you want the user to be removed from.", - "Delete_Team": "Delete Team", - "Select_channels_to_delete": "This can't be undone. Once you delete a team, all chat content and configuration will be deleted. \n\nSelect the channels you would like to delete. The ones you decide to keep will be available on your workspace. Notice that public channels will still be public and visible to everyone.", - "You_are_deleting_the_team": "You are deleting this team.", - "Removing_user_from_this_team": "You are removing {{user}} from this team", - "Remove_User_Team_Channels": "Select the channels you want the user to be removed from.", - "Remove_Member": "Remove Member", - "leaving_team": "leaving team", - "removing_team": "removing from team", - "moving_channel_to_team": "moving channel to team", - "deleting_team": "deleting team", - "member-does-not-exist": "Member does not exist", - "Convert": "Convert", - "Convert_to_Team": "Convert to Team", - "Convert_to_Team_Warning": "You are converting this Channel to a Team. All Members will be kept.", - "Move_to_Team": "Move to Team", - "Move_Channel_Paragraph": "Moving a channel inside a team means that this channel will be added in the team’s context, however, all channel’s members, which are not members of the respective team, will still have access to this channel, but will not be added as team’s members. \n\nAll channel’s management will still be made by the owners of this channel.\n\nTeam’s members and even team’s owners, if not a member of this channel, can not have access to the channel’s content. \n\nPlease notice that the Team’s owner will be able remove members from the Channel.", - "Move_to_Team_Warning": "After reading the previous intructions about this behavior, do you still want to move this channel to the selected team?", - "Load_More": "Load More", - "Load_Newer": "Load Newer", - "Load_Older": "Load Older", - "room-name-already-exists": "Room name already exists", - "error-team-creation": "Error team creation", - "unauthorized": "Unauthorized", - "Left_The_Room_Successfully": "Left the room successfully", - "Deleted_The_Team_Successfully": "Team deleted successfully", - "Deleted_The_Room_Successfully": "Room deleted successfully", - "Convert_to_Channel": "Convert to Channel", - "Converting_Team_To_Channel": "Converting Team to Channel", - "Select_Team_Channels_To_Delete": "Select the Team’s Channels you would like to delete, the ones you do not select will be moved to the Workspace. \n\nNotice that public Channels will be public and visible to everyone.", - "You_are_converting_the_team": "You are converting this Team to a Channel", - "creating_discussion": "creating discussion" -} \ No newline at end of file + "1_person_reacted": "1 person reacted", + "1_user": "1 user", + "error-action-not-allowed": "{{action}} is not allowed", + "error-application-not-found": "Application not found", + "error-archived-duplicate-name": "There's an archived channel with name {{room_name}}", + "error-avatar-invalid-url": "Invalid avatar URL: {{url}}", + "error-avatar-url-handling": "Error while handling avatar setting from a URL ({{url}}) for {{username}}", + "error-cant-invite-for-direct-room": "Can't invite user to direct rooms", + "error-could-not-change-email": "Could not change email", + "error-could-not-change-name": "Could not change name", + "error-could-not-change-username": "Could not change username", + "error-could-not-change-status": "Could not change status", + "error-delete-protected-role": "Cannot delete a protected role", + "error-department-not-found": "Department not found", + "error-direct-message-file-upload-not-allowed": "File sharing not allowed in direct messages", + "error-duplicate-channel-name": "A channel with name {{room_name}} exists", + "error-email-domain-blacklisted": "The email domain is blacklisted", + "error-email-send-failed": "Error trying to send email: {{message}}", + "error-save-image": "Error while saving image", + "error-save-video": "Error while saving video", + "error-field-unavailable": "{{field}} is already in use :(", + "error-file-too-large": "File is too large", + "error-importer-not-defined": "The importer was not defined correctly, it is missing the Import class.", + "error-input-is-not-a-valid-field": "{{input}} is not a valid {{field}}", + "error-invalid-actionlink": "Invalid action link", + "error-invalid-arguments": "Invalid arguments", + "error-invalid-asset": "Invalid asset", + "error-invalid-channel": "Invalid channel.", + "error-invalid-channel-start-with-chars": "Invalid channel. Start with @ or #", + "error-invalid-custom-field": "Invalid custom field", + "error-invalid-custom-field-name": "Invalid custom field name. Use only letters, numbers, hyphens and underscores.", + "error-invalid-date": "Invalid date provided.", + "error-invalid-description": "Invalid description", + "error-invalid-domain": "Invalid domain", + "error-invalid-email": "Invalid email {{email}}", + "error-invalid-email-address": "Invalid email address", + "error-invalid-file-height": "Invalid file height", + "error-invalid-file-type": "Invalid file type", + "error-invalid-file-width": "Invalid file width", + "error-invalid-from-address": "You informed an invalid FROM address.", + "error-invalid-integration": "Invalid integration", + "error-invalid-message": "Invalid message", + "error-invalid-method": "Invalid method", + "error-invalid-name": "Invalid name", + "error-invalid-password": "Invalid password", + "error-invalid-redirectUri": "Invalid redirectUri", + "error-invalid-role": "Invalid role", + "error-invalid-room": "Invalid room", + "error-invalid-room-name": "{{room_name}} is not a valid room name", + "error-invalid-room-type": "{{type}} is not a valid room type.", + "error-invalid-settings": "Invalid settings provided", + "error-invalid-subscription": "Invalid subscription", + "error-invalid-token": "Invalid token", + "error-invalid-triggerWords": "Invalid triggerWords", + "error-invalid-urls": "Invalid URLs", + "error-invalid-user": "Invalid user", + "error-invalid-username": "Invalid username", + "error-invalid-webhook-response": "The webhook URL responded with a status other than 200", + "error-message-deleting-blocked": "Message deleting is blocked", + "error-message-editing-blocked": "Message editing is blocked", + "error-message-size-exceeded": "Message size exceeds Message_MaxAllowedSize", + "error-missing-unsubscribe-link": "You must provide the [unsubscribe] link.", + "error-no-owner-channel": "You don't own the channel", + "error-no-tokens-for-this-user": "There are no tokens for this user", + "error-not-allowed": "Not allowed", + "error-not-authorized": "Not authorized", + "error-push-disabled": "Push is disabled", + "error-remove-last-owner": "This is the last owner. Please set a new owner before removing this one.", + "error-role-in-use": "Cannot delete role because it's in use", + "error-role-name-required": "Role name is required", + "error-the-field-is-required": "The field {{field}} is required.", + "error-too-many-requests": "Error, too many requests. Please slow down. You must wait {{seconds}} seconds before trying again.", + "error-user-is-not-activated": "User is not activated", + "error-user-has-no-roles": "User has no roles", + "error-user-limit-exceeded": "The number of users you are trying to invite to #channel_name exceeds the limit set by the administrator", + "error-user-not-in-room": "User is not in this room", + "error-user-registration-custom-field": "error-user-registration-custom-field", + "error-user-registration-disabled": "User registration is disabled", + "error-user-registration-secret": "User registration is only allowed via Secret URL", + "error-you-are-last-owner": "You are the last owner. Please set new owner before leaving the room.", + "error-status-not-allowed": "Invisible status is disabled", + "Actions": "Actions", + "activity": "activity", + "Activity": "Activity", + "Add_Reaction": "Add Reaction", + "Add_Server": "Add Server", + "Add_users": "Add users", + "Admin_Panel": "Admin Panel", + "Agent": "Agent", + "Alert": "Alert", + "alert": "alert", + "alerts": "alerts", + "All_users_in_the_channel_can_write_new_messages": "All users in the channel can write new messages", + "All_users_in_the_team_can_write_new_messages": "All users in the team can write new messages", + "A_meaningful_name_for_the_discussion_room": "A meaningful name for the discussion room", + "All": "All", + "All_Messages": "All Messages", + "Allow_Reactions": "Allow Reactions", + "Alphabetical": "Alphabetical", + "and_more": "and more", + "and": "and", + "announcement": "announcement", + "Announcement": "Announcement", + "Apply_Your_Certificate": "Apply Your Certificate", + "ARCHIVE": "ARCHIVE", + "archive": "archive", + "are_typing": "are typing", + "Are_you_sure_question_mark": "Are you sure?", + "Are_you_sure_you_want_to_leave_the_room": "Are you sure you want to leave the room {{room}}?", + "Audio": "Audio", + "Authenticating": "Authenticating", + "Automatic": "Automatic", + "Auto_Translate": "Auto-Translate", + "Avatar_changed_successfully": "Avatar changed successfully!", + "Avatar_Url": "Avatar URL", + "Away": "Away", + "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", + "By_proceeding_you_are_agreeing": "By proceeding you are agreeing to our", + "Cancel_editing": "Cancel editing", + "Cancel_recording": "Cancel recording", + "Cancel": "Cancel", + "changing_avatar": "changing avatar", + "creating_channel": "creating channel", + "creating_invite": "creating invite", + "Channel_Name": "Channel Name", + "Channels": "Channels", + "Chats": "Chats", + "Call_already_ended": "Call already ended!", + "Clear_cookies_alert": "Do you want to clear all cookies?", + "Clear_cookies_desc": "This action will clear all login cookies, allowing you to login into other accounts.", + "Clear_cookies_yes": "Yes, clear cookies", + "Clear_cookies_no": "No, keep cookies", + "Click_to_join": "Click to Join!", + "Close": "Close", + "Close_emoji_selector": "Close emoji selector", + "Closing_chat": "Closing chat", + "Change_language_loading": "Changing language.", + "Chat_closed_by_agent": "Chat closed by agent", + "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", + "Code_or_password_invalid": "Code or password invalid", + "Collaborative": "Collaborative", + "Confirm": "Confirm", + "Connect": "Connect", + "Connected": "Connected", + "connecting_server": "connecting to server", + "Connecting": "Connecting...", + "Contact_us": "Contact us", + "Contact_your_server_admin": "Contact your server admin.", + "Continue_with": "Continue with", + "Copied_to_clipboard": "Copied to clipboard!", + "Copy": "Copy", + "Conversation": "Conversation", + "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", + "Create_Direct_Messages": "Create Direct Messages", + "Create_Discussion": "Create Discussion", + "Created_snippet": "created a snippet", + "Create_a_new_workspace": "Create a new workspace", + "Create": "Create", + "Custom_Status": "Custom Status", + "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.", + "Department": "Department", + "delete": "delete", + "Delete": "Delete", + "DELETE": "DELETE", + "move": "move", + "deleting_room": "deleting room", + "description": "description", + "Description": "Description", + "Desktop_Options": "Desktop Options", + "Desktop_Notifications": "Desktop Notifications", + "Desktop_Alert_info": "These notifications are delivered in desktop", + "Directory": "Directory", + "Direct_Messages": "Direct Messages", + "Disable_notifications": "Disable notifications", + "Discussions": "Discussions", + "Discussion_Desc": "Help keeping an overview about what's going on! By creating a discussion, a sub-channel of the one you selected is created and both are linked.", + "Discussion_name": "Discussion name", + "Done": "Done", + "Dont_Have_An_Account": "Don't you have an account?", + "Do_you_have_an_account": "Do you have an account?", + "Do_you_have_a_certificate": "Do you have a certificate?", + "Do_you_really_want_to_key_this_room_question_mark": "Do you really want to {{key}} this room?", + "E2E_Encryption": "E2E Encryption", + "E2E_How_It_Works_info1": "You can now create encrypted private groups and direct messages. You may also change existing private groups or DMs to encrypted.", + "E2E_How_It_Works_info2": "This is *end to end encryption* so the key to encode/decode your messages and they will not be saved on the server. For that reason *you need to store this password somewhere safe* which you can access later if you may need.", + "E2E_How_It_Works_info3": "If you proceed, it will be auto generated an E2E password.", + "E2E_How_It_Works_info4": "You can also setup a new password for your encryption key any time from any browser you have entered the existing E2E password.", + "edit": "edit", + "edited": "edited", + "Edit": "Edit", + "Edit_Status": "Edit Status", + "Edit_Invite": "Edit Invite", + "End_to_end_encrypted_room": "End to end encrypted room", + "end_to_end_encryption": "end to end encryption", + "Email_Notification_Mode_All": "Every Mention/DM", + "Email_Notification_Mode_Disabled": "Disabled", + "Email_or_password_field_is_empty": "Email or password field is empty", + "Email": "E-mail", + "email": "e-mail", + "Empty_title": "Empty title", + "Enable_Auto_Translate": "Enable Auto-Translate", + "Enable_notifications": "Enable notifications", + "Encrypted": "Encrypted", + "Encrypted_message": "Encrypted message", + "Enter_Your_E2E_Password": "Enter Your E2E Password", + "Enter_Your_Encryption_Password_desc1": "This will allow you to access your encrypted private groups and direct messages.", + "Enter_Your_Encryption_Password_desc2": "You need to enter the password to encode/decode messages every place you use the chat.", + "Encryption_error_title": "Your encryption password seems wrong", + "Encryption_error_desc": "It wasn't possible to decode your encryption key to be imported.", + "Everyone_can_access_this_channel": "Everyone can access this channel", + "Everyone_can_access_this_team": "Everyone can access this team", + "Error_uploading": "Error uploading", + "Expiration_Days": "Expiration (Days)", + "Favorite": "Favorite", + "Favorites": "Favorites", + "Files": "Files", + "File_description": "File description", + "File_name": "File name", + "Finish_recording": "Finish recording", + "Following_thread": "Following thread", + "For_your_security_you_must_enter_your_current_password_to_continue": "For your security, you must enter your current password to continue", + "Forgot_password_If_this_email_is_registered": "If this email is registered, we'll send instructions on how to reset your password. If you do not receive an email shortly, please come back and try again.", + "Forgot_password": "Forgot your password?", + "Forgot_Password": "Forgot Password", + "Forward": "Forward", + "Forward_Chat": "Forward Chat", + "Forward_to_department": "Forward to department", + "Forward_to_user": "Forward to user", + "Full_table": "Click to see full table", + "Generate_New_Link": "Generate New Link", + "Group_by_favorites": "Group favorites", + "Group_by_type": "Group by type", + "Hide": "Hide", + "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", + "How_It_Works": "How It Works", + "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", + "I_Saved_My_E2E_Password": "I Saved My E2E Password", + "IP": "IP", + "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", + "Invite": "Invite", + "is_a_valid_RocketChat_instance": "is a valid Rocket.Chat instance", + "is_not_a_valid_RocketChat_instance": "is not a valid Rocket.Chat instance", + "is_typing": "is typing", + "Invalid_or_expired_invite_token": "Invalid or expired invite token", + "Invalid_server_version": "The server you're trying to connect is using a version that's not supported by the app anymore: {{currentVersion}}.\n\nWe require version {{minVersion}}", + "Invite_Link": "Invite Link", + "Invite_users": "Invite users", + "Join": "Join", + "Join_Code": "Join Code", + "Insert_Join_Code": "Insert Join Code", + "Join_our_open_workspace": "Join our open workspace", + "Join_your_workspace": "Join your workspace", + "Just_invited_people_can_access_this_channel": "Just invited people can access this channel", + "Just_invited_people_can_access_this_team": "Just invited people can access this team", + "Language": "Language", + "last_message": "last message", + "Leave_channel": "Leave channel", + "leaving_room": "leaving room", + "Leave": "Leave", + "leave": "leave", + "Legal": "Legal", + "Light": "Light", + "License": "License", + "Livechat": "Livechat", + "Livechat_edit": "Livechat edit", + "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", + "Max_number_of_users_allowed_is_number": "Max number of users allowed is {{maxUsers}}", + "members": "members", + "Members": "Members", + "Mentioned_Messages": "Mentioned Messages", + "mentioned": "mentioned", + "Mentions": "Mentions", + "Message_accessibility": "Message from {{user}} at {{time}}: {{message}}", + "Message_actions": "Message actions", + "Message_pinned": "Message pinned", + "Message_removed": "Message removed", + "Message_starred": "Message starred", + "Message_unstarred": "Message unstarred", + "message": "message", + "messages": "messages", + "Message": "Message", + "Messages": "Messages", + "Message_Reported": "Message reported", + "Microphone_Permission_Message": "Rocket.Chat needs access to your microphone so you can send audio message.", + "Microphone_Permission": "Microphone Permission", + "Mute": "Mute", + "muted": "muted", + "My_servers": "My servers", + "N_people_reacted": "{{n}} people reacted", + "N_users": "{{n}} users", + "N_channels": "{{n}} channels", + "name": "name", + "Name": "Name", + "Navigation_history": "Navigation history", + "Never": "Never", + "New_Message": "New Message", + "New_Password": "New Password", + "New_Server": "New Server", + "Next": "Next", + "No_files": "No files", + "No_limit": "No limit", + "No_mentioned_messages": "No mentioned messages", + "No_pinned_messages": "No pinned messages", + "No_results_found": "No results found", + "No_starred_messages": "No starred messages", + "No_thread_messages": "No thread messages", + "No_label_provided": "No {{label}} provided.", + "No_Message": "No Message", + "No_messages_yet": "No messages yet", + "No_Reactions": "No Reactions", + "No_Read_Receipts": "No Read Receipts", + "Not_logged": "Not logged", + "Not_RC_Server": "This is not a Rocket.Chat server.\n{{contact}}", + "Nothing": "Nothing", + "Nothing_to_save": "Nothing to save!", + "Notify_active_in_this_room": "Notify active users in this room", + "Notify_all_in_this_room": "Notify all in this room", + "Notifications": "Notifications", + "Notification_Duration": "Notification Duration", + "Notification_Preferences": "Notification Preferences", + "No_available_agents_to_transfer": "No available agents to transfer", + "Offline": "Offline", + "Oops": "Oops!", + "Omnichannel": "Omnichannel", + "Open_Livechats": "Chats in Progress", + "Omnichannel_enable_alert": "You're not available on Omnichannel. Would you like to be available?", + "Onboarding_description": "A workspace is your team or organization’s space to collaborate. Ask the workspace admin for address to join or create one for your team.", + "Onboarding_join_workspace": "Join a workspace", + "Onboarding_subtitle": "Beyond Team Collaboration", + "Onboarding_title": "Welcome to Rocket.Chat", + "Onboarding_join_open_description": "Join our open workspace to chat with the Rocket.Chat team and community.", + "Onboarding_agree_terms": "By continuing, you agree to Rocket.Chat", + "Onboarding_less_options": "Less options", + "Onboarding_more_options": "More options", + "Online": "Online", + "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", + "Open_your_authentication_app_and_enter_the_code": "Open your authentication app and enter the code.", + "OR": "OR", + "OS": "OS", + "Overwrites_the_server_configuration_and_use_room_config": "Overwrites the server configuration and use room config", + "Password": "Password", + "Parent_channel_or_group": "Parent channel or group", + "Permalink_copied_to_clipboard": "Permalink copied to clipboard!", + "Phone": "Phone", + "Pin": "Pin", + "Pinned_Messages": "Pinned Messages", + "pinned": "pinned", + "Pinned": "Pinned", + "Please_add_a_comment": "Please add a comment", + "Please_enter_your_password": "Please enter your password", + "Please_wait": "Please wait.", + "Preferences": "Preferences", + "Preferences_saved": "Preferences saved!", + "Privacy_Policy": " Privacy Policy", + "Private_Channel": "Private Channel", + "Private": "Private", + "Processing": "Processing...", + "Profile_saved_successfully": "Profile saved successfully!", + "Profile": "Profile", + "Public_Channel": "Public Channel", + "Public": "Public", + "Push_Notifications": "Push Notifications", + "Push_Notifications_Alert_Info": "These notifications are delivered to you when the app is not open", + "Quote": "Quote", + "Reactions_are_disabled": "Reactions are disabled", + "Reactions_are_enabled": "Reactions are enabled", + "Reactions": "Reactions", + "Read": "Read", + "Read_External_Permission_Message": "Rocket.Chat needs to access photos, media, and files on your device", + "Read_External_Permission": "Read Media Permission", + "Read_Only_Channel": "Read Only Channel", + "Read_Only": "Read Only", + "Read_Receipt": "Read Receipt", + "Receive_Group_Mentions": "Receive Group Mentions", + "Receive_Group_Mentions_Info": "Receive @all and @here mentions", + "Register": "Register", + "Repeat_Password": "Repeat Password", + "Replied_on": "Replied on:", + "replies": "replies", + "reply": "reply", + "Reply": "Reply", + "Report": "Report", + "Receive_Notification": "Receive Notification", + "Receive_notifications_from": "Receive notifications from {{name}}", + "Resend": "Resend", + "Reset_password": "Reset password", + "resetting_password": "resetting password", + "RESET": "RESET", + "Return": "Return", + "Review_app_title": "Are you enjoying this app?", + "Review_app_desc": "Give us 5 stars on {{store}}", + "Review_app_yes": "Sure!", + "Review_app_no": "No", + "Review_app_later": "Maybe later", + "Review_app_unable_store": "Unable to open {{store}}", + "Review_this_app": "Review this app", + "Remove": "Remove", + "remove": "remove", + "Roles": "Roles", + "Room_actions": "Room actions", + "Room_changed_announcement": "Room announcement changed to: {{announcement}} by {{userBy}}", + "Room_changed_avatar": "Room avatar changed by {{userBy}}", + "Room_changed_description": "Room description changed to: {{description}} by {{userBy}}", + "Room_changed_privacy": "Room type changed to: {{type}} by {{userBy}}", + "Room_changed_topic": "Room topic changed to: {{topic}} by {{userBy}}", + "Room_Files": "Room Files", + "Room_Info_Edit": "Room Info Edit", + "Room_Info": "Room Info", + "Room_Members": "Room Members", + "Room_name_changed": "Room name changed to: {{name}} by {{userBy}}", + "SAVE": "SAVE", + "Save_Changes": "Save Changes", + "Save": "Save", + "Saved": "Saved", + "saving_preferences": "saving preferences", + "saving_profile": "saving profile", + "saving_settings": "saving settings", + "saved_to_gallery": "Saved to gallery", + "Save_Your_E2E_Password": "Save Your E2E Password", + "Save_Your_Encryption_Password": "Save Your Encryption Password", + "Save_Your_Encryption_Password_warning": "This password is not stored anywhere so save it carefully somewhere else.", + "Save_Your_Encryption_Password_info": "Notice that you lose your password, there is no way to recover it and you will lose access to your messages.", + "Search_Messages": "Search Messages", + "Search": "Search", + "Search_by": "Search by", + "Search_global_users": "Search for global users", + "Search_global_users_description": "If you turn-on, you can search for any user from others companies or servers.", + "Seconds": "{{second}} seconds", + "Security_and_privacy": "Security and privacy", + "Select_Avatar": "Select Avatar", + "Select_Server": "Select Server", + "Select_Users": "Select Users", + "Select_a_Channel": "Select a Channel", + "Select_a_Department": "Select a Department", + "Select_an_option": "Select an option", + "Select_a_User": "Select a User", + "Send": "Send", + "Send_audio_message": "Send audio message", + "Send_crash_report": "Send crash report", + "Send_message": "Send message", + "Send_me_the_code_again": "Send me the code again", + "Send_to": "Send to...", + "Sending_to": "Sending to", + "Sent_an_attachment": "Sent an attachment", + "Server": "Server", + "Servers": "Servers", + "Server_version": "Server version: {{version}}", + "Set_username_subtitle": "The username is used to allow others to mention you in messages", + "Set_custom_status": "Set custom status", + "Set_status": "Set status", + "Status_saved_successfully": "Status saved successfully!", + "Settings": "Settings", + "Settings_succesfully_changed": "Settings succesfully changed!", + "Share": "Share", + "Share_Link": "Share Link", + "Share_this_app": "Share this app", + "Show_more": "Show more..", + "Show_Unread_Counter": "Show Unread Counter", + "Show_Unread_Counter_Info": "Unread counter is displayed as a badge on the right of the channel, in the list", + "Sign_in_your_server": "Sign in your server", + "Sign_Up": "Sign Up", + "Some_field_is_invalid_or_empty": "Some field is invalid or empty", + "Sorting_by": "Sorting by {{key}}", + "Sound": "Sound", + "Star_room": "Star room", + "Star": "Star", + "Starred_Messages": "Starred Messages", + "starred": "starred", + "Starred": "Starred", + "Start_of_conversation": "Start of conversation", + "Start_a_Discussion": "Start a Discussion", + "Started_discussion": "Started a discussion:", + "Started_call": "Call started by {{userBy}}", + "Submit": "Submit", + "Table": "Table", + "Tags": "Tags", + "Take_a_photo": "Take a photo", + "Take_a_video": "Take a video", + "Take_it": "Take it!", + "tap_to_change_status": "tap to change status", + "Tap_to_view_servers_list": "Tap to view servers list", + "Terms_of_Service": " Terms of Service ", + "Theme": "Theme", + "The_user_wont_be_able_to_type_in_roomName": "The user won't be able to type in {{roomName}}", + "The_user_will_be_able_to_type_in_roomName": "The user will be able to type in {{roomName}}", + "There_was_an_error_while_action": "There was an error while {{action}}!", + "This_room_is_blocked": "This room is blocked", + "This_room_is_read_only": "This room is read only", + "Thread": "Thread", + "Threads": "Threads", + "Timezone": "Timezone", + "To": "To", + "topic": "topic", + "Topic": "Topic", + "Translate": "Translate", + "Try_again": "Try again", + "Two_Factor_Authentication": "Two-factor Authentication", + "Type_the_channel_name_here": "Type the channel name here", + "unarchive": "unarchive", + "UNARCHIVE": "UNARCHIVE", + "Unblock_user": "Unblock user", + "Unfavorite": "Unfavorite", + "Unfollowed_thread": "Unfollowed thread", + "Unmute": "Unmute", + "unmuted": "unmuted", + "Unpin": "Unpin", + "unread_messages": "unread", + "Unread": "Unread", + "Unread_on_top": "Unread on top", + "Unstar": "Unstar", + "Updating": "Updating...", + "Uploading": "Uploading", + "Upload_file_question_mark": "Upload file?", + "User": "User", + "Users": "Users", + "User_added_by": "User {{userAdded}} added by {{userBy}}", + "User_Info": "User Info", + "User_has_been_key": "User has been {{key}}", + "User_is_no_longer_role_by_": "{{user}} is no longer {{role}} by {{userBy}}", + "User_muted_by": "User {{userMuted}} muted by {{userBy}}", + "User_removed_by": "User {{userRemoved}} removed by {{userBy}}", + "User_sent_an_attachment": "{{user}} sent an attachment", + "User_unmuted_by": "User {{userUnmuted}} unmuted by {{userBy}}", + "User_was_set_role_by_": "{{user}} was set {{role}} by {{userBy}}", + "Username_is_empty": "Username is empty", + "Username": "Username", + "Username_or_email": "Username or email", + "Uses_server_configuration": "Uses server configuration", + "Validating": "Validating", + "Registration_Succeeded": "Registration Succeeded!", + "Verify": "Verify", + "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.", + "Verify_your_email_for_the_code_we_sent": "Verify your email for the code we sent", + "Video_call": "Video call", + "View_Original": "View Original", + "Voice_call": "Voice call", + "Waiting_for_network": "Waiting for network...", + "Websocket_disabled": "Websocket is disabled for this server.\n{{contact}}", + "Welcome": "Welcome", + "What_are_you_doing_right_now": "What are you doing right now?", + "Whats_your_2fa": "What's your 2FA code?", + "Without_Servers": "Without Servers", + "Workspaces": "Workspaces", + "Would_you_like_to_return_the_inquiry": "Would you like to return the inquiry?", + "Write_External_Permission_Message": "Rocket.Chat needs access to your gallery so you can save images.", + "Write_External_Permission": "Gallery Permission", + "Yes": "Yes", + "Yes_action_it": "Yes, {{action}} it!", + "Yesterday": "Yesterday", + "You_are_in_preview_mode": "You are in preview mode", + "You_are_offline": "You are offline", + "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.", + "You_need_to_access_at_least_one_RocketChat_server_to_share_something": "You need to access at least one Rocket.Chat server to share something.", + "You_need_to_verifiy_your_email_address_to_get_notications": "You need to verify your email address to get notifications", + "Your_certificate": "Your Certificate", + "Your_invite_link_will_expire_after__usesLeft__uses": "Your invite link will expire after {{usesLeft}} uses.", + "Your_invite_link_will_expire_on__date__or_after__usesLeft__uses": "Your invite link will expire on {{date}} or after {{usesLeft}} uses.", + "Your_invite_link_will_expire_on__date__": "Your invite link will expire on {{date}}.", + "Your_invite_link_will_never_expire": "Your invite link will never expire.", + "Your_workspace": "Your workspace", + "Your_password_is": "Your password is", + "Version_no": "Version: {{version}}", + "You_will_not_be_able_to_recover_this_message": "You will not be able to recover this message!", + "You_will_unset_a_certificate_for_this_server": "You will unset a certificate for this server", + "Change_Language": "Change Language", + "Crash_report_disclaimer": "We never track the content of your chats. The crash report and analytics events only contains relevant information for us in order to identify and fix issues.", + "Type_message": "Type message", + "Room_search": "Rooms search", + "Room_selection": "Room selection 1...9", + "Next_room": "Next room", + "Previous_room": "Previous room", + "New_room": "New room", + "Upload_room": "Upload to room", + "Search_messages": "Search messages", + "Scroll_messages": "Scroll messages", + "Reply_latest": "Reply to latest", + "Reply_in_Thread": "Reply in Thread", + "Server_selection": "Server selection", + "Server_selection_numbers": "Server selection 1...9", + "Add_server": "Add server", + "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_remove_all_data_from_this_server": "This will remove all data from this server.", + "Mark_unread": "Mark Unread", + "Wait_activation_warning": "Before you can login, your account must be manually activated by an administrator.", + "Screen_lock": "Screen lock", + "Local_authentication_biometry_title": "Authenticate", + "Local_authentication_biometry_fallback": "Use passcode", + "Local_authentication_unlock_option": "Unlock with Passcode", + "Local_authentication_change_passcode": "Change Passcode", + "Local_authentication_info": "Note: if you forget the Passcode, you'll need to delete and reinstall the app.", + "Local_authentication_facial_recognition": "facial recognition", + "Local_authentication_fingerprint": "fingerprint", + "Local_authentication_unlock_with_label": "Unlock with {{label}}", + "Local_authentication_auto_lock_60": "After 1 minute", + "Local_authentication_auto_lock_300": "After 5 minutes", + "Local_authentication_auto_lock_900": "After 15 minutes", + "Local_authentication_auto_lock_1800": "After 30 minutes", + "Local_authentication_auto_lock_3600": "After 1 hour", + "Passcode_enter_title": "Enter your passcode", + "Passcode_choose_title": "Choose your new passcode", + "Passcode_choose_confirm_title": "Confirm your new passcode", + "Passcode_choose_error": "Passcodes don't match. Try again.", + "Passcode_choose_force_set": "Passcode required by admin", + "Passcode_app_locked_title": "App locked", + "Passcode_app_locked_subtitle": "Try again in {{timeLeft}} seconds", + "After_seconds_set_by_admin": "After {{seconds}} seconds (set by admin)", + "Dont_activate": "Don't activate now", + "Queued_chats": "Queued chats", + "Queue_is_empty": "Queue is empty", + "Logout_from_other_logged_in_locations": "Logout from other logged in locations", + "You_will_be_logged_out_from_other_locations": "You'll be logged out from other locations.", + "Logged_out_of_other_clients_successfully": "Logged out of other clients successfully", + "Logout_failed": "Logout failed!", + "Log_analytics_events": "Log analytics events", + "E2E_encryption_change_password_title": "Change Encryption Password", + "E2E_encryption_change_password_description": "You can now create encrypted private groups and direct messages. You may also change existing private groups or DMs to encrypted. \nThis is end to end encryption so the key to encode/decode your messages will not be saved on the server. For that reason you need to store your password somewhere safe. You will be required to enter it on other devices you wish to use e2e encryption on.", + "E2E_encryption_change_password_error": "Error while changing E2E key password!", + "E2E_encryption_change_password_success": "E2E key password changed successfully!", + "E2E_encryption_change_password_message": "Make sure you've saved it carefully somewhere else.", + "E2E_encryption_change_password_confirmation": "Yes, change it", + "E2E_encryption_reset_title": "Reset E2E Key", + "E2E_encryption_reset_description": "This option will remove your current E2E key and log you out. \nWhen you login again, Rocket.Chat will generate you a new key and restore your access to any encrypted room that has one or more members online. \nDue to the nature of the E2E encryption, Rocket.Chat will not be able to restore access to any encrypted room that has no member online.", + "E2E_encryption_reset_button": "Reset E2E Key", + "E2E_encryption_reset_error": "Error while resetting E2E key!", + "E2E_encryption_reset_message": "You're going to be logged out.", + "E2E_encryption_reset_confirmation": "Yes, reset it", + "Following": "Following", + "Threads_displaying_all": "Displaying All", + "Threads_displaying_following": "Displaying Following", + "Threads_displaying_unread": "Displaying Unread", + "No_threads": "There are no threads", + "No_threads_following": "You are not following any threads", + "No_threads_unread": "There are no unread threads", + "Messagebox_Send_to_channel": "Send to channel", + "Leader": "Leader", + "Moderator": "Moderator", + "Owner": "Owner", + "Remove_from_room": "Remove from room", + "Ignore": "Ignore", + "Unignore": "Unignore", + "User_has_been_ignored": "User has been ignored", + "User_has_been_unignored": "User is no longer ignored", + "User_has_been_removed_from_s": "User has been removed from {{s}}", + "User__username__is_now_a_leader_of__room_name_": "User {{username}} is now a leader of {{room_name}}", + "User__username__is_now_a_moderator_of__room_name_": "User {{username}} is now a moderator of {{room_name}}", + "User__username__is_now_a_owner_of__room_name_": "User {{username}} is now a owner of {{room_name}}", + "User__username__removed_from__room_name__leaders": "User {{username}} removed from {{room_name}} leaders", + "User__username__removed_from__room_name__moderators": "User {{username}} removed from {{room_name}} moderators", + "User__username__removed_from__room_name__owners": "User {{username}} removed from {{room_name}} owners", + "The_user_will_be_removed_from_s": "The user will be removed from {{s}}", + "Yes_remove_user": "Yes, remove user!", + "Direct_message": "Direct message", + "Message_Ignored": "Message ignored. Tap to display it.", + "Enter_workspace_URL": "Enter workspace URL", + "Workspace_URL_Example": "Ex. your-company.rocket.chat", + "This_room_encryption_has_been_enabled_by__username_": "This room's encryption has been enabled by {{username}}", + "This_room_encryption_has_been_disabled_by__username_": "This room's encryption has been disabled by {{username}}", + "Teams": "Teams", + "No_team_channels_found": "No channels found", + "Team_not_found": "Team not found", + "Create_Team": "Create Team", + "Team_Name": "Team Name", + "Private_Team": "Private Team", + "Read_Only_Team": "Read Only Team", + "Broadcast_Team": "Broadcast Team", + "creating_team": "creating team", + "team-name-already-exists": "A team with that name already exists", + "Add_Channel_to_Team": "Add Channel to Team", + "Left_The_Team_Successfully": "Left the team successfully", + "Create_New": "Create New", + "Add_Existing": "Add Existing", + "Add_Existing_Channel": "Add Existing Channel", + "Remove_from_Team": "Remove from Team", + "Auto-join": "Auto-join", + "Remove_Team_Room_Warning": "Woud you like to remove this channel from the team? The channel will be moved back to the workspace", + "Confirmation": "Confirmation", + "invalid-room": "Invalid room", + "You_are_leaving_the_team": "You are leaving the team '{{team}}'", + "Leave_Team": "Leave Team", + "Select_Team": "Select Team", + "Select_Team_Channels": "Select the Team's channels you would like to leave.", + "Cannot_leave": "Cannot leave", + "Cannot_remove": "Cannot remove", + "Cannot_delete": "Cannot delete", + "Last_owner_team_room": "You are the last owner of this channel. Once you leave the team, the channel will be kept inside the team but you will be managing it from outside.", + "last-owner-can-not-be-removed": "Last owner cannot be removed", + "Remove_User_Teams": "Select channels you want the user to be removed from.", + "Delete_Team": "Delete Team", + "Select_channels_to_delete": "This can't be undone. Once you delete a team, all chat content and configuration will be deleted. \n\nSelect the channels you would like to delete. The ones you decide to keep will be available on your workspace. Notice that public channels will still be public and visible to everyone.", + "You_are_deleting_the_team": "You are deleting this team.", + "Removing_user_from_this_team": "You are removing {{user}} from this team", + "Remove_User_Team_Channels": "Select the channels you want the user to be removed from.", + "Remove_Member": "Remove Member", + "leaving_team": "leaving team", + "removing_team": "removing from team", + "moving_channel_to_team": "moving channel to team", + "deleting_team": "deleting team", + "member-does-not-exist": "Member does not exist", + "Convert": "Convert", + "Convert_to_Team": "Convert to Team", + "Convert_to_Team_Warning": "You are converting this Channel to a Team. All Members will be kept.", + "Move_to_Team": "Move to Team", + "Move_Channel_Paragraph": "Moving a channel inside a team means that this channel will be added in the team’s context, however, all channel’s members, which are not members of the respective team, will still have access to this channel, but will not be added as team’s members. \n\nAll channel’s management will still be made by the owners of this channel.\n\nTeam’s members and even team’s owners, if not a member of this channel, can not have access to the channel’s content. \n\nPlease notice that the Team’s owner will be able remove members from the Channel.", + "Move_to_Team_Warning": "After reading the previous intructions about this behavior, do you still want to move this channel to the selected team?", + "Load_More": "Load More", + "Load_Newer": "Load Newer", + "Load_Older": "Load Older", + "room-name-already-exists": "Room name already exists", + "error-team-creation": "Error team creation", + "unauthorized": "Unauthorized", + "Left_The_Room_Successfully": "Left the room successfully", + "Deleted_The_Team_Successfully": "Team deleted successfully", + "Deleted_The_Room_Successfully": "Room deleted successfully", + "Convert_to_Channel": "Convert to Channel", + "Converting_Team_To_Channel": "Converting Team to Channel", + "Select_Team_Channels_To_Delete": "Select the Team’s Channels you would like to delete, the ones you do not select will be moved to the Workspace. \n\nNotice that public Channels will be public and visible to everyone.", + "You_are_converting_the_team": "You are converting this Team to a Channel", + "creating_discussion": "creating discussion", + "Canned_Responses": "Canned Responses", + "No_match_found": "No match found.", + "Check_canned_responses": "Check on canned responses.", + "Searching": "Searching", + "Use": "Use", + "Shortcut": "Shortcut", + "Content": "Content", + "Sharing": "Sharing", + "No_canned_responses": "No canned responses" +} diff --git a/app/i18n/locales/es-ES.json b/app/i18n/locales/es-ES.json index 25a65523d..faf9bca29 100644 --- a/app/i18n/locales/es-ES.json +++ b/app/i18n/locales/es-ES.json @@ -1,454 +1,454 @@ { - "1_person_reacted": "1 persona reaccionó", - "1_user": "1 usuario", - "error-action-not-allowed": "{{action}} no permitida", - "error-application-not-found": "Aplicación no encontrada", - "error-archived-duplicate-name": "Hay un canal archivado con nombre {{room_name}}", - "error-avatar-invalid-url": "URL de avatar inválida: {{url}}", - "error-avatar-url-handling": "Error durante el procesamiento de ajuste de imagen de usuario desde una dirección URL ({{url}}) para {{username}}", - "error-cant-invite-for-direct-room": "No se puede invitar a los usuarios a salas de chat directas", - "error-could-not-change-email": "No es posible cambiar la dirección de correo electrónico", - "error-could-not-change-name": "No es posible cambiar el nombre", - "error-could-not-change-username": "No es posible cambiar el nombre de usuario", - "error-delete-protected-role": "No se puede eliminar un rol protegido", - "error-department-not-found": "Departamento no encontrado", - "error-direct-message-file-upload-not-allowed": "No se permite compartir archivos en mensajes directos", - "error-duplicate-channel-name": "Ya existe un canal con nombre {{room_name}}", - "error-email-domain-blacklisted": "El dominio del correo electrónico está en la lista negra", - "error-email-send-failed": "Error al enviar el correo electrónico: {{message}}", - "error-field-unavailable": "{{field}} ya está en uso :(", - "error-file-too-large": "El archivo es demasiado grande", - "error-importer-not-defined": "El importador no se configuró correctamente. Falta la clase de importación", - "error-input-is-not-a-valid-field": "{{input}} no es válido {{field}}", - "error-invalid-actionlink": "Enlace de acción inválido", - "error-invalid-arguments": "Los argumentos no son correctos", - "error-invalid-asset": "El archivo archivo no es correcto", - "error-invalid-channel": "El canal no es correcto.", - "error-invalid-channel-start-with-chars": "Canal incorrecto. Debe comenzar con @ o #", - "error-invalid-custom-field": "Campo personalizado no válido", - "error-invalid-custom-field-name": "Nombre no válido para el campo personalizado. Utilice sólo letras, números, guiones o guión bajo", - "error-invalid-date": "La fecha proporcionada no es correcta.", - "error-invalid-description": "La descripción no es correcta", - "error-invalid-domain": "El dominio no es correcto", - "error-invalid-email": "El email {{email}} no es correcto", - "error-invalid-email-address": "La dirección de correo no es correcta", - "error-invalid-file-height": "La altura de la imagen no es correcta", - "error-invalid-file-type": "El formato del archivo no es correcto", - "error-invalid-file-width": "El ancho de la imagen o es correcto", - "error-invalid-from-address": "La dirección del remitente (FROM) no es correcta.", - "error-invalid-integration": "La integración no es correcta", - "error-invalid-message": "El mensaje no es correcto", - "error-invalid-method": "El método no es correcto", - "error-invalid-name": "El nombre no es correcto", - "error-invalid-password": "La contraseña no es correcta", - "error-invalid-redirectUri": "La URL de redirección no es correcta.", - "error-invalid-role": "El rol no es correcto", - "error-invalid-room": "La sala no es correcta", - "error-invalid-room-name": "No se puede asignar el nombre {{room_name}} a una sala.", - "error-invalid-room-type": "No se puede asignar el tipo {{type}} a una sala.", - "error-invalid-settings": "La configuración proporcionada no es correcta", - "error-invalid-subscription": "La suscripción no es correcta", - "error-invalid-token": "El token no es correcto", - "error-invalid-triggerWords": "El triggerWords no es correcto", - "error-invalid-urls": "Las URLs no son correctas", - "error-invalid-user": "El usuario no es correcto", - "error-invalid-username": "El nombre de usuario no es correcto", - "error-invalid-webhook-response": "El webhook no ha respondido con código de estado HTTP 200.", - "error-message-deleting-blocked": "No está permitido eliminar mensajes", - "error-message-editing-blocked": "No está permitido editar mensajes", - "error-message-size-exceeded": "El mensaje supera el tamaño máximo permitido", - "error-missing-unsubscribe-link": "Debes proporcionar el enlace para cancelar la suscripción [unsubscribe].", - "error-no-tokens-for-this-user": "No hay tokens asignados para el usuario", - "error-not-allowed": "No permitido", - "error-not-authorized": "No autorizado", - "error-push-disabled": "El Push está desactivado", - "error-remove-last-owner": "El usuario es el único propietario existente. Debes establecer un nuevo propietario antes de eliminarlo.", - "error-role-in-use": "No puedes eliminar el rol dado que está en uso", - "error-role-name-required": "Debes indicar el nombre del rol", - "error-the-field-is-required": "El campo {{field}} es obligatorio.", - "error-too-many-requests": "Error, demasiadas peticiones. Debes esperar {{seconds}} segundos antes de continuar. Por favor, sé paciente.", - "error-user-is-not-activated": "El usuario no está activo", - "error-user-has-no-roles": "El usuario no tiene roles", - "error-user-limit-exceeded": "El número de usuarios que quieres invitar al canal #channel_name supera el límite establecido por el administrador.", - "error-user-not-in-room": "El usuario no está en la sala", - "error-user-registration-custom-field": "error-user-registration-custom-field", - "error-user-registration-disabled": "El registro de usuario está deshabilitado", - "error-user-registration-secret": "El registro de usuarios sólo está permitido por URL secretas", - "error-you-are-last-owner": "Eres el único propietario existente. Debes establecer un nuevo propietario antes de abandonar la sala.", - "Actions": "Acciones", - "activity": "actividad", - "Activity": "Actividad", - "Add_Reaction": "Añadir reacción", - "Add_Server": "Añadir servidor", - "Admin_Panel": "Panel de Control", - "Alert": "Alerta", - "alert": "alerta", - "alerts": "alertas", - "All_users_in_the_channel_can_write_new_messages": "Todos los usuarios en el canal pueden escribir mensajes", - "All": "Todos", - "All_Messages": "Todos los mensajes", - "Allow_Reactions": "Permitir reacciones", - "Alphabetical": "Alfabético", - "and_more": "y más", - "and": "y", - "announcement": "anuncio", - "Announcement": "Anuncio", - "Apply_Your_Certificate": "Aplica tu certificado", - "ARCHIVE": "FICHERO", - "archive": "fichero", - "are_typing": "están escribiendo", - "Are_you_sure_question_mark": "¿Estás seguro?", - "Are_you_sure_you_want_to_leave_the_room": "¿Deseas salir de la sala {{room}}?", - "Audio": "Audio", - "Authenticating": "Autenticando", - "Automatic": "Automático", - "Auto_Translate": "Traducción automática", - "Avatar_changed_successfully": "¡Avatar modificado correctamente!", - "Avatar_Url": "URL del Avatar", - "Away": "Ausente", - "Back": "Volver", - "Black": "Negro", - "Block_user": "Bloquear usuario", - "Broadcast_channel_Description": "Sólo los usuarios autorizados pueden escribir nuevos mensajes, el resto podrán responder sobre los mismos.", - "Broadcast_Channel": "Canal de Transmisión", - "Busy": "Ocupado", - "By_proceeding_you_are_agreeing": "Al proceder estarás de acuerdo", - "Cancel_editing": "Cancelar edición", - "Cancel_recording": "Cancelar grabación", - "Cancel": "Cancelar", - "changing_avatar": "cambiando avatar", - "creating_channel": "creando channel", - "Channel_Name": "Nombre sala", - "Channels": "Salas", - "Chats": "Chats", - "Call_already_ended": "¡!La llamada ya ha finalizado!", - "Click_to_join": "¡Unirme!", - "Close": "Cerrar", - "Close_emoji_selector": "Cerrar selector de emojis", - "Choose": "Seleccionar", - "Choose_from_library": "Seleccionar desde galería", - "Choose_file": "Seleccionar archivo", - "Code": "Código", - "Collaborative": "Colaborativo", - "Confirm": "Confirmar", - "Connect": "Conectar", - "Connected": "Conectado", - "connecting_server": "conectando al servidor", - "Connecting": "Conectando...", - "Contact_us": "Contacta con nosotros", - "Contact_your_server_admin": "Contacta con el administrador.", - "Continue_with": "Continuar con", - "Copied_to_clipboard": "¡Copiado al portapapeles!", - "Copy": "Copiar", - "Permalink": "Enlace permanente", - "Certificate_password": "Contraseña del certificado", - "Whats_the_password_for_your_certificate": "¿Cuál es la contraseña de tu certificado?", - "Create_account": "Crear una cuenta", - "Create_Channel": "Crear sala", - "Created_snippet": "crear mensaje en bloque", - "Create_a_new_workspace": "Crear un nuevo espacio de trabajo", - "Create": "Crear", - "Dark": "Oscuro", - "Dark_level": "Nivel de oscuridad", - "Default": "Por defecto", - "Delete_Room_Warning": "Eliminar a un usuario causará la eliminación de todos los mensajes creados por dicho usuario. Esta operación no se puede deshacer.", - "delete": "eliminar", - "Delete": "Eliminar", - "DELETE": "ELIMINAR", - "deleting_room": "eliminando sala", - "description": "descripción", - "Description": "Descripción", - "Desktop_Options": "Opciones de escritorio", - "Directory": "Directorio", - "Direct_Messages": "Mensajes directos", - "Disable_notifications": "Desactivar notificaciones", - "Discussions": "Conversaciones", - "Dont_Have_An_Account": "¿Todavía no tienes una cuenta?", - "Do_you_have_a_certificate": "¿Tienes un certificado?", - "Do_you_really_want_to_key_this_room_question_mark": "¿Deseas {{key}} de esta sala?", - "edit": "editar", - "edited": "editado", - "Edit": "Editar", - "Email_or_password_field_is_empty": "El email o la contraseña están vacíos", - "Email": "E-mail", - "email": "e-mail", - "Enable_Auto_Translate": "Permitir Auto-Translate", - "Enable_notifications": "Permitir notificaciones", - "Everyone_can_access_this_channel": "Todos los usuarios pueden acceder a este canal", - "Error_uploading": "Error en la subida", - "Favorite": "Favorito", - "Favorites": "Favoritos", - "Files": "Archivos", - "File_description": "Descripción del archivo", - "File_name": "Nombre del archivo", - "Finish_recording": "Finalizar grabación", - "Following_thread": "Siguiendo hilo", - "For_your_security_you_must_enter_your_current_password_to_continue": "Por seguridad, debes introducir tu contraseña para continuar", - "Forgot_password_If_this_email_is_registered": "Si este email está registrado, te enviaremos las instrucciones para resetear tu contraseña. Si no recibes un email en breve, vuelve aquí e inténtalo de nuevo.", - "Forgot_password": "¿Ha olvidado su contraseña?", - "Forgot_Password": "Olvidé la contraseña", - "Full_table": "Click para ver la tabla completa", - "Group_by_favorites": "Agrupar por favoritos", - "Group_by_type": "Agrupar por tipo", - "Hide": "Ocultar", - "Has_joined_the_channel": "se ha unido al canal", - "Has_joined_the_conversation": "se ha unido a la conversación", - "Has_left_the_channel": "ha dejado el canal", - "In_App_And_Desktop": "En la aplicación y en el escritorio", - "In_App_and_Desktop_Alert_info": "Muestra un banner en la parte superior de la pantalla cuando la aplicación esté abierta y muestra una notificación en el escritorio", - "Invisible": "Invisible", - "Invite": "Invitar", - "is_a_valid_RocketChat_instance": "es una instancia válida de Rocket.Chat", - "is_not_a_valid_RocketChat_instance": "no es una instancia válida de Rocket.Chat", - "is_typing": "escribiendo", - "Invalid_server_version": "El servidor que intentas conectar está usando una versión que ya no está soportada por la aplicación : {{currentVersion}}. Se requiere una versión {{minVersion}}.", - "Join": "Conectar", - "Just_invited_people_can_access_this_channel": "Sólo gente invitada puede acceder a este canal.", - "Language": "Idioma", - "last_message": "último mensaje", - "Leave_channel": "Abandonar el canal", - "leaving_room": "abandonando sala", - "leave": "abandonar", - "Legal": "Legal", - "Light": "Claro", - "License": "Licencia", - "Livechat": "LiveChat", - "Login": "Inicio de sesión", - "Login_error": "¡Sus credenciales fueron rechazadas! Por favor, inténtelo de nuevo.", - "Login_with": "Iniciar sesión con", - "Logout": "Cerrar sesión", - "members": "miembros", - "Members": "Miembros", - "Mentioned_Messages": "Mensajes mencionados", - "mentioned": "mencionado", - "Mentions": "Menciones", - "Message_accessibility": "Mensaje de {{user}} a las {{time}}: {{message}}", - "Message_actions": "Acciones de mensaje", - "Message_pinned": "Mensaje fijado", - "Message_removed": "Mensaje eliminado", - "message": "mensaje", - "messages": "mensajes", - "Messages": "Mensajes", - "Message_Reported": "Mensaje notificado", - "Microphone_Permission_Message": "Rocket.Chat necesita acceso a su micrófono para que pueda enviar un mensaje de audio.", - "Microphone_Permission": "Permiso de micrófono", - "Mute": "Mutear", - "muted": "muteado", - "My_servers": "Mis servidores", - "N_people_reacted": "Han reaccionado {{n}} personas", - "N_users": "{{n}} usuarios", - "name": "nombre", - "Name": "Nombre", - "New_Message": "Nuevo mensaje", - "New_Password": "Nueva contraseña", - "New_Server": "Nuevo servidor", - "Next": "Siguiente", - "No_files": "No hay archivos", - "No_mentioned_messages": "No hay mensajes mencionados", - "No_pinned_messages": "No hay mensajes fijados", - "No_results_found": "No hay resultados", - "No_starred_messages": "No hay mensajes destacados", - "No_thread_messages": "No hay hilos", - "No_Message": "Sin mensajes", - "No_messages_yet": "No hay mensajes todavía", - "No_Reactions": "No hay reacciones", - "No_Read_Receipts": "No hay confirmaciones de lectura", - "Not_logged": "No ha iniciado sesión", - "Not_RC_Server": "Esto no es un servidor de Rocket.Chat.\n{{contact}}", - "Nothing": "Nada", - "Nothing_to_save": "¡No hay nada por guardar!", - "Notify_active_in_this_room": "Notificar a los usuarios activos en esta sala", - "Notify_all_in_this_room": "Notificar a todos en esta sala", - "Notifications": "Notificaciones", - "Notification_Duration": "Duración de la notificación", - "Notification_Preferences": "Configuración de notificaciones", - "Offline": "Sin conexión", - "Oops": "Oops!", - "Onboarding_title": "Bienvenido a Rocket.Chat", - "Online": "Conectado", - "Only_authorized_users_can_write_new_messages": "Sólo pueden escribir mensajes usuarios autorizados", - "Open_emoji_selector": "Abrir selector de emojis", - "Open_Source_Communication": "Comunicación Open Source", - "Password": "Contraseña", - "Permalink_copied_to_clipboard": "¡Enlace permanente copiado al portapapeles!", - "Pin": "Fijar", - "Pinned_Messages": "Mensajes fijados", - "pinned": "fijado", - "Pinned": "Fijado", - "Please_enter_your_password": "Por favor introduce la contraseña", - "Preferences": "Preferencias", - "Preferences_saved": "¡Preferencias guardadas!", - "Privacy_Policy": "Política de privacidad", - "Private_Channel": "Canal privado", - "Private": "Privado", - "Processing": "Procesando...", - "Profile_saved_successfully": "¡Perfil guardado correctamente!", - "Profile": "Perfil", - "Public_Channel": "Canal público", - "Public": "Público", - "Push_Notifications": "Notificaciones Push", - "Push_Notifications_Alert_Info": "Estas notificaciones se le entregan cuando la aplicación no está abierta", - "Quote": "Citar", - "Reactions_are_disabled": "Las reacciones están desactivadas", - "Reactions_are_enabled": "Las reacciones están activadas", - "Reactions": "Reacciones", - "Read": "Leer", - "Read_Only_Channel": "Canal de sólo lectura", - "Read_Only": "Sólo lectura ", - "Read_Receipt": "Comprobante de lectura", - "Receive_Group_Mentions": "Recibir menciones de grupo", - "Receive_Group_Mentions_Info": "Recibir menciones @all y @here", - "Register": "Registrar", - "Repeat_Password": "Repetir contraseña", - "Replied_on": "Respondido el:", - "replies": "respuestas", - "reply": "respuesta", - "Reply": "Respuesta", - "Report": "Informe", - "Receive_Notification": "Recibir notificación", - "Receive_notifications_from": "Recibir notificación de {{name}}", - "Resend": "Reenviar", - "Reset_password": "Resetear contraseña", - "resetting_password": "reseteando contraseña", - "RESET": "RESET", - "Roles": "Roles", - "Room_actions": "Acciones de sala", - "Room_changed_announcement": "El anuncio de la sala cambió a: {{announcement}} por {{userBy}}", - "Room_changed_description": "La descripción de la sala cambió a: {{description}} por {{userBy}}", - "Room_changed_privacy": "El tipo de la sala cambió a: {{type}} por {{userBy}}", - "Room_changed_topic": "El asunto de la sala cambió a: {{topic}} por {{userBy}}", - "Room_Files": "Archivos", - "Room_Info_Edit": "Editar información de la sala", - "Room_Info": "Información de la sala", - "Room_Members": "Miembros de la sala", - "Room_name_changed": "El nombre de la sala cambió a: {{name}} por {{userBy}}", - "SAVE": "GUARDAR", - "Save_Changes": "Guardar cambios", - "Save": "Guardar", - "saving_preferences": "guardando preferencias", - "saving_profile": "guardando perfil", - "saving_settings": "guardando configuración", - "Search_Messages": "Buscar mensajes", - "Search": "Buscar", - "Search_by": "Buscar por", - "Search_global_users": "Buscar por usuarios globales", - "Search_global_users_description": "Si lo activas, puedes buscar cualquier usuario de otras empresas o servidores.", - "Seconds": "{{second}} segundos", - "Select_Avatar": "Selecciona avatar", - "Select_Server": "Selecciona servidor", - "Select_Users": "Selecciona usuarios", - "Send": "Enviar", - "Send_audio_message": "Enviar nota de audio", - "Send_crash_report": "Enviar informe errores", - "Send_message": "Enviar mensaje", - "Send_to": "Enviar a..", - "Sent_an_attachment": "Enviar un adjunto", - "Server": "Servidor", - "Servers": "Servidores", - "Server_version": "Versión servidor: {{version}}", - "Set_username_subtitle": "El nombre de usuario se utiliza para permitir que otros le mencionen en los mensajes", - "Settings": "Configuración", - "Settings_succesfully_changed": "¡Configuración cambiada correctamente!", - "Share": "Compartir", - "Share_this_app": "Compartir esta aplicación", - "Show_Unread_Counter": "Mostrar contador de no leídos", - "Show_Unread_Counter_Info": "El contador de no leídos se muestra como una insignia a la derecha del canal, en la lista", - "Sign_in_your_server": "Accede a tu servidor", - "Sign_Up": "Registrarse", - "Some_field_is_invalid_or_empty": "Algún campo no es correcto o está vacío", - "Sorting_by": "Ordenado por {{key}}", - "Sound": "Sonido", - "Star_room": "Destacar sala", - "Star": "Destacar", - "Starred_Messages": "Mensajes destacados", - "starred": "destacado", - "Starred": "Destacado", - "Start_of_conversation": "Comienzo de la conversación", - "Started_discussion": "Comenzar una conversación:", - "Started_call": "Llamada iniciada por {{userBy}}", - "Submit": "Enviar", - "Table": "Tabla", - "Take_a_photo": "Enviar una foto", - "Take_a_video": "Enviar un vídeo", - "tap_to_change_status": "pulsa para cambiar el estado", - "Tap_to_view_servers_list": "Pulsa para ver la lista de servidores", - "Terms_of_Service": "Términos de servicio", - "Theme": "Tema", - "There_was_an_error_while_action": "¡Ha habido un error mientras {{action}}!", - "This_room_is_blocked": "La sala está bloqueada", - "This_room_is_read_only": "Esta sala es de sólo lectura", - "Thread": "Hilo", - "Threads": "Hilos", - "Timezone": "Zona horaria", - "To": "Para", - "topic": "asunto", - "Topic": "Asunto", - "Translate": "Traducir", - "Try_again": "Intentar de nuevo", - "Two_Factor_Authentication": "Autenticación de doble factor", - "Type_the_channel_name_here": "Escribe el nombre del canal aquí", - "unarchive": "desarchivar", - "UNARCHIVE": "DESARCHIVAR", - "Unblock_user": "Desbloquear usuario", - "Unfavorite": "Quitar favorito", - "Unfollowed_thread": "Dejar de seguir el hilo", - "Unmute": "Desmutear", - "unmuted": "Desmuteado", - "Unpin": "Quitar estado fijado", - "unread_messages": "marcar como no leído", - "Unread": "Marcar como no leído", - "Unread_on_top": "Mensajes no leídos en la parte superior", - "Unstar": "Quitar destacado", - "Updating": "Actualizando...", - "Uploading": "Subiendo", - "Upload_file_question_mark": "¿Subir fichero?", - "Users": "Usuarios", - "User_added_by": "Usuario {{userAdded}} añadido por {{userBy}}", - "User_has_been_key": "El usuario ha sido {{key}}", - "User_is_no_longer_role_by_": "{{user}} ha dejado de ser {{role}} por {{userBy}}", - "User_muted_by": "Usuario {{userMuted}} muteado por {{userBy}}", - "User_removed_by": "Usuario {{userRemoved}} eliminado por {{userBy}}", - "User_sent_an_attachment": "{{user}} envío un adjunto", - "User_unmuted_by": "Usuario {{userUnmuted}} desmuteado por {{userBy}}", - "User_was_set_role_by_": "{{user}} ha comenzado a ser {{role}} por {{userBy}}", - "Username_is_empty": "Nombre de usuario está vacío", - "Username": "Nombre de usuario", - "Username_or_email": "Nombre de usuario o email", - "Validating": "Validando", - "Video_call": "Vídeo llamada", - "View_Original": "Ver original", - "Voice_call": "Llamada de voz", - "Websocket_disabled": "Websocket está deshabilitado para este servidor.\n{{contact}}", - "Welcome": "Bienvenido", - "Whats_your_2fa": "¿Cuál es tu código 2FA?", - "Without_Servers": "Sin servidores", - "Yes_action_it": "Sí, ¡{{action}}!", - "Yesterday": "Ayer", - "You_are_in_preview_mode": "Estás en modo vista previa", - "You_are_offline": "Estás desconectado", - "You_can_search_using_RegExp_eg": "Puedes usar expresiones regulares. Por ejemplo, `/^text$/i`", - "You_colon": "Tú: ", - "you_were_mentioned": "has sido mencionado", - "you": "tú", - "You": "Tú", - "You_need_to_access_at_least_one_RocketChat_server_to_share_something": "Necesita acceder al menos a un servidor Rocket.Chat para compartir algo.", - "Your_certificate": "Tu certificado", - "Version_no": "Versión: {{version}}", - "You_will_not_be_able_to_recover_this_message": "¡No podrás recuperar este mensaje!", - "Change_Language": "Cambiar idioma", - "Crash_report_disclaimer": "Nunca rastreamos el contenido de sus conversaciones. El informe del error sólo contiene información relevante para nosotros con el fin de identificar los problemas y solucionarlos.", - "Type_message": "Escribir mensaje", - "Room_search": "Búsqueda de salas", - "Room_selection": "Selecciona sala 1...9", - "Next_room": "Siguiente sala", - "Previous_room": "Sala anterior", - "New_room": "Nueva sala", - "Upload_room": "Subir a sala", - "Search_messages": "Buscar mensajes", - "Scroll_messages": "Scroll mensajes", - "Reply_latest": "Responder al último", - "Server_selection": "Seleccionar servidor", - "Server_selection_numbers": "Seleccionar servidor 1...9", - "Add_server": "Añadir servidor", - "New_line": "Nueva línea" -} \ No newline at end of file + "1_person_reacted": "1 persona reaccionó", + "1_user": "1 usuario", + "error-action-not-allowed": "{{action}} no permitida", + "error-application-not-found": "Aplicación no encontrada", + "error-archived-duplicate-name": "Hay un canal archivado con nombre {{room_name}}", + "error-avatar-invalid-url": "URL de avatar inválida: {{url}}", + "error-avatar-url-handling": "Error durante el procesamiento de ajuste de imagen de usuario desde una dirección URL ({{url}}) para {{username}}", + "error-cant-invite-for-direct-room": "No se puede invitar a los usuarios a salas de chat directas", + "error-could-not-change-email": "No es posible cambiar la dirección de correo electrónico", + "error-could-not-change-name": "No es posible cambiar el nombre", + "error-could-not-change-username": "No es posible cambiar el nombre de usuario", + "error-delete-protected-role": "No se puede eliminar un rol protegido", + "error-department-not-found": "Departamento no encontrado", + "error-direct-message-file-upload-not-allowed": "No se permite compartir archivos en mensajes directos", + "error-duplicate-channel-name": "Ya existe un canal con nombre {{room_name}}", + "error-email-domain-blacklisted": "El dominio del correo electrónico está en la lista negra", + "error-email-send-failed": "Error al enviar el correo electrónico: {{message}}", + "error-field-unavailable": "{{field}} ya está en uso :(", + "error-file-too-large": "El archivo es demasiado grande", + "error-importer-not-defined": "El importador no se configuró correctamente. Falta la clase de importación", + "error-input-is-not-a-valid-field": "{{input}} no es válido {{field}}", + "error-invalid-actionlink": "Enlace de acción inválido", + "error-invalid-arguments": "Los argumentos no son correctos", + "error-invalid-asset": "El archivo archivo no es correcto", + "error-invalid-channel": "El canal no es correcto.", + "error-invalid-channel-start-with-chars": "Canal incorrecto. Debe comenzar con @ o #", + "error-invalid-custom-field": "Campo personalizado no válido", + "error-invalid-custom-field-name": "Nombre no válido para el campo personalizado. Utilice sólo letras, números, guiones o guión bajo", + "error-invalid-date": "La fecha proporcionada no es correcta.", + "error-invalid-description": "La descripción no es correcta", + "error-invalid-domain": "El dominio no es correcto", + "error-invalid-email": "El email {{email}} no es correcto", + "error-invalid-email-address": "La dirección de correo no es correcta", + "error-invalid-file-height": "La altura de la imagen no es correcta", + "error-invalid-file-type": "El formato del archivo no es correcto", + "error-invalid-file-width": "El ancho de la imagen o es correcto", + "error-invalid-from-address": "La dirección del remitente (FROM) no es correcta.", + "error-invalid-integration": "La integración no es correcta", + "error-invalid-message": "El mensaje no es correcto", + "error-invalid-method": "El método no es correcto", + "error-invalid-name": "El nombre no es correcto", + "error-invalid-password": "La contraseña no es correcta", + "error-invalid-redirectUri": "La URL de redirección no es correcta.", + "error-invalid-role": "El rol no es correcto", + "error-invalid-room": "La sala no es correcta", + "error-invalid-room-name": "No se puede asignar el nombre {{room_name}} a una sala.", + "error-invalid-room-type": "No se puede asignar el tipo {{type}} a una sala.", + "error-invalid-settings": "La configuración proporcionada no es correcta", + "error-invalid-subscription": "La suscripción no es correcta", + "error-invalid-token": "El token no es correcto", + "error-invalid-triggerWords": "El triggerWords no es correcto", + "error-invalid-urls": "Las URLs no son correctas", + "error-invalid-user": "El usuario no es correcto", + "error-invalid-username": "El nombre de usuario no es correcto", + "error-invalid-webhook-response": "El webhook no ha respondido con código de estado HTTP 200.", + "error-message-deleting-blocked": "No está permitido eliminar mensajes", + "error-message-editing-blocked": "No está permitido editar mensajes", + "error-message-size-exceeded": "El mensaje supera el tamaño máximo permitido", + "error-missing-unsubscribe-link": "Debes proporcionar el enlace para cancelar la suscripción [unsubscribe].", + "error-no-tokens-for-this-user": "No hay tokens asignados para el usuario", + "error-not-allowed": "No permitido", + "error-not-authorized": "No autorizado", + "error-push-disabled": "El Push está desactivado", + "error-remove-last-owner": "El usuario es el único propietario existente. Debes establecer un nuevo propietario antes de eliminarlo.", + "error-role-in-use": "No puedes eliminar el rol dado que está en uso", + "error-role-name-required": "Debes indicar el nombre del rol", + "error-the-field-is-required": "El campo {{field}} es obligatorio.", + "error-too-many-requests": "Error, demasiadas peticiones. Debes esperar {{seconds}} segundos antes de continuar. Por favor, sé paciente.", + "error-user-is-not-activated": "El usuario no está activo", + "error-user-has-no-roles": "El usuario no tiene roles", + "error-user-limit-exceeded": "El número de usuarios que quieres invitar al canal #channel_name supera el límite establecido por el administrador.", + "error-user-not-in-room": "El usuario no está en la sala", + "error-user-registration-custom-field": "error-user-registration-custom-field", + "error-user-registration-disabled": "El registro de usuario está deshabilitado", + "error-user-registration-secret": "El registro de usuarios sólo está permitido por URL secretas", + "error-you-are-last-owner": "Eres el único propietario existente. Debes establecer un nuevo propietario antes de abandonar la sala.", + "Actions": "Acciones", + "activity": "actividad", + "Activity": "Actividad", + "Add_Reaction": "Añadir reacción", + "Add_Server": "Añadir servidor", + "Admin_Panel": "Panel de Control", + "Alert": "Alerta", + "alert": "alerta", + "alerts": "alertas", + "All_users_in_the_channel_can_write_new_messages": "Todos los usuarios en el canal pueden escribir mensajes", + "All": "Todos", + "All_Messages": "Todos los mensajes", + "Allow_Reactions": "Permitir reacciones", + "Alphabetical": "Alfabético", + "and_more": "y más", + "and": "y", + "announcement": "anuncio", + "Announcement": "Anuncio", + "Apply_Your_Certificate": "Aplica tu certificado", + "ARCHIVE": "FICHERO", + "archive": "fichero", + "are_typing": "están escribiendo", + "Are_you_sure_question_mark": "¿Estás seguro?", + "Are_you_sure_you_want_to_leave_the_room": "¿Deseas salir de la sala {{room}}?", + "Audio": "Audio", + "Authenticating": "Autenticando", + "Automatic": "Automático", + "Auto_Translate": "Traducción automática", + "Avatar_changed_successfully": "¡Avatar modificado correctamente!", + "Avatar_Url": "URL del Avatar", + "Away": "Ausente", + "Back": "Volver", + "Black": "Negro", + "Block_user": "Bloquear usuario", + "Broadcast_channel_Description": "Sólo los usuarios autorizados pueden escribir nuevos mensajes, el resto podrán responder sobre los mismos.", + "Broadcast_Channel": "Canal de Transmisión", + "Busy": "Ocupado", + "By_proceeding_you_are_agreeing": "Al proceder estarás de acuerdo", + "Cancel_editing": "Cancelar edición", + "Cancel_recording": "Cancelar grabación", + "Cancel": "Cancelar", + "changing_avatar": "cambiando avatar", + "creating_channel": "creando channel", + "Channel_Name": "Nombre sala", + "Channels": "Salas", + "Chats": "Chats", + "Call_already_ended": "¡!La llamada ya ha finalizado!", + "Click_to_join": "¡Unirme!", + "Close": "Cerrar", + "Close_emoji_selector": "Cerrar selector de emojis", + "Choose": "Seleccionar", + "Choose_from_library": "Seleccionar desde galería", + "Choose_file": "Seleccionar archivo", + "Code": "Código", + "Collaborative": "Colaborativo", + "Confirm": "Confirmar", + "Connect": "Conectar", + "Connected": "Conectado", + "connecting_server": "conectando al servidor", + "Connecting": "Conectando...", + "Contact_us": "Contacta con nosotros", + "Contact_your_server_admin": "Contacta con el administrador.", + "Continue_with": "Continuar con", + "Copied_to_clipboard": "¡Copiado al portapapeles!", + "Copy": "Copiar", + "Permalink": "Enlace permanente", + "Certificate_password": "Contraseña del certificado", + "Whats_the_password_for_your_certificate": "¿Cuál es la contraseña de tu certificado?", + "Create_account": "Crear una cuenta", + "Create_Channel": "Crear sala", + "Created_snippet": "crear mensaje en bloque", + "Create_a_new_workspace": "Crear un nuevo espacio de trabajo", + "Create": "Crear", + "Dark": "Oscuro", + "Dark_level": "Nivel de oscuridad", + "Default": "Por defecto", + "Delete_Room_Warning": "Eliminar a un usuario causará la eliminación de todos los mensajes creados por dicho usuario. Esta operación no se puede deshacer.", + "delete": "eliminar", + "Delete": "Eliminar", + "DELETE": "ELIMINAR", + "deleting_room": "eliminando sala", + "description": "descripción", + "Description": "Descripción", + "Desktop_Options": "Opciones de escritorio", + "Directory": "Directorio", + "Direct_Messages": "Mensajes directos", + "Disable_notifications": "Desactivar notificaciones", + "Discussions": "Conversaciones", + "Dont_Have_An_Account": "¿Todavía no tienes una cuenta?", + "Do_you_have_a_certificate": "¿Tienes un certificado?", + "Do_you_really_want_to_key_this_room_question_mark": "¿Deseas {{key}} de esta sala?", + "edit": "editar", + "edited": "editado", + "Edit": "Editar", + "Email_or_password_field_is_empty": "El email o la contraseña están vacíos", + "Email": "E-mail", + "email": "e-mail", + "Enable_Auto_Translate": "Permitir Auto-Translate", + "Enable_notifications": "Permitir notificaciones", + "Everyone_can_access_this_channel": "Todos los usuarios pueden acceder a este canal", + "Error_uploading": "Error en la subida", + "Favorite": "Favorito", + "Favorites": "Favoritos", + "Files": "Archivos", + "File_description": "Descripción del archivo", + "File_name": "Nombre del archivo", + "Finish_recording": "Finalizar grabación", + "Following_thread": "Siguiendo hilo", + "For_your_security_you_must_enter_your_current_password_to_continue": "Por seguridad, debes introducir tu contraseña para continuar", + "Forgot_password_If_this_email_is_registered": "Si este email está registrado, te enviaremos las instrucciones para resetear tu contraseña. Si no recibes un email en breve, vuelve aquí e inténtalo de nuevo.", + "Forgot_password": "¿Ha olvidado su contraseña?", + "Forgot_Password": "Olvidé la contraseña", + "Full_table": "Click para ver la tabla completa", + "Group_by_favorites": "Agrupar por favoritos", + "Group_by_type": "Agrupar por tipo", + "Hide": "Ocultar", + "Has_joined_the_channel": "se ha unido al canal", + "Has_joined_the_conversation": "se ha unido a la conversación", + "Has_left_the_channel": "ha dejado el canal", + "In_App_And_Desktop": "En la aplicación y en el escritorio", + "In_App_and_Desktop_Alert_info": "Muestra un banner en la parte superior de la pantalla cuando la aplicación esté abierta y muestra una notificación en el escritorio", + "Invisible": "Invisible", + "Invite": "Invitar", + "is_a_valid_RocketChat_instance": "es una instancia válida de Rocket.Chat", + "is_not_a_valid_RocketChat_instance": "no es una instancia válida de Rocket.Chat", + "is_typing": "escribiendo", + "Invalid_server_version": "El servidor que intentas conectar está usando una versión que ya no está soportada por la aplicación : {{currentVersion}}. Se requiere una versión {{minVersion}}.", + "Join": "Conectar", + "Just_invited_people_can_access_this_channel": "Sólo gente invitada puede acceder a este canal.", + "Language": "Idioma", + "last_message": "último mensaje", + "Leave_channel": "Abandonar el canal", + "leaving_room": "abandonando sala", + "leave": "abandonar", + "Legal": "Legal", + "Light": "Claro", + "License": "Licencia", + "Livechat": "LiveChat", + "Login": "Inicio de sesión", + "Login_error": "¡Sus credenciales fueron rechazadas! Por favor, inténtelo de nuevo.", + "Login_with": "Iniciar sesión con", + "Logout": "Cerrar sesión", + "members": "miembros", + "Members": "Miembros", + "Mentioned_Messages": "Mensajes mencionados", + "mentioned": "mencionado", + "Mentions": "Menciones", + "Message_accessibility": "Mensaje de {{user}} a las {{time}}: {{message}}", + "Message_actions": "Acciones de mensaje", + "Message_pinned": "Mensaje fijado", + "Message_removed": "Mensaje eliminado", + "message": "mensaje", + "messages": "mensajes", + "Messages": "Mensajes", + "Message_Reported": "Mensaje notificado", + "Microphone_Permission_Message": "Rocket.Chat necesita acceso a su micrófono para que pueda enviar un mensaje de audio.", + "Microphone_Permission": "Permiso de micrófono", + "Mute": "Mutear", + "muted": "muteado", + "My_servers": "Mis servidores", + "N_people_reacted": "Han reaccionado {{n}} personas", + "N_users": "{{n}} usuarios", + "name": "nombre", + "Name": "Nombre", + "New_Message": "Nuevo mensaje", + "New_Password": "Nueva contraseña", + "New_Server": "Nuevo servidor", + "Next": "Siguiente", + "No_files": "No hay archivos", + "No_mentioned_messages": "No hay mensajes mencionados", + "No_pinned_messages": "No hay mensajes fijados", + "No_results_found": "No hay resultados", + "No_starred_messages": "No hay mensajes destacados", + "No_thread_messages": "No hay hilos", + "No_Message": "Sin mensajes", + "No_messages_yet": "No hay mensajes todavía", + "No_Reactions": "No hay reacciones", + "No_Read_Receipts": "No hay confirmaciones de lectura", + "Not_logged": "No ha iniciado sesión", + "Not_RC_Server": "Esto no es un servidor de Rocket.Chat.\n{{contact}}", + "Nothing": "Nada", + "Nothing_to_save": "¡No hay nada por guardar!", + "Notify_active_in_this_room": "Notificar a los usuarios activos en esta sala", + "Notify_all_in_this_room": "Notificar a todos en esta sala", + "Notifications": "Notificaciones", + "Notification_Duration": "Duración de la notificación", + "Notification_Preferences": "Configuración de notificaciones", + "Offline": "Sin conexión", + "Oops": "Oops!", + "Onboarding_title": "Bienvenido a Rocket.Chat", + "Online": "Conectado", + "Only_authorized_users_can_write_new_messages": "Sólo pueden escribir mensajes usuarios autorizados", + "Open_emoji_selector": "Abrir selector de emojis", + "Open_Source_Communication": "Comunicación Open Source", + "Password": "Contraseña", + "Permalink_copied_to_clipboard": "¡Enlace permanente copiado al portapapeles!", + "Pin": "Fijar", + "Pinned_Messages": "Mensajes fijados", + "pinned": "fijado", + "Pinned": "Fijado", + "Please_enter_your_password": "Por favor introduce la contraseña", + "Preferences": "Preferencias", + "Preferences_saved": "¡Preferencias guardadas!", + "Privacy_Policy": "Política de privacidad", + "Private_Channel": "Canal privado", + "Private": "Privado", + "Processing": "Procesando...", + "Profile_saved_successfully": "¡Perfil guardado correctamente!", + "Profile": "Perfil", + "Public_Channel": "Canal público", + "Public": "Público", + "Push_Notifications": "Notificaciones Push", + "Push_Notifications_Alert_Info": "Estas notificaciones se le entregan cuando la aplicación no está abierta", + "Quote": "Citar", + "Reactions_are_disabled": "Las reacciones están desactivadas", + "Reactions_are_enabled": "Las reacciones están activadas", + "Reactions": "Reacciones", + "Read": "Leer", + "Read_Only_Channel": "Canal de sólo lectura", + "Read_Only": "Sólo lectura ", + "Read_Receipt": "Comprobante de lectura", + "Receive_Group_Mentions": "Recibir menciones de grupo", + "Receive_Group_Mentions_Info": "Recibir menciones @all y @here", + "Register": "Registrar", + "Repeat_Password": "Repetir contraseña", + "Replied_on": "Respondido el:", + "replies": "respuestas", + "reply": "respuesta", + "Reply": "Respuesta", + "Report": "Informe", + "Receive_Notification": "Recibir notificación", + "Receive_notifications_from": "Recibir notificación de {{name}}", + "Resend": "Reenviar", + "Reset_password": "Resetear contraseña", + "resetting_password": "reseteando contraseña", + "RESET": "RESET", + "Roles": "Roles", + "Room_actions": "Acciones de sala", + "Room_changed_announcement": "El anuncio de la sala cambió a: {{announcement}} por {{userBy}}", + "Room_changed_description": "La descripción de la sala cambió a: {{description}} por {{userBy}}", + "Room_changed_privacy": "El tipo de la sala cambió a: {{type}} por {{userBy}}", + "Room_changed_topic": "El asunto de la sala cambió a: {{topic}} por {{userBy}}", + "Room_Files": "Archivos", + "Room_Info_Edit": "Editar información de la sala", + "Room_Info": "Información de la sala", + "Room_Members": "Miembros de la sala", + "Room_name_changed": "El nombre de la sala cambió a: {{name}} por {{userBy}}", + "SAVE": "GUARDAR", + "Save_Changes": "Guardar cambios", + "Save": "Guardar", + "saving_preferences": "guardando preferencias", + "saving_profile": "guardando perfil", + "saving_settings": "guardando configuración", + "Search_Messages": "Buscar mensajes", + "Search": "Buscar", + "Search_by": "Buscar por", + "Search_global_users": "Buscar por usuarios globales", + "Search_global_users_description": "Si lo activas, puedes buscar cualquier usuario de otras empresas o servidores.", + "Seconds": "{{second}} segundos", + "Select_Avatar": "Selecciona avatar", + "Select_Server": "Selecciona servidor", + "Select_Users": "Selecciona usuarios", + "Send": "Enviar", + "Send_audio_message": "Enviar nota de audio", + "Send_crash_report": "Enviar informe errores", + "Send_message": "Enviar mensaje", + "Send_to": "Enviar a..", + "Sent_an_attachment": "Enviar un adjunto", + "Server": "Servidor", + "Servers": "Servidores", + "Server_version": "Versión servidor: {{version}}", + "Set_username_subtitle": "El nombre de usuario se utiliza para permitir que otros le mencionen en los mensajes", + "Settings": "Configuración", + "Settings_succesfully_changed": "¡Configuración cambiada correctamente!", + "Share": "Compartir", + "Share_this_app": "Compartir esta aplicación", + "Show_Unread_Counter": "Mostrar contador de no leídos", + "Show_Unread_Counter_Info": "El contador de no leídos se muestra como una insignia a la derecha del canal, en la lista", + "Sign_in_your_server": "Accede a tu servidor", + "Sign_Up": "Registrarse", + "Some_field_is_invalid_or_empty": "Algún campo no es correcto o está vacío", + "Sorting_by": "Ordenado por {{key}}", + "Sound": "Sonido", + "Star_room": "Destacar sala", + "Star": "Destacar", + "Starred_Messages": "Mensajes destacados", + "starred": "destacado", + "Starred": "Destacado", + "Start_of_conversation": "Comienzo de la conversación", + "Started_discussion": "Comenzar una conversación:", + "Started_call": "Llamada iniciada por {{userBy}}", + "Submit": "Enviar", + "Table": "Tabla", + "Take_a_photo": "Enviar una foto", + "Take_a_video": "Enviar un vídeo", + "tap_to_change_status": "pulsa para cambiar el estado", + "Tap_to_view_servers_list": "Pulsa para ver la lista de servidores", + "Terms_of_Service": "Términos de servicio", + "Theme": "Tema", + "There_was_an_error_while_action": "¡Ha habido un error mientras {{action}}!", + "This_room_is_blocked": "La sala está bloqueada", + "This_room_is_read_only": "Esta sala es de sólo lectura", + "Thread": "Hilo", + "Threads": "Hilos", + "Timezone": "Zona horaria", + "To": "Para", + "topic": "asunto", + "Topic": "Asunto", + "Translate": "Traducir", + "Try_again": "Intentar de nuevo", + "Two_Factor_Authentication": "Autenticación de doble factor", + "Type_the_channel_name_here": "Escribe el nombre del canal aquí", + "unarchive": "desarchivar", + "UNARCHIVE": "DESARCHIVAR", + "Unblock_user": "Desbloquear usuario", + "Unfavorite": "Quitar favorito", + "Unfollowed_thread": "Dejar de seguir el hilo", + "Unmute": "Desmutear", + "unmuted": "Desmuteado", + "Unpin": "Quitar estado fijado", + "unread_messages": "marcar como no leído", + "Unread": "Marcar como no leído", + "Unread_on_top": "Mensajes no leídos en la parte superior", + "Unstar": "Quitar destacado", + "Updating": "Actualizando...", + "Uploading": "Subiendo", + "Upload_file_question_mark": "¿Subir fichero?", + "Users": "Usuarios", + "User_added_by": "Usuario {{userAdded}} añadido por {{userBy}}", + "User_has_been_key": "El usuario ha sido {{key}}", + "User_is_no_longer_role_by_": "{{user}} ha dejado de ser {{role}} por {{userBy}}", + "User_muted_by": "Usuario {{userMuted}} muteado por {{userBy}}", + "User_removed_by": "Usuario {{userRemoved}} eliminado por {{userBy}}", + "User_sent_an_attachment": "{{user}} envío un adjunto", + "User_unmuted_by": "Usuario {{userUnmuted}} desmuteado por {{userBy}}", + "User_was_set_role_by_": "{{user}} ha comenzado a ser {{role}} por {{userBy}}", + "Username_is_empty": "Nombre de usuario está vacío", + "Username": "Nombre de usuario", + "Username_or_email": "Nombre de usuario o email", + "Validating": "Validando", + "Video_call": "Vídeo llamada", + "View_Original": "Ver original", + "Voice_call": "Llamada de voz", + "Websocket_disabled": "Websocket está deshabilitado para este servidor.\n{{contact}}", + "Welcome": "Bienvenido", + "Whats_your_2fa": "¿Cuál es tu código 2FA?", + "Without_Servers": "Sin servidores", + "Yes_action_it": "Sí, ¡{{action}}!", + "Yesterday": "Ayer", + "You_are_in_preview_mode": "Estás en modo vista previa", + "You_are_offline": "Estás desconectado", + "You_can_search_using_RegExp_eg": "Puedes usar expresiones regulares. Por ejemplo, `/^text$/i`", + "You_colon": "Tú: ", + "you_were_mentioned": "has sido mencionado", + "you": "tú", + "You": "Tú", + "You_need_to_access_at_least_one_RocketChat_server_to_share_something": "Necesita acceder al menos a un servidor Rocket.Chat para compartir algo.", + "Your_certificate": "Tu certificado", + "Version_no": "Versión: {{version}}", + "You_will_not_be_able_to_recover_this_message": "¡No podrás recuperar este mensaje!", + "Change_Language": "Cambiar idioma", + "Crash_report_disclaimer": "Nunca rastreamos el contenido de sus conversaciones. El informe del error sólo contiene información relevante para nosotros con el fin de identificar los problemas y solucionarlos.", + "Type_message": "Escribir mensaje", + "Room_search": "Búsqueda de salas", + "Room_selection": "Selecciona sala 1...9", + "Next_room": "Siguiente sala", + "Previous_room": "Sala anterior", + "New_room": "Nueva sala", + "Upload_room": "Subir a sala", + "Search_messages": "Buscar mensajes", + "Scroll_messages": "Scroll mensajes", + "Reply_latest": "Responder al último", + "Server_selection": "Seleccionar servidor", + "Server_selection_numbers": "Seleccionar servidor 1...9", + "Add_server": "Añadir servidor", + "New_line": "Nueva línea" +} diff --git a/app/i18n/locales/fr.json b/app/i18n/locales/fr.json index 07d441eed..242a3e277 100644 --- a/app/i18n/locales/fr.json +++ b/app/i18n/locales/fr.json @@ -1,772 +1,776 @@ { - "1_person_reacted": "1 personne a réagi", - "1_user": "1 utilisateur", - "error-action-not-allowed": "{{action}} n'est pas autorisé", - "error-application-not-found": "Application non trouvée", - "error-archived-duplicate-name": "Il y a un canal archivé avec le nom {{room_name}}", - "error-avatar-invalid-url": "URL d'avatar invalide : {{url}}", - "error-avatar-url-handling": "Erreur lors de la gestion du paramètre d'avatar à partir d'une URL ({{url}}) pour {{username}}", - "error-cant-invite-for-direct-room": "Impossible d'inviter l'utilisateur aux salons directs", - "error-could-not-change-email": "Impossible de changer l'adresse e-mail", - "error-could-not-change-name": "Impossible de changer le nom", - "error-could-not-change-username": "Impossible de changer le nom d'utilisateur", - "error-could-not-change-status": "Impossible de changer le statut", - "error-delete-protected-role": "Impossible de supprimer un rôle protégé", - "error-department-not-found": "Département introuvable", - "error-direct-message-file-upload-not-allowed": "Partage de fichiers non autorisé dans les messages privés", - "error-duplicate-channel-name": "Un canal avec nom {{room_name}} existe", - "error-email-domain-blacklisted": "Le domaine de messagerie est sur liste noire", - "error-email-send-failed": "Erreur lors de la tentative d'envoi de l'e-mail : {{message}}", - "error-save-image": "Erreur lors de l'enregistrement de l'image", - "error-save-video": "Erreur en sauvegardant la vidéo", - "error-field-unavailable": "{{field}} est déjà utilisé: (", - "error-file-too-large": "Le fichier est trop grand", - "error-importer-not-defined": "L'importateur n'a pas été défini correctement, il manque la classe Import.", - "error-input-is-not-a-valid-field": "{{input}} n'est pas un {{field}} valide", - "error-invalid-actionlink": "Lien d'action non valide", - "error-invalid-arguments": "Arguments non valides", - "error-invalid-asset": "Elément non valide", - "error-invalid-channel": "Canal invalide.", - "error-invalid-channel-start-with-chars": "Canal non valide. Commencez par @ ou #", - "error-invalid-custom-field": "Champ personnalisé non valide", - "error-invalid-custom-field-name": "Nom de champ personnalisé non valide. Utilisez uniquement des lettres, des chiffres, des traits d'union et des traits de soulignement.", - "error-invalid-date": "Date fournie non valide.", - "error-invalid-description": "Description invalide", - "error-invalid-domain": "Domaine invalide", - "error-invalid-email": "E-mail {{email}} invalide", - "error-invalid-email-address": "Adresse e-mail invalide", - "error-invalid-file-height": "Hauteur de fichier non valide", - "error-invalid-file-type": "Type de fichier invalide", - "error-invalid-file-width": "Largeur de fichier non valide", - "error-invalid-from-address": "Vous avez renseigné une adresse FROM invalide.", - "error-invalid-integration": "Intégration invalide", - "error-invalid-message": "Message invalide", - "error-invalid-method": "Méthode non valide", - "error-invalid-name": "Nom incorrect", - "error-invalid-password": "Mot de passe incorrect", - "error-invalid-redirectUri": "RedirectUri invalide", - "error-invalid-role": "Rôle invalide", - "error-invalid-room": "Salon invalide", - "error-invalid-room-name": "{{room_name}} n'est pas un nom de salon valide", - "error-invalid-room-type": "{{type}} n'est pas un type de salon valide.", - "error-invalid-settings": "Paramètres fournis non valides", - "error-invalid-subscription": "Abonnement invalide", - "error-invalid-token": "Jeton invalide", - "error-invalid-triggerWords": "Mots déclencheurs invalides", - "error-invalid-urls": "URL non valides", - "error-invalid-user": "Utilisateur invalide", - "error-invalid-username": "Nom d'utilisateur invalide", - "error-invalid-webhook-response": "L'URL du webhook a répondu avec un statut autre que 200", - "error-message-deleting-blocked": "La suppression du message est bloquée", - "error-message-editing-blocked": "La modification du message est bloquée", - "error-message-size-exceeded": "La taille du message dépasse Message_MaxAllowedSize", - "error-missing-unsubscribe-link": "Vous devez fournir le lien [unsubscribe].", - "error-no-owner-channel": "Vous n'êtes pas propriétaire du canal", - "error-no-tokens-for-this-user": "Il n'y a pas de jetons pour cet utilisateur", - "error-not-allowed": "Interdit", - "error-not-authorized": "Pas autorisé", - "error-push-disabled": "Push est désactivé", - "error-remove-last-owner": "C'est le dernier propriétaire. Veuillez définir un nouveau propriétaire avant de supprimer celui-ci.", - "error-role-in-use": "Impossible de supprimer le rôle car il est en cours d'utilisation", - "error-role-name-required": "Le nom du rôle est requis", - "error-the-field-is-required": "Le champ {{field}} est requis.", - "error-too-many-requests": "Erreur, trop de demandes. Ralentissez, s'il vous plaît. Vous devez attendre {{seconds}} secondes avant de réessayer.", - "error-user-is-not-activated": "L'utilisateur n'est pas activé", - "error-user-has-no-roles": "L'utilisateur n'a aucun rôle", - "error-user-limit-exceeded": "Le nombre d'utilisateurs que vous essayez d'inviter à #channel_name dépasse la limite définie par l'administrateur", - "error-user-not-in-room": "L'utilisateur n'est pas dans ce salon", - "error-user-registration-custom-field": "error-user-registration-custom-field", - "error-user-registration-disabled": "L'enregistrement de l'utilisateur est désactivé", - "error-user-registration-secret": "L'enregistrement de l'utilisateur n'est autorisé que via l'URL secrète", - "error-you-are-last-owner": "Vous êtes le dernier propriétaire. Veuillez définir un nouveau propriétaire avant de quitter le salon.", - "error-status-not-allowed": "Le statut invisible est désactivé", - "Actions": "Actions", - "activity": "activité", - "Activity": "Activité", - "Add_Reaction": "Ajouter une réaction", - "Add_Server": "Ajouter un serveur", - "Add_users": "Ajouter des utilisateurs", - "Admin_Panel": "Panneau d'administration", - "Agent": "Agent", - "Alert": "Alerte", - "alert": "alerte", - "alerts": "alertes", - "All_users_in_the_channel_can_write_new_messages": "Tous les utilisateurs du canal peuvent écrire de nouveaux messages", - "All_users_in_the_team_can_write_new_messages": "Tous les utilisateurs de l'équipe peuvent écrire de nouveaux messages", - "A_meaningful_name_for_the_discussion_room": "Un nom significatif pour le salon de discussion", - "All": "Tout", - "All_Messages": "Tous les messages", - "Allow_Reactions": "Autoriser les réactions", - "Alphabetical": "Alphabétique", - "and_more": "et plus", - "and": "et", - "announcement": "annonce", - "Announcement": "Annonce", - "Apply_Your_Certificate": "Appliquer votre certificat", - "ARCHIVE": "ARCHIVER", - "archive": "archiver", - "are_typing": "sont en train d'écrire", - "Are_you_sure_question_mark": "Êtes-vous sûr ?", - "Are_you_sure_you_want_to_leave_the_room": "Êtes-vous sûr de vouloir quitter le salon {{room}} ?", - "Audio": "Audio", - "Authenticating": "Authentification", - "Automatic": "Automatique", - "Auto_Translate": "Traduction automatique", - "Avatar_changed_successfully": "Avatar changé avec succès !", - "Avatar_Url": "URL de l'avatar", - "Away": "Absent", - "Back": "Retour", - "Black": "Noir", - "Block_user": "Bloquer l'utilisateur", - "Browser": "Navigateur", - "Broadcast_channel_Description": "Seuls les utilisateurs autorisés peuvent écrire de nouveaux messages, mais les autres utilisateurs pourront répondre.", - "Broadcast_Channel": "Canal de diffusion", - "Busy": "Occupé", - "By_proceeding_you_are_agreeing": "En poursuivant, vous acceptez nos", - "Cancel_editing": "Annuler la modification", - "Cancel_recording": "Annuler l'enregistrement", - "Cancel": "Annuler", - "changing_avatar": "changer d'avatar", - "creating_channel": "création d'un canal", - "creating_invite": "création d'une invitation", - "Channel_Name": "Nom du canal", - "Channels": "Canaux", - "Chats": "Chats", - "Call_already_ended": "Appel déjà terminé !", - "Clear_cookies_alert": "Voulez-vous effacer tous les cookies ?", - "Clear_cookies_desc": "Cette action effacera tous les cookies de connexion ce qui vous permettra de vous connecter à d'autres comptes.", - "Clear_cookies_yes": "Oui, effacez les cookies", - "Clear_cookies_no": "Non, gardez les cookies", - "Click_to_join": "Cliquez pour rejoindre !", - "Close": "Fermer", - "Close_emoji_selector": "Fermer le sélecteur d'émoji", - "Closing_chat": "Fermeture du chat", - "Change_language_loading": "Changement de la langue.", - "Chat_closed_by_agent": "Chat fermé par l'agent", - "Choose": "Choisir", - "Choose_from_library": "Choisissez dans la bibliothèque", - "Choose_file": "Choisir le fichier", - "Choose_where_you_want_links_be_opened": "Choisissez oµ vous souhaitez ouvrir les liens", - "Code": "Code", - "Code_or_password_invalid": "Code ou mot de passe invalide", - "Collaborative": "Collaboratif", - "Confirm": "Confirmer", - "Connect": "Connecter", - "Connected": "Connecté", - "connecting_server": "connexion en cours au serveur", - "Connecting": "Connexion...", - "Contact_us": "Contactez-nous", - "Contact_your_server_admin": "Contactez votre administrateur de serveur.", - "Continue_with": "Continuer avec", - "Copied_to_clipboard": "Copié dans le presse-papier !", - "Copy": "Copier", - "Conversation": "Conversation", - "Permalink": "Lien permanent", - "Certificate_password": "Mot de passe du certificat", - "Clear_cache": "Effacer le cache du serveur local", - "Clear_cache_loading": "Effacement du cache.", - "Whats_the_password_for_your_certificate": "Quel est le mot de passe de votre certificat ?", - "Create_account": "Créer un compte", - "Create_Channel": "Créer un canal", - "Create_Direct_Messages": "Créer des messages directs", - "Create_Discussion": "Créer une discussion", - "Created_snippet": "créé un extrait", - "Create_a_new_workspace": "Créer un nouvel espace de travail", - "Create": "Créer", - "Custom_Status": "Statut personnalisé", - "Dark": "Sombre", - "Dark_level": "Niveau d'obscurité", - "Default": "Défaut", - "Default_browser": "Navigateur par défaut", - "Delete_Room_Warning": "Supprimer une salon supprimera tous les messages publiés dans le salon. Ça ne peut pas être annulé.", - "Department": "Département", - "delete": "supprimer", - "Delete": "Supprimer", - "DELETE": "SUPPRIMER", - "move": "déplacer", - "deleting_room": "suppression du salon", - "description": "la description", - "Description": "La description", - "Desktop_Options": "Options de bureau", - "Desktop_Notifications": "Notifications de bureau", - "Desktop_Alert_info": "Ces notifications sont transmises sur le bureau", - "Directory": "Répertoire", - "Direct_Messages": "Messages directs", - "Disable_notifications": "Désactiver les notifications", - "Discussions": "Discussions", - "Discussion_Desc": "Aide à garder une vue d'ensemble sur ce qui se passe ! En créant une discussion, un sous-canal de celui que vous avez sélectionné est créé et les deux sont liés.", - "Discussion_name": "Nom de la discussion", - "Done": "Fait", - "Dont_Have_An_Account": "Vous n'avez pas de compte ?", - "Do_you_have_an_account": "Avez-vous un compte ?", - "Do_you_have_a_certificate": "Avez-vous un certificat ?", - "Do_you_really_want_to_key_this_room_question_mark": "Voulez-vous vraiment {{key}} ce salon ?", - "E2E_Encryption": "Cryptage E2E", - "E2E_How_It_Works_info1": "Vous pouvez désormais créer des groupes privés et des messages directs chiffrés. Vous pouvez également modifier les groupes privés ou DM existants pour les crypter.", - "E2E_How_It_Works_info2": "Il s'agit du *chiffrement de bout en bout*, la clé permettant de coder/décoder vos messages ne sera pas enregistrée sur le serveur. C'est pourquoi *vous devez stocker ce mot de passe à un endroit sûr* auquel vous pourrez accéder plus tard si vous en avez besoin.", - "E2E_How_It_Works_info3": "Si vous continuez, un mot de passe E2E sera automatiquement généré.", - "E2E_How_It_Works_info4": "Vous pouvez également configurer un nouveau mot de passe pour votre clé de cryptage à tout moment à partir de n'importe quel navigateur dans lequel vous avez entré le mot de passe E2E existant.", - "edit": "modifier", - "edited": "modifié", - "Edit": "Modifier", - "Edit_Status": "Modifier le statut", - "Edit_Invite": "Modifier l'invitation", - "End_to_end_encrypted_room": "Salon crypté de bout en bout", - "end_to_end_encryption": "chiffrement de bout en bout", - "Email_Notification_Mode_All": "Chaque mention/MD", - "Email_Notification_Mode_Disabled": "Désactivé", - "Email_or_password_field_is_empty": "Le champ e-mail ou mot de passe est vide", - "Email": "E-mail", - "email": "e-mail", - "Empty_title": "Titre vide", - "Enable_Auto_Translate": "Activer la traduction automatique", - "Enable_notifications": "Activer les notifications", - "Encrypted": "Crypté", - "Encrypted_message": "Message crypté", - "Enter_Your_E2E_Password": "Entrez votre mot de passe E2E", - "Enter_Your_Encryption_Password_desc1": "Cela vous permettra d'accéder à vos groupes privés cryptés et à vos messages directs.", - "Enter_Your_Encryption_Password_desc2": "Vous devez entrer le mot de passe pour coder/décoder les messages à chaque endroit où vous utilisez le chat.", - "Encryption_error_title": "Votre mot de passe de cryptage semble erroné", - "Encryption_error_desc": "Il n'a pas été possible de décoder votre clé de cryptage pour être importé.", - "Everyone_can_access_this_channel": "Tout le monde peut accéder à ce canal", - "Everyone_can_access_this_team": "Tout le monde peut accéder à cette équipe", - "Error_uploading": "Erreur lors de l'envoi", - "Expiration_Days": "Expiration (Jours)", - "Favorite": "Favori", - "Favorites": "Favoris", - "Files": "Fichiers", - "File_description": "Description du fichier", - "File_name": "Nom de fichier", - "Finish_recording": "Terminer l'enregistrement", - "Following_thread": "Suivre le fil", - "For_your_security_you_must_enter_your_current_password_to_continue": "Pour votre sécurité, vous devez entrer votre mot de passe actuel pour continuer.", - "Forgot_password_If_this_email_is_registered": "Si cet e-mail est enregistré, nous vous enverrons des instructions pour réinitialiser votre mot de passe. Si vous ne recevez pas d'e-mail sous peu, veuillez revenir et réessayer.", - "Forgot_password": "Mot de passe oublié ?", - "Forgot_Password": "Mot de passe oublié", - "Forward": "Transmettre", - "Forward_Chat": "Transmettre la conversation", - "Forward_to_department": "Transmettre au département", - "Forward_to_user": "Transmettre à l'utilisateur", - "Full_table": "Cliquez pour voir le tableau complet", - "Generate_New_Link": "Générer un nouveau lien", - "Group_by_favorites": "Grouper par favoris", - "Group_by_type": "Grouper par type", - "Hide": "Cacher", - "Has_joined_the_channel": "a rejoint le canal", - "Has_joined_the_conversation": "a rejoint la conversation", - "Has_left_the_channel": "a quitté le canal", - "Hide_System_Messages": "Masquer les messages système", - "Hide_type_messages": "Masquer les messages \"{{type}}\"", - "How_It_Works": "Comment cela fonctionne", - "Message_HideType_uj": "L'utilisateur a rejoint", - "Message_HideType_ul": "L'utilisateur est parti", - "Message_HideType_ru": "Utilisateur supprimé", - "Message_HideType_au": "Utilisateur ajouté", - "Message_HideType_mute_unmute": "Utilisateur rendu muet / a retrouvé la parole", - "Message_HideType_r": "Nom du salon modifié", - "Message_HideType_ut": "L'utilisateur a rejoint la conversation", - "Message_HideType_wm": "Bienvenue", - "Message_HideType_rm": "Message supprimé", - "Message_HideType_subscription_role_added": "Rôle assigné", - "Message_HideType_subscription_role_removed": "Le rôle n'est plus défini", - "Message_HideType_room_archived": "Salon archivé", - "Message_HideType_room_unarchived": "Salon désarchivé", - "I_Saved_My_E2E_Password": "J'ai enregistré mon mot de passe E2E", - "IP": "IP", - "In_app": "Dans l'app", - "In_App_And_Desktop": "Dans l'application et sur le bureau", - "In_App_and_Desktop_Alert_info": "Affiche une bannière en haut de l'écran lorsque l'application est ouverte et affiche une notification sur le bureau", - "Invisible": "Invisible", - "Invite": "Inviter", - "is_a_valid_RocketChat_instance": "est une instance valide de Rocket.Chat", - "is_not_a_valid_RocketChat_instance": "n'est pas une instance valide de Rocket.Chat", - "is_typing": "est en train d'écrire", - "Invalid_or_expired_invite_token": "Jeton d'invitation non valide ou expiré", - "Invalid_server_version": "Le serveur auquel vous essayez de vous connecter utilise une version qui n'est plus prise en charge par l'application : {{currentVersion}}.\n\nNous exigeons la version {{minVersion}}", - "Invite_Link": "Lien d'invitation", - "Invite_users": "Inviter des utilisateurs", - "Join": "Rejoindre", - "Join_Code": "Code d'adhésion", - "Insert_Join_Code": "Insérer le code d'adhésion", - "Join_our_open_workspace": "Rejoignez notre espace de travail ouvert", - "Join_your_workspace": "Rejoignez votre espace de travail", - "Just_invited_people_can_access_this_channel": "Seuls les personnes invitées peuvent accéder à ce canal", - "Just_invited_people_can_access_this_team": "Seules les personnes invitées peuvent accéder à cette équipe", - "Language": "Langue", - "last_message": "dernier message", - "Leave_channel": "Quitter le canal", - "leaving_room": "quittant le salon", - "Leave": "Quitter", - "leave": "quitter", - "Legal": "Légal", - "Light": "Clair", - "License": "Licence", - "Livechat": "Chat en direct", - "Livechat_edit": "Modifier le chat en direct", - "Login": "Connexion", - "Login_error": "Vos identifiants ont été rejetés ! Veuillez réessayer.", - "Login_with": "Se connecter avec", - "Logging_out": "Déconnexion.", - "Logout": "Se déconnecter", - "Max_number_of_uses": "Nombre maximum d'utilisations", - "Max_number_of_users_allowed_is_number": "Le nombre maximum d'utilisateurs autorisés est {{maxUsers}}", - "members": "membres", - "Members": "Membres", - "Mentioned_Messages": "Messages mentionnés", - "mentioned": "mentionné", - "Mentions": "Mentions", - "Message_accessibility": "Message de {{user}} à {{time}} : {{message}}", - "Message_actions": "Actions de message", - "Message_pinned": "Message épinglé", - "Message_removed": "Message supprimé", - "Message_starred": "Message suivi", - "Message_unstarred": "Message non suivi", - "message": "message", - "messages": "messages", - "Message": "Message", - "Messages": "Messages", - "Message_Reported": "Message signalé", - "Microphone_Permission_Message": "Rocket.Chat a besoin d'accéder à votre microphone pour que vous puissiez envoyer un message audio.", - "Microphone_Permission": "Permission de microphone", - "Mute": "Rendre muet", - "muted": "muet", - "My_servers": "Mes serveurs", - "N_people_reacted": "{{n}} personnes ont réagi", - "N_users": "{{n}} utilisateurs", - "N_channels": "{{n}} canaux", - "name": "nom", - "Name": "Nom", - "Navigation_history": "Historique de navigation", - "Never": "Jamais", - "New_Message": "Nouveau message", - "New_Password": "Nouveau mot de passe", - "New_Server": "Nouveau serveur", - "Next": "Suivant", - "No_files": "Aucun fichier", - "No_limit": "Pas de limites", - "No_mentioned_messages": "Aucun message mentionné", - "No_pinned_messages": "Aucun message épinglé", - "No_results_found": "Aucun résultat trouvé", - "No_starred_messages": "Aucun message suivi", - "No_thread_messages": "Aucun message de fil de discussion", - "No_label_provided": "Aucun {{label}} fourni.", - "No_Message": "Aucun message", - "No_messages_yet": "Pas encore de messages", - "No_Reactions": "Aucune réaction", - "No_Read_Receipts": "Aucun accusé de lecture", - "Not_logged": "Non connecté", - "Not_RC_Server": "Ce n'est pas un serveur Rocket.Chat.\n{{contact}}", - "Nothing": "Rien", - "Nothing_to_save": "Rien à enregistrer !", - "Notify_active_in_this_room": "Notifier les utilisateurs actifs dans ce salon", - "Notify_all_in_this_room": "Avertir tout le monde dans ce salon", - "Notifications": "Notifications", - "Notification_Duration": "Durée des notifications", - "Notification_Preferences": "Préférences de notification", - "No_available_agents_to_transfer": "Aucun agent disponible pour le transfert", - "Offline": "Hors ligne", - "Oops": "Oups !", - "Omnichannel": "Omnicanal", - "Open_Livechats": "Discussions en cours", - "Omnichannel_enable_alert": "Vous n'êtes pas disponible sur Omnicanal. Souhaitez-vous être disponible ?", - "Onboarding_description": "Un espace de travail est l'espace de collaboration de votre équipe ou organisation. Demandez à l'administrateur de l'espace de travail l'adresse pour rejoindre ou créez-en une pour votre équipe.", - "Onboarding_join_workspace": "Rejoindre un espace de travail", - "Onboarding_subtitle": "Au-delà de la collaboration d'équipe", - "Onboarding_title": "Bienvenue sur Rocket.Chat", - "Onboarding_join_open_description": "Rejoignez notre espace de travail ouvert pour discuter avec l'équipe et la communauté Rocket.Chat.", - "Onboarding_agree_terms": "En continuant, vous acceptez Rocket.Chat", - "Onboarding_less_options": "Moins d'options", - "Onboarding_more_options": "Plus d'options", - "Online": "En ligne", - "Only_authorized_users_can_write_new_messages": "Seuls les utilisateurs autorisés peuvent écrire de nouveaux messages.", - "Open_emoji_selector": "Ouvrir le sélecteur d'émoji", - "Open_Source_Communication": "Communication Open Source", - "Open_your_authentication_app_and_enter_the_code": "Ouvrez votre application d'authentification et entrez le code.", - "OR": "OU", - "OS": "OS", - "Overwrites_the_server_configuration_and_use_room_config": "Écrase la configuration du serveur et utilise la configuration du salon", - "Password": "Mot de passe", - "Parent_channel_or_group": "Canal ou groupe parent", - "Permalink_copied_to_clipboard": "Lien permanent copié dans le presse-papiers !", - "Phone": "Téléphone", - "Pin": "Épingler", - "Pinned_Messages": "Messages épinglés", - "pinned": "épinglé", - "Pinned": "Épinglé", - "Please_add_a_comment": "Veuillez ajouter un commentaire", - "Please_enter_your_password": "Veuillez entrer votre mot de passe", - "Please_wait": "Veuillez patienter.", - "Preferences": "Préférences", - "Preferences_saved": "Préférences sauvegardées !", - "Privacy_Policy": " Politique de confidentialité", - "Private_Channel": "Canal privé", - "Private": "Privé", - "Processing": "Traitement...", - "Profile_saved_successfully": "Profil enregistré avec succès !", - "Profile": "Profil", - "Public_Channel": "Canal public", - "Public": "Public", - "Push_Notifications": "Notifications Push", - "Push_Notifications_Alert_Info": "Ces notifications vous sont envoyées lorsque l'application n'est pas ouverte", - "Quote": "Citation", - "Reactions_are_disabled": "Les réactions sont désactivées", - "Reactions_are_enabled": "Les réactions sont activées", - "Reactions": "Réactions", - "Read": "Lecture", - "Read_External_Permission_Message": "Rocket.Chat doit accéder aux photos, aux médias et aux fichiers sur votre appareil", - "Read_External_Permission": "Permission de lecture des fichiers", - "Read_Only_Channel": "Canal en lecture seule", - "Read_Only": "Lecture seule", - "Read_Receipt": "Accusé de réception", - "Receive_Group_Mentions": "Recevoir des mentions de groupe", - "Receive_Group_Mentions_Info": "Recevoir des mentions @all et @here", - "Register": "S'inscrire", - "Repeat_Password": "Répéter le mot de passe", - "Replied_on": "A répondu le :", - "replies": "réponses", - "reply": "répondre", - "Reply": "Répondre", - "Report": "Signaler", - "Receive_Notification": "Recevoir une notification", - "Receive_notifications_from": "Recevoir des notifications de {{name}}", - "Resend": "Renvoyer", - "Reset_password": "Réinitialiser le mot de passe", - "resetting_password": "réinitialisation du mot de passe", - "RESET": "RÉINITIALISER", - "Return": "Retour", - "Review_app_title": "Appréciez-vous cette application ?", - "Review_app_desc": "Donnez-nous 5 étoiles sur {{store}}", - "Review_app_yes": "Bien sûr !", - "Review_app_no": "Non", - "Review_app_later": "Peut-être plus tard", - "Review_app_unable_store": "Impossible d'ouvrir {{store}}", - "Review_this_app": "Donnez votre avis sur cette application", - "Remove": "Supprimer", - "remove": "supprimer", - "Roles": "Rôles", - "Room_actions": "Actions du salon", - "Room_changed_announcement": "Annonce du salon changé en : {{announcement}} par {{userBy}}", - "Room_changed_avatar": "Avatar du salon modifié par {{userBy}}", - "Room_changed_description": "Description du salon changé en : {{description}} par {{userBy}}", - "Room_changed_privacy": "Type de salon changé en : {{type}} par {{userBy}}", - "Room_changed_topic": "Le sujet de salon est changé en : {{topic}} par {{userBy}}", - "Room_Files": "Fichiers du salon", - "Room_Info_Edit": "Modifier les informations du salon", - "Room_Info": "Info sur le salon", - "Room_Members": "Membres du salon", - "Room_name_changed": "Nom de salon changé en : {{name}} par {{userBy}}", - "SAVE": "SAUVEGARDER", - "Save_Changes": "Sauvegarder les modifications", - "Save": "Sauvegarder", - "Saved": "Enregistré", - "saving_preferences": "enregistrement des préférences", - "saving_profile": "enregistrement du profil", - "saving_settings": "enregistrement des paramètres", - "saved_to_gallery": "Enregistré dans la galerie", - "Save_Your_E2E_Password": "Enregistrez votre mot de passe E2E", - "Save_Your_Encryption_Password": "Enregistrez votre mot de passe de cryptage", - "Save_Your_Encryption_Password_warning": "Ce mot de passe n'est stocké nulle part, enregistrez-le donc soigneusement ailleurs.", - "Save_Your_Encryption_Password_info": "Si vous perdez le mot de passe, il n'y a aucun moyen de le récupérer et vous perdrez l'accès à vos messages.", - "Search_Messages": "Rechercher des messages", - "Search": "Recherche", - "Search_by": "Rechercher par", - "Search_global_users": "Rechercher des utilisateurs mondiaux", - "Search_global_users_description": "Si vous activez, vous pouvez rechercher n'importe quel utilisateur d'autres sociétés ou serveurs.", - "Seconds": "{{second}} secondes", - "Security_and_privacy": "Sécurité et vie privée", - "Select_Avatar": "Sélectionnez un avatar", - "Select_Server": "Sélectionnez un serveur", - "Select_Users": "Sélectionner les utilisateurs", - "Select_a_Channel": "Sélectionnez un canal", - "Select_a_Department": "Sélectionnez un département", - "Select_an_option": "Sélectionnez une option", - "Select_a_User": "Sélectionnez un utilisateur", - "Send": "Envoyer", - "Send_audio_message": "Envoyer un message audio", - "Send_crash_report": "Envoyer un rapport de plantage", - "Send_message": "Envoyer un message", - "Send_me_the_code_again": "Envoyez-moi à nouveau le code", - "Send_to": "Envoyer à...", - "Sending_to": "Envoi à", - "Sent_an_attachment": "Envoyé une pièce jointe", - "Server": "Serveur", - "Servers": "Serveurs", - "Server_version": "Version du serveur : {{version}}", - "Set_username_subtitle": "Le nom d'utilisateur est utilisé pour permettre aux autres de vous mentionner dans les messages", - "Set_custom_status": "Définir le statut personnalisé", - "Set_status": "Définir le statut", - "Status_saved_successfully": "Statut enregistré avec succès !", - "Settings": "Paramètres", - "Settings_succesfully_changed": "Paramètres modifiés avec succès !", - "Share": "Partager", - "Share_Link": "Partager le lien", - "Share_this_app": "Partager cette application", - "Show_more": "Afficher plus..", - "Show_Unread_Counter": "Afficher le compteur non lu", - "Show_Unread_Counter_Info": "Le compteur non lu est affiché sous forme de badge à droite du canal, dans la liste", - "Sign_in_your_server": "Connectez-vous à votre serveur", - "Sign_Up": "S'inscrire", - "Some_field_is_invalid_or_empty": "Certains champs sont invalides ou vides", - "Sorting_by": "Tri par {{key}}", - "Sound": "Son", - "Star_room": "Canal favoris", - "Star": "Mettre en favoris", - "Starred_Messages": "Les messages favoris", - "starred": "favoris", - "Starred": "Favoris", - "Start_of_conversation": "Début de conversation", - "Start_a_Discussion": "Lancer une discussion", - "Started_discussion": "A commencé une discussion :", - "Started_call": "Appel lancé par {{userBy}}", - "Submit": "Soumettre", - "Table": "Tableau", - "Tags": "Mots clés", - "Take_a_photo": "Prendre une photo", - "Take_a_video": "Prendre une vidéo", - "Take_it": "Prends-le !", - "tap_to_change_status": "appuyez pour changer de statut", - "Tap_to_view_servers_list": "Appuyez pour afficher la liste des serveurs", - "Terms_of_Service": " Conditions d'utilisation ", - "Theme": "Thème", - "The_user_wont_be_able_to_type_in_roomName": "L'utilisateur ne pourra pas écrire dans {{roomName}}", - "The_user_will_be_able_to_type_in_roomName": "L'utilisateur pourra écrire dans {{roomName}}", - "There_was_an_error_while_action": "Une erreur s'est produite lors de {{action}} !", - "This_room_is_blocked": "Ce salon est bloqué", - "This_room_is_read_only": "Ce salon est en lecture seule", - "Thread": "Fil de discussion", - "Threads": "Fils de discussions", - "Timezone": "Fuseau horaire", - "To": "A", - "topic": "sujet", - "Topic": "Sujet", - "Translate": "Traduire", - "Try_again": "Réessayer", - "Two_Factor_Authentication": "Authentification à deux facteurs", - "Type_the_channel_name_here": "Tapez le nom de canal ici", - "unarchive": "désarchiver", - "UNARCHIVE": "DÉSARCHIVER", - "Unblock_user": "Débloquer l'utilisateur", - "Unfavorite": "Supprimer des favoris", - "Unfollowed_thread": "Ne plus suivre ce fil", - "Unmute": "Rendre la parole", - "unmuted": "rendu la parole", - "Unpin": "Détacher", - "unread_messages": "non lu", - "Unread": "Non lu", - "Unread_on_top": "Non lu en haut", - "Unstar": "Enlever des favoris", - "Updating": "Mise à jour...", - "Uploading": "Envoyer", - "Upload_file_question_mark": "Téléverser un fichier ?", - "User": "Utilisateur", - "Users": "Utilisateurs", - "User_added_by": "Utilisateur {{userAdded}} ajouté par {{userBy}}", - "User_Info": "Info d'utilisateur", - "User_has_been_key": "L'utilisateur a été {{key}}", - "User_is_no_longer_role_by_": "{{user}} n'est plus {{role}} par {{userBy}}", - "User_muted_by": "L'utilisateur {{userMuted}} a été rendu muet par {{userBy}}", - "User_removed_by": "Utilisateur {{userRemoved}} supprimé par {{userBy}}", - "User_sent_an_attachment": "{{user}} a envoyé une pièce jointe", - "User_unmuted_by": "L'utilisateur {{userBy}} a rendu la parole à {{userUnmuted}}", - "User_was_set_role_by_": "{{user}} a été défini {{role}} par {{userBy}}", - "Username_is_empty": "Nom d'utilisateur est vide", - "Username": "Nom d'utilisateur", - "Username_or_email": "Nom d'utilisateur ou e-mail", - "Uses_server_configuration": "Utilise la configuration du serveur", - "Validating": "Validation", - "Registration_Succeeded": "Inscription réussie !", - "Verify": "Vérifier", - "Verify_email_title": "Inscription réussie !", - "Verify_email_desc": "Nous vous avons envoyé un e-mail pour confirmer votre inscription. Si vous ne recevez pas d'e-mail sous peu, veuillez revenir et réessayer.", - "Verify_your_email_for_the_code_we_sent": "Vérifiez votre e-mail pour le code que nous avons envoyé", - "Video_call": "Appel vidéo", - "View_Original": "Voir l'original", - "Voice_call": "Appel vocal", - "Waiting_for_network": "En attente du réseau...", - "Websocket_disabled": "Le Websocket est désactivé pour ce serveur.\n{{contact}}", - "Welcome": "Bienvenue", - "What_are_you_doing_right_now": "Que fais-tu en ce moment ?", - "Whats_your_2fa": "Quel est votre code 2FA ?", - "Without_Servers": "Sans serveurs", - "Workspaces": "Espaces de travail", - "Would_you_like_to_return_the_inquiry": "Souhaitez-vous retourner la demande ?", - "Write_External_Permission_Message": "Rocket.Chat a besoin d'accéder à votre galerie pour que vous puissiez enregistrer des images.", - "Write_External_Permission": "Autorisation de la galerie", - "Yes": "Oui", - "Yes_action_it": "Oui, {{action}} le !", - "Yesterday": "Hier", - "You_are_in_preview_mode": "Vous êtes en mode aperçu", - "You_are_offline": "Vous êtes hors ligne", - "You_can_search_using_RegExp_eg": "Vous pouvez utiliser RegExp., par exemple `/^texte$/i`", - "You_colon": "Vous: ", - "you_were_mentioned": "vous avez été mentionné", - "You_were_removed_from_channel": "Vous avez été retiré de {{channel}}", - "you": "vous", - "You": "Vous", - "Logged_out_by_server": "Vous avez été déconnecté du serveur. Veuillez vous reconnecter.", - "You_need_to_access_at_least_one_RocketChat_server_to_share_something": "Vous devez accéder à au moins un serveur Rocket.Chat pour partager quelque chose.", - "You_need_to_verifiy_your_email_address_to_get_notications": "Vous devez vérifier votre adresse e-mail pour recevoir des notifications", - "Your_certificate": "Votre certificat", - "Your_invite_link_will_expire_after__usesLeft__uses": "Votre lien d'invitation expirera après {{usesLeft}} utilisations.", - "Your_invite_link_will_expire_on__date__or_after__usesLeft__uses": "Votre lien d'invitation expirera le {{date}} ou après {{usesLeft}} utilisations.", - "Your_invite_link_will_expire_on__date__": "Votre lien d'invitation expirera le {{date}}.", - "Your_invite_link_will_never_expire": "Votre lien d'invitation n'expirera jamais.", - "Your_workspace": "Votre espace de travail", - "Your_password_is": "Votre mot de passe est", - "Version_no": "Version : {{version}}", - "You_will_not_be_able_to_recover_this_message": "Vous ne pourrez pas récupérer ce message !", - "You_will_unset_a_certificate_for_this_server": "Vous allez supprimer un certificat pour ce serveur", - "Change_Language": "Changer la langue", - "Crash_report_disclaimer": "Nous ne suivons jamais le contenu de vos chats. Le rapport d'incident et les évènements d'analyse ne contiennent que des informations pertinentes pour nous afin d'identifier et de résoudre les problèmes.", - "Type_message": "Tapez le message", - "Room_search": "Recherche de salons", - "Room_selection": "Sélection de salon 1...9", - "Next_room": "Salon suivant", - "Previous_room": "Salon précédent", - "New_room": "Nouveau salon", - "Upload_room": "Envoyer dans un salon", - "Search_messages": "Rechercher des messages", - "Scroll_messages": "Faire défiler les messages", - "Reply_latest": "Répondre au dernier", - "Reply_in_Thread": "Répondre dans le fil", - "Server_selection": "Sélection du serveur", - "Server_selection_numbers": "Sélection du serveur 1...9", - "Add_server": "Ajouter un serveur", - "New_line": "Nouvelle ligne", - "You_will_be_logged_out_of_this_application": "Vous serez déconnecté de cette application.", - "Clear": "Effacer", - "This_will_clear_all_your_offline_data": "Cela effacera toutes vos données hors ligne.", - "This_will_remove_all_data_from_this_server": "Cela supprimera toutes les données de ce serveur.", - "Mark_unread": "Marquer comme non lu", - "Wait_activation_warning": "Avant de pouvoir vous connecter, votre compte doit être activé manuellement par un administrateur.", - "Screen_lock": "Verrouillage d'écran", - "Local_authentication_biometry_title": "Authentifier", - "Local_authentication_biometry_fallback": "Utiliser le code d'accès", - "Local_authentication_unlock_option": "Déverrouiller avec le code d'accès", - "Local_authentication_change_passcode": "Changer le code d'accès", - "Local_authentication_info": "Remarque : si vous oubliez le code d'accès, vous devrez supprimer et réinstaller l'application.", - "Local_authentication_facial_recognition": "reconnaissance faciale", - "Local_authentication_fingerprint": "empreinte digitale", - "Local_authentication_unlock_with_label": "Déverrouiller avec {{label}}", - "Local_authentication_auto_lock_60": "Après 1 minute", - "Local_authentication_auto_lock_300": "Après 5 minutes", - "Local_authentication_auto_lock_900": "Après 15 minutes", - "Local_authentication_auto_lock_1800": "Après 30 minutes", - "Local_authentication_auto_lock_3600": "Après 1 heure", - "Passcode_enter_title": "Entrez votre code d'accès", - "Passcode_choose_title": "Choisissez votre nouveau code d'accès", - "Passcode_choose_confirm_title": "Confirmez votre nouveau code d'accès", - "Passcode_choose_error": "Les codes d'accès ne correspondent pas. Réessayer.", - "Passcode_choose_force_set": "Code d'accès requis par l'administrateur", - "Passcode_app_locked_title": "App verrouillée", - "Passcode_app_locked_subtitle": "Réessayez dans {{timeLeft}} secondes", - "After_seconds_set_by_admin": "Après {{seconds}} secondes (défini par l'administrateur)", - "Dont_activate": "Ne pas activer maintenant", - "Queued_chats": "Discussions en file d'attente", - "Queue_is_empty": "La file d'attente est vide", - "Logout_from_other_logged_in_locations": "Déconnexion des autres emplacements connectés", - "You_will_be_logged_out_from_other_locations": "Vous serez déconnecté des autres emplacements.", - "Logged_out_of_other_clients_successfully": "Déconnexion réussie des autres clients", - "Logout_failed": "Echec de la déconnexion !", - "Log_analytics_events": "Journal des événements d'analyse", - "E2E_encryption_change_password_title": "Changer le mot de passe de cryptage", - "E2E_encryption_change_password_description": "Vous pouvez désormais créer des groupes privés et des messages directs chiffrés. Vous pouvez également modifier les groupes privés ou DM existants pour les crypter.\nIl s'agit du chiffrement de bout en bout, la clé permettant de coder/décoder vos messages ne sera pas enregistrée sur le serveur. Pour cette raison, vous devez stocker ce mot de passe à un endroit sûr. Vous devrez le saisir sur les autres appareils sur lesquels vous souhaitez utiliser le cryptage E2E.", - "E2E_encryption_change_password_error": "Erreur lors de la modification du mot de passe de la clé E2E", - "E2E_encryption_change_password_success": "Le mot de passe de la clé E2E a été changé avec succès !", - "E2E_encryption_change_password_message": "Assurez-vous de l'avoir enregistré soigneusement ailleurs.", - "E2E_encryption_change_password_confirmation": "Oui, changez-le", - "E2E_encryption_reset_title": "Réinitialiser la clé E2E", - "E2E_encryption_reset_description": "Cette option supprimera la clé E2E actuelle et vous déconnectera.\nLorsque vous vous reconnecterez, Rocket.Chat générera une nouvelle clé et restaurera votre accès aux salons cryptés qui a un ou plusieurs membres en ligne.\nEn raison de la nature du cryptage E2E, Rocket.Chat ne pourra pas restaurer l'accès à un salon crypté qui n'a aucun membre en ligne.", - "E2E_encryption_reset_button": "Réinitialiser la clé E2E", - "E2E_encryption_reset_error": "Erreur lors de la réinitialisation de la clé E2E !", - "E2E_encryption_reset_message": "Vous allez être déconnecté.", - "E2E_encryption_reset_confirmation": "Oui, réinitialisez-le", - "Following": "Suivant", - "Threads_displaying_all": "Tout afficher", - "Threads_displaying_following": "Affichage suivant", - "Threads_displaying_unread": "Affichage non lu", - "No_threads": "Il n'y a pas de fils", - "No_threads_following": "Vous ne suivez aucun fil de discussion", - "No_threads_unread": "Il n'y a pas de fils non lus", - "Messagebox_Send_to_channel": "Envoyer au canal", - "Leader": "Leader", - "Moderator": "Modérateur", - "Owner": "Propriétaire", - "Remove_from_room": "Retirer du salon", - "Ignore": "Ignorer", - "Unignore": "Ne pas ignorer", - "User_has_been_ignored": "L'utilisateur a été ignoré", - "User_has_been_unignored": "L'utilisateur n'est plus ignoré", - "User_has_been_removed_from_s": "L'utilisateur a été retiré de {{s}}", - "User__username__is_now_a_leader_of__room_name_": "L'utilisateur {{username}} est désormais un leader de {{room_name}}", - "User__username__is_now_a_moderator_of__room_name_": "L'utilisateur {{username}} est désormais un modérateur de {{room_name}}", - "User__username__is_now_a_owner_of__room_name_": "L'utilisateur {{username}} est désormais un propriétaire de {{room_name}}", - "User__username__removed_from__room_name__leaders": "L'utilisateur {{username}} a été supprimé des leaders de {{room_name}}", - "User__username__removed_from__room_name__moderators": "L'utilisateur {{username}} a été supprimé des modérateurs de {{room_name}}", - "User__username__removed_from__room_name__owners": "L'utilisateur {{username}} a été supprimé des propriétaires de {{room_name}}", - "The_user_will_be_removed_from_s": "L'utilisateur sera supprimé de {{s}}", - "Yes_remove_user": "Oui, supprimez l'utilisateur !", - "Direct_message": "Message direct", - "Message_Ignored": "Message ignoré. Touchez pour l'afficher.", - "Enter_workspace_URL": "Entrez l'URL de l'espace de travail", - "Workspace_URL_Example": "Ex. votre-société.rocket.chat", - "This_room_encryption_has_been_enabled_by__username_": "Le cryptage de ce salon a été activé par {{username}}", - "This_room_encryption_has_been_disabled_by__username_": "Le cryptage de ce salon a été désactivé par {{username}}", - "Teams": "Equipes", - "No_team_channels_found": "Aucun canal trouvé", - "Team_not_found": "Equipe non trouvée", - "Create_Team": "Créer une équipe", - "Team_Name": "Nom de l'équipe", - "Private_Team": "Equipe privée", - "Read_Only_Team": "Equipe en lecture seule", - "Broadcast_Team": "Equipe de diffusion", - "creating_team": "création de l'équipe", - "team-name-already-exists": "Une équipe portant ce nom existe déjà", - "Add_Channel_to_Team": "Ajouter un canal à l'équipe", - "Left_The_Team_Successfully": "A quitté l'équipe avec succès", - "Create_New": "Créer un nouveau", - "Add_Existing": "Ajouter existant", - "Add_Existing_Channel": "Ajouter un canal existant", - "Remove_from_Team": "Retirer de l'équipe", - "Auto-join": "Rejoindre automatiquement", - "Remove_Team_Room_Warning": "Souhaitez-vous supprimer ce canal de l'équipe ? Le canal sera déplacé vers l'espace de travail", - "Confirmation": "Confirmation", - "invalid-room": "Salon invalide", - "You_are_leaving_the_team": "Vous quittez l'équipe '{{team}}'", - "Leave_Team": "Quitter l'équipe", - "Select_Team": "Sélectionnez l'équipe", - "Select_Team_Channels": "Sélectionnez les canaux de l'équipe que vous souhaitez quitter.", - "Cannot_leave": "Ne peut pas partir", - "Cannot_remove": "Impossible d'enlever", - "Cannot_delete": "Impossible de supprimer", - "Last_owner_team_room": "Vous êtes le dernier propriétaire de ce canal. Une fois que vous quittez l'équipe, le canal sera conservé au sein de l'équipe mais vous le gérerez de l'extérieur.", - "last-owner-can-not-be-removed": "Le dernier propriétaire ne peut pas être supprimé", - "Remove_User_Teams": "Sélectionnez les canaux dont vous souhaitez supprimer l'utilisateur.", - "Delete_Team": "Supprimer l'équipe", - "Select_channels_to_delete": "Ceci ne peut pas être annulé. Une fois que vous supprimez une équipe, tout le contenu et la configuration du chat seront supprimés.\n\nSélectionnez les canaux que vous souhaitez supprimer. Ceux que vous décidez de conserver seront disponible dans votre espace de travail. Notez que les canaux publics seront toujours publics et visibles par tous.", - "You_are_deleting_the_team": "Vous supprimez cette équipe.", - "Removing_user_from_this_team": "Vous supprimez {{user}} de cette équipe", - "Remove_User_Team_Channels": "Sélectionnez les canaux dont vous souhaitez supprimer l'utilisateur.", - "Remove_Member": "Supprimer un membre", - "leaving_team": "quitter l'équipe", - "removing_team": "retirer de l'équipe", - "moving_channel_to_team": "transfert de canal à l'équipe", - "deleting_team": "suppression de l'équipe", - "member-does-not-exist": "Le membre n'existe pas", - "Convert": "Convertir", - "Convert_to_Team": "Convertir en équipe", - "Convert_to_Team_Warning": "Vous convertissez ce canal en équipe. Tous les membres seront conservés.", - "Move_to_Team": "Déplacer vers l'équipe", - "Move_Channel_Paragraph": "Le déplacement d'un canal dans une équipe signifie que ce canal sera ajouté dans le contexte d'équipe. Cependant, tous les membres du canal, qui ne sont pas membres de l'équipe respective, auront toujours accès à ce canal, mais ne seront pas ajoutés comme membres de l'équipe.\n\nLa gestion de tout le canal sera toujours assurée par les propriétaires de ce canal.\n\nLes membres de l'équipe et même les propriétaires de l'équipe, s'ils ne sont pas membres de ce canal, ne peuvent pas avoir accès au contenu du canal.\n\nVeuillez noter que le propriétaire de l'équipe pourra supprimer des membres du canal.", - "Move_to_Team_Warning": "Après avoir lu les instructions précédentes sur ce comportement, voulez-vous toujours déplacer ce canal vers l'équipe sélectionnée ?", - "Load_More": "Charger plus", - "Load_Newer": "Charger plus récent", - "Load_Older": "Charger plus ancien", - "Left_The_Room_Successfully": "A quitté le salon avec succès", - "Deleted_The_Team_Successfully": "Equipe supprimée avec succès", - "Deleted_The_Room_Successfully": "Salon supprimé avec succès", - "Convert_to_Channel": "Convertir en canal", - "Converting_Team_To_Channel": "Conversion de l’équipe en canal", - "Select_Team_Channels_To_Delete": "Sélectionnez les canaux de l'équipe que vous souhaitez supprimer, ceux que vous ne sélectionnez pas, seront déplacés vers l'espace de travail. \n\n\nNotez que les canaux publics seront publics et visibles par tous.", - "You_are_converting_the_team": "Vous convertissez cette équipe en canal" -} \ No newline at end of file + "1_person_reacted": "1 personne a réagi", + "1_user": "1 utilisateur", + "error-action-not-allowed": "{{action}} n'est pas autorisé", + "error-application-not-found": "Application non trouvée", + "error-archived-duplicate-name": "Il y a un canal archivé avec le nom {{room_name}}", + "error-avatar-invalid-url": "URL d'avatar invalide : {{url}}", + "error-avatar-url-handling": "Erreur lors de la gestion du paramètre d'avatar à partir d'une URL ({{url}}) pour {{username}}", + "error-cant-invite-for-direct-room": "Impossible d'inviter l'utilisateur aux salons directs", + "error-could-not-change-email": "Impossible de changer l'adresse e-mail", + "error-could-not-change-name": "Impossible de changer le nom", + "error-could-not-change-username": "Impossible de changer le nom d'utilisateur", + "error-could-not-change-status": "Impossible de changer le statut", + "error-delete-protected-role": "Impossible de supprimer un rôle protégé", + "error-department-not-found": "Département introuvable", + "error-direct-message-file-upload-not-allowed": "Partage de fichiers non autorisé dans les messages privés", + "error-duplicate-channel-name": "Un canal avec nom {{room_name}} existe", + "error-email-domain-blacklisted": "Le domaine de messagerie est sur liste noire", + "error-email-send-failed": "Erreur lors de la tentative d'envoi de l'e-mail : {{message}}", + "error-save-image": "Erreur lors de l'enregistrement de l'image", + "error-save-video": "Erreur en sauvegardant la vidéo", + "error-field-unavailable": "{{field}} est déjà utilisé: (", + "error-file-too-large": "Le fichier est trop grand", + "error-importer-not-defined": "L'importateur n'a pas été défini correctement, il manque la classe Import.", + "error-input-is-not-a-valid-field": "{{input}} n'est pas un {{field}} valide", + "error-invalid-actionlink": "Lien d'action non valide", + "error-invalid-arguments": "Arguments non valides", + "error-invalid-asset": "Elément non valide", + "error-invalid-channel": "Canal invalide.", + "error-invalid-channel-start-with-chars": "Canal non valide. Commencez par @ ou #", + "error-invalid-custom-field": "Champ personnalisé non valide", + "error-invalid-custom-field-name": "Nom de champ personnalisé non valide. Utilisez uniquement des lettres, des chiffres, des traits d'union et des traits de soulignement.", + "error-invalid-date": "Date fournie non valide.", + "error-invalid-description": "Description invalide", + "error-invalid-domain": "Domaine invalide", + "error-invalid-email": "E-mail {{email}} invalide", + "error-invalid-email-address": "Adresse e-mail invalide", + "error-invalid-file-height": "Hauteur de fichier non valide", + "error-invalid-file-type": "Type de fichier invalide", + "error-invalid-file-width": "Largeur de fichier non valide", + "error-invalid-from-address": "Vous avez renseigné une adresse FROM invalide.", + "error-invalid-integration": "Intégration invalide", + "error-invalid-message": "Message invalide", + "error-invalid-method": "Méthode non valide", + "error-invalid-name": "Nom incorrect", + "error-invalid-password": "Mot de passe incorrect", + "error-invalid-redirectUri": "RedirectUri invalide", + "error-invalid-role": "Rôle invalide", + "error-invalid-room": "Salon invalide", + "error-invalid-room-name": "{{room_name}} n'est pas un nom de salon valide", + "error-invalid-room-type": "{{type}} n'est pas un type de salon valide.", + "error-invalid-settings": "Paramètres fournis non valides", + "error-invalid-subscription": "Abonnement invalide", + "error-invalid-token": "Jeton invalide", + "error-invalid-triggerWords": "Mots déclencheurs invalides", + "error-invalid-urls": "URL non valides", + "error-invalid-user": "Utilisateur invalide", + "error-invalid-username": "Nom d'utilisateur invalide", + "error-invalid-webhook-response": "L'URL du webhook a répondu avec un statut autre que 200", + "error-message-deleting-blocked": "La suppression du message est bloquée", + "error-message-editing-blocked": "La modification du message est bloquée", + "error-message-size-exceeded": "La taille du message dépasse Message_MaxAllowedSize", + "error-missing-unsubscribe-link": "Vous devez fournir le lien [unsubscribe].", + "error-no-owner-channel": "Vous n'êtes pas propriétaire du canal", + "error-no-tokens-for-this-user": "Il n'y a pas de jetons pour cet utilisateur", + "error-not-allowed": "Interdit", + "error-not-authorized": "Pas autorisé", + "error-push-disabled": "Push est désactivé", + "error-remove-last-owner": "C'est le dernier propriétaire. Veuillez définir un nouveau propriétaire avant de supprimer celui-ci.", + "error-role-in-use": "Impossible de supprimer le rôle car il est en cours d'utilisation", + "error-role-name-required": "Le nom du rôle est requis", + "error-the-field-is-required": "Le champ {{field}} est requis.", + "error-too-many-requests": "Erreur, trop de demandes. Ralentissez, s'il vous plaît. Vous devez attendre {{seconds}} secondes avant de réessayer.", + "error-user-is-not-activated": "L'utilisateur n'est pas activé", + "error-user-has-no-roles": "L'utilisateur n'a aucun rôle", + "error-user-limit-exceeded": "Le nombre d'utilisateurs que vous essayez d'inviter à #channel_name dépasse la limite définie par l'administrateur", + "error-user-not-in-room": "L'utilisateur n'est pas dans ce salon", + "error-user-registration-custom-field": "error-user-registration-custom-field", + "error-user-registration-disabled": "L'enregistrement de l'utilisateur est désactivé", + "error-user-registration-secret": "L'enregistrement de l'utilisateur n'est autorisé que via l'URL secrète", + "error-you-are-last-owner": "Vous êtes le dernier propriétaire. Veuillez définir un nouveau propriétaire avant de quitter le salon.", + "error-status-not-allowed": "Le statut invisible est désactivé", + "Actions": "Actions", + "activity": "activité", + "Activity": "Activité", + "Add_Reaction": "Ajouter une réaction", + "Add_Server": "Ajouter un serveur", + "Add_users": "Ajouter des utilisateurs", + "Admin_Panel": "Panneau d'administration", + "Agent": "Agent", + "Alert": "Alerte", + "alert": "alerte", + "alerts": "alertes", + "All_users_in_the_channel_can_write_new_messages": "Tous les utilisateurs du canal peuvent écrire de nouveaux messages", + "All_users_in_the_team_can_write_new_messages": "Tous les utilisateurs de l'équipe peuvent écrire de nouveaux messages", + "A_meaningful_name_for_the_discussion_room": "Un nom significatif pour le salon de discussion", + "All": "Tout", + "All_Messages": "Tous les messages", + "Allow_Reactions": "Autoriser les réactions", + "Alphabetical": "Alphabétique", + "and_more": "et plus", + "and": "et", + "announcement": "annonce", + "Announcement": "Annonce", + "Apply_Your_Certificate": "Appliquer votre certificat", + "ARCHIVE": "ARCHIVER", + "archive": "archiver", + "are_typing": "sont en train d'écrire", + "Are_you_sure_question_mark": "Êtes-vous sûr ?", + "Are_you_sure_you_want_to_leave_the_room": "Êtes-vous sûr de vouloir quitter le salon {{room}} ?", + "Audio": "Audio", + "Authenticating": "Authentification", + "Automatic": "Automatique", + "Auto_Translate": "Traduction automatique", + "Avatar_changed_successfully": "Avatar changé avec succès !", + "Avatar_Url": "URL de l'avatar", + "Away": "Absent", + "Back": "Retour", + "Black": "Noir", + "Block_user": "Bloquer l'utilisateur", + "Browser": "Navigateur", + "Broadcast_channel_Description": "Seuls les utilisateurs autorisés peuvent écrire de nouveaux messages, mais les autres utilisateurs pourront répondre.", + "Broadcast_Channel": "Canal de diffusion", + "Busy": "Occupé", + "By_proceeding_you_are_agreeing": "En poursuivant, vous acceptez nos", + "Cancel_editing": "Annuler la modification", + "Cancel_recording": "Annuler l'enregistrement", + "Cancel": "Annuler", + "changing_avatar": "changer d'avatar", + "creating_channel": "création d'un canal", + "creating_invite": "création d'une invitation", + "Channel_Name": "Nom du canal", + "Channels": "Canaux", + "Chats": "Chats", + "Call_already_ended": "Appel déjà terminé !", + "Clear_cookies_alert": "Voulez-vous effacer tous les cookies ?", + "Clear_cookies_desc": "Cette action effacera tous les cookies de connexion ce qui vous permettra de vous connecter à d'autres comptes.", + "Clear_cookies_yes": "Oui, effacez les cookies", + "Clear_cookies_no": "Non, gardez les cookies", + "Click_to_join": "Cliquez pour rejoindre !", + "Close": "Fermer", + "Close_emoji_selector": "Fermer le sélecteur d'émoji", + "Closing_chat": "Fermeture du chat", + "Change_language_loading": "Changement de la langue.", + "Chat_closed_by_agent": "Chat fermé par l'agent", + "Choose": "Choisir", + "Choose_from_library": "Choisissez dans la bibliothèque", + "Choose_file": "Choisir le fichier", + "Choose_where_you_want_links_be_opened": "Choisissez oµ vous souhaitez ouvrir les liens", + "Code": "Code", + "Code_or_password_invalid": "Code ou mot de passe invalide", + "Collaborative": "Collaboratif", + "Confirm": "Confirmer", + "Connect": "Connecter", + "Connected": "Connecté", + "connecting_server": "connexion en cours au serveur", + "Connecting": "Connexion...", + "Contact_us": "Contactez-nous", + "Contact_your_server_admin": "Contactez votre administrateur de serveur.", + "Continue_with": "Continuer avec", + "Copied_to_clipboard": "Copié dans le presse-papier !", + "Copy": "Copier", + "Conversation": "Conversation", + "Permalink": "Lien permanent", + "Certificate_password": "Mot de passe du certificat", + "Clear_cache": "Effacer le cache du serveur local", + "Clear_cache_loading": "Effacement du cache.", + "Whats_the_password_for_your_certificate": "Quel est le mot de passe de votre certificat ?", + "Create_account": "Créer un compte", + "Create_Channel": "Créer un canal", + "Create_Direct_Messages": "Créer des messages directs", + "Create_Discussion": "Créer une discussion", + "Created_snippet": "créé un extrait", + "Create_a_new_workspace": "Créer un nouvel espace de travail", + "Create": "Créer", + "Custom_Status": "Statut personnalisé", + "Dark": "Sombre", + "Dark_level": "Niveau d'obscurité", + "Default": "Défaut", + "Default_browser": "Navigateur par défaut", + "Delete_Room_Warning": "Supprimer une salon supprimera tous les messages publiés dans le salon. Ça ne peut pas être annulé.", + "Department": "Département", + "delete": "supprimer", + "Delete": "Supprimer", + "DELETE": "SUPPRIMER", + "move": "déplacer", + "deleting_room": "suppression du salon", + "description": "la description", + "Description": "La description", + "Desktop_Options": "Options de bureau", + "Desktop_Notifications": "Notifications de bureau", + "Desktop_Alert_info": "Ces notifications sont transmises sur le bureau", + "Directory": "Répertoire", + "Direct_Messages": "Messages directs", + "Disable_notifications": "Désactiver les notifications", + "Discussions": "Discussions", + "Discussion_Desc": "Aide à garder une vue d'ensemble sur ce qui se passe ! En créant une discussion, un sous-canal de celui que vous avez sélectionné est créé et les deux sont liés.", + "Discussion_name": "Nom de la discussion", + "Done": "Fait", + "Dont_Have_An_Account": "Vous n'avez pas de compte ?", + "Do_you_have_an_account": "Avez-vous un compte ?", + "Do_you_have_a_certificate": "Avez-vous un certificat ?", + "Do_you_really_want_to_key_this_room_question_mark": "Voulez-vous vraiment {{key}} ce salon ?", + "E2E_Encryption": "Cryptage E2E", + "E2E_How_It_Works_info1": "Vous pouvez désormais créer des groupes privés et des messages directs chiffrés. Vous pouvez également modifier les groupes privés ou DM existants pour les crypter.", + "E2E_How_It_Works_info2": "Il s'agit du *chiffrement de bout en bout*, la clé permettant de coder/décoder vos messages ne sera pas enregistrée sur le serveur. C'est pourquoi *vous devez stocker ce mot de passe à un endroit sûr* auquel vous pourrez accéder plus tard si vous en avez besoin.", + "E2E_How_It_Works_info3": "Si vous continuez, un mot de passe E2E sera automatiquement généré.", + "E2E_How_It_Works_info4": "Vous pouvez également configurer un nouveau mot de passe pour votre clé de cryptage à tout moment à partir de n'importe quel navigateur dans lequel vous avez entré le mot de passe E2E existant.", + "edit": "modifier", + "edited": "modifié", + "Edit": "Modifier", + "Edit_Status": "Modifier le statut", + "Edit_Invite": "Modifier l'invitation", + "End_to_end_encrypted_room": "Salon crypté de bout en bout", + "end_to_end_encryption": "chiffrement de bout en bout", + "Email_Notification_Mode_All": "Chaque mention/MD", + "Email_Notification_Mode_Disabled": "Désactivé", + "Email_or_password_field_is_empty": "Le champ e-mail ou mot de passe est vide", + "Email": "E-mail", + "email": "e-mail", + "Empty_title": "Titre vide", + "Enable_Auto_Translate": "Activer la traduction automatique", + "Enable_notifications": "Activer les notifications", + "Encrypted": "Crypté", + "Encrypted_message": "Message crypté", + "Enter_Your_E2E_Password": "Entrez votre mot de passe E2E", + "Enter_Your_Encryption_Password_desc1": "Cela vous permettra d'accéder à vos groupes privés cryptés et à vos messages directs.", + "Enter_Your_Encryption_Password_desc2": "Vous devez entrer le mot de passe pour coder/décoder les messages à chaque endroit où vous utilisez le chat.", + "Encryption_error_title": "Votre mot de passe de cryptage semble erroné", + "Encryption_error_desc": "Il n'a pas été possible de décoder votre clé de cryptage pour être importé.", + "Everyone_can_access_this_channel": "Tout le monde peut accéder à ce canal", + "Everyone_can_access_this_team": "Tout le monde peut accéder à cette équipe", + "Error_uploading": "Erreur lors de l'envoi", + "Expiration_Days": "Expiration (Jours)", + "Favorite": "Favori", + "Favorites": "Favoris", + "Files": "Fichiers", + "File_description": "Description du fichier", + "File_name": "Nom de fichier", + "Finish_recording": "Terminer l'enregistrement", + "Following_thread": "Suivre le fil", + "For_your_security_you_must_enter_your_current_password_to_continue": "Pour votre sécurité, vous devez entrer votre mot de passe actuel pour continuer.", + "Forgot_password_If_this_email_is_registered": "Si cet e-mail est enregistré, nous vous enverrons des instructions pour réinitialiser votre mot de passe. Si vous ne recevez pas d'e-mail sous peu, veuillez revenir et réessayer.", + "Forgot_password": "Mot de passe oublié ?", + "Forgot_Password": "Mot de passe oublié", + "Forward": "Transmettre", + "Forward_Chat": "Transmettre la conversation", + "Forward_to_department": "Transmettre au département", + "Forward_to_user": "Transmettre à l'utilisateur", + "Full_table": "Cliquez pour voir le tableau complet", + "Generate_New_Link": "Générer un nouveau lien", + "Group_by_favorites": "Grouper par favoris", + "Group_by_type": "Grouper par type", + "Hide": "Cacher", + "Has_joined_the_channel": "a rejoint le canal", + "Has_joined_the_conversation": "a rejoint la conversation", + "Has_left_the_channel": "a quitté le canal", + "Hide_System_Messages": "Masquer les messages système", + "Hide_type_messages": "Masquer les messages \"{{type}}\"", + "How_It_Works": "Comment cela fonctionne", + "Message_HideType_uj": "L'utilisateur a rejoint", + "Message_HideType_ul": "L'utilisateur est parti", + "Message_HideType_ru": "Utilisateur supprimé", + "Message_HideType_au": "Utilisateur ajouté", + "Message_HideType_mute_unmute": "Utilisateur rendu muet / a retrouvé la parole", + "Message_HideType_r": "Nom du salon modifié", + "Message_HideType_ut": "L'utilisateur a rejoint la conversation", + "Message_HideType_wm": "Bienvenue", + "Message_HideType_rm": "Message supprimé", + "Message_HideType_subscription_role_added": "Rôle assigné", + "Message_HideType_subscription_role_removed": "Le rôle n'est plus défini", + "Message_HideType_room_archived": "Salon archivé", + "Message_HideType_room_unarchived": "Salon désarchivé", + "I_Saved_My_E2E_Password": "J'ai enregistré mon mot de passe E2E", + "IP": "IP", + "In_app": "Dans l'app", + "In_App_And_Desktop": "Dans l'application et sur le bureau", + "In_App_and_Desktop_Alert_info": "Affiche une bannière en haut de l'écran lorsque l'application est ouverte et affiche une notification sur le bureau", + "Invisible": "Invisible", + "Invite": "Inviter", + "is_a_valid_RocketChat_instance": "est une instance valide de Rocket.Chat", + "is_not_a_valid_RocketChat_instance": "n'est pas une instance valide de Rocket.Chat", + "is_typing": "est en train d'écrire", + "Invalid_or_expired_invite_token": "Jeton d'invitation non valide ou expiré", + "Invalid_server_version": "Le serveur auquel vous essayez de vous connecter utilise une version qui n'est plus prise en charge par l'application : {{currentVersion}}.\n\nNous exigeons la version {{minVersion}}", + "Invite_Link": "Lien d'invitation", + "Invite_users": "Inviter des utilisateurs", + "Join": "Rejoindre", + "Join_Code": "Code d'adhésion", + "Insert_Join_Code": "Insérer le code d'adhésion", + "Join_our_open_workspace": "Rejoignez notre espace de travail ouvert", + "Join_your_workspace": "Rejoignez votre espace de travail", + "Just_invited_people_can_access_this_channel": "Seuls les personnes invitées peuvent accéder à ce canal", + "Just_invited_people_can_access_this_team": "Seules les personnes invitées peuvent accéder à cette équipe", + "Language": "Langue", + "last_message": "dernier message", + "Leave_channel": "Quitter le canal", + "leaving_room": "quittant le salon", + "Leave": "Quitter", + "leave": "quitter", + "Legal": "Légal", + "Light": "Clair", + "License": "Licence", + "Livechat": "Chat en direct", + "Livechat_edit": "Modifier le chat en direct", + "Login": "Connexion", + "Login_error": "Vos identifiants ont été rejetés ! Veuillez réessayer.", + "Login_with": "Se connecter avec", + "Logging_out": "Déconnexion.", + "Logout": "Se déconnecter", + "Max_number_of_uses": "Nombre maximum d'utilisations", + "Max_number_of_users_allowed_is_number": "Le nombre maximum d'utilisateurs autorisés est {{maxUsers}}", + "members": "membres", + "Members": "Membres", + "Mentioned_Messages": "Messages mentionnés", + "mentioned": "mentionné", + "Mentions": "Mentions", + "Message_accessibility": "Message de {{user}} à {{time}} : {{message}}", + "Message_actions": "Actions de message", + "Message_pinned": "Message épinglé", + "Message_removed": "Message supprimé", + "Message_starred": "Message suivi", + "Message_unstarred": "Message non suivi", + "message": "message", + "messages": "messages", + "Message": "Message", + "Messages": "Messages", + "Message_Reported": "Message signalé", + "Microphone_Permission_Message": "Rocket.Chat a besoin d'accéder à votre microphone pour que vous puissiez envoyer un message audio.", + "Microphone_Permission": "Permission de microphone", + "Mute": "Rendre muet", + "muted": "muet", + "My_servers": "Mes serveurs", + "N_people_reacted": "{{n}} personnes ont réagi", + "N_users": "{{n}} utilisateurs", + "N_channels": "{{n}} canaux", + "name": "nom", + "Name": "Nom", + "Navigation_history": "Historique de navigation", + "Never": "Jamais", + "New_Message": "Nouveau message", + "New_Password": "Nouveau mot de passe", + "New_Server": "Nouveau serveur", + "Next": "Suivant", + "No_files": "Aucun fichier", + "No_limit": "Pas de limites", + "No_mentioned_messages": "Aucun message mentionné", + "No_pinned_messages": "Aucun message épinglé", + "No_results_found": "Aucun résultat trouvé", + "No_starred_messages": "Aucun message suivi", + "No_thread_messages": "Aucun message de fil de discussion", + "No_label_provided": "Aucun {{label}} fourni.", + "No_Message": "Aucun message", + "No_messages_yet": "Pas encore de messages", + "No_Reactions": "Aucune réaction", + "No_Read_Receipts": "Aucun accusé de lecture", + "Not_logged": "Non connecté", + "Not_RC_Server": "Ce n'est pas un serveur Rocket.Chat.\n{{contact}}", + "Nothing": "Rien", + "Nothing_to_save": "Rien à enregistrer !", + "Notify_active_in_this_room": "Notifier les utilisateurs actifs dans ce salon", + "Notify_all_in_this_room": "Avertir tout le monde dans ce salon", + "Notifications": "Notifications", + "Notification_Duration": "Durée des notifications", + "Notification_Preferences": "Préférences de notification", + "No_available_agents_to_transfer": "Aucun agent disponible pour le transfert", + "Offline": "Hors ligne", + "Oops": "Oups !", + "Omnichannel": "Omnicanal", + "Open_Livechats": "Discussions en cours", + "Omnichannel_enable_alert": "Vous n'êtes pas disponible sur Omnicanal. Souhaitez-vous être disponible ?", + "Onboarding_description": "Un espace de travail est l'espace de collaboration de votre équipe ou organisation. Demandez à l'administrateur de l'espace de travail l'adresse pour rejoindre ou créez-en une pour votre équipe.", + "Onboarding_join_workspace": "Rejoindre un espace de travail", + "Onboarding_subtitle": "Au-delà de la collaboration d'équipe", + "Onboarding_title": "Bienvenue sur Rocket.Chat", + "Onboarding_join_open_description": "Rejoignez notre espace de travail ouvert pour discuter avec l'équipe et la communauté Rocket.Chat.", + "Onboarding_agree_terms": "En continuant, vous acceptez Rocket.Chat", + "Onboarding_less_options": "Moins d'options", + "Onboarding_more_options": "Plus d'options", + "Online": "En ligne", + "Only_authorized_users_can_write_new_messages": "Seuls les utilisateurs autorisés peuvent écrire de nouveaux messages.", + "Open_emoji_selector": "Ouvrir le sélecteur d'émoji", + "Open_Source_Communication": "Communication Open Source", + "Open_your_authentication_app_and_enter_the_code": "Ouvrez votre application d'authentification et entrez le code.", + "OR": "OU", + "OS": "OS", + "Overwrites_the_server_configuration_and_use_room_config": "Écrase la configuration du serveur et utilise la configuration du salon", + "Password": "Mot de passe", + "Parent_channel_or_group": "Canal ou groupe parent", + "Permalink_copied_to_clipboard": "Lien permanent copié dans le presse-papiers !", + "Phone": "Téléphone", + "Pin": "Épingler", + "Pinned_Messages": "Messages épinglés", + "pinned": "épinglé", + "Pinned": "Épinglé", + "Please_add_a_comment": "Veuillez ajouter un commentaire", + "Please_enter_your_password": "Veuillez entrer votre mot de passe", + "Please_wait": "Veuillez patienter.", + "Preferences": "Préférences", + "Preferences_saved": "Préférences sauvegardées !", + "Privacy_Policy": " Politique de confidentialité", + "Private_Channel": "Canal privé", + "Private": "Privé", + "Processing": "Traitement...", + "Profile_saved_successfully": "Profil enregistré avec succès !", + "Profile": "Profil", + "Public_Channel": "Canal public", + "Public": "Public", + "Push_Notifications": "Notifications Push", + "Push_Notifications_Alert_Info": "Ces notifications vous sont envoyées lorsque l'application n'est pas ouverte", + "Quote": "Citation", + "Reactions_are_disabled": "Les réactions sont désactivées", + "Reactions_are_enabled": "Les réactions sont activées", + "Reactions": "Réactions", + "Read": "Lecture", + "Read_External_Permission_Message": "Rocket.Chat doit accéder aux photos, aux médias et aux fichiers sur votre appareil", + "Read_External_Permission": "Permission de lecture des fichiers", + "Read_Only_Channel": "Canal en lecture seule", + "Read_Only": "Lecture seule", + "Read_Receipt": "Accusé de réception", + "Receive_Group_Mentions": "Recevoir des mentions de groupe", + "Receive_Group_Mentions_Info": "Recevoir des mentions @all et @here", + "Register": "S'inscrire", + "Repeat_Password": "Répéter le mot de passe", + "Replied_on": "A répondu le :", + "replies": "réponses", + "reply": "répondre", + "Reply": "Répondre", + "Report": "Signaler", + "Receive_Notification": "Recevoir une notification", + "Receive_notifications_from": "Recevoir des notifications de {{name}}", + "Resend": "Renvoyer", + "Reset_password": "Réinitialiser le mot de passe", + "resetting_password": "réinitialisation du mot de passe", + "RESET": "RÉINITIALISER", + "Return": "Retour", + "Review_app_title": "Appréciez-vous cette application ?", + "Review_app_desc": "Donnez-nous 5 étoiles sur {{store}}", + "Review_app_yes": "Bien sûr !", + "Review_app_no": "Non", + "Review_app_later": "Peut-être plus tard", + "Review_app_unable_store": "Impossible d'ouvrir {{store}}", + "Review_this_app": "Donnez votre avis sur cette application", + "Remove": "Supprimer", + "remove": "supprimer", + "Roles": "Rôles", + "Room_actions": "Actions du salon", + "Room_changed_announcement": "Annonce du salon changé en : {{announcement}} par {{userBy}}", + "Room_changed_avatar": "Avatar du salon modifié par {{userBy}}", + "Room_changed_description": "Description du salon changé en : {{description}} par {{userBy}}", + "Room_changed_privacy": "Type de salon changé en : {{type}} par {{userBy}}", + "Room_changed_topic": "Le sujet de salon est changé en : {{topic}} par {{userBy}}", + "Room_Files": "Fichiers du salon", + "Room_Info_Edit": "Modifier les informations du salon", + "Room_Info": "Info sur le salon", + "Room_Members": "Membres du salon", + "Room_name_changed": "Nom de salon changé en : {{name}} par {{userBy}}", + "SAVE": "SAUVEGARDER", + "Save_Changes": "Sauvegarder les modifications", + "Save": "Sauvegarder", + "Saved": "Enregistré", + "saving_preferences": "enregistrement des préférences", + "saving_profile": "enregistrement du profil", + "saving_settings": "enregistrement des paramètres", + "saved_to_gallery": "Enregistré dans la galerie", + "Save_Your_E2E_Password": "Enregistrez votre mot de passe E2E", + "Save_Your_Encryption_Password": "Enregistrez votre mot de passe de cryptage", + "Save_Your_Encryption_Password_warning": "Ce mot de passe n'est stocké nulle part, enregistrez-le donc soigneusement ailleurs.", + "Save_Your_Encryption_Password_info": "Si vous perdez le mot de passe, il n'y a aucun moyen de le récupérer et vous perdrez l'accès à vos messages.", + "Search_Messages": "Rechercher des messages", + "Search": "Recherche", + "Search_by": "Rechercher par", + "Search_global_users": "Rechercher des utilisateurs mondiaux", + "Search_global_users_description": "Si vous activez, vous pouvez rechercher n'importe quel utilisateur d'autres sociétés ou serveurs.", + "Seconds": "{{second}} secondes", + "Security_and_privacy": "Sécurité et vie privée", + "Select_Avatar": "Sélectionnez un avatar", + "Select_Server": "Sélectionnez un serveur", + "Select_Users": "Sélectionner les utilisateurs", + "Select_a_Channel": "Sélectionnez un canal", + "Select_a_Department": "Sélectionnez un département", + "Select_an_option": "Sélectionnez une option", + "Select_a_User": "Sélectionnez un utilisateur", + "Send": "Envoyer", + "Send_audio_message": "Envoyer un message audio", + "Send_crash_report": "Envoyer un rapport de plantage", + "Send_message": "Envoyer un message", + "Send_me_the_code_again": "Envoyez-moi à nouveau le code", + "Send_to": "Envoyer à...", + "Sending_to": "Envoi à", + "Sent_an_attachment": "Envoyé une pièce jointe", + "Server": "Serveur", + "Servers": "Serveurs", + "Server_version": "Version du serveur : {{version}}", + "Set_username_subtitle": "Le nom d'utilisateur est utilisé pour permettre aux autres de vous mentionner dans les messages", + "Set_custom_status": "Définir le statut personnalisé", + "Set_status": "Définir le statut", + "Status_saved_successfully": "Statut enregistré avec succès !", + "Settings": "Paramètres", + "Settings_succesfully_changed": "Paramètres modifiés avec succès !", + "Share": "Partager", + "Share_Link": "Partager le lien", + "Share_this_app": "Partager cette application", + "Show_more": "Afficher plus..", + "Show_Unread_Counter": "Afficher le compteur non lu", + "Show_Unread_Counter_Info": "Le compteur non lu est affiché sous forme de badge à droite du canal, dans la liste", + "Sign_in_your_server": "Connectez-vous à votre serveur", + "Sign_Up": "S'inscrire", + "Some_field_is_invalid_or_empty": "Certains champs sont invalides ou vides", + "Sorting_by": "Tri par {{key}}", + "Sound": "Son", + "Star_room": "Canal favoris", + "Star": "Mettre en favoris", + "Starred_Messages": "Les messages favoris", + "starred": "favoris", + "Starred": "Favoris", + "Start_of_conversation": "Début de conversation", + "Start_a_Discussion": "Lancer une discussion", + "Started_discussion": "A commencé une discussion :", + "Started_call": "Appel lancé par {{userBy}}", + "Submit": "Soumettre", + "Table": "Tableau", + "Tags": "Mots clés", + "Take_a_photo": "Prendre une photo", + "Take_a_video": "Prendre une vidéo", + "Take_it": "Prends-le !", + "tap_to_change_status": "appuyez pour changer de statut", + "Tap_to_view_servers_list": "Appuyez pour afficher la liste des serveurs", + "Terms_of_Service": " Conditions d'utilisation ", + "Theme": "Thème", + "The_user_wont_be_able_to_type_in_roomName": "L'utilisateur ne pourra pas écrire dans {{roomName}}", + "The_user_will_be_able_to_type_in_roomName": "L'utilisateur pourra écrire dans {{roomName}}", + "There_was_an_error_while_action": "Une erreur s'est produite lors de {{action}} !", + "This_room_is_blocked": "Ce salon est bloqué", + "This_room_is_read_only": "Ce salon est en lecture seule", + "Thread": "Fil de discussion", + "Threads": "Fils de discussions", + "Timezone": "Fuseau horaire", + "To": "A", + "topic": "sujet", + "Topic": "Sujet", + "Translate": "Traduire", + "Try_again": "Réessayer", + "Two_Factor_Authentication": "Authentification à deux facteurs", + "Type_the_channel_name_here": "Tapez le nom de canal ici", + "unarchive": "désarchiver", + "UNARCHIVE": "DÉSARCHIVER", + "Unblock_user": "Débloquer l'utilisateur", + "Unfavorite": "Supprimer des favoris", + "Unfollowed_thread": "Ne plus suivre ce fil", + "Unmute": "Rendre la parole", + "unmuted": "rendu la parole", + "Unpin": "Détacher", + "unread_messages": "non lu", + "Unread": "Non lu", + "Unread_on_top": "Non lu en haut", + "Unstar": "Enlever des favoris", + "Updating": "Mise à jour...", + "Uploading": "Envoyer", + "Upload_file_question_mark": "Téléverser un fichier ?", + "User": "Utilisateur", + "Users": "Utilisateurs", + "User_added_by": "Utilisateur {{userAdded}} ajouté par {{userBy}}", + "User_Info": "Info d'utilisateur", + "User_has_been_key": "L'utilisateur a été {{key}}", + "User_is_no_longer_role_by_": "{{user}} n'est plus {{role}} par {{userBy}}", + "User_muted_by": "L'utilisateur {{userMuted}} a été rendu muet par {{userBy}}", + "User_removed_by": "Utilisateur {{userRemoved}} supprimé par {{userBy}}", + "User_sent_an_attachment": "{{user}} a envoyé une pièce jointe", + "User_unmuted_by": "L'utilisateur {{userBy}} a rendu la parole à {{userUnmuted}}", + "User_was_set_role_by_": "{{user}} a été défini {{role}} par {{userBy}}", + "Username_is_empty": "Nom d'utilisateur est vide", + "Username": "Nom d'utilisateur", + "Username_or_email": "Nom d'utilisateur ou e-mail", + "Uses_server_configuration": "Utilise la configuration du serveur", + "Validating": "Validation", + "Registration_Succeeded": "Inscription réussie !", + "Verify": "Vérifier", + "Verify_email_title": "Inscription réussie !", + "Verify_email_desc": "Nous vous avons envoyé un e-mail pour confirmer votre inscription. Si vous ne recevez pas d'e-mail sous peu, veuillez revenir et réessayer.", + "Verify_your_email_for_the_code_we_sent": "Vérifiez votre e-mail pour le code que nous avons envoyé", + "Video_call": "Appel vidéo", + "View_Original": "Voir l'original", + "Voice_call": "Appel vocal", + "Waiting_for_network": "En attente du réseau...", + "Websocket_disabled": "Le Websocket est désactivé pour ce serveur.\n{{contact}}", + "Welcome": "Bienvenue", + "What_are_you_doing_right_now": "Que fais-tu en ce moment ?", + "Whats_your_2fa": "Quel est votre code 2FA ?", + "Without_Servers": "Sans serveurs", + "Workspaces": "Espaces de travail", + "Would_you_like_to_return_the_inquiry": "Souhaitez-vous retourner la demande ?", + "Write_External_Permission_Message": "Rocket.Chat a besoin d'accéder à votre galerie pour que vous puissiez enregistrer des images.", + "Write_External_Permission": "Autorisation de la galerie", + "Yes": "Oui", + "Yes_action_it": "Oui, {{action}} le !", + "Yesterday": "Hier", + "You_are_in_preview_mode": "Vous êtes en mode aperçu", + "You_are_offline": "Vous êtes hors ligne", + "You_can_search_using_RegExp_eg": "Vous pouvez utiliser RegExp., par exemple `/^texte$/i`", + "You_colon": "Vous: ", + "you_were_mentioned": "vous avez été mentionné", + "You_were_removed_from_channel": "Vous avez été retiré de {{channel}}", + "you": "vous", + "You": "Vous", + "Logged_out_by_server": "Vous avez été déconnecté du serveur. Veuillez vous reconnecter.", + "You_need_to_access_at_least_one_RocketChat_server_to_share_something": "Vous devez accéder à au moins un serveur Rocket.Chat pour partager quelque chose.", + "You_need_to_verifiy_your_email_address_to_get_notications": "Vous devez vérifier votre adresse e-mail pour recevoir des notifications", + "Your_certificate": "Votre certificat", + "Your_invite_link_will_expire_after__usesLeft__uses": "Votre lien d'invitation expirera après {{usesLeft}} utilisations.", + "Your_invite_link_will_expire_on__date__or_after__usesLeft__uses": "Votre lien d'invitation expirera le {{date}} ou après {{usesLeft}} utilisations.", + "Your_invite_link_will_expire_on__date__": "Votre lien d'invitation expirera le {{date}}.", + "Your_invite_link_will_never_expire": "Votre lien d'invitation n'expirera jamais.", + "Your_workspace": "Votre espace de travail", + "Your_password_is": "Votre mot de passe est", + "Version_no": "Version : {{version}}", + "You_will_not_be_able_to_recover_this_message": "Vous ne pourrez pas récupérer ce message !", + "You_will_unset_a_certificate_for_this_server": "Vous allez supprimer un certificat pour ce serveur", + "Change_Language": "Changer la langue", + "Crash_report_disclaimer": "Nous ne suivons jamais le contenu de vos chats. Le rapport d'incident et les évènements d'analyse ne contiennent que des informations pertinentes pour nous afin d'identifier et de résoudre les problèmes.", + "Type_message": "Tapez le message", + "Room_search": "Recherche de salons", + "Room_selection": "Sélection de salon 1...9", + "Next_room": "Salon suivant", + "Previous_room": "Salon précédent", + "New_room": "Nouveau salon", + "Upload_room": "Envoyer dans un salon", + "Search_messages": "Rechercher des messages", + "Scroll_messages": "Faire défiler les messages", + "Reply_latest": "Répondre au dernier", + "Reply_in_Thread": "Répondre dans le fil", + "Server_selection": "Sélection du serveur", + "Server_selection_numbers": "Sélection du serveur 1...9", + "Add_server": "Ajouter un serveur", + "New_line": "Nouvelle ligne", + "You_will_be_logged_out_of_this_application": "Vous serez déconnecté de cette application.", + "Clear": "Effacer", + "This_will_clear_all_your_offline_data": "Cela effacera toutes vos données hors ligne.", + "This_will_remove_all_data_from_this_server": "Cela supprimera toutes les données de ce serveur.", + "Mark_unread": "Marquer comme non lu", + "Wait_activation_warning": "Avant de pouvoir vous connecter, votre compte doit être activé manuellement par un administrateur.", + "Screen_lock": "Verrouillage d'écran", + "Local_authentication_biometry_title": "Authentifier", + "Local_authentication_biometry_fallback": "Utiliser le code d'accès", + "Local_authentication_unlock_option": "Déverrouiller avec le code d'accès", + "Local_authentication_change_passcode": "Changer le code d'accès", + "Local_authentication_info": "Remarque : si vous oubliez le code d'accès, vous devrez supprimer et réinstaller l'application.", + "Local_authentication_facial_recognition": "reconnaissance faciale", + "Local_authentication_fingerprint": "empreinte digitale", + "Local_authentication_unlock_with_label": "Déverrouiller avec {{label}}", + "Local_authentication_auto_lock_60": "Après 1 minute", + "Local_authentication_auto_lock_300": "Après 5 minutes", + "Local_authentication_auto_lock_900": "Après 15 minutes", + "Local_authentication_auto_lock_1800": "Après 30 minutes", + "Local_authentication_auto_lock_3600": "Après 1 heure", + "Passcode_enter_title": "Entrez votre code d'accès", + "Passcode_choose_title": "Choisissez votre nouveau code d'accès", + "Passcode_choose_confirm_title": "Confirmez votre nouveau code d'accès", + "Passcode_choose_error": "Les codes d'accès ne correspondent pas. Réessayer.", + "Passcode_choose_force_set": "Code d'accès requis par l'administrateur", + "Passcode_app_locked_title": "App verrouillée", + "Passcode_app_locked_subtitle": "Réessayez dans {{timeLeft}} secondes", + "After_seconds_set_by_admin": "Après {{seconds}} secondes (défini par l'administrateur)", + "Dont_activate": "Ne pas activer maintenant", + "Queued_chats": "Discussions en file d'attente", + "Queue_is_empty": "La file d'attente est vide", + "Logout_from_other_logged_in_locations": "Déconnexion des autres emplacements connectés", + "You_will_be_logged_out_from_other_locations": "Vous serez déconnecté des autres emplacements.", + "Logged_out_of_other_clients_successfully": "Déconnexion réussie des autres clients", + "Logout_failed": "Echec de la déconnexion !", + "Log_analytics_events": "Journal des événements d'analyse", + "E2E_encryption_change_password_title": "Changer le mot de passe de cryptage", + "E2E_encryption_change_password_description": "Vous pouvez désormais créer des groupes privés et des messages directs chiffrés. Vous pouvez également modifier les groupes privés ou DM existants pour les crypter.\nIl s'agit du chiffrement de bout en bout, la clé permettant de coder/décoder vos messages ne sera pas enregistrée sur le serveur. Pour cette raison, vous devez stocker ce mot de passe à un endroit sûr. Vous devrez le saisir sur les autres appareils sur lesquels vous souhaitez utiliser le cryptage E2E.", + "E2E_encryption_change_password_error": "Erreur lors de la modification du mot de passe de la clé E2E", + "E2E_encryption_change_password_success": "Le mot de passe de la clé E2E a été changé avec succès !", + "E2E_encryption_change_password_message": "Assurez-vous de l'avoir enregistré soigneusement ailleurs.", + "E2E_encryption_change_password_confirmation": "Oui, changez-le", + "E2E_encryption_reset_title": "Réinitialiser la clé E2E", + "E2E_encryption_reset_description": "Cette option supprimera la clé E2E actuelle et vous déconnectera.\nLorsque vous vous reconnecterez, Rocket.Chat générera une nouvelle clé et restaurera votre accès aux salons cryptés qui a un ou plusieurs membres en ligne.\nEn raison de la nature du cryptage E2E, Rocket.Chat ne pourra pas restaurer l'accès à un salon crypté qui n'a aucun membre en ligne.", + "E2E_encryption_reset_button": "Réinitialiser la clé E2E", + "E2E_encryption_reset_error": "Erreur lors de la réinitialisation de la clé E2E !", + "E2E_encryption_reset_message": "Vous allez être déconnecté.", + "E2E_encryption_reset_confirmation": "Oui, réinitialisez-le", + "Following": "Suivant", + "Threads_displaying_all": "Tout afficher", + "Threads_displaying_following": "Affichage suivant", + "Threads_displaying_unread": "Affichage non lu", + "No_threads": "Il n'y a pas de fils", + "No_threads_following": "Vous ne suivez aucun fil de discussion", + "No_threads_unread": "Il n'y a pas de fils non lus", + "Messagebox_Send_to_channel": "Envoyer au canal", + "Leader": "Leader", + "Moderator": "Modérateur", + "Owner": "Propriétaire", + "Remove_from_room": "Retirer du salon", + "Ignore": "Ignorer", + "Unignore": "Ne pas ignorer", + "User_has_been_ignored": "L'utilisateur a été ignoré", + "User_has_been_unignored": "L'utilisateur n'est plus ignoré", + "User_has_been_removed_from_s": "L'utilisateur a été retiré de {{s}}", + "User__username__is_now_a_leader_of__room_name_": "L'utilisateur {{username}} est désormais un leader de {{room_name}}", + "User__username__is_now_a_moderator_of__room_name_": "L'utilisateur {{username}} est désormais un modérateur de {{room_name}}", + "User__username__is_now_a_owner_of__room_name_": "L'utilisateur {{username}} est désormais un propriétaire de {{room_name}}", + "User__username__removed_from__room_name__leaders": "L'utilisateur {{username}} a été supprimé des leaders de {{room_name}}", + "User__username__removed_from__room_name__moderators": "L'utilisateur {{username}} a été supprimé des modérateurs de {{room_name}}", + "User__username__removed_from__room_name__owners": "L'utilisateur {{username}} a été supprimé des propriétaires de {{room_name}}", + "The_user_will_be_removed_from_s": "L'utilisateur sera supprimé de {{s}}", + "Yes_remove_user": "Oui, supprimez l'utilisateur !", + "Direct_message": "Message direct", + "Message_Ignored": "Message ignoré. Touchez pour l'afficher.", + "Enter_workspace_URL": "Entrez l'URL de l'espace de travail", + "Workspace_URL_Example": "Ex. votre-société.rocket.chat", + "This_room_encryption_has_been_enabled_by__username_": "Le cryptage de ce salon a été activé par {{username}}", + "This_room_encryption_has_been_disabled_by__username_": "Le cryptage de ce salon a été désactivé par {{username}}", + "Teams": "Equipes", + "No_team_channels_found": "Aucun canal trouvé", + "Team_not_found": "Equipe non trouvée", + "Create_Team": "Créer une équipe", + "Team_Name": "Nom de l'équipe", + "Private_Team": "Equipe privée", + "Read_Only_Team": "Equipe en lecture seule", + "Broadcast_Team": "Equipe de diffusion", + "creating_team": "création de l'équipe", + "team-name-already-exists": "Une équipe portant ce nom existe déjà", + "Add_Channel_to_Team": "Ajouter un canal à l'équipe", + "Left_The_Team_Successfully": "A quitté l'équipe avec succès", + "Create_New": "Créer un nouveau", + "Add_Existing": "Ajouter existant", + "Add_Existing_Channel": "Ajouter un canal existant", + "Remove_from_Team": "Retirer de l'équipe", + "Auto-join": "Rejoindre automatiquement", + "Remove_Team_Room_Warning": "Souhaitez-vous supprimer ce canal de l'équipe ? Le canal sera déplacé vers l'espace de travail", + "Confirmation": "Confirmation", + "invalid-room": "Salon invalide", + "You_are_leaving_the_team": "Vous quittez l'équipe '{{team}}'", + "Leave_Team": "Quitter l'équipe", + "Select_Team": "Sélectionnez l'équipe", + "Select_Team_Channels": "Sélectionnez les canaux de l'équipe que vous souhaitez quitter.", + "Cannot_leave": "Ne peut pas partir", + "Cannot_remove": "Impossible d'enlever", + "Cannot_delete": "Impossible de supprimer", + "Last_owner_team_room": "Vous êtes le dernier propriétaire de ce canal. Une fois que vous quittez l'équipe, le canal sera conservé au sein de l'équipe mais vous le gérerez de l'extérieur.", + "last-owner-can-not-be-removed": "Le dernier propriétaire ne peut pas être supprimé", + "Remove_User_Teams": "Sélectionnez les canaux dont vous souhaitez supprimer l'utilisateur.", + "Delete_Team": "Supprimer l'équipe", + "Select_channels_to_delete": "Ceci ne peut pas être annulé. Une fois que vous supprimez une équipe, tout le contenu et la configuration du chat seront supprimés.\n\nSélectionnez les canaux que vous souhaitez supprimer. Ceux que vous décidez de conserver seront disponible dans votre espace de travail. Notez que les canaux publics seront toujours publics et visibles par tous.", + "You_are_deleting_the_team": "Vous supprimez cette équipe.", + "Removing_user_from_this_team": "Vous supprimez {{user}} de cette équipe", + "Remove_User_Team_Channels": "Sélectionnez les canaux dont vous souhaitez supprimer l'utilisateur.", + "Remove_Member": "Supprimer un membre", + "leaving_team": "quitter l'équipe", + "removing_team": "retirer de l'équipe", + "moving_channel_to_team": "transfert de canal à l'équipe", + "deleting_team": "suppression de l'équipe", + "member-does-not-exist": "Le membre n'existe pas", + "Convert": "Convertir", + "Convert_to_Team": "Convertir en équipe", + "Convert_to_Team_Warning": "Vous convertissez ce canal en équipe. Tous les membres seront conservés.", + "Move_to_Team": "Déplacer vers l'équipe", + "Move_Channel_Paragraph": "Le déplacement d'un canal dans une équipe signifie que ce canal sera ajouté dans le contexte d'équipe. Cependant, tous les membres du canal, qui ne sont pas membres de l'équipe respective, auront toujours accès à ce canal, mais ne seront pas ajoutés comme membres de l'équipe.\n\nLa gestion de tout le canal sera toujours assurée par les propriétaires de ce canal.\n\nLes membres de l'équipe et même les propriétaires de l'équipe, s'ils ne sont pas membres de ce canal, ne peuvent pas avoir accès au contenu du canal.\n\nVeuillez noter que le propriétaire de l'équipe pourra supprimer des membres du canal.", + "Move_to_Team_Warning": "Après avoir lu les instructions précédentes sur ce comportement, voulez-vous toujours déplacer ce canal vers l'équipe sélectionnée ?", + "Load_More": "Charger plus", + "Load_Newer": "Charger plus récent", + "Load_Older": "Charger plus ancien", + "room-name-already-exists": "Le nom du salon existe déjà", + "error-team-creation": "Erreur de création d'équipe", + "unauthorized": "Non autorisé", + "Left_The_Room_Successfully": "A quitté le salon avec succès", + "Deleted_The_Team_Successfully": "Equipe supprimée avec succès", + "Deleted_The_Room_Successfully": "Salon supprimé avec succès", + "Convert_to_Channel": "Convertir en canal", + "Converting_Team_To_Channel": "Conversion de l’équipe en canal", + "Select_Team_Channels_To_Delete": "Sélectionnez les canaux de l'équipe que vous souhaitez supprimer, ceux que vous ne sélectionnez pas, seront déplacés vers l'espace de travail. \n\n\nNotez que les canaux publics seront publics et visibles par tous.", + "You_are_converting_the_team": "Vous convertissez cette équipe en canal", + "creating_discussion": "créer une discussion" +} diff --git a/app/i18n/locales/it.json b/app/i18n/locales/it.json index e8618f28e..87447244c 100644 --- a/app/i18n/locales/it.json +++ b/app/i18n/locales/it.json @@ -1,702 +1,702 @@ { - "1_person_reacted": "1 persona ha reagito", - "1_user": "1 utente", - "error-action-not-allowed": "{{action}} non autorizzata", - "error-application-not-found": "Applicazione non trovata", - "error-archived-duplicate-name": "Esiste già un canale archiviato con nome {{room_name}}", - "error-avatar-invalid-url": "URL avatar non valido: {{url}}", - "error-avatar-url-handling": "Errore nella gestione dell'impostazione avatar dall'URL ({{url}}) per {{username}}", - "error-cant-invite-for-direct-room": "Impossibile invitare l'utente alle stanze dirette", - "error-could-not-change-email": "Impossibile cambiare l'indirizzo e-mail", - "error-could-not-change-name": "Impossibile cambiare nome", - "error-could-not-change-username": "Impossibile cambiare username", - "error-could-not-change-status": "Impossibile cambiare lo stato", - "error-delete-protected-role": "Impossibile eliminare un ruolo protetto", - "error-department-not-found": "Reparto non trovato", - "error-direct-message-file-upload-not-allowed": "La condivisione di file non è autorizzata nei messaggi diretti", - "error-duplicate-channel-name": "Esiste già un canale con nome {{channel_name}}", - "error-email-domain-blacklisted": "Il dominio e-mail è nella lista nera", - "error-email-send-failed": "Errore nel tentativo di invio e-mail: {{message}}", - "error-save-image": "Errore nel salvataggio dell'immagine", - "error-save-video": "Errore nel salvataggio del video", - "error-field-unavailable": "{{field}} è già in uso :(", - "error-file-too-large": "File troppo grande", - "error-importer-not-defined": "L'importatore non è stato definito correttamente: classe Import mancante.", - "error-input-is-not-a-valid-field": "{{input}} non è valido come {{field}}", - "error-invalid-actionlink": "Link azione non valido", - "error-invalid-arguments": "Parametri non validi", - "error-invalid-asset": "Risorsa non valida", - "error-invalid-channel": "Canale non valido.", - "error-invalid-channel-start-with-chars": "Canale non valido. Inizia con @ o #", - "error-invalid-custom-field": "Campo personalizzato non valido", - "error-invalid-custom-field-name": "Nome campo personalizzato non valido. Usa solo lettere, numeri, trattini e underscore.", - "error-invalid-date": "Data fornita non valida.", - "error-invalid-description": "Descrizione non valida", - "error-invalid-domain": "Dominio non valido", - "error-invalid-email": "E-mail {{email}} non valida", - "error-invalid-email-address": "Indirizzo e-mail non valido", - "error-invalid-file-height": "Altezza del file non valida", - "error-invalid-file-type": "Tipo di file non valido", - "error-invalid-file-width": "Larghezza del file non valida", - "error-invalid-from-address": "Hai informato un indirizzo FROM non valido.", - "error-invalid-integration": "Integrazione non valida", - "error-invalid-message": "Messaggio non valido", - "error-invalid-method": "Metodo o funzione non valida", - "error-invalid-name": "Nome non corretto", - "error-invalid-password": "Password non corretta", - "error-invalid-redirectUri": "redirectUri non valido", - "error-invalid-role": "Ruolo non valido", - "error-invalid-room": "Stanza non valida", - "error-invalid-room-name": "{{room_name}} non è un nome di stanza valido", - "error-invalid-room-type": "{{type}} non è un tipo di stanza valido", - "error-invalid-settings": "Impostazioni fornite non valide", - "error-invalid-subscription": "Iscrizione non valida", - "error-invalid-token": "Token non valido", - "error-invalid-triggerWords": "triggerWords non valide", - "error-invalid-urls": "URL non validi", - "error-invalid-user": "Utente non valido", - "error-invalid-username": "Nome utente non valido", - "error-invalid-webhook-response": "L'URL del webhook ha risposto con uno stato diverso da 200", - "error-message-deleting-blocked": "Cancellazione di messaggi bloccata", - "error-message-editing-blocked": "Modifica di messaggi bloccata", - "error-message-size-exceeded": "La dimensione del messaggio supera Message_MaxAllowedSize", - "error-missing-unsubscribe-link": "Devi fornire il link [unsubscribe].", - "error-no-tokens-for-this-user": "Non ci sono token per questo utente", - "error-not-allowed": "Non permesso", - "error-not-authorized": "Non autorizzato", - "error-push-disabled": "Push è disabilitato", - "error-remove-last-owner": "Questo è l'ultimo proprietario rimasto. Imposta un nuovo proprietario prima di rimuoverlo.", - "error-role-in-use": "Impossibile eliminare il ruolo perchè ancora in uso", - "error-role-name-required": "Il nome del ruolo è obbligatorio", - "error-the-field-is-required": "Il campo {{field}} è obbligatorio.", - "error-too-many-requests": "Errore, troppe richieste effettuate. Rallenta. Devi attendere {{seconds}} secondi prima di riprovare.", - "error-user-is-not-activated": "L'utente non è attivato", - "error-user-has-no-roles": "L'utente non ha ruoli", - "error-user-limit-exceeded": "Il numero di utenti che stai invitando in #channel_name supera il limite imposto dall'amministratore", - "error-user-not-in-room": "L'utente non è in questa stanza", - "error-user-registration-custom-field": "error-user-registration-custom-field", - "error-user-registration-disabled": "Registrazione utente disabilitata", - "error-user-registration-secret": "Registrazione utente permessa solo via URL segreto", - "error-you-are-last-owner": "Sei l'ultimo proprietario rimasto. Imposta un nuovo proprietario prima di lasciare la stanza.", - "Actions": "Azioni", - "activity": "attività", - "Activity": "Attività", - "Add_Reaction": "Aggiungi reazione", - "Add_Server": "Aggiungi server", - "Add_users": "Aggiungi utenti", - "Admin_Panel": "Amministrazione", - "Agent": "Agente", - "Alert": "Avviso", - "alert": "avviso", - "alerts": "avvisi", - "All_users_in_the_channel_can_write_new_messages": "Tutti gli utenti nel canale possono scrivere messaggi", - "A_meaningful_name_for_the_discussion_room": "Un nome significativo per il canale di discussione", - "All": "Tutti", - "All_Messages": "Tutti i messaggi", - "Allow_Reactions": "Permetti reazioni", - "Alphabetical": "Alfabetico", - "and_more": "e altro", - "and": "e", - "announcement": "annuncio", - "Announcement": "Annuncio", - "Apply_Your_Certificate": "Applica il tuo certificato", - "ARCHIVE": "ARCHIVIO", - "archive": "archivio", - "are_typing": "stanno scrivendo", - "Are_you_sure_question_mark": "Sei sicuro?", - "Are_you_sure_you_want_to_leave_the_room": "Sei sicuro di voler lasciare la stanza {{room}}?", - "Audio": "Audio", - "Authenticating": "Autenticazione", - "Automatic": "Automatico", - "Auto_Translate": "Traduzione automatica", - "Avatar_changed_successfully": "Avatar aggiornato correttamente!", - "Avatar_Url": "URL avatar", - "Away": "Assente", - "Back": "Indietro", - "Black": "Nero", - "Block_user": "Blocca utente", - "Browser": "Browser", - "Broadcast_channel_Description": "Solo gli utenti autorizzati possono scrivere messaggi, ma gli altri utenti saranno in grado di rispondere", - "Broadcast_Channel": "Canale broadcast", - "Busy": "Occupato", - "By_proceeding_you_are_agreeing": "Procedendo accetti i nostri", - "Cancel_editing": "Annulla modifica", - "Cancel_recording": "Annulla registrazione", - "Cancel": "Annulla", - "changing_avatar": "cambio avatar", - "creating_channel": "creo canale", - "creating_invite": "creo invito", - "Channel_Name": "Nome canale", - "Channels": "Canali", - "Chats": "Chat", - "Call_already_ended": "Chiamata già terminata!", - "Clear_cookies_alert": "Vuoi cancellare tutti i cookie?", - "Clear_cookies_desc": "Questo cancellerà tutti i coockie di login, permettendoti di accedere con altri account.", - "Clear_cookies_yes": "Si, cancella i cookie", - "Clear_cookies_no": "No, mantieni i cookie", - "Click_to_join": "Clicca per unirti!", - "Close": "Chiudi", - "Close_emoji_selector": "Chiudi selettore emoji", - "Closing_chat": "Chiudendo la chat", - "Change_language_loading": "Cambiando la lingua.", - "Chat_closed_by_agent": "Chat chiusa dall'agente", - "Choose": "Scegli", - "Choose_from_library": "Scegli dalla libreria", - "Choose_file": "Scegli file", - "Choose_where_you_want_links_be_opened": "Scegli dove vuoi che vengano aperti i link", - "Code": "Codice", - "Code_or_password_invalid": "Codice o password non validi", - "Collaborative": "Collaborativo", - "Confirm": "Conferma", - "Connect": "Connetti", - "Connected": "Connesso", - "connecting_server": "connessione al server", - "Connecting": "Connessione...", - "Contact_us": "Contattaci", - "Contact_your_server_admin": "Contatta l'amministratore.", - "Continue_with": "Continua con", - "Copied_to_clipboard": "Copiato negli appunti!", - "Copy": "Copia", - "Conversation": "Conversazione", - "Permalink": "Permalink", - "Certificate_password": "Password certificato", - "Clear_cache": "Cancella la cache locale", - "Clear_cache_loading": "Cancellando la cache.", - "Whats_the_password_for_your_certificate": "Qual'è la password del tuo certificato?", - "Create_account": "Crea un account", - "Create_Channel": "Crea canale", - "Create_Direct_Messages": "Crea Messaggi Privati", - "Create_Discussion": "Crea una Discussione", - "Created_snippet": "Snippet creato", - "Create_a_new_workspace": "Crea un nuovo workspace", - "Create": "Crea", - "Custom_Status": "Stato personalizzato", - "Dark": "Scuro", - "Dark_level": "Contrasto", - "Default": "Predefinito", - "Default_browser": "Browser predefinito", - "Delete_Room_Warning": "Eliminare una stanza cancellerà tutti i messaggi in essa contenuti. Questa azione non può essere annullata.", - "Department": "Dipartimento", - "delete": "elimina", - "Delete": "Elimina", - "DELETE": "ELIMINA", - "deleting_room": "cancellazione stanza", - "description": "descrizione", - "Description": "Descrizione", - "Desktop_Options": "Opzioni Desktop", - "Desktop_Notifications": "Notifiche Desktop", - "Desktop_Alert_info": "Queste notifiche vengono inviate sul client desktop", - "Directory": "Rubrica", - "Direct_Messages": "Messaggi diretti", - "Disable_notifications": "Disabilita notifiche", - "Discussions": "Discussioni", - "Discussion_Desc": "Aiuta a mantenere una panoramica di ciò che sta succedendo! Creando una discussione verrà creato un sotto-canale di quello selezionato ed entrambi saranno collegati", - "Discussion_name": "Nome della discussione", - "Done": "Fatto", - "Dont_Have_An_Account": "Non hai un account?", - "Do_you_have_an_account": "Hai un account?", - "Do_you_have_a_certificate": "Hai un certificato?", - "Do_you_really_want_to_key_this_room_question_mark": "Sei sicuro di voler {{key}} questa stanza?", - "E2E_Encryption": "Crittografia E2E", - "E2E_How_It_Works_info1": "Ora puoi creare gruppi e messaggi privati crittografati. Puoi anche crittografare quelli già esistenti.", - "E2E_How_It_Works_info2": "Questa è *crittografia end-to-end*, quindi la chiave per cifrare/decifrare i messaggi non verrà salvata sul server. Per questo motivo *devi salvare questa password in un luogo sicuro* dove poterla recuperare in seguito qualora necessario.", - "E2E_How_It_Works_info3": "Procedendo verrà generata automaticamente una password E2E.", - "E2E_How_It_Works_info4": "Puoi impostare una nuova password per la chiave di cifratura in qualsiasi momento da uno dei browser dove hai inserito la password E2E esistente.", - "edit": "modifica", - "edited": "modificato", - "Edit": "Modifica", - "Edit_Status": "Modifica Stato", - "Edit_Invite": "Modifica invito", - "End_to_end_encrypted_room": "Stanza crittografata end to end", - "end_to_end_encryption": "Crittografia end to end", - "Email_Notification_Mode_All": "Ogni Menzione/Messaggio Privato", - "Email_Notification_Mode_Disabled": "Disabilitato", - "Email_or_password_field_is_empty": "Il campo e-mail o password sono vuoti", - "Email": "E-mail", - "email": "e-mail", - "Empty_title": "Titolo vuoto", - "Enable_Auto_Translate": "Abilita traduzione automatica", - "Enable_notifications": "Abilita notifiche", - "Encrypted": "Crittografato", - "Encrypted_message": "Messaggio crittografato", - "Enter_Your_E2E_Password": "Inserisci la tua password E2E", - "Enter_Your_Encryption_Password_desc1": "Potrai così accedere ai tuoi gruppi privati e messaggi privati crittografati.", - "Enter_Your_Encryption_Password_desc2": "Devi inserire la password per cifrare/decifrare i messaggi ovunque usi la chat.", - "Encryption_error_title": "La tua password di cifratura sembra errata", - "Encryption_error_desc": "Non è stato possibile importare la tua chiave di cifratura.", - "Everyone_can_access_this_channel": "Tutti hanno accesso a questo canale", - "Error_uploading": "Errore nel caricamento di", - "Expiration_Days": "Scadenza (giorni)", - "Favorite": "Preferito", - "Favorites": "Preferiti", - "Files": "File", - "File_description": "Descrizione file", - "File_name": "Nome file", - "Finish_recording": "Termina registrazione", - "Following_thread": "Thread seguito", - "For_your_security_you_must_enter_your_current_password_to_continue": "Per garantire la sicurezza del tuo account, inserisci la password per continuare.", - "Forgot_password_If_this_email_is_registered": "Se questa e-mail è registrata, manderemo istruzioni su come resettare la tua password. Se non ricevi nulla, torna qui e riprova di nuovo.", - "Forgot_password": "Password dimenticata", - "Forgot_Password": "Password dimenticata", - "Forward": "Inoltra", - "Forward_Chat": "Inoltra Chat", - "Forward_to_department": "Inoltra a dipartimento", - "Forward_to_user": "Inoltra ad udente", - "Full_table": "Clicca per la tabella completa", - "Generate_New_Link": "Genera nuovo link", - "Group_by_favorites": "Raggruppa per preferiti", - "Group_by_type": "Raggruppa per tipo", - "Hide": "Nascondi", - "Has_joined_the_channel": "si è unito al canale", - "Has_joined_the_conversation": "si è unito alla conversazione", - "Has_left_the_channel": "ha lasciato il canale", - "Hide_System_Messages": "Nascondi messaggi di sistema", - "Hide_type_messages": "Nascondi messaggi di \"{{type}}\"", - "How_It_Works": "Come funziona", - "Message_HideType_uj": "Ingresso Utente", - "Message_HideType_ul": "Uscita Utente", - "Message_HideType_ru": "Rimozione Utente", - "Message_HideType_au": "Aggiunta Utente", - "Message_HideType_mute_unmute": "Microfono Utente attivato / disattivato", - "Message_HideType_r": "Nome Stanza cambiato", - "Message_HideType_ut": "Ingresso Utente in Conversazione", - "Message_HideType_wm": "Benvenuto", - "Message_HideType_rm": "Rimozione Messaggio", - "Message_HideType_subscription_role_added": "Creazione ruolo", - "Message_HideType_subscription_role_removed": "Rimozione ruolo", - "Message_HideType_room_archived": "Stanza archiviata", - "Message_HideType_room_unarchived": "Stanza ripristinata dall'archivio", - "I_Saved_My_E2E_Password": "Ho salvato la mia Password E2E", - "IP": "Indirizzo IP", - "In_app": "In-app", - "In_App_And_Desktop": "In-app e Desktop", - "In_App_and_Desktop_Alert_info": "Mostra una notifica in cima allo schermo quando l'app è aperta, e mostra una notifica sul desktop", - "Invisible": "Invisibile", - "Invite": "Invita", - "is_a_valid_RocketChat_instance": "è un'istanza di Rocket.Chat valida", - "is_not_a_valid_RocketChat_instance": "non è una valida istanza di Rocket.Chat", - "is_typing": "sta scrivendo", - "Invalid_or_expired_invite_token": "Token di invito non valido o scaduto", - "Invalid_server_version": "Il server a cui stai cercando di connetterti sta utilizzando una versione non più supportata dall'app: {{currentVersion}}.\n\nVersione minima richiesta: {{minVersion}}", - "Invite_Link": "Link di invito", - "Invite_users": "Invita utenti", - "Join": "Entra", - "Join_Code": "Codice d'accesso", - "Insert_Join_Code": "Inserisci il codice d'accesso", - "Join_our_open_workspace": "Unisciti al nostro workspace", - "Join_your_workspace": "Unisciti al tuo workspace", - "Just_invited_people_can_access_this_channel": "Solo le persone invitate possono accedere a questo canale", - "Language": "Lingua", - "last_message": "ultimo messaggio", - "Leave_channel": "Abbandona canale", - "leaving_room": "abbandonando stanza", - "Leave": "Lasciare il canale", - "leave": "abbandona", - "Legal": "Informazioni", - "Light": "Chiaro", - "License": "Licenza", - "Livechat_edit": "Modifica Livechat", - "Login": "Accedi", - "Login_error": "Le tue credenziali sono state rifiutate! Prova di nuovo.", - "Login_with": "Accedi con", - "Logging_out": "Disconnettendo.", - "Logout": "Disconnetti", - "Max_number_of_uses": "Max numero di utilizzi", - "Max_number_of_users_allowed_is_number": "Il numero massimo di utenti ammessi è {{maxUsers}}", - "members": "membri", - "Members": "Membri", - "Mentioned_Messages": "Messaggi menzionati", - "mentioned": "menzionato", - "Mentions": "Menzioni", - "Message_accessibility": "Messaggio da {{user}} alle {{time}}: {{message}}", - "Message_actions": "Azioni messaggio", - "Message_pinned": "Messaggio appuntato", - "Message_removed": "Messaggio rimosso", - "Message_starred": "Messaggio importante", - "Message_unstarred": "Messaggio non importante", - "message": "messaggio", - "messages": "messaggi", - "Message": "Messaggio", - "Messages": "Messaggi", - "Message_Reported": "Messaggio segnalato", - "Microphone_Permission_Message": "Rocket.Chat richiede l'accesso al microfono per inviare messaggi audio.", - "Microphone_Permission": "Permesso microfono", - "Mute": "Silenzia", - "muted": "silenziato", - "My_servers": "I miei server", - "N_people_reacted": "{{n}} persone hanno reagito", - "N_users": "{{n}} utenti", - "name": "nome", - "Name": "Nome", - "Navigation_history": "Cronologia di navigazione", - "Never": "Mai", - "New_Message": "Nuovo messaggio", - "New_Password": "Nuova password", - "New_Server": "Nuovo server", - "Next": "Successivo", - "No_files": "Nessun file", - "No_limit": "Nessun limite", - "No_mentioned_messages": "Nessun messaggio menzionato", - "No_pinned_messages": "Nessun messaggio attaccato", - "No_results_found": "Nessun risultato", - "No_starred_messages": "Nessun messaggio preferito", - "No_thread_messages": "Nessun messaggio thread", - "No_label_provided": "Nessun {{label}} fornito.", - "No_Message": "Nessun messaggio", - "No_messages_yet": "Non ci sono ancora messaggi", - "No_Reactions": "Nessuna reazione", - "No_Read_Receipts": "Nessuna conferma di lettura", - "Not_logged": "Non loggato", - "Not_RC_Server": "Questo non è un server di Rocket.Chat.\n{{contact}}", - "Nothing": "Niente", - "Nothing_to_save": "Niente da salvare!", - "Notify_active_in_this_room": "Notifica solo gli utenti attivi in questa stanza", - "Notify_all_in_this_room": "Notifica tutti gli utenti in questa stanza", - "Notifications": "Notifiche", - "Notification_Duration": "Durata notifiche", - "Notification_Preferences": "Impostazioni notifiche", - "No_available_agents_to_transfer": "Nessun agente disponibile a cui trasferire", - "Offline": "Offline", - "Oops": "Oops!", - "Omnichannel": "Omnichannel", - "Open_Livechats": "Chat in corso", - "Omnichannel_enable_alert": "Non sei ancora su Onmichannel. Vuoi attivarlo?", - "Onboarding_description": "Un workspace è lo spazio dove il tuo team o la tua organizzazione possono collaborare. Chiedi l'indirizzo all'amministratore del tuo workspace oppure creane uno per il tuo team.", - "Onboarding_join_workspace": "Unisciti", - "Onboarding_subtitle": "Oltre la collaborazione di gruppo", - "Onboarding_title": "Benvenuto in Rocket.Chat", - "Onboarding_join_open_description": "Unisciti al nostro workspace per parlare con il team e la community di Rocket.Chat.", - "Onboarding_agree_terms": "Procedendo, accetti Rocket.Chat", - "Onboarding_less_options": "Meno opzioni", - "Onboarding_more_options": "Più opzioni", - "Online": "Online", - "Only_authorized_users_can_write_new_messages": "Solo gli utenti autorizzati possono scrivere nuovi messaggi", - "Open_emoji_selector": "Apri selettore emoji", - "Open_Source_Communication": "Comunicazione open-source", - "Open_your_authentication_app_and_enter_the_code": "Apri l'app di autenticazione e inserisci il codice.", - "OR": "O", - "OS": "SO", - "Overwrites_the_server_configuration_and_use_room_config": "Sovrascrive la configurazione del server in favore di quella della stanza", - "Password": "Password", - "Parent_channel_or_group": "Gruppo o canale originario", - "Permalink_copied_to_clipboard": "Permalink copiato negli appunti!", - "Phone": "Telefono", - "Pin": "Appunta", - "Pinned_Messages": "Messaggi appuntati", - "pinned": "appuntati", - "Pinned": "Appuntati", - "Please_add_a_comment": "Per favore, aggiungi un commento", - "Please_enter_your_password": "Per favore, inserisci la tua password", - "Please_wait": "Si prega di attendere.", - "Preferences": "Impostazioni", - "Preferences_saved": "Impostazioni salvate!", - "Privacy_Policy": " Privacy Policy", - "Private_Channel": "Canale privato", - "Private": "Privato", - "Processing": "Elaborazione...", - "Profile_saved_successfully": "Profilo salvato correttamente!", - "Profile": "Profilo", - "Public_Channel": "Canale pubblico", - "Public": "Pubblico", - "Push_Notifications": "Notifiche Push", - "Push_Notifications_Alert_Info": "Queste notifiche ti vengono recapitate quando l'app non è aperta", - "Quote": "Cita", - "Reactions_are_disabled": "Le reazioni sono disabilitate", - "Reactions_are_enabled": "Le reazioni sono abilitate", - "Reactions": "Reazioni", - "Read": "Letto", - "Read_External_Permission_Message": "Rocket.Chat deve accedere alle foto, media, e documenti sul tuo dispositivo", - "Read_External_Permission": "Permesso di Lettura della Memoria", - "Read_Only_Channel": "Canale in sola lettura", - "Read_Only": "Sola lettura", - "Read_Receipt": "Conferma di lettura", - "Receive_Group_Mentions": "Ricevi menzioni di gruppo", - "Receive_Group_Mentions_Info": "Ricevi menzioni @all e @here", - "Register": "Registrati", - "Repeat_Password": "Conferma password", - "Replied_on": "Risposto il:", - "replies": "risposte", - "reply": "risposta", - "Reply": "Rispondi", - "Report": "Segnala", - "Receive_Notification": "Ricevi notifiche", - "Receive_notifications_from": "Ricevi notifiche da {{name}}", - "Resend": "Invia di nuovo", - "Reset_password": "Ripristina password", - "resetting_password": "ripristinando password", - "RESET": "RIPRISTINA", - "Return": "Ritorno", - "Review_app_title": "Ti piace questa app?", - "Review_app_desc": "Dacci 5 stesse su {{store}}", - "Review_app_yes": "Certo!", - "Review_app_no": "No", - "Review_app_later": "In seguito", - "Review_app_unable_store": "Impossibile aprire {{store}}", - "Review_this_app": "Recensisci questa app", - "Remove": "Rimuovi", - "Roles": "Ruoli", - "Room_actions": "Azioni stanza", - "Room_changed_announcement": "Annuncio stanza cambiato in: {{announcement}} da {{userBy}}", - "Room_changed_avatar": "Immagine stanza cambiata da {{userBy}}", - "Room_changed_description": "Descrizione stanza cambiata in: {{description}} da {{userBy}}", - "Room_changed_privacy": "Tipo stanza cambiato in: {{type}} da {{userBy}}", - "Room_changed_topic": "Argomento stanza cambiato in: {{topic}} da {{userBy}}", - "Room_Files": "File stanza", - "Room_Info_Edit": "Modifica informazioni stanza", - "Room_Info": "Informazioni stanza", - "Room_Members": "Membri stanza", - "Room_name_changed": "Nome stanza cambiato in: {{name}} da {{userBy}}", - "SAVE": "SALVA", - "Save_Changes": "Salva cambiamenti", - "Save": "Salva", - "Saved": "Salvato", - "saving_preferences": "salvataggio impostazioni", - "saving_profile": "salvataggio profilo", - "saving_settings": "salvataggio impostazioni", - "saved_to_gallery": "Salvato in Galleria", - "Save_Your_E2E_Password": "Salva la tua Password E2E", - "Save_Your_Encryption_Password": "Salva la tua Password di cifratura", - "Save_Your_Encryption_Password_warning": "Questa password non è salvata da nessuna parte, conservala con cura.", - "Save_Your_Encryption_Password_info": "Nota: se dovessi perdere la tua password, non c'è alcun modo per recuperarla e perderesti l'accesso ai tuoi messaggi.", - "Search_Messages": "Cerca messaggi", - "Search": "Cerca", - "Search_by": "Cerca per", - "Search_global_users": "Cerca utenti globali", - "Search_global_users_description": "Se attivi questa opzione, puoi cercare qualsiasi utente da altre aziende o server.", - "Seconds": "{{second}} secondi", - "Security_and_privacy": "Sicurezza e privacy", - "Select_Avatar": "Seleziona avatar", - "Select_Server": "Seleziona server", - "Select_Users": "Seleziona utenti", - "Select_a_Channel": "Seleziona un Canale", - "Select_a_Department": "Seleziona un Dipartimento", - "Select_an_option": "Seleziona un' opzione", - "Select_a_User": "Seleziona un Utente", - "Send": "Invia", - "Send_audio_message": "Invia messaggio audio", - "Send_crash_report": "Invia report sui crash", - "Send_message": "Invia messaggio", - "Send_me_the_code_again": "Inviami nuovamente il codice", - "Send_to": "Invia a...", - "Sending_to": "Invio a", - "Sent_an_attachment": "Inviato un allegato", - "Server": "Server", - "Servers": "Servers", - "Server_version": "Versione server: {{version}}", - "Set_username_subtitle": "Il nome utente viene utilizzato per permettere ad altri di menzionarti nei messaggi", - "Set_custom_status": "Imposta stato personalizzato", - "Set_status": "Imposta stato", - "Status_saved_successfully": "Stato salvato correttamente!", - "Settings": "Impostazioni", - "Settings_succesfully_changed": "Impostazioni modificate correttamente!", - "Share": "Condividi", - "Share_Link": "Condividi link", - "Share_this_app": "Condividi questa app", - "Show_more": "Mostra altri..", - "Show_Unread_Counter": "Mostra contatore messaggi non letti", - "Show_Unread_Counter_Info": "Il contatore viene mostrato come un'etichetta alla destra del canale, nella lista", - "Sign_in_your_server": "Accedi al tuo server", - "Sign_Up": "Registrati", - "Some_field_is_invalid_or_empty": "Un campo non è valido o è vuoto", - "Sorting_by": "Ordina per {{key}}", - "Sound": "Suono", - "Star_room": "Aggiungi stanza ai preferiti", - "Star": "Aggiungi ai preferiti", - "Starred_Messages": "Messaggi preferiti", - "starred": "preferiti", - "Starred": "Preferiti", - "Start_of_conversation": "Inizio della conversazione", - "Start_a_Discussion": "Avvia una Discussione", - "Started_discussion": "Discussione iniziata:", - "Started_call": "Chiamata iniziata da {{userBy}}", - "Submit": "Invia", - "Table": "Tabella", - "Tags": "Tag", - "Take_a_photo": "Scatta una foto", - "Take_a_video": "Registra un video", - "Take_it": "Prendi!", - "tap_to_change_status": "tocca per cambiare stato", - "Tap_to_view_servers_list": "Tocca per vedere la lista server", - "Terms_of_Service": " Termini di servizio ", - "Theme": "Tema", - "The_user_wont_be_able_to_type_in_roomName": "L'utente non potrà scrivere in {{roomName}}", - "The_user_will_be_able_to_type_in_roomName": "L'utente potrà scrivere in {{roomName}}", - "There_was_an_error_while_action": "Si è verificato un errore nel {{action}}!", - "This_room_is_blocked": "Questa stanza è bloccata", - "This_room_is_read_only": "Questa stanza è in sola lettura", - "Thread": "Thread", - "Threads": "Threads", - "Timezone": "Fuso orario", - "To": "A", - "topic": "argomento", - "Topic": "Argomento", - "Translate": "Traduci", - "Try_again": "Riprova", - "Two_Factor_Authentication": "Autenticazione a due fattori", - "Type_the_channel_name_here": "Scrivi il nome del canale qui", - "unarchive": "rimuovi dall'archivio", - "UNARCHIVE": "RIMUOVI DALL'ARCHIVIO", - "Unblock_user": "Sblocca utente", - "Unfavorite": "Rimuovi preferito", - "Unfollowed_thread": "Non segui più il thread", - "Unmute": "Attiva notifiche", - "unmuted": "notifiche attivate", - "Unpin": "Stacca", - "unread_messages": "non letti", - "Unread": "Non letto", - "Unread_on_top": "Non letti sopra", - "Unstar": "Rimuovi dai preferiti", - "Updating": "Aggiornamento...", - "Uploading": "Caricamento", - "Upload_file_question_mark": "Carica file?", - "User": "Utente", - "Users": "Utenti", - "User_added_by": "Utente {{userAdded}} aggiunto da {{userBy}}", - "User_Info": "Informazioni utente", - "User_has_been_key": "Utente {{key}}", - "User_is_no_longer_role_by_": "{{user}} non è più {{role}} da {{userBy}}", - "User_muted_by": "Utente {{userMuted}} silenziato da {{userBy}}", - "User_removed_by": "Utente {{userRemoved}} rimosso da {{userBy}}", - "User_sent_an_attachment": "{{user}} ha inviato un allegato", - "User_unmuted_by": "Utente {{userUnmuted}} de-silenziato da {{userBy}}", - "User_was_set_role_by_": "{{user}} è stato impostato come {{role}} da {{userBy}}", - "Username_is_empty": "Username vuoto", - "Username": "Username", - "Username_or_email": "Username o email", - "Uses_server_configuration": "Usa la configurazione del server", - "Validating": "Validazione", - "Registration_Succeeded": "Registrazione completata!", - "Verify": "Verifica", - "Verify_email_title": "Verifica completata!", - "Verify_email_desc": "Ti abbiamo inviato una e-mail per confermare la tua registrazione. Se non la ricevi, ritorna qui e riprova", - "Verify_your_email_for_the_code_we_sent": "Controlla l'e-mail con il codice che ti abbiamo inviato", - "Video_call": "Videochiamata", - "View_Original": "Mostra originale", - "Voice_call": "Chiamata vocale", - "Waiting_for_network": "In attesa di connessione ...", - "Websocket_disabled": "Websocket disabilitata per questo server.\n{{contact}}", - "Welcome": "Benvenuto", - "What_are_you_doing_right_now": "Cosa stai facendo in questo momento?", - "Whats_your_2fa": "Qual'è il tuo codice 2FA?", - "Without_Servers": "Senza server", - "Workspaces": "Workspace", - "Would_you_like_to_return_the_inquiry": "Vorresti ritirare la tua domanda?", - "Write_External_Permission_Message": "Rocket.Chat ha bisogno dell'accesso alla galleria per salvare le immagini.", - "Write_External_Permission": "Permesso galleria", - "Yes": "Si", - "Yes_action_it": "Sì, {{action}}!", - "Yesterday": "Ieri", - "You_are_in_preview_mode": "Sei in modalità anteprima", - "You_are_offline": "Sei offline", - "You_can_search_using_RegExp_eg": "Puoi usare espressioni regolari. es. `/^testo$/i`", - "You_colon": "Tu: ", - "you_were_mentioned": "sei stato menzionato", - "You_were_removed_from_channel": "Sei stato rimosso da {{channel}}", - "you": "tu", - "You": "Tu", - "Logged_out_by_server": "Sei stato disconnesso dal server. Esegui nuovamente l'accesso.", - "You_need_to_access_at_least_one_RocketChat_server_to_share_something": "Devi accedere ad almeno un server Rocket.Chat prima di condividere qualcosa.", - "You_need_to_verifiy_your_email_address_to_get_notications": "Devi verificare il tuo indirizzo e-mail per ricevere le notifiche", - "Your_certificate": "Il tuo certificato", - "Your_invite_link_will_expire_after__usesLeft__uses": "Il tuo link di invito scadrà dopo {{usesLeft}} utilizzi.", - "Your_invite_link_will_expire_on__date__or_after__usesLeft__uses": "Il tuo link di invito scadrà il {{date}} oppure dopo {{usesLeft}} utilizzi.", - "Your_invite_link_will_expire_on__date__": "Il tuo link di invito scadrà il {{date}}.", - "Your_invite_link_will_never_expire": "Il tuo link di invito non scadrà mai.", - "Your_workspace": "Il tuo workspace", - "Your_password_is": "La tua password è", - "Version_no": "Versione: {{version}}", - "You_will_not_be_able_to_recover_this_message": "Non sarai in grado di ripristinare questo messaggio!", - "You_will_unset_a_certificate_for_this_server": "Rimuoverai un certificato per questo server", - "Change_Language": "Cambia lingua", - "Crash_report_disclaimer": "Non registreremo mai il contenuto delle tue chat. Il crash report contiene solo informazioni necessarie per l'identificazione e la risoluzione dei problemi.", - "Type_message": "Scrivi messaggio", - "Room_search": "Ricerca stanze", - "Room_selection": "Selezione stanza 1...9", - "Next_room": "Prossima stanza", - "Previous_room": "Stanza precedente", - "New_room": "Nuova stanza", - "Upload_room": "Carica nella stanza", - "Search_messages": "Cerca messaggi", - "Scroll_messages": "Scorri i messaggi", - "Reply_latest": "Rispondi all'ultimo", - "Reply_in_Thread": "Rispondi nella discussione", - "Server_selection": "Selezione server", - "Server_selection_numbers": "Selezione server 1...9", - "Add_server": "Aggiungi server", - "New_line": "Nuova linea", - "You_will_be_logged_out_of_this_application": "Verrai disconnesso da questa applicazione.", - "Clear": "Cancella", - "This_will_clear_all_your_offline_data": "Questo cancellerà tutti i tuoi dati offline.", - "This_will_remove_all_data_from_this_server": "Questo rimuoverà tutti i dati dal server.", - "Mark_unread": "Segna come non letto", - "Wait_activation_warning": "Prima di poter accedere, il tuo account deve essere attivato manualmente da un amministratore.", - "Screen_lock": "Blocco schermo", - "Local_authentication_biometry_title": "Autenticazione", - "Local_authentication_biometry_fallback": "Usa passcode", - "Local_authentication_unlock_option": "Sblocca con Passcode", - "Local_authentication_change_passcode": "Cambia Passcode", - "Local_authentication_info": "Nota: se dimentichi il Passcode, dovrai cancellare e installare nuovamente l'app.", - "Local_authentication_facial_recognition": "riconoscimento facciale", - "Local_authentication_fingerprint": "impronta digitale", - "Local_authentication_unlock_with_label": "Sblocca con {{label}}", - "Local_authentication_auto_lock_60": "Dopo 1 minuto", - "Local_authentication_auto_lock_300": "Dopo 5 minuti", - "Local_authentication_auto_lock_900": "Dopo 15 minuti", - "Local_authentication_auto_lock_1800": "Dopo 30 minuti", - "Local_authentication_auto_lock_3600": "Dopo 1 ora", - "Passcode_enter_title": "Inserisci il passcode", - "Passcode_choose_title": "Scegli il tuo nuovo passcode", - "Passcode_choose_confirm_title": "Conferma il tuo nuovo passcode", - "Passcode_choose_error": "I passcode non corrispondono. Riprova.", - "Passcode_choose_force_set": "Passcode richiesto dall'admin", - "Passcode_app_locked_title": "App bloccata", - "Passcode_app_locked_subtitle": "Riprova tra {{timeLeft}} secondi", - "After_seconds_set_by_admin": "Dopo {{seconds}} secondi (impostati dall'admin)", - "Dont_activate": "Non attivare ora", - "Queued_chats": "Chat in coda", - "Queue_is_empty": "La coda è vuota", - "Logout_from_other_logged_in_locations": "Disconnetti da altre postazioni", - "You_will_be_logged_out_from_other_locations": "Verrai disconnesso dalle altre postazioni.", - "Logged_out_of_other_clients_successfully": "Disconnesso dalle altre postazioni con successo", - "Logout_failed": "Disconnessione fallita!", - "Log_analytics_events": "Invia statistiche anonime", - "E2E_encryption_change_password_title": "Cambia la password di cifratura", - "E2E_encryption_change_password_description": "Ora puoi creare gruppi e messaggi privati crittografati. Potrai, inoltre, crittografare i tuoi gruppi e messaggi privati esistenti. \nQuesta è crittografia end-to-end, la chiave per codificare/decodificare i tuoi messaggi non verrà salvata sul server. Per questo motivo devi salvarla in un luogo sicuro. Ti verrà richiesto di inserirla sugli altri dispositivi sui quali vuoi usare la crittografia e2e.", - "E2E_encryption_change_password_error": "Si è verificato un errore durante il cambio della password della chiave E2E!", - "E2E_encryption_change_password_success": "La password della chiave E2E è stata cambiata con successo!", - "E2E_encryption_change_password_message": "Assicurati di salvarla in un posto sicuro.", - "E2E_encryption_change_password_confirmation": "Si, cambiala", - "E2E_encryption_reset_title": "Ripristina la Chiave E2E", - "E2E_encryption_reset_description": "Questa opzione rimuoverà la tua chiave E2E corrente e sarai disconnesso. \nAl prossimo login, Rocket.Chat genererà una nuova chiave e ripristinerà l'accesso a tutte le stanze crittografate con uno o più membri online. \nA causa della natura della crittografia E2E, Rocket.Chat non sarà in grado di ripristinare l'accesso alle stanze senza membri online.", - "E2E_encryption_reset_button": "Ripristina", - "E2E_encryption_reset_error": "Si è verificato un errore durante il ripristino della chiave E2E!", - "E2E_encryption_reset_message": "Stai per essere disconnesso.", - "E2E_encryption_reset_confirmation": "Si, resettala", - "Following": "Seguiti", - "Threads_displaying_all": "Visualizza Tutti", - "Threads_displaying_following": "Visualizza Seguiti", - "Threads_displaying_unread": "Visualizza Non letti", - "No_threads": "Non ci sono thread", - "No_threads_following": "Non stai seguendo alcun thread", - "No_threads_unread": "Non ci sono thread non letti", - "Messagebox_Send_to_channel": "Invia sul canale", - "Remove_from_room": "Rimuovi dalla stanza", - "Ignore": "Ignora", - "Unignore": "Non ignorare", - "User_has_been_ignored": "Utente ignorato", - "User_has_been_unignored": "Utente non ignorato", - "User_has_been_removed_from_s": "L'utente è stato rimosso da {{s}}", - "User__username__is_now_a_leader_of__room_name_": "L'utente {{username}} è ora un leader di {{room_name}}", - "User__username__is_now_a_moderator_of__room_name_": "L'utente {{username}} è ora un moderatore di {{room_name}}", - "User__username__is_now_a_owner_of__room_name_": "L'utente {{username}} è ora un proprietario di {{room_name}}", - "User__username__removed_from__room_name__leaders": "L'utente {{username}} non è più un leader di {{room_name}}", - "User__username__removed_from__room_name__moderators": "L'utente {{username}} non è più un moderatore di {{room_name}}", - "User__username__removed_from__room_name__owners": "L'utente {{username}} non è più un proprietario di {{room_name}}", - "The_user_will_be_removed_from_s": "L'utente sarà rimosso da {{s}}", - "Yes_remove_user": "Si, rimuovi utente!", - "Direct_message": "Messaggio diretto", - "Message_Ignored": "Messaggio ignorato. Tocca per visualizzarlo.", - "Enter_workspace_URL": "Inserisci la url del workspace", - "Workspace_URL_Example": "Es. tua-azienda.rocket.chat", - "invalid-room": "Canale non valido" -} \ No newline at end of file + "1_person_reacted": "1 persona ha reagito", + "1_user": "1 utente", + "error-action-not-allowed": "{{action}} non autorizzata", + "error-application-not-found": "Applicazione non trovata", + "error-archived-duplicate-name": "Esiste già un canale archiviato con nome {{room_name}}", + "error-avatar-invalid-url": "URL avatar non valido: {{url}}", + "error-avatar-url-handling": "Errore nella gestione dell'impostazione avatar dall'URL ({{url}}) per {{username}}", + "error-cant-invite-for-direct-room": "Impossibile invitare l'utente alle stanze dirette", + "error-could-not-change-email": "Impossibile cambiare l'indirizzo e-mail", + "error-could-not-change-name": "Impossibile cambiare nome", + "error-could-not-change-username": "Impossibile cambiare username", + "error-could-not-change-status": "Impossibile cambiare lo stato", + "error-delete-protected-role": "Impossibile eliminare un ruolo protetto", + "error-department-not-found": "Reparto non trovato", + "error-direct-message-file-upload-not-allowed": "La condivisione di file non è autorizzata nei messaggi diretti", + "error-duplicate-channel-name": "Esiste già un canale con nome {{channel_name}}", + "error-email-domain-blacklisted": "Il dominio e-mail è nella lista nera", + "error-email-send-failed": "Errore nel tentativo di invio e-mail: {{message}}", + "error-save-image": "Errore nel salvataggio dell'immagine", + "error-save-video": "Errore nel salvataggio del video", + "error-field-unavailable": "{{field}} è già in uso :(", + "error-file-too-large": "File troppo grande", + "error-importer-not-defined": "L'importatore non è stato definito correttamente: classe Import mancante.", + "error-input-is-not-a-valid-field": "{{input}} non è valido come {{field}}", + "error-invalid-actionlink": "Link azione non valido", + "error-invalid-arguments": "Parametri non validi", + "error-invalid-asset": "Risorsa non valida", + "error-invalid-channel": "Canale non valido.", + "error-invalid-channel-start-with-chars": "Canale non valido. Inizia con @ o #", + "error-invalid-custom-field": "Campo personalizzato non valido", + "error-invalid-custom-field-name": "Nome campo personalizzato non valido. Usa solo lettere, numeri, trattini e underscore.", + "error-invalid-date": "Data fornita non valida.", + "error-invalid-description": "Descrizione non valida", + "error-invalid-domain": "Dominio non valido", + "error-invalid-email": "E-mail {{email}} non valida", + "error-invalid-email-address": "Indirizzo e-mail non valido", + "error-invalid-file-height": "Altezza del file non valida", + "error-invalid-file-type": "Tipo di file non valido", + "error-invalid-file-width": "Larghezza del file non valida", + "error-invalid-from-address": "Hai informato un indirizzo FROM non valido.", + "error-invalid-integration": "Integrazione non valida", + "error-invalid-message": "Messaggio non valido", + "error-invalid-method": "Metodo o funzione non valida", + "error-invalid-name": "Nome non corretto", + "error-invalid-password": "Password non corretta", + "error-invalid-redirectUri": "redirectUri non valido", + "error-invalid-role": "Ruolo non valido", + "error-invalid-room": "Stanza non valida", + "error-invalid-room-name": "{{room_name}} non è un nome di stanza valido", + "error-invalid-room-type": "{{type}} non è un tipo di stanza valido", + "error-invalid-settings": "Impostazioni fornite non valide", + "error-invalid-subscription": "Iscrizione non valida", + "error-invalid-token": "Token non valido", + "error-invalid-triggerWords": "triggerWords non valide", + "error-invalid-urls": "URL non validi", + "error-invalid-user": "Utente non valido", + "error-invalid-username": "Nome utente non valido", + "error-invalid-webhook-response": "L'URL del webhook ha risposto con uno stato diverso da 200", + "error-message-deleting-blocked": "Cancellazione di messaggi bloccata", + "error-message-editing-blocked": "Modifica di messaggi bloccata", + "error-message-size-exceeded": "La dimensione del messaggio supera Message_MaxAllowedSize", + "error-missing-unsubscribe-link": "Devi fornire il link [unsubscribe].", + "error-no-tokens-for-this-user": "Non ci sono token per questo utente", + "error-not-allowed": "Non permesso", + "error-not-authorized": "Non autorizzato", + "error-push-disabled": "Push è disabilitato", + "error-remove-last-owner": "Questo è l'ultimo proprietario rimasto. Imposta un nuovo proprietario prima di rimuoverlo.", + "error-role-in-use": "Impossibile eliminare il ruolo perchè ancora in uso", + "error-role-name-required": "Il nome del ruolo è obbligatorio", + "error-the-field-is-required": "Il campo {{field}} è obbligatorio.", + "error-too-many-requests": "Errore, troppe richieste effettuate. Rallenta. Devi attendere {{seconds}} secondi prima di riprovare.", + "error-user-is-not-activated": "L'utente non è attivato", + "error-user-has-no-roles": "L'utente non ha ruoli", + "error-user-limit-exceeded": "Il numero di utenti che stai invitando in #channel_name supera il limite imposto dall'amministratore", + "error-user-not-in-room": "L'utente non è in questa stanza", + "error-user-registration-custom-field": "error-user-registration-custom-field", + "error-user-registration-disabled": "Registrazione utente disabilitata", + "error-user-registration-secret": "Registrazione utente permessa solo via URL segreto", + "error-you-are-last-owner": "Sei l'ultimo proprietario rimasto. Imposta un nuovo proprietario prima di lasciare la stanza.", + "Actions": "Azioni", + "activity": "attività", + "Activity": "Attività", + "Add_Reaction": "Aggiungi reazione", + "Add_Server": "Aggiungi server", + "Add_users": "Aggiungi utenti", + "Admin_Panel": "Amministrazione", + "Agent": "Agente", + "Alert": "Avviso", + "alert": "avviso", + "alerts": "avvisi", + "All_users_in_the_channel_can_write_new_messages": "Tutti gli utenti nel canale possono scrivere messaggi", + "A_meaningful_name_for_the_discussion_room": "Un nome significativo per il canale di discussione", + "All": "Tutti", + "All_Messages": "Tutti i messaggi", + "Allow_Reactions": "Permetti reazioni", + "Alphabetical": "Alfabetico", + "and_more": "e altro", + "and": "e", + "announcement": "annuncio", + "Announcement": "Annuncio", + "Apply_Your_Certificate": "Applica il tuo certificato", + "ARCHIVE": "ARCHIVIO", + "archive": "archivio", + "are_typing": "stanno scrivendo", + "Are_you_sure_question_mark": "Sei sicuro?", + "Are_you_sure_you_want_to_leave_the_room": "Sei sicuro di voler lasciare la stanza {{room}}?", + "Audio": "Audio", + "Authenticating": "Autenticazione", + "Automatic": "Automatico", + "Auto_Translate": "Traduzione automatica", + "Avatar_changed_successfully": "Avatar aggiornato correttamente!", + "Avatar_Url": "URL avatar", + "Away": "Assente", + "Back": "Indietro", + "Black": "Nero", + "Block_user": "Blocca utente", + "Browser": "Browser", + "Broadcast_channel_Description": "Solo gli utenti autorizzati possono scrivere messaggi, ma gli altri utenti saranno in grado di rispondere", + "Broadcast_Channel": "Canale broadcast", + "Busy": "Occupato", + "By_proceeding_you_are_agreeing": "Procedendo accetti i nostri", + "Cancel_editing": "Annulla modifica", + "Cancel_recording": "Annulla registrazione", + "Cancel": "Annulla", + "changing_avatar": "cambio avatar", + "creating_channel": "creo canale", + "creating_invite": "creo invito", + "Channel_Name": "Nome canale", + "Channels": "Canali", + "Chats": "Chat", + "Call_already_ended": "Chiamata già terminata!", + "Clear_cookies_alert": "Vuoi cancellare tutti i cookie?", + "Clear_cookies_desc": "Questo cancellerà tutti i coockie di login, permettendoti di accedere con altri account.", + "Clear_cookies_yes": "Si, cancella i cookie", + "Clear_cookies_no": "No, mantieni i cookie", + "Click_to_join": "Clicca per unirti!", + "Close": "Chiudi", + "Close_emoji_selector": "Chiudi selettore emoji", + "Closing_chat": "Chiudendo la chat", + "Change_language_loading": "Cambiando la lingua.", + "Chat_closed_by_agent": "Chat chiusa dall'agente", + "Choose": "Scegli", + "Choose_from_library": "Scegli dalla libreria", + "Choose_file": "Scegli file", + "Choose_where_you_want_links_be_opened": "Scegli dove vuoi che vengano aperti i link", + "Code": "Codice", + "Code_or_password_invalid": "Codice o password non validi", + "Collaborative": "Collaborativo", + "Confirm": "Conferma", + "Connect": "Connetti", + "Connected": "Connesso", + "connecting_server": "connessione al server", + "Connecting": "Connessione...", + "Contact_us": "Contattaci", + "Contact_your_server_admin": "Contatta l'amministratore.", + "Continue_with": "Continua con", + "Copied_to_clipboard": "Copiato negli appunti!", + "Copy": "Copia", + "Conversation": "Conversazione", + "Permalink": "Permalink", + "Certificate_password": "Password certificato", + "Clear_cache": "Cancella la cache locale", + "Clear_cache_loading": "Cancellando la cache.", + "Whats_the_password_for_your_certificate": "Qual'è la password del tuo certificato?", + "Create_account": "Crea un account", + "Create_Channel": "Crea canale", + "Create_Direct_Messages": "Crea Messaggi Privati", + "Create_Discussion": "Crea una Discussione", + "Created_snippet": "Snippet creato", + "Create_a_new_workspace": "Crea un nuovo workspace", + "Create": "Crea", + "Custom_Status": "Stato personalizzato", + "Dark": "Scuro", + "Dark_level": "Contrasto", + "Default": "Predefinito", + "Default_browser": "Browser predefinito", + "Delete_Room_Warning": "Eliminare una stanza cancellerà tutti i messaggi in essa contenuti. Questa azione non può essere annullata.", + "Department": "Dipartimento", + "delete": "elimina", + "Delete": "Elimina", + "DELETE": "ELIMINA", + "deleting_room": "cancellazione stanza", + "description": "descrizione", + "Description": "Descrizione", + "Desktop_Options": "Opzioni Desktop", + "Desktop_Notifications": "Notifiche Desktop", + "Desktop_Alert_info": "Queste notifiche vengono inviate sul client desktop", + "Directory": "Rubrica", + "Direct_Messages": "Messaggi diretti", + "Disable_notifications": "Disabilita notifiche", + "Discussions": "Discussioni", + "Discussion_Desc": "Aiuta a mantenere una panoramica di ciò che sta succedendo! Creando una discussione verrà creato un sotto-canale di quello selezionato ed entrambi saranno collegati", + "Discussion_name": "Nome della discussione", + "Done": "Fatto", + "Dont_Have_An_Account": "Non hai un account?", + "Do_you_have_an_account": "Hai un account?", + "Do_you_have_a_certificate": "Hai un certificato?", + "Do_you_really_want_to_key_this_room_question_mark": "Sei sicuro di voler {{key}} questa stanza?", + "E2E_Encryption": "Crittografia E2E", + "E2E_How_It_Works_info1": "Ora puoi creare gruppi e messaggi privati crittografati. Puoi anche crittografare quelli già esistenti.", + "E2E_How_It_Works_info2": "Questa è *crittografia end-to-end*, quindi la chiave per cifrare/decifrare i messaggi non verrà salvata sul server. Per questo motivo *devi salvare questa password in un luogo sicuro* dove poterla recuperare in seguito qualora necessario.", + "E2E_How_It_Works_info3": "Procedendo verrà generata automaticamente una password E2E.", + "E2E_How_It_Works_info4": "Puoi impostare una nuova password per la chiave di cifratura in qualsiasi momento da uno dei browser dove hai inserito la password E2E esistente.", + "edit": "modifica", + "edited": "modificato", + "Edit": "Modifica", + "Edit_Status": "Modifica Stato", + "Edit_Invite": "Modifica invito", + "End_to_end_encrypted_room": "Stanza crittografata end to end", + "end_to_end_encryption": "Crittografia end to end", + "Email_Notification_Mode_All": "Ogni Menzione/Messaggio Privato", + "Email_Notification_Mode_Disabled": "Disabilitato", + "Email_or_password_field_is_empty": "Il campo e-mail o password sono vuoti", + "Email": "E-mail", + "email": "e-mail", + "Empty_title": "Titolo vuoto", + "Enable_Auto_Translate": "Abilita traduzione automatica", + "Enable_notifications": "Abilita notifiche", + "Encrypted": "Crittografato", + "Encrypted_message": "Messaggio crittografato", + "Enter_Your_E2E_Password": "Inserisci la tua password E2E", + "Enter_Your_Encryption_Password_desc1": "Potrai così accedere ai tuoi gruppi privati e messaggi privati crittografati.", + "Enter_Your_Encryption_Password_desc2": "Devi inserire la password per cifrare/decifrare i messaggi ovunque usi la chat.", + "Encryption_error_title": "La tua password di cifratura sembra errata", + "Encryption_error_desc": "Non è stato possibile importare la tua chiave di cifratura.", + "Everyone_can_access_this_channel": "Tutti hanno accesso a questo canale", + "Error_uploading": "Errore nel caricamento di", + "Expiration_Days": "Scadenza (giorni)", + "Favorite": "Preferito", + "Favorites": "Preferiti", + "Files": "File", + "File_description": "Descrizione file", + "File_name": "Nome file", + "Finish_recording": "Termina registrazione", + "Following_thread": "Thread seguito", + "For_your_security_you_must_enter_your_current_password_to_continue": "Per garantire la sicurezza del tuo account, inserisci la password per continuare.", + "Forgot_password_If_this_email_is_registered": "Se questa e-mail è registrata, manderemo istruzioni su come resettare la tua password. Se non ricevi nulla, torna qui e riprova di nuovo.", + "Forgot_password": "Password dimenticata", + "Forgot_Password": "Password dimenticata", + "Forward": "Inoltra", + "Forward_Chat": "Inoltra Chat", + "Forward_to_department": "Inoltra a dipartimento", + "Forward_to_user": "Inoltra ad udente", + "Full_table": "Clicca per la tabella completa", + "Generate_New_Link": "Genera nuovo link", + "Group_by_favorites": "Raggruppa per preferiti", + "Group_by_type": "Raggruppa per tipo", + "Hide": "Nascondi", + "Has_joined_the_channel": "si è unito al canale", + "Has_joined_the_conversation": "si è unito alla conversazione", + "Has_left_the_channel": "ha lasciato il canale", + "Hide_System_Messages": "Nascondi messaggi di sistema", + "Hide_type_messages": "Nascondi messaggi di \"{{type}}\"", + "How_It_Works": "Come funziona", + "Message_HideType_uj": "Ingresso Utente", + "Message_HideType_ul": "Uscita Utente", + "Message_HideType_ru": "Rimozione Utente", + "Message_HideType_au": "Aggiunta Utente", + "Message_HideType_mute_unmute": "Microfono Utente attivato / disattivato", + "Message_HideType_r": "Nome Stanza cambiato", + "Message_HideType_ut": "Ingresso Utente in Conversazione", + "Message_HideType_wm": "Benvenuto", + "Message_HideType_rm": "Rimozione Messaggio", + "Message_HideType_subscription_role_added": "Creazione ruolo", + "Message_HideType_subscription_role_removed": "Rimozione ruolo", + "Message_HideType_room_archived": "Stanza archiviata", + "Message_HideType_room_unarchived": "Stanza ripristinata dall'archivio", + "I_Saved_My_E2E_Password": "Ho salvato la mia Password E2E", + "IP": "Indirizzo IP", + "In_app": "In-app", + "In_App_And_Desktop": "In-app e Desktop", + "In_App_and_Desktop_Alert_info": "Mostra una notifica in cima allo schermo quando l'app è aperta, e mostra una notifica sul desktop", + "Invisible": "Invisibile", + "Invite": "Invita", + "is_a_valid_RocketChat_instance": "è un'istanza di Rocket.Chat valida", + "is_not_a_valid_RocketChat_instance": "non è una valida istanza di Rocket.Chat", + "is_typing": "sta scrivendo", + "Invalid_or_expired_invite_token": "Token di invito non valido o scaduto", + "Invalid_server_version": "Il server a cui stai cercando di connetterti sta utilizzando una versione non più supportata dall'app: {{currentVersion}}.\n\nVersione minima richiesta: {{minVersion}}", + "Invite_Link": "Link di invito", + "Invite_users": "Invita utenti", + "Join": "Entra", + "Join_Code": "Codice d'accesso", + "Insert_Join_Code": "Inserisci il codice d'accesso", + "Join_our_open_workspace": "Unisciti al nostro workspace", + "Join_your_workspace": "Unisciti al tuo workspace", + "Just_invited_people_can_access_this_channel": "Solo le persone invitate possono accedere a questo canale", + "Language": "Lingua", + "last_message": "ultimo messaggio", + "Leave_channel": "Abbandona canale", + "leaving_room": "abbandonando stanza", + "Leave": "Lasciare il canale", + "leave": "abbandona", + "Legal": "Informazioni", + "Light": "Chiaro", + "License": "Licenza", + "Livechat_edit": "Modifica Livechat", + "Login": "Accedi", + "Login_error": "Le tue credenziali sono state rifiutate! Prova di nuovo.", + "Login_with": "Accedi con", + "Logging_out": "Disconnettendo.", + "Logout": "Disconnetti", + "Max_number_of_uses": "Max numero di utilizzi", + "Max_number_of_users_allowed_is_number": "Il numero massimo di utenti ammessi è {{maxUsers}}", + "members": "membri", + "Members": "Membri", + "Mentioned_Messages": "Messaggi menzionati", + "mentioned": "menzionato", + "Mentions": "Menzioni", + "Message_accessibility": "Messaggio da {{user}} alle {{time}}: {{message}}", + "Message_actions": "Azioni messaggio", + "Message_pinned": "Messaggio appuntato", + "Message_removed": "Messaggio rimosso", + "Message_starred": "Messaggio importante", + "Message_unstarred": "Messaggio non importante", + "message": "messaggio", + "messages": "messaggi", + "Message": "Messaggio", + "Messages": "Messaggi", + "Message_Reported": "Messaggio segnalato", + "Microphone_Permission_Message": "Rocket.Chat richiede l'accesso al microfono per inviare messaggi audio.", + "Microphone_Permission": "Permesso microfono", + "Mute": "Silenzia", + "muted": "silenziato", + "My_servers": "I miei server", + "N_people_reacted": "{{n}} persone hanno reagito", + "N_users": "{{n}} utenti", + "name": "nome", + "Name": "Nome", + "Navigation_history": "Cronologia di navigazione", + "Never": "Mai", + "New_Message": "Nuovo messaggio", + "New_Password": "Nuova password", + "New_Server": "Nuovo server", + "Next": "Successivo", + "No_files": "Nessun file", + "No_limit": "Nessun limite", + "No_mentioned_messages": "Nessun messaggio menzionato", + "No_pinned_messages": "Nessun messaggio attaccato", + "No_results_found": "Nessun risultato", + "No_starred_messages": "Nessun messaggio preferito", + "No_thread_messages": "Nessun messaggio thread", + "No_label_provided": "Nessun {{label}} fornito.", + "No_Message": "Nessun messaggio", + "No_messages_yet": "Non ci sono ancora messaggi", + "No_Reactions": "Nessuna reazione", + "No_Read_Receipts": "Nessuna conferma di lettura", + "Not_logged": "Non loggato", + "Not_RC_Server": "Questo non è un server di Rocket.Chat.\n{{contact}}", + "Nothing": "Niente", + "Nothing_to_save": "Niente da salvare!", + "Notify_active_in_this_room": "Notifica solo gli utenti attivi in questa stanza", + "Notify_all_in_this_room": "Notifica tutti gli utenti in questa stanza", + "Notifications": "Notifiche", + "Notification_Duration": "Durata notifiche", + "Notification_Preferences": "Impostazioni notifiche", + "No_available_agents_to_transfer": "Nessun agente disponibile a cui trasferire", + "Offline": "Offline", + "Oops": "Oops!", + "Omnichannel": "Omnichannel", + "Open_Livechats": "Chat in corso", + "Omnichannel_enable_alert": "Non sei ancora su Onmichannel. Vuoi attivarlo?", + "Onboarding_description": "Un workspace è lo spazio dove il tuo team o la tua organizzazione possono collaborare. Chiedi l'indirizzo all'amministratore del tuo workspace oppure creane uno per il tuo team.", + "Onboarding_join_workspace": "Unisciti", + "Onboarding_subtitle": "Oltre la collaborazione di gruppo", + "Onboarding_title": "Benvenuto in Rocket.Chat", + "Onboarding_join_open_description": "Unisciti al nostro workspace per parlare con il team e la community di Rocket.Chat.", + "Onboarding_agree_terms": "Procedendo, accetti Rocket.Chat", + "Onboarding_less_options": "Meno opzioni", + "Onboarding_more_options": "Più opzioni", + "Online": "Online", + "Only_authorized_users_can_write_new_messages": "Solo gli utenti autorizzati possono scrivere nuovi messaggi", + "Open_emoji_selector": "Apri selettore emoji", + "Open_Source_Communication": "Comunicazione open-source", + "Open_your_authentication_app_and_enter_the_code": "Apri l'app di autenticazione e inserisci il codice.", + "OR": "O", + "OS": "SO", + "Overwrites_the_server_configuration_and_use_room_config": "Sovrascrive la configurazione del server in favore di quella della stanza", + "Password": "Password", + "Parent_channel_or_group": "Gruppo o canale originario", + "Permalink_copied_to_clipboard": "Permalink copiato negli appunti!", + "Phone": "Telefono", + "Pin": "Appunta", + "Pinned_Messages": "Messaggi appuntati", + "pinned": "appuntati", + "Pinned": "Appuntati", + "Please_add_a_comment": "Per favore, aggiungi un commento", + "Please_enter_your_password": "Per favore, inserisci la tua password", + "Please_wait": "Si prega di attendere.", + "Preferences": "Impostazioni", + "Preferences_saved": "Impostazioni salvate!", + "Privacy_Policy": " Privacy Policy", + "Private_Channel": "Canale privato", + "Private": "Privato", + "Processing": "Elaborazione...", + "Profile_saved_successfully": "Profilo salvato correttamente!", + "Profile": "Profilo", + "Public_Channel": "Canale pubblico", + "Public": "Pubblico", + "Push_Notifications": "Notifiche Push", + "Push_Notifications_Alert_Info": "Queste notifiche ti vengono recapitate quando l'app non è aperta", + "Quote": "Cita", + "Reactions_are_disabled": "Le reazioni sono disabilitate", + "Reactions_are_enabled": "Le reazioni sono abilitate", + "Reactions": "Reazioni", + "Read": "Letto", + "Read_External_Permission_Message": "Rocket.Chat deve accedere alle foto, media, e documenti sul tuo dispositivo", + "Read_External_Permission": "Permesso di Lettura della Memoria", + "Read_Only_Channel": "Canale in sola lettura", + "Read_Only": "Sola lettura", + "Read_Receipt": "Conferma di lettura", + "Receive_Group_Mentions": "Ricevi menzioni di gruppo", + "Receive_Group_Mentions_Info": "Ricevi menzioni @all e @here", + "Register": "Registrati", + "Repeat_Password": "Conferma password", + "Replied_on": "Risposto il:", + "replies": "risposte", + "reply": "risposta", + "Reply": "Rispondi", + "Report": "Segnala", + "Receive_Notification": "Ricevi notifiche", + "Receive_notifications_from": "Ricevi notifiche da {{name}}", + "Resend": "Invia di nuovo", + "Reset_password": "Ripristina password", + "resetting_password": "ripristinando password", + "RESET": "RIPRISTINA", + "Return": "Ritorno", + "Review_app_title": "Ti piace questa app?", + "Review_app_desc": "Dacci 5 stesse su {{store}}", + "Review_app_yes": "Certo!", + "Review_app_no": "No", + "Review_app_later": "In seguito", + "Review_app_unable_store": "Impossibile aprire {{store}}", + "Review_this_app": "Recensisci questa app", + "Remove": "Rimuovi", + "Roles": "Ruoli", + "Room_actions": "Azioni stanza", + "Room_changed_announcement": "Annuncio stanza cambiato in: {{announcement}} da {{userBy}}", + "Room_changed_avatar": "Immagine stanza cambiata da {{userBy}}", + "Room_changed_description": "Descrizione stanza cambiata in: {{description}} da {{userBy}}", + "Room_changed_privacy": "Tipo stanza cambiato in: {{type}} da {{userBy}}", + "Room_changed_topic": "Argomento stanza cambiato in: {{topic}} da {{userBy}}", + "Room_Files": "File stanza", + "Room_Info_Edit": "Modifica informazioni stanza", + "Room_Info": "Informazioni stanza", + "Room_Members": "Membri stanza", + "Room_name_changed": "Nome stanza cambiato in: {{name}} da {{userBy}}", + "SAVE": "SALVA", + "Save_Changes": "Salva cambiamenti", + "Save": "Salva", + "Saved": "Salvato", + "saving_preferences": "salvataggio impostazioni", + "saving_profile": "salvataggio profilo", + "saving_settings": "salvataggio impostazioni", + "saved_to_gallery": "Salvato in Galleria", + "Save_Your_E2E_Password": "Salva la tua Password E2E", + "Save_Your_Encryption_Password": "Salva la tua Password di cifratura", + "Save_Your_Encryption_Password_warning": "Questa password non è salvata da nessuna parte, conservala con cura.", + "Save_Your_Encryption_Password_info": "Nota: se dovessi perdere la tua password, non c'è alcun modo per recuperarla e perderesti l'accesso ai tuoi messaggi.", + "Search_Messages": "Cerca messaggi", + "Search": "Cerca", + "Search_by": "Cerca per", + "Search_global_users": "Cerca utenti globali", + "Search_global_users_description": "Se attivi questa opzione, puoi cercare qualsiasi utente da altre aziende o server.", + "Seconds": "{{second}} secondi", + "Security_and_privacy": "Sicurezza e privacy", + "Select_Avatar": "Seleziona avatar", + "Select_Server": "Seleziona server", + "Select_Users": "Seleziona utenti", + "Select_a_Channel": "Seleziona un Canale", + "Select_a_Department": "Seleziona un Dipartimento", + "Select_an_option": "Seleziona un' opzione", + "Select_a_User": "Seleziona un Utente", + "Send": "Invia", + "Send_audio_message": "Invia messaggio audio", + "Send_crash_report": "Invia report sui crash", + "Send_message": "Invia messaggio", + "Send_me_the_code_again": "Inviami nuovamente il codice", + "Send_to": "Invia a...", + "Sending_to": "Invio a", + "Sent_an_attachment": "Inviato un allegato", + "Server": "Server", + "Servers": "Servers", + "Server_version": "Versione server: {{version}}", + "Set_username_subtitle": "Il nome utente viene utilizzato per permettere ad altri di menzionarti nei messaggi", + "Set_custom_status": "Imposta stato personalizzato", + "Set_status": "Imposta stato", + "Status_saved_successfully": "Stato salvato correttamente!", + "Settings": "Impostazioni", + "Settings_succesfully_changed": "Impostazioni modificate correttamente!", + "Share": "Condividi", + "Share_Link": "Condividi link", + "Share_this_app": "Condividi questa app", + "Show_more": "Mostra altri..", + "Show_Unread_Counter": "Mostra contatore messaggi non letti", + "Show_Unread_Counter_Info": "Il contatore viene mostrato come un'etichetta alla destra del canale, nella lista", + "Sign_in_your_server": "Accedi al tuo server", + "Sign_Up": "Registrati", + "Some_field_is_invalid_or_empty": "Un campo non è valido o è vuoto", + "Sorting_by": "Ordina per {{key}}", + "Sound": "Suono", + "Star_room": "Aggiungi stanza ai preferiti", + "Star": "Aggiungi ai preferiti", + "Starred_Messages": "Messaggi preferiti", + "starred": "preferiti", + "Starred": "Preferiti", + "Start_of_conversation": "Inizio della conversazione", + "Start_a_Discussion": "Avvia una Discussione", + "Started_discussion": "Discussione iniziata:", + "Started_call": "Chiamata iniziata da {{userBy}}", + "Submit": "Invia", + "Table": "Tabella", + "Tags": "Tag", + "Take_a_photo": "Scatta una foto", + "Take_a_video": "Registra un video", + "Take_it": "Prendi!", + "tap_to_change_status": "tocca per cambiare stato", + "Tap_to_view_servers_list": "Tocca per vedere la lista server", + "Terms_of_Service": " Termini di servizio ", + "Theme": "Tema", + "The_user_wont_be_able_to_type_in_roomName": "L'utente non potrà scrivere in {{roomName}}", + "The_user_will_be_able_to_type_in_roomName": "L'utente potrà scrivere in {{roomName}}", + "There_was_an_error_while_action": "Si è verificato un errore nel {{action}}!", + "This_room_is_blocked": "Questa stanza è bloccata", + "This_room_is_read_only": "Questa stanza è in sola lettura", + "Thread": "Thread", + "Threads": "Threads", + "Timezone": "Fuso orario", + "To": "A", + "topic": "argomento", + "Topic": "Argomento", + "Translate": "Traduci", + "Try_again": "Riprova", + "Two_Factor_Authentication": "Autenticazione a due fattori", + "Type_the_channel_name_here": "Scrivi il nome del canale qui", + "unarchive": "rimuovi dall'archivio", + "UNARCHIVE": "RIMUOVI DALL'ARCHIVIO", + "Unblock_user": "Sblocca utente", + "Unfavorite": "Rimuovi preferito", + "Unfollowed_thread": "Non segui più il thread", + "Unmute": "Attiva notifiche", + "unmuted": "notifiche attivate", + "Unpin": "Stacca", + "unread_messages": "non letti", + "Unread": "Non letto", + "Unread_on_top": "Non letti sopra", + "Unstar": "Rimuovi dai preferiti", + "Updating": "Aggiornamento...", + "Uploading": "Caricamento", + "Upload_file_question_mark": "Carica file?", + "User": "Utente", + "Users": "Utenti", + "User_added_by": "Utente {{userAdded}} aggiunto da {{userBy}}", + "User_Info": "Informazioni utente", + "User_has_been_key": "Utente {{key}}", + "User_is_no_longer_role_by_": "{{user}} non è più {{role}} da {{userBy}}", + "User_muted_by": "Utente {{userMuted}} silenziato da {{userBy}}", + "User_removed_by": "Utente {{userRemoved}} rimosso da {{userBy}}", + "User_sent_an_attachment": "{{user}} ha inviato un allegato", + "User_unmuted_by": "Utente {{userUnmuted}} de-silenziato da {{userBy}}", + "User_was_set_role_by_": "{{user}} è stato impostato come {{role}} da {{userBy}}", + "Username_is_empty": "Username vuoto", + "Username": "Username", + "Username_or_email": "Username o email", + "Uses_server_configuration": "Usa la configurazione del server", + "Validating": "Validazione", + "Registration_Succeeded": "Registrazione completata!", + "Verify": "Verifica", + "Verify_email_title": "Verifica completata!", + "Verify_email_desc": "Ti abbiamo inviato una e-mail per confermare la tua registrazione. Se non la ricevi, ritorna qui e riprova", + "Verify_your_email_for_the_code_we_sent": "Controlla l'e-mail con il codice che ti abbiamo inviato", + "Video_call": "Videochiamata", + "View_Original": "Mostra originale", + "Voice_call": "Chiamata vocale", + "Waiting_for_network": "In attesa di connessione ...", + "Websocket_disabled": "Websocket disabilitata per questo server.\n{{contact}}", + "Welcome": "Benvenuto", + "What_are_you_doing_right_now": "Cosa stai facendo in questo momento?", + "Whats_your_2fa": "Qual'è il tuo codice 2FA?", + "Without_Servers": "Senza server", + "Workspaces": "Workspace", + "Would_you_like_to_return_the_inquiry": "Vorresti ritirare la tua domanda?", + "Write_External_Permission_Message": "Rocket.Chat ha bisogno dell'accesso alla galleria per salvare le immagini.", + "Write_External_Permission": "Permesso galleria", + "Yes": "Si", + "Yes_action_it": "Sì, {{action}}!", + "Yesterday": "Ieri", + "You_are_in_preview_mode": "Sei in modalità anteprima", + "You_are_offline": "Sei offline", + "You_can_search_using_RegExp_eg": "Puoi usare espressioni regolari. es. `/^testo$/i`", + "You_colon": "Tu: ", + "you_were_mentioned": "sei stato menzionato", + "You_were_removed_from_channel": "Sei stato rimosso da {{channel}}", + "you": "tu", + "You": "Tu", + "Logged_out_by_server": "Sei stato disconnesso dal server. Esegui nuovamente l'accesso.", + "You_need_to_access_at_least_one_RocketChat_server_to_share_something": "Devi accedere ad almeno un server Rocket.Chat prima di condividere qualcosa.", + "You_need_to_verifiy_your_email_address_to_get_notications": "Devi verificare il tuo indirizzo e-mail per ricevere le notifiche", + "Your_certificate": "Il tuo certificato", + "Your_invite_link_will_expire_after__usesLeft__uses": "Il tuo link di invito scadrà dopo {{usesLeft}} utilizzi.", + "Your_invite_link_will_expire_on__date__or_after__usesLeft__uses": "Il tuo link di invito scadrà il {{date}} oppure dopo {{usesLeft}} utilizzi.", + "Your_invite_link_will_expire_on__date__": "Il tuo link di invito scadrà il {{date}}.", + "Your_invite_link_will_never_expire": "Il tuo link di invito non scadrà mai.", + "Your_workspace": "Il tuo workspace", + "Your_password_is": "La tua password è", + "Version_no": "Versione: {{version}}", + "You_will_not_be_able_to_recover_this_message": "Non sarai in grado di ripristinare questo messaggio!", + "You_will_unset_a_certificate_for_this_server": "Rimuoverai un certificato per questo server", + "Change_Language": "Cambia lingua", + "Crash_report_disclaimer": "Non registreremo mai il contenuto delle tue chat. Il crash report contiene solo informazioni necessarie per l'identificazione e la risoluzione dei problemi.", + "Type_message": "Scrivi messaggio", + "Room_search": "Ricerca stanze", + "Room_selection": "Selezione stanza 1...9", + "Next_room": "Prossima stanza", + "Previous_room": "Stanza precedente", + "New_room": "Nuova stanza", + "Upload_room": "Carica nella stanza", + "Search_messages": "Cerca messaggi", + "Scroll_messages": "Scorri i messaggi", + "Reply_latest": "Rispondi all'ultimo", + "Reply_in_Thread": "Rispondi nella discussione", + "Server_selection": "Selezione server", + "Server_selection_numbers": "Selezione server 1...9", + "Add_server": "Aggiungi server", + "New_line": "Nuova linea", + "You_will_be_logged_out_of_this_application": "Verrai disconnesso da questa applicazione.", + "Clear": "Cancella", + "This_will_clear_all_your_offline_data": "Questo cancellerà tutti i tuoi dati offline.", + "This_will_remove_all_data_from_this_server": "Questo rimuoverà tutti i dati dal server.", + "Mark_unread": "Segna come non letto", + "Wait_activation_warning": "Prima di poter accedere, il tuo account deve essere attivato manualmente da un amministratore.", + "Screen_lock": "Blocco schermo", + "Local_authentication_biometry_title": "Autenticazione", + "Local_authentication_biometry_fallback": "Usa passcode", + "Local_authentication_unlock_option": "Sblocca con Passcode", + "Local_authentication_change_passcode": "Cambia Passcode", + "Local_authentication_info": "Nota: se dimentichi il Passcode, dovrai cancellare e installare nuovamente l'app.", + "Local_authentication_facial_recognition": "riconoscimento facciale", + "Local_authentication_fingerprint": "impronta digitale", + "Local_authentication_unlock_with_label": "Sblocca con {{label}}", + "Local_authentication_auto_lock_60": "Dopo 1 minuto", + "Local_authentication_auto_lock_300": "Dopo 5 minuti", + "Local_authentication_auto_lock_900": "Dopo 15 minuti", + "Local_authentication_auto_lock_1800": "Dopo 30 minuti", + "Local_authentication_auto_lock_3600": "Dopo 1 ora", + "Passcode_enter_title": "Inserisci il passcode", + "Passcode_choose_title": "Scegli il tuo nuovo passcode", + "Passcode_choose_confirm_title": "Conferma il tuo nuovo passcode", + "Passcode_choose_error": "I passcode non corrispondono. Riprova.", + "Passcode_choose_force_set": "Passcode richiesto dall'admin", + "Passcode_app_locked_title": "App bloccata", + "Passcode_app_locked_subtitle": "Riprova tra {{timeLeft}} secondi", + "After_seconds_set_by_admin": "Dopo {{seconds}} secondi (impostati dall'admin)", + "Dont_activate": "Non attivare ora", + "Queued_chats": "Chat in coda", + "Queue_is_empty": "La coda è vuota", + "Logout_from_other_logged_in_locations": "Disconnetti da altre postazioni", + "You_will_be_logged_out_from_other_locations": "Verrai disconnesso dalle altre postazioni.", + "Logged_out_of_other_clients_successfully": "Disconnesso dalle altre postazioni con successo", + "Logout_failed": "Disconnessione fallita!", + "Log_analytics_events": "Invia statistiche anonime", + "E2E_encryption_change_password_title": "Cambia la password di cifratura", + "E2E_encryption_change_password_description": "Ora puoi creare gruppi e messaggi privati crittografati. Potrai, inoltre, crittografare i tuoi gruppi e messaggi privati esistenti. \nQuesta è crittografia end-to-end, la chiave per codificare/decodificare i tuoi messaggi non verrà salvata sul server. Per questo motivo devi salvarla in un luogo sicuro. Ti verrà richiesto di inserirla sugli altri dispositivi sui quali vuoi usare la crittografia e2e.", + "E2E_encryption_change_password_error": "Si è verificato un errore durante il cambio della password della chiave E2E!", + "E2E_encryption_change_password_success": "La password della chiave E2E è stata cambiata con successo!", + "E2E_encryption_change_password_message": "Assicurati di salvarla in un posto sicuro.", + "E2E_encryption_change_password_confirmation": "Si, cambiala", + "E2E_encryption_reset_title": "Ripristina la Chiave E2E", + "E2E_encryption_reset_description": "Questa opzione rimuoverà la tua chiave E2E corrente e sarai disconnesso. \nAl prossimo login, Rocket.Chat genererà una nuova chiave e ripristinerà l'accesso a tutte le stanze crittografate con uno o più membri online. \nA causa della natura della crittografia E2E, Rocket.Chat non sarà in grado di ripristinare l'accesso alle stanze senza membri online.", + "E2E_encryption_reset_button": "Ripristina", + "E2E_encryption_reset_error": "Si è verificato un errore durante il ripristino della chiave E2E!", + "E2E_encryption_reset_message": "Stai per essere disconnesso.", + "E2E_encryption_reset_confirmation": "Si, resettala", + "Following": "Seguiti", + "Threads_displaying_all": "Visualizza Tutti", + "Threads_displaying_following": "Visualizza Seguiti", + "Threads_displaying_unread": "Visualizza Non letti", + "No_threads": "Non ci sono thread", + "No_threads_following": "Non stai seguendo alcun thread", + "No_threads_unread": "Non ci sono thread non letti", + "Messagebox_Send_to_channel": "Invia sul canale", + "Remove_from_room": "Rimuovi dalla stanza", + "Ignore": "Ignora", + "Unignore": "Non ignorare", + "User_has_been_ignored": "Utente ignorato", + "User_has_been_unignored": "Utente non ignorato", + "User_has_been_removed_from_s": "L'utente è stato rimosso da {{s}}", + "User__username__is_now_a_leader_of__room_name_": "L'utente {{username}} è ora un leader di {{room_name}}", + "User__username__is_now_a_moderator_of__room_name_": "L'utente {{username}} è ora un moderatore di {{room_name}}", + "User__username__is_now_a_owner_of__room_name_": "L'utente {{username}} è ora un proprietario di {{room_name}}", + "User__username__removed_from__room_name__leaders": "L'utente {{username}} non è più un leader di {{room_name}}", + "User__username__removed_from__room_name__moderators": "L'utente {{username}} non è più un moderatore di {{room_name}}", + "User__username__removed_from__room_name__owners": "L'utente {{username}} non è più un proprietario di {{room_name}}", + "The_user_will_be_removed_from_s": "L'utente sarà rimosso da {{s}}", + "Yes_remove_user": "Si, rimuovi utente!", + "Direct_message": "Messaggio diretto", + "Message_Ignored": "Messaggio ignorato. Tocca per visualizzarlo.", + "Enter_workspace_URL": "Inserisci la url del workspace", + "Workspace_URL_Example": "Es. tua-azienda.rocket.chat", + "invalid-room": "Canale non valido" +} diff --git a/app/i18n/locales/ja.json b/app/i18n/locales/ja.json index 61accdd79..48d0ef1be 100644 --- a/app/i18n/locales/ja.json +++ b/app/i18n/locales/ja.json @@ -1,492 +1,492 @@ { - "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": "不正なメールアドレスです。 {{email}}", - "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": "証明書を適用する", - "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": "接続", - "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": "メールアドレス", - "Enable_Auto_Translate": "自動翻訳を有効にする", - "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_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": "参加", - "Just_invited_people_can_access_this_channel": "招待されたユーザーだけがこのチャンネルに参加できます", - "Language": "言語", - "last_message": "最後のメッセージ", - "Leave_channel": "チャンネルを退出", - "leaving_room": "チャンネルを退出", - "Leave": "ルームを退出", - "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": "おっと!", - "Onboarding_title": "Rocket.Chatへようこそ", - "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": "プライベート", - "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": "テーマ", - "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": "ようこそ", - "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": "オフラインデータをすべて削除します。", - "invalid-room": "無効なルーム" -} \ No newline at end of file + "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": "不正なメールアドレスです。 {{email}}", + "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": "証明書を適用する", + "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": "接続", + "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": "メールアドレス", + "Enable_Auto_Translate": "自動翻訳を有効にする", + "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_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": "参加", + "Just_invited_people_can_access_this_channel": "招待されたユーザーだけがこのチャンネルに参加できます", + "Language": "言語", + "last_message": "最後のメッセージ", + "Leave_channel": "チャンネルを退出", + "leaving_room": "チャンネルを退出", + "Leave": "ルームを退出", + "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": "おっと!", + "Onboarding_title": "Rocket.Chatへようこそ", + "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": "プライベート", + "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": "テーマ", + "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": "ようこそ", + "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": "オフラインデータをすべて削除します。", + "invalid-room": "無効なルーム" +} diff --git a/app/i18n/locales/nl.json b/app/i18n/locales/nl.json index 7c823c9ba..33acb243b 100644 --- a/app/i18n/locales/nl.json +++ b/app/i18n/locales/nl.json @@ -1,772 +1,776 @@ { - "1_person_reacted": "1 persoon heeft gereageerd", - "1_user": "1 gebruiker", - "error-action-not-allowed": "{{action}} is niet toegestaan", - "error-application-not-found": "Applicatie niet gevonden", - "error-archived-duplicate-name": "Er is een gearchiveerd kanaal met de naam {{room_name}}", - "error-avatar-invalid-url": "Ongeldige avatar-URL: {{url}}", - "error-avatar-url-handling": "Fout bij het verwerken van de avatar-instelling van een URL ({{url}}) voor {{username}}", - "error-cant-invite-for-direct-room": "Kan gebruikers in directe kamers niet uitnodigen", - "error-could-not-change-email": "Kan e-mail niet veranderen", - "error-could-not-change-name": "Kan naam niet veranderen", - "error-could-not-change-username": "Kan gebruikersnaam niet veranderen", - "error-could-not-change-status": "Kan status niet wijzigen", - "error-delete-protected-role": "Kan een beveiligde rol niet verwijderen", - "error-department-not-found": "Afdeling niet gevonden", - "error-direct-message-file-upload-not-allowed": "Delen van bestanden in privéberichten niet toegestaan", - "error-duplicate-channel-name": "Een kanaal met naam {{room_name}} bestaat", - "error-email-domain-blacklisted": "Het e-maildomein staat op de zwarte lijst", - "error-email-send-failed": "Fout bij het verzenden van e-mail: {{message}}", - "error-save-image": "Fout bij het opslaan van afbeelding", - "error-save-video": "Fout bij het opslaan van video", - "error-field-unavailable": "{{field}} is al in gebruik :(", - "error-file-too-large": "Bestand is te groot", - "error-importer-not-defined": "De importeur is niet correct gedefinieerd, de klasse Import ontbreekt.", - "error-input-is-not-a-valid-field": "{{input}} is geen geldig {{field}}", - "error-invalid-actionlink": "Ongeldige actielink", - "error-invalid-arguments": "Ongeldige argumenten", - "error-invalid-asset": "Ongeldig item", - "error-invalid-channel": "Ongeldig kanaal.", - "error-invalid-channel-start-with-chars": "Ongeldig kanaal. Begin met @ of #", - "error-invalid-custom-field": "Ongeldig aangepast veld", - "error-invalid-custom-field-name": "Ongeldige aangepaste veldnaam. Gebruik alleen letters, cijfers, koppeltekens en underscores.", - "error-invalid-date": "Ongeldige datum opgegeven.", - "error-invalid-description": "Ongeldige beschrijving", - "error-invalid-domain": "Ongeldig domein", - "error-invalid-email": "Ongeldig e-mail {{email}}", - "error-invalid-email-address": "Ongeldig e-mailadres", - "error-invalid-file-height": "Ongeldige bestandshoogte", - "error-invalid-file-type": "Ongeldig bestandstype", - "error-invalid-file-width": "Ongeldige bestandsbreedte", - "error-invalid-from-address": "Je hebt een ongeldig FROM adres opgegeven.", - "error-invalid-integration": "Ongeldige integratie", - "error-invalid-message": "Ongeldig bericht", - "error-invalid-method": "Ongeldige methode", - "error-invalid-name": "Ongeldige naam", - "error-invalid-password": "Ongeldig wachtwoord", - "error-invalid-redirectUri": "Ongeldige redirectUri", - "error-invalid-role": "Ongeldige rol", - "error-invalid-room": "Ongeldige kamer", - "error-invalid-room-name": "{{room_name}} is geen geldige kamernaam", - "error-invalid-room-type": "{{type}} is geen geldig kamertype.", - "error-invalid-settings": "Ongeldige instellingen opgegeven", - "error-invalid-subscription": "Ongeldig abonnement", - "error-invalid-token": "Ongeldige token", - "error-invalid-triggerWords": "Ongeldige triggerWoorden", - "error-invalid-urls": "Ongeldige URL's", - "error-invalid-user": "Ongeldige gebruiker", - "error-invalid-username": "Ongeldige gebruikersnaam", - "error-invalid-webhook-response": "De webhook-URL heeft met een andere status dan 200 gereageerd", - "error-message-deleting-blocked": "Het verwijderen van berichten is geblokkeerd", - "error-message-editing-blocked": "Het aanpassen van berichten is geblokkeerd", - "error-message-size-exceeded": "Berichtgrootte is groter dan Message_MaxAllowedSize", - "error-missing-unsubscribe-link": "Je moet de link [unsubscribe] opgeven.", - "error-no-owner-channel": "Je bent niet de eigenaar van het kanaal", - "error-no-tokens-for-this-user": "Er zijn geen tokens voor deze gebruiker", - "error-not-allowed": "Niet toegestaan", - "error-not-authorized": "Geen bevoegdheid", - "error-push-disabled": "Push is uitgeschakeld", - "error-remove-last-owner": "Dit is de laatste eigenaar. Stel een nieuwe eigenaar in voordat je deze verwijdert.", - "error-role-in-use": "Kan rol niet verwijderen omdat deze in gebruik is", - "error-role-name-required": "Rolnaam is vereist", - "error-the-field-is-required": "Het veld {{field}} is verplicht.", - "error-too-many-requests": "Fout, te veel verzoeken. Vertraag, alsjeblieft. Je moet {{seconds}} seconden wachten voordat je het opnieuw probeert.", - "error-user-is-not-activated": "Gebruiker is niet geactiveerd", - "error-user-has-no-roles": "Gebruiker heeft geen rollen", - "error-user-limit-exceeded": "Het aantal gebruikers die je probeert uit te nodigen voor #channel_name overschrijdt de limiet ingesteld door de beheerder", - "error-user-not-in-room": "Gebruiker is niet in deze kamer", - "error-user-registration-custom-field": "error-user-registration-custom-field", - "error-user-registration-disabled": "Gebruikersregistratie is uitgeschakeld", - "error-user-registration-secret": "Gebruikersregistratie is alleen via geheime URL toegestaan", - "error-you-are-last-owner": "Je bent de laatste eigenaar. Stel een nieuwe eigenaar in voordat je de kamer verlaat.", - "error-status-not-allowed": "Onzichtbare status is uitgeschakeld", - "Actions": "Acties", - "activity": "activiteit", - "Activity": "Activiteit", - "Add_Reaction": "Reactie toevoegen", - "Add_Server": "Server toevoegen", - "Add_users": "Gebruikers toevoegen", - "Admin_Panel": "Admin Paneel", - "Agent": "Agent", - "Alert": "Waarschuwing", - "alert": "waarschuwing", - "alerts": "waarschuwingen", - "All_users_in_the_channel_can_write_new_messages": "Alle gebruikers in het kanaal kunnen nieuwe berichten schrijven", - "All_users_in_the_team_can_write_new_messages": "Alle gebruikers in het team kunnen nieuwe berichten schrijven", - "A_meaningful_name_for_the_discussion_room": "Een betekenisvolle naam voor de discussieruimte", - "All": "Alle", - "All_Messages": "Alle berichten", - "Allow_Reactions": "Reacties toestaan", - "Alphabetical": "Alfabetisch", - "and_more": "en meer", - "and": "en", - "announcement": "aankondiging", - "Announcement": "Aankondiging", - "Apply_Your_Certificate": "Pas jouw certificaat toe", - "ARCHIVE": "ARCHIVEER", - "archive": "archiveer", - "are_typing": "zijn aan het typen", - "Are_you_sure_question_mark": "Weet je het zeker?", - "Are_you_sure_you_want_to_leave_the_room": "Weet je zeker dat je de kamer {{room}} wilt verlaten?", - "Audio": "Audio", - "Authenticating": "Authenticatie", - "Automatic": "Automatisch", - "Auto_Translate": "Automatisch vertalen", - "Avatar_changed_successfully": "Avatar succesvol gewijzigd!", - "Avatar_Url": "Avatar-URL", - "Away": "Afwezig", - "Back": "Terug", - "Black": "Zwart", - "Block_user": "Blokkeer gebruiker", - "Browser": "Browser", - "Broadcast_channel_Description": "Alleen geautoriseerde gebruikers kunnen nieuwe berichten schrijven, maar de andere gebruikers zullen kunnen antwoorden", - "Broadcast_Channel": "Uitzendkanaal", - "Busy": "Bezig", - "By_proceeding_you_are_agreeing": "Door verder te gaan ga je akkoord met onze", - "Cancel_editing": "Bewerken annuleren", - "Cancel_recording": "Opname annuleren", - "Cancel": "Annuleren", - "changing_avatar": "avatar aan het veranderen", - "creating_channel": "kanaal aan het maken", - "creating_invite": "uitnodiging maken", - "Channel_Name": "Kanaal naam", - "Channels": "Kanalen", - "Chats": "Chats", - "Call_already_ended": "Gesprek al beeïndigd!", - "Clear_cookies_alert": "Wilt u alle cookies wissen?", - "Clear_cookies_desc": "Met deze actie worden alle inlogcookies gewist, zodat u op andere accounts kunt inloggen.", - "Clear_cookies_yes": "Ja, cookies wissen", - "Clear_cookies_no": "Nee, bewaar cookies", - "Click_to_join": "Klik om mee te doen!", - "Close": "Sluiten", - "Close_emoji_selector": "Emoji-kiezer sluiten", - "Closing_chat": "Chat sluiten", - "Change_language_loading": "Taal veranderen", - "Chat_closed_by_agent": "Chat gesloten door agent", - "Choose": "Kies", - "Choose_from_library": "Kies uit bibliotheek", - "Choose_file": "Kies bestand", - "Choose_where_you_want_links_be_opened": "Kies waar je links wilt openen", - "Code": "Code", - "Code_or_password_invalid": "Code of wachtwoord ongeldig", - "Collaborative": "Samenwerkend", - "Confirm": "Bevestig", - "Connect": "Verbinden", - "Connected": "Verbonden", - "connecting_server": "verbonden met de server", - "Connecting": "Aan het verbinden...", - "Contact_us": "Neem contact op", - "Contact_your_server_admin": "Neem contact op met je serverbeheerder.", - "Continue_with": "Ga verder met", - "Copied_to_clipboard": "Gekopieerd naar klembord!", - "Copy": "Kopiëren", - "Conversation": "Conversatie", - "Permalink": "Permalink", - "Certificate_password": "Certificaat wachtwoord", - "Clear_cache": "Lokale server cache wissen", - "Clear_cache_loading": "Cache wissen.", - "Whats_the_password_for_your_certificate": "Wat is het wachtwoord voor jouw certificaat?", - "Create_account": "Account aanmaken", - "Create_Channel": "Kanaal aanmaken", - "Create_Direct_Messages": "Directe berichten aanmaken", - "Create_Discussion": "Discussie aanmaken", - "Created_snippet": "knipsel aangemaakt", - "Create_a_new_workspace": "Een nieuwe werkruimte aanmaken", - "Create": "Aanmaken", - "Custom_Status": "Aangepaste status", - "Dark": "Donker", - "Dark_level": "Donker niveau", - "Default": "Standaard", - "Default_browser": "Standaard browser", - "Delete_Room_Warning": "Als je een kamer verwijdert, worden alle berichten die in de kamer geplaatst zijn, verwijderd. Dit kan niet ongedaan worden gemaakt.", - "Department": "Afdeling", - "delete": "verwijderen", - "Delete": "Verwijderen", - "DELETE": "VERWIJDEREN", - "move": "verplaatsen", - "deleting_room": "kamer verwijderen", - "description": "omschrijving", - "Description": "Omschrijving", - "Desktop_Options": "Bureaubladopties", - "Desktop_Notifications": "Desktopmeldingen", - "Desktop_Alert_info": "Deze meldingen worden op desktop geleverd", - "Directory": "Map", - "Direct_Messages": "Directe berichten", - "Disable_notifications": "Zet notificaties uit", - "Discussions": "Discussies", - "Discussion_Desc": "Help het overzicht te houden over wat er aan de hand is! Door een discussie aan te maken, wordt een subkanaal van het geselecteerde kanaal aangemaakt en worden beide gekoppeld.", - "Discussion_name": "Discussienaam", - "Done": "Gedaan", - "Dont_Have_An_Account": "Heb je geen account?", - "Do_you_have_an_account": "Heb je een account?", - "Do_you_have_a_certificate": "Heb je een certificaat?", - "Do_you_really_want_to_key_this_room_question_mark": "Wil je deze kamer echt {{key}}?", - "E2E_Encryption": "E2E-codering", - "E2E_How_It_Works_info1": "U kunt nu versleutelde privégroepen en directe berichten aanmaken. U kunt ook bestaande privégroepen of DM's wijzigen in versleuteld.", - "E2E_How_It_Works_info2": "Dit is *end-to-end codering*, dus de sleutel om jouw berichten te coderen/decoderen en deze wordt niet op de server opgeslagen. Daarom *moet je dit wachtwoord op een veilige plaats opslaan* waar je later toegang hebt als je dat nodig hebt.", - "E2E_How_It_Works_info3": "Als je doorgaat, wordt er automatisch een E2E-wachtwoord gegenereerd.", - "E2E_How_It_Works_info4": "Je kan ook op elk moment een nieuw wachtwoord voor uw coderingssleutel instellen vanuit elke browser waarin u het bestaande E2E-wachtwoord hebt ingevoerd.", - "edit": "bewerk", - "edited": "bewerkt", - "Edit": "Bewerk", - "Edit_Status": "Status bewerken", - "Edit_Invite": "Bewerk uitnodiging", - "End_to_end_encrypted_room": "End-to-end versleutelde kamer", - "end_to_end_encryption": "end-to-end encryptie", - "Email_Notification_Mode_All": "Elke vermelding/DM", - "Email_Notification_Mode_Disabled": "Uitgeschakeld", - "Email_or_password_field_is_empty": "E-mail of wachtwoordveld is leeg", - "Email": "E-mail", - "email": "e-mail", - "Empty_title": "Lege titel", - "Enable_Auto_Translate": "Automatisch vertalen inschakelen", - "Enable_notifications": "Notificaties aanzetten", - "Encrypted": "Versleuteld", - "Encrypted_message": "Versleuteld bericht", - "Enter_Your_E2E_Password": "Voer uw E2E-wachtwoord in", - "Enter_Your_Encryption_Password_desc1": "Hiermee krijg je toegang tot uw gecodeerde privégroepen en directe berichten.", - "Enter_Your_Encryption_Password_desc2": "Op elke plaats waar je de chat gebruikt, moet je het wachtwoord invoeren om berichten te coderen/decoderen.", - "Encryption_error_title": "Jouw coderingswachtwoord lijkt verkeerd", - "Encryption_error_desc": "Het was niet mogelijk om uw coderingssleutel te decoderen om te worden geïmporteerd.", - "Everyone_can_access_this_channel": "Iedereen heeft toegang tot dit kanaal", - "Everyone_can_access_this_team": "Iedereen heeft toegang tot dit team", - "Error_uploading": "Fout bij uploaden", - "Expiration_Days": "Vervaldatum (Dagen)", - "Favorite": "Favoriet", - "Favorites": "Favorieten", - "Files": "Bestanden", - "File_description": "Bestandsbeschrijving", - "File_name": "Bestandsnaam", - "Finish_recording": "Opname beëindigen", - "Following_thread": "Volg discussie", - "For_your_security_you_must_enter_your_current_password_to_continue": "Voor je veiligheid moet je je huidige wachtwoord invoeren om door te gaan", - "Forgot_password_If_this_email_is_registered": "Als dit e-mailadres geregistreerd is, sturen we instructies om je wachtwoord opnieuw in te stellen. Als je geen e-mail ontvangt, kom dan terug en probeer het opnieuw.", - "Forgot_password": "Wachtwoord vergeten?", - "Forgot_Password": "Wachtwoord vergeten", - "Forward": "Doorsturen", - "Forward_Chat": "Chat doorsturen", - "Forward_to_department": "Doorsturen naar afdeling", - "Forward_to_user": "Doorsturen naar gebruiker", - "Full_table": "Klik om de volledige tabel te zien", - "Generate_New_Link": "Nieuwe link genereren", - "Group_by_favorites": "Groepeer favorieten", - "Group_by_type": "Groeperen op type", - "Hide": "Verberg", - "Has_joined_the_channel": "is bij het kanaal gekomen", - "Has_joined_the_conversation": "heeft zich bij het gesprek aangesloten", - "Has_left_the_channel": "heeft het kanaal verlaten", - "Hide_System_Messages": "Verberg systeemberichten", - "Hide_type_messages": "Verberg \"{{type}}\" berichten", - "How_It_Works": "Hoe het werkt", - "Message_HideType_uj": "Gebruiker neemt deel", - "Message_HideType_ul": "Gebruiker vertrokken", - "Message_HideType_ru": "Gebruiker verwijderd", - "Message_HideType_au": "Gebruiker toegevoegd", - "Message_HideType_mute_unmute": "Gebruiker gedempt / kan weer praten", - "Message_HideType_r": "Kamernaam gewijzigd", - "Message_HideType_ut": "Gebruiker neemt deel aan gesprek", - "Message_HideType_wm": "Welkom", - "Message_HideType_rm": "Bericht verwijderd", - "Message_HideType_subscription_role_added": "Kreeg rol", - "Message_HideType_subscription_role_removed": "Rol niet langer gedefinieerd", - "Message_HideType_room_archived": "Kamer gearchiveerd", - "Message_HideType_room_unarchived": "Kamer niet gearchiveerd", - "I_Saved_My_E2E_Password": "Ik heb mijn E2E-wachtwoord opgeslagen", - "IP": "IP", - "In_app": "In-app", - "In_App_And_Desktop": "In-app en desktop", - "In_App_and_Desktop_Alert_info": "Geeft een banner boven aan het scherm weer wanneer de app geopend is, en geeft een melding op desktop weer", - "Invisible": "Onzichtbaar", - "Invite": "Nodig uit", - "is_a_valid_RocketChat_instance": "is een geldige Rocket.Chat instantie", - "is_not_a_valid_RocketChat_instance": "is geen geldige Rocket.Chat instantie", - "is_typing": "is aan het typen", - "Invalid_or_expired_invite_token": "Ongeldige of verlopen uitnodigingstoken", - "Invalid_server_version": "De server waarmee je probeert te verbinden, gebruikt een versie die niet meer door de app wordt ondersteund: {{currentVersion}}.\n\nWe hebben versie {{minVersion}} nodig", - "Invite_Link": "Uitnodigingslink", - "Invite_users": "Gebruikers uitnodigen", - "Join": "Doe mee", - "Join_Code": "Deelnamecode", - "Insert_Join_Code": "Deelnamecode invoegen", - "Join_our_open_workspace": "Word lid van onze open werkruimte", - "Join_your_workspace": "Word lid van jouw werkruimte", - "Just_invited_people_can_access_this_channel": "Alleen uitgenodigde mensen hebben toegang tot dit kanaal", - "Just_invited_people_can_access_this_team": "Alleen uitgenodigde mensen hebben toegang tot dit team", - "Language": "Taal", - "last_message": "laatste bericht", - "Leave_channel": "Kanaal verlaten", - "leaving_room": "ruimte verlaten", - "Leave": "Verlaten", - "leave": "verlaten", - "Legal": "Legaal", - "Light": "Licht", - "License": "Licentie", - "Livechat": "Livechat", - "Livechat_edit": "Livechat bewerken", - "Login": "Inloggen", - "Login_error": "Je inloggegevens zijn geweigerd! Probeer het opnieuw.", - "Login_with": "Inloggen met", - "Logging_out": "Uitloggen.", - "Logout": "Uitloggen", - "Max_number_of_uses": "Max aantal toepassingen", - "Max_number_of_users_allowed_is_number": "Max aantal toegestane gebruikers is {{maxUsers}}", - "members": "leden", - "Members": "Leden", - "Mentioned_Messages": "Vermelde berichten", - "mentioned": "vermeld", - "Mentions": "Vermeldingen", - "Message_accessibility": "Bericht van {{user}} om {{time}}: {{message}}", - "Message_actions": "Berichtacties", - "Message_pinned": "Bericht vastgezet", - "Message_removed": "Bericht verwijderd", - "Message_starred": "Bericht met ster", - "Message_unstarred": "Bericht zonder ster", - "message": "bericht", - "messages": "berichten", - "Message": "Bericht", - "Messages": "Berichten", - "Message_Reported": "Bericht gerapporteerd", - "Microphone_Permission_Message": "Rocket.Chat heeft toegang tot je microfoon nodig zodat je een audiobericht kunt verzenden.", - "Microphone_Permission": "Microfoontoestemming", - "Mute": "Dempen", - "muted": "gedempt", - "My_servers": "Mijn servers", - "N_people_reacted": "{{n}} mensen hebben gereageerd", - "N_users": "{{n}} gebruikers", - "N_channels": "{{n}} kanalen", - "name": "naam", - "Name": "Naam", - "Navigation_history": "Navigatie geschiedenis", - "Never": "Nooit", - "New_Message": "Nieuw bericht", - "New_Password": "Nieuw wachtwoord", - "New_Server": "Nieuwe server", - "Next": "Volgende", - "No_files": "Geen bestanden", - "No_limit": "Geen limiet", - "No_mentioned_messages": "Geen vermelde berichten", - "No_pinned_messages": "Geen vastgezette berichten", - "No_results_found": "Geen resultaten gevonden", - "No_starred_messages": "Geen berichten met ster gemarkeerd", - "No_thread_messages": "Geen discussieberichten", - "No_label_provided": "Geen {{label}} opgegeven.", - "No_Message": "Geen bericht", - "No_messages_yet": "Nog geen berichten", - "No_Reactions": "Geen reacties", - "No_Read_Receipts": "Geen leesbevestigingen", - "Not_logged": "Niet ingelogd", - "Not_RC_Server": "Dit is geen Rocket.Chat-server.\n{{contact}}", - "Nothing": "Niets", - "Nothing_to_save": "Niets om op te slaan!", - "Notify_active_in_this_room": "Waarschuw actieve gebruikers in deze kamer", - "Notify_all_in_this_room": "Breng iedereen in deze kamer op de hoogte", - "Notifications": "Notificaties", - "Notification_Duration": "Duur van de notificatie", - "Notification_Preferences": "Notificatievoorkeuren", - "No_available_agents_to_transfer": "Geen beschikbare agenten om door te sturen", - "Offline": "Offline", - "Oops": "Oeps!", - "Omnichannel": "Omnichannel", - "Open_Livechats": "Bezig met chatten", - "Omnichannel_enable_alert": "Je bent niet beschikbaar op Omnichannel. Wil je beschikbaar zijn?", - "Onboarding_description": "Een werkruimte is de ruimte van jouw team of organisatie om samen te werken. Vraag aan de beheerder van de werkruimte een adres om lid te worden of maak er een aan voor jouw team.", - "Onboarding_join_workspace": "Word lid van een werkruimte", - "Onboarding_subtitle": "Meer dan teamsamenwerking", - "Onboarding_title": "Welkom bij Rocket.Chat", - "Onboarding_join_open_description": "Word lid van onze open werkruimte om met het Rocket.Chat team en de community te chatten.", - "Onboarding_agree_terms": "Door verder te gaan, ga je akkoord met Rocket.Chat", - "Onboarding_less_options": "Minder opties", - "Onboarding_more_options": "Meer opties", - "Online": "Online", - "Only_authorized_users_can_write_new_messages": "Alleen geautoriseerde gebruikers kunnen nieuwe berichten schrijven", - "Open_emoji_selector": "Emoji-kiezer openen", - "Open_Source_Communication": "Open Source Communicatie", - "Open_your_authentication_app_and_enter_the_code": "Open je authenticatie-app en voer de code in.", - "OR": "OF", - "OS": "OS", - "Overwrites_the_server_configuration_and_use_room_config": "Overschrijft de serverconfiguratie en gebruikt kamer config", - "Password": "Wachtwoord", - "Parent_channel_or_group": "Bovenliggend kanaal of groep", - "Permalink_copied_to_clipboard": "Permalink gekopiëerd naar klembord!", - "Phone": "Telefoon", - "Pin": "Vastzetten", - "Pinned_Messages": "Vastgezette berichten", - "pinned": "vastgezet", - "Pinned": "Vastgezet", - "Please_add_a_comment": "Voeg een reactie toe", - "Please_enter_your_password": "Voer je wachtwoord in", - "Please_wait": "Even geduld.", - "Preferences": "Voorkeuren", - "Preferences_saved": "Voorkeuren opgeslagen!", - "Privacy_Policy": " Privacybeleid", - "Private_Channel": "Privékanaal", - "Private": "Privé", - "Processing": "Verwerking...", - "Profile_saved_successfully": "Profiel succesvol opgeslagen!", - "Profile": "Profiel", - "Public_Channel": "Publiek kanaal", - "Public": "Publiek", - "Push_Notifications": "Pushmeldingen", - "Push_Notifications_Alert_Info": "Deze notificaties worden aan jouw enkel geleverd als de app niet geopend is", - "Quote": "Citaat", - "Reactions_are_disabled": "Reacties zijn uitgeschakeld", - "Reactions_are_enabled": "Reacties zijn ingeschakeld", - "Reactions": "Reacties", - "Read": "Lezen", - "Read_External_Permission_Message": "Rocket.Chat heeft toegang nodig tot foto's, media en bestanden op je apparaat", - "Read_External_Permission": "Lees toestemming voor media", - "Read_Only_Channel": "Alleen-lezen kanaal", - "Read_Only": "Alleen lezen", - "Read_Receipt": "Leesbevestiging", - "Receive_Group_Mentions": "Groepsvermeldingen ontvangen", - "Receive_Group_Mentions_Info": "Ontvang @all en @here vermeldingen", - "Register": "Registreren", - "Repeat_Password": "Herhaal wachtwoord", - "Replied_on": "Beantwoord op:", - "replies": "antwoordt", - "reply": "antwoord", - "Reply": "Antwoord", - "Report": "Rapporteren", - "Receive_Notification": "Notificatie ontvangen", - "Receive_notifications_from": "Ontvang notificaties van {{name}}", - "Resend": "Opnieuw verzenden", - "Reset_password": "Wachtwoord resetten", - "resetting_password": "wachtwoord aan het resetten", - "RESET": "RESET", - "Return": "Terug", - "Review_app_title": "Geniet je van deze app?", - "Review_app_desc": "Geef ons 5 sterren op {{store}}", - "Review_app_yes": "Zeker!", - "Review_app_no": "Nee", - "Review_app_later": "Misschien later", - "Review_app_unable_store": "Kan {{store}} niet openen", - "Review_this_app": "Beoordeel deze app", - "Remove": "Verwijderen", - "remove": "verwijderen", - "Roles": "Rollen", - "Room_actions": "Kameracties", - "Room_changed_announcement": "Kameraankondiging gewijzigd in: {{announcement}} door {{userBy}}", - "Room_changed_avatar": "Kameravatar gewijzigd door {{userBy}}", - "Room_changed_description": "Kamerbeschrijving gewijzigd in: {{description}} door {{userBy}}", - "Room_changed_privacy": "Kamertype gewijzigd in: {{type}} door {{userBy}}", - "Room_changed_topic": "Oonderwerp van kamer gewijzigd in: {{topic}} door {{userBy}}", - "Room_Files": "Kamerbestanden", - "Room_Info_Edit": "Kamer info bewerken", - "Room_Info": "Kamer info", - "Room_Members": "Kamerleden", - "Room_name_changed": "Kamernaam gewijzigd in: {{name}} door {{userBy}}", - "SAVE": "OPSLAAN", - "Save_Changes": "Wijzigingen opslaan", - "Save": "Opslaan", - "Saved": "Opgeslagen", - "saving_preferences": "voorkeuren opslaan", - "saving_profile": "profiel opslaan", - "saving_settings": "instellingen opslaan", - "saved_to_gallery": "Opgeslagen in galerij", - "Save_Your_E2E_Password": "Bewaar jouw E2E-wachtwoord", - "Save_Your_Encryption_Password": "Bewaar jouw versleutelingswachtwoord", - "Save_Your_Encryption_Password_warning": "Dit wachtwoord wordt nergens opgeslagen, bewaar het dus zorgvuldig ergens anders.", - "Save_Your_Encryption_Password_info": "Indien je je wachtwoord verliest, is er geen enkel manier om het te herstellen en verlies je toegang tot je berichten.", - "Search_Messages": "Berichten zoeken", - "Search": "Zoeken", - "Search_by": "Zoeken op", - "Search_global_users": "Zoeken naar wereldwijde gebruikers", - "Search_global_users_description": "Als je dit inschakelt, kan je gebruikers van andere bedrijven en servers opzoeken.", - "Seconds": "{{second}} seconden", - "Security_and_privacy": "Veiligheid en privacy", - "Select_Avatar": "Selecteer avatar", - "Select_Server": "Selecteer server", - "Select_Users": "Selecteer gebruikers", - "Select_a_Channel": "Selecteer een kanaal", - "Select_a_Department": "Selecteer een afdeling", - "Select_an_option": "Selecteer een optie", - "Select_a_User": "Selecteer een gebruiker", - "Send": "Verzenden", - "Send_audio_message": "Audiobericht verzenden", - "Send_crash_report": "Crashrapport verzenden", - "Send_message": "Bericht verzenden", - "Send_me_the_code_again": "Stuur me de code opnieuw", - "Send_to": "Verzenden naar...", - "Sending_to": "Verzenden naar", - "Sent_an_attachment": "Een bijlage verzonden", - "Server": "Server", - "Servers": "Servers", - "Server_version": "Server versie: {{version}}", - "Set_username_subtitle": "De gebruikersnaam wordt gebruikt om anderen toe te staan jou in berichten te vermelden", - "Set_custom_status": "Aangepaste status instellen", - "Set_status": "Status instellen", - "Status_saved_successfully": "Status succesvol opgeslagen!", - "Settings": "Instellingen", - "Settings_succesfully_changed": "Instellingen succesvol gewijzigd!", - "Share": "Delen", - "Share_Link": "Deel link", - "Share_this_app": "Deel deze app", - "Show_more": "Meer tonen..", - "Show_Unread_Counter": "Toon ongelezen teller", - "Show_Unread_Counter_Info": "Ongelezen teller wordt weergegeven als een badge aan de rechterkant van het kanaal, in de lijst", - "Sign_in_your_server": "Log in op je server", - "Sign_Up": "Registreren", - "Some_field_is_invalid_or_empty": "Sommige velden zijn ongeldig of leeg", - "Sorting_by": "Sorteren op {{key}}", - "Sound": "Geluid", - "Star_room": "Favoriete kanalen", - "Star": "Ster", - "Starred_Messages": "Berichten met ster gemarkeerd", - "starred": "met ster gemarkeerd", - "Starred": "Met ster gemarkeerd", - "Start_of_conversation": "Begin van een gesprek", - "Start_a_Discussion": "Start een discussie", - "Started_discussion": "Begin van een discussie:", - "Started_call": "Oproep gestart door {{userBy}}", - "Submit": "Verzenden", - "Table": "Tabel", - "Tags": "Tags", - "Take_a_photo": "Neem een foto", - "Take_a_video": "Maak een video", - "Take_it": "Pak het!", - "tap_to_change_status": "tik om de status te wijzigen", - "Tap_to_view_servers_list": "Tik om de serverlijst te bekijken", - "Terms_of_Service": " Servicevoorwaarden ", - "Theme": "Thema", - "The_user_wont_be_able_to_type_in_roomName": "De gebruiker zal in {{roomName}} niet kunnen typen", - "The_user_will_be_able_to_type_in_roomName": "De gebruiker zal in {{roomName}} kunnen typen", - "There_was_an_error_while_action": "Er is een fout opgetreden tijdens {{action}}!", - "This_room_is_blocked": "Deze kamer is geblokkeerd", - "This_room_is_read_only": "Deze kamer is alleen-lezen", - "Thread": "Draad", - "Threads": "Draden", - "Timezone": "Tijdzone", - "To": "Naar", - "topic": "onderwerp", - "Topic": "Onderwerp", - "Translate": "Vertalen", - "Try_again": "Probeer het opnieuw", - "Two_Factor_Authentication": "Twee-factor authenticatie", - "Type_the_channel_name_here": "Typ hier de kanaalnaam", - "unarchive": "dearchiveren", - "UNARCHIVE": "DEARCHIVEREN", - "Unblock_user": "Deblokkeer gebruiker", - "Unfavorite": "Uit favorieten halen", - "Unfollowed_thread": "Draad ontvolgd", - "Unmute": "Dempen opheffen", - "unmuted": "ongedempt", - "Unpin": "Losmaken", - "unread_messages": "ongelezen", - "Unread": "Ongelezen", - "Unread_on_top": "Ongelezen bovenaan", - "Unstar": "Ster verwijderen", - "Updating": "Updaten...", - "Uploading": "Uploaden", - "Upload_file_question_mark": "Bestand uploaden?", - "User": "Gebruiker", - "Users": "Gebruikers", - "User_added_by": "Gebruiker {{userAdded}} toegevoegd door {{userBy}}", - "User_Info": "Gebruikers info", - "User_has_been_key": "Gebruiker is {{key}}", - "User_is_no_longer_role_by_": "{{user}} is niet langer {{role}} door {{userBy}}", - "User_muted_by": "Gebruiker {{userMuted}} gedempt door {{userBy}}", - "User_removed_by": "Gebruiker {{userRemoved}} verwijderd door {{userBy}}", - "User_sent_an_attachment": "{{user}} stuurde een bijlage", - "User_unmuted_by": "Dempen voor {{userUnmuted}} opgeheven door {{userBy}}", - "User_was_set_role_by_": "{{user}} is als {{role}} ingesteld door {{userBy}}", - "Username_is_empty": "Gebruikersnaam is leeg", - "Username": "Gebruikersnaam", - "Username_or_email": "Gebruikersnaam of e-mail", - "Uses_server_configuration": "Gebruikt serverconfiguratie", - "Validating": "Valideren", - "Registration_Succeeded": "Registratie geslaagd!", - "Verify": "Verifiëren", - "Verify_email_title": "Registratie geslaagd!", - "Verify_email_desc": "We hebben je een e-mail gestuurd om je inschrijving te bevestigen. Als je binnenkort geen e-mail ontvangt, gelieve terug te komen en het opnieuw te proberen.", - "Verify_your_email_for_the_code_we_sent": "Verifieer je e-mail voor de code die we hebben gestuurd", - "Video_call": "Videogesprek", - "View_Original": "Bekijk origineel", - "Voice_call": "Spraakoproep", - "Waiting_for_network": "Wachten op netwerk...", - "Websocket_disabled": "Websocket is uitgeschakeld voor deze server.\n{{contact}}", - "Welcome": "Welkom", - "What_are_you_doing_right_now": "Wat doe je op dit moment?", - "Whats_your_2fa": "Wat is je 2FA code?", - "Without_Servers": "Zonder servers", - "Workspaces": "Werkruimten", - "Would_you_like_to_return_the_inquiry": "Wil je de aanvraag retourneren?", - "Write_External_Permission_Message": "Rocket.Chat heeft toegang nodig tot je galerij zodat je afbeeldingen kunt opslaan.", - "Write_External_Permission": "Galerij toestemming", - "Yes": "Ja", - "Yes_action_it": "Ja, {{action}} het!", - "Yesterday": "Gisteren", - "You_are_in_preview_mode": "Je bent in voorbeeldmodus", - "You_are_offline": "Je bent offline", - "You_can_search_using_RegExp_eg": "Je kan RegExp. gebruiken, bijv. `/^tekst$/i`", - "You_colon": "Jij: ", - "you_were_mentioned": "je bent vermeld", - "You_were_removed_from_channel": "Je bent verwijderd uit {{channel}}", - "you": "jij", - "You": "Jij", - "Logged_out_by_server": "Je bent uitgelogd door de server. Gelieve opnieuw in te loggen.", - "You_need_to_access_at_least_one_RocketChat_server_to_share_something": "Je moet minstens toegang hebben tot één Rocket.Chat-server om iets te delen.", - "You_need_to_verifiy_your_email_address_to_get_notications": "Je moet je e-mailadres verifiëren om meldingen te ontvangen", - "Your_certificate": "Jouw certificaat", - "Your_invite_link_will_expire_after__usesLeft__uses": "Je uitnodigingslink verloopt na {{usesLeft}} keer.", - "Your_invite_link_will_expire_on__date__or_after__usesLeft__uses": "Je uitnodigingslink verloopt op {{date}} of na {{usesLeft}} keer.", - "Your_invite_link_will_expire_on__date__": "Je uitnodigingslink verloopt op {{date}}.", - "Your_invite_link_will_never_expire": "Je uitnodigingslink zal nooit verlopen.", - "Your_workspace": "Jouw werkruimte", - "Your_password_is": "Jouw wachtwoord is", - "Version_no": "Versie: {{version}}", - "You_will_not_be_able_to_recover_this_message": "Je zal dit bericht niet meer kunnen herstellen!", - "You_will_unset_a_certificate_for_this_server": "Je zal een certificaat voor deze server uitschakelen", - "Change_Language": "Taal veranderen", - "Crash_report_disclaimer": "We volgen nooit nooit de inhoud van je chats. Het crashrapport en de analytische gebeurtenissen bevatten alleen relevante informatie voor ons om problemen te identificeren en op te lossen.", - "Type_message": "Typ bericht", - "Room_search": "Kamers zoeken", - "Room_selection": "Kamerkeuze 1...9", - "Next_room": "Volgende kamer", - "Previous_room": "Vorige kamer", - "New_room": "Nieuwe kamer", - "Upload_room": "Uploaden naar kamer", - "Search_messages": "Berichten zoeken", - "Scroll_messages": "Berichten scrollen", - "Reply_latest": "Antwoord op laatste", - "Reply_in_Thread": "Reageer in discussie", - "Server_selection": "Server selectie", - "Server_selection_numbers": "Server selectie 1...9", - "Add_server": "Server toevoegen", - "New_line": "Nieuwe lijn", - "You_will_be_logged_out_of_this_application": "Je wordt uitgelogd van deze applicatie.", - "Clear": "Wissen", - "This_will_clear_all_your_offline_data": "Hiermee worden al jouw offline gegevens gewist.", - "This_will_remove_all_data_from_this_server": "Dit zal alle gegevens van deze server verwijderen.", - "Mark_unread": "Markeer als ongelezen", - "Wait_activation_warning": "Voordat u kunt inloggen, moet uw account handmatig worden geactiveerd door een beheerder.", - "Screen_lock": "Schermvergrendeling", - "Local_authentication_biometry_title": "Authenticeren", - "Local_authentication_biometry_fallback": "Gebruik toegangscode", - "Local_authentication_unlock_option": "Ontgrendelen met toegangscode", - "Local_authentication_change_passcode": "Wijzig toegangscode", - "Local_authentication_info": "Opmerking: als je de toegangscode vergeet, moet je de app verwijderen en opnieuw installeren.", - "Local_authentication_facial_recognition": "gezichtsherkenning", - "Local_authentication_fingerprint": "vingerafdruk", - "Local_authentication_unlock_with_label": "Ontgrendel met {{label}}", - "Local_authentication_auto_lock_60": "Na 1 minuut", - "Local_authentication_auto_lock_300": "Na 5 minuten", - "Local_authentication_auto_lock_900": "Na 15 minuten", - "Local_authentication_auto_lock_1800": "Na 30 minuten", - "Local_authentication_auto_lock_3600": "Na 1 uur", - "Passcode_enter_title": "Voer uw toegangscode in", - "Passcode_choose_title": "Kies je nieuwe toegangscode", - "Passcode_choose_confirm_title": "Bevestig je nieuwe toegangscode", - "Passcode_choose_error": "Toegangscodes komen niet overeen. Probeer het opnieuw.", - "Passcode_choose_force_set": "Toegangscode vereist door beheerder", - "Passcode_app_locked_title": "App vergrendeld", - "Passcode_app_locked_subtitle": "Probeer het over {{timeLeft}} seconden opnieuw", - "After_seconds_set_by_admin": "Na {{seconds}} seconden (ingesteld door beheerder)", - "Dont_activate": "Nu niet activeren", - "Queued_chats": "Chats in de wachtrij", - "Queue_is_empty": "Wachtrij is leeg", - "Logout_from_other_logged_in_locations": "Afmelden bij andere ingelogde locaties", - "You_will_be_logged_out_from_other_locations": "Je wordt uitgelogd van andere locaties.", - "Logged_out_of_other_clients_successfully": "Succesvol uitgelogd bij andere klanten", - "Logout_failed": "Uitloggen mislukt!", - "Log_analytics_events": "Analysegebeurtenissen loggen", - "E2E_encryption_change_password_title": "Versleutelingswachtwoord wijzigen", - "E2E_encryption_change_password_description": "Je kan nu versleutelde privégroepen en directe berichten aanmaken. Je kan ook bestaande privégroepen of DM's wijzigen in versleuteld.\nDit is end-to-end codering, dus de sleutel om jouw berichten te coderen/decoderen en deze wordt niet op de server opgeslagen. Daarom moet je dit wachtwoord op een veilige plaats opslaan. Je moet het invoeren op andere apparaten waarop je e2e-codering wilt gebruiken.", - "E2E_encryption_change_password_error": "Fout bij het wijzigen van het E2E-wachtwoord", - "E2E_encryption_change_password_success": "E2E-wachtwoord succesvol gewijzigd!", - "E2E_encryption_change_password_message": "Zorg ervoor dat je het zorgvuldig ergens anders hebt bewaard.", - "E2E_encryption_change_password_confirmation": "Ja, verander het", - "E2E_encryption_reset_title": "E2E-sleutel resetten", - "E2E_encryption_reset_description": "Deze optie zal je huidige E2E-sleutel verwijderen en je wordt uitgelogd.\nWanneer je opniew inlogt, genereert Rocket.Chat je een nieuwe sleutel en herstelt je toegang tot elke versleutelde kamer die een of meer leden heeft.\nDoor de aard van E2E-versleuteling kan Rocket.Chat de toegang tot een versleutelde kamer zonder online lid niet herstellen.", - "E2E_encryption_reset_button": "E2E-sleutel resetten", - "E2E_encryption_reset_error": "Fout bij het resetten van E2E-sleutel!", - "E2E_encryption_reset_message": "Je wordt uitgelogd.", - "E2E_encryption_reset_confirmation": "Ja, reset het", - "Following": "Volgend", - "Threads_displaying_all": "Alles weergeven", - "Threads_displaying_following": "Volgend weergeven", - "Threads_displaying_unread": "Ongelezen weergeven", - "No_threads": "Er zijn geen discussies", - "No_threads_following": "Je volgt geen discussies", - "No_threads_unread": "Er zijn geen ongelezen discussies", - "Messagebox_Send_to_channel": "Stuur naar kanaal", - "Leader": "Leider", - "Moderator": "Moderator", - "Owner": "Eigenaar", - "Remove_from_room": "Verwijderen uit kamer", - "Ignore": "Negeren", - "Unignore": "Niet meer negeren", - "User_has_been_ignored": "Gebruiker is genegeerd", - "User_has_been_unignored": "Gebruiker wordt niet langer genegeerd", - "User_has_been_removed_from_s": "Gebruiker is verwijderd van {{s}}", - "User__username__is_now_a_leader_of__room_name_": "Gebruiker {{username}} is nu een leider van {{room_name}}", - "User__username__is_now_a_moderator_of__room_name_": "Gebruiker {{username}} is nu een moderator van {{room_name}}", - "User__username__is_now_a_owner_of__room_name_": "Gebruiker {{username}} is nu eigenaar van {{room_name}}", - "User__username__removed_from__room_name__leaders": "Gebruiker {{username}} verwijderd uit {{room_name}} leiders", - "User__username__removed_from__room_name__moderators": "Gebruiker {{username}} verwijderd uit {{room_name}} moderators", - "User__username__removed_from__room_name__owners": "Gebruiker {{username}} verwijderd uit {{room_name}} eigenaars", - "The_user_will_be_removed_from_s": "De gebruiker wordt verwijderd uit {{s}}", - "Yes_remove_user": "Ja, verwijder gebruiker!", - "Direct_message": "Direct bericht", - "Message_Ignored": "Bericht genegeerd. Tik om het weer te geven.", - "Enter_workspace_URL": "Voer de werkruimte-URL in", - "Workspace_URL_Example": "Vb. uw-bedrijf.rocket.chat", - "This_room_encryption_has_been_enabled_by__username_": "De versleuteling van deze kamer is ingeschakeld door {{username}}", - "This_room_encryption_has_been_disabled_by__username_": "De versleuteling van deze kamer is uitgeschakeld door {{username}}", - "Teams": "Teams", - "No_team_channels_found": "Geen kanalen gevonden", - "Team_not_found": "Team niet gevonden", - "Create_Team": "Team aanmaken", - "Team_Name": "Teamnaam", - "Private_Team": "Privé team", - "Read_Only_Team": "Alleen-lezen team", - "Broadcast_Team": "Broadcast team", - "creating_team": "team maken", - "team-name-already-exists": "Er bestaat al een team met die naam", - "Add_Channel_to_Team": "Kanaal toevoegen aan team", - "Left_The_Team_Successfully": "Het team met succes verlaten", - "Create_New": "Maak nieuw", - "Add_Existing": "Voeg bestaande", - "Add_Existing_Channel": "Bestaand kanaal toevoegen", - "Remove_from_Team": "Verwijderen uit team", - "Auto-join": "Automatisch deelnemen", - "Remove_Team_Room_Warning": "Wil je dit kanaal uit het team verwijderen? Het kanaal wordt terug naar de werkruimte verplaatst", - "Confirmation": "Bevestiging", - "invalid-room": "Ongeldige kamer", - "You_are_leaving_the_team": "Je verlaat het team '{{team}}'", - "Leave_Team": "Team verlaten", - "Select_Team": "Selecteer team", - "Select_Team_Channels": "Selecteer de kanalen van het team die je wilt verlaten.", - "Cannot_leave": "Kan niet weggaan", - "Cannot_remove": "Kan niet verwijderen", - "Cannot_delete": "Kan niet verwijderen", - "Last_owner_team_room": "Je bent de laatste eigenaar van dit kanaal. Zodra u het team verlaat, blijft het kanaal binnen het team, maar beheert u het van buitenaf.", - "last-owner-can-not-be-removed": "Laatste eigenaar kan niet worden verwijderd.", - "Remove_User_Teams": "Selecteer de kanalen waarvan je de gebruiker wilt verwijderen.", - "Delete_Team": "Team verwijderen", - "Select_channels_to_delete": "Dit kan niet ongedaan worden gemaakt. Zodra je een team verwijdert, worden alle chatinhoud en configuratie verwijderd.\n\nSelecteer de kanalen die je wilt verwijderen. Degene die je besluit te behouden, zullen in jouw werkruimte beschikbaar zijn. Hou er rekening mee dat openbare kanalen nog steeds openbaar en voor iedereen zichtbaar zijn.", - "You_are_deleting_the_team": "Je verwijdert dit team.", - "Removing_user_from_this_team": "Je verwijdert {{user}} uit dit team", - "Remove_User_Team_Channels": "Selecteer de kanalen waarvan je de gebruiker wilt verwijderen.", - "Remove_Member": "Lid verwijderen", - "leaving_team": "team verlaten", - "removing_team": "verwijderen uit team", - "moving_channel_to_team": "kanaal verplaatsen naar team", - "deleting_team": "team verwijderen", - "member-does-not-exist": "Lid bestaat niet", - "Convert": "Converteren", - "Convert_to_Team": "Converteren naar team", - "Convert_to_Team_Warning": "Je converteert dit kanaal naar een team. Alle leden blijven behouden.", - "Move_to_Team": "Verplaats naar team", - "Move_Channel_Paragraph": "Het verplaatsen van een kanaal binnen een team betekent dat dit kanaal wordt toegevoegd in de context van het team. Maar, alle leden van dit kanaal, die geen lid zijn van het respectieve team, zullen nog steeds toegang hebben tot dit kanaal, maar worden niet als teamleden toegevoegd.\n\nHet volledige beheer van dit kanaal wordt nog steeds door de eigenaren van dit kanaal gedaan.\n\nTeamleden en zelfs teameigenaren, wanneer ze geen lid zijn van dit kanaal, hebben geen toegang tot de content van het kanaal.\n\nHou er rekening mee dat de eigenaar van het team de leden uit het kanaal kan verwijderen.", - "Move_to_Team_Warning": "Wil je na het lezen van de vorige instructies over dit gedrag, dit kanaal nog steeds naar het geselecteerde team verplaatsen?", - "Load_More": "Meer laden", - "Load_Newer": "Nieuwer laden", - "Load_Older": "Ouder laden", - "Left_The_Room_Successfully": "Heeft kamer met succes verlaten", - "Deleted_The_Team_Successfully": "Team succesvol verwijderd", - "Deleted_The_Room_Successfully": "Kamer succesvol verwijderd", - "Convert_to_Channel": "Converteren naar kanaal", - "Converting_Team_To_Channel": "Team converteren naar kanaal", - "Select_Team_Channels_To_Delete": "Selecteer de teamkanalen die je wilt verwijderen, de kanalen die u niet selecteert, worden naar de werkruimte verplaatst.\n\nMerk op dat openbare kanalen openbaar en voor iedereen zichtbaar zullen zijn.", - "You_are_converting_the_team": "Je converteert dit team naar een kanaal" -} \ No newline at end of file + "1_person_reacted": "1 persoon heeft gereageerd", + "1_user": "1 gebruiker", + "error-action-not-allowed": "{{action}} is niet toegestaan", + "error-application-not-found": "Applicatie niet gevonden", + "error-archived-duplicate-name": "Er is een gearchiveerd kanaal met de naam {{room_name}}", + "error-avatar-invalid-url": "Ongeldige avatar-URL: {{url}}", + "error-avatar-url-handling": "Fout bij het verwerken van de avatar-instelling van een URL ({{url}}) voor {{username}}", + "error-cant-invite-for-direct-room": "Kan gebruikers in directe kamers niet uitnodigen", + "error-could-not-change-email": "Kan e-mail niet veranderen", + "error-could-not-change-name": "Kan naam niet veranderen", + "error-could-not-change-username": "Kan gebruikersnaam niet veranderen", + "error-could-not-change-status": "Kan status niet wijzigen", + "error-delete-protected-role": "Kan een beveiligde rol niet verwijderen", + "error-department-not-found": "Afdeling niet gevonden", + "error-direct-message-file-upload-not-allowed": "Delen van bestanden in privéberichten niet toegestaan", + "error-duplicate-channel-name": "Een kanaal met naam {{room_name}} bestaat", + "error-email-domain-blacklisted": "Het e-maildomein staat op de zwarte lijst", + "error-email-send-failed": "Fout bij het verzenden van e-mail: {{message}}", + "error-save-image": "Fout bij het opslaan van afbeelding", + "error-save-video": "Fout bij het opslaan van video", + "error-field-unavailable": "{{field}} is al in gebruik :(", + "error-file-too-large": "Bestand is te groot", + "error-importer-not-defined": "De importeur is niet correct gedefinieerd, de klasse Import ontbreekt.", + "error-input-is-not-a-valid-field": "{{input}} is geen geldig {{field}}", + "error-invalid-actionlink": "Ongeldige actielink", + "error-invalid-arguments": "Ongeldige argumenten", + "error-invalid-asset": "Ongeldig item", + "error-invalid-channel": "Ongeldig kanaal.", + "error-invalid-channel-start-with-chars": "Ongeldig kanaal. Begin met @ of #", + "error-invalid-custom-field": "Ongeldig aangepast veld", + "error-invalid-custom-field-name": "Ongeldige aangepaste veldnaam. Gebruik alleen letters, cijfers, koppeltekens en underscores.", + "error-invalid-date": "Ongeldige datum opgegeven.", + "error-invalid-description": "Ongeldige beschrijving", + "error-invalid-domain": "Ongeldig domein", + "error-invalid-email": "Ongeldig e-mail {{email}}", + "error-invalid-email-address": "Ongeldig e-mailadres", + "error-invalid-file-height": "Ongeldige bestandshoogte", + "error-invalid-file-type": "Ongeldig bestandstype", + "error-invalid-file-width": "Ongeldige bestandsbreedte", + "error-invalid-from-address": "Je hebt een ongeldig FROM adres opgegeven.", + "error-invalid-integration": "Ongeldige integratie", + "error-invalid-message": "Ongeldig bericht", + "error-invalid-method": "Ongeldige methode", + "error-invalid-name": "Ongeldige naam", + "error-invalid-password": "Ongeldig wachtwoord", + "error-invalid-redirectUri": "Ongeldige redirectUri", + "error-invalid-role": "Ongeldige rol", + "error-invalid-room": "Ongeldige kamer", + "error-invalid-room-name": "{{room_name}} is geen geldige kamernaam", + "error-invalid-room-type": "{{type}} is geen geldig kamertype.", + "error-invalid-settings": "Ongeldige instellingen opgegeven", + "error-invalid-subscription": "Ongeldig abonnement", + "error-invalid-token": "Ongeldige token", + "error-invalid-triggerWords": "Ongeldige triggerWoorden", + "error-invalid-urls": "Ongeldige URL's", + "error-invalid-user": "Ongeldige gebruiker", + "error-invalid-username": "Ongeldige gebruikersnaam", + "error-invalid-webhook-response": "De webhook-URL heeft met een andere status dan 200 gereageerd", + "error-message-deleting-blocked": "Het verwijderen van berichten is geblokkeerd", + "error-message-editing-blocked": "Het aanpassen van berichten is geblokkeerd", + "error-message-size-exceeded": "Berichtgrootte is groter dan Message_MaxAllowedSize", + "error-missing-unsubscribe-link": "Je moet de link [unsubscribe] opgeven.", + "error-no-owner-channel": "Je bent niet de eigenaar van het kanaal", + "error-no-tokens-for-this-user": "Er zijn geen tokens voor deze gebruiker", + "error-not-allowed": "Niet toegestaan", + "error-not-authorized": "Geen bevoegdheid", + "error-push-disabled": "Push is uitgeschakeld", + "error-remove-last-owner": "Dit is de laatste eigenaar. Stel een nieuwe eigenaar in voordat je deze verwijdert.", + "error-role-in-use": "Kan rol niet verwijderen omdat deze in gebruik is", + "error-role-name-required": "Rolnaam is vereist", + "error-the-field-is-required": "Het veld {{field}} is verplicht.", + "error-too-many-requests": "Fout, te veel verzoeken. Vertraag, alsjeblieft. Je moet {{seconds}} seconden wachten voordat je het opnieuw probeert.", + "error-user-is-not-activated": "Gebruiker is niet geactiveerd", + "error-user-has-no-roles": "Gebruiker heeft geen rollen", + "error-user-limit-exceeded": "Het aantal gebruikers die je probeert uit te nodigen voor #channel_name overschrijdt de limiet ingesteld door de beheerder", + "error-user-not-in-room": "Gebruiker is niet in deze kamer", + "error-user-registration-custom-field": "error-user-registration-custom-field", + "error-user-registration-disabled": "Gebruikersregistratie is uitgeschakeld", + "error-user-registration-secret": "Gebruikersregistratie is alleen via geheime URL toegestaan", + "error-you-are-last-owner": "Je bent de laatste eigenaar. Stel een nieuwe eigenaar in voordat je de kamer verlaat.", + "error-status-not-allowed": "Onzichtbare status is uitgeschakeld", + "Actions": "Acties", + "activity": "activiteit", + "Activity": "Activiteit", + "Add_Reaction": "Reactie toevoegen", + "Add_Server": "Server toevoegen", + "Add_users": "Gebruikers toevoegen", + "Admin_Panel": "Admin Paneel", + "Agent": "Agent", + "Alert": "Waarschuwing", + "alert": "waarschuwing", + "alerts": "waarschuwingen", + "All_users_in_the_channel_can_write_new_messages": "Alle gebruikers in het kanaal kunnen nieuwe berichten schrijven", + "All_users_in_the_team_can_write_new_messages": "Alle gebruikers in het team kunnen nieuwe berichten schrijven", + "A_meaningful_name_for_the_discussion_room": "Een betekenisvolle naam voor de discussieruimte", + "All": "Alle", + "All_Messages": "Alle berichten", + "Allow_Reactions": "Reacties toestaan", + "Alphabetical": "Alfabetisch", + "and_more": "en meer", + "and": "en", + "announcement": "aankondiging", + "Announcement": "Aankondiging", + "Apply_Your_Certificate": "Pas jouw certificaat toe", + "ARCHIVE": "ARCHIVEER", + "archive": "archiveer", + "are_typing": "zijn aan het typen", + "Are_you_sure_question_mark": "Weet je het zeker?", + "Are_you_sure_you_want_to_leave_the_room": "Weet je zeker dat je de kamer {{room}} wilt verlaten?", + "Audio": "Audio", + "Authenticating": "Authenticatie", + "Automatic": "Automatisch", + "Auto_Translate": "Automatisch vertalen", + "Avatar_changed_successfully": "Avatar succesvol gewijzigd!", + "Avatar_Url": "Avatar-URL", + "Away": "Afwezig", + "Back": "Terug", + "Black": "Zwart", + "Block_user": "Blokkeer gebruiker", + "Browser": "Browser", + "Broadcast_channel_Description": "Alleen geautoriseerde gebruikers kunnen nieuwe berichten schrijven, maar de andere gebruikers zullen kunnen antwoorden", + "Broadcast_Channel": "Uitzendkanaal", + "Busy": "Bezig", + "By_proceeding_you_are_agreeing": "Door verder te gaan ga je akkoord met onze", + "Cancel_editing": "Bewerken annuleren", + "Cancel_recording": "Opname annuleren", + "Cancel": "Annuleren", + "changing_avatar": "avatar aan het veranderen", + "creating_channel": "kanaal aan het maken", + "creating_invite": "uitnodiging maken", + "Channel_Name": "Kanaal naam", + "Channels": "Kanalen", + "Chats": "Chats", + "Call_already_ended": "Gesprek al beeïndigd!", + "Clear_cookies_alert": "Wilt u alle cookies wissen?", + "Clear_cookies_desc": "Met deze actie worden alle inlogcookies gewist, zodat u op andere accounts kunt inloggen.", + "Clear_cookies_yes": "Ja, cookies wissen", + "Clear_cookies_no": "Nee, bewaar cookies", + "Click_to_join": "Klik om mee te doen!", + "Close": "Sluiten", + "Close_emoji_selector": "Emoji-kiezer sluiten", + "Closing_chat": "Chat sluiten", + "Change_language_loading": "Taal veranderen", + "Chat_closed_by_agent": "Chat gesloten door agent", + "Choose": "Kies", + "Choose_from_library": "Kies uit bibliotheek", + "Choose_file": "Kies bestand", + "Choose_where_you_want_links_be_opened": "Kies waar je links wilt openen", + "Code": "Code", + "Code_or_password_invalid": "Code of wachtwoord ongeldig", + "Collaborative": "Samenwerkend", + "Confirm": "Bevestig", + "Connect": "Verbinden", + "Connected": "Verbonden", + "connecting_server": "verbonden met de server", + "Connecting": "Aan het verbinden...", + "Contact_us": "Neem contact op", + "Contact_your_server_admin": "Neem contact op met je serverbeheerder.", + "Continue_with": "Ga verder met", + "Copied_to_clipboard": "Gekopieerd naar klembord!", + "Copy": "Kopiëren", + "Conversation": "Conversatie", + "Permalink": "Permalink", + "Certificate_password": "Certificaat wachtwoord", + "Clear_cache": "Lokale server cache wissen", + "Clear_cache_loading": "Cache wissen.", + "Whats_the_password_for_your_certificate": "Wat is het wachtwoord voor jouw certificaat?", + "Create_account": "Account aanmaken", + "Create_Channel": "Kanaal aanmaken", + "Create_Direct_Messages": "Directe berichten aanmaken", + "Create_Discussion": "Discussie aanmaken", + "Created_snippet": "knipsel aangemaakt", + "Create_a_new_workspace": "Een nieuwe werkruimte aanmaken", + "Create": "Aanmaken", + "Custom_Status": "Aangepaste status", + "Dark": "Donker", + "Dark_level": "Donker niveau", + "Default": "Standaard", + "Default_browser": "Standaard browser", + "Delete_Room_Warning": "Als je een kamer verwijdert, worden alle berichten die in de kamer geplaatst zijn, verwijderd. Dit kan niet ongedaan worden gemaakt.", + "Department": "Afdeling", + "delete": "verwijderen", + "Delete": "Verwijderen", + "DELETE": "VERWIJDEREN", + "move": "verplaatsen", + "deleting_room": "kamer verwijderen", + "description": "omschrijving", + "Description": "Omschrijving", + "Desktop_Options": "Bureaubladopties", + "Desktop_Notifications": "Desktopmeldingen", + "Desktop_Alert_info": "Deze meldingen worden op desktop geleverd", + "Directory": "Map", + "Direct_Messages": "Directe berichten", + "Disable_notifications": "Zet notificaties uit", + "Discussions": "Discussies", + "Discussion_Desc": "Help het overzicht te houden over wat er aan de hand is! Door een discussie aan te maken, wordt een subkanaal van het geselecteerde kanaal aangemaakt en worden beide gekoppeld.", + "Discussion_name": "Discussienaam", + "Done": "Gedaan", + "Dont_Have_An_Account": "Heb je geen account?", + "Do_you_have_an_account": "Heb je een account?", + "Do_you_have_a_certificate": "Heb je een certificaat?", + "Do_you_really_want_to_key_this_room_question_mark": "Wil je deze kamer echt {{key}}?", + "E2E_Encryption": "E2E-codering", + "E2E_How_It_Works_info1": "U kunt nu versleutelde privégroepen en directe berichten aanmaken. U kunt ook bestaande privégroepen of DM's wijzigen in versleuteld.", + "E2E_How_It_Works_info2": "Dit is *end-to-end codering*, dus de sleutel om jouw berichten te coderen/decoderen en deze wordt niet op de server opgeslagen. Daarom *moet je dit wachtwoord op een veilige plaats opslaan* waar je later toegang hebt als je dat nodig hebt.", + "E2E_How_It_Works_info3": "Als je doorgaat, wordt er automatisch een E2E-wachtwoord gegenereerd.", + "E2E_How_It_Works_info4": "Je kan ook op elk moment een nieuw wachtwoord voor uw coderingssleutel instellen vanuit elke browser waarin u het bestaande E2E-wachtwoord hebt ingevoerd.", + "edit": "bewerk", + "edited": "bewerkt", + "Edit": "Bewerk", + "Edit_Status": "Status bewerken", + "Edit_Invite": "Bewerk uitnodiging", + "End_to_end_encrypted_room": "End-to-end versleutelde kamer", + "end_to_end_encryption": "end-to-end encryptie", + "Email_Notification_Mode_All": "Elke vermelding/DM", + "Email_Notification_Mode_Disabled": "Uitgeschakeld", + "Email_or_password_field_is_empty": "E-mail of wachtwoordveld is leeg", + "Email": "E-mail", + "email": "e-mail", + "Empty_title": "Lege titel", + "Enable_Auto_Translate": "Automatisch vertalen inschakelen", + "Enable_notifications": "Notificaties aanzetten", + "Encrypted": "Versleuteld", + "Encrypted_message": "Versleuteld bericht", + "Enter_Your_E2E_Password": "Voer uw E2E-wachtwoord in", + "Enter_Your_Encryption_Password_desc1": "Hiermee krijg je toegang tot uw gecodeerde privégroepen en directe berichten.", + "Enter_Your_Encryption_Password_desc2": "Op elke plaats waar je de chat gebruikt, moet je het wachtwoord invoeren om berichten te coderen/decoderen.", + "Encryption_error_title": "Jouw coderingswachtwoord lijkt verkeerd", + "Encryption_error_desc": "Het was niet mogelijk om uw coderingssleutel te decoderen om te worden geïmporteerd.", + "Everyone_can_access_this_channel": "Iedereen heeft toegang tot dit kanaal", + "Everyone_can_access_this_team": "Iedereen heeft toegang tot dit team", + "Error_uploading": "Fout bij uploaden", + "Expiration_Days": "Vervaldatum (Dagen)", + "Favorite": "Favoriet", + "Favorites": "Favorieten", + "Files": "Bestanden", + "File_description": "Bestandsbeschrijving", + "File_name": "Bestandsnaam", + "Finish_recording": "Opname beëindigen", + "Following_thread": "Volg discussie", + "For_your_security_you_must_enter_your_current_password_to_continue": "Voor je veiligheid moet je je huidige wachtwoord invoeren om door te gaan", + "Forgot_password_If_this_email_is_registered": "Als dit e-mailadres geregistreerd is, sturen we instructies om je wachtwoord opnieuw in te stellen. Als je geen e-mail ontvangt, kom dan terug en probeer het opnieuw.", + "Forgot_password": "Wachtwoord vergeten?", + "Forgot_Password": "Wachtwoord vergeten", + "Forward": "Doorsturen", + "Forward_Chat": "Chat doorsturen", + "Forward_to_department": "Doorsturen naar afdeling", + "Forward_to_user": "Doorsturen naar gebruiker", + "Full_table": "Klik om de volledige tabel te zien", + "Generate_New_Link": "Nieuwe link genereren", + "Group_by_favorites": "Groepeer favorieten", + "Group_by_type": "Groeperen op type", + "Hide": "Verberg", + "Has_joined_the_channel": "is bij het kanaal gekomen", + "Has_joined_the_conversation": "heeft zich bij het gesprek aangesloten", + "Has_left_the_channel": "heeft het kanaal verlaten", + "Hide_System_Messages": "Verberg systeemberichten", + "Hide_type_messages": "Verberg \"{{type}}\" berichten", + "How_It_Works": "Hoe het werkt", + "Message_HideType_uj": "Gebruiker neemt deel", + "Message_HideType_ul": "Gebruiker vertrokken", + "Message_HideType_ru": "Gebruiker verwijderd", + "Message_HideType_au": "Gebruiker toegevoegd", + "Message_HideType_mute_unmute": "Gebruiker gedempt / kan weer praten", + "Message_HideType_r": "Kamernaam gewijzigd", + "Message_HideType_ut": "Gebruiker neemt deel aan gesprek", + "Message_HideType_wm": "Welkom", + "Message_HideType_rm": "Bericht verwijderd", + "Message_HideType_subscription_role_added": "Kreeg rol", + "Message_HideType_subscription_role_removed": "Rol niet langer gedefinieerd", + "Message_HideType_room_archived": "Kamer gearchiveerd", + "Message_HideType_room_unarchived": "Kamer niet gearchiveerd", + "I_Saved_My_E2E_Password": "Ik heb mijn E2E-wachtwoord opgeslagen", + "IP": "IP", + "In_app": "In-app", + "In_App_And_Desktop": "In-app en desktop", + "In_App_and_Desktop_Alert_info": "Geeft een banner boven aan het scherm weer wanneer de app geopend is, en geeft een melding op desktop weer", + "Invisible": "Onzichtbaar", + "Invite": "Nodig uit", + "is_a_valid_RocketChat_instance": "is een geldige Rocket.Chat instantie", + "is_not_a_valid_RocketChat_instance": "is geen geldige Rocket.Chat instantie", + "is_typing": "is aan het typen", + "Invalid_or_expired_invite_token": "Ongeldige of verlopen uitnodigingstoken", + "Invalid_server_version": "De server waarmee je probeert te verbinden, gebruikt een versie die niet meer door de app wordt ondersteund: {{currentVersion}}.\n\nWe hebben versie {{minVersion}} nodig", + "Invite_Link": "Uitnodigingslink", + "Invite_users": "Gebruikers uitnodigen", + "Join": "Doe mee", + "Join_Code": "Deelnamecode", + "Insert_Join_Code": "Deelnamecode invoegen", + "Join_our_open_workspace": "Word lid van onze open werkruimte", + "Join_your_workspace": "Word lid van jouw werkruimte", + "Just_invited_people_can_access_this_channel": "Alleen uitgenodigde mensen hebben toegang tot dit kanaal", + "Just_invited_people_can_access_this_team": "Alleen uitgenodigde mensen hebben toegang tot dit team", + "Language": "Taal", + "last_message": "laatste bericht", + "Leave_channel": "Kanaal verlaten", + "leaving_room": "ruimte verlaten", + "Leave": "Verlaten", + "leave": "verlaten", + "Legal": "Legaal", + "Light": "Licht", + "License": "Licentie", + "Livechat": "Livechat", + "Livechat_edit": "Livechat bewerken", + "Login": "Inloggen", + "Login_error": "Je inloggegevens zijn geweigerd! Probeer het opnieuw.", + "Login_with": "Inloggen met", + "Logging_out": "Uitloggen.", + "Logout": "Uitloggen", + "Max_number_of_uses": "Max aantal toepassingen", + "Max_number_of_users_allowed_is_number": "Max aantal toegestane gebruikers is {{maxUsers}}", + "members": "leden", + "Members": "Leden", + "Mentioned_Messages": "Vermelde berichten", + "mentioned": "vermeld", + "Mentions": "Vermeldingen", + "Message_accessibility": "Bericht van {{user}} om {{time}}: {{message}}", + "Message_actions": "Berichtacties", + "Message_pinned": "Bericht vastgezet", + "Message_removed": "Bericht verwijderd", + "Message_starred": "Bericht met ster", + "Message_unstarred": "Bericht zonder ster", + "message": "bericht", + "messages": "berichten", + "Message": "Bericht", + "Messages": "Berichten", + "Message_Reported": "Bericht gerapporteerd", + "Microphone_Permission_Message": "Rocket.Chat heeft toegang tot je microfoon nodig zodat je een audiobericht kunt verzenden.", + "Microphone_Permission": "Microfoontoestemming", + "Mute": "Dempen", + "muted": "gedempt", + "My_servers": "Mijn servers", + "N_people_reacted": "{{n}} mensen hebben gereageerd", + "N_users": "{{n}} gebruikers", + "N_channels": "{{n}} kanalen", + "name": "naam", + "Name": "Naam", + "Navigation_history": "Navigatie geschiedenis", + "Never": "Nooit", + "New_Message": "Nieuw bericht", + "New_Password": "Nieuw wachtwoord", + "New_Server": "Nieuwe server", + "Next": "Volgende", + "No_files": "Geen bestanden", + "No_limit": "Geen limiet", + "No_mentioned_messages": "Geen vermelde berichten", + "No_pinned_messages": "Geen vastgezette berichten", + "No_results_found": "Geen resultaten gevonden", + "No_starred_messages": "Geen berichten met ster gemarkeerd", + "No_thread_messages": "Geen discussieberichten", + "No_label_provided": "Geen {{label}} opgegeven.", + "No_Message": "Geen bericht", + "No_messages_yet": "Nog geen berichten", + "No_Reactions": "Geen reacties", + "No_Read_Receipts": "Geen leesbevestigingen", + "Not_logged": "Niet ingelogd", + "Not_RC_Server": "Dit is geen Rocket.Chat-server.\n{{contact}}", + "Nothing": "Niets", + "Nothing_to_save": "Niets om op te slaan!", + "Notify_active_in_this_room": "Waarschuw actieve gebruikers in deze kamer", + "Notify_all_in_this_room": "Breng iedereen in deze kamer op de hoogte", + "Notifications": "Notificaties", + "Notification_Duration": "Duur van de notificatie", + "Notification_Preferences": "Notificatievoorkeuren", + "No_available_agents_to_transfer": "Geen beschikbare agenten om door te sturen", + "Offline": "Offline", + "Oops": "Oeps!", + "Omnichannel": "Omnichannel", + "Open_Livechats": "Bezig met chatten", + "Omnichannel_enable_alert": "Je bent niet beschikbaar op Omnichannel. Wil je beschikbaar zijn?", + "Onboarding_description": "Een werkruimte is de ruimte van jouw team of organisatie om samen te werken. Vraag aan de beheerder van de werkruimte een adres om lid te worden of maak er een aan voor jouw team.", + "Onboarding_join_workspace": "Word lid van een werkruimte", + "Onboarding_subtitle": "Meer dan teamsamenwerking", + "Onboarding_title": "Welkom bij Rocket.Chat", + "Onboarding_join_open_description": "Word lid van onze open werkruimte om met het Rocket.Chat team en de community te chatten.", + "Onboarding_agree_terms": "Door verder te gaan, ga je akkoord met Rocket.Chat", + "Onboarding_less_options": "Minder opties", + "Onboarding_more_options": "Meer opties", + "Online": "Online", + "Only_authorized_users_can_write_new_messages": "Alleen geautoriseerde gebruikers kunnen nieuwe berichten schrijven", + "Open_emoji_selector": "Emoji-kiezer openen", + "Open_Source_Communication": "Open Source Communicatie", + "Open_your_authentication_app_and_enter_the_code": "Open je authenticatie-app en voer de code in.", + "OR": "OF", + "OS": "OS", + "Overwrites_the_server_configuration_and_use_room_config": "Overschrijft de serverconfiguratie en gebruikt kamer config", + "Password": "Wachtwoord", + "Parent_channel_or_group": "Bovenliggend kanaal of groep", + "Permalink_copied_to_clipboard": "Permalink gekopiëerd naar klembord!", + "Phone": "Telefoon", + "Pin": "Vastzetten", + "Pinned_Messages": "Vastgezette berichten", + "pinned": "vastgezet", + "Pinned": "Vastgezet", + "Please_add_a_comment": "Voeg een reactie toe", + "Please_enter_your_password": "Voer je wachtwoord in", + "Please_wait": "Even geduld.", + "Preferences": "Voorkeuren", + "Preferences_saved": "Voorkeuren opgeslagen!", + "Privacy_Policy": " Privacybeleid", + "Private_Channel": "Privékanaal", + "Private": "Privé", + "Processing": "Verwerking...", + "Profile_saved_successfully": "Profiel succesvol opgeslagen!", + "Profile": "Profiel", + "Public_Channel": "Publiek kanaal", + "Public": "Publiek", + "Push_Notifications": "Pushmeldingen", + "Push_Notifications_Alert_Info": "Deze notificaties worden aan jouw enkel geleverd als de app niet geopend is", + "Quote": "Citaat", + "Reactions_are_disabled": "Reacties zijn uitgeschakeld", + "Reactions_are_enabled": "Reacties zijn ingeschakeld", + "Reactions": "Reacties", + "Read": "Lezen", + "Read_External_Permission_Message": "Rocket.Chat heeft toegang nodig tot foto's, media en bestanden op je apparaat", + "Read_External_Permission": "Lees toestemming voor media", + "Read_Only_Channel": "Alleen-lezen kanaal", + "Read_Only": "Alleen lezen", + "Read_Receipt": "Leesbevestiging", + "Receive_Group_Mentions": "Groepsvermeldingen ontvangen", + "Receive_Group_Mentions_Info": "Ontvang @all en @here vermeldingen", + "Register": "Registreren", + "Repeat_Password": "Herhaal wachtwoord", + "Replied_on": "Beantwoord op:", + "replies": "antwoordt", + "reply": "antwoord", + "Reply": "Antwoord", + "Report": "Rapporteren", + "Receive_Notification": "Notificatie ontvangen", + "Receive_notifications_from": "Ontvang notificaties van {{name}}", + "Resend": "Opnieuw verzenden", + "Reset_password": "Wachtwoord resetten", + "resetting_password": "wachtwoord aan het resetten", + "RESET": "RESET", + "Return": "Terug", + "Review_app_title": "Geniet je van deze app?", + "Review_app_desc": "Geef ons 5 sterren op {{store}}", + "Review_app_yes": "Zeker!", + "Review_app_no": "Nee", + "Review_app_later": "Misschien later", + "Review_app_unable_store": "Kan {{store}} niet openen", + "Review_this_app": "Beoordeel deze app", + "Remove": "Verwijderen", + "remove": "verwijderen", + "Roles": "Rollen", + "Room_actions": "Kameracties", + "Room_changed_announcement": "Kameraankondiging gewijzigd in: {{announcement}} door {{userBy}}", + "Room_changed_avatar": "Kameravatar gewijzigd door {{userBy}}", + "Room_changed_description": "Kamerbeschrijving gewijzigd in: {{description}} door {{userBy}}", + "Room_changed_privacy": "Kamertype gewijzigd in: {{type}} door {{userBy}}", + "Room_changed_topic": "Oonderwerp van kamer gewijzigd in: {{topic}} door {{userBy}}", + "Room_Files": "Kamerbestanden", + "Room_Info_Edit": "Kamer info bewerken", + "Room_Info": "Kamer info", + "Room_Members": "Kamerleden", + "Room_name_changed": "Kamernaam gewijzigd in: {{name}} door {{userBy}}", + "SAVE": "OPSLAAN", + "Save_Changes": "Wijzigingen opslaan", + "Save": "Opslaan", + "Saved": "Opgeslagen", + "saving_preferences": "voorkeuren opslaan", + "saving_profile": "profiel opslaan", + "saving_settings": "instellingen opslaan", + "saved_to_gallery": "Opgeslagen in galerij", + "Save_Your_E2E_Password": "Bewaar jouw E2E-wachtwoord", + "Save_Your_Encryption_Password": "Bewaar jouw versleutelingswachtwoord", + "Save_Your_Encryption_Password_warning": "Dit wachtwoord wordt nergens opgeslagen, bewaar het dus zorgvuldig ergens anders.", + "Save_Your_Encryption_Password_info": "Indien je je wachtwoord verliest, is er geen enkel manier om het te herstellen en verlies je toegang tot je berichten.", + "Search_Messages": "Berichten zoeken", + "Search": "Zoeken", + "Search_by": "Zoeken op", + "Search_global_users": "Zoeken naar wereldwijde gebruikers", + "Search_global_users_description": "Als je dit inschakelt, kan je gebruikers van andere bedrijven en servers opzoeken.", + "Seconds": "{{second}} seconden", + "Security_and_privacy": "Veiligheid en privacy", + "Select_Avatar": "Selecteer avatar", + "Select_Server": "Selecteer server", + "Select_Users": "Selecteer gebruikers", + "Select_a_Channel": "Selecteer een kanaal", + "Select_a_Department": "Selecteer een afdeling", + "Select_an_option": "Selecteer een optie", + "Select_a_User": "Selecteer een gebruiker", + "Send": "Verzenden", + "Send_audio_message": "Audiobericht verzenden", + "Send_crash_report": "Crashrapport verzenden", + "Send_message": "Bericht verzenden", + "Send_me_the_code_again": "Stuur me de code opnieuw", + "Send_to": "Verzenden naar...", + "Sending_to": "Verzenden naar", + "Sent_an_attachment": "Een bijlage verzonden", + "Server": "Server", + "Servers": "Servers", + "Server_version": "Server versie: {{version}}", + "Set_username_subtitle": "De gebruikersnaam wordt gebruikt om anderen toe te staan jou in berichten te vermelden", + "Set_custom_status": "Aangepaste status instellen", + "Set_status": "Status instellen", + "Status_saved_successfully": "Status succesvol opgeslagen!", + "Settings": "Instellingen", + "Settings_succesfully_changed": "Instellingen succesvol gewijzigd!", + "Share": "Delen", + "Share_Link": "Deel link", + "Share_this_app": "Deel deze app", + "Show_more": "Meer tonen..", + "Show_Unread_Counter": "Toon ongelezen teller", + "Show_Unread_Counter_Info": "Ongelezen teller wordt weergegeven als een badge aan de rechterkant van het kanaal, in de lijst", + "Sign_in_your_server": "Log in op je server", + "Sign_Up": "Registreren", + "Some_field_is_invalid_or_empty": "Sommige velden zijn ongeldig of leeg", + "Sorting_by": "Sorteren op {{key}}", + "Sound": "Geluid", + "Star_room": "Favoriete kanalen", + "Star": "Ster", + "Starred_Messages": "Berichten met ster gemarkeerd", + "starred": "met ster gemarkeerd", + "Starred": "Met ster gemarkeerd", + "Start_of_conversation": "Begin van een gesprek", + "Start_a_Discussion": "Start een discussie", + "Started_discussion": "Begin van een discussie:", + "Started_call": "Oproep gestart door {{userBy}}", + "Submit": "Verzenden", + "Table": "Tabel", + "Tags": "Tags", + "Take_a_photo": "Neem een foto", + "Take_a_video": "Maak een video", + "Take_it": "Pak het!", + "tap_to_change_status": "tik om de status te wijzigen", + "Tap_to_view_servers_list": "Tik om de serverlijst te bekijken", + "Terms_of_Service": " Servicevoorwaarden ", + "Theme": "Thema", + "The_user_wont_be_able_to_type_in_roomName": "De gebruiker zal in {{roomName}} niet kunnen typen", + "The_user_will_be_able_to_type_in_roomName": "De gebruiker zal in {{roomName}} kunnen typen", + "There_was_an_error_while_action": "Er is een fout opgetreden tijdens {{action}}!", + "This_room_is_blocked": "Deze kamer is geblokkeerd", + "This_room_is_read_only": "Deze kamer is alleen-lezen", + "Thread": "Draad", + "Threads": "Draden", + "Timezone": "Tijdzone", + "To": "Naar", + "topic": "onderwerp", + "Topic": "Onderwerp", + "Translate": "Vertalen", + "Try_again": "Probeer het opnieuw", + "Two_Factor_Authentication": "Twee-factor authenticatie", + "Type_the_channel_name_here": "Typ hier de kanaalnaam", + "unarchive": "dearchiveren", + "UNARCHIVE": "DEARCHIVEREN", + "Unblock_user": "Deblokkeer gebruiker", + "Unfavorite": "Uit favorieten halen", + "Unfollowed_thread": "Draad ontvolgd", + "Unmute": "Dempen opheffen", + "unmuted": "ongedempt", + "Unpin": "Losmaken", + "unread_messages": "ongelezen", + "Unread": "Ongelezen", + "Unread_on_top": "Ongelezen bovenaan", + "Unstar": "Ster verwijderen", + "Updating": "Updaten...", + "Uploading": "Uploaden", + "Upload_file_question_mark": "Bestand uploaden?", + "User": "Gebruiker", + "Users": "Gebruikers", + "User_added_by": "Gebruiker {{userAdded}} toegevoegd door {{userBy}}", + "User_Info": "Gebruikers info", + "User_has_been_key": "Gebruiker is {{key}}", + "User_is_no_longer_role_by_": "{{user}} is niet langer {{role}} door {{userBy}}", + "User_muted_by": "Gebruiker {{userMuted}} gedempt door {{userBy}}", + "User_removed_by": "Gebruiker {{userRemoved}} verwijderd door {{userBy}}", + "User_sent_an_attachment": "{{user}} stuurde een bijlage", + "User_unmuted_by": "Dempen voor {{userUnmuted}} opgeheven door {{userBy}}", + "User_was_set_role_by_": "{{user}} is als {{role}} ingesteld door {{userBy}}", + "Username_is_empty": "Gebruikersnaam is leeg", + "Username": "Gebruikersnaam", + "Username_or_email": "Gebruikersnaam of e-mail", + "Uses_server_configuration": "Gebruikt serverconfiguratie", + "Validating": "Valideren", + "Registration_Succeeded": "Registratie geslaagd!", + "Verify": "Verifiëren", + "Verify_email_title": "Registratie geslaagd!", + "Verify_email_desc": "We hebben je een e-mail gestuurd om je inschrijving te bevestigen. Als je binnenkort geen e-mail ontvangt, gelieve terug te komen en het opnieuw te proberen.", + "Verify_your_email_for_the_code_we_sent": "Verifieer je e-mail voor de code die we hebben gestuurd", + "Video_call": "Videogesprek", + "View_Original": "Bekijk origineel", + "Voice_call": "Spraakoproep", + "Waiting_for_network": "Wachten op netwerk...", + "Websocket_disabled": "Websocket is uitgeschakeld voor deze server.\n{{contact}}", + "Welcome": "Welkom", + "What_are_you_doing_right_now": "Wat doe je op dit moment?", + "Whats_your_2fa": "Wat is je 2FA code?", + "Without_Servers": "Zonder servers", + "Workspaces": "Werkruimten", + "Would_you_like_to_return_the_inquiry": "Wil je de aanvraag retourneren?", + "Write_External_Permission_Message": "Rocket.Chat heeft toegang nodig tot je galerij zodat je afbeeldingen kunt opslaan.", + "Write_External_Permission": "Galerij toestemming", + "Yes": "Ja", + "Yes_action_it": "Ja, {{action}} het!", + "Yesterday": "Gisteren", + "You_are_in_preview_mode": "Je bent in voorbeeldmodus", + "You_are_offline": "Je bent offline", + "You_can_search_using_RegExp_eg": "Je kan RegExp. gebruiken, bijv. `/^tekst$/i`", + "You_colon": "Jij: ", + "you_were_mentioned": "je bent vermeld", + "You_were_removed_from_channel": "Je bent verwijderd uit {{channel}}", + "you": "jij", + "You": "Jij", + "Logged_out_by_server": "Je bent uitgelogd door de server. Gelieve opnieuw in te loggen.", + "You_need_to_access_at_least_one_RocketChat_server_to_share_something": "Je moet minstens toegang hebben tot één Rocket.Chat-server om iets te delen.", + "You_need_to_verifiy_your_email_address_to_get_notications": "Je moet je e-mailadres verifiëren om meldingen te ontvangen", + "Your_certificate": "Jouw certificaat", + "Your_invite_link_will_expire_after__usesLeft__uses": "Je uitnodigingslink verloopt na {{usesLeft}} keer.", + "Your_invite_link_will_expire_on__date__or_after__usesLeft__uses": "Je uitnodigingslink verloopt op {{date}} of na {{usesLeft}} keer.", + "Your_invite_link_will_expire_on__date__": "Je uitnodigingslink verloopt op {{date}}.", + "Your_invite_link_will_never_expire": "Je uitnodigingslink zal nooit verlopen.", + "Your_workspace": "Jouw werkruimte", + "Your_password_is": "Jouw wachtwoord is", + "Version_no": "Versie: {{version}}", + "You_will_not_be_able_to_recover_this_message": "Je zal dit bericht niet meer kunnen herstellen!", + "You_will_unset_a_certificate_for_this_server": "Je zal een certificaat voor deze server uitschakelen", + "Change_Language": "Taal veranderen", + "Crash_report_disclaimer": "We volgen nooit nooit de inhoud van je chats. Het crashrapport en de analytische gebeurtenissen bevatten alleen relevante informatie voor ons om problemen te identificeren en op te lossen.", + "Type_message": "Typ bericht", + "Room_search": "Kamers zoeken", + "Room_selection": "Kamerkeuze 1...9", + "Next_room": "Volgende kamer", + "Previous_room": "Vorige kamer", + "New_room": "Nieuwe kamer", + "Upload_room": "Uploaden naar kamer", + "Search_messages": "Berichten zoeken", + "Scroll_messages": "Berichten scrollen", + "Reply_latest": "Antwoord op laatste", + "Reply_in_Thread": "Reageer in discussie", + "Server_selection": "Server selectie", + "Server_selection_numbers": "Server selectie 1...9", + "Add_server": "Server toevoegen", + "New_line": "Nieuwe lijn", + "You_will_be_logged_out_of_this_application": "Je wordt uitgelogd van deze applicatie.", + "Clear": "Wissen", + "This_will_clear_all_your_offline_data": "Hiermee worden al jouw offline gegevens gewist.", + "This_will_remove_all_data_from_this_server": "Dit zal alle gegevens van deze server verwijderen.", + "Mark_unread": "Markeer als ongelezen", + "Wait_activation_warning": "Voordat u kunt inloggen, moet uw account handmatig worden geactiveerd door een beheerder.", + "Screen_lock": "Schermvergrendeling", + "Local_authentication_biometry_title": "Authenticeren", + "Local_authentication_biometry_fallback": "Gebruik toegangscode", + "Local_authentication_unlock_option": "Ontgrendelen met toegangscode", + "Local_authentication_change_passcode": "Wijzig toegangscode", + "Local_authentication_info": "Opmerking: als je de toegangscode vergeet, moet je de app verwijderen en opnieuw installeren.", + "Local_authentication_facial_recognition": "gezichtsherkenning", + "Local_authentication_fingerprint": "vingerafdruk", + "Local_authentication_unlock_with_label": "Ontgrendel met {{label}}", + "Local_authentication_auto_lock_60": "Na 1 minuut", + "Local_authentication_auto_lock_300": "Na 5 minuten", + "Local_authentication_auto_lock_900": "Na 15 minuten", + "Local_authentication_auto_lock_1800": "Na 30 minuten", + "Local_authentication_auto_lock_3600": "Na 1 uur", + "Passcode_enter_title": "Voer uw toegangscode in", + "Passcode_choose_title": "Kies je nieuwe toegangscode", + "Passcode_choose_confirm_title": "Bevestig je nieuwe toegangscode", + "Passcode_choose_error": "Toegangscodes komen niet overeen. Probeer het opnieuw.", + "Passcode_choose_force_set": "Toegangscode vereist door beheerder", + "Passcode_app_locked_title": "App vergrendeld", + "Passcode_app_locked_subtitle": "Probeer het over {{timeLeft}} seconden opnieuw", + "After_seconds_set_by_admin": "Na {{seconds}} seconden (ingesteld door beheerder)", + "Dont_activate": "Nu niet activeren", + "Queued_chats": "Chats in de wachtrij", + "Queue_is_empty": "Wachtrij is leeg", + "Logout_from_other_logged_in_locations": "Afmelden bij andere ingelogde locaties", + "You_will_be_logged_out_from_other_locations": "Je wordt uitgelogd van andere locaties.", + "Logged_out_of_other_clients_successfully": "Succesvol uitgelogd bij andere klanten", + "Logout_failed": "Uitloggen mislukt!", + "Log_analytics_events": "Analysegebeurtenissen loggen", + "E2E_encryption_change_password_title": "Versleutelingswachtwoord wijzigen", + "E2E_encryption_change_password_description": "Je kan nu versleutelde privégroepen en directe berichten aanmaken. Je kan ook bestaande privégroepen of DM's wijzigen in versleuteld.\nDit is end-to-end codering, dus de sleutel om jouw berichten te coderen/decoderen en deze wordt niet op de server opgeslagen. Daarom moet je dit wachtwoord op een veilige plaats opslaan. Je moet het invoeren op andere apparaten waarop je e2e-codering wilt gebruiken.", + "E2E_encryption_change_password_error": "Fout bij het wijzigen van het E2E-wachtwoord", + "E2E_encryption_change_password_success": "E2E-wachtwoord succesvol gewijzigd!", + "E2E_encryption_change_password_message": "Zorg ervoor dat je het zorgvuldig ergens anders hebt bewaard.", + "E2E_encryption_change_password_confirmation": "Ja, verander het", + "E2E_encryption_reset_title": "E2E-sleutel resetten", + "E2E_encryption_reset_description": "Deze optie zal je huidige E2E-sleutel verwijderen en je wordt uitgelogd.\nWanneer je opniew inlogt, genereert Rocket.Chat je een nieuwe sleutel en herstelt je toegang tot elke versleutelde kamer die een of meer leden heeft.\nDoor de aard van E2E-versleuteling kan Rocket.Chat de toegang tot een versleutelde kamer zonder online lid niet herstellen.", + "E2E_encryption_reset_button": "E2E-sleutel resetten", + "E2E_encryption_reset_error": "Fout bij het resetten van E2E-sleutel!", + "E2E_encryption_reset_message": "Je wordt uitgelogd.", + "E2E_encryption_reset_confirmation": "Ja, reset het", + "Following": "Volgend", + "Threads_displaying_all": "Alles weergeven", + "Threads_displaying_following": "Volgend weergeven", + "Threads_displaying_unread": "Ongelezen weergeven", + "No_threads": "Er zijn geen discussies", + "No_threads_following": "Je volgt geen discussies", + "No_threads_unread": "Er zijn geen ongelezen discussies", + "Messagebox_Send_to_channel": "Stuur naar kanaal", + "Leader": "Leider", + "Moderator": "Moderator", + "Owner": "Eigenaar", + "Remove_from_room": "Verwijderen uit kamer", + "Ignore": "Negeren", + "Unignore": "Niet meer negeren", + "User_has_been_ignored": "Gebruiker is genegeerd", + "User_has_been_unignored": "Gebruiker wordt niet langer genegeerd", + "User_has_been_removed_from_s": "Gebruiker is verwijderd van {{s}}", + "User__username__is_now_a_leader_of__room_name_": "Gebruiker {{username}} is nu een leider van {{room_name}}", + "User__username__is_now_a_moderator_of__room_name_": "Gebruiker {{username}} is nu een moderator van {{room_name}}", + "User__username__is_now_a_owner_of__room_name_": "Gebruiker {{username}} is nu eigenaar van {{room_name}}", + "User__username__removed_from__room_name__leaders": "Gebruiker {{username}} verwijderd uit {{room_name}} leiders", + "User__username__removed_from__room_name__moderators": "Gebruiker {{username}} verwijderd uit {{room_name}} moderators", + "User__username__removed_from__room_name__owners": "Gebruiker {{username}} verwijderd uit {{room_name}} eigenaars", + "The_user_will_be_removed_from_s": "De gebruiker wordt verwijderd uit {{s}}", + "Yes_remove_user": "Ja, verwijder gebruiker!", + "Direct_message": "Direct bericht", + "Message_Ignored": "Bericht genegeerd. Tik om het weer te geven.", + "Enter_workspace_URL": "Voer de werkruimte-URL in", + "Workspace_URL_Example": "Vb. uw-bedrijf.rocket.chat", + "This_room_encryption_has_been_enabled_by__username_": "De versleuteling van deze kamer is ingeschakeld door {{username}}", + "This_room_encryption_has_been_disabled_by__username_": "De versleuteling van deze kamer is uitgeschakeld door {{username}}", + "Teams": "Teams", + "No_team_channels_found": "Geen kanalen gevonden", + "Team_not_found": "Team niet gevonden", + "Create_Team": "Team aanmaken", + "Team_Name": "Teamnaam", + "Private_Team": "Privé team", + "Read_Only_Team": "Alleen-lezen team", + "Broadcast_Team": "Broadcast team", + "creating_team": "team maken", + "team-name-already-exists": "Er bestaat al een team met die naam", + "Add_Channel_to_Team": "Kanaal toevoegen aan team", + "Left_The_Team_Successfully": "Het team met succes verlaten", + "Create_New": "Maak nieuw", + "Add_Existing": "Voeg bestaande", + "Add_Existing_Channel": "Bestaand kanaal toevoegen", + "Remove_from_Team": "Verwijderen uit team", + "Auto-join": "Automatisch deelnemen", + "Remove_Team_Room_Warning": "Wil je dit kanaal uit het team verwijderen? Het kanaal wordt terug naar de werkruimte verplaatst", + "Confirmation": "Bevestiging", + "invalid-room": "Ongeldige kamer", + "You_are_leaving_the_team": "Je verlaat het team '{{team}}'", + "Leave_Team": "Team verlaten", + "Select_Team": "Selecteer team", + "Select_Team_Channels": "Selecteer de kanalen van het team die je wilt verlaten.", + "Cannot_leave": "Kan niet weggaan", + "Cannot_remove": "Kan niet verwijderen", + "Cannot_delete": "Kan niet verwijderen", + "Last_owner_team_room": "Je bent de laatste eigenaar van dit kanaal. Zodra u het team verlaat, blijft het kanaal binnen het team, maar beheert u het van buitenaf.", + "last-owner-can-not-be-removed": "Laatste eigenaar kan niet worden verwijderd.", + "Remove_User_Teams": "Selecteer de kanalen waarvan je de gebruiker wilt verwijderen.", + "Delete_Team": "Team verwijderen", + "Select_channels_to_delete": "Dit kan niet ongedaan worden gemaakt. Zodra je een team verwijdert, worden alle chatinhoud en configuratie verwijderd.\n\nSelecteer de kanalen die je wilt verwijderen. Degene die je besluit te behouden, zullen in jouw werkruimte beschikbaar zijn. Hou er rekening mee dat openbare kanalen nog steeds openbaar en voor iedereen zichtbaar zijn.", + "You_are_deleting_the_team": "Je verwijdert dit team.", + "Removing_user_from_this_team": "Je verwijdert {{user}} uit dit team", + "Remove_User_Team_Channels": "Selecteer de kanalen waarvan je de gebruiker wilt verwijderen.", + "Remove_Member": "Lid verwijderen", + "leaving_team": "team verlaten", + "removing_team": "verwijderen uit team", + "moving_channel_to_team": "kanaal verplaatsen naar team", + "deleting_team": "team verwijderen", + "member-does-not-exist": "Lid bestaat niet", + "Convert": "Converteren", + "Convert_to_Team": "Converteren naar team", + "Convert_to_Team_Warning": "Je converteert dit kanaal naar een team. Alle leden blijven behouden.", + "Move_to_Team": "Verplaats naar team", + "Move_Channel_Paragraph": "Het verplaatsen van een kanaal binnen een team betekent dat dit kanaal wordt toegevoegd in de context van het team. Maar, alle leden van dit kanaal, die geen lid zijn van het respectieve team, zullen nog steeds toegang hebben tot dit kanaal, maar worden niet als teamleden toegevoegd.\n\nHet volledige beheer van dit kanaal wordt nog steeds door de eigenaren van dit kanaal gedaan.\n\nTeamleden en zelfs teameigenaren, wanneer ze geen lid zijn van dit kanaal, hebben geen toegang tot de content van het kanaal.\n\nHou er rekening mee dat de eigenaar van het team de leden uit het kanaal kan verwijderen.", + "Move_to_Team_Warning": "Wil je na het lezen van de vorige instructies over dit gedrag, dit kanaal nog steeds naar het geselecteerde team verplaatsen?", + "Load_More": "Meer laden", + "Load_Newer": "Nieuwer laden", + "Load_Older": "Ouder laden", + "room-name-already-exists": "Kamernaam bestaat al", + "error-team-creation": "Fout bij het aanmaken van team", + "unauthorized": "Onbevoegd", + "Left_The_Room_Successfully": "Heeft kamer met succes verlaten", + "Deleted_The_Team_Successfully": "Team succesvol verwijderd", + "Deleted_The_Room_Successfully": "Kamer succesvol verwijderd", + "Convert_to_Channel": "Converteren naar kanaal", + "Converting_Team_To_Channel": "Team converteren naar kanaal", + "Select_Team_Channels_To_Delete": "Selecteer de teamkanalen die je wilt verwijderen, de kanalen die u niet selecteert, worden naar de werkruimte verplaatst.\n\nMerk op dat openbare kanalen openbaar en voor iedereen zichtbaar zullen zijn.", + "You_are_converting_the_team": "Je converteert dit team naar een kanaal", + "creating_discussion": "discussie aanmaken" +} diff --git a/app/i18n/locales/pt-BR.json b/app/i18n/locales/pt-BR.json index 074b10efb..57c31c067 100644 --- a/app/i18n/locales/pt-BR.json +++ b/app/i18n/locales/pt-BR.json @@ -1,676 +1,684 @@ { - "1_person_reacted": "1 pessoa reagiu", - "1_user": "1 usuário", - "error-action-not-allowed": "{{action}} não é permitido", - "error-application-not-found": "Aplicação não encontrada", - "error-archived-duplicate-name": "Já há um canal arquivado com o nome {{room_name}}", - "error-avatar-invalid-url": "URL inválida de avatar: {{url}}", - "error-avatar-url-handling": "Erro durante o manuseio configuração avatar a partir de uma URL ({{url}}) para {{username}}", - "error-cant-invite-for-direct-room": "Não é possível convidar usuários para salas diretas", - "error-could-not-change-email": "Não foi possível mudar e-mail", - "error-could-not-change-name": "Não foi possível mudar o nome", - "error-could-not-change-username": "Não foi possível alterar o nome de usuário", - "error-delete-protected-role": "Não é possível remover um papel protegido", - "error-department-not-found": "Departamento não encontrado", - "error-direct-message-file-upload-not-allowed": "Compartilhamento de arquivos não está permitido em mensagens diretas", - "error-duplicate-channel-name": "Já existe um canal com nome {{channel_name}}", - "error-email-domain-blacklisted": "O domínio de e-mail está na lista negra", - "error-email-send-failed": "Erro ao tentar enviar e-mail: {{message}}", - "error-save-image": "Erro ao salvar imagem", - "error-field-unavailable": "{{field}} já está sendo usado :(", - "error-file-too-large": "Arquivo é muito grande", - "error-importer-not-defined": "O importador não foi definido corretamente; está faltando a classe Import.", - "error-input-is-not-a-valid-field": "{{input}} não é válido um {{field}}", - "error-invalid-actionlink": "Link de ação inválido", - "error-invalid-arguments": "Argumentos inválidos", - "error-invalid-asset": "Arquivo Inválido", - "error-invalid-channel": "Canal inválido.", - "error-invalid-channel-start-with-chars": "Canal inválido. Comece com @ ou #", - "error-invalid-custom-field": "Campo personalizado inválido", - "error-invalid-custom-field-name": "Nome inválido para o campo personalizado. Use apenas letras, números, hífens e underscores.", - "error-invalid-date": "Data fornecida inválida", - "error-invalid-description": "Descrição inválida", - "error-invalid-domain": "Domínio inválido", - "error-invalid-email": "{{email}} não é um e-mail válido", - "error-invalid-email-address": "Endereço de e-mail inválido", - "error-invalid-file-height": "Altura de arquivo inválida", - "error-invalid-file-type": "Tipo de arquivo inválido", - "error-invalid-file-width": "Altura de arquivo inválida", - "error-invalid-from-address": "Você informou um e-mail DE inválido.", - "error-invalid-integration": "Integração inválida", - "error-invalid-message": "Mensagem inválida", - "error-invalid-method": "Método inválido", - "error-invalid-name": "Nome inválido", - "error-invalid-password": "Senha inválida", - "error-invalid-redirectUri": "redirectUri inválido", - "error-invalid-role": "Papel inválido", - "error-invalid-room": "Sala inválida", - "error-invalid-room-name": "{{room_name}} não é um nome de sala válido", - "error-invalid-room-type": "{{type}} não é um tipo de sala válido.", - "error-invalid-settings": "Configurações fornecidas inválidas", - "error-invalid-subscription": "Assinatura inválida", - "error-invalid-token": "Token inválido", - "error-invalid-triggerWords": "triggerWords inválidos", - "error-invalid-urls": "URLs inválidas", - "error-invalid-user": "Usuário inválido", - "error-invalid-username": "Nome de usuário Inválido", - "error-invalid-webhook-response": "O URL do webhook respondeu com um status diferente de 200", - "error-message-deleting-blocked": "Exclusão de mensagens está bloqueada", - "error-message-editing-blocked": "Edição de mensagens está bloqueada", - "error-message-size-exceeded": "O tamanho da mensagem excede Message_MaxAllowedSize", - "error-missing-unsubscribe-link": "Você deve fornecer o link para desinscrever-se: [unsubscribe].", - "error-no-tokens-for-this-user": "Não existem tokens para este usuário", - "error-not-allowed": "Não permitido", - "error-not-authorized": "Não autorizado", - "error-push-disabled": "Notificações push desativadas", - "error-remove-last-owner": "Este é o último proprietário. Por favor, defina um novo proprietário antes de remover este.", - "error-role-in-use": "Não é possível remover o papel pois ele está em uso", - "error-role-name-required": "Nome do papel é obrigatório", - "error-the-field-is-required": "O campo {{field}} é obrigatório.", - "error-too-many-requests": "Erro, muitas solicitações. Por favor, diminua a velocidade. Você deve esperar {{seconds}} segundos antes de tentar novamente.", - "error-user-is-not-activated": "O usuário não está ativo", - "error-user-has-no-roles": "O usuário não possui permissões", - "error-user-limit-exceeded": "O número de usuários que você está tentando convidar para #channel_name excede o limite determindado pelo administrador", - "error-user-not-in-room": "O usuário não está nesta sala", - "error-user-registration-disabled": "O registro do usuário está desativado", - "error-user-registration-secret": "O registro de usuário é permitido somente via URL secreta", - "error-you-are-last-owner": "Você é o último proprietário da sala. Por favor defina um novo proprietário antes de sair.", - "error-status-not-allowed": "O status invisível está desativado", - "Actions": "Ações", - "activity": "atividade", - "Activity": "Atividade", - "Add_Reaction": "Reagir", - "Add_Server": "Adicionar servidor", - "Add_users": "Adicionar usuário", - "Agent": "Agente", - "Alert": "Alerta", - "alert": "alerta", - "alerts": "alertas", - "All_users_in_the_channel_can_write_new_messages": "Todos usuários no canal podem enviar mensagens novas", - "A_meaningful_name_for_the_discussion_room": "Um nome significativo para o canal de discussão", - "All": "Todos", - "Allow_Reactions": "Permitir reagir", - "Alphabetical": "Alfabético", - "and_more": "e mais", - "and": "e", - "announcement": "anúncio", - "Announcement": "Anúncio", - "Apply_Your_Certificate": "Aplicar certificado", - "ARCHIVE": "ARQUIVAR", - "archive": "arquivar", - "are_typing": "estão digitando", - "Are_you_sure_question_mark": "Você tem certeza?", - "Are_you_sure_you_want_to_leave_the_room": "Tem certeza de que deseja sair da sala {{room}}?", - "Audio": "Áudio", - "Authenticating": "Autenticando", - "Automatic": "Automático", - "Auto_Translate": "Tradução automática", - "Avatar_changed_successfully": "Avatar alterado com sucesso!", - "Avatar_Url": "Avatar URL", - "Away": "Ausente", - "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", - "By_proceeding_you_are_agreeing": "Ao prosseguir você está aceitando", - "Cancel_editing": "Cancelar edição", - "Cancel_recording": "Cancelar gravação", - "Cancel": "Cancelar", - "changing_avatar": "trocando avatar", - "creating_channel": "criando canal", - "creating_invite": "criando convite", - "Channel_Name": "Nome do Canal", - "Channels": "Canais", - "Chats": "Conversas", - "Call_already_ended": "A chamada já terminou!", - "Clear_cookies_alert": "Você quer limpar seus cookies?", - "Clear_cookies_desc": "Esta ação limpará todos os cookies de login permitindo que você faça login em outras contas.", - "Clear_cookies_yes": "Sim, limpar cookies", - "Clear_cookies_no": "Não, manter cookies", - "Click_to_join": "Clique para participar!", - "Close": "Fechar", - "Close_emoji_selector": "Fechar seletor de emojis", - "Closing_chat": "Fechando conversa", - "Change_language_loading": "Alterando idioma.", - "Chat_closed_by_agent": "Conversa fechada por agente", - "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", - "Code_or_password_invalid": "Código ou senha inválido", - "Collaborative": "Colaborativo", - "Confirm": "Confirmar", - "Connect": "Conectar", - "Connected": "Conectado", - "connecting_server": "conectando no servidor", - "Connecting": "Conectando...", - "Contact_us": "Entre em contato", - "Contact_your_server_admin": "Contate o administrador do servidor.", - "Continue_with": "Entrar com", - "Copied_to_clipboard": "Copiado para a área de transferência!", - "Copy": "Copiar", - "Conversation": "Conversação", - "Permalink": "Link-Permanente", - "Clear_cache_loading": "Limpando cache.", - "Create_account": "Criar conta", - "Create_Channel": "Criar Canal", - "Create_Direct_Messages": "Criar Mensagens Diretas", - "Create_Discussion": "Criar Discussão", - "Created_snippet": "criou um snippet", - "Create_a_new_workspace": "Criar nova área de trabalho", - "Create": "Criar", - "Dark": "Escuro", - "Dark_level": "Nível escuro", - "Default": "Padrão", - "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.", - "Department": "Departamento", - "delete": "excluir", - "Delete": "Excluir", - "DELETE": "EXCLUIR", - "deleting_room": "excluindo sala", - "description": "descrição", - "Description": "Descrição", - "Desktop_Options": "Opções De Área De Trabalho", - "Desktop_Notifications": "Notificações da Área de Trabalho", - "Desktop_Alert_info": "Essas notificações são entregues a você na área de trabalho", - "Directory": "Diretório", - "Direct_Messages": "Mensagens Diretas", - "Disable_notifications": "Desabilitar notificações", - "Discussions": "Discussões", - "Discussion_Desc": "Ajude a manter uma visão geral sobre o que está acontecendo! Ao criar uma discussão, um sub-canal do que você selecionou é criado e os dois são vinculados.", - "Discussion_name": "Nome da discussão", - "Done": "Pronto", - "Dont_Have_An_Account": "Não tem uma conta?", - "Do_you_have_an_account": "Você tem uma conta?", - "Do_you_have_a_certificate": "Você tem um certificado?", - "Do_you_really_want_to_key_this_room_question_mark": "Você quer realmente {{key}} esta sala?", - "E2E_Encryption": "Encriptação ponta a ponta", - "E2E_How_It_Works_info1": "Agora você pode criar grupos privados criptografados e mensagens diretas. Você também pode alterar grupos privados existentes ou DMs para criptografados.", - "E2E_How_It_Works_info2": "Esta é a criptografia *ponta a ponta*, portanto, a chave para codificar/decodificar suas mensagens e elas não serão salvas no servidor. Por esse motivo *você precisa armazenar esta senha em algum lugar seguro* que você pode acessar mais tarde se precisar.", - "E2E_How_It_Works_info3": "Se você continuar, será gerada automaticamente uma senha E2E.", - "E2E_How_It_Works_info4": "Você também pode configurar uma nova senha para sua chave de criptografia a qualquer momento em qualquer navegador em que tenha inserido a senha E2E existente.", - "edit": "editar", - "edited": "editado", - "Edit": "Editar", - "Edit_Status": "Editar Status", - "Edit_Invite": "Editar convite", - "End_to_end_encrypted_room": "Sala criptografada de ponta a ponta", - "end_to_end_encryption": "criptografia de ponta a ponta", - "Email_Notification_Mode_All": "Cada Menção / Mensagem Direta", - "Email_Notification_Mode_Disabled": "Desativado", - "Email_or_password_field_is_empty": "Email ou senha estão vazios", - "Email": "E-mail", - "email": "e-mail", - "Empty_title": "Título vazio", - "Enable_Auto_Translate": "Ativar a tradução automática", - "Enable_notifications": "Habilitar notificações", - "Encrypted": "Criptografado", - "Encrypted_message": "Mensagem criptografada", - "Enter_Your_E2E_Password": "Digite Sua Senha E2E", - "Enter_Your_Encryption_Password_desc1": "Isso permitirá que você acesse seus grupos privados e mensagens diretas criptografadas.", - "Enter_Your_Encryption_Password_desc2": "Você precisa inserir a senha para codificar/decodificar mensagens em todos os lugares em que usar o chat.", - "Encryption_error_title": "Sua senha de criptografia parece errada", - "Encryption_error_desc": "Não foi possível decodificar sua chave de criptografia para ser importada.", - "Everyone_can_access_this_channel": "Todos podem acessar este canal", - "Error_uploading": "Erro subindo", - "Expiration_Days": "Expira em (dias)", - "Favorite": "Adicionar aos Favoritos", - "Favorites": "Favoritos", - "Files": "Arquivos", - "File_description": "Descrição do arquivo", - "File_name": "Nome do arquivo", - "Finish_recording": "Encerrar gravação", - "Following_thread": "Começou a seguir tópico", - "For_your_security_you_must_enter_your_current_password_to_continue": "Para sua segurança, você precisa digitar sua senha", - "Forgot_password_If_this_email_is_registered": "Se este e-mail estiver cadastrado, enviaremos instruções sobre como redefinir sua senha. Se você não receber um e-mail em breve, volte e tente novamente.", - "Forgot_password": "Esqueceu sua senha?", - "Forgot_Password": "Esqueci minha senha", - "Forward": "Encaminhar", - "Forward_Chat": "Encaminhar Conversa", - "Forward_to_department": "Encaminhar para departamento", - "Forward_to_user": "Encaminhar para usuário", - "Full_table": "Clique para ver a tabela completa", - "Generate_New_Link": "Gerar novo convite", - "Group_by_favorites": "Agrupar favoritos", - "Group_by_type": "Agrupar por tipo", - "Hide": "Ocultar", - "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", - "IP": "IP", - "In_app": "No app", - "In_App_and_Desktop_Alert_info": "Exibe um banner na parte superior da tela quando o aplicativo é aberto e exibe uma notificação na área de trabalho", - "Invisible": "Invisível", - "Invite": "Convidar", - "is_typing": "está digitando", - "Invalid_or_expired_invite_token": "Token de convite inválido ou vencido", - "Invalid_server_version": "O servidor que você está conectando não é suportado mais por esta versão do aplicativo: {{currentVersion}}.\n\nEsta versão do aplicativo requer a versão {{minVersion}} do servidor para funcionar corretamente.", - "Invite_Link": "Link de Convite", - "Invite_users": "Convidar usuários", - "Join": "Entrar", - "Join_Code": "Insira o Código da Sala", - "Insert_Join_Code": "Insira o código para entrar na sala", - "Join_our_open_workspace": "Entra na nossa workspace pública", - "Join_your_workspace": "Entre na sua workspace", - "Just_invited_people_can_access_this_channel": "Apenas as pessoas convidadas podem acessar este canal", - "Language": "Idioma", - "last_message": "última mensagem", - "Leave_channel": "Sair do canal", - "leaving_room": "saindo do canal", - "Leave": "Sair da sala", - "leave": "sair", - "Legal": "Legal", - "Light": "Claro", - "Livechat": "Livechat", - "Login": "Entrar", - "Login_error": "Suas credenciais foram rejeitadas. Tente novamente por favor!", - "Login_with": "Login with", - "Logging_out": "Saindo.", - "Logout": "Sair", - "Max_number_of_uses": "Número máximo de usos", - "Max_number_of_users_allowed_is_number": "Número máximo de usuários é {{maxUsers}}", - "Members": "Membros", - "Mentioned_Messages": "Mensagens mencionadas", - "mentioned": "mencionado", - "Mentions": "Menções", - "Message_accessibility": "Mensagem de {{user}} às {{time}}: {{message}}", - "Message_actions": "Ações", - "Message_pinned": "Fixou uma mensagem", - "Message_removed": "Mensagem removida", - "message": "mensagem", - "messages": "mensagens", - "Message": "Mensagem", - "Messages": "Mensagens", - "Microphone_Permission_Message": "Rocket.Chat precisa de acesso ao seu microfone para enviar mensagens de áudio.", - "Microphone_Permission": "Acesso ao Microfone", - "Mute": "Mudo", - "muted": "mudo", - "N_people_reacted": "{{n}} pessoas reagiram", - "N_users": "{{n}} usuários", - "name": "nome", - "Name": "Nome", - "Navigation_history": "Histórico de navegação", - "Never": "Nunca", - "New_Message": "Nova Mensagem", - "New_Password": "Nova Senha", - "Next": "Próximo", - "No_files": "Não há arquivos", - "No_limit": "Sem limite", - "No_mentioned_messages": "Não há menções", - "No_pinned_messages": "Não há mensagens fixadas", - "No_results_found": "Nenhum resultado encontrado", - "No_starred_messages": "Não há mensagens favoritas", - "No_thread_messages": "Não há tópicos", - "No_label_provided": "Sem {{label}}.", - "No_Message": "Não há mensagens", - "No_messages_yet": "Não há mensagens ainda", - "No_Reactions": "Sem reações", - "Not_RC_Server": "Este não é um servidor Rocket.Chat.\n{{contact}}", - "Nothing": "Nada", - "Nothing_to_save": "Nada para salvar!", - "Notify_active_in_this_room": "Notificar usuários ativos nesta sala", - "Notify_all_in_this_room": "Notificar todos nesta sala", - "Notifications": "Notificações", - "Notification_Duration": "Duração da notificação", - "Notification_Preferences": "Preferências de notificação", - "No_available_agents_to_transfer": "Nenhum agente disponível para transferência", - "Offline": "Offline", - "Oops": "Ops!", - "Omnichannel": "Omnichannel", - "Open_Livechats": "Bate-papos em Andamento", - "Omnichannel_enable_alert": "Você não está disponível no Omnichannel. Você quer ficar disponível?", - "Onboarding_description": "Workspace é o espaço de colaboração do seu time ou organização. Peça um convite ou o endereço ao seu administrador ou crie uma workspace para o seu time.", - "Onboarding_join_workspace": "Entre numa workspace", - "Onboarding_subtitle": "Além da colaboração em equipe", - "Onboarding_title": "Bem vindo ao Rocket.Chat", - "Onboarding_join_open_description": "Entre na nossa workspace pública para conversar com o time da Rocket.Chat e nossa comunidade.", - "Onboarding_agree_terms": "Ao continuar, você aceita nossos ", - "Onboarding_less_options": "Menos opções", - "Onboarding_more_options": "Mais opções", - "Online": "Online", - "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", - "Open_your_authentication_app_and_enter_the_code": "Abra seu aplicativo de autenticação e digite o código.", - "OR": "OU", - "OS": "SO", - "Overwrites_the_server_configuration_and_use_room_config": "Substituir a configuração do servidor e usar a configuração da sala", - "Password": "Senha", - "Parent_channel_or_group": "Canal ou grupo pai", - "Permalink_copied_to_clipboard": "Link-permanente copiado para a área de transferência!", - "Phone": "Telefone", - "Pin": "Fixar", - "Pinned_Messages": "Mensagens Fixadas", - "pinned": "fixada", - "Pinned": "Mensagens Fixadas", - "Please_add_a_comment": "Por favor, adicione um comentário", - "Please_enter_your_password": "Por favor, digite sua senha", - "Please_wait": "Por favor, aguarde.", - "Preferences": "Preferências", - "Preferences_saved": "Preferências salvas!", - "Privacy_Policy": " Política de Privacidade", - "Private_Channel": "Canal Privado", - "Private": "Privado", - "Processing": "Processando...", - "Profile_saved_successfully": "Perfil salvo com sucesso!", - "Profile": "Perfil", - "Public_Channel": "Canal Público", - "Public": "Público", - "Push_Notifications": "Notificações Push", - "Push_Notifications_Alert_Info": "Essas notificações são entregues a você quando o aplicativo não está aberto", - "Quote": "Citar", - "Reactions_are_disabled": "Reagir está desabilitado", - "Reactions_are_enabled": "Reagir está habilitado", - "Reactions": "Reações", - "Read_External_Permission_Message": "Rocket.Chat precisa acessar fotos, mídia e arquivos no seu dispositivo", - "Read_External_Permission": "Permissão de acesso à arquivos", - "Read_Only_Channel": "Canal Somente Leitura", - "Read_Only": "Somente Leitura", - "Read_Receipt": "Lida por", - "Receive_Group_Mentions": "Receber menções de grupo", - "Receive_Group_Mentions_Info": "Receber menções @all e @here", - "Register": "Registrar", - "Repeat_Password": "Repetir Senha", - "Replied_on": "Respondido em:", - "replies": "respostas", - "reply": "resposta", - "Reply": "Responder", - "Report": "Reportar", - "Receive_Notification": "Receber Notificação", - "Receive_notifications_from": "Receber notificação de {{name}}", - "Resend": "Reenviar", - "Reset_password": "Resetar senha", - "resetting_password": "redefinindo senha", - "RESET": "RESETAR", - "Return": "Retornar", - "Review_app_title": "Você está gostando do app?", - "Review_app_desc": "Nos dê 5 estrelas na {{store}}", - "Review_app_yes": "Claro!", - "Review_app_no": "Não", - "Review_app_later": "Talvez depois", - "Review_app_unable_store": "Não foi possível abrir {{store}}", - "Review_this_app": "Avaliar esse app", - "Remove": "Remover", - "Roles": "Papéis", - "Room_actions": "Ações", - "Room_changed_announcement": "O anúncio da sala foi alterado para: {{announcement}} por {{userBy}}", - "Room_changed_description": "A descrição da sala foi alterada para: {{description}} por {{userBy}}", - "Room_changed_privacy": "Tipo da sala mudou para: {{type}} por {{userBy}}", - "Room_changed_topic": "Tópico da sala mudou para: {{topic}} por {{userBy}}", - "Room_Files": "Arquivos", - "Room_Info_Edit": "Editar", - "Room_Info": "Informações da Sala", - "Room_Members": "Membros", - "Room_name_changed": "Nome da sala alterado para: {{name}} por {{userBy}}", - "SAVE": "SALVAR", - "Save_Changes": "Salvar Alterações", - "Save": "Salvar", - "Saved": "Salvo", - "saving_preferences": "salvando preferências", - "saving_profile": "salvando perfil", - "saving_settings": "salvando configurações", - "saved_to_gallery": "Salvo na galeria", - "Save_Your_E2E_Password": "Salve sua senha E2E", - "Save_Your_Encryption_Password": "Salve Sua Senha de Criptografia", - "Save_Your_Encryption_Password_warning": "Esta senha não é armazenada em nenhum lugar, portanto, salve-a com cuidado em outro lugar.", - "Save_Your_Encryption_Password_info": "Observe que se você perder sua senha, não há como recuperá-la e você perderá o acesso às suas mensagens.", - "Search_Messages": "Buscar Mensagens", - "Search": "Buscar", - "Search_by": "Buscar por", - "Search_global_users": "Busca por usuários globais", - "Search_global_users_description": "Caso ativado, busca por usuários de outras empresas ou servidores.", - "Security_and_privacy": "Segurança e privacidade", - "Select_Avatar": "Selecionar Avatar", - "Select_Server": "Selecionar Servidor", - "Select_Users": "Selecionar Usuários", - "Select_a_Channel": "Selecione um canal", - "Select_a_Department": "Selecione um Departamento", - "Select_an_option": "Selecione uma opção", - "Select_a_User": "Selecione um Usuário", - "Send": "Enviar", - "Send_audio_message": "Enviar mensagem de áudio", - "Send_crash_report": "Enviar relatório de erros", - "Send_message": "Enviar mensagem", - "Send_me_the_code_again": "Envie-me o código novamente", - "Send_to": "Enviar para...", - "Sent_an_attachment": "Enviou um anexo", - "Server": "Servidor", - "Set_username_subtitle": "O usuário é utilizado para permitir que você seja mencionado em mensagens", - "Settings": "Configurações", - "Settings_succesfully_changed": "Configurações salvas com sucesso!", - "Share": "Compartilhar", - "Share_Link": "Share Link", - "Show_more": "Mostrar mais..", - "Show_Unread_Counter": "Mostrar contador não lido", - "Show_Unread_Counter_Info": "O contador não lido é exibido como um emblema à direita do canal, na lista", - "Sign_in_your_server": "Entrar no seu servidor", - "Sign_Up": "Registrar", - "Some_field_is_invalid_or_empty": "Algum campo está inválido ou vazio", - "Sorting_by": "Ordenando por {{key}}", - "Sound": "Som da notificação", - "Star_room": "Favoritar sala", - "Star": "Favorito", - "Starred_Messages": "Mensagens Favoritas", - "starred": "favoritou", - "Starred": "Mensagens Favoritas", - "Start_of_conversation": "Início da conversa", - "Start_a_Discussion": "Iniciar uma Discussão", - "Started_discussion": "Iniciou uma discussão:", - "Started_call": "Chamada iniciada por {{userBy}}", - "Submit": "Enviar", - "Table": "Tabela", - "Take_a_photo": "Tirar uma foto", - "Take_a_video": "Gravar um vídeo", - "Take_it": "Pegue!", - "Terms_of_Service": " Termos de Serviço ", - "Theme": "Tema", - "The_user_wont_be_able_to_type_in_roomName": "O usuário não poderá digitar em {{roomName}}", - "The_user_will_be_able_to_type_in_roomName": "O usuário poderá digitar em {{roomName}}", - "There_was_an_error_while_action": "Aconteceu um erro {{action}}!", - "This_room_is_blocked": "Este quarto está bloqueado", - "This_room_is_read_only": "Este quarto é apenas de leitura", - "Thread": "Tópico", - "Threads": "Tópicos", - "Timezone": "Fuso horário", - "To": "Para", - "topic": "tópico", - "Topic": "Tópico", - "Translate": "Traduzir", - "Try_again": "Tentar novamente", - "Two_Factor_Authentication": "Autenticação de dois fatores", - "Type_the_channel_name_here": "Digite o nome do canal", - "unarchive": "desarquivar", - "UNARCHIVE": "DESARQUIVAR", - "Unblock_user": "Desbloquear usuário", - "Unfavorite": "Remover dos Favoritos", - "Unfollowed_thread": "Parou de seguir tópico", - "Unmute": "Permitir que o usuário fale", - "unmuted": "permitiu que o usuário fale", - "Unpin": "Desafixar Mensagem", - "unread_messages": "não lidas", - "Unread": "Não lidas", - "Unread_on_top": "Não lidas no topo", - "Unstar": "Remover favorito", - "Updating": "Atualizando...", - "Uploading": "Subindo arquivo", - "Upload_file_question_mark": "Enviar arquivo?", - "User": "Usuário", - "Users": "Usuários", - "User_added_by": "Usuário {{userAdded}} adicionado por {{userBy}}", - "User_Info": "Informações do usuário", - "User_has_been_key": "Usuário foi {{key}}", - "User_is_no_longer_role_by_": "{{user}} não pertence mais à {{role}} por {{userBy}}", - "User_muted_by": "User {{userMuted}} muted por {{userBy}}", - "User_removed_by": "Usuário {{userRemoved}} removido por {{userBy}}", - "User_sent_an_attachment": "{{user}} enviou um anexo", - "User_unmuted_by": "{{userBy}} permitiu que {{userUnmuted}} fale na sala", - "User_was_set_role_by_": "{{user}} foi definido como {{role}} por {{userBy}}", - "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": "Verificar", - "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.", - "Verify_your_email_for_the_code_we_sent": "Verifique em seu e-mail o código que enviamos", - "Video_call": "Chamada de vídeo", - "View_Original": "Visualizar original", - "Voice_call": "Chamada de voz", - "Waiting_for_network": "Aguardando rede...", - "Websocket_disabled": "Websocket está desativado para esse servidor.\n{{contact}}", - "Welcome": "Bem vindo", - "What_are_you_doing_right_now": "O que você está fazendo agora?", - "Whats_your_2fa": "Qual seu código de autenticação?", - "Without_Servers": "Sem Servidores", - "Workspaces": "Workspaces", - "Would_you_like_to_return_the_inquiry": "Deseja retornar a consulta?", - "Write_External_Permission_Message": "Rocket.Chat precisa de acesso à sua galeria para salvar imagens", - "Write_External_Permission": "Acesso à Galeria", - "Yes": "Sim", - "Yes_action_it": "Sim, {{action}}!", - "Yesterday": "Ontem", - "You_are_in_preview_mode": "Está é uma prévia do canal", - "You_are_offline": "Você está offline", - "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ê", - "You_need_to_verifiy_your_email_address_to_get_notications": "Você precisa confirmar seu endereço de e-mail para obter notificações", - "Your_certificate": "Seu certificado", - "Your_invite_link_will_expire_after__usesLeft__uses": "Seu link de convite irá vencer depois de {{usesLeft}} usos.", - "Your_invite_link_will_expire_on__date__or_after__usesLeft__uses": "Seu link de convite irá vencer em {{date}} ou depois de {{usesLeft}} usos.", - "Your_invite_link_will_expire_on__date__": "Seu link de convite irá vencer em {{date}}.", - "Your_invite_link_will_never_expire": "Seu link de convite nunca irá vencer.", - "Your_workspace": "Sua workspace", - "You_will_not_be_able_to_recover_this_message": "Você não será capaz de recuperar essa mensagem!", - "You_will_unset_a_certificate_for_this_server": "Você cancelará a configuração de um certificado para este servidor", - "Change_Language": "Alterar idioma", - "Crash_report_disclaimer": "Nós não rastreamos o conteúdo das suas conversas. O relatório de erros e os eventos do analytics apenas contém informações relevantes para identificarmos problemas e corrigí-los.", - "Type_message": "Digitar mensagem", - "Room_search": "Busca de sala", - "Room_selection": "Selecionar sala 1...9", - "Next_room": "Próxima sala", - "Previous_room": "Sala anterior", - "New_room": "Nova sala", - "Upload_room": "Enviar arquivo", - "Search_messages": "Buscar mensagens", - "Scroll_messages": "Rolar mensagens", - "Reply_latest": "Responder para última mensagem", - "Reply_in_Thread": "Responder por Tópico", - "Server_selection": "Seleção de servidor", - "Server_selection_numbers": "Selecionar servidor 1...9", - "Add_server": "Adicionar servidor", - "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_remove_all_data_from_this_server": "Isto removerá todos os dados desse servidor.", - "Mark_unread": "Marcar como não Lida", - "Wait_activation_warning": "Antes que você possa fazer o login, sua conta deve ser manualmente ativada por um administrador.", - "Screen_lock": "Bloqueio de Tela", - "Local_authentication_biometry_title": "Autenticar", - "Local_authentication_biometry_fallback": "Usar senha", - "Local_authentication_unlock_option": "Desbloquear com senha", - "Local_authentication_change_passcode": "Alterar senha", - "Local_authentication_info": "Nota: se você esquecer sua senha, terá de apagar e reinstalar o app.", - "Local_authentication_facial_recognition": "reconhecimento facial", - "Local_authentication_fingerprint": "impressão digital", - "Local_authentication_unlock_with_label": "Desbloquear com {{label}}", - "Local_authentication_auto_lock_60": "Após 1 minuto", - "Local_authentication_auto_lock_300": "Após 5 minutos", - "Local_authentication_auto_lock_900": "Após 15 minutos", - "Local_authentication_auto_lock_1800": "Após 30 minutos", - "Local_authentication_auto_lock_3600": "Após 1 hora", - "Passcode_enter_title": "Digite sua senha", - "Passcode_choose_title": "Insira sua nova senha", - "Passcode_choose_confirm_title": "Confirme sua nova senha", - "Passcode_choose_error": "As senhas não coincidem. Tente novamente.", - "Passcode_choose_force_set": "Senha foi exigida pelo admin", - "Passcode_app_locked_title": "Aplicativo bloqueado", - "Passcode_app_locked_subtitle": "Tente novamente em {{timeLeft}} segundos", - "After_seconds_set_by_admin": "Após {{seconds}} segundos (Configurado pelo adm)", - "Dont_activate": "Não ativar agora", - "Queued_chats": "Bate-papos na fila", - "Queue_is_empty": "A fila está vazia", - "Logout_from_other_logged_in_locations": "Sair de outros locais logados", - "You_will_be_logged_out_from_other_locations": "Você perderá a sessão de outros clientes", - "Logged_out_of_other_clients_successfully": "Desconectado de outros clientes com sucesso", - "Logout_failed": "Falha ao desconectar!", - "Log_analytics_events": "Logar eventos no analytics", - "E2E_encryption_change_password_title": "Alterar Senha de Criptografia", - "E2E_encryption_change_password_description": "Agora você pode criar grupos privados criptografados e mensagens diretas. Você também pode alterar os grupos privados ou DMs existentes para criptografados. Esta é uma criptografia de ponta a ponta, logo a chave para codificar / decodificar suas mensagens não será salva no servidor. Por esse motivo, você precisa armazenar sua senha em algum lugar seguro. Será solicitada a inserção de senha em outros dispositivos nos quais deseja usar a criptografia E2E.", - "E2E_encryption_change_password_error": "Erro ao alterar senha de criptografia!", - "E2E_encryption_change_password_success": "Senha de criptografia alterada com sucesso!", - "E2E_encryption_change_password_message": "Certifique-se de tê-la guardado em local seguro.", - "E2E_encryption_change_password_confirmation": "Sim, alterar", - "E2E_encryption_reset_title": "Redefinir Chave de Criptografia", - "E2E_encryption_reset_description": "Essa opção irá remover a chave de criptografia corrente e desconectá-lo. \nQuando você se conectar novamente, uma nova chave será gerada e restaurará acesso a qualquer canal com uma ou mais pessoas online. \nDevico à natureza da criptografia ponta a ponta, não será possível restaurar acesso a canais sem membros online.", - "E2E_encryption_reset_button": "Redefinir", - "E2E_encryption_reset_error": "Erro ao redefinir chave!", - "E2E_encryption_reset_message": "Você será desconectado.", - "E2E_encryption_reset_confirmation": "Sim, redefinir", - "Following": "Seguindo", - "Threads_displaying_all": "Mostrando Tudo", - "Threads_displaying_following": "Mostrando Seguindo", - "Threads_displaying_unread": "Mostrando Não Lidos", - "No_threads": "Não há tópicos", - "No_threads_following": "Você não está seguindo tópicos", - "No_threads_unread": "Não há tópicos não lidos", - "Messagebox_Send_to_channel": "Mostrar no canal", - "Remove_from_room": "Remover do canal", - "Ignore": "Ignorar", - "Unignore": "Deixar de ignorar", - "User_has_been_ignored": "Usuário foi ignorado", - "User_has_been_unignored": "O usuário não é mais ignorado", - "User_has_been_removed_from_s": "Usuário foi removido de {{s}}", - "User__username__is_now_a_leader_of__room_name_": "O usuário {{username}} agora é líder de {{room_name}}", - "User__username__is_now_a_moderator_of__room_name_": "O usuário {{username}} agora é moderador de {{room_name}}", - "User__username__is_now_a_owner_of__room_name_": "O usuário {{username}} agora é proprietário de {{room_name}}", - "User__username__removed_from__room_name__leaders": "O usuário {{username}} foi removido dos líderes de {{room_name}}", - "User__username__removed_from__room_name__moderators": "O usuário {{username}} foi removido dos moderadores de {{room_name}}", - "User__username__removed_from__room_name__owners": "O usuário {{username}} foi removido dos proprietários de {{room_name}}", - "The_user_will_be_removed_from_s": "O usuário será removido de {{s}}", - "Yes_remove_user": "Sim, remover usuário!", - "Direct_message": "Mensagem direta", - "Message_Ignored": "Mensagem ignorada. Toque para mostrar.", - "Enter_workspace_URL": "Digite a URL da sua workspace", - "Workspace_URL_Example": "Ex. sua-empresa.rocket.chat", - "This_room_encryption_has_been_enabled_by__username_": "A criptografia para essa sala foi habilitada por {{username}}", - "This_room_encryption_has_been_disabled_by__username_": "A criptografia para essa sala foi desabilitada por {{username}}", - "Teams": "Times", - "No_team_channels_found": "Nenhum canal encontrado", - "Team_not_found": "Time não encontrado", - "Private_Team": "Equipe Privada", - "Left_The_Team_Successfully": "Saiu do time com sucesso", - "Add_Existing_Channel": "Adicionar Canal Existente", - "invalid-room": "Sala inválida", - "room-name-already-exists": "Nome da sala já existe", - "error-team-creation": "Erro na criação do time", - "unauthorized": "Não autorizado", - "Left_The_Room_Successfully": "Saiu da sala com sucesso", - "Deleted_The_Team_Successfully": "Time deletado com sucesso", - "Deleted_The_Room_Successfully": "Sala deletada com sucesso", - "Convert_to_Channel": "Converter para um Canal" -} \ No newline at end of file + "1_person_reacted": "1 pessoa reagiu", + "1_user": "1 usuário", + "error-action-not-allowed": "{{action}} não é permitido", + "error-application-not-found": "Aplicação não encontrada", + "error-archived-duplicate-name": "Já há um canal arquivado com o nome {{room_name}}", + "error-avatar-invalid-url": "URL inválida de avatar: {{url}}", + "error-avatar-url-handling": "Erro durante o manuseio configuração avatar a partir de uma URL ({{url}}) para {{username}}", + "error-cant-invite-for-direct-room": "Não é possível convidar usuários para salas diretas", + "error-could-not-change-email": "Não foi possível mudar e-mail", + "error-could-not-change-name": "Não foi possível mudar o nome", + "error-could-not-change-username": "Não foi possível alterar o nome de usuário", + "error-delete-protected-role": "Não é possível remover um papel protegido", + "error-department-not-found": "Departamento não encontrado", + "error-direct-message-file-upload-not-allowed": "Compartilhamento de arquivos não está permitido em mensagens diretas", + "error-duplicate-channel-name": "Já existe um canal com nome {{channel_name}}", + "error-email-domain-blacklisted": "O domínio de e-mail está na lista negra", + "error-email-send-failed": "Erro ao tentar enviar e-mail: {{message}}", + "error-save-image": "Erro ao salvar imagem", + "error-field-unavailable": "{{field}} já está sendo usado :(", + "error-file-too-large": "Arquivo é muito grande", + "error-importer-not-defined": "O importador não foi definido corretamente; está faltando a classe Import.", + "error-input-is-not-a-valid-field": "{{input}} não é válido um {{field}}", + "error-invalid-actionlink": "Link de ação inválido", + "error-invalid-arguments": "Argumentos inválidos", + "error-invalid-asset": "Arquivo Inválido", + "error-invalid-channel": "Canal inválido.", + "error-invalid-channel-start-with-chars": "Canal inválido. Comece com @ ou #", + "error-invalid-custom-field": "Campo personalizado inválido", + "error-invalid-custom-field-name": "Nome inválido para o campo personalizado. Use apenas letras, números, hífens e underscores.", + "error-invalid-date": "Data fornecida inválida", + "error-invalid-description": "Descrição inválida", + "error-invalid-domain": "Domínio inválido", + "error-invalid-email": "{{email}} não é um e-mail válido", + "error-invalid-email-address": "Endereço de e-mail inválido", + "error-invalid-file-height": "Altura de arquivo inválida", + "error-invalid-file-type": "Tipo de arquivo inválido", + "error-invalid-file-width": "Altura de arquivo inválida", + "error-invalid-from-address": "Você informou um e-mail DE inválido.", + "error-invalid-integration": "Integração inválida", + "error-invalid-message": "Mensagem inválida", + "error-invalid-method": "Método inválido", + "error-invalid-name": "Nome inválido", + "error-invalid-password": "Senha inválida", + "error-invalid-redirectUri": "redirectUri inválido", + "error-invalid-role": "Papel inválido", + "error-invalid-room": "Sala inválida", + "error-invalid-room-name": "{{room_name}} não é um nome de sala válido", + "error-invalid-room-type": "{{type}} não é um tipo de sala válido.", + "error-invalid-settings": "Configurações fornecidas inválidas", + "error-invalid-subscription": "Assinatura inválida", + "error-invalid-token": "Token inválido", + "error-invalid-triggerWords": "triggerWords inválidos", + "error-invalid-urls": "URLs inválidas", + "error-invalid-user": "Usuário inválido", + "error-invalid-username": "Nome de usuário Inválido", + "error-invalid-webhook-response": "O URL do webhook respondeu com um status diferente de 200", + "error-message-deleting-blocked": "Exclusão de mensagens está bloqueada", + "error-message-editing-blocked": "Edição de mensagens está bloqueada", + "error-message-size-exceeded": "O tamanho da mensagem excede Message_MaxAllowedSize", + "error-missing-unsubscribe-link": "Você deve fornecer o link para desinscrever-se: [unsubscribe].", + "error-no-tokens-for-this-user": "Não existem tokens para este usuário", + "error-not-allowed": "Não permitido", + "error-not-authorized": "Não autorizado", + "error-push-disabled": "Notificações push desativadas", + "error-remove-last-owner": "Este é o último proprietário. Por favor, defina um novo proprietário antes de remover este.", + "error-role-in-use": "Não é possível remover o papel pois ele está em uso", + "error-role-name-required": "Nome do papel é obrigatório", + "error-the-field-is-required": "O campo {{field}} é obrigatório.", + "error-too-many-requests": "Erro, muitas solicitações. Por favor, diminua a velocidade. Você deve esperar {{seconds}} segundos antes de tentar novamente.", + "error-user-is-not-activated": "O usuário não está ativo", + "error-user-has-no-roles": "O usuário não possui permissões", + "error-user-limit-exceeded": "O número de usuários que você está tentando convidar para #channel_name excede o limite determindado pelo administrador", + "error-user-not-in-room": "O usuário não está nesta sala", + "error-user-registration-disabled": "O registro do usuário está desativado", + "error-user-registration-secret": "O registro de usuário é permitido somente via URL secreta", + "error-you-are-last-owner": "Você é o último proprietário da sala. Por favor defina um novo proprietário antes de sair.", + "error-status-not-allowed": "O status invisível está desativado", + "Actions": "Ações", + "activity": "atividade", + "Activity": "Atividade", + "Add_Reaction": "Reagir", + "Add_Server": "Adicionar servidor", + "Add_users": "Adicionar usuário", + "Agent": "Agente", + "Alert": "Alerta", + "alert": "alerta", + "alerts": "alertas", + "All_users_in_the_channel_can_write_new_messages": "Todos usuários no canal podem enviar mensagens novas", + "A_meaningful_name_for_the_discussion_room": "Um nome significativo para o canal de discussão", + "All": "Todos", + "Allow_Reactions": "Permitir reagir", + "Alphabetical": "Alfabético", + "and_more": "e mais", + "and": "e", + "announcement": "anúncio", + "Announcement": "Anúncio", + "Apply_Your_Certificate": "Aplicar certificado", + "ARCHIVE": "ARQUIVAR", + "archive": "arquivar", + "are_typing": "estão digitando", + "Are_you_sure_question_mark": "Você tem certeza?", + "Are_you_sure_you_want_to_leave_the_room": "Tem certeza de que deseja sair da sala {{room}}?", + "Audio": "Áudio", + "Authenticating": "Autenticando", + "Automatic": "Automático", + "Auto_Translate": "Tradução automática", + "Avatar_changed_successfully": "Avatar alterado com sucesso!", + "Avatar_Url": "Avatar URL", + "Away": "Ausente", + "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", + "By_proceeding_you_are_agreeing": "Ao prosseguir você está aceitando", + "Cancel_editing": "Cancelar edição", + "Cancel_recording": "Cancelar gravação", + "Cancel": "Cancelar", + "changing_avatar": "trocando avatar", + "creating_channel": "criando canal", + "creating_invite": "criando convite", + "Channel_Name": "Nome do Canal", + "Channels": "Canais", + "Chats": "Conversas", + "Call_already_ended": "A chamada já terminou!", + "Clear_cookies_alert": "Você quer limpar seus cookies?", + "Clear_cookies_desc": "Esta ação limpará todos os cookies de login permitindo que você faça login em outras contas.", + "Clear_cookies_yes": "Sim, limpar cookies", + "Clear_cookies_no": "Não, manter cookies", + "Click_to_join": "Clique para participar!", + "Close": "Fechar", + "Close_emoji_selector": "Fechar seletor de emojis", + "Closing_chat": "Fechando conversa", + "Change_language_loading": "Alterando idioma.", + "Chat_closed_by_agent": "Conversa fechada por agente", + "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", + "Code_or_password_invalid": "Código ou senha inválido", + "Collaborative": "Colaborativo", + "Confirm": "Confirmar", + "Connect": "Conectar", + "Connected": "Conectado", + "connecting_server": "conectando no servidor", + "Connecting": "Conectando...", + "Contact_us": "Entre em contato", + "Contact_your_server_admin": "Contate o administrador do servidor.", + "Continue_with": "Entrar com", + "Copied_to_clipboard": "Copiado para a área de transferência!", + "Copy": "Copiar", + "Conversation": "Conversação", + "Permalink": "Link-Permanente", + "Clear_cache_loading": "Limpando cache.", + "Create_account": "Criar conta", + "Create_Channel": "Criar Canal", + "Create_Direct_Messages": "Criar Mensagens Diretas", + "Create_Discussion": "Criar Discussão", + "Created_snippet": "criou um snippet", + "Create_a_new_workspace": "Criar nova área de trabalho", + "Create": "Criar", + "Dark": "Escuro", + "Dark_level": "Nível escuro", + "Default": "Padrão", + "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.", + "Department": "Departamento", + "delete": "excluir", + "Delete": "Excluir", + "DELETE": "EXCLUIR", + "deleting_room": "excluindo sala", + "description": "descrição", + "Description": "Descrição", + "Desktop_Options": "Opções De Área De Trabalho", + "Desktop_Notifications": "Notificações da Área de Trabalho", + "Desktop_Alert_info": "Essas notificações são entregues a você na área de trabalho", + "Directory": "Diretório", + "Direct_Messages": "Mensagens Diretas", + "Disable_notifications": "Desabilitar notificações", + "Discussions": "Discussões", + "Discussion_Desc": "Ajude a manter uma visão geral sobre o que está acontecendo! Ao criar uma discussão, um sub-canal do que você selecionou é criado e os dois são vinculados.", + "Discussion_name": "Nome da discussão", + "Done": "Pronto", + "Dont_Have_An_Account": "Não tem uma conta?", + "Do_you_have_an_account": "Você tem uma conta?", + "Do_you_have_a_certificate": "Você tem um certificado?", + "Do_you_really_want_to_key_this_room_question_mark": "Você quer realmente {{key}} esta sala?", + "E2E_Encryption": "Encriptação ponta a ponta", + "E2E_How_It_Works_info1": "Agora você pode criar grupos privados criptografados e mensagens diretas. Você também pode alterar grupos privados existentes ou DMs para criptografados.", + "E2E_How_It_Works_info2": "Esta é a criptografia *ponta a ponta*, portanto, a chave para codificar/decodificar suas mensagens e elas não serão salvas no servidor. Por esse motivo *você precisa armazenar esta senha em algum lugar seguro* que você pode acessar mais tarde se precisar.", + "E2E_How_It_Works_info3": "Se você continuar, será gerada automaticamente uma senha E2E.", + "E2E_How_It_Works_info4": "Você também pode configurar uma nova senha para sua chave de criptografia a qualquer momento em qualquer navegador em que tenha inserido a senha E2E existente.", + "edit": "editar", + "edited": "editado", + "Edit": "Editar", + "Edit_Status": "Editar Status", + "Edit_Invite": "Editar convite", + "End_to_end_encrypted_room": "Sala criptografada de ponta a ponta", + "end_to_end_encryption": "criptografia de ponta a ponta", + "Email_Notification_Mode_All": "Cada Menção / Mensagem Direta", + "Email_Notification_Mode_Disabled": "Desativado", + "Email_or_password_field_is_empty": "Email ou senha estão vazios", + "Email": "E-mail", + "email": "e-mail", + "Empty_title": "Título vazio", + "Enable_Auto_Translate": "Ativar a tradução automática", + "Enable_notifications": "Habilitar notificações", + "Encrypted": "Criptografado", + "Encrypted_message": "Mensagem criptografada", + "Enter_Your_E2E_Password": "Digite Sua Senha E2E", + "Enter_Your_Encryption_Password_desc1": "Isso permitirá que você acesse seus grupos privados e mensagens diretas criptografadas.", + "Enter_Your_Encryption_Password_desc2": "Você precisa inserir a senha para codificar/decodificar mensagens em todos os lugares em que usar o chat.", + "Encryption_error_title": "Sua senha de criptografia parece errada", + "Encryption_error_desc": "Não foi possível decodificar sua chave de criptografia para ser importada.", + "Everyone_can_access_this_channel": "Todos podem acessar este canal", + "Error_uploading": "Erro subindo", + "Expiration_Days": "Expira em (dias)", + "Favorite": "Adicionar aos Favoritos", + "Favorites": "Favoritos", + "Files": "Arquivos", + "File_description": "Descrição do arquivo", + "File_name": "Nome do arquivo", + "Finish_recording": "Encerrar gravação", + "Following_thread": "Começou a seguir tópico", + "For_your_security_you_must_enter_your_current_password_to_continue": "Para sua segurança, você precisa digitar sua senha", + "Forgot_password_If_this_email_is_registered": "Se este e-mail estiver cadastrado, enviaremos instruções sobre como redefinir sua senha. Se você não receber um e-mail em breve, volte e tente novamente.", + "Forgot_password": "Esqueceu sua senha?", + "Forgot_Password": "Esqueci minha senha", + "Forward": "Encaminhar", + "Forward_Chat": "Encaminhar Conversa", + "Forward_to_department": "Encaminhar para departamento", + "Forward_to_user": "Encaminhar para usuário", + "Full_table": "Clique para ver a tabela completa", + "Generate_New_Link": "Gerar novo convite", + "Group_by_favorites": "Agrupar favoritos", + "Group_by_type": "Agrupar por tipo", + "Hide": "Ocultar", + "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", + "IP": "IP", + "In_app": "No app", + "In_App_and_Desktop_Alert_info": "Exibe um banner na parte superior da tela quando o aplicativo é aberto e exibe uma notificação na área de trabalho", + "Invisible": "Invisível", + "Invite": "Convidar", + "is_typing": "está digitando", + "Invalid_or_expired_invite_token": "Token de convite inválido ou vencido", + "Invalid_server_version": "O servidor que você está conectando não é suportado mais por esta versão do aplicativo: {{currentVersion}}.\n\nEsta versão do aplicativo requer a versão {{minVersion}} do servidor para funcionar corretamente.", + "Invite_Link": "Link de Convite", + "Invite_users": "Convidar usuários", + "Join": "Entrar", + "Join_Code": "Insira o Código da Sala", + "Insert_Join_Code": "Insira o código para entrar na sala", + "Join_our_open_workspace": "Entra na nossa workspace pública", + "Join_your_workspace": "Entre na sua workspace", + "Just_invited_people_can_access_this_channel": "Apenas as pessoas convidadas podem acessar este canal", + "Language": "Idioma", + "last_message": "última mensagem", + "Leave_channel": "Sair do canal", + "leaving_room": "saindo do canal", + "Leave": "Sair da sala", + "leave": "sair", + "Legal": "Legal", + "Light": "Claro", + "Livechat": "Livechat", + "Login": "Entrar", + "Login_error": "Suas credenciais foram rejeitadas. Tente novamente por favor!", + "Login_with": "Login with", + "Logging_out": "Saindo.", + "Logout": "Sair", + "Max_number_of_uses": "Número máximo de usos", + "Max_number_of_users_allowed_is_number": "Número máximo de usuários é {{maxUsers}}", + "Members": "Membros", + "Mentioned_Messages": "Mensagens mencionadas", + "mentioned": "mencionado", + "Mentions": "Menções", + "Message_accessibility": "Mensagem de {{user}} às {{time}}: {{message}}", + "Message_actions": "Ações", + "Message_pinned": "Fixou uma mensagem", + "Message_removed": "Mensagem removida", + "message": "mensagem", + "messages": "mensagens", + "Message": "Mensagem", + "Messages": "Mensagens", + "Microphone_Permission_Message": "Rocket.Chat precisa de acesso ao seu microfone para enviar mensagens de áudio.", + "Microphone_Permission": "Acesso ao Microfone", + "Mute": "Mudo", + "muted": "mudo", + "N_people_reacted": "{{n}} pessoas reagiram", + "N_users": "{{n}} usuários", + "name": "nome", + "Name": "Nome", + "Navigation_history": "Histórico de navegação", + "Never": "Nunca", + "New_Message": "Nova Mensagem", + "New_Password": "Nova Senha", + "Next": "Próximo", + "No_files": "Não há arquivos", + "No_limit": "Sem limite", + "No_mentioned_messages": "Não há menções", + "No_pinned_messages": "Não há mensagens fixadas", + "No_results_found": "Nenhum resultado encontrado", + "No_starred_messages": "Não há mensagens favoritas", + "No_thread_messages": "Não há tópicos", + "No_label_provided": "Sem {{label}}.", + "No_Message": "Não há mensagens", + "No_messages_yet": "Não há mensagens ainda", + "No_Reactions": "Sem reações", + "Not_RC_Server": "Este não é um servidor Rocket.Chat.\n{{contact}}", + "Nothing": "Nada", + "Nothing_to_save": "Nada para salvar!", + "Notify_active_in_this_room": "Notificar usuários ativos nesta sala", + "Notify_all_in_this_room": "Notificar todos nesta sala", + "Notifications": "Notificações", + "Notification_Duration": "Duração da notificação", + "Notification_Preferences": "Preferências de notificação", + "No_available_agents_to_transfer": "Nenhum agente disponível para transferência", + "Offline": "Offline", + "Oops": "Ops!", + "Omnichannel": "Omnichannel", + "Open_Livechats": "Bate-papos em Andamento", + "Omnichannel_enable_alert": "Você não está disponível no Omnichannel. Você quer ficar disponível?", + "Onboarding_description": "Workspace é o espaço de colaboração do seu time ou organização. Peça um convite ou o endereço ao seu administrador ou crie uma workspace para o seu time.", + "Onboarding_join_workspace": "Entre numa workspace", + "Onboarding_subtitle": "Além da colaboração em equipe", + "Onboarding_title": "Bem vindo ao Rocket.Chat", + "Onboarding_join_open_description": "Entre na nossa workspace pública para conversar com o time da Rocket.Chat e nossa comunidade.", + "Onboarding_agree_terms": "Ao continuar, você aceita nossos ", + "Onboarding_less_options": "Menos opções", + "Onboarding_more_options": "Mais opções", + "Online": "Online", + "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", + "Open_your_authentication_app_and_enter_the_code": "Abra seu aplicativo de autenticação e digite o código.", + "OR": "OU", + "OS": "SO", + "Overwrites_the_server_configuration_and_use_room_config": "Substituir a configuração do servidor e usar a configuração da sala", + "Password": "Senha", + "Parent_channel_or_group": "Canal ou grupo pai", + "Permalink_copied_to_clipboard": "Link-permanente copiado para a área de transferência!", + "Phone": "Telefone", + "Pin": "Fixar", + "Pinned_Messages": "Mensagens Fixadas", + "pinned": "fixada", + "Pinned": "Mensagens Fixadas", + "Please_add_a_comment": "Por favor, adicione um comentário", + "Please_enter_your_password": "Por favor, digite sua senha", + "Please_wait": "Por favor, aguarde.", + "Preferences": "Preferências", + "Preferences_saved": "Preferências salvas!", + "Privacy_Policy": " Política de Privacidade", + "Private_Channel": "Canal Privado", + "Private": "Privado", + "Processing": "Processando...", + "Profile_saved_successfully": "Perfil salvo com sucesso!", + "Profile": "Perfil", + "Public_Channel": "Canal Público", + "Public": "Público", + "Push_Notifications": "Notificações Push", + "Push_Notifications_Alert_Info": "Essas notificações são entregues a você quando o aplicativo não está aberto", + "Quote": "Citar", + "Reactions_are_disabled": "Reagir está desabilitado", + "Reactions_are_enabled": "Reagir está habilitado", + "Reactions": "Reações", + "Read_External_Permission_Message": "Rocket.Chat precisa acessar fotos, mídia e arquivos no seu dispositivo", + "Read_External_Permission": "Permissão de acesso à arquivos", + "Read_Only_Channel": "Canal Somente Leitura", + "Read_Only": "Somente Leitura", + "Read_Receipt": "Lida por", + "Receive_Group_Mentions": "Receber menções de grupo", + "Receive_Group_Mentions_Info": "Receber menções @all e @here", + "Register": "Registrar", + "Repeat_Password": "Repetir Senha", + "Replied_on": "Respondido em:", + "replies": "respostas", + "reply": "resposta", + "Reply": "Responder", + "Report": "Reportar", + "Receive_Notification": "Receber Notificação", + "Receive_notifications_from": "Receber notificação de {{name}}", + "Resend": "Reenviar", + "Reset_password": "Resetar senha", + "resetting_password": "redefinindo senha", + "RESET": "RESETAR", + "Return": "Retornar", + "Review_app_title": "Você está gostando do app?", + "Review_app_desc": "Nos dê 5 estrelas na {{store}}", + "Review_app_yes": "Claro!", + "Review_app_no": "Não", + "Review_app_later": "Talvez depois", + "Review_app_unable_store": "Não foi possível abrir {{store}}", + "Review_this_app": "Avaliar esse app", + "Remove": "Remover", + "Roles": "Papéis", + "Room_actions": "Ações", + "Room_changed_announcement": "O anúncio da sala foi alterado para: {{announcement}} por {{userBy}}", + "Room_changed_description": "A descrição da sala foi alterada para: {{description}} por {{userBy}}", + "Room_changed_privacy": "Tipo da sala mudou para: {{type}} por {{userBy}}", + "Room_changed_topic": "Tópico da sala mudou para: {{topic}} por {{userBy}}", + "Room_Files": "Arquivos", + "Room_Info_Edit": "Editar", + "Room_Info": "Informações da Sala", + "Room_Members": "Membros", + "Room_name_changed": "Nome da sala alterado para: {{name}} por {{userBy}}", + "SAVE": "SALVAR", + "Save_Changes": "Salvar Alterações", + "Save": "Salvar", + "Saved": "Salvo", + "saving_preferences": "salvando preferências", + "saving_profile": "salvando perfil", + "saving_settings": "salvando configurações", + "saved_to_gallery": "Salvo na galeria", + "Save_Your_E2E_Password": "Salve sua senha E2E", + "Save_Your_Encryption_Password": "Salve Sua Senha de Criptografia", + "Save_Your_Encryption_Password_warning": "Esta senha não é armazenada em nenhum lugar, portanto, salve-a com cuidado em outro lugar.", + "Save_Your_Encryption_Password_info": "Observe que se você perder sua senha, não há como recuperá-la e você perderá o acesso às suas mensagens.", + "Search_Messages": "Buscar Mensagens", + "Search": "Buscar", + "Search_by": "Buscar por", + "Search_global_users": "Busca por usuários globais", + "Search_global_users_description": "Caso ativado, busca por usuários de outras empresas ou servidores.", + "Security_and_privacy": "Segurança e privacidade", + "Select_Avatar": "Selecionar Avatar", + "Select_Server": "Selecionar Servidor", + "Select_Users": "Selecionar Usuários", + "Select_a_Channel": "Selecione um canal", + "Select_a_Department": "Selecione um Departamento", + "Select_an_option": "Selecione uma opção", + "Select_a_User": "Selecione um Usuário", + "Send": "Enviar", + "Send_audio_message": "Enviar mensagem de áudio", + "Send_crash_report": "Enviar relatório de erros", + "Send_message": "Enviar mensagem", + "Send_me_the_code_again": "Envie-me o código novamente", + "Send_to": "Enviar para...", + "Sent_an_attachment": "Enviou um anexo", + "Server": "Servidor", + "Set_username_subtitle": "O usuário é utilizado para permitir que você seja mencionado em mensagens", + "Settings": "Configurações", + "Settings_succesfully_changed": "Configurações salvas com sucesso!", + "Share": "Compartilhar", + "Share_Link": "Share Link", + "Show_more": "Mostrar mais..", + "Show_Unread_Counter": "Mostrar contador não lido", + "Show_Unread_Counter_Info": "O contador não lido é exibido como um emblema à direita do canal, na lista", + "Sign_in_your_server": "Entrar no seu servidor", + "Sign_Up": "Registrar", + "Some_field_is_invalid_or_empty": "Algum campo está inválido ou vazio", + "Sorting_by": "Ordenando por {{key}}", + "Sound": "Som da notificação", + "Star_room": "Favoritar sala", + "Star": "Favorito", + "Starred_Messages": "Mensagens Favoritas", + "starred": "favoritou", + "Starred": "Mensagens Favoritas", + "Start_of_conversation": "Início da conversa", + "Start_a_Discussion": "Iniciar uma Discussão", + "Started_discussion": "Iniciou uma discussão:", + "Started_call": "Chamada iniciada por {{userBy}}", + "Submit": "Enviar", + "Table": "Tabela", + "Take_a_photo": "Tirar uma foto", + "Take_a_video": "Gravar um vídeo", + "Take_it": "Pegue!", + "Terms_of_Service": " Termos de Serviço ", + "Theme": "Tema", + "The_user_wont_be_able_to_type_in_roomName": "O usuário não poderá digitar em {{roomName}}", + "The_user_will_be_able_to_type_in_roomName": "O usuário poderá digitar em {{roomName}}", + "There_was_an_error_while_action": "Aconteceu um erro {{action}}!", + "This_room_is_blocked": "Este quarto está bloqueado", + "This_room_is_read_only": "Este quarto é apenas de leitura", + "Thread": "Tópico", + "Threads": "Tópicos", + "Timezone": "Fuso horário", + "To": "Para", + "topic": "tópico", + "Topic": "Tópico", + "Translate": "Traduzir", + "Try_again": "Tentar novamente", + "Two_Factor_Authentication": "Autenticação de dois fatores", + "Type_the_channel_name_here": "Digite o nome do canal", + "unarchive": "desarquivar", + "UNARCHIVE": "DESARQUIVAR", + "Unblock_user": "Desbloquear usuário", + "Unfavorite": "Remover dos Favoritos", + "Unfollowed_thread": "Parou de seguir tópico", + "Unmute": "Permitir que o usuário fale", + "unmuted": "permitiu que o usuário fale", + "Unpin": "Desafixar Mensagem", + "unread_messages": "não lidas", + "Unread": "Não lidas", + "Unread_on_top": "Não lidas no topo", + "Unstar": "Remover favorito", + "Updating": "Atualizando...", + "Uploading": "Subindo arquivo", + "Upload_file_question_mark": "Enviar arquivo?", + "User": "Usuário", + "Users": "Usuários", + "User_added_by": "Usuário {{userAdded}} adicionado por {{userBy}}", + "User_Info": "Informações do usuário", + "User_has_been_key": "Usuário foi {{key}}", + "User_is_no_longer_role_by_": "{{user}} não pertence mais à {{role}} por {{userBy}}", + "User_muted_by": "User {{userMuted}} muted por {{userBy}}", + "User_removed_by": "Usuário {{userRemoved}} removido por {{userBy}}", + "User_sent_an_attachment": "{{user}} enviou um anexo", + "User_unmuted_by": "{{userBy}} permitiu que {{userUnmuted}} fale na sala", + "User_was_set_role_by_": "{{user}} foi definido como {{role}} por {{userBy}}", + "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": "Verificar", + "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.", + "Verify_your_email_for_the_code_we_sent": "Verifique em seu e-mail o código que enviamos", + "Video_call": "Chamada de vídeo", + "View_Original": "Visualizar original", + "Voice_call": "Chamada de voz", + "Waiting_for_network": "Aguardando rede...", + "Websocket_disabled": "Websocket está desativado para esse servidor.\n{{contact}}", + "Welcome": "Bem vindo", + "What_are_you_doing_right_now": "O que você está fazendo agora?", + "Whats_your_2fa": "Qual seu código de autenticação?", + "Without_Servers": "Sem Servidores", + "Workspaces": "Workspaces", + "Would_you_like_to_return_the_inquiry": "Deseja retornar a consulta?", + "Write_External_Permission_Message": "Rocket.Chat precisa de acesso à sua galeria para salvar imagens", + "Write_External_Permission": "Acesso à Galeria", + "Yes": "Sim", + "Yes_action_it": "Sim, {{action}}!", + "Yesterday": "Ontem", + "You_are_in_preview_mode": "Está é uma prévia do canal", + "You_are_offline": "Você está offline", + "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ê", + "You_need_to_verifiy_your_email_address_to_get_notications": "Você precisa confirmar seu endereço de e-mail para obter notificações", + "Your_certificate": "Seu certificado", + "Your_invite_link_will_expire_after__usesLeft__uses": "Seu link de convite irá vencer depois de {{usesLeft}} usos.", + "Your_invite_link_will_expire_on__date__or_after__usesLeft__uses": "Seu link de convite irá vencer em {{date}} ou depois de {{usesLeft}} usos.", + "Your_invite_link_will_expire_on__date__": "Seu link de convite irá vencer em {{date}}.", + "Your_invite_link_will_never_expire": "Seu link de convite nunca irá vencer.", + "Your_workspace": "Sua workspace", + "You_will_not_be_able_to_recover_this_message": "Você não será capaz de recuperar essa mensagem!", + "You_will_unset_a_certificate_for_this_server": "Você cancelará a configuração de um certificado para este servidor", + "Change_Language": "Alterar idioma", + "Crash_report_disclaimer": "Nós não rastreamos o conteúdo das suas conversas. O relatório de erros e os eventos do analytics apenas contém informações relevantes para identificarmos problemas e corrigí-los.", + "Type_message": "Digitar mensagem", + "Room_search": "Busca de sala", + "Room_selection": "Selecionar sala 1...9", + "Next_room": "Próxima sala", + "Previous_room": "Sala anterior", + "New_room": "Nova sala", + "Upload_room": "Enviar arquivo", + "Search_messages": "Buscar mensagens", + "Scroll_messages": "Rolar mensagens", + "Reply_latest": "Responder para última mensagem", + "Reply_in_Thread": "Responder por Tópico", + "Server_selection": "Seleção de servidor", + "Server_selection_numbers": "Selecionar servidor 1...9", + "Add_server": "Adicionar servidor", + "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_remove_all_data_from_this_server": "Isto removerá todos os dados desse servidor.", + "Mark_unread": "Marcar como não Lida", + "Wait_activation_warning": "Antes que você possa fazer o login, sua conta deve ser manualmente ativada por um administrador.", + "Screen_lock": "Bloqueio de Tela", + "Local_authentication_biometry_title": "Autenticar", + "Local_authentication_biometry_fallback": "Usar senha", + "Local_authentication_unlock_option": "Desbloquear com senha", + "Local_authentication_change_passcode": "Alterar senha", + "Local_authentication_info": "Nota: se você esquecer sua senha, terá de apagar e reinstalar o app.", + "Local_authentication_facial_recognition": "reconhecimento facial", + "Local_authentication_fingerprint": "impressão digital", + "Local_authentication_unlock_with_label": "Desbloquear com {{label}}", + "Local_authentication_auto_lock_60": "Após 1 minuto", + "Local_authentication_auto_lock_300": "Após 5 minutos", + "Local_authentication_auto_lock_900": "Após 15 minutos", + "Local_authentication_auto_lock_1800": "Após 30 minutos", + "Local_authentication_auto_lock_3600": "Após 1 hora", + "Passcode_enter_title": "Digite sua senha", + "Passcode_choose_title": "Insira sua nova senha", + "Passcode_choose_confirm_title": "Confirme sua nova senha", + "Passcode_choose_error": "As senhas não coincidem. Tente novamente.", + "Passcode_choose_force_set": "Senha foi exigida pelo admin", + "Passcode_app_locked_title": "Aplicativo bloqueado", + "Passcode_app_locked_subtitle": "Tente novamente em {{timeLeft}} segundos", + "After_seconds_set_by_admin": "Após {{seconds}} segundos (Configurado pelo adm)", + "Dont_activate": "Não ativar agora", + "Queued_chats": "Bate-papos na fila", + "Queue_is_empty": "A fila está vazia", + "Logout_from_other_logged_in_locations": "Sair de outros locais logados", + "You_will_be_logged_out_from_other_locations": "Você perderá a sessão de outros clientes", + "Logged_out_of_other_clients_successfully": "Desconectado de outros clientes com sucesso", + "Logout_failed": "Falha ao desconectar!", + "Log_analytics_events": "Logar eventos no analytics", + "E2E_encryption_change_password_title": "Alterar Senha de Criptografia", + "E2E_encryption_change_password_description": "Agora você pode criar grupos privados criptografados e mensagens diretas. Você também pode alterar os grupos privados ou DMs existentes para criptografados. Esta é uma criptografia de ponta a ponta, logo a chave para codificar / decodificar suas mensagens não será salva no servidor. Por esse motivo, você precisa armazenar sua senha em algum lugar seguro. Será solicitada a inserção de senha em outros dispositivos nos quais deseja usar a criptografia E2E.", + "E2E_encryption_change_password_error": "Erro ao alterar senha de criptografia!", + "E2E_encryption_change_password_success": "Senha de criptografia alterada com sucesso!", + "E2E_encryption_change_password_message": "Certifique-se de tê-la guardado em local seguro.", + "E2E_encryption_change_password_confirmation": "Sim, alterar", + "E2E_encryption_reset_title": "Redefinir Chave de Criptografia", + "E2E_encryption_reset_description": "Essa opção irá remover a chave de criptografia corrente e desconectá-lo. \nQuando você se conectar novamente, uma nova chave será gerada e restaurará acesso a qualquer canal com uma ou mais pessoas online. \nDevico à natureza da criptografia ponta a ponta, não será possível restaurar acesso a canais sem membros online.", + "E2E_encryption_reset_button": "Redefinir", + "E2E_encryption_reset_error": "Erro ao redefinir chave!", + "E2E_encryption_reset_message": "Você será desconectado.", + "E2E_encryption_reset_confirmation": "Sim, redefinir", + "Following": "Seguindo", + "Threads_displaying_all": "Mostrando Tudo", + "Threads_displaying_following": "Mostrando Seguindo", + "Threads_displaying_unread": "Mostrando Não Lidos", + "No_threads": "Não há tópicos", + "No_threads_following": "Você não está seguindo tópicos", + "No_threads_unread": "Não há tópicos não lidos", + "Messagebox_Send_to_channel": "Mostrar no canal", + "Remove_from_room": "Remover do canal", + "Ignore": "Ignorar", + "Unignore": "Deixar de ignorar", + "User_has_been_ignored": "Usuário foi ignorado", + "User_has_been_unignored": "O usuário não é mais ignorado", + "User_has_been_removed_from_s": "Usuário foi removido de {{s}}", + "User__username__is_now_a_leader_of__room_name_": "O usuário {{username}} agora é líder de {{room_name}}", + "User__username__is_now_a_moderator_of__room_name_": "O usuário {{username}} agora é moderador de {{room_name}}", + "User__username__is_now_a_owner_of__room_name_": "O usuário {{username}} agora é proprietário de {{room_name}}", + "User__username__removed_from__room_name__leaders": "O usuário {{username}} foi removido dos líderes de {{room_name}}", + "User__username__removed_from__room_name__moderators": "O usuário {{username}} foi removido dos moderadores de {{room_name}}", + "User__username__removed_from__room_name__owners": "O usuário {{username}} foi removido dos proprietários de {{room_name}}", + "The_user_will_be_removed_from_s": "O usuário será removido de {{s}}", + "Yes_remove_user": "Sim, remover usuário!", + "Direct_message": "Mensagem direta", + "Message_Ignored": "Mensagem ignorada. Toque para mostrar.", + "Enter_workspace_URL": "Digite a URL da sua workspace", + "Workspace_URL_Example": "Ex. sua-empresa.rocket.chat", + "This_room_encryption_has_been_enabled_by__username_": "A criptografia para essa sala foi habilitada por {{username}}", + "This_room_encryption_has_been_disabled_by__username_": "A criptografia para essa sala foi desabilitada por {{username}}", + "Teams": "Times", + "No_team_channels_found": "Nenhum canal encontrado", + "Team_not_found": "Time não encontrado", + "Private_Team": "Equipe Privada", + "Left_The_Team_Successfully": "Saiu do time com sucesso", + "Add_Existing_Channel": "Adicionar Canal Existente", + "invalid-room": "Sala inválida", + "room-name-already-exists": "Nome da sala já existe", + "error-team-creation": "Erro na criação do time", + "unauthorized": "Não autorizado", + "Left_The_Room_Successfully": "Saiu da sala com sucesso", + "Deleted_The_Team_Successfully": "Time deletado com sucesso", + "Deleted_The_Room_Successfully": "Sala deletada com sucesso", + "Convert_to_Channel": "Converter para um Canal", + "Canned_Responses": "Respostas Predefinidas", + "No_match_found": "Nenhum resultado encontrado", + "Check_canned_responses": "Verifique nas respostas predefinidas", + "Searching": "Buscando", + "Use": "Use", + "Shortcut": "Atalho", + "Content": "Conteúdo", + "No_canned_responses": "Não há respostas predefinidas" +} diff --git a/app/i18n/locales/pt-PT.json b/app/i18n/locales/pt-PT.json index fa10dcd36..fb96c42e7 100644 --- a/app/i18n/locales/pt-PT.json +++ b/app/i18n/locales/pt-PT.json @@ -1,521 +1,521 @@ { - "1_person_reacted": "1 pessoa reagiu", - "1_user": "1 utilizador", - "error-action-not-allowed": "{{action}} não é permitida", - "error-application-not-found": "Aplicação não encontrada", - "error-archived-duplicate-name": "Existe um canal arquivado com o nome {{room_name}}", - "error-avatar-invalid-url": "URL de avatar inválido: {{url}}", - "error-avatar-url-handling": "Erro ao manipular a configuração de avatar de um URL ({{url}}) para {{username}}", - "error-cant-invite-for-direct-room": "Não pode convidar utilizadores para salas de mensagens directas", - "error-could-not-change-email": "Não foi possível alterar o e-mail", - "error-could-not-change-name": "Não foi possível alterar o nome", - "error-could-not-change-username": "Não foi possível alterar o nome de utilizador", - "error-could-not-change-status": "Impossível mudar estado", - "error-delete-protected-role": "Não é possível eliminar uma função protegida", - "error-department-not-found": "Departamento não encontrado", - "error-direct-message-file-upload-not-allowed": "Partilha de ficheiros não permitido em mensagens diretas", - "error-duplicate-channel-name": "Existe um canal com o nome {{room_name}}", - "error-email-domain-blacklisted": "O domínio de e-mail está na lista negra", - "error-email-send-failed": "Erro ao tentar enviar e-mail: {{message}}", - "error-save-image": "Erro ao salvar imagem", - "error-save-video": "Erro ao salvar vídeo", - "error-field-unavailable": "{{field}} já está em uso :(", - "error-file-too-large": "Ficheiro demasiado grande", - "error-importer-not-defined": "O importador não foi definido correctamente, a classe Import está em falta.", - "error-input-is-not-a-valid-field": "{{input}} não é um {{field}} válido", - "error-invalid-actionlink": "Link de acção inválido", - "error-invalid-arguments": "Argumentos inválidos", - "error-invalid-asset": "Ficheiro inválido", - "error-invalid-channel": "Canal inválido.", - "error-invalid-channel-start-with-chars": "Canal inválido. Começa por @ ou #", - "error-invalid-custom-field": "Campo personalizado inválido", - "error-invalid-custom-field-name": "Nome de campo personalizado inválido. Use apenas letras, números, hífens e sublinhados.", - "error-invalid-date": "Data inválida fornecida.", - "error-invalid-description": "Descrição inválida", - "error-invalid-domain": "Domínio inválido", - "error-invalid-email": "E-mail inválido {{email}}", - "error-invalid-email-address": "Endereço de e-mail invalido", - "error-invalid-file-height": "Altura de ficheiro inválida", - "error-invalid-file-type": "Tipo de ficheiro inválido", - "error-invalid-file-width": "Largura de ficheiro inválida", - "error-invalid-from-address": "Você informou um endereço DE inválido.", - "error-invalid-integration": "Integração inválida", - "error-invalid-message": "Mensagem inválida", - "error-invalid-method": "Método inválido", - "error-invalid-name": "Nome inválido", - "error-invalid-password": "Palavra-passe inválida", - "error-invalid-redirectUri": "redirectUri inválido", - "error-invalid-role": "Função inválido", - "error-invalid-room": "Sala inválida", - "error-invalid-room-name": "{{room_name}} não é um nome de sala válido", - "error-invalid-room-type": "{{type}} não é um tipo de sala válido.", - "error-invalid-settings": "Configurações inválidas fornecidas", - "error-invalid-subscription": "Subscrição inválida", - "error-invalid-token": "Token inválido", - "error-invalid-triggerWords": "triggerWords inválido", - "error-invalid-urls": "URLs inválidos", - "error-invalid-user": "Utilizador inválido", - "error-invalid-username": "Nome de utilizador inválido", - "error-invalid-webhook-response": "O URL do webhook respondeu com um estado diferente de 200", - "error-message-deleting-blocked": "A remoção de mensagens está bloqueada", - "error-message-editing-blocked": "A edição de mensagens está bloqueada", - "error-message-size-exceeded": "O tamanho da mensagem excede Message_MaxAllowedSize", - "error-missing-unsubscribe-link": "Você deve fornecer o link para cancelar a subscrição: [unsubscribe].", - "error-no-owner-channel": "Você não é dono do canal", - "error-no-tokens-for-this-user": "Não há tokens para este utilizador", - "error-not-allowed": "Não permitido", - "error-not-authorized": "Não autorizado", - "error-push-disabled": "Push está desactivado", - "error-remove-last-owner": "Este é o último proprietário. Por favor, defina um novo proprietário antes de remover este.", - "error-role-in-use": "Não é possível remover função porque está em uso", - "error-role-name-required": "Nome da função requerido", - "error-the-field-is-required": "O campo {{field}} é obrigatório.", - "error-too-many-requests": "Erro, demasiados pedidos. Por favor, diminua a velocidade. Você deve esperar {{seconds}} segundos antes de tentar novamente.", - "error-user-is-not-activated": "O utilizador não está activado", - "error-user-has-no-roles": "O utilizador não tem funções", - "error-user-limit-exceeded": "O número de utilizadores que você está a tentar convidar para #channel_name excede o limite definido pelo administrador", - "error-user-not-in-room": "O utilizador não está nesta sala", - "error-user-registration-custom-field": "error-user-registration-custom-field", - "error-user-registration-disabled": "O registo de utilizadores está desactivado", - "error-user-registration-secret": "O registo de utilizadores só é permitido por meio de um URL secreto", - "error-you-are-last-owner": "Você é o último proprietário. Por favor, defina novo proprietário antes de sair da sala.", - "error-status-not-allowed": "O estado invisível está desactivado", - "Actions": "Acções", - "activity": "actividade", - "Activity": "Actividade", - "Add_Reaction": "Adicionar Reacção", - "Add_Server": "Adicionar Servidor", - "Add_users": "Adicionar utilizadores", - "Admin_Panel": "Painel de Administração", - "Agent": "Agente", - "Alert": "Alerta", - "alert": "alerta", - "alerts": "alertas", - "All_users_in_the_channel_can_write_new_messages": "Todos os utilizadores no canal podem escrever novas mensagens", - "All_users_in_the_team_can_write_new_messages": "Todos os usuários da equipa podem escrever novas mensagens", - "A_meaningful_name_for_the_discussion_room": "Um nome significativo para a sala de discussão", - "All": "Todos", - "All_Messages": "Todas as Mensagens", - "Allow_Reactions": "Permitir Reacções", - "Alphabetical": "Alfabética", - "and_more": "e mais", - "and": "e", - "announcement": "anúncio", - "Announcement": "Anúncio", - "Apply_Your_Certificate": "Aplique o seu Certificado", - "ARCHIVE": "ARQUIVAR", - "archive": "arquivar", - "are_typing": "estão a escrever", - "Are_you_sure_question_mark": "Tem a certeza?", - "Are_you_sure_you_want_to_leave_the_room": "Tem certeza de que quer sair da sala {{room}}?", - "Audio": "Áudio", - "Authenticating": "Autenticando", - "Automatic": "Automático", - "Auto_Translate": "Auto-Tradução", - "Avatar_changed_successfully": "Avatar alterado com sucesso!", - "Avatar_Url": "URL do Avatar", - "Away": "Ausente", - "Back": "Voltar", - "Black": "Preto", - "Block_user": "Bloquear utilizador", - "Browser": "Navegador", - "Broadcast_channel_Description": "Apenas utilizadores autorizados podem escrever novas mensagens, mas os outros utilizadores poderão responder", - "Broadcast_Channel": "Canal de Transmissão", - "Busy": "Ocupado", - "By_proceeding_you_are_agreeing": "Ao prosseguir você concorda com o(s) nosso(s)", - "Cancel_editing": "Cancelar edição", - "Cancel_recording": "Cancelar gravação", - "Cancel": "Cancelar", - "changing_avatar": "a alterar avatar", - "creating_channel": "a criar canal", - "creating_invite": "a criar convite", - "Channel_Name": "Nome do Canal", - "Channels": "Canais", - "Chats": "Chats", - "Call_already_ended": "Chamada já terminada!", - "Clear_cookies_alert": "Quer limpar todas as cookies?", - "Clear_cookies_desc": "Esta acção irá limpar todos os cookies de login, permitindo que você faça login em outras contas.", - "Clear_cookies_yes": "Sim, limpar cookies", - "Clear_cookies_no": "Não, guardar cookies", - "Click_to_join": "Clique para Entrar!", - "Close": "Fechar", - "Close_emoji_selector": "Fechar selector de emoticons", - "Closing_chat": "A fechar o chat", - "Change_language_loading": "Mudança de idioma.", - "Chat_closed_by_agent": "Chat fechado por agente", - "Choose": "Escolher", - "Choose_from_library": "Escolher da biblioteca", - "Choose_file": "Escolher arquivo", - "Choose_where_you_want_links_be_opened": "Escolha onde você quer que os links sejam abertos", - "Code": "Código", - "Code_or_password_invalid": "Código ou senha inválidos", - "Collaborative": "Colaborativa", - "Confirm": "Confirmar", - "Connect": "Ligar", - "Connected": "Ligado", - "connecting_server": "conexão ao servidor", - "Connecting": "A ligar...", - "Contact_us": "Contacte-nos", - "Contact_your_server_admin": "Contacte o administrador do seu servidor.", - "Continue_with": "Continuar com", - "Copied_to_clipboard": "Copiado para a área de transferência!", - "Copy": "Copiar", - "Conversation": "Conversa", - "Permalink": "Link permanente", - "Certificate_password": "Senha do Certificado", - "Clear_cache": "Limpar a cache do servidor local", - "Clear_cache_loading": "A limpar a cache.", - "Whats_the_password_for_your_certificate": "Qual é a senha para o seu certificado?", - "Create_account": "Criar uma conta", - "Create_Channel": "Criar Canal", - "Create_Direct_Messages": "Criar Mensagens Diretas", - "Create_Discussion": "Criar Discussão", - "Created_snippet": "criado um extracto", - "Create_a_new_workspace": "Criar um novo espaço de trabalho", - "Create": "Criar", - "Custom_Status": "Status Personalizado", - "Dark": "Escuro", - "Dark_level": "Nível Escuro", - "Default": "Predefinição", - "Default_browser": "Navegador predefinido", - "Delete_Room_Warning": "Apagar uma sala irá remover todas as mensagens contidas nela. Isto não pode ser desfeito.", - "Department": "Departamento", - "delete": "apagar", - "Delete": "Apagar", - "DELETE": "APAGAR", - "move": "mover", - "deleting_room": "apagando sala", - "description": "descrição", - "Description": "Descrição", - "Desktop_Options": "Opções da área de trabalho", - "Desktop_Notifications": "Notificações da área de trabalho", - "Desktop_Alert_info": "Estas notificações são entregues na área de trabalho", - "Directory": "Directório", - "Direct_Messages": "Mensagens Directas", - "Disable_notifications": "Desactivar notificações", - "Discussions": "Discussões", - "Discussion_Desc": "Ajude a manter uma visão geral sobre o que está acontecendo! Ao criar uma discussão, é criado um sub-canal do que você selecionou e ambos estão ligados.", - "Discussion_name": "Nome da discussão", - "Done": "Feito", - "Dont_Have_An_Account": "Não tem uma conta?", - "Do_you_have_an_account": "Você tem uma conta?", - "Do_you_have_a_certificate": "Você tem um certificado?", - "Do_you_really_want_to_key_this_room_question_mark": "Você quer mesmo {{key}} esta sala?", - "E2E_Encryption": "Encriptação E2E", - "E2E_How_It_Works_info1": "Agora você pode criar grupos privados criptografados e mensagens diretas. Você também pode alterar grupos privados existentes ou DMs para criptografados.", - "E2E_How_It_Works_info2": "Isto é *criptografia ponto a ponto* portanto a chave para codificar/descodificar as suas mensagens não será salva no servidor. Por essa razão *você precisa armazenar esta senha em algum lugar seguro* que você posa aceder mais tarde, se precisar.", - "E2E_How_It_Works_info3": "Se você prosseguir, uma senha E2E será gerada automaticamente.", - "E2E_How_It_Works_info4": "Você também pode configurar uma nova senha para sua chave de criptografia a qualquer momento a partir de qualquer navegador que você tenha inserido a senha existente do E2E.", - "edit": "editar", - "edited": "editado", - "Edit": "Editar", - "Edit_Status": "Editar Status", - "Edit_Invite": "Editar Convite", - "End_to_end_encrypted_room": "Sala encriptada de ponta a ponta", - "end_to_end_encryption": "encriptação de ponta a ponta", - "Email_Notification_Mode_All": "Cada Menção/DM", - "Email_Notification_Mode_Disabled": "Desactivado", - "Email_or_password_field_is_empty": "O campo de e-mail ou palavra-passe está vazio", - "Email": "E-mail", - "email": "e-mail", - "Empty_title": "Título vazio", - "Enable_Auto_Translate": "Activar Auto-Tradução", - "Enable_notifications": "Activar notificações", - "Encrypted": "Encriptado", - "Encrypted_message": "Mensagem encriptada", - "Enter_Your_E2E_Password": "Digite a sua senha E2E", - "Enter_Your_Encryption_Password_desc1": "Isto permitir-lhe-á aceder aos seus grupos privados encriptados e às suas mensagens directas.", - "Enter_Your_Encryption_Password_desc2": "Você precisa digitar a senha para codificar/descodificar mensagens em cada lugar que você usar o chat.", - "Encryption_error_title": "A sua senha de encriptação parece errada", - "Encryption_error_desc": "Não foi possível descodificar a sua chave de encriptação para ser importada.", - "Everyone_can_access_this_channel": "Todos podem aceder a este canal", - "Everyone_can_access_this_team": "Todos podem aceder a esta equipa", - "Error_uploading": "Erro ao fazer o envio", - "Expiration_Days": "Validade (Dias)", - "Favorite": "Favorito", - "Favorites": "Favoritos", - "Files": "Ficheiros", - "File_description": "Descrição do ficheiro", - "File_name": "Nome do ficheiro", - "Finish_recording": "Terminar a gravação", - "Following_thread": "Seguir discussão", - "For_your_security_you_must_enter_your_current_password_to_continue": "Para sua segurança, você deve escrever a sua palavra-passe actual para continuar", - "Forgot_password_If_this_email_is_registered": "Se este e-mail estiver registado, enviaremos instruções sobre como repor a sua palavra-passe. Se você não receber um e-mail em breve, volte e tente novamente.", - "Forgot_password": "Esquecer palavra-passe", - "Forgot_Password": "Esquecer Palavra-passe", - "Forward": "Reencaminhar", - "Forward_Chat": "Reencaminhar Chat", - "Forward_to_department": "Reencaminhar para o departamento", - "Forward_to_user": "Reencaminhar para o utilizador", - "Full_table": "Clique para ver a tabela completa", - "Generate_New_Link": "Gerar Novo Link", - "Group_by_favorites": "Agrupar por favoritos", - "Group_by_type": "Agrupar por tipo", - "Hide": "Esconder", - "Has_joined_the_channel": "entrou no canal", - "Has_joined_the_conversation": "entrou na conversa", - "Has_left_the_channel": "saiu do canal", - "Hide_System_Messages": "Esconder mensagens do sistema", - "Hide_type_messages": "Esconder mensagens \"{{type}}\"", - "How_It_Works": "Como Funciona", - "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/de-silenciado", - "Message_HideType_r": "Nome da sala alterado", - "Message_HideType_ut": "Utilizador entrou na conversação", - "Message_HideType_wm": "Bem-vindo", - "Message_HideType_rm": "Mensagem Removida", - "Message_HideType_subscription_role_added": "Foi definido o estatuto", - "Message_HideType_subscription_role_removed": "Definição de estatuto removida", - "Message_HideType_room_archived": "Sala arquivada", - "Message_HideType_room_unarchived": "Sala desarquivada", - "I_Saved_My_E2E_Password": "Guardei a minha senha E2E", - "IP": "IP", - "In_app": "Na aplicação", - "In_App_And_Desktop": "Na aplicação e área de trabalho", - "In_App_and_Desktop_Alert_info": "Exibe um banner no topo da tela quando a aplicação está aberto, e exibe uma notificação na área de trabalho", - "Invisible": "Invisível", - "Invite": "Convidar", - "is_a_valid_RocketChat_instance": "é uma instância válida do Rocket.Chat", - "is_not_a_valid_RocketChat_instance": "is not a valid Rocket.Chat instance", - "is_typing": "está a escrever", - "Invalid_or_expired_invite_token": "Token de convite invalido ou expirado", - "Invalid_server_version": "O servidor ao qual esta tentando ligar-se, utiliza uma versão que não é suporta pela aplicação: {{currentVersion}}.\n\nA versão mínima requerida é {{minVersion}}", - "Invite_Link": "Link de convite", - "Invite_users": "Convidar utilizadores", - "Join": "Entrar", - "Join_Code": "Código de entrada", - "Insert_Join_Code": "Insira o código de entrada", - "Join_our_open_workspace": "Junte-se ao nosso espaço de trabalho aberto", - "Join_your_workspace": "Junte-se ao seu espaço de trabalho", - "Just_invited_people_can_access_this_channel": "Apenas utilizadores convidados podem aceder a este canal", - "Just_invited_people_can_access_this_team": "Apenas pessoas convidadas podem aceder a esta equipa", - "Language": "Idioma", - "last_message": "última mensagem", - "Leave_channel": "Sair do canal", - "leaving_room": "a sair da sala", - "Leave": "Sair", - "leave": "sair", - "Legal": "Legal", - "Light": "Luz", - "License": "Licença", - "Livechat": "Livechat", - "Login": "Entrar", - "Login_error": "As suas credenciais foram rejeitadas! Por favor, tente novamente.", - "Login_with": "Entrar com", - "Logging_out": "A terminar a sessão.", - "Logout": "Sair", - "Max_number_of_uses": "Número máximo de utilizações", - "Max_number_of_users_allowed_is_number": "O número máximo de utilizadores permitido é {{maxUsers}}", - "members": "membros", - "Members": "Membros", - "Mentioned_Messages": "Mensagens Mencionadas", - "mentioned": "mencionado", - "Mentions": "Menções", - "Message_accessibility": "Mensagem de {{user}} às {{time}}: {{message}}", - "Message_actions": "Acções de mensagem", - "Message_pinned": "Mensagem afixada", - "Message_removed": "Mensagem removida", - "Message_starred": "Mensagem estrelada", - "Message_unstarred": "Mensagem não estrelada", - "message": "mensagem", - "messages": "mensagens", - "Message": "Mensagem", - "Messages": "Mensagens", - "Message_Reported": "Mensagem reportada", - "Microphone_Permission_Message": "O Rocket.Chat necessita de acesso ao seu microfone para que você possa enviar mensagens de áudio.", - "Microphone_Permission": "Permissão de Microfone", - "Mute": "Silenciar", - "muted": "silenciado", - "My_servers": "Meus servidores", - "N_people_reacted": "{{n}} pessoas reagiram", - "N_users": "{{n}} utilizadores", - "N_channels": "{{n}} canais", - "name": "nome", - "Name": "Nome", - "Navigation_history": "Histórico de navegação", - "Never": "Nunca", - "New_Message": "Nova Mensagem", - "New_Password": "Nova Palavra-passe", - "New_Server": "Novo Servidor", - "Next": "Próximo", - "No_files": "Nenhum ficheiro", - "No_limit": "Sem limite", - "No_mentioned_messages": "Nenhuma mensagem mencionada", - "No_pinned_messages": "Nenhuma mensagem afixada", - "No_results_found": "Nenhum resultado encontrado", - "No_starred_messages": "Nenhuma mensagem marcada com estrela", - "No_thread_messages": "Sem mensagens de discussão ", - "No_label_provided": "{{label}} não fornecida/o", - "No_Message": "Nenhuma mensagem", - "No_messages_yet": "Ainda sem mensagens", - "No_Reactions": "Nenhuma reação", - "No_Read_Receipts": "Sem recibos de leitura", - "Not_logged": "Não ligado", - "Not_RC_Server": "Isto não é um servidor Rocket.Chat.\n{{contact}}", - "Nothing": "Nada", - "Nothing_to_save": "Nada para guardar!", - "Notify_active_in_this_room": "Notifica utilizadores activos nesta sala", - "Notify_all_in_this_room": "Notifica todos os utilizadores nesta sala", - "Notifications": "Notificações", - "Notification_Duration": "Duração da Notificação", - "Notification_Preferences": "Preferências de Notificação", - "No_available_agents_to_transfer": "Não há agentes disponíveis para transferir", - "Offline": "Desligado", - "Oops": "Oops!", - "Omnichannel": "Omnichannel", - "Open_Livechats": "Chats em andamento", - "Omnichannel_enable_alert": "Você não está disponível no Omnichannel. Você gostaria de estar disponível?", - "Onboarding_description": "Um espaço de trabalho é o espaço da sua equipa ou organização para colaborar. Peça ao administrador do espaço de trabalho um endereço para se juntar ou criar um para a sua equipa.", - "Onboarding_join_workspace": "Junte-se a um espaço de trabalho", - "Onboarding_subtitle": "Além da Colaboração da Equipe", - "Onboarding_title": "Bem vindo(a) ao Rocket.Chat", - "Onboarding_join_open_description": "Junte-se ao nosso espaço de trabalho aberto para conversar com a equipa e comunidade Rocket.Chat.", - "Onboarding_agree_terms": "Ao continuar, você concorda com Rocket.Chat", - "Onboarding_less_options": "Menos opções", - "Onboarding_more_options": "Mais opções", - "Online": "Ligado", - "Only_authorized_users_can_write_new_messages": "Apenas utilizadores autorizados podem escrever novas mensagens", - "Open_emoji_selector": "Abra o selector de emoticons", - "Open_Source_Communication": "Comunicação Open Source", - "Open_your_authentication_app_and_enter_the_code": "Abra o seu aplicativo de autenticação e digite o código.", - "OR": "OU", - "OS": "OS", - "Overwrites_the_server_configuration_and_use_room_config": "Sobrescreve a configuração do servidor e a configuração da sala de uso", - "Password": "Palavra-passe", - "Parent_channel_or_group": "Canal de origem ou grupo", - "Permalink_copied_to_clipboard": "Link permanente copiado para a área de transferência!", - "Phone": "Telefone", - "Pin": "Afixar", - "Pinned_Messages": "Mensagens Afixadas", - "pinned": "afixada", - "Pinned": "Afixada", - "Please_add_a_comment": "Por favor, acrescente um comentário", - "Please_enter_your_password": "Por favor, introduza a sua palavra-passe", - "Please_wait": "Por favor, espere.", - "Preferences": "Preferências", - "Preferences_saved": "Preferências guardadas!", - "Privacy_Policy": " Política de Privacidade", - "Private_Channel": "Canal Privado", - "Private": "Privado", - "Processing": "A processar...", - "Profile_saved_successfully": "Perfil actualizado com sucesso!", - "Profile": "Perfil", - "Public_Channel": "Canal Público", - "Public": "Público", - "Push_Notifications": "Notificações Push", - "Push_Notifications_Alert_Info": "Estas notificações são entregues quando o aplicativo não está aberto", - "Quote": "Citar", - "Reactions_are_disabled": "Reacções desactivadas", - "Reactions_are_enabled": "Reacções activadas", - "Reactions": "Reacções", - "Read": "Ler", - "Read_External_Permission_Message": "Rocket.Chat precisa acessar fotos, média e arquivos em seu dispositivo", - "Read_External_Permission": "Permissão de leitura da média", - "Read_Only_Channel": "Canal só de leitura", - "Read_Only": "Só de Leitura", - "Read_Receipt": "Recibos de leitura", - "Register": "Registar", - "Repeat_Password": "Repita a palavra-passe", - "Reply": "Responder", - "Resend": "Reenviar", - "Reset_password": "Repor palavra-passe", - "resetting_password": "a repor palavra-passe", - "RESET": "REPOR", - "Roles": "Funções", - "Room_actions": "Ações de sala", - "Room_changed_announcement": "Anúncio da sala alterado para: {{announcement}} por {{userBy}}", - "Room_changed_description": "Descrição da sala alterada para: {{description}} por {{userBy}}", - "Room_changed_privacy": "Tipo de sala alterado para: {{type}} por {{userBy}}", - "Room_changed_topic": "Tópico da sala alterado para: {{topic}} por {{userBy}}", - "Room_Files": "Fiheiros da Sala", - "Room_Info_Edit": "Editar Informação da Sala", - "Room_Info": "Informação da Sala", - "Room_Members": "Membros da Sala", - "Room_name_changed": "Nome da sala alterado para: {{name}} por {{userBy}}", - "SAVE": "GUARDAR", - "Save_Changes": "Guardar Alterações", - "Save": "Guardar", - "saving_preferences": "a guardar preferências", - "saving_profile": "a guardar perfil", - "saving_settings": "a guardar configurações", - "Search_Messages": "Pesquisar Mensagens", - "Search": "Pesquisar", - "Select_Avatar": "Seleccionar Avatar", - "Select_Users": "Seleccionar Utilizadores", - "Send": "Enviar", - "Send_audio_message": "Enviar mensagem de áudio", - "Send_message": "Enviar mensagem", - "Sent_an_attachment": "Enviou um ficheiro", - "Server": "Servidor", - "Servers": "Servidores", - "Set_username_subtitle": "O nome de utilizador é usado para permitir que outros mencionem você em mensagens", - "Settings": "Definições", - "Settings_succesfully_changed": "Definições guardadas com sucesso!", - "Share": "Partilhar", - "Sign_in_your_server": "Entre no seu servidor", - "Sign_Up": "Inscreva-se", - "Some_field_is_invalid_or_empty": "Algum campo é inválido ou está vazio", - "Sorting_by": "Ordenar por {{key}}", - "Star_room": "Marcar como favorito", - "Star": "Dar estrela", - "Starred_Messages": "Mensagens com estrela", - "starred": "deu uma estrela", - "Starred": "Deu uma estrela", - "Start_of_conversation": "Início da conversa", - "Submit": "Enviar", - "Take_a_photo": "Tirar uma foto", - "tap_to_change_status": "toque para alterar o estado", - "Tap_to_view_servers_list": "Toque para ver a lista de servidores", - "Terms_of_Service": " Termos do Serviço ", - "There_was_an_error_while_action": "Houve um erro enquanto {{action}}!", - "This_room_is_blocked": "Esta sala está bloqueada", - "This_room_is_read_only": "Esta sala é apenas de leitura", - "Timezone": "Fuso Horário", - "topic": "tópico", - "Topic": "Tópico", - "Try_again": "Tente novamente", - "Two_Factor_Authentication": "Autenticação 2FA", - "Type_the_channel_name_here": "Escreva o nome do canal aqui", - "unarchive": "desarquivar", - "UNARCHIVE": "DESARQUIVAR", - "Unblock_user": "Desbloquear utilizador", - "Unmute": "Retirar silêncio", - "unmuted": "silêncio removido", - "Unpin": "Desafixar", - "unread_messages": "não lidas", - "Unread": "Não lidas", - "Unread_on_top": "Não lidas no topo", - "Unstar": "Retirar estrela", - "Updating": "A actualizar...", - "Uploading": "A enviar", - "Upload_file_question_mark": "Enviar ficheiro?", - "User_added_by": "Utilizador {{userAdded}} adicionado por {{userBy}}", - "User_has_been_key": "Utilizador foi {{key}}", - "User_is_no_longer_role_by_": "{{userBy}} removeu o estatuto de {{role}} de {{user}}", - "User_muted_by": "Utilizador {{userMuted}} foi silenciado por {{userBy}}", - "User_removed_by": "Utilizador {{userRemoved}} removido por {{userBy}}", - "User_sent_an_attachment": "{{user}} enviou um ficheiro", - "User_unmuted_by": "{{userBy}} retirou o silêncio a {{userUnmuted}}", - "User_was_set_role_by_": "{{userBy}} deu estatuto de {{role}} a {{user}}", - "Username_is_empty": "O nome de utilizador está vazio", - "Username": "Nome de utilizador", - "Username_or_email": "Nome de utilizador ou e-mail", - "Validating": "A validar", - "Video_call": "Video chamada", - "Voice_call": "Chamada de voz", - "Welcome": "Bem vindo(a)", - "Whats_your_2fa": "Qual é o seu código 2FA?", - "Yes_action_it": "Sim, {{action}}!", - "Yesterday": "Ontem", - "You_are_in_preview_mode": "Você está no modo de pré-visualização", - "You_are_offline": "Você está desligado", - "You_can_search_using_RegExp_eg": "Você pode pesquisar usando RegEx. por exemplo, `/^text$/i`", - "You_colon": "Você: ", - "you_were_mentioned": "você foi mencionado", - "you": "você", - "You": "Você", - "You_will_not_be_able_to_recover_this_message": "Você será incapaz de recuperar esta mensagem!" -} \ No newline at end of file + "1_person_reacted": "1 pessoa reagiu", + "1_user": "1 utilizador", + "error-action-not-allowed": "{{action}} não é permitida", + "error-application-not-found": "Aplicação não encontrada", + "error-archived-duplicate-name": "Existe um canal arquivado com o nome {{room_name}}", + "error-avatar-invalid-url": "URL de avatar inválido: {{url}}", + "error-avatar-url-handling": "Erro ao manipular a configuração de avatar de um URL ({{url}}) para {{username}}", + "error-cant-invite-for-direct-room": "Não pode convidar utilizadores para salas de mensagens directas", + "error-could-not-change-email": "Não foi possível alterar o e-mail", + "error-could-not-change-name": "Não foi possível alterar o nome", + "error-could-not-change-username": "Não foi possível alterar o nome de utilizador", + "error-could-not-change-status": "Impossível mudar estado", + "error-delete-protected-role": "Não é possível eliminar uma função protegida", + "error-department-not-found": "Departamento não encontrado", + "error-direct-message-file-upload-not-allowed": "Partilha de ficheiros não permitido em mensagens diretas", + "error-duplicate-channel-name": "Existe um canal com o nome {{room_name}}", + "error-email-domain-blacklisted": "O domínio de e-mail está na lista negra", + "error-email-send-failed": "Erro ao tentar enviar e-mail: {{message}}", + "error-save-image": "Erro ao salvar imagem", + "error-save-video": "Erro ao salvar vídeo", + "error-field-unavailable": "{{field}} já está em uso :(", + "error-file-too-large": "Ficheiro demasiado grande", + "error-importer-not-defined": "O importador não foi definido correctamente, a classe Import está em falta.", + "error-input-is-not-a-valid-field": "{{input}} não é um {{field}} válido", + "error-invalid-actionlink": "Link de acção inválido", + "error-invalid-arguments": "Argumentos inválidos", + "error-invalid-asset": "Ficheiro inválido", + "error-invalid-channel": "Canal inválido.", + "error-invalid-channel-start-with-chars": "Canal inválido. Começa por @ ou #", + "error-invalid-custom-field": "Campo personalizado inválido", + "error-invalid-custom-field-name": "Nome de campo personalizado inválido. Use apenas letras, números, hífens e sublinhados.", + "error-invalid-date": "Data inválida fornecida.", + "error-invalid-description": "Descrição inválida", + "error-invalid-domain": "Domínio inválido", + "error-invalid-email": "E-mail inválido {{email}}", + "error-invalid-email-address": "Endereço de e-mail invalido", + "error-invalid-file-height": "Altura de ficheiro inválida", + "error-invalid-file-type": "Tipo de ficheiro inválido", + "error-invalid-file-width": "Largura de ficheiro inválida", + "error-invalid-from-address": "Você informou um endereço DE inválido.", + "error-invalid-integration": "Integração inválida", + "error-invalid-message": "Mensagem inválida", + "error-invalid-method": "Método inválido", + "error-invalid-name": "Nome inválido", + "error-invalid-password": "Palavra-passe inválida", + "error-invalid-redirectUri": "redirectUri inválido", + "error-invalid-role": "Função inválido", + "error-invalid-room": "Sala inválida", + "error-invalid-room-name": "{{room_name}} não é um nome de sala válido", + "error-invalid-room-type": "{{type}} não é um tipo de sala válido.", + "error-invalid-settings": "Configurações inválidas fornecidas", + "error-invalid-subscription": "Subscrição inválida", + "error-invalid-token": "Token inválido", + "error-invalid-triggerWords": "triggerWords inválido", + "error-invalid-urls": "URLs inválidos", + "error-invalid-user": "Utilizador inválido", + "error-invalid-username": "Nome de utilizador inválido", + "error-invalid-webhook-response": "O URL do webhook respondeu com um estado diferente de 200", + "error-message-deleting-blocked": "A remoção de mensagens está bloqueada", + "error-message-editing-blocked": "A edição de mensagens está bloqueada", + "error-message-size-exceeded": "O tamanho da mensagem excede Message_MaxAllowedSize", + "error-missing-unsubscribe-link": "Você deve fornecer o link para cancelar a subscrição: [unsubscribe].", + "error-no-owner-channel": "Você não é dono do canal", + "error-no-tokens-for-this-user": "Não há tokens para este utilizador", + "error-not-allowed": "Não permitido", + "error-not-authorized": "Não autorizado", + "error-push-disabled": "Push está desactivado", + "error-remove-last-owner": "Este é o último proprietário. Por favor, defina um novo proprietário antes de remover este.", + "error-role-in-use": "Não é possível remover função porque está em uso", + "error-role-name-required": "Nome da função requerido", + "error-the-field-is-required": "O campo {{field}} é obrigatório.", + "error-too-many-requests": "Erro, demasiados pedidos. Por favor, diminua a velocidade. Você deve esperar {{seconds}} segundos antes de tentar novamente.", + "error-user-is-not-activated": "O utilizador não está activado", + "error-user-has-no-roles": "O utilizador não tem funções", + "error-user-limit-exceeded": "O número de utilizadores que você está a tentar convidar para #channel_name excede o limite definido pelo administrador", + "error-user-not-in-room": "O utilizador não está nesta sala", + "error-user-registration-custom-field": "error-user-registration-custom-field", + "error-user-registration-disabled": "O registo de utilizadores está desactivado", + "error-user-registration-secret": "O registo de utilizadores só é permitido por meio de um URL secreto", + "error-you-are-last-owner": "Você é o último proprietário. Por favor, defina novo proprietário antes de sair da sala.", + "error-status-not-allowed": "O estado invisível está desactivado", + "Actions": "Acções", + "activity": "actividade", + "Activity": "Actividade", + "Add_Reaction": "Adicionar Reacção", + "Add_Server": "Adicionar Servidor", + "Add_users": "Adicionar utilizadores", + "Admin_Panel": "Painel de Administração", + "Agent": "Agente", + "Alert": "Alerta", + "alert": "alerta", + "alerts": "alertas", + "All_users_in_the_channel_can_write_new_messages": "Todos os utilizadores no canal podem escrever novas mensagens", + "All_users_in_the_team_can_write_new_messages": "Todos os usuários da equipa podem escrever novas mensagens", + "A_meaningful_name_for_the_discussion_room": "Um nome significativo para a sala de discussão", + "All": "Todos", + "All_Messages": "Todas as Mensagens", + "Allow_Reactions": "Permitir Reacções", + "Alphabetical": "Alfabética", + "and_more": "e mais", + "and": "e", + "announcement": "anúncio", + "Announcement": "Anúncio", + "Apply_Your_Certificate": "Aplique o seu Certificado", + "ARCHIVE": "ARQUIVAR", + "archive": "arquivar", + "are_typing": "estão a escrever", + "Are_you_sure_question_mark": "Tem a certeza?", + "Are_you_sure_you_want_to_leave_the_room": "Tem certeza de que quer sair da sala {{room}}?", + "Audio": "Áudio", + "Authenticating": "Autenticando", + "Automatic": "Automático", + "Auto_Translate": "Auto-Tradução", + "Avatar_changed_successfully": "Avatar alterado com sucesso!", + "Avatar_Url": "URL do Avatar", + "Away": "Ausente", + "Back": "Voltar", + "Black": "Preto", + "Block_user": "Bloquear utilizador", + "Browser": "Navegador", + "Broadcast_channel_Description": "Apenas utilizadores autorizados podem escrever novas mensagens, mas os outros utilizadores poderão responder", + "Broadcast_Channel": "Canal de Transmissão", + "Busy": "Ocupado", + "By_proceeding_you_are_agreeing": "Ao prosseguir você concorda com o(s) nosso(s)", + "Cancel_editing": "Cancelar edição", + "Cancel_recording": "Cancelar gravação", + "Cancel": "Cancelar", + "changing_avatar": "a alterar avatar", + "creating_channel": "a criar canal", + "creating_invite": "a criar convite", + "Channel_Name": "Nome do Canal", + "Channels": "Canais", + "Chats": "Chats", + "Call_already_ended": "Chamada já terminada!", + "Clear_cookies_alert": "Quer limpar todas as cookies?", + "Clear_cookies_desc": "Esta acção irá limpar todos os cookies de login, permitindo que você faça login em outras contas.", + "Clear_cookies_yes": "Sim, limpar cookies", + "Clear_cookies_no": "Não, guardar cookies", + "Click_to_join": "Clique para Entrar!", + "Close": "Fechar", + "Close_emoji_selector": "Fechar selector de emoticons", + "Closing_chat": "A fechar o chat", + "Change_language_loading": "Mudança de idioma.", + "Chat_closed_by_agent": "Chat fechado por agente", + "Choose": "Escolher", + "Choose_from_library": "Escolher da biblioteca", + "Choose_file": "Escolher arquivo", + "Choose_where_you_want_links_be_opened": "Escolha onde você quer que os links sejam abertos", + "Code": "Código", + "Code_or_password_invalid": "Código ou senha inválidos", + "Collaborative": "Colaborativa", + "Confirm": "Confirmar", + "Connect": "Ligar", + "Connected": "Ligado", + "connecting_server": "conexão ao servidor", + "Connecting": "A ligar...", + "Contact_us": "Contacte-nos", + "Contact_your_server_admin": "Contacte o administrador do seu servidor.", + "Continue_with": "Continuar com", + "Copied_to_clipboard": "Copiado para a área de transferência!", + "Copy": "Copiar", + "Conversation": "Conversa", + "Permalink": "Link permanente", + "Certificate_password": "Senha do Certificado", + "Clear_cache": "Limpar a cache do servidor local", + "Clear_cache_loading": "A limpar a cache.", + "Whats_the_password_for_your_certificate": "Qual é a senha para o seu certificado?", + "Create_account": "Criar uma conta", + "Create_Channel": "Criar Canal", + "Create_Direct_Messages": "Criar Mensagens Diretas", + "Create_Discussion": "Criar Discussão", + "Created_snippet": "criado um extracto", + "Create_a_new_workspace": "Criar um novo espaço de trabalho", + "Create": "Criar", + "Custom_Status": "Status Personalizado", + "Dark": "Escuro", + "Dark_level": "Nível Escuro", + "Default": "Predefinição", + "Default_browser": "Navegador predefinido", + "Delete_Room_Warning": "Apagar uma sala irá remover todas as mensagens contidas nela. Isto não pode ser desfeito.", + "Department": "Departamento", + "delete": "apagar", + "Delete": "Apagar", + "DELETE": "APAGAR", + "move": "mover", + "deleting_room": "apagando sala", + "description": "descrição", + "Description": "Descrição", + "Desktop_Options": "Opções da área de trabalho", + "Desktop_Notifications": "Notificações da área de trabalho", + "Desktop_Alert_info": "Estas notificações são entregues na área de trabalho", + "Directory": "Directório", + "Direct_Messages": "Mensagens Directas", + "Disable_notifications": "Desactivar notificações", + "Discussions": "Discussões", + "Discussion_Desc": "Ajude a manter uma visão geral sobre o que está acontecendo! Ao criar uma discussão, é criado um sub-canal do que você selecionou e ambos estão ligados.", + "Discussion_name": "Nome da discussão", + "Done": "Feito", + "Dont_Have_An_Account": "Não tem uma conta?", + "Do_you_have_an_account": "Você tem uma conta?", + "Do_you_have_a_certificate": "Você tem um certificado?", + "Do_you_really_want_to_key_this_room_question_mark": "Você quer mesmo {{key}} esta sala?", + "E2E_Encryption": "Encriptação E2E", + "E2E_How_It_Works_info1": "Agora você pode criar grupos privados criptografados e mensagens diretas. Você também pode alterar grupos privados existentes ou DMs para criptografados.", + "E2E_How_It_Works_info2": "Isto é *criptografia ponto a ponto* portanto a chave para codificar/descodificar as suas mensagens não será salva no servidor. Por essa razão *você precisa armazenar esta senha em algum lugar seguro* que você posa aceder mais tarde, se precisar.", + "E2E_How_It_Works_info3": "Se você prosseguir, uma senha E2E será gerada automaticamente.", + "E2E_How_It_Works_info4": "Você também pode configurar uma nova senha para sua chave de criptografia a qualquer momento a partir de qualquer navegador que você tenha inserido a senha existente do E2E.", + "edit": "editar", + "edited": "editado", + "Edit": "Editar", + "Edit_Status": "Editar Status", + "Edit_Invite": "Editar Convite", + "End_to_end_encrypted_room": "Sala encriptada de ponta a ponta", + "end_to_end_encryption": "encriptação de ponta a ponta", + "Email_Notification_Mode_All": "Cada Menção/DM", + "Email_Notification_Mode_Disabled": "Desactivado", + "Email_or_password_field_is_empty": "O campo de e-mail ou palavra-passe está vazio", + "Email": "E-mail", + "email": "e-mail", + "Empty_title": "Título vazio", + "Enable_Auto_Translate": "Activar Auto-Tradução", + "Enable_notifications": "Activar notificações", + "Encrypted": "Encriptado", + "Encrypted_message": "Mensagem encriptada", + "Enter_Your_E2E_Password": "Digite a sua senha E2E", + "Enter_Your_Encryption_Password_desc1": "Isto permitir-lhe-á aceder aos seus grupos privados encriptados e às suas mensagens directas.", + "Enter_Your_Encryption_Password_desc2": "Você precisa digitar a senha para codificar/descodificar mensagens em cada lugar que você usar o chat.", + "Encryption_error_title": "A sua senha de encriptação parece errada", + "Encryption_error_desc": "Não foi possível descodificar a sua chave de encriptação para ser importada.", + "Everyone_can_access_this_channel": "Todos podem aceder a este canal", + "Everyone_can_access_this_team": "Todos podem aceder a esta equipa", + "Error_uploading": "Erro ao fazer o envio", + "Expiration_Days": "Validade (Dias)", + "Favorite": "Favorito", + "Favorites": "Favoritos", + "Files": "Ficheiros", + "File_description": "Descrição do ficheiro", + "File_name": "Nome do ficheiro", + "Finish_recording": "Terminar a gravação", + "Following_thread": "Seguir discussão", + "For_your_security_you_must_enter_your_current_password_to_continue": "Para sua segurança, você deve escrever a sua palavra-passe actual para continuar", + "Forgot_password_If_this_email_is_registered": "Se este e-mail estiver registado, enviaremos instruções sobre como repor a sua palavra-passe. Se você não receber um e-mail em breve, volte e tente novamente.", + "Forgot_password": "Esquecer palavra-passe", + "Forgot_Password": "Esquecer Palavra-passe", + "Forward": "Reencaminhar", + "Forward_Chat": "Reencaminhar Chat", + "Forward_to_department": "Reencaminhar para o departamento", + "Forward_to_user": "Reencaminhar para o utilizador", + "Full_table": "Clique para ver a tabela completa", + "Generate_New_Link": "Gerar Novo Link", + "Group_by_favorites": "Agrupar por favoritos", + "Group_by_type": "Agrupar por tipo", + "Hide": "Esconder", + "Has_joined_the_channel": "entrou no canal", + "Has_joined_the_conversation": "entrou na conversa", + "Has_left_the_channel": "saiu do canal", + "Hide_System_Messages": "Esconder mensagens do sistema", + "Hide_type_messages": "Esconder mensagens \"{{type}}\"", + "How_It_Works": "Como Funciona", + "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/de-silenciado", + "Message_HideType_r": "Nome da sala alterado", + "Message_HideType_ut": "Utilizador entrou na conversação", + "Message_HideType_wm": "Bem-vindo", + "Message_HideType_rm": "Mensagem Removida", + "Message_HideType_subscription_role_added": "Foi definido o estatuto", + "Message_HideType_subscription_role_removed": "Definição de estatuto removida", + "Message_HideType_room_archived": "Sala arquivada", + "Message_HideType_room_unarchived": "Sala desarquivada", + "I_Saved_My_E2E_Password": "Guardei a minha senha E2E", + "IP": "IP", + "In_app": "Na aplicação", + "In_App_And_Desktop": "Na aplicação e área de trabalho", + "In_App_and_Desktop_Alert_info": "Exibe um banner no topo da tela quando a aplicação está aberto, e exibe uma notificação na área de trabalho", + "Invisible": "Invisível", + "Invite": "Convidar", + "is_a_valid_RocketChat_instance": "é uma instância válida do Rocket.Chat", + "is_not_a_valid_RocketChat_instance": "is not a valid Rocket.Chat instance", + "is_typing": "está a escrever", + "Invalid_or_expired_invite_token": "Token de convite invalido ou expirado", + "Invalid_server_version": "O servidor ao qual esta tentando ligar-se, utiliza uma versão que não é suporta pela aplicação: {{currentVersion}}.\n\nA versão mínima requerida é {{minVersion}}", + "Invite_Link": "Link de convite", + "Invite_users": "Convidar utilizadores", + "Join": "Entrar", + "Join_Code": "Código de entrada", + "Insert_Join_Code": "Insira o código de entrada", + "Join_our_open_workspace": "Junte-se ao nosso espaço de trabalho aberto", + "Join_your_workspace": "Junte-se ao seu espaço de trabalho", + "Just_invited_people_can_access_this_channel": "Apenas utilizadores convidados podem aceder a este canal", + "Just_invited_people_can_access_this_team": "Apenas pessoas convidadas podem aceder a esta equipa", + "Language": "Idioma", + "last_message": "última mensagem", + "Leave_channel": "Sair do canal", + "leaving_room": "a sair da sala", + "Leave": "Sair", + "leave": "sair", + "Legal": "Legal", + "Light": "Luz", + "License": "Licença", + "Livechat": "Livechat", + "Login": "Entrar", + "Login_error": "As suas credenciais foram rejeitadas! Por favor, tente novamente.", + "Login_with": "Entrar com", + "Logging_out": "A terminar a sessão.", + "Logout": "Sair", + "Max_number_of_uses": "Número máximo de utilizações", + "Max_number_of_users_allowed_is_number": "O número máximo de utilizadores permitido é {{maxUsers}}", + "members": "membros", + "Members": "Membros", + "Mentioned_Messages": "Mensagens Mencionadas", + "mentioned": "mencionado", + "Mentions": "Menções", + "Message_accessibility": "Mensagem de {{user}} às {{time}}: {{message}}", + "Message_actions": "Acções de mensagem", + "Message_pinned": "Mensagem afixada", + "Message_removed": "Mensagem removida", + "Message_starred": "Mensagem estrelada", + "Message_unstarred": "Mensagem não estrelada", + "message": "mensagem", + "messages": "mensagens", + "Message": "Mensagem", + "Messages": "Mensagens", + "Message_Reported": "Mensagem reportada", + "Microphone_Permission_Message": "O Rocket.Chat necessita de acesso ao seu microfone para que você possa enviar mensagens de áudio.", + "Microphone_Permission": "Permissão de Microfone", + "Mute": "Silenciar", + "muted": "silenciado", + "My_servers": "Meus servidores", + "N_people_reacted": "{{n}} pessoas reagiram", + "N_users": "{{n}} utilizadores", + "N_channels": "{{n}} canais", + "name": "nome", + "Name": "Nome", + "Navigation_history": "Histórico de navegação", + "Never": "Nunca", + "New_Message": "Nova Mensagem", + "New_Password": "Nova Palavra-passe", + "New_Server": "Novo Servidor", + "Next": "Próximo", + "No_files": "Nenhum ficheiro", + "No_limit": "Sem limite", + "No_mentioned_messages": "Nenhuma mensagem mencionada", + "No_pinned_messages": "Nenhuma mensagem afixada", + "No_results_found": "Nenhum resultado encontrado", + "No_starred_messages": "Nenhuma mensagem marcada com estrela", + "No_thread_messages": "Sem mensagens de discussão ", + "No_label_provided": "{{label}} não fornecida/o", + "No_Message": "Nenhuma mensagem", + "No_messages_yet": "Ainda sem mensagens", + "No_Reactions": "Nenhuma reação", + "No_Read_Receipts": "Sem recibos de leitura", + "Not_logged": "Não ligado", + "Not_RC_Server": "Isto não é um servidor Rocket.Chat.\n{{contact}}", + "Nothing": "Nada", + "Nothing_to_save": "Nada para guardar!", + "Notify_active_in_this_room": "Notifica utilizadores activos nesta sala", + "Notify_all_in_this_room": "Notifica todos os utilizadores nesta sala", + "Notifications": "Notificações", + "Notification_Duration": "Duração da Notificação", + "Notification_Preferences": "Preferências de Notificação", + "No_available_agents_to_transfer": "Não há agentes disponíveis para transferir", + "Offline": "Desligado", + "Oops": "Oops!", + "Omnichannel": "Omnichannel", + "Open_Livechats": "Chats em andamento", + "Omnichannel_enable_alert": "Você não está disponível no Omnichannel. Você gostaria de estar disponível?", + "Onboarding_description": "Um espaço de trabalho é o espaço da sua equipa ou organização para colaborar. Peça ao administrador do espaço de trabalho um endereço para se juntar ou criar um para a sua equipa.", + "Onboarding_join_workspace": "Junte-se a um espaço de trabalho", + "Onboarding_subtitle": "Além da Colaboração da Equipe", + "Onboarding_title": "Bem vindo(a) ao Rocket.Chat", + "Onboarding_join_open_description": "Junte-se ao nosso espaço de trabalho aberto para conversar com a equipa e comunidade Rocket.Chat.", + "Onboarding_agree_terms": "Ao continuar, você concorda com Rocket.Chat", + "Onboarding_less_options": "Menos opções", + "Onboarding_more_options": "Mais opções", + "Online": "Ligado", + "Only_authorized_users_can_write_new_messages": "Apenas utilizadores autorizados podem escrever novas mensagens", + "Open_emoji_selector": "Abra o selector de emoticons", + "Open_Source_Communication": "Comunicação Open Source", + "Open_your_authentication_app_and_enter_the_code": "Abra o seu aplicativo de autenticação e digite o código.", + "OR": "OU", + "OS": "OS", + "Overwrites_the_server_configuration_and_use_room_config": "Sobrescreve a configuração do servidor e a configuração da sala de uso", + "Password": "Palavra-passe", + "Parent_channel_or_group": "Canal de origem ou grupo", + "Permalink_copied_to_clipboard": "Link permanente copiado para a área de transferência!", + "Phone": "Telefone", + "Pin": "Afixar", + "Pinned_Messages": "Mensagens Afixadas", + "pinned": "afixada", + "Pinned": "Afixada", + "Please_add_a_comment": "Por favor, acrescente um comentário", + "Please_enter_your_password": "Por favor, introduza a sua palavra-passe", + "Please_wait": "Por favor, espere.", + "Preferences": "Preferências", + "Preferences_saved": "Preferências guardadas!", + "Privacy_Policy": " Política de Privacidade", + "Private_Channel": "Canal Privado", + "Private": "Privado", + "Processing": "A processar...", + "Profile_saved_successfully": "Perfil actualizado com sucesso!", + "Profile": "Perfil", + "Public_Channel": "Canal Público", + "Public": "Público", + "Push_Notifications": "Notificações Push", + "Push_Notifications_Alert_Info": "Estas notificações são entregues quando o aplicativo não está aberto", + "Quote": "Citar", + "Reactions_are_disabled": "Reacções desactivadas", + "Reactions_are_enabled": "Reacções activadas", + "Reactions": "Reacções", + "Read": "Ler", + "Read_External_Permission_Message": "Rocket.Chat precisa acessar fotos, média e arquivos em seu dispositivo", + "Read_External_Permission": "Permissão de leitura da média", + "Read_Only_Channel": "Canal só de leitura", + "Read_Only": "Só de Leitura", + "Read_Receipt": "Recibos de leitura", + "Register": "Registar", + "Repeat_Password": "Repita a palavra-passe", + "Reply": "Responder", + "Resend": "Reenviar", + "Reset_password": "Repor palavra-passe", + "resetting_password": "a repor palavra-passe", + "RESET": "REPOR", + "Roles": "Funções", + "Room_actions": "Ações de sala", + "Room_changed_announcement": "Anúncio da sala alterado para: {{announcement}} por {{userBy}}", + "Room_changed_description": "Descrição da sala alterada para: {{description}} por {{userBy}}", + "Room_changed_privacy": "Tipo de sala alterado para: {{type}} por {{userBy}}", + "Room_changed_topic": "Tópico da sala alterado para: {{topic}} por {{userBy}}", + "Room_Files": "Fiheiros da Sala", + "Room_Info_Edit": "Editar Informação da Sala", + "Room_Info": "Informação da Sala", + "Room_Members": "Membros da Sala", + "Room_name_changed": "Nome da sala alterado para: {{name}} por {{userBy}}", + "SAVE": "GUARDAR", + "Save_Changes": "Guardar Alterações", + "Save": "Guardar", + "saving_preferences": "a guardar preferências", + "saving_profile": "a guardar perfil", + "saving_settings": "a guardar configurações", + "Search_Messages": "Pesquisar Mensagens", + "Search": "Pesquisar", + "Select_Avatar": "Seleccionar Avatar", + "Select_Users": "Seleccionar Utilizadores", + "Send": "Enviar", + "Send_audio_message": "Enviar mensagem de áudio", + "Send_message": "Enviar mensagem", + "Sent_an_attachment": "Enviou um ficheiro", + "Server": "Servidor", + "Servers": "Servidores", + "Set_username_subtitle": "O nome de utilizador é usado para permitir que outros mencionem você em mensagens", + "Settings": "Definições", + "Settings_succesfully_changed": "Definições guardadas com sucesso!", + "Share": "Partilhar", + "Sign_in_your_server": "Entre no seu servidor", + "Sign_Up": "Inscreva-se", + "Some_field_is_invalid_or_empty": "Algum campo é inválido ou está vazio", + "Sorting_by": "Ordenar por {{key}}", + "Star_room": "Marcar como favorito", + "Star": "Dar estrela", + "Starred_Messages": "Mensagens com estrela", + "starred": "deu uma estrela", + "Starred": "Deu uma estrela", + "Start_of_conversation": "Início da conversa", + "Submit": "Enviar", + "Take_a_photo": "Tirar uma foto", + "tap_to_change_status": "toque para alterar o estado", + "Tap_to_view_servers_list": "Toque para ver a lista de servidores", + "Terms_of_Service": " Termos do Serviço ", + "There_was_an_error_while_action": "Houve um erro enquanto {{action}}!", + "This_room_is_blocked": "Esta sala está bloqueada", + "This_room_is_read_only": "Esta sala é apenas de leitura", + "Timezone": "Fuso Horário", + "topic": "tópico", + "Topic": "Tópico", + "Try_again": "Tente novamente", + "Two_Factor_Authentication": "Autenticação 2FA", + "Type_the_channel_name_here": "Escreva o nome do canal aqui", + "unarchive": "desarquivar", + "UNARCHIVE": "DESARQUIVAR", + "Unblock_user": "Desbloquear utilizador", + "Unmute": "Retirar silêncio", + "unmuted": "silêncio removido", + "Unpin": "Desafixar", + "unread_messages": "não lidas", + "Unread": "Não lidas", + "Unread_on_top": "Não lidas no topo", + "Unstar": "Retirar estrela", + "Updating": "A actualizar...", + "Uploading": "A enviar", + "Upload_file_question_mark": "Enviar ficheiro?", + "User_added_by": "Utilizador {{userAdded}} adicionado por {{userBy}}", + "User_has_been_key": "Utilizador foi {{key}}", + "User_is_no_longer_role_by_": "{{userBy}} removeu o estatuto de {{role}} de {{user}}", + "User_muted_by": "Utilizador {{userMuted}} foi silenciado por {{userBy}}", + "User_removed_by": "Utilizador {{userRemoved}} removido por {{userBy}}", + "User_sent_an_attachment": "{{user}} enviou um ficheiro", + "User_unmuted_by": "{{userBy}} retirou o silêncio a {{userUnmuted}}", + "User_was_set_role_by_": "{{userBy}} deu estatuto de {{role}} a {{user}}", + "Username_is_empty": "O nome de utilizador está vazio", + "Username": "Nome de utilizador", + "Username_or_email": "Nome de utilizador ou e-mail", + "Validating": "A validar", + "Video_call": "Video chamada", + "Voice_call": "Chamada de voz", + "Welcome": "Bem vindo(a)", + "Whats_your_2fa": "Qual é o seu código 2FA?", + "Yes_action_it": "Sim, {{action}}!", + "Yesterday": "Ontem", + "You_are_in_preview_mode": "Você está no modo de pré-visualização", + "You_are_offline": "Você está desligado", + "You_can_search_using_RegExp_eg": "Você pode pesquisar usando RegEx. por exemplo, `/^text$/i`", + "You_colon": "Você: ", + "you_were_mentioned": "você foi mencionado", + "you": "você", + "You": "Você", + "You_will_not_be_able_to_recover_this_message": "Você será incapaz de recuperar esta mensagem!" +} diff --git a/app/i18n/locales/ru.json b/app/i18n/locales/ru.json index 303ca52c0..359aadf99 100644 --- a/app/i18n/locales/ru.json +++ b/app/i18n/locales/ru.json @@ -1,772 +1,776 @@ { - "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-could-not-change-status": "Не удалось изменить статус", - "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-save-video": "Ошибка при попытке сохранить видео", - "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": "Неверный адрес электронной почты {{email}}", - "error-invalid-email-address": "Неверный адрес электронной почты", - "error-invalid-file-height": "Недопустимая высота файла", - "error-invalid-file-type": "Неверный тип файла", - "error-invalid-file-width": "Недопустимая ширина файла", - "error-invalid-from-address": "Вы указали неверный адрес FROM.", - "error-invalid-integration": "Недопустимая интеграция", - "error-invalid-message": "Недопустимое сообщение", - "error-invalid-method": "Недопустимый метод", - "error-invalid-name": "Недопустимое имя", - "error-invalid-password": "Неверный пароль", - "error-invalid-redirectUri": "Недопустимый redirectUri", - "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": "URL-адрес ответил статусом, отличным от 200", - "error-message-deleting-blocked": "Удаление сообщений заблокировано", - "error-message-editing-blocked": "Правка сообщений заблокирована", - "error-message-size-exceeded": "Размер сообщения превышает максимально разрешенный", - "error-missing-unsubscribe-link": "Вы должны указать ссылку [отписаться].", - "error-no-owner-channel": "Вы не являетесь владельцем данного чата", - "error-no-tokens-for-this-user": "Для этого пользователя нет токенов", - "error-not-allowed": "Не допускается", - "error-not-authorized": "Не разрешено", - "error-push-disabled": "Push отключен", - "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": "Вы последний владелец. Пожалуйста, назначьте нового владельца, прежде чем покинуть чат.", - "error-status-not-allowed": "Статус Невидимый отключён", - "Actions": "Действия", - "activity": "активности", - "Activity": "По активности", - "Add_Reaction": "Добавить реакцию", - "Add_Server": "Добавить сервер", - "Add_users": "Добавить пользователей", - "Admin_Panel": "Панель админа", - "Agent": "Агент", - "Alert": "Оповещение", - "alert": "оповещение", - "alerts": "оповещения", - "All_users_in_the_channel_can_write_new_messages": "Все пользователи канала могут писать новые сообщения", - "All_users_in_the_team_can_write_new_messages": "Все пользователи в Команде могут писать новые сообщения", - "A_meaningful_name_for_the_discussion_room": "Осмысленное имя для обсуждения", - "All": "Все", - "All_Messages": "Все сообщения", - "Allow_Reactions": "Разрешить реакции", - "Alphabetical": "По алфавиту", - "and_more": "и более", - "and": "и", - "announcement": "объявление", - "Announcement": "Объявление", - "Apply_Your_Certificate": "Применить ваш сертификат", - "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": "Блокировать пользователя", - "Browser": "Браузер", - "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": "Вызов уже завершен!", - "Clear_cookies_alert": "Вы действительно хотите очистить все cookies?", - "Clear_cookies_desc": "Это действие очистит все ваши cookies для входа, это позволит вам войти под другой учетной записью.", - "Clear_cookies_yes": "Да, очистить cookies", - "Clear_cookies_no": "Нет, сохранить cookies", - "Click_to_join": "Нажмите, чтобы присоединиться!", - "Close": "Закрыть", - "Close_emoji_selector": "Закрыть выбор emoji", - "Closing_chat": "Закрытие чата", - "Change_language_loading": "Изменение языка.", - "Chat_closed_by_agent": "Чат закрыт агентом", - "Choose": "Выбрать", - "Choose_from_library": "Выбрать из библиотеки", - "Choose_file": "Выбрать файл", - "Choose_where_you_want_links_be_opened": "Выберите где вы хотите открывать ссылки", - "Code": "Код", - "Code_or_password_invalid": "Код или пароль не верны", - "Collaborative": "Совместный", - "Confirm": "Подтверждение", - "Connect": "Соединение", - "Connected": "Подключено", - "connecting_server": "подключение к серверу", - "Connecting": "Соединение...", - "Contact_us": "Связаться с нами", - "Contact_your_server_admin": "Свяжитесь с администратором сервера.", - "Continue_with": "Продолжить с", - "Copied_to_clipboard": "Скопировано в буфер обмена!", - "Copy": "Копировать", - "Conversation": "Диалог", - "Permalink": "Постоянная ссылка", - "Certificate_password": "Пароль сертификата", - "Clear_cache": "Очистить локальный кэш сервера", - "Clear_cache_loading": "Очистка кэша.", - "Whats_the_password_for_your_certificate": "Какой пароль для вашего сертификата?", - "Create_account": "Создать аккаунт", - "Create_Channel": "Создать канал", - "Create_Direct_Messages": "Создать личное сообщение", - "Create_Discussion": "Создать обсуждение", - "Created_snippet": "создать сниппет", - "Create_a_new_workspace": "Новое рабочее пространство", - "Create": "Создать", - "Custom_Status": "Персонализированный Статус", - "Dark": "Темный", - "Dark_level": "Уровень затемненности", - "Default": "По умолчанию", - "Default_browser": "Браузер по умолчанию", - "Delete_Room_Warning": "Удаление канала приведет к удалению всех сообщений, размещенных в нем. Это не может быть отменено.", - "Department": "Отдел", - "delete": "удалить", - "Delete": "Удалить", - "DELETE": "УДАЛИТЬ", - "move": "переместить", - "deleting_room": "удаление чата", - "description": "описание", - "Description": "Описание", - "Desktop_Options": "Параметры рабочего стола", - "Desktop_Notifications": "Уведомления рабочего стола", - "Desktop_Alert_info": "Эти уведомления появятся на рабочем столе", - "Directory": "Директория", - "Direct_Messages": "Личные сообщения", - "Disable_notifications": "Отключить уведомления", - "Discussions": "Обсуждения", - "Discussion_Desc": "Помогают разобраться в том, что происходит! При создании Обсуждения, суб-канала одного из выбранных вами каналов так же создается и привязка к нему.", - "Discussion_name": "Имя Обсуждения", - "Done": "Готово", - "Dont_Have_An_Account": "Нет аккаунта?", - "Do_you_have_an_account": "У вас есть аккаунт?", - "Do_you_have_a_certificate": "У вас есть сертификат?", - "Do_you_really_want_to_key_this_room_question_mark": "Вы действительно хотите {{key}} этот канал?", - "E2E_Encryption": "E2E шифрование", - "E2E_How_It_Works_info1": "Теперь Вы можете создавать зашифрованные приватные чаты и личные сообщения. Вы так же можете изменить существующе приватные чаты и личные сообщения сделав их зашифрованными.", - "E2E_How_It_Works_info2": "Это *сквозное шифрование*, поэтому ключ для шифрования/расшифровки ваших сообщений не будет сохранен на сервере. Значит *Вам нужно сохранить этот пароль где-то в безопасном месте*, где он будет Вам доступен, если потребуется.", - "E2E_How_It_Works_info3": "В случаем продолжения, пароль Е2Е будет сгенерирован автоматически.", - "E2E_How_It_Works_info4": "Так же Вы можете задать новый пароль для вашего ключа шифрования в любое время при помощи браузера там же, где Вы ввели существующий Е2Е пароль.", - "edit": "править", - "edited": "исправлено", - "Edit": "Правка", - "Edit_Status": "Изменить статус", - "Edit_Invite": "Редактировать Приглашение", - "End_to_end_encrypted_room": "Чат со сквозным шифрованием", - "end_to_end_encryption": "сквозное шифрование", - "Email_Notification_Mode_All": "Каждое Упоминание/Личное сообщение", - "Email_Notification_Mode_Disabled": "Отключено", - "Email_or_password_field_is_empty": "Поле электронной почты или пароля пусты", - "Email": "E-mail", - "email": "e-mail", - "Empty_title": "Пустой заголовок", - "Enable_Auto_Translate": "Включить автоперевод", - "Enable_notifications": "Включить уведомления", - "Encrypted": "Зашифрован", - "Encrypted_message": "Зашифрованное сообщение", - "Enter_Your_E2E_Password": "Введите Ваш E2E Пароль", - "Enter_Your_Encryption_Password_desc1": "Вы сможете получить доступ к вашим зашифрованным приватным чатам и личным сообщениям.", - "Enter_Your_Encryption_Password_desc2": "Вам нужно ввести пароль для шифрования/расшифровки сообщений в каждом клиенте.", - "Encryption_error_title": "Введен не верный пароль шифрования", - "Encryption_error_desc": "Невозможно расшифровать ваш ключ шифрования, чтобы импортировать его", - "Everyone_can_access_this_channel": "Каждый может получить доступ к этому каналу", - "Everyone_can_access_this_team": "Каждый может получить доступ к этой Команде", - "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_password_If_this_email_is_registered": "Если эта электронная почта зарегистрирована, мы отправим инструкции о том, как сбросить пароль. Если вы не получите письмо в ближайшее время, вернитесь и повторите попытку.", - "Forgot_password": "Забыли пароль", - "Forgot_Password": "Забыли Пароль", - "Forward": "Перенаправить", - "Forward_Chat": "Перенаправить Чат", - "Forward_to_department": "Перенаправить в отдел", - "Forward_to_user": "Перенаправить пользователю", - "Full_table": "Нажмите, чтобы увидеть полную таблицу", - "Generate_New_Link": "Сгенерировать Новую Ссылку", - "Group_by_favorites": "По избранным", - "Group_by_type": "По типу", - "Hide": "Скрыть", - "Has_joined_the_channel": "присоединился к каналу", - "Has_joined_the_conversation": "присоединился к беседе", - "Has_left_the_channel": "покинул канал", - "Hide_System_Messages": "Скрыть Системные Сообщения", - "Hide_type_messages": "Скрыть \"{{type}}\" сообщения", - "How_It_Works": "Как Это Работает", - "Message_HideType_uj": "Пользователь Присоединился", - "Message_HideType_ul": "Пользователь Покинул", - "Message_HideType_ru": "Пользователь Удален", - "Message_HideType_au": "Пользователь Добавлен", - "Message_HideType_mute_unmute": "Пользователь Заглушен / Заглушивание отменено", - "Message_HideType_r": "Имя Чата Изменено", - "Message_HideType_ut": "Пользователь Присоединился к Беседе", - "Message_HideType_wm": "Добро пожаловать", - "Message_HideType_rm": "Сообщение Удалено", - "Message_HideType_subscription_role_added": "Была назначена Роль", - "Message_HideType_subscription_role_removed": "Роль более не определена", - "Message_HideType_room_archived": "Чат Архивирован", - "Message_HideType_room_unarchived": "Чат Разархивирован", - "I_Saved_My_E2E_Password": "Я Сохранил Свой E2E Пароль", - "IP": "IP", - "In_app": "В приложении", - "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": "Присоединиться", - "Join_Code": "Код присоединения", - "Insert_Join_Code": "Вставить код присоединения", - "Join_our_open_workspace": "Присоединиться к нашему открытому серверу", - "Join_your_workspace": "Присоединиться к вашему серверу", - "Just_invited_people_can_access_this_channel": "Только приглашенные люди могут получить доступ к этому каналу", - "Just_invited_people_can_access_this_team": "Только приглашенные пользователи могут получить доступ к этой Команде", - "Language": "Язык", - "last_message": "последнее сообщение", - "Leave_channel": "Покинуть канал", - "leaving_room": "покинуть комнату", - "Leave": "Покинуть комнату", - "leave": "покинуть", - "Legal": "Правовые аспекты", - "Light": "Светлая", - "License": "Лицензия", - "Livechat": "Livechat", - "Livechat_edit": "Редактирование Livechat", - "Login": "Вход", - "Login_error": "Ваши учетные данные были отклонены! Пожалуйста, попробуйте еще раз.", - "Login_with": "Войти с", - "Logging_out": "Осуществляется выход.", - "Logout": "Выйти", - "Max_number_of_uses": "Максимальное количество", - "Max_number_of_users_allowed_is_number": "Максимальное количество разрешенных пользователей {{maxUsers}}", - "members": "пользователи", - "Members": "Пользователи", - "Mentioned_Messages": "Упомянутые сообщения", - "mentioned": "упомянутые", - "Mentions": "Упоминания", - "Message_accessibility": "Сообщение от {{user}} в {{time}}: {{message}}", - "Message_actions": "Действия с сообщением", - "Message_pinned": "Сообщение прикреплено", - "Message_removed": "Сообщение удалено", - "Message_starred": "Сообщение отмечено звездой", - "Message_unstarred": "Отметка сообщения звездой удалена", - "message": "сообщение", - "messages": "сообщения", - "Message": "Сообщение", - "Messages": "Сообщения", - "Message_Reported": "Сообщение отправлено", - "Microphone_Permission_Message": "Rocket.Chat нужен доступ к вашему микрофону, чтобы вы могли отправлять аудио сообщения.", - "Microphone_Permission": "Разрешение на использование микрофона", - "Mute": "Заглушить", - "muted": "Заглушен", - "My_servers": "Мои серверы", - "N_people_reacted": "отреагировало {{n}} человек", - "N_users": "{{n}} пользователи", - "N_channels": "{{n}} каналов", - "name": "имя", - "Name": "Имя", - "Navigation_history": "История навигации", - "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_label_provided": "{{label}} не указан.", - "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": "Настройки уведомлений", - "No_available_agents_to_transfer": "Нет свободных агентов для передачи", - "Offline": "Офлайн", - "Oops": "Упс!", - "Omnichannel": "Omnichannel", - "Open_Livechats": "Чаты в Работе", - "Omnichannel_enable_alert": "Вы не доступны в Omnichannel. Хотите стать доступными?", - "Onboarding_description": "Сервер это пространство для взаимодействия вашей команды или организации. Уточните адрес сервера у вашего администратора или создайте свой сервер для команды.", - "Onboarding_join_workspace": "Присоединиться к серверу", - "Onboarding_subtitle": "За пределами Командного Взаимодействия", - "Onboarding_title": "Добро пожаловать в Rocket.Chat", - "Onboarding_join_open_description": "Присоединяйтесь к нашему открытому серверу, чтобы пообщаться с командой и сообществом Rocket.Chat.", - "Onboarding_agree_terms": "Продолжая вы соглашаетесь с правилами Rocket.Chat", - "Onboarding_less_options": "Меньше опций", - "Onboarding_more_options": "Больше опций", - "Online": "В сети", - "Only_authorized_users_can_write_new_messages": "Только авторизованные пользователи могут писать новые сообщения", - "Open_emoji_selector": "Открыть выбор emoji", - "Open_Source_Communication": "Общение с открытым кодом", - "Open_your_authentication_app_and_enter_the_code": "Откройте ваше приложение для аутентификации и введите код и него.", - "OR": "ИЛИ", - "OS": "ОС", - "Overwrites_the_server_configuration_and_use_room_config": "Перезаписывает конфигурацию сервера и использует конфигурацию чата", - "Password": "Пароль", - "Parent_channel_or_group": "Родительский канал или чат", - "Permalink_copied_to_clipboard": "Постоянная ссылка скопирована в буфер обмена!", - "Phone": "Телефон", - "Pin": "Прикрепить сообщение", - "Pinned_Messages": "Прикрепленные сообщения", - "pinned": "прикреплено", - "Pinned": "Прикреплено", - "Please_add_a_comment": "Пожалуйста добавьте комментарий", - "Please_enter_your_password": "Пожалуйста введите ваш пароль", - "Please_wait": "Пожалуйста подождите.", - "Preferences": "Настройки", - "Preferences_saved": "Настройки сохранены!", - "Privacy_Policy": " Политика конфиденциальности", - "Private_Channel": "Приватный канал", - "Private": "Приватный", - "Processing": "Обработка...", - "Profile_saved_successfully": "Профиль успешно сохранен!", - "Profile": "Профиль", - "Public_Channel": "Публичный канал", - "Public": "Публичный", - "Push_Notifications": "Push Уведомления", - "Push_Notifications_Alert_Info": "Эти уведомления доставляются вам, когда приложение не открыто", - "Quote": "Цитата", - "Reactions_are_disabled": "Реакции отключены", - "Reactions_are_enabled": "Реакции активированы", - "Reactions": "Реакции", - "Read": "Читать", - "Read_External_Permission_Message": "Rocket.Chat необходим доступ к фотографиям, медиа и другим файлам на вашем устройстве", - "Read_External_Permission": "Разрешение на Чтение Медиа", - "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": "СБРОС", - "Return": "Возврат", - "Review_app_title": "Нравится ли вам это приложение?", - "Review_app_desc": "Поставьте нам 5 звезд в {{store}}", - "Review_app_yes": "Конечно!", - "Review_app_no": "Нет", - "Review_app_later": "Может позже", - "Review_app_unable_store": "Невозможно открыть {{store}}", - "Review_this_app": "Оценить это приложение", - "Remove": "Удалить", - "remove": "удалить", - "Roles": "Роли", - "Room_actions": "Действия с чатом", - "Room_changed_announcement": "Объявление чата было изменено на: {{announcement}} пользователем {{userBy}}", - "Room_changed_avatar": "Аватар чата изменен пользователем {{userBy}}", - "Room_changed_description": "Описание чата было изменено на: {{description}} пользователем {{userBy}}", - "Room_changed_privacy": "Тип чата был изменен на: {{type}} пользователем {{userBy}}", - "Room_changed_topic": "Тема чата была изменена на: {{topic}} пользователем {{userBy}}", - "Room_Files": "Файлы", - "Room_Info_Edit": "Изменить информацию о чате", - "Room_Info": "Информация о канале", - "Room_Members": "Пользователи", - "Room_name_changed": "Название чата было изменено на: {{name}} пользователем {{userBy}}", - "SAVE": "СОХРАНИТЬ", - "Save_Changes": "Сохранить изменения", - "Save": "Сохранить", - "Saved": "Сохранено", - "saving_preferences": "сохранение персональных настроек", - "saving_profile": "сохранение профиля", - "saving_settings": "сохранение настроек", - "saved_to_gallery": "Сохранено в Галерею", - "Save_Your_E2E_Password": "Сохранить Ваш E2E Пароль", - "Save_Your_Encryption_Password": "Сохранить Ваш Пароль Шифрования", - "Save_Your_Encryption_Password_warning": "Этот пароль не сохраняется нигде, поэтому бережно сохраните его в надежном месте.", - "Save_Your_Encryption_Password_info": "Имейте ввиду, что если вы потеряете свой пароль, его будет невозможно восстановить и вы потеряете доступ к вашим сообщениям.", - "Search_Messages": "Поиск сообщений", - "Search": "Поиск", - "Search_by": "Поиск по", - "Search_global_users": "Глобальный поиск пользователей", - "Search_global_users_description": "При активации станет возможен поиск пользователей на других серверах.", - "Seconds": "{{second}} секунд", - "Security_and_privacy": "Безопасность и конфиденциальность", - "Select_Avatar": "Выбор аватара", - "Select_Server": "Выбор сервера", - "Select_Users": "Выбор пользователей", - "Select_a_Channel": "Выбор Канала", - "Select_a_Department": "Выбор отдела", - "Select_an_option": "Выбор Опции", - "Select_a_User": "Выбор Пользователя", - "Send": "Отправить", - "Send_audio_message": "Отправить аудиосообщение", - "Send_crash_report": "Отправить отчет об ошибке", - "Send_message": "Отправить сообщение", - "Send_me_the_code_again": "Отправить мне код снова", - "Send_to": "Отправить...", - "Sending_to": "Отправляется", - "Sent_an_attachment": "Отправить вложение", - "Server": "Сервер", - "Servers": "Серверы", - "Server_version": "Версия сервера: {{version}}", - "Set_username_subtitle": "Имя пользователя необходимо для того, чтобы позволить другим упомянуть вас в сообщениях", - "Set_custom_status": "Установить персонализированный статус", - "Set_status": "Установить статус", - "Status_saved_successfully": "Статус установлен успешно!", - "Settings": "Настройки", - "Settings_succesfully_changed": "Настройки успешно изменены!", - "Share": "Поделиться", - "Share_Link": "Ссылка, чтобы Поделиться", - "Share_this_app": "Рассказать о приложении", - "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": "Начало разговора", - "Start_a_Discussion": "Начать Обсуждение", - "Started_discussion": "Началось обсуждение :", - "Started_call": "Звонок, начат {{userBy}}", - "Submit": "Отправить", - "Table": "Таблица", - "Tags": "Тэги", - "Take_a_photo": "Сделать фото", - "Take_a_video": "Записать видео", - "Take_it": "Снять!", - "tap_to_change_status": "нажмите для изменения статуса", - "Tap_to_view_servers_list": "Нажмите, чтобы просмотреть список серверов", - "Terms_of_Service": " Условия использования ", - "Theme": "Тема", - "The_user_wont_be_able_to_type_in_roomName": "Пользователь не сможет писать сообщения в {{roomName}}", - "The_user_will_be_able_to_type_in_roomName": "Пользователь сможет писать сообщения в {{roomName}}", - "There_was_an_error_while_action": "Произошла ошибка в процессе {{action}}!", - "This_room_is_blocked": "Этот чат заблокирован", - "This_room_is_read_only": "Этот чат доступен только для чтения", - "Thread": "Тред", - "Threads": "Треды", - "Timezone": "Часовой пояс", - "To": "К", - "topic": "тема", - "Topic": "Тема", - "Translate": "Перевести", - "Try_again": "Попробуйте еще раз", - "Two_Factor_Authentication": "Двухфакторная аутентификация", - "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": "Загрузить файл?", - "User": "Пользователь", - "Users": "Пользователи", - "User_added_by": "Пользователь {{userAdded}} добавлен по решению {{userBy}}", - "User_Info": "Информация о пользователе", - "User_has_been_key": "Пользователь был {{key}}", - "User_is_no_longer_role_by_": "{{user}} больше не {{role}} по решению {{userBy}}", - "User_muted_by": "Пользователь {{userMuted}} заглушен по решению {{userBy}}", - "User_removed_by": "Пользователь {{userRemoved}} удален по решению {{userBy}}", - "User_sent_an_attachment": "{{user}} отправил вложение", - "User_unmuted_by": "Пользователь {{userUnmuted}} перестал быть заглушенным по решению {{userBy}}", - "User_was_set_role_by_": "{{user}} был назначен {{role}} пользователем {{userBy}}", - "Username_is_empty": "Имя пользователя пусто", - "Username": "Имя пользователя", - "Username_or_email": "Имя пользователя или email", - "Uses_server_configuration": "Используется конфигурация сервера", - "Validating": "Проверка", - "Registration_Succeeded": "Регистрация Успешна!", - "Verify": "Проверить", - "Verify_email_title": "Регистрация Успешна!", - "Verify_email_desc": "Вам был отправлен email для подтверждения регистрации. Если вы не получили этого сообщения, пожалуйста, попробуйте еще раз.", - "Verify_your_email_for_the_code_we_sent": "Проверка вашего email с помощью отправленного нами кода", - "Video_call": "Видеозвонок", - "View_Original": "Посмотреть оригинал", - "Voice_call": "Голосовой вызов", - "Waiting_for_network": "Ожидание сети...", - "Websocket_disabled": "Websocket отключен для этого сервера.\n{{contact}}", - "Welcome": "Добро пожаловать,", - "What_are_you_doing_right_now": "Что вы делаете сейчас?", - "Whats_your_2fa": "Какой у вас код 2FA?", - "Without_Servers": "Без серверов", - "Workspaces": "Серверы", - "Would_you_like_to_return_the_inquiry": "Вы хотите отозвать запрос?", - "Write_External_Permission_Message": "Rocket.Chat необходим доступ к ваше Галерее, чтобы Вы могли сохранять изображения.", - "Write_External_Permission": "Разрешения на запись в Галерею", - "Yes": "Да", - "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_were_removed_from_channel": "Вы были удалены из {{channel}}", - "you": "вы", - "You": "Вы", - "Logged_out_by_server": "Сервером произведен ваш выход из системы. Пожалуйста, войдите снова.", - "You_need_to_access_at_least_one_RocketChat_server_to_share_something": "Вам нужно получить доступ как минимум к одному серверу Rocket.Chat, чтобы поделиться чем-то.", - "You_need_to_verifiy_your_email_address_to_get_notications": "Вам необходимо проверить ваш email адрес, чтобы получать уведомления", - "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": "Ваша ссылка-приглашение никогда не будет просроченной.", - "Your_workspace": "Ваш сервер", - "Your_password_is": "Ваш пароль", - "Version_no": "Версия: {{version}}", - "You_will_not_be_able_to_recover_this_message": "Вы не сможете восстановить это сообщение!", - "You_will_unset_a_certificate_for_this_server": "Вы произведете сброс сертификата для этого сервера", - "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": "Ответить на последнее", - "Reply_in_Thread": "Ответить в Треде", - "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": "Это очистит все ваши офлайн данные.", - "This_will_remove_all_data_from_this_server": "Это удалит все данные с этого сервера.", - "Mark_unread": "Отметить Непрочитанным", - "Wait_activation_warning": "До того как вы сможете войти, ваш аккаунт должен быть вручную активирован администратором сервера.", - "Screen_lock": "Блокировка экрана", - "Local_authentication_biometry_title": "Аутентификация", - "Local_authentication_biometry_fallback": "Использовать пароль", - "Local_authentication_unlock_option": "Разблокировать при помощи Пароля", - "Local_authentication_change_passcode": "Изменить Пароль", - "Local_authentication_info": "Внимание: Если вы забудете ваш Пароль, вам нужно будет удалить и заново установить это приложение.", - "Local_authentication_facial_recognition": "распознавания лица", - "Local_authentication_fingerprint": "отпечатка пальца", - "Local_authentication_unlock_with_label": "Разблокировать при помощи {{label}}", - "Local_authentication_auto_lock_60": "Через 1 минуту", - "Local_authentication_auto_lock_300": "Через 5 минут", - "Local_authentication_auto_lock_900": "Через 15 минут", - "Local_authentication_auto_lock_1800": "Через 30 минут", - "Local_authentication_auto_lock_3600": "Через 1 час", - "Passcode_enter_title": "Введите ваш пароль", - "Passcode_choose_title": "Выберите ваш новый пароль", - "Passcode_choose_confirm_title": "Подтвердите ваш новый пароль", - "Passcode_choose_error": "Пароли не соответствуют. Попробуйте еще раз.", - "Passcode_choose_force_set": "Пароль затребован вашим администратором", - "Passcode_app_locked_title": "Приложение заблокировано", - "Passcode_app_locked_subtitle": "Попробуйте снова через {{timeLeft}} секунд", - "After_seconds_set_by_admin": "Через {{seconds}} секунд (установлено администратором сервера)", - "Dont_activate": "Не активировать сейчас", - "Queued_chats": "Чаты в очереди", - "Queue_is_empty": "Очередь пуста", - "Logout_from_other_logged_in_locations": "Выйти из всех других подключенных расположений", - "You_will_be_logged_out_from_other_locations": "Будет произведен ваш выход из всех других подключенных расположений.", - "Logged_out_of_other_clients_successfully": "Выход из других клиентских подключений выполнен успешно", - "Logout_failed": "Выход не успешен!", - "Log_analytics_events": "Журнал событий аналитики", - "E2E_encryption_change_password_title": "Изменить пароль шифрования", - "E2E_encryption_change_password_description": "Теперь вы можете создавать зашифрованные приватные чаты и личные беседы. Вы так же можете сделать существующие приватные чаты и личные беседы шифрованными. \nЭто сквозное шифрование, поэтому ключ для шифрования и дешифрования ваших сообщений не будет сохранен на сервере. Именно поэтому вам необходимо сохранить ваш пароль в надежном и безопасном месте. Вам необходимо вводить этот пароль на всех устройствах, где вы хотите использовать E2E шифрование.", - "E2E_encryption_change_password_error": "Ошибка при смене пароля E2E ключа!", - "E2E_encryption_change_password_success": "Пароль ключа E2E изменен успешно!", - "E2E_encryption_change_password_message": "Убедитесь, что вы сохранили пароль в надежном месте.", - "E2E_encryption_change_password_confirmation": "Да, изменить его", - "E2E_encryption_reset_title": "Сбросить E2E ключ", - "E2E_encryption_reset_description": "Эта опция удалит ваш текущий E2E ключ и произведет ваш выход из системы. \nКогда вы снова войдете в систему, Rocket.Chat сгенерирует для вас новый ключ и восстановит ваш доступ ко всем зашифрованным чатам, в которых есть пользователи в сети. \nИсходя из природы E2E шифрования, Rocket.Chat не сможет восстановить доступ к чатам, в которых нет участников в сети.", - "E2E_encryption_reset_button": "Сбросить E2E ключ", - "E2E_encryption_reset_error": "Ошибка при сбросе E2E ключа!", - "E2E_encryption_reset_message": "Будет совершен ваш выход из системы.", - "E2E_encryption_reset_confirmation": "Да, сбросить его", - "Following": "Следить", - "Threads_displaying_all": "Показать все", - "Threads_displaying_following": "Показать отслеживаемые", - "Threads_displaying_unread": "Показать непрочитанные", - "No_threads": "Тредов нет", - "No_threads_following": "Нет тредов, за которыми вы следите", - "No_threads_unread": "Непрочитанных тредов нет", - "Messagebox_Send_to_channel": "Отправить в чат", - "Leader": "Лидер", - "Moderator": "Модератор", - "Owner": "Владелец", - "Remove_from_room": "Удалить из чата", - "Ignore": "Игнориновать", - "Unignore": "Прекратить игнорировать", - "User_has_been_ignored": "Пользователь теперь игнорируется", - "User_has_been_unignored": "Пользователь больше не игнорируется", - "User_has_been_removed_from_s": "Пользователь удален из {{s}}", - "User__username__is_now_a_leader_of__room_name_": "Пользователь {{username}} больше не лидер в чате {{room_name}}", - "User__username__is_now_a_moderator_of__room_name_": "Пользователь {{username}} больше не модератор в чате {{room_name}}", - "User__username__is_now_a_owner_of__room_name_": "Пользователь {{username}} больше не владелец в чате {{room_name}}", - "User__username__removed_from__room_name__leaders": "Пользователь {{username}} удален из {{room_name}} лидеров", - "User__username__removed_from__room_name__moderators": "Пользователь {{username}} удален из {{room_name}} модераторов", - "User__username__removed_from__room_name__owners": "Пользователь {{username}} удален из {{room_name}} владельцев", - "The_user_will_be_removed_from_s": "Пользователь будет удален из {{s}}", - "Yes_remove_user": "Да, удалить пользователя!", - "Direct_message": "Личное сообщение", - "Message_Ignored": "Сообщение игнорируется. Тапните по нему, чтобы отобразить его.", - "Enter_workspace_URL": "Введите URL вашего рабочего пространства", - "Workspace_URL_Example": "Например, your-company.rocket.chat", - "This_room_encryption_has_been_enabled_by__username_": "Шифрование для этого чата включено {{username}}", - "This_room_encryption_has_been_disabled_by__username_": "Шифрование для этого чата выключено {{username}}", - "Teams": "Команды", - "No_team_channels_found": "Каналы не найдены", - "Team_not_found": "Команда не найдена", - "Create_Team": "Создать Команду", - "Team_Name": "Имя Команды", - "Private_Team": "Приватная Команда", - "Read_Only_Team": "Команда только для чтения", - "Broadcast_Team": "Широковещательная Команда", - "creating_team": "создание Команды", - "team-name-already-exists": "Команда с таким названием уже существует", - "Add_Channel_to_Team": "Добавить канал в Команду", - "Left_The_Team_Successfully": "Успешно покинул команду", - "Create_New": "Создать", - "Add_Existing": "Добавить существующее", - "Add_Existing_Channel": "Добавить существующий канал", - "Remove_from_Team": "Удалить из Команды", - "Auto-join": "Автодобавление", - "Remove_Team_Room_Warning": "Хотите ли вы удалить этот канал из Команды? Канал будет перемещен обратно в рабочее пространство", - "Confirmation": "Подтверждение", - "invalid-room": "Такого канала не существует", - "You_are_leaving_the_team": "Вы покидаете Команду '{{team}}'", - "Leave_Team": "Покинуть команду", - "Select_Team": "Выберите Команду", - "Select_Team_Channels": "Выберите каналы Команды, которые вы хотите покинуть.", - "Cannot_leave": "Невозможно выйти", - "Cannot_remove": "Невозможно удалить", - "Cannot_delete": "Невозможно удалить", - "Last_owner_team_room": "Вы последний владелец этого чата. Как только вы покинете Команду, чат будет храниться внутри нее, но вы будете управлять ею снаружи.", - "last-owner-can-not-be-removed": "Последний владелец не может быть удален", - "Remove_User_Teams": "Выберите каналы, из которых вы хотите удалить пользователя.", - "Delete_Team": "Удалить Команду", - "Select_channels_to_delete": "Это нельзя отменить. После удаления Команды все содержимое чата и конфигурация будут удалены \n\nВыберите каналы, которые вы хотите удалить. Те, которые вы решите оставить, будут доступны в вашем рабочем пространстве. Обратите внимание, что публичные каналы по-прежнему будут открытыми и видимыми для всех.", - "You_are_deleting_the_team": "Вы удаляете эту Команду.", - "Removing_user_from_this_team": "Вы удаляете {{user}} из этой Команды", - "Remove_User_Team_Channels": "Выберите каналы, из которых вы хотите удалить пользователя.", - "Remove_Member": "Удалить участника", - "leaving_team": "выход из Команды", - "removing_team": "удаление из Команды", - "moving_channel_to_team": "перемещение канала в Команду", - "deleting_team": "удаление Команды", - "member-does-not-exist": "Участник не существует", - "Convert": "Конвертировать", - "Convert_to_Team": "Конвертировать в команду", - "Convert_to_Team_Warning": "Это нельзя отменить. После преобразования канала в Команду, вы не сможете преобразовать его обратно в канал.", - "Move_to_Team": "Перенести в команду", - "Move_Channel_Paragraph": "Перемещение канала внутрь Команды означает, что этот канал будет добавлен в контекст Команды, однако все участники канала, которые не являются членами соответствующей Команды, по-прежнему будут иметь доступ к этому каналу, но не будут добавлены как участники Команды \n\nВсе управление каналом по-прежнему будет осуществляться владельцами этого канала.\n\nЧлены Команды и даже владельцы Команды, если они не являются членами этого канала, не могут иметь доступ к содержимому канала \n\nОбратите внимание, что владелец Команды сможет удалять участников с канала.", - "Move_to_Team_Warning": "После прочтения предыдущих инструкций об этом поведении, вы все еще хотите переместить этот канал в выбранную Команду?", - "Load_More": "Загрузить еще", - "Load_Newer": "Загрузить более позднее", - "Load_Older": "Загрузить более раннее", - "Left_The_Room_Successfully": "Успешно покинул комнату", - "Deleted_The_Team_Successfully": "Команда успешно удалена", - "Deleted_The_Room_Successfully": "Комната успешно удалена", - "Convert_to_Channel": "Преобразовать в канал", - "Converting_Team_To_Channel": "Преобразование Команды в канал", - "Select_Team_Channels_To_Delete": "Выберите каналы Команды, которые вы хотите удалить, те, которые вы не выбрали, будут перемещены в рабочую область \n\nОбратите внимание, что публичные каналы будут открытыми и видимыми для всех.", - "You_are_converting_the_team": "Вы преобразуете эту Команду в канал" -} \ No newline at end of file + "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-could-not-change-status": "Не удалось изменить статус", + "error-delete-protected-role": "Не удается удалить защищенную роль", + "error-department-not-found": "Отдел не найден", + "error-direct-message-file-upload-not-allowed": "Общий доступ к файлам не разрешен в личных сообщениях", + "error-duplicate-channel-name": "Канал с именем {{room_name}} существует", + "error-email-domain-blacklisted": "Домен электронной почты включен в черный список", + "error-email-send-failed": "Ошибка при попытке отправить электронное письмо: {{message}}", + "error-save-image": "Ошибка при попытке сохранить изображение", + "error-save-video": "Ошибка при попытке сохранить видео", + "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": "Неверный адрес электронной почты {{email}}", + "error-invalid-email-address": "Неверный адрес электронной почты", + "error-invalid-file-height": "Недопустимая высота файла", + "error-invalid-file-type": "Неверный тип файла", + "error-invalid-file-width": "Недопустимая ширина файла", + "error-invalid-from-address": "Вы указали неверный адрес FROM.", + "error-invalid-integration": "Недопустимая интеграция", + "error-invalid-message": "Недопустимое сообщение", + "error-invalid-method": "Недопустимый метод", + "error-invalid-name": "Недопустимое имя", + "error-invalid-password": "Неверный пароль", + "error-invalid-redirectUri": "Недопустимый redirectUri", + "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": "URL-адрес ответил статусом, отличным от 200", + "error-message-deleting-blocked": "Удаление сообщений заблокировано", + "error-message-editing-blocked": "Правка сообщений заблокирована", + "error-message-size-exceeded": "Размер сообщения превышает максимально разрешенный", + "error-missing-unsubscribe-link": "Вы должны указать ссылку [отписаться].", + "error-no-owner-channel": "Вы не являетесь владельцем данного чата", + "error-no-tokens-for-this-user": "Для этого пользователя нет токенов", + "error-not-allowed": "Не допускается", + "error-not-authorized": "Не разрешено", + "error-push-disabled": "Push отключен", + "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": "Вы последний владелец. Пожалуйста, назначьте нового владельца, прежде чем покинуть чат.", + "error-status-not-allowed": "Статус Невидимый отключён", + "Actions": "Действия", + "activity": "активности", + "Activity": "По активности", + "Add_Reaction": "Добавить реакцию", + "Add_Server": "Добавить сервер", + "Add_users": "Добавить пользователей", + "Admin_Panel": "Панель админа", + "Agent": "Агент", + "Alert": "Оповещение", + "alert": "оповещение", + "alerts": "оповещения", + "All_users_in_the_channel_can_write_new_messages": "Все пользователи канала могут писать новые сообщения", + "All_users_in_the_team_can_write_new_messages": "Все пользователи в Команде могут писать новые сообщения", + "A_meaningful_name_for_the_discussion_room": "Осмысленное имя для обсуждения", + "All": "Все", + "All_Messages": "Все сообщения", + "Allow_Reactions": "Разрешить реакции", + "Alphabetical": "По алфавиту", + "and_more": "и более", + "and": "и", + "announcement": "объявление", + "Announcement": "Объявление", + "Apply_Your_Certificate": "Применить ваш сертификат", + "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": "Блокировать пользователя", + "Browser": "Браузер", + "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": "Вызов уже завершен!", + "Clear_cookies_alert": "Вы действительно хотите очистить все cookies?", + "Clear_cookies_desc": "Это действие очистит все ваши cookies для входа, это позволит вам войти под другой учетной записью.", + "Clear_cookies_yes": "Да, очистить cookies", + "Clear_cookies_no": "Нет, сохранить cookies", + "Click_to_join": "Нажмите, чтобы присоединиться!", + "Close": "Закрыть", + "Close_emoji_selector": "Закрыть выбор emoji", + "Closing_chat": "Закрытие чата", + "Change_language_loading": "Изменение языка.", + "Chat_closed_by_agent": "Чат закрыт агентом", + "Choose": "Выбрать", + "Choose_from_library": "Выбрать из библиотеки", + "Choose_file": "Выбрать файл", + "Choose_where_you_want_links_be_opened": "Выберите где вы хотите открывать ссылки", + "Code": "Код", + "Code_or_password_invalid": "Код или пароль не верны", + "Collaborative": "Совместный", + "Confirm": "Подтверждение", + "Connect": "Соединение", + "Connected": "Подключено", + "connecting_server": "подключение к серверу", + "Connecting": "Соединение...", + "Contact_us": "Связаться с нами", + "Contact_your_server_admin": "Свяжитесь с администратором сервера.", + "Continue_with": "Продолжить с", + "Copied_to_clipboard": "Скопировано в буфер обмена!", + "Copy": "Копировать", + "Conversation": "Диалог", + "Permalink": "Постоянная ссылка", + "Certificate_password": "Пароль сертификата", + "Clear_cache": "Очистить локальный кэш сервера", + "Clear_cache_loading": "Очистка кэша.", + "Whats_the_password_for_your_certificate": "Какой пароль для вашего сертификата?", + "Create_account": "Создать аккаунт", + "Create_Channel": "Создать канал", + "Create_Direct_Messages": "Создать личное сообщение", + "Create_Discussion": "Создать обсуждение", + "Created_snippet": "создать сниппет", + "Create_a_new_workspace": "Новое рабочее пространство", + "Create": "Создать", + "Custom_Status": "Персонализированный Статус", + "Dark": "Темный", + "Dark_level": "Уровень затемненности", + "Default": "По умолчанию", + "Default_browser": "Браузер по умолчанию", + "Delete_Room_Warning": "Удаление канала приведет к удалению всех сообщений, размещенных в нем. Это не может быть отменено.", + "Department": "Отдел", + "delete": "удалить", + "Delete": "Удалить", + "DELETE": "УДАЛИТЬ", + "move": "переместить", + "deleting_room": "удаление чата", + "description": "описание", + "Description": "Описание", + "Desktop_Options": "Параметры рабочего стола", + "Desktop_Notifications": "Уведомления рабочего стола", + "Desktop_Alert_info": "Эти уведомления появятся на рабочем столе", + "Directory": "Директория", + "Direct_Messages": "Личные сообщения", + "Disable_notifications": "Отключить уведомления", + "Discussions": "Обсуждения", + "Discussion_Desc": "Помогают разобраться в том, что происходит! При создании Обсуждения, суб-канала одного из выбранных вами каналов так же создается и привязка к нему.", + "Discussion_name": "Имя обсуждения", + "Done": "Готово", + "Dont_Have_An_Account": "Нет аккаунта?", + "Do_you_have_an_account": "У вас есть аккаунт?", + "Do_you_have_a_certificate": "У вас есть сертификат?", + "Do_you_really_want_to_key_this_room_question_mark": "Вы действительно хотите {{key}} этот канал?", + "E2E_Encryption": "E2E шифрование", + "E2E_How_It_Works_info1": "Теперь Вы можете создавать зашифрованные приватные чаты и личные сообщения. Вы так же можете изменить существующе приватные чаты и личные сообщения сделав их зашифрованными.", + "E2E_How_It_Works_info2": "Это *сквозное шифрование*, поэтому ключ для шифрования/расшифровки ваших сообщений не будет сохранен на сервере. Значит *Вам нужно сохранить этот пароль где-то в безопасном месте*, где он будет Вам доступен, если потребуется.", + "E2E_How_It_Works_info3": "В случаем продолжения, пароль Е2Е будет сгенерирован автоматически.", + "E2E_How_It_Works_info4": "Так же Вы можете задать новый пароль для вашего ключа шифрования в любое время при помощи браузера там же, где Вы ввели существующий Е2Е пароль.", + "edit": "править", + "edited": "исправлено", + "Edit": "Правка", + "Edit_Status": "Изменить статус", + "Edit_Invite": "Редактировать Приглашение", + "End_to_end_encrypted_room": "Чат со сквозным шифрованием", + "end_to_end_encryption": "сквозное шифрование", + "Email_Notification_Mode_All": "Каждое Упоминание/Личное сообщение", + "Email_Notification_Mode_Disabled": "Отключено", + "Email_or_password_field_is_empty": "Поле электронной почты или пароля пусты", + "Email": "E-mail", + "email": "e-mail", + "Empty_title": "Пустой заголовок", + "Enable_Auto_Translate": "Включить автоперевод", + "Enable_notifications": "Включить уведомления", + "Encrypted": "Зашифрован", + "Encrypted_message": "Зашифрованное сообщение", + "Enter_Your_E2E_Password": "Введите Ваш E2E Пароль", + "Enter_Your_Encryption_Password_desc1": "Вы сможете получить доступ к вашим зашифрованным приватным чатам и личным сообщениям.", + "Enter_Your_Encryption_Password_desc2": "Вам нужно ввести пароль для шифрования/расшифровки сообщений в каждом клиенте.", + "Encryption_error_title": "Введен не верный пароль шифрования", + "Encryption_error_desc": "Невозможно расшифровать ваш ключ шифрования, чтобы импортировать его", + "Everyone_can_access_this_channel": "Каждый может получить доступ к этому каналу", + "Everyone_can_access_this_team": "Каждый может получить доступ к этой Команде", + "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_password_If_this_email_is_registered": "Если эта электронная почта зарегистрирована, мы отправим инструкции о том, как сбросить пароль. Если вы не получите письмо в ближайшее время, вернитесь и повторите попытку.", + "Forgot_password": "Забыли пароль", + "Forgot_Password": "Забыли Пароль", + "Forward": "Перенаправить", + "Forward_Chat": "Перенаправить Чат", + "Forward_to_department": "Перенаправить в отдел", + "Forward_to_user": "Перенаправить пользователю", + "Full_table": "Нажмите, чтобы увидеть полную таблицу", + "Generate_New_Link": "Сгенерировать Новую Ссылку", + "Group_by_favorites": "По избранным", + "Group_by_type": "По типу", + "Hide": "Скрыть", + "Has_joined_the_channel": "присоединился к каналу", + "Has_joined_the_conversation": "присоединился к беседе", + "Has_left_the_channel": "покинул канал", + "Hide_System_Messages": "Скрыть Системные Сообщения", + "Hide_type_messages": "Скрыть \"{{type}}\" сообщения", + "How_It_Works": "Как Это Работает", + "Message_HideType_uj": "Пользователь Присоединился", + "Message_HideType_ul": "Пользователь Покинул", + "Message_HideType_ru": "Пользователь Удален", + "Message_HideType_au": "Пользователь Добавлен", + "Message_HideType_mute_unmute": "Пользователь Заглушен / Заглушивание отменено", + "Message_HideType_r": "Имя Чата Изменено", + "Message_HideType_ut": "Пользователь Присоединился к Беседе", + "Message_HideType_wm": "Добро пожаловать", + "Message_HideType_rm": "Сообщение Удалено", + "Message_HideType_subscription_role_added": "Была назначена Роль", + "Message_HideType_subscription_role_removed": "Роль более не определена", + "Message_HideType_room_archived": "Чат Архивирован", + "Message_HideType_room_unarchived": "Чат Разархивирован", + "I_Saved_My_E2E_Password": "Я Сохранил Свой E2E Пароль", + "IP": "IP", + "In_app": "В приложении", + "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": "Присоединиться", + "Join_Code": "Код присоединения", + "Insert_Join_Code": "Вставить код присоединения", + "Join_our_open_workspace": "Присоединиться к нашему открытому серверу", + "Join_your_workspace": "Присоединиться к вашему серверу", + "Just_invited_people_can_access_this_channel": "Только приглашенные люди могут получить доступ к этому каналу", + "Just_invited_people_can_access_this_team": "Только приглашенные пользователи могут получить доступ к этой Команде", + "Language": "Язык", + "last_message": "последнее сообщение", + "Leave_channel": "Покинуть канал", + "leaving_room": "покинуть комнату", + "Leave": "Покинуть комнату", + "leave": "покинуть", + "Legal": "Правовые аспекты", + "Light": "Светлая", + "License": "Лицензия", + "Livechat": "Livechat", + "Livechat_edit": "Редактирование Livechat", + "Login": "Вход", + "Login_error": "Ваши учетные данные были отклонены! Пожалуйста, попробуйте еще раз.", + "Login_with": "Войти с", + "Logging_out": "Осуществляется выход.", + "Logout": "Выйти", + "Max_number_of_uses": "Максимальное количество", + "Max_number_of_users_allowed_is_number": "Максимальное количество разрешенных пользователей {{maxUsers}}", + "members": "пользователи", + "Members": "Пользователи", + "Mentioned_Messages": "Упомянутые сообщения", + "mentioned": "упомянутые", + "Mentions": "Упоминания", + "Message_accessibility": "Сообщение от {{user}} в {{time}}: {{message}}", + "Message_actions": "Действия с сообщением", + "Message_pinned": "Сообщение прикреплено", + "Message_removed": "Сообщение удалено", + "Message_starred": "Сообщение отмечено звездой", + "Message_unstarred": "Отметка сообщения звездой удалена", + "message": "сообщение", + "messages": "сообщения", + "Message": "Сообщение", + "Messages": "Сообщения", + "Message_Reported": "Сообщение отправлено", + "Microphone_Permission_Message": "Rocket.Chat нужен доступ к вашему микрофону, чтобы вы могли отправлять аудио сообщения.", + "Microphone_Permission": "Разрешение на использование микрофона", + "Mute": "Заглушить", + "muted": "Заглушен", + "My_servers": "Мои серверы", + "N_people_reacted": "отреагировало {{n}} человек", + "N_users": "{{n}} пользователи", + "N_channels": "{{n}} каналов", + "name": "имя", + "Name": "Имя", + "Navigation_history": "История навигации", + "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_label_provided": "{{label}} не указан.", + "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": "Настройки уведомлений", + "No_available_agents_to_transfer": "Нет свободных агентов для передачи", + "Offline": "Офлайн", + "Oops": "Упс!", + "Omnichannel": "Omnichannel", + "Open_Livechats": "Чаты в Работе", + "Omnichannel_enable_alert": "Вы не доступны в Omnichannel. Хотите стать доступными?", + "Onboarding_description": "Сервер это пространство для взаимодействия вашей команды или организации. Уточните адрес сервера у вашего администратора или создайте свой сервер для команды.", + "Onboarding_join_workspace": "Присоединиться к серверу", + "Onboarding_subtitle": "За пределами Командного Взаимодействия", + "Onboarding_title": "Добро пожаловать в Rocket.Chat", + "Onboarding_join_open_description": "Присоединяйтесь к нашему открытому серверу, чтобы пообщаться с командой и сообществом Rocket.Chat.", + "Onboarding_agree_terms": "Продолжая вы соглашаетесь с правилами Rocket.Chat", + "Onboarding_less_options": "Меньше опций", + "Onboarding_more_options": "Больше опций", + "Online": "В сети", + "Only_authorized_users_can_write_new_messages": "Только авторизованные пользователи могут писать новые сообщения", + "Open_emoji_selector": "Открыть выбор emoji", + "Open_Source_Communication": "Общение с открытым кодом", + "Open_your_authentication_app_and_enter_the_code": "Откройте ваше приложение для аутентификации и введите код и него.", + "OR": "ИЛИ", + "OS": "ОС", + "Overwrites_the_server_configuration_and_use_room_config": "Перезаписывает конфигурацию сервера и использует конфигурацию чата", + "Password": "Пароль", + "Parent_channel_or_group": "Родительский канал или чат", + "Permalink_copied_to_clipboard": "Постоянная ссылка скопирована в буфер обмена!", + "Phone": "Телефон", + "Pin": "Прикрепить сообщение", + "Pinned_Messages": "Прикрепленные сообщения", + "pinned": "прикреплено", + "Pinned": "Прикреплено", + "Please_add_a_comment": "Пожалуйста добавьте комментарий", + "Please_enter_your_password": "Пожалуйста введите ваш пароль", + "Please_wait": "Пожалуйста подождите.", + "Preferences": "Настройки", + "Preferences_saved": "Настройки сохранены!", + "Privacy_Policy": " Политика конфиденциальности", + "Private_Channel": "Приватный канал", + "Private": "Приватный", + "Processing": "Обработка...", + "Profile_saved_successfully": "Профиль успешно сохранен!", + "Profile": "Профиль", + "Public_Channel": "Публичный канал", + "Public": "Публичный", + "Push_Notifications": "Push Уведомления", + "Push_Notifications_Alert_Info": "Эти уведомления доставляются вам, когда приложение не открыто", + "Quote": "Цитата", + "Reactions_are_disabled": "Реакции отключены", + "Reactions_are_enabled": "Реакции активированы", + "Reactions": "Реакции", + "Read": "Читать", + "Read_External_Permission_Message": "Rocket.Chat необходим доступ к фотографиям, медиа и другим файлам на вашем устройстве", + "Read_External_Permission": "Разрешение на Чтение Медиа", + "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": "СБРОС", + "Return": "Возврат", + "Review_app_title": "Нравится ли вам это приложение?", + "Review_app_desc": "Поставьте нам 5 звезд в {{store}}", + "Review_app_yes": "Конечно!", + "Review_app_no": "Нет", + "Review_app_later": "Может позже", + "Review_app_unable_store": "Невозможно открыть {{store}}", + "Review_this_app": "Оценить это приложение", + "Remove": "Удалить", + "remove": "удалить", + "Roles": "Роли", + "Room_actions": "Действия с чатом", + "Room_changed_announcement": "Объявление чата было изменено на: {{announcement}} пользователем {{userBy}}", + "Room_changed_avatar": "Аватар чата изменен пользователем {{userBy}}", + "Room_changed_description": "Описание чата было изменено на: {{description}} пользователем {{userBy}}", + "Room_changed_privacy": "Тип чата был изменен на: {{type}} пользователем {{userBy}}", + "Room_changed_topic": "Тема чата была изменена на: {{topic}} пользователем {{userBy}}", + "Room_Files": "Файлы", + "Room_Info_Edit": "Изменить информацию о чате", + "Room_Info": "Информация о канале", + "Room_Members": "Пользователи", + "Room_name_changed": "Название чата было изменено на: {{name}} пользователем {{userBy}}", + "SAVE": "СОХРАНИТЬ", + "Save_Changes": "Сохранить изменения", + "Save": "Сохранить", + "Saved": "Сохранено", + "saving_preferences": "сохранение персональных настроек", + "saving_profile": "сохранение профиля", + "saving_settings": "сохранение настроек", + "saved_to_gallery": "Сохранено в Галерею", + "Save_Your_E2E_Password": "Сохранить Ваш E2E Пароль", + "Save_Your_Encryption_Password": "Сохранить Ваш Пароль Шифрования", + "Save_Your_Encryption_Password_warning": "Этот пароль не сохраняется нигде, поэтому бережно сохраните его в надежном месте.", + "Save_Your_Encryption_Password_info": "Имейте ввиду, что если вы потеряете свой пароль, его будет невозможно восстановить и вы потеряете доступ к вашим сообщениям.", + "Search_Messages": "Поиск сообщений", + "Search": "Поиск", + "Search_by": "Поиск по", + "Search_global_users": "Глобальный поиск пользователей", + "Search_global_users_description": "При активации станет возможен поиск пользователей на других серверах.", + "Seconds": "{{second}} секунд", + "Security_and_privacy": "Безопасность и конфиденциальность", + "Select_Avatar": "Выбор аватара", + "Select_Server": "Выбор сервера", + "Select_Users": "Выбор пользователей", + "Select_a_Channel": "Выбор Канала", + "Select_a_Department": "Выбор отдела", + "Select_an_option": "Выбор Опции", + "Select_a_User": "Выбор Пользователя", + "Send": "Отправить", + "Send_audio_message": "Отправить аудиосообщение", + "Send_crash_report": "Отправить отчет об ошибке", + "Send_message": "Отправить сообщение", + "Send_me_the_code_again": "Отправить мне код снова", + "Send_to": "Отправить...", + "Sending_to": "Отправляется", + "Sent_an_attachment": "Отправить вложение", + "Server": "Сервер", + "Servers": "Серверы", + "Server_version": "Версия сервера: {{version}}", + "Set_username_subtitle": "Имя пользователя необходимо для того, чтобы позволить другим упомянуть вас в сообщениях", + "Set_custom_status": "Установить персонализированный статус", + "Set_status": "Установить статус", + "Status_saved_successfully": "Статус установлен успешно!", + "Settings": "Настройки", + "Settings_succesfully_changed": "Настройки успешно изменены!", + "Share": "Поделиться", + "Share_Link": "Ссылка, чтобы Поделиться", + "Share_this_app": "Рассказать о приложении", + "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": "Начало разговора", + "Start_a_Discussion": "Начать обсуждение", + "Started_discussion": "Началось обсуждение:", + "Started_call": "Звонок, начат {{userBy}}", + "Submit": "Отправить", + "Table": "Таблица", + "Tags": "Тэги", + "Take_a_photo": "Сделать фото", + "Take_a_video": "Записать видео", + "Take_it": "Снять!", + "tap_to_change_status": "нажмите для изменения статуса", + "Tap_to_view_servers_list": "Нажмите, чтобы просмотреть список серверов", + "Terms_of_Service": " Условия использования ", + "Theme": "Тема", + "The_user_wont_be_able_to_type_in_roomName": "Пользователь не сможет писать сообщения в {{roomName}}", + "The_user_will_be_able_to_type_in_roomName": "Пользователь сможет писать сообщения в {{roomName}}", + "There_was_an_error_while_action": "Произошла ошибка в процессе {{action}}!", + "This_room_is_blocked": "Этот чат заблокирован", + "This_room_is_read_only": "Этот чат доступен только для чтения", + "Thread": "Тред", + "Threads": "Треды", + "Timezone": "Часовой пояс", + "To": "К", + "topic": "тема", + "Topic": "Тема", + "Translate": "Перевести", + "Try_again": "Попробуйте еще раз", + "Two_Factor_Authentication": "Двухфакторная аутентификация", + "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": "Загрузить файл?", + "User": "Пользователь", + "Users": "Пользователи", + "User_added_by": "Пользователь {{userAdded}} добавлен по решению {{userBy}}", + "User_Info": "Информация о пользователе", + "User_has_been_key": "Пользователь был {{key}}", + "User_is_no_longer_role_by_": "{{user}} больше не {{role}} по решению {{userBy}}", + "User_muted_by": "Пользователь {{userMuted}} заглушен по решению {{userBy}}", + "User_removed_by": "Пользователь {{userRemoved}} удален по решению {{userBy}}", + "User_sent_an_attachment": "{{user}} отправил вложение", + "User_unmuted_by": "Пользователь {{userUnmuted}} перестал быть заглушенным по решению {{userBy}}", + "User_was_set_role_by_": "{{user}} был назначен {{role}} пользователем {{userBy}}", + "Username_is_empty": "Имя пользователя пусто", + "Username": "Имя пользователя", + "Username_or_email": "Имя пользователя или email", + "Uses_server_configuration": "Используется конфигурация сервера", + "Validating": "Проверка", + "Registration_Succeeded": "Регистрация Успешна!", + "Verify": "Проверить", + "Verify_email_title": "Регистрация Успешна!", + "Verify_email_desc": "Вам был отправлен email для подтверждения регистрации. Если вы не получили этого сообщения, пожалуйста, попробуйте еще раз.", + "Verify_your_email_for_the_code_we_sent": "Проверка вашего email с помощью отправленного нами кода", + "Video_call": "Видеозвонок", + "View_Original": "Посмотреть оригинал", + "Voice_call": "Голосовой вызов", + "Waiting_for_network": "Ожидание сети...", + "Websocket_disabled": "Websocket отключен для этого сервера.\n{{contact}}", + "Welcome": "Добро пожаловать,", + "What_are_you_doing_right_now": "Что вы делаете сейчас?", + "Whats_your_2fa": "Какой у вас код 2FA?", + "Without_Servers": "Без серверов", + "Workspaces": "Серверы", + "Would_you_like_to_return_the_inquiry": "Вы хотите отозвать запрос?", + "Write_External_Permission_Message": "Rocket.Chat необходим доступ к ваше Галерее, чтобы Вы могли сохранять изображения.", + "Write_External_Permission": "Разрешения на запись в Галерею", + "Yes": "Да", + "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_were_removed_from_channel": "Вы были удалены из {{channel}}", + "you": "вы", + "You": "Вы", + "Logged_out_by_server": "Сервером произведен ваш выход из системы. Пожалуйста, войдите снова.", + "You_need_to_access_at_least_one_RocketChat_server_to_share_something": "Вам нужно получить доступ как минимум к одному серверу Rocket.Chat, чтобы поделиться чем-то.", + "You_need_to_verifiy_your_email_address_to_get_notications": "Вам необходимо проверить ваш email адрес, чтобы получать уведомления", + "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": "Ваша ссылка-приглашение никогда не будет просроченной.", + "Your_workspace": "Ваш сервер", + "Your_password_is": "Ваш пароль", + "Version_no": "Версия: {{version}}", + "You_will_not_be_able_to_recover_this_message": "Вы не сможете восстановить это сообщение!", + "You_will_unset_a_certificate_for_this_server": "Вы произведете сброс сертификата для этого сервера", + "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": "Ответить на последнее", + "Reply_in_Thread": "Ответить в Треде", + "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": "Это очистит все ваши офлайн данные.", + "This_will_remove_all_data_from_this_server": "Это удалит все данные с этого сервера.", + "Mark_unread": "Отметить Непрочитанным", + "Wait_activation_warning": "До того как вы сможете войти, ваш аккаунт должен быть вручную активирован администратором сервера.", + "Screen_lock": "Блокировка экрана", + "Local_authentication_biometry_title": "Аутентификация", + "Local_authentication_biometry_fallback": "Использовать пароль", + "Local_authentication_unlock_option": "Разблокировать при помощи Пароля", + "Local_authentication_change_passcode": "Изменить Пароль", + "Local_authentication_info": "Внимание: Если вы забудете ваш Пароль, вам нужно будет удалить и заново установить это приложение.", + "Local_authentication_facial_recognition": "распознавания лица", + "Local_authentication_fingerprint": "отпечатка пальца", + "Local_authentication_unlock_with_label": "Разблокировать при помощи {{label}}", + "Local_authentication_auto_lock_60": "Через 1 минуту", + "Local_authentication_auto_lock_300": "Через 5 минут", + "Local_authentication_auto_lock_900": "Через 15 минут", + "Local_authentication_auto_lock_1800": "Через 30 минут", + "Local_authentication_auto_lock_3600": "Через 1 час", + "Passcode_enter_title": "Введите ваш пароль", + "Passcode_choose_title": "Выберите ваш новый пароль", + "Passcode_choose_confirm_title": "Подтвердите ваш новый пароль", + "Passcode_choose_error": "Пароли не соответствуют. Попробуйте еще раз.", + "Passcode_choose_force_set": "Пароль затребован вашим администратором", + "Passcode_app_locked_title": "Приложение заблокировано", + "Passcode_app_locked_subtitle": "Попробуйте снова через {{timeLeft}} секунд", + "After_seconds_set_by_admin": "Через {{seconds}} секунд (установлено администратором сервера)", + "Dont_activate": "Не активировать сейчас", + "Queued_chats": "Чаты в очереди", + "Queue_is_empty": "Очередь пуста", + "Logout_from_other_logged_in_locations": "Выйти из всех других подключенных расположений", + "You_will_be_logged_out_from_other_locations": "Будет произведен ваш выход из всех других подключенных расположений.", + "Logged_out_of_other_clients_successfully": "Выход из других клиентских подключений выполнен успешно", + "Logout_failed": "Выход не успешен!", + "Log_analytics_events": "Журнал событий аналитики", + "E2E_encryption_change_password_title": "Изменить пароль шифрования", + "E2E_encryption_change_password_description": "Теперь вы можете создавать зашифрованные приватные чаты и личные беседы. Вы так же можете сделать существующие приватные чаты и личные беседы шифрованными. \nЭто сквозное шифрование, поэтому ключ для шифрования и дешифрования ваших сообщений не будет сохранен на сервере. Именно поэтому вам необходимо сохранить ваш пароль в надежном и безопасном месте. Вам необходимо вводить этот пароль на всех устройствах, где вы хотите использовать E2E шифрование.", + "E2E_encryption_change_password_error": "Ошибка при смене пароля E2E ключа!", + "E2E_encryption_change_password_success": "Пароль ключа E2E изменен успешно!", + "E2E_encryption_change_password_message": "Убедитесь, что вы сохранили пароль в надежном месте.", + "E2E_encryption_change_password_confirmation": "Да, изменить его", + "E2E_encryption_reset_title": "Сбросить E2E ключ", + "E2E_encryption_reset_description": "Эта опция удалит ваш текущий E2E ключ и произведет ваш выход из системы. \nКогда вы снова войдете в систему, Rocket.Chat сгенерирует для вас новый ключ и восстановит ваш доступ ко всем зашифрованным чатам, в которых есть пользователи в сети. \nИсходя из природы E2E шифрования, Rocket.Chat не сможет восстановить доступ к чатам, в которых нет участников в сети.", + "E2E_encryption_reset_button": "Сбросить E2E ключ", + "E2E_encryption_reset_error": "Ошибка при сбросе E2E ключа!", + "E2E_encryption_reset_message": "Будет совершен ваш выход из системы.", + "E2E_encryption_reset_confirmation": "Да, сбросить его", + "Following": "Следить", + "Threads_displaying_all": "Показать все", + "Threads_displaying_following": "Показать отслеживаемые", + "Threads_displaying_unread": "Показать непрочитанные", + "No_threads": "Тредов нет", + "No_threads_following": "Нет тредов, за которыми вы следите", + "No_threads_unread": "Непрочитанных тредов нет", + "Messagebox_Send_to_channel": "Отправить в чат", + "Leader": "Лидер", + "Moderator": "Модератор", + "Owner": "Владелец", + "Remove_from_room": "Удалить из чата", + "Ignore": "Игнориновать", + "Unignore": "Прекратить игнорировать", + "User_has_been_ignored": "Пользователь теперь игнорируется", + "User_has_been_unignored": "Пользователь больше не игнорируется", + "User_has_been_removed_from_s": "Пользователь удален из {{s}}", + "User__username__is_now_a_leader_of__room_name_": "Пользователь {{username}} больше не лидер в чате {{room_name}}", + "User__username__is_now_a_moderator_of__room_name_": "Пользователь {{username}} больше не модератор в чате {{room_name}}", + "User__username__is_now_a_owner_of__room_name_": "Пользователь {{username}} больше не владелец в чате {{room_name}}", + "User__username__removed_from__room_name__leaders": "Пользователь {{username}} удален из {{room_name}} лидеров", + "User__username__removed_from__room_name__moderators": "Пользователь {{username}} удален из {{room_name}} модераторов", + "User__username__removed_from__room_name__owners": "Пользователь {{username}} удален из {{room_name}} владельцев", + "The_user_will_be_removed_from_s": "Пользователь будет удален из {{s}}", + "Yes_remove_user": "Да, удалить пользователя!", + "Direct_message": "Личное сообщение", + "Message_Ignored": "Сообщение игнорируется. Тапните по нему, чтобы отобразить его.", + "Enter_workspace_URL": "Введите URL вашего рабочего пространства", + "Workspace_URL_Example": "Например, your-company.rocket.chat", + "This_room_encryption_has_been_enabled_by__username_": "Шифрование для этого чата включено {{username}}", + "This_room_encryption_has_been_disabled_by__username_": "Шифрование для этого чата выключено {{username}}", + "Teams": "Команды", + "No_team_channels_found": "Каналы не найдены", + "Team_not_found": "Команда не найдена", + "Create_Team": "Создать Команду", + "Team_Name": "Имя Команды", + "Private_Team": "Приватная Команда", + "Read_Only_Team": "Команда только для чтения", + "Broadcast_Team": "Широковещательная Команда", + "creating_team": "создание Команды", + "team-name-already-exists": "Команда с таким названием уже существует", + "Add_Channel_to_Team": "Добавить канал в Команду", + "Left_The_Team_Successfully": "Успешно покинул команду", + "Create_New": "Создать", + "Add_Existing": "Добавить существующее", + "Add_Existing_Channel": "Добавить существующий канал", + "Remove_from_Team": "Удалить из Команды", + "Auto-join": "Автодобавление", + "Remove_Team_Room_Warning": "Хотите ли вы удалить этот канал из Команды? Канал будет перемещен обратно в рабочее пространство", + "Confirmation": "Подтверждение", + "invalid-room": "Такого канала не существует", + "You_are_leaving_the_team": "Вы покидаете Команду '{{team}}'", + "Leave_Team": "Покинуть команду", + "Select_Team": "Выберите Команду", + "Select_Team_Channels": "Выберите каналы Команды, которые вы хотите покинуть.", + "Cannot_leave": "Невозможно выйти", + "Cannot_remove": "Невозможно удалить", + "Cannot_delete": "Невозможно удалить", + "Last_owner_team_room": "Вы последний владелец этого чата. Как только вы покинете Команду, чат будет храниться внутри нее, но вы будете управлять ею снаружи.", + "last-owner-can-not-be-removed": "Последний владелец не может быть удален", + "Remove_User_Teams": "Выберите каналы, из которых вы хотите удалить пользователя.", + "Delete_Team": "Удалить Команду", + "Select_channels_to_delete": "Это нельзя отменить. После удаления Команды все содержимое чата и конфигурация будут удалены \n\nВыберите каналы, которые вы хотите удалить. Те, которые вы решите оставить, будут доступны в вашем рабочем пространстве. Обратите внимание, что публичные каналы по-прежнему будут открытыми и видимыми для всех.", + "You_are_deleting_the_team": "Вы удаляете эту Команду.", + "Removing_user_from_this_team": "Вы удаляете {{user}} из этой Команды", + "Remove_User_Team_Channels": "Выберите каналы, из которых вы хотите удалить пользователя.", + "Remove_Member": "Удалить участника", + "leaving_team": "выход из Команды", + "removing_team": "удаление из Команды", + "moving_channel_to_team": "перемещение канала в Команду", + "deleting_team": "удаление Команды", + "member-does-not-exist": "Участник не существует", + "Convert": "Конвертировать", + "Convert_to_Team": "Конвертировать в команду", + "Convert_to_Team_Warning": "Это нельзя отменить. После преобразования канала в Команду, вы не сможете преобразовать его обратно в канал.", + "Move_to_Team": "Перенести в команду", + "Move_Channel_Paragraph": "Перемещение канала внутрь Команды означает, что этот канал будет добавлен в контекст Команды, однако все участники канала, которые не являются членами соответствующей Команды, по-прежнему будут иметь доступ к этому каналу, но не будут добавлены как участники Команды \n\nВсе управление каналом по-прежнему будет осуществляться владельцами этого канала.\n\nЧлены Команды и даже владельцы Команды, если они не являются членами этого канала, не могут иметь доступ к содержимому канала \n\nОбратите внимание, что владелец Команды сможет удалять участников с канала.", + "Move_to_Team_Warning": "После прочтения предыдущих инструкций об этом поведении, вы все еще хотите переместить этот канал в выбранную Команду?", + "Load_More": "Загрузить еще", + "Load_Newer": "Загрузить более позднее", + "Load_Older": "Загрузить более раннее", + "room-name-already-exists": "Имя чата уже существует", + "error-team-creation": "Ошибка создания Команды", + "unauthorized": "Неавторизованный", + "Left_The_Room_Successfully": "Успешно покинул комнату", + "Deleted_The_Team_Successfully": "Команда успешно удалена", + "Deleted_The_Room_Successfully": "Комната успешно удалена", + "Convert_to_Channel": "Преобразовать в канал", + "Converting_Team_To_Channel": "Преобразование Команды в канал", + "Select_Team_Channels_To_Delete": "Выберите каналы Команды, которые вы хотите удалить, те, которые вы не выбрали, будут перемещены в рабочую область \n\nОбратите внимание, что публичные каналы будут открытыми и видимыми для всех.", + "You_are_converting_the_team": "Вы преобразуете эту Команду в канал", + "creating_discussion": "создание обсуждения" +} diff --git a/app/i18n/locales/tr.json b/app/i18n/locales/tr.json index bc58ee92b..81a06449b 100644 --- a/app/i18n/locales/tr.json +++ b/app/i18n/locales/tr.json @@ -1,702 +1,702 @@ { - "1_person_reacted": "1 kişi tepki verdi.", - "1_user": "1 kullanıcı", - "error-action-not-allowed": "{{action}}'a izin verilmiyor!", - "error-application-not-found": "Uygulama bulunamadı!", - "error-archived-duplicate-name": "{{room_name}} adında arşivlenmiş bir kanal var!", - "error-avatar-invalid-url": "Geçersiz avatar URL'si: {{url}}", - "error-avatar-url-handling": "{{username}} için bir URL'den ({{url}}) avatar ayarı işlenirken hata oluştu!", - "error-cant-invite-for-direct-room": "Kullanıcı özel odalara davet edilemedi!", - "error-could-not-change-email": "E-posta değiştirilemedi!", - "error-could-not-change-name": "İsim değiştirilemedi!", - "error-could-not-change-username": "Kullanıcı adı değiştirilemedi!", - "error-could-not-change-status": "Durum değiştirilemedi!", - "error-delete-protected-role": "Korunan bir rol silinemez!", - "error-department-not-found": "Bölüm bulunamadı!", - "error-direct-message-file-upload-not-allowed": "Özel iletilerde dosya paylaşımına izin verilmiyor!", - "error-duplicate-channel-name": "{{channel_name}} adında bir kanal var!", - "error-email-domain-blacklisted": "E-posta alan adı kara listeye alındı!", - "error-email-send-failed": "E-posta göndermeye çalışırken hata oluştu: {{message}}", - "error-save-image": "Görüntüyü kaydederken hata oluştu!", - "error-save-video": "Videoyu kaydederken hata oluştu!", - "error-field-unavailable": "{{field}} zaten kullanılıyor! :(", - "error-file-too-large": "Dosya çok büyük!", - "error-importer-not-defined": "İçe aktarıcı doğru tanımlanmadı, \"Import\" sınıfı eksik!", - "error-input-is-not-a-valid-field": "{{input}} geçerli bir {{field}} değil!", - "error-invalid-actionlink": "Geçersiz işlem bağlantısı!", - "error-invalid-arguments": "Geçersiz parametreler!", - "error-invalid-asset": "Geçersiz veri!", - "error-invalid-channel": "Geçersiz kanal.", - "error-invalid-channel-start-with-chars": "Geçersiz kanal! @ veya # ile başlayın.", - "error-invalid-custom-field": "Geçersiz özelleştirilmiş alan", - "error-invalid-custom-field-name": "Geçersiz özelleştirilmiş alan adı! Yalnızca harf, rakam, kısa çizgi ve alt çizgi kullanın.", - "error-invalid-date": "Geçersiz tarih!", - "error-invalid-description": "Geçersiz açıklama!", - "error-invalid-domain": "Geçersiz alan adı!", - "error-invalid-email": "Geçersiz e-posta {{email}}!", - "error-invalid-email-address": "Geçersiz e-posta adresi!", - "error-invalid-file-height": "Geçersiz fotoğraf yüksekliği!", - "error-invalid-file-type": "Geçersiz dosya türü!", - "error-invalid-file-width": "Geçersiz fotoğraf genişliği!", - "error-invalid-from-address": "Geçersiz bir KİMDEN adresi bildirdiniz!", - "error-invalid-integration": "Geçersiz entegrasyon", - "error-invalid-message": "Geçersiz ileti!", - "error-invalid-method": "Geçersiz metot!", - "error-invalid-name": "Geçersiz isim!", - "error-invalid-password": "Geçersiz şifre!", - "error-invalid-redirectUri": "Geçersiz yönlendirme bağlantısı!", - "error-invalid-role": "Geçersiz rol!", - "error-invalid-room": "Geçersiz oda!", - "error-invalid-room-name": "{{room_name}}, geçerli bir oda adı değil!", - "error-invalid-room-type": "{{type}}, geçerli bir oda türü değil!", - "error-invalid-settings": "Geçersiz ayar!", - "error-invalid-subscription": "Geçersiz başvuru!", - "error-invalid-token": "Geçersiz belirteç!", - "error-invalid-triggerWords": "Geçersiz tetikleyici parametreleri!", - "error-invalid-urls": "Geçersiz URL'ler!", - "error-invalid-user": "Geçersiz kullanıcı!", - "error-invalid-username": "Geçersiz kullanıcı adı!", - "error-invalid-webhook-response": "İstek URL'si 200'den farklı bir durumla yanıt verdi!", - "error-message-deleting-blocked": "İleti silme engellendi!", - "error-message-editing-blocked": "İleti düzenleme engellendi", - "error-message-size-exceeded": "İleti boyutu, Message_MaxAllowedSize değerini aşıyor", - "error-missing-unsubscribe-link": "[unsubscribe] bağlantısını belirtmelisiniz.", - "error-no-tokens-for-this-user": "Bu kullanıcı için belirteç (token) yok", - "error-not-allowed": "İzin verilmedi", - "error-not-authorized": "Yetkili değil", - "error-push-disabled": "Push devre dışı", - "error-remove-last-owner": "Lütfen bunu kaldırmadan önce yeni bir sahip belirleyin.", - "error-role-in-use": "Rol kullanımda olduğu için silinemiyor", - "error-role-name-required": "Rol adı gerekli", - "error-the-field-is-required": "{{field}} alanı gereklidir.", - "error-too-many-requests": "Hata, çok fazla istek. Lütfen yavaşla. Tekrar denemeden önce {{seconds}} saniye beklemelisiniz.", - "error-user-is-not-activated": "Kullanıcı etkinleştirilmedi!", - "error-user-has-no-roles": "Kullanıcıya tanımlı rol yok!", - "error-user-limit-exceeded": "#channel_name kanalına davet etmeye çalıştığınız kullanıcıların sayısı, yönetici tarafından belirlenen sınırı aşıyor!", - "error-user-not-in-room": "Kullanıcı bu odada değil!", - "error-user-registration-custom-field": "error-user-registration-custom-field", - "error-user-registration-disabled": "Kullanıcı kaydı devre dışı bırakıldı!", - "error-user-registration-secret": "Kullanıcı kaydına yalnızca Gizli URL aracılığıyla izin verilir!", - "error-you-are-last-owner": "Son sahibi sizsiniz. Lütfen odadan ayrılmadan önce yeni bir sahip belirleyin.", - "Actions": "İşlemler", - "activity": "etkinlik", - "Activity": "Etkinlik", - "Add_Reaction": "Tepki ekle", - "Add_Server": "Sunucu ekle", - "Add_users": "Kullanıcı ekle", - "Admin_Panel": "Yönetim Paneli", - "Agent": "Temsilci", - "Alert": "Uyarı", - "alert": "uyarı", - "alerts": "uyarılar", - "All_users_in_the_channel_can_write_new_messages": "Kanaldaki tüm kullanıcılar yeni ileti yazabilir!", - "A_meaningful_name_for_the_discussion_room": "Tartışma odası için anlamlı bir isim", - "All": "Tümü", - "All_Messages": "Tüm İletiler", - "Allow_Reactions": "Tüm Tepkiler", - "Alphabetical": "Alfabetik", - "and_more": "ve daha", - "and": "ve", - "announcement": "duyuru", - "Announcement": "Duyuru", - "Apply_Your_Certificate": "Sertifikanızı Onaylayın", - "ARCHIVE": "ARŞİVLE", - "archive": "arşivle", - "are_typing": "yazıyor", - "Are_you_sure_question_mark": "Emin misiniz?", - "Are_you_sure_you_want_to_leave_the_room": "{{room}} odasından ayrılmak istediğinizden emin misiniz?", - "Audio": "Ses", - "Authenticating": "Doğrulanıyor", - "Automatic": "Otomatik", - "Auto_Translate": "Otomatik Çevir", - "Avatar_changed_successfully": "Profil fotoğrafı başarıyla değiştirildi!", - "Avatar_Url": "Profil fotoğrafı URL'si", - "Away": "Uzakta", - "Back": "Geri", - "Black": "Koyu", - "Block_user": "Kullanıcıyı engelle", - "Browser": "Tarayıcı", - "Broadcast_channel_Description": "Yalnızca yetkili kullanıcılar yeni ileti yazabilir, ancak diğer kullanıcılar yanıt verebilir", - "Broadcast_Channel": "Kanala Yayınla", - "Busy": "Meşgul", - "By_proceeding_you_are_agreeing": "Devam ederek kabul ediyorsunuz: ", - "Cancel_editing": "Düzenlemeyi iptal et", - "Cancel_recording": "Kaydı iptal et", - "Cancel": "İptal et", - "changing_avatar": "profil fotoğrafı değiştirme", - "creating_channel": "kanal açılıyor", - "creating_invite": "davet üretiliyor", - "Channel_Name": "Kanal Adı", - "Channels": "Kanallar", - "Chats": "Sohbetler", - "Call_already_ended": "Çağrı sona erdi!", - "Clear_cookies_alert": "Tüm çerezleri temizlemek istiyor musunuz?", - "Clear_cookies_desc": "Bu işlem, tüm oturum açma çerezlerini temizleyerek diğer hesaplara giriş yapmanıza olanak tanır.", - "Clear_cookies_yes": "Evet, çerezleri temizle", - "Clear_cookies_no": "Hayır, çerezleri koru", - "Click_to_join": "Katılmak için tıklayın!", - "Close": "Kapat", - "Close_emoji_selector": "Emoji seçiciyi kapat", - "Closing_chat": "Sohbet kapatılıyor", - "Change_language_loading": "Dil değiştiriliyor", - "Chat_closed_by_agent": "Sohbet temsilci tarafından kapatıldı", - "Choose": "Seç", - "Choose_from_library": "Kütüphaneden seç", - "Choose_file": "Dosya seç", - "Choose_where_you_want_links_be_opened": "Bağlantıların açılmasını istediğiniz yeri seçin", - "Code": "Kod", - "Code_or_password_invalid": "Kod veya şifre geçersiz", - "Collaborative": "İşbirlikçi", - "Confirm": "Onayla", - "Connect": "Bağlan", - "Connected": "Bağlandı", - "connecting_server": "sunucuya bağlanıyor", - "Connecting": "Bağlanıyor...", - "Contact_us": "Bize ulaşın", - "Contact_your_server_admin": "Sunucu yöneticisiyle iletişime geçin.", - "Continue_with": "Devam et:", - "Copied_to_clipboard": "Panoya kopyalandı!", - "Copy": "Kopyala", - "Conversation": "Sohbet", - "Permalink": "Kalıcı bağlantı", - "Certificate_password": "Sertifika Şifresi", - "Clear_cache": "Yerel sunucu önbelleğini temizleyin", - "Clear_cache_loading": "Önbellek temizleniyor.", - "Whats_the_password_for_your_certificate": "Sertifikanızın şifresi nedir?", - "Create_account": "Hesap oluştur", - "Create_Channel": "Kanal Oluştur", - "Create_Direct_Messages": "Özel İleti Oluştur", - "Create_Discussion": "Tartışma Oluştur", - "Created_snippet": "kalıp oluşturdu", - "Create_a_new_workspace": "Çalışma alanı oluştur", - "Create": "Oluştur", - "Custom_Status": "Özelleştirilmiş durum", - "Dark": "Karanlık", - "Dark_level": "Karanlık Seviyesi", - "Default": "Varsayılan", - "Default_browser": "Varsayılan tarayıcı", - "Delete_Room_Warning": "Bir odanın silinmesi, oda içinde yayınlanan tüm iletileri silecektir. Bu geri alınamaz.", - "Department": "Bölüm", - "delete": "sil", - "Delete": "Sil", - "DELETE": "SİL", - "deleting_room": "oda siliniyor", - "description": "açıklama", - "Description": "Açıklama", - "Desktop_Options": "Masaüstü Seçenekleri", - "Desktop_Notifications": "Masaüstü Bildirimleri", - "Desktop_Alert_info": "Bu bildirimler masaüstünde teslim edilir", - "Directory": "Dizin", - "Direct_Messages": "Özel İletiler", - "Disable_notifications": "Bildirimleri devre dışı bırak", - "Discussions": "Tartışmalar", - "Discussion_Desc": "Neler olup bittiğini gözden geçirmeye yardımcı olun! Bir tartışma oluşturarak, seçtiğinizin bir alt kanalı oluşturulur ve her ikisi de bağlanır.", - "Discussion_name": "Tartışma adı", - "Done": "Tamamlandı", - "Dont_Have_An_Account": "Hesabın yok mu?", - "Do_you_have_an_account": "Hesabın var mı?", - "Do_you_have_a_certificate": "Sertifikanız var mı?", - "Do_you_really_want_to_key_this_room_question_mark": "Bu odayı gerçekten {{key}} istiyor musun?", - "E2E_Encryption": "Uçtan Uca Şifreleme (E2E)", - "E2E_How_It_Works_info1": "Artık şifrelenmiş özel gruplar ve doğrudan iletiler oluşturabilirsiniz. Mevcut özel grupları veya özel iletileri şifreli olarak da değiştirebilirsiniz.", - "E2E_How_It_Works_info2": "Bu, *Uçtan Uca Şifreleme'dir (E2E)*, böylece iletilerinizi şifrelemek / çözmek için anahtar ve bunlar sunucuya kaydedilmez. Bu nedenle *bu şifreyi daha sonra ihtiyaç duyduğunuzda erişebileceğiniz güvenli bir yerde* saklamanız gerekir.", - "E2E_How_It_Works_info3": "Devam ederseniz, otomatik olarak bir Uçtan Uca Şifreleme (E2E) şifresi oluşturulacaktır.", - "E2E_How_It_Works_info4": "Mevcut Uçtan Uca Şifreleme (E2E) şifresini, herhangi bir tarayıcıdan istediğiniz zaman girdiğiniz şifreleme anahtarınız için yeni bir şifre de ayarlayabilirsiniz.", - "edit": "düzenle", - "edited": "düzenlendi", - "Edit": "Düzenle", - "Edit_Status": "Durumu Düzenle", - "Edit_Invite": "Daveti Düzenle", - "End_to_end_encrypted_room": "Uçtan uca şifreli oda", - "end_to_end_encryption": "uçtan uca şifreleme", - "Email_Notification_Mode_All": "Tüm Bahsetmeler/Özel İletiler", - "Email_Notification_Mode_Disabled": "Devre Dışı", - "Email_or_password_field_is_empty": "E-posta veya şifre alanı boş", - "Email": "E-posta", - "email": "e-posta", - "Empty_title": "Boş başlık", - "Enable_Auto_Translate": "Otomatik Çeviriyi Etkinleştir", - "Enable_notifications": "Bildirimleri etkinleştir", - "Encrypted": "Şifreli", - "Encrypted_message": "Şifreli ileti", - "Enter_Your_E2E_Password": "Uçtan Uca Şifreleme (E2E) Şifrenizi Girin", - "Enter_Your_Encryption_Password_desc1": "Bu, şifrelenmiş özel gruplarınıza ve doğrudan iletilerinize erişmenize izin verecektir.", - "Enter_Your_Encryption_Password_desc2": "Sohbeti kullandığınız her yerde iletileri şifrelemek / çözmek için şifre girmeniz gerekir.", - "Encryption_error_title": "Şifreleme şifreniz yanlış görünüyor", - "Encryption_error_desc": "İçe aktarılacak şifreleme anahtarınızın kodu çözülemedi.", - "Everyone_can_access_this_channel": "Bu kanala herkes erişebilir", - "Error_uploading": "Yükleme hatası", - "Expiration_Days": "Geçerlilik Süresi (Gün)", - "Favorite": "Favori", - "Favorites": "Favoriler", - "Files": "Dosyalar", - "File_description": "Dosya açıklaması", - "File_name": "Dosya adı", - "Finish_recording": "Kaydı bitir", - "Following_thread": "Konu takip ediliyor", - "For_your_security_you_must_enter_your_current_password_to_continue": "Güvenliğiniz için, devam etmek için mevcut şifrenizi girmelisiniz", - "Forgot_password_If_this_email_is_registered": "Bu e-posta kayıtlıysa, şifrenizi nasıl sıfırlayacağınıza dair talimatlar göndereceğiz. Kısa süre içinde bir e-posta almazsanız, lütfen geri gelin ve tekrar deneyin.", - "Forgot_password": "Parolanızı mı unuttunuz?", - "Forgot_Password": "Parolamı Unuttum", - "Forward": "İlet", - "Forward_Chat": "Sohbete İlet", - "Forward_to_department": "Bölüme İlet", - "Forward_to_user": "Kullanıcıya İlet", - "Full_table": "Tam tabloyu görmek için tıklayın", - "Generate_New_Link": "Yeni Bağlantı Oluştur", - "Group_by_favorites": "Favorilere göre grupla", - "Group_by_type": "Türe göre grupla", - "Hide": "Gizle", - "Has_joined_the_channel": "kanala katıldı", - "Has_joined_the_conversation": "sohbete katıldı", - "Has_left_the_channel": "kanaldan ayrıldı", - "Hide_System_Messages": "Sistem İletilerını Gizle", - "Hide_type_messages": "\"{{type}}\" iletilerini gizle", - "How_It_Works": "Nasıl Çalışır", - "Message_HideType_uj": "Kullanıcı Katıldı", - "Message_HideType_ul": "Kullanıcı Ayrıldı", - "Message_HideType_ru": "Kullanıcı Kaldırıldı", - "Message_HideType_au": "Kullanıcı Eklendi", - "Message_HideType_mute_unmute": "Kullanıcı Sesi Kapatıldı / Sesi Açıldı", - "Message_HideType_r": "Oda Adı Değiştirildi", - "Message_HideType_ut": "Kullanıcı Sohbete Katıldı", - "Message_HideType_wm": "Hoşgeldiniz", - "Message_HideType_rm": "İleti Kaldırıldı", - "Message_HideType_subscription_role_added": "Rol Belirlendi", - "Message_HideType_subscription_role_removed": "Artık Kullanılmayan Rol", - "Message_HideType_room_archived": "Oda Arşivlendi", - "Message_HideType_room_unarchived": "Oda Arşivden Çıkarıldı", - "I_Saved_My_E2E_Password": "Uçtan Uca Şifreleme (E2E) Şifremi Kaydettim", - "IP": "IP", - "In_app": "Uygulama İçi", - "In_App_And_Desktop": "Uygulama İçi ve Masaüstü", - "In_App_and_Desktop_Alert_info": "Uygulama açıkken ekranın üst kısmında bir başlık görüntüler ve masaüstünde bir bildirim görüntüler", - "Invisible": "Görünmez", - "Invite": "Davet Et", - "is_a_valid_RocketChat_instance": "geçerli bir Rocket.Chat örneği", - "is_not_a_valid_RocketChat_instance": "geçerli bir Rocket.Chat örneği değil", - "is_typing": "yazıyor", - "Invalid_or_expired_invite_token": "Geçersiz veya süresi dolmuş davet belirteci (token)", - "Invalid_server_version": "Bağlanmaya çalıştığınız sunucu artık uygulama tarafından desteklenmeyen bir sürüm kullanıyor: {{currentVersion}}.\n\n{{minVersion}} sürümüne ihtiyacımız var", - "Invite_Link": "Davet Bağlantısı", - "Invite_users": "Kullanıcıları davet et", - "Join": "Katıl", - "Join_Code": "Katılım Kodu", - "Insert_Join_Code": "Katılım Kodu Girin", - "Join_our_open_workspace": "Açık çalışma alanımıza katılın", - "Join_your_workspace": "Çalışma alanınıza katılın", - "Just_invited_people_can_access_this_channel": "Yalnızca davet edilen kişiler bu kanala erişebilir", - "Language": "Dil", - "last_message": "son ileti", - "Leave_channel": "Kanaldan ayrıl", - "leaving_room": "odadan ayrılıyor", - "Leave": "Odadan ayrıl", - "leave": "ayrıl", - "Legal": "Yasal", - "Light": "Açık", - "License": "Lisans", - "Livechat": "Canlı Sohbet", - "Livechat_edit": "Canlı sohbeti düzenle", - "Login": "Oturum aç", - "Login_error": "Kimlik bilgileriniz reddedildi! Lütfen tekrar deneyin.", - "Login_with": "ile giriş", - "Logging_out": "Çıkış Yapılıyor", - "Logout": "Çıkış Yap", - "Max_number_of_uses": "Maksimum kullanım sayısı", - "Max_number_of_users_allowed_is_number": "İzin verilen maksimum kullanıcı sayısı {{maxUsers}}", - "members": "üyeler", - "Members": "Üyeler", - "Mentioned_Messages": "Bahsedilen İletiler", - "mentioned": "bahsedildi", - "Mentions": "Bahsetmeler", - "Message_accessibility": "{{user}} tarafından {{time}} itibarıyla ileti: {{message}}", - "Message_actions": "İleti işlemleri", - "Message_pinned": "İleti sabitlendi", - "Message_removed": "İleti kaldırıldı", - "Message_starred": "İletia yıldız eklendi", - "Message_unstarred": "İletiın yıldızı kaldırıldı", - "message": "ileti", - "messages": "iletiler", - "Message": "İleti", - "Messages": "İletiler", - "Message_Reported": "İleti bildirildi", - "Microphone_Permission_Message": "Rocket.Chat'in mikrofonunuza erişmesi gerekiyor, böylece sesli ileti gönderebilirsiniz.", - "Microphone_Permission": "Mikrofon İzni", - "Mute": "Sessize Al", - "muted": "sessize alındı", - "My_servers": "Sunucularım", - "N_people_reacted": "{{n}} kişi tepki verdi", - "N_users": "{{n}} kullanıcı", - "name": "isim", - "Name": "İsim", - "Navigation_history": "Gezinti geçmişi", - "Never": "Asla", - "New_Message": "Yeni İleti", - "New_Password": "Yeni Şifre", - "New_Server": "Yeni Sunucu", - "Next": "Sonraki", - "No_files": "Dosya yok", - "No_limit": "Limit yok", - "No_mentioned_messages": "Belirtilen ileti yok", - "No_pinned_messages": "Sabitlenmiş ileti yok", - "No_results_found": "Sonuç bulunamadı", - "No_starred_messages": "Yıldızlı ileti yok", - "No_thread_messages": "Konu iletisi yok", - "No_label_provided": "Hiç {{label}} sağlanmadı.", - "No_Message": "İleti Yok", - "No_messages_yet": "Şu ana kadar ileti yok", - "No_Reactions": "Tepki Yok", - "No_Read_Receipts": "Okundu Bilgisi Yok", - "Not_logged": "Kayıtlı değil", - "Not_RC_Server": "Bu bir Rocket.Chat sunucusu değil.\n{{contact}}", - "Nothing": "Hiçbir Şey", - "Nothing_to_save": "Kaydedilecek bir şey yok!", - "Notify_active_in_this_room": "Bu odadaki çevrimiçi kullanıcıları bilgilendir", - "Notify_all_in_this_room": "Bu odadaki herkesi bilgilendir", - "Notifications": "Bildirimler", - "Notification_Duration": "Bildirim Süresi", - "Notification_Preferences": "Bildirim Tercihleri", - "No_available_agents_to_transfer": "Aktarılacak yemsilci yok", - "Offline": "Çevrimdışı", - "Oops": "Ahh!", - "Omnichannel": "Çoklu Kanal", - "Open_Livechats": "Devam Eden Sohbetler", - "Omnichannel_enable_alert": "Çoklu Kanal'da mevcut değilsiniz. Erişilebilir olmak ister misiniz?", - "Onboarding_description": "Çalışma alanı, ekibinizin veya kuruluşunuzun işbirliği alanıdır. Çalışma alanı yöneticisinden bir ekibe katılmak veya bir ekip oluşturmak için yardım isteyin.", - "Onboarding_join_workspace": "Bir çalışma alanına katılın", - "Onboarding_subtitle": "Ekip İşbirliğinin Ötesinde", - "Onboarding_title": "Rocket.Chat'e hoş geldiniz", - "Onboarding_join_open_description": "Rocket.Chat ekibi ve topluluğu ile sohbet etmek için açık çalışma alanımıza katılın.", - "Onboarding_agree_terms": "Devam ederek Rocket.Chat'i kabul etmiş olursunuz", - "Onboarding_less_options": "Daha az seçenek", - "Onboarding_more_options": "Daha çok seçenek", - "Online": "Çevrimiçi", - "Only_authorized_users_can_write_new_messages": "Yalnızca yetkili kullanıcılar yeni ileti yazabilir", - "Open_emoji_selector": "Emoji seçiciyi aç", - "Open_Source_Communication": "Açık Kaynak İletişim", - "Open_your_authentication_app_and_enter_the_code": "Kimlik doğrulama uygulamanızı açın ve kodu girin.", - "OR": "OR", - "OS": "OS", - "Overwrites_the_server_configuration_and_use_room_config": "Sunucu yapılandırmasının üzerine yazar ve oda yapılandırmasını kullanır", - "Password": "Şifre", - "Parent_channel_or_group": "Üst kanal veya grup", - "Permalink_copied_to_clipboard": "Kalıcı bağlantı panoya kopyalandı!", - "Phone": "Telefon", - "Pin": "Sabitle", - "Pinned_Messages": "Sabitlenen İletiler", - "pinned": "sabitlendi", - "Pinned": "Sabitlendi", - "Please_add_a_comment": "Lütfen bir yorum ekleyin", - "Please_enter_your_password": "Lütfen şifrenizi giriniz", - "Please_wait": "Lütfen bekle.", - "Preferences": "Tercihler", - "Preferences_saved": "Tercihler kaydedildi!", - "Privacy_Policy": " Privacy Policy", - "Private_Channel": "Özel Kanal", - "Private": "Özel", - "Processing": "İşleniyor...", - "Profile_saved_successfully": "Profil başarıyla kaydedildi!", - "Profile": "Profil", - "Public_Channel": "Herkese Açık Kanal", - "Public": "Herkese Açık", - "Push_Notifications": "Anlık Bildirimler", - "Push_Notifications_Alert_Info": "Bu bildirimler, uygulama açık olmadığında size teslim edilir", - "Quote": "Alıntı", - "Reactions_are_disabled": "Tepkiler devre dışı bırakıldı", - "Reactions_are_enabled": "Tepkiler etkinleştirildi", - "Reactions": "Tepkiler", - "Read": "Oku", - "Read_External_Permission_Message": "Rocket.Chat'in cihazınızdaki fotoğraflara, medyaya ve dosyalara erişmesi gerekiyor", - "Read_External_Permission": "Medya Okuma İzni ", - "Read_Only_Channel": "Yazma Kısıtlı Kanal", - "Read_Only": "Yazma Kısıtlı", - "Read_Receipt": "Okundu Bilgisi", - "Receive_Group_Mentions": "Grup Bahsetmelerini Al", - "Receive_Group_Mentions_Info": "Grup bahsetmelerini al", - "Register": "Kayıt Ol", - "Repeat_Password": "Şifre Tekrarı", - "Replied_on": "Yanıtlandı:", - "replies": "yanıtlar", - "reply": "yanıtla", - "Reply": "Yanıtla", - "Report": "Bildir", - "Receive_Notification": "Bildirim Al", - "Receive_notifications_from": "{{name}} bildirimlerini alın", - "Resend": "Yeniden yolla", - "Reset_password": "Şifre sıfırla", - "resetting_password": "şifre sıfırlanıyor", - "RESET": "SIFIRLA", - "Return": "Geri dön", - "Review_app_title": "Uygulama hoşunuza gitti mi?", - "Review_app_desc": "{{store}} üzerinde bize 5 yıldız verin", - "Review_app_yes": "Elbette!", - "Review_app_no": "Hayır", - "Review_app_later": "Belki daha sonra", - "Review_app_unable_store": "{{store}} açılamıyor!", - "Review_this_app": "Bu uygulamayı değerlendirin", - "Remove": "Kaldır", - "Roles": "Roller", - "Room_actions": "Oda işlemleri", - "Room_changed_announcement": "Oda duyurusu, {{userBy}} tarafından {{announcement}} olarak değiştirildi", - "Room_changed_avatar": "Oda profil fotoğrafı {{userBy}} tarafından değiştirildi", - "Room_changed_description": "Oda açıklaması, {{userBy}} tarafından {{description}} olarak değiştirildi", - "Room_changed_topic": "Oda konusu, {{userBy}} tarafından {{topic}} olarak değiştirildi", - "Room_Files": "Oda Dosyaları", - "Room_Info_Edit": "Oda Bilgilerini Düzenle", - "Room_Info": "Oda Bilgisi", - "Room_Members": "Oda Üyeleri", - "Room_name_changed": "Oda adı, {{userBy}} tarafından {{name}} olarak değiştirildi", - "SAVE": "KAYDET", - "Save_Changes": "Değişiklikleri Kaydet", - "Save": "Kaydet", - "Saved": "Kaydedildi", - "saving_preferences": "tercihler kaydediliyor", - "saving_profile": "profil kaydediliyor", - "saving_settings": "ayarlar kaydediliyor", - "saved_to_gallery": "Galeriye kaydedildi", - "Save_Your_E2E_Password": "(E2E) Şifrenizi Kaydedin", - "Save_Your_Encryption_Password": "Şifreleme Parolanızı Kaydedin", - "Save_Your_Encryption_Password_warning": "Bu parola hiçbir yerde saklanmadığından başka bir yere dikkatlice kaydedin.", - "Save_Your_Encryption_Password_info": "Parolanızı kaybettiğinizde, kurtarmanın bir yolu olmadığını ve iletilerinize erişiminizi kaybedeceğinizi unutmayın.", - "Search_Messages": "İleti ara", - "Search": "Ara", - "Search_by": "Ara", - "Search_global_users": "Global kullanıcıları ara", - "Search_global_users_description": "Açarsanız, diğer şirketlerden veya sunuculardan herhangi bir kullanıcıyı arayabilirsiniz.", - "Seconds": "{{second}} saniye", - "Security_and_privacy": "Güvenlik ve gizlilik", - "Select_Avatar": "Profil resmi seç", - "Select_Server": "Sunucu seç", - "Select_Users": "Kullanıcıları seç", - "Select_a_Channel": "Kanal Seç", - "Select_a_Department": "Bölüm Seç", - "Select_an_option": "Bir seçenek seçin", - "Select_a_User": "Kullanıcı Seç", - "Send": "Yolla", - "Send_audio_message": "Sesli ileti gönder", - "Send_crash_report": "Çökme raporu gönder", - "Send_message": "İleti gönder", - "Send_me_the_code_again": "Kodu tekrar gönder", - "Send_to": "Gönderiliyor...", - "Sending_to": "Gönderiliyor:", - "Sent_an_attachment": "Bir ek gönderildi", - "Server": "Sunucu", - "Servers": "Sunucular", - "Server_version": "Sunucu versiyonu: {{version}}", - "Set_username_subtitle": "Kullanıcı adı başkalarının iletilerde sizden bahsetmesine izin vermek için kullanılır", - "Set_custom_status": "Özelleştirilmiş durumu ayarlayın", - "Set_status": "Durumu ayarla", - "Status_saved_successfully": "Durum başarıyla kaydedildi!", - "Settings": "Ayarlar", - "Settings_succesfully_changed": "Ayarlar başarıyla değiştirildi!", - "Share": "Paylaş", - "Share_Link": "Bağlantı paylaş", - "Share_this_app": "Bu uygulamayı paylaş", - "Show_more": "Daha fazla göster..", - "Show_Unread_Counter": "Okunmamış Sayacını Göster", - "Show_Unread_Counter_Info": "Okunmamış sayacı, listede kanalın sağ tarafında bir rozet olarak görüntülenir", - "Sign_in_your_server": "Sunucunuzda oturum açın", - "Sign_Up": "Kaydol", - "Some_field_is_invalid_or_empty": "Bazı alanlar geçersiz veya boş", - "Sorting_by": "{{key}} göre sıralanıyor", - "Sound": "Ses", - "Star_room": "Odayı Yıldızla", - "Star": "Yıldızla", - "Starred_Messages": "Yıldızlı İletiler", - "starred": "yıldızlandı", - "Starred": "Yıldızlandı", - "Start_of_conversation": "Konuşma başlangıcı", - "Start_a_Discussion": "Tartışma Başlat", - "Started_discussion": "Bir tartışma başlattı:", - "Started_call": "Arama {{userBy}} tarafından başlatıldı", - "Submit": "Kaydet", - "Table": "Tablo", - "Tags": "Etiketler", - "Take_a_photo": "Fotoğraf çek", - "Take_a_video": "Video çek", - "Take_it": "Al!", - "tap_to_change_status": "durumu değiştirmek için dokunun", - "Tap_to_view_servers_list": "Sunucu listesini görüntülemek için dokunun", - "Terms_of_Service": " Kullanım Şartları ", - "Theme": "Tema", - "The_user_wont_be_able_to_type_in_roomName": "Kullanıcı {{roomName}} içinde yazamayacak", - "The_user_will_be_able_to_type_in_roomName": "Kullanıcı {{roomName}} içinde yazabilecek", - "There_was_an_error_while_action": "{{action}} sırasında bir hata oluştu!", - "This_room_is_blocked": "Bu oda engellendi", - "This_room_is_read_only": "Bu oda yazma kısıtlı", - "Thread": "Başlık", - "Threads": "Başlıklar", - "Timezone": "Saat dilimi", - "To": "Kime", - "topic": "konu", - "Topic": "Konu", - "Translate": "Çevir", - "Try_again": "Tekrar deneyin", - "Two_Factor_Authentication": "İki faktörlü Kimlik Doğrulama", - "Type_the_channel_name_here": "Kanal adını buraya yazın", - "unarchive": "arşivden çıkar", - "UNARCHIVE": "ARŞİVDEN ÇIKAR", - "Unblock_user": "Kullanıcının engelini kaldır", - "Unfavorite": "Favorilerden Çıkar", - "Unfollowed_thread": "Takip edilmeyen başlık", - "Unmute": "Sesi Aç", - "unmuted": "Sesi Açıldı", - "Unpin": "Sabitlemeyi kaldır", - "unread_messages": "okunmamış", - "Unread": "Okunmamış", - "Unread_on_top": "Okunmamışlar üstte", - "Unstar": "Yıldızı kaldır", - "Updating": "Güncelleniyor...", - "Uploading": "Gönderiliyor", - "Upload_file_question_mark": "Dosya gönderilsin mi?", - "User": "Kullanıcı", - "Users": "Kullanıcılar", - "User_added_by": "{{userAdded}} adlı kullanıcı {{userBy}} tarafından eklendi", - "User_Info": "Kullanıcı bilgisi", - "User_has_been_key": "Kullanıcı {{key}} olmuştur", - "User_is_no_longer_role_by_": "{{user}} artık {{role}} değil ({{userBy}} tarafından)", - "User_muted_by": "{{userMuted}} adlı kullanıcının sesini {{userBy}} kapattı", - "User_removed_by": "{{userRemoved}} kullanıcısı {{userBy}} tarafından kaldırıldı", - "User_sent_an_attachment": "{{user}} bir ek gönderdi", - "User_unmuted_by": "{{userUnmuted}} kullanıcısının sesi {{userBy}} tarafından açıldı", - "User_was_set_role_by_": "{{user}}, {{userBy}} tarafından {{role}} ayarlandı", - "Username_is_empty": "Kullanıcı adı boş!", - "Username": "Kullanıcı adı", - "Username_or_email": "Kullanıcı adı ya da e-posta", - "Uses_server_configuration": "Sunucu yapılandırmasını kullanır", - "Validating": "Doğrulanıyor", - "Registration_Succeeded": "Kayıt Başarılı!", - "Verify": "Onayla", - "Verify_email_title": "Kaydınızı onaylayın!", - "Verify_email_desc": "Kaydınızı onaylamak için size bir e-posta gönderdik. Kısa süre içinde bir e-posta almazsanız, lütfen geri gelin ve tekrar deneyin.", - "Verify_your_email_for_the_code_we_sent": "Gönderdiğimiz kod için e-postanızı doğrulayın", - "Video_call": "Görüntülü arama", - "View_Original": "Orijinali Görüntüle", - "Voice_call": "Sesli arama", - "Waiting_for_network": "Ağ bağlantısı bekleniyor ...", - "Websocket_disabled": "Bu sunucu için Websocket devre dışı bırakıldı.\n{{contact}}", - "Welcome": "Hoşgeldiniz", - "What_are_you_doing_right_now": "Şu an ne yapıyorsun?", - "Whats_your_2fa": "2 faktör doğrulama (2FA) kodunuz nedir?", - "Without_Servers": "Sunucusuz", - "Workspaces": "Çalışma alanları", - "Would_you_like_to_return_the_inquiry": "Başvuruyu geri çevirmek ister misiniz?", - "Write_External_Permission_Message": "Rocket.Chat'in galerinize erişmesi gerekiyor, böylece resimleri kaydedebilirsiniz.", - "Write_External_Permission": "Galeri İzni", - "Yes": "Evet", - "Yes_action_it": "Evet, {{action}}!", - "Yesterday": "Dün", - "You_are_in_preview_mode": "İzleme modundasınız", - "You_are_offline": "Çevrimdışısınız", - "You_can_search_using_RegExp_eg": "Düzenli ifadeleri (Regular Expressions) kullanabilirsiniz. Örneğin: `/^text$/i`", - "You_colon": "Siz: ", - "you_were_mentioned": "senden bahsedildi", - "You_were_removed_from_channel": "{{channel}} kanalından çıkarıldınız", - "you": "siz", - "You": "Siz", - "Logged_out_by_server": "Sunucu tarafından çıkış yaptınız. Lütfen tekrar giriş yapın.", - "You_need_to_access_at_least_one_RocketChat_server_to_share_something": "Bir şeyler paylaşmak için Rocket.Chat sunucusuna erişmeniz gerekir.", - "You_need_to_verifiy_your_email_address_to_get_notications": "Bildirim almak için e-posta adresinizi doğrulamanız gerekiyor", - "Your_certificate": "Sertifikanız", - "Your_invite_link_will_expire_after__usesLeft__uses": "Davet bağlantınızın geçerliliği {{usesLeft}} kullanımdan sonra sona erecek.", - "Your_invite_link_will_expire_on__date__or_after__usesLeft__uses": "Davet bağlantınızın geçerliliği {{date}} tarihinde veya {{usesLeft}} kullanımdan sonra sona erecek.", - "Your_invite_link_will_expire_on__date__": "Davet bağlantınızın geçerlilik süresi {{date}} tarihinde sona erecek.", - "Your_invite_link_will_never_expire": "Davet bağlantınızın geçerlilik süresi asla dolmayacak.", - "Your_workspace": "Çalışma alanınız", - "Your_password_is": "Şifreniz", - "Version_no": "Versiyon: {{version}}", - "You_will_not_be_able_to_recover_this_message": "Bu iletiyi kurtaramayacaksınız!", - "You_will_unset_a_certificate_for_this_server": "Bu sunucu için bir sertifika ayarını kaldıracaksınız", - "Change_Language": "Dili değiştir", - "Crash_report_disclaimer": "Sohbetlerinizin içeriğini asla takip etmiyoruz. Çökme raporu ve analiz olayları, sorunları tanımlamak ve düzeltmek için yalnızca bizim için ilgili bilgileri içerir.", - "Type_message": "İleti yaz", - "Room_search": "Oda arama", - "Room_selection": "Oda seçimi 1...9", - "Next_room": "Sonraki oda", - "Previous_room": "Önceki oda", - "New_room": "Yeni oda", - "Upload_room": "Odaya yükle", - "Search_messages": "İletilerda ara", - "Scroll_messages": "İletilerı kaydır", - "Reply_latest": "Sonuncuyu yanıtla", - "Reply_in_Thread": "Konu içinde cevapla", - "Server_selection": "Sunucu seçimi", - "Server_selection_numbers": "Sunucu seçimi 1...9", - "Add_server": "Sunucu ekle", - "New_line": "Yeni satır", - "You_will_be_logged_out_of_this_application": "Bu uygulamadan çıkış yapacaksınız.", - "Clear": "Temizle", - "This_will_clear_all_your_offline_data": "Bu, tüm çevrimdışı verilerinizi temizleyecektir.", - "This_will_remove_all_data_from_this_server": "Bu, bu sunucudaki tüm verileri kaldıracaktır.", - "Mark_unread": "Okunmadı olarak işaretle", - "Wait_activation_warning": "Giriş yapmadan önce, hesabınız bir yönetici tarafından manuel olarak etkinleştirilmelidir.", - "Screen_lock": "Ekran kilidi", - "Local_authentication_biometry_title": "Doğrula", - "Local_authentication_biometry_fallback": "Parola kullan", - "Local_authentication_unlock_option": "Şifre ile Kilidi Açın", - "Local_authentication_change_passcode": "Parolayı Değiştir", - "Local_authentication_info": "Not: Parolayı unutursanız, uygulamayı silmeniz ve yeniden yüklemeniz gerekir.", - "Local_authentication_facial_recognition": "yüz tanıma", - "Local_authentication_fingerprint": "parmak izi", - "Local_authentication_unlock_with_label": "{{label}} ile kilidi açın", - "Local_authentication_auto_lock_60": "1 dakika sonra", - "Local_authentication_auto_lock_300": "5 dakika sonra", - "Local_authentication_auto_lock_900": "15 dakika sonra", - "Local_authentication_auto_lock_1800": "30 dakika sonra", - "Local_authentication_auto_lock_3600": "1 saat sonra", - "Passcode_enter_title": "Şifrenizi giriniz", - "Passcode_choose_title": "Yeni şifrenizi yazın", - "Passcode_choose_confirm_title": "Yeni şifrenizi onaylayın", - "Passcode_choose_error": "Parolalar eşleşmiyor. Tekrar deneyin.", - "Passcode_choose_force_set": "Yönetici tarafından istenen şifre", - "Passcode_app_locked_title": "Uygulama kilitlendi", - "Passcode_app_locked_subtitle": "{{timeLeft}} saniye içinde tekrar deneyin", - "After_seconds_set_by_admin": "{{seconds}} saniye sonra (yönetici tarafından belirlenir)", - "Dont_activate": "Şimdi etkinleştirme", - "Queued_chats": "Sıralı sohbetler", - "Queue_is_empty": "Sıra boş", - "Logout_from_other_logged_in_locations": "Giriş yapılan diğer konumlardan çıkış yap", - "You_will_be_logged_out_from_other_locations": "Diğer konumlardan çıkış yapacaksınız.", - "Logged_out_of_other_clients_successfully": "Diğer istemcilerden başarıyla çıkış yapıldı", - "Logout_failed": "Oturum kapatma başarısız oldu!", - "Log_analytics_events": "Olayları günlüğe kaydet", - "E2E_encryption_change_password_title": "Şifreleme Parolasını Değiştir", - "E2E_encryption_change_password_description": "Artık şifrelenmiş özel gruplar ve doğrudan iletiler oluşturabilirsiniz. Mevcut özel grupları veya özel iletileri şifreli olarak da değiştirebilirsiniz. \nBu uçtan uca şifrelemedir, bu nedenle iletilerinizi şifrelemek / çözmek için kullanılan anahtar sunucuya kaydedilmez. Bu nedenle şifrenizi güvenli bir yerde saklamanız gerekir. Uçtan uca şifrelemeyi (E2E) kullanmak istediğiniz diğer cihazlara girmeniz istenecektir.", - "E2E_encryption_change_password_error": "Uçtan uca şifreleme (E2E) anahtar şifresi değiştirilirken hata!", - "E2E_encryption_change_password_success": "Uçtan uca şifreleme (E2E) anahtar şifresi başarıyla değiştirildi!", - "E2E_encryption_change_password_message": "Dikkatlice kaydettiğinizden emin olun.", - "E2E_encryption_change_password_confirmation": "Evet, değiştir!", - "E2E_encryption_reset_title": "Uçtan uca şifreleme (E2E) Anahtarını Sıfırla", - "E2E_encryption_reset_description": "Bu seçenek mevcut uçtan uca şifreleme (E2E) anahtarınızı kaldıracak ve oturumu kapatacaktır. \nTekrar oturum açtığınızda, Rocket.Chat size yeni bir anahtar oluşturacak ve çevrimiçi olarak bir veya daha fazla üyesi olan şifreli herhangi bir odaya erişiminizi geri yükleyecektir. \nUçtan uca şifrelemenin (E2E) doğası gereği Rocket.Chat, çevrimiçi üyesi olmayan hiçbir şifreli odaya erişimi geri yükleyemez.", - "E2E_encryption_reset_button": "Uçtan uca şifreleme (E2E) anahtarını sıfırla", - "E2E_encryption_reset_error": "Uçtan uca şifreleme (E2E) anahtarı sıfırlanırken hata!", - "E2E_encryption_reset_message": "Oturumunuz kapatılacak!", - "E2E_encryption_reset_confirmation": "Evet, sıfırla", - "Following": "Takip ediliyor", - "Threads_displaying_all": "Tüm konular görüntüleniyor", - "Threads_displaying_following": "Takip edilen konular görüntüleniyor", - "Threads_displaying_unread": "Okunmamış konular görüntüleniyor", - "No_threads": "Konu yok", - "No_threads_following": "Herhangi bir konuyu takip etmiyorsunuz", - "No_threads_unread": "Okunmamış konu yok", - "Messagebox_Send_to_channel": "Kanala gönder", - "Remove_from_room": "Odadan çıkar", - "Ignore": "Yok say", - "Unignore": "Yok sayma", - "User_has_been_ignored": "Kullanıcı yok sayıldı.", - "User_has_been_unignored": "Kullanıcı artık yok sayılmıyor.", - "User_has_been_removed_from_s": "Kullanıcı {{s}} alanından kaldırıldı.", - "User__username__is_now_a_leader_of__room_name_": "{{username}} kullanıcısı artık {{room_name}} lideridir.", - "User__username__is_now_a_moderator_of__room_name_": "{{username}} kullanıcısı artık bir {{room_name}} moderatörüdür.", - "User__username__is_now_a_owner_of__room_name_": "{{username}} kullanıcısı artık {{room_name}} adlı odanın sahibidir.", - "User__username__removed_from__room_name__leaders": "{{username}} adlı kullanıcı, {{room_name}} liderlerinden kaldırıldı.", - "User__username__removed_from__room_name__moderators": "{{username}} adlı kullanıcı, {{room_name}} moderatörlerinden kaldırıldı.", - "User__username__removed_from__room_name__owners": "{{username}} adlı kullanıcı, {{room_name}} sahiplerinden kaldırıldı.", - "The_user_will_be_removed_from_s": "Kullanıcı, {{s}} alanından kaldırılacak!", - "Yes_remove_user": "Evet, kullanıcıyı kaldır!", - "Direct_message": "Özel ileti", - "Message_Ignored": "İleti yok sayıldı. Görüntülemek için dokunun.", - "Enter_workspace_URL": "Çalışma alanı URL'nizi girin", - "Workspace_URL_Example": "Örn. sirketiniz.rocket.chat", - "invalid-room": "Geçersiz oda" -} \ No newline at end of file + "1_person_reacted": "1 kişi tepki verdi.", + "1_user": "1 kullanıcı", + "error-action-not-allowed": "{{action}}'a izin verilmiyor!", + "error-application-not-found": "Uygulama bulunamadı!", + "error-archived-duplicate-name": "{{room_name}} adında arşivlenmiş bir kanal var!", + "error-avatar-invalid-url": "Geçersiz avatar URL'si: {{url}}", + "error-avatar-url-handling": "{{username}} için bir URL'den ({{url}}) avatar ayarı işlenirken hata oluştu!", + "error-cant-invite-for-direct-room": "Kullanıcı özel odalara davet edilemedi!", + "error-could-not-change-email": "E-posta değiştirilemedi!", + "error-could-not-change-name": "İsim değiştirilemedi!", + "error-could-not-change-username": "Kullanıcı adı değiştirilemedi!", + "error-could-not-change-status": "Durum değiştirilemedi!", + "error-delete-protected-role": "Korunan bir rol silinemez!", + "error-department-not-found": "Bölüm bulunamadı!", + "error-direct-message-file-upload-not-allowed": "Özel iletilerde dosya paylaşımına izin verilmiyor!", + "error-duplicate-channel-name": "{{channel_name}} adında bir kanal var!", + "error-email-domain-blacklisted": "E-posta alan adı kara listeye alındı!", + "error-email-send-failed": "E-posta göndermeye çalışırken hata oluştu: {{message}}", + "error-save-image": "Görüntüyü kaydederken hata oluştu!", + "error-save-video": "Videoyu kaydederken hata oluştu!", + "error-field-unavailable": "{{field}} zaten kullanılıyor! :(", + "error-file-too-large": "Dosya çok büyük!", + "error-importer-not-defined": "İçe aktarıcı doğru tanımlanmadı, \"Import\" sınıfı eksik!", + "error-input-is-not-a-valid-field": "{{input}} geçerli bir {{field}} değil!", + "error-invalid-actionlink": "Geçersiz işlem bağlantısı!", + "error-invalid-arguments": "Geçersiz parametreler!", + "error-invalid-asset": "Geçersiz veri!", + "error-invalid-channel": "Geçersiz kanal.", + "error-invalid-channel-start-with-chars": "Geçersiz kanal! @ veya # ile başlayın.", + "error-invalid-custom-field": "Geçersiz özelleştirilmiş alan", + "error-invalid-custom-field-name": "Geçersiz özelleştirilmiş alan adı! Yalnızca harf, rakam, kısa çizgi ve alt çizgi kullanın.", + "error-invalid-date": "Geçersiz tarih!", + "error-invalid-description": "Geçersiz açıklama!", + "error-invalid-domain": "Geçersiz alan adı!", + "error-invalid-email": "Geçersiz e-posta {{email}}!", + "error-invalid-email-address": "Geçersiz e-posta adresi!", + "error-invalid-file-height": "Geçersiz fotoğraf yüksekliği!", + "error-invalid-file-type": "Geçersiz dosya türü!", + "error-invalid-file-width": "Geçersiz fotoğraf genişliği!", + "error-invalid-from-address": "Geçersiz bir KİMDEN adresi bildirdiniz!", + "error-invalid-integration": "Geçersiz entegrasyon", + "error-invalid-message": "Geçersiz ileti!", + "error-invalid-method": "Geçersiz metot!", + "error-invalid-name": "Geçersiz isim!", + "error-invalid-password": "Geçersiz şifre!", + "error-invalid-redirectUri": "Geçersiz yönlendirme bağlantısı!", + "error-invalid-role": "Geçersiz rol!", + "error-invalid-room": "Geçersiz oda!", + "error-invalid-room-name": "{{room_name}}, geçerli bir oda adı değil!", + "error-invalid-room-type": "{{type}}, geçerli bir oda türü değil!", + "error-invalid-settings": "Geçersiz ayar!", + "error-invalid-subscription": "Geçersiz başvuru!", + "error-invalid-token": "Geçersiz belirteç!", + "error-invalid-triggerWords": "Geçersiz tetikleyici parametreleri!", + "error-invalid-urls": "Geçersiz URL'ler!", + "error-invalid-user": "Geçersiz kullanıcı!", + "error-invalid-username": "Geçersiz kullanıcı adı!", + "error-invalid-webhook-response": "İstek URL'si 200'den farklı bir durumla yanıt verdi!", + "error-message-deleting-blocked": "İleti silme engellendi!", + "error-message-editing-blocked": "İleti düzenleme engellendi", + "error-message-size-exceeded": "İleti boyutu, Message_MaxAllowedSize değerini aşıyor", + "error-missing-unsubscribe-link": "[unsubscribe] bağlantısını belirtmelisiniz.", + "error-no-tokens-for-this-user": "Bu kullanıcı için belirteç (token) yok", + "error-not-allowed": "İzin verilmedi", + "error-not-authorized": "Yetkili değil", + "error-push-disabled": "Push devre dışı", + "error-remove-last-owner": "Lütfen bunu kaldırmadan önce yeni bir sahip belirleyin.", + "error-role-in-use": "Rol kullanımda olduğu için silinemiyor", + "error-role-name-required": "Rol adı gerekli", + "error-the-field-is-required": "{{field}} alanı gereklidir.", + "error-too-many-requests": "Hata, çok fazla istek. Lütfen yavaşla. Tekrar denemeden önce {{seconds}} saniye beklemelisiniz.", + "error-user-is-not-activated": "Kullanıcı etkinleştirilmedi!", + "error-user-has-no-roles": "Kullanıcıya tanımlı rol yok!", + "error-user-limit-exceeded": "#channel_name kanalına davet etmeye çalıştığınız kullanıcıların sayısı, yönetici tarafından belirlenen sınırı aşıyor!", + "error-user-not-in-room": "Kullanıcı bu odada değil!", + "error-user-registration-custom-field": "error-user-registration-custom-field", + "error-user-registration-disabled": "Kullanıcı kaydı devre dışı bırakıldı!", + "error-user-registration-secret": "Kullanıcı kaydına yalnızca Gizli URL aracılığıyla izin verilir!", + "error-you-are-last-owner": "Son sahibi sizsiniz. Lütfen odadan ayrılmadan önce yeni bir sahip belirleyin.", + "Actions": "İşlemler", + "activity": "etkinlik", + "Activity": "Etkinlik", + "Add_Reaction": "Tepki ekle", + "Add_Server": "Sunucu ekle", + "Add_users": "Kullanıcı ekle", + "Admin_Panel": "Yönetim Paneli", + "Agent": "Temsilci", + "Alert": "Uyarı", + "alert": "uyarı", + "alerts": "uyarılar", + "All_users_in_the_channel_can_write_new_messages": "Kanaldaki tüm kullanıcılar yeni ileti yazabilir!", + "A_meaningful_name_for_the_discussion_room": "Tartışma odası için anlamlı bir isim", + "All": "Tümü", + "All_Messages": "Tüm İletiler", + "Allow_Reactions": "Tüm Tepkiler", + "Alphabetical": "Alfabetik", + "and_more": "ve daha", + "and": "ve", + "announcement": "duyuru", + "Announcement": "Duyuru", + "Apply_Your_Certificate": "Sertifikanızı Onaylayın", + "ARCHIVE": "ARŞİVLE", + "archive": "arşivle", + "are_typing": "yazıyor", + "Are_you_sure_question_mark": "Emin misiniz?", + "Are_you_sure_you_want_to_leave_the_room": "{{room}} odasından ayrılmak istediğinizden emin misiniz?", + "Audio": "Ses", + "Authenticating": "Doğrulanıyor", + "Automatic": "Otomatik", + "Auto_Translate": "Otomatik Çevir", + "Avatar_changed_successfully": "Profil fotoğrafı başarıyla değiştirildi!", + "Avatar_Url": "Profil fotoğrafı URL'si", + "Away": "Uzakta", + "Back": "Geri", + "Black": "Koyu", + "Block_user": "Kullanıcıyı engelle", + "Browser": "Tarayıcı", + "Broadcast_channel_Description": "Yalnızca yetkili kullanıcılar yeni ileti yazabilir, ancak diğer kullanıcılar yanıt verebilir", + "Broadcast_Channel": "Kanala Yayınla", + "Busy": "Meşgul", + "By_proceeding_you_are_agreeing": "Devam ederek kabul ediyorsunuz: ", + "Cancel_editing": "Düzenlemeyi iptal et", + "Cancel_recording": "Kaydı iptal et", + "Cancel": "İptal et", + "changing_avatar": "profil fotoğrafı değiştirme", + "creating_channel": "kanal açılıyor", + "creating_invite": "davet üretiliyor", + "Channel_Name": "Kanal Adı", + "Channels": "Kanallar", + "Chats": "Sohbetler", + "Call_already_ended": "Çağrı sona erdi!", + "Clear_cookies_alert": "Tüm çerezleri temizlemek istiyor musunuz?", + "Clear_cookies_desc": "Bu işlem, tüm oturum açma çerezlerini temizleyerek diğer hesaplara giriş yapmanıza olanak tanır.", + "Clear_cookies_yes": "Evet, çerezleri temizle", + "Clear_cookies_no": "Hayır, çerezleri koru", + "Click_to_join": "Katılmak için tıklayın!", + "Close": "Kapat", + "Close_emoji_selector": "Emoji seçiciyi kapat", + "Closing_chat": "Sohbet kapatılıyor", + "Change_language_loading": "Dil değiştiriliyor", + "Chat_closed_by_agent": "Sohbet temsilci tarafından kapatıldı", + "Choose": "Seç", + "Choose_from_library": "Kütüphaneden seç", + "Choose_file": "Dosya seç", + "Choose_where_you_want_links_be_opened": "Bağlantıların açılmasını istediğiniz yeri seçin", + "Code": "Kod", + "Code_or_password_invalid": "Kod veya şifre geçersiz", + "Collaborative": "İşbirlikçi", + "Confirm": "Onayla", + "Connect": "Bağlan", + "Connected": "Bağlandı", + "connecting_server": "sunucuya bağlanıyor", + "Connecting": "Bağlanıyor...", + "Contact_us": "Bize ulaşın", + "Contact_your_server_admin": "Sunucu yöneticisiyle iletişime geçin.", + "Continue_with": "Devam et:", + "Copied_to_clipboard": "Panoya kopyalandı!", + "Copy": "Kopyala", + "Conversation": "Sohbet", + "Permalink": "Kalıcı bağlantı", + "Certificate_password": "Sertifika Şifresi", + "Clear_cache": "Yerel sunucu önbelleğini temizleyin", + "Clear_cache_loading": "Önbellek temizleniyor.", + "Whats_the_password_for_your_certificate": "Sertifikanızın şifresi nedir?", + "Create_account": "Hesap oluştur", + "Create_Channel": "Kanal Oluştur", + "Create_Direct_Messages": "Özel İleti Oluştur", + "Create_Discussion": "Tartışma Oluştur", + "Created_snippet": "kalıp oluşturdu", + "Create_a_new_workspace": "Çalışma alanı oluştur", + "Create": "Oluştur", + "Custom_Status": "Özelleştirilmiş durum", + "Dark": "Karanlık", + "Dark_level": "Karanlık Seviyesi", + "Default": "Varsayılan", + "Default_browser": "Varsayılan tarayıcı", + "Delete_Room_Warning": "Bir odanın silinmesi, oda içinde yayınlanan tüm iletileri silecektir. Bu geri alınamaz.", + "Department": "Bölüm", + "delete": "sil", + "Delete": "Sil", + "DELETE": "SİL", + "deleting_room": "oda siliniyor", + "description": "açıklama", + "Description": "Açıklama", + "Desktop_Options": "Masaüstü Seçenekleri", + "Desktop_Notifications": "Masaüstü Bildirimleri", + "Desktop_Alert_info": "Bu bildirimler masaüstünde teslim edilir", + "Directory": "Dizin", + "Direct_Messages": "Özel İletiler", + "Disable_notifications": "Bildirimleri devre dışı bırak", + "Discussions": "Tartışmalar", + "Discussion_Desc": "Neler olup bittiğini gözden geçirmeye yardımcı olun! Bir tartışma oluşturarak, seçtiğinizin bir alt kanalı oluşturulur ve her ikisi de bağlanır.", + "Discussion_name": "Tartışma adı", + "Done": "Tamamlandı", + "Dont_Have_An_Account": "Hesabın yok mu?", + "Do_you_have_an_account": "Hesabın var mı?", + "Do_you_have_a_certificate": "Sertifikanız var mı?", + "Do_you_really_want_to_key_this_room_question_mark": "Bu odayı gerçekten {{key}} istiyor musun?", + "E2E_Encryption": "Uçtan Uca Şifreleme (E2E)", + "E2E_How_It_Works_info1": "Artık şifrelenmiş özel gruplar ve doğrudan iletiler oluşturabilirsiniz. Mevcut özel grupları veya özel iletileri şifreli olarak da değiştirebilirsiniz.", + "E2E_How_It_Works_info2": "Bu, *Uçtan Uca Şifreleme'dir (E2E)*, böylece iletilerinizi şifrelemek / çözmek için anahtar ve bunlar sunucuya kaydedilmez. Bu nedenle *bu şifreyi daha sonra ihtiyaç duyduğunuzda erişebileceğiniz güvenli bir yerde* saklamanız gerekir.", + "E2E_How_It_Works_info3": "Devam ederseniz, otomatik olarak bir Uçtan Uca Şifreleme (E2E) şifresi oluşturulacaktır.", + "E2E_How_It_Works_info4": "Mevcut Uçtan Uca Şifreleme (E2E) şifresini, herhangi bir tarayıcıdan istediğiniz zaman girdiğiniz şifreleme anahtarınız için yeni bir şifre de ayarlayabilirsiniz.", + "edit": "düzenle", + "edited": "düzenlendi", + "Edit": "Düzenle", + "Edit_Status": "Durumu Düzenle", + "Edit_Invite": "Daveti Düzenle", + "End_to_end_encrypted_room": "Uçtan uca şifreli oda", + "end_to_end_encryption": "uçtan uca şifreleme", + "Email_Notification_Mode_All": "Tüm Bahsetmeler/Özel İletiler", + "Email_Notification_Mode_Disabled": "Devre Dışı", + "Email_or_password_field_is_empty": "E-posta veya şifre alanı boş", + "Email": "E-posta", + "email": "e-posta", + "Empty_title": "Boş başlık", + "Enable_Auto_Translate": "Otomatik Çeviriyi Etkinleştir", + "Enable_notifications": "Bildirimleri etkinleştir", + "Encrypted": "Şifreli", + "Encrypted_message": "Şifreli ileti", + "Enter_Your_E2E_Password": "Uçtan Uca Şifreleme (E2E) Şifrenizi Girin", + "Enter_Your_Encryption_Password_desc1": "Bu, şifrelenmiş özel gruplarınıza ve doğrudan iletilerinize erişmenize izin verecektir.", + "Enter_Your_Encryption_Password_desc2": "Sohbeti kullandığınız her yerde iletileri şifrelemek / çözmek için şifre girmeniz gerekir.", + "Encryption_error_title": "Şifreleme şifreniz yanlış görünüyor", + "Encryption_error_desc": "İçe aktarılacak şifreleme anahtarınızın kodu çözülemedi.", + "Everyone_can_access_this_channel": "Bu kanala herkes erişebilir", + "Error_uploading": "Yükleme hatası", + "Expiration_Days": "Geçerlilik Süresi (Gün)", + "Favorite": "Favori", + "Favorites": "Favoriler", + "Files": "Dosyalar", + "File_description": "Dosya açıklaması", + "File_name": "Dosya adı", + "Finish_recording": "Kaydı bitir", + "Following_thread": "Konu takip ediliyor", + "For_your_security_you_must_enter_your_current_password_to_continue": "Güvenliğiniz için, devam etmek için mevcut şifrenizi girmelisiniz", + "Forgot_password_If_this_email_is_registered": "Bu e-posta kayıtlıysa, şifrenizi nasıl sıfırlayacağınıza dair talimatlar göndereceğiz. Kısa süre içinde bir e-posta almazsanız, lütfen geri gelin ve tekrar deneyin.", + "Forgot_password": "Parolanızı mı unuttunuz?", + "Forgot_Password": "Parolamı Unuttum", + "Forward": "İlet", + "Forward_Chat": "Sohbete İlet", + "Forward_to_department": "Bölüme İlet", + "Forward_to_user": "Kullanıcıya İlet", + "Full_table": "Tam tabloyu görmek için tıklayın", + "Generate_New_Link": "Yeni Bağlantı Oluştur", + "Group_by_favorites": "Favorilere göre grupla", + "Group_by_type": "Türe göre grupla", + "Hide": "Gizle", + "Has_joined_the_channel": "kanala katıldı", + "Has_joined_the_conversation": "sohbete katıldı", + "Has_left_the_channel": "kanaldan ayrıldı", + "Hide_System_Messages": "Sistem İletilerını Gizle", + "Hide_type_messages": "\"{{type}}\" iletilerini gizle", + "How_It_Works": "Nasıl Çalışır", + "Message_HideType_uj": "Kullanıcı Katıldı", + "Message_HideType_ul": "Kullanıcı Ayrıldı", + "Message_HideType_ru": "Kullanıcı Kaldırıldı", + "Message_HideType_au": "Kullanıcı Eklendi", + "Message_HideType_mute_unmute": "Kullanıcı Sesi Kapatıldı / Sesi Açıldı", + "Message_HideType_r": "Oda Adı Değiştirildi", + "Message_HideType_ut": "Kullanıcı Sohbete Katıldı", + "Message_HideType_wm": "Hoşgeldiniz", + "Message_HideType_rm": "İleti Kaldırıldı", + "Message_HideType_subscription_role_added": "Rol Belirlendi", + "Message_HideType_subscription_role_removed": "Artık Kullanılmayan Rol", + "Message_HideType_room_archived": "Oda Arşivlendi", + "Message_HideType_room_unarchived": "Oda Arşivden Çıkarıldı", + "I_Saved_My_E2E_Password": "Uçtan Uca Şifreleme (E2E) Şifremi Kaydettim", + "IP": "IP", + "In_app": "Uygulama İçi", + "In_App_And_Desktop": "Uygulama İçi ve Masaüstü", + "In_App_and_Desktop_Alert_info": "Uygulama açıkken ekranın üst kısmında bir başlık görüntüler ve masaüstünde bir bildirim görüntüler", + "Invisible": "Görünmez", + "Invite": "Davet Et", + "is_a_valid_RocketChat_instance": "geçerli bir Rocket.Chat örneği", + "is_not_a_valid_RocketChat_instance": "geçerli bir Rocket.Chat örneği değil", + "is_typing": "yazıyor", + "Invalid_or_expired_invite_token": "Geçersiz veya süresi dolmuş davet belirteci (token)", + "Invalid_server_version": "Bağlanmaya çalıştığınız sunucu artık uygulama tarafından desteklenmeyen bir sürüm kullanıyor: {{currentVersion}}.\n\n{{minVersion}} sürümüne ihtiyacımız var", + "Invite_Link": "Davet Bağlantısı", + "Invite_users": "Kullanıcıları davet et", + "Join": "Katıl", + "Join_Code": "Katılım Kodu", + "Insert_Join_Code": "Katılım Kodu Girin", + "Join_our_open_workspace": "Açık çalışma alanımıza katılın", + "Join_your_workspace": "Çalışma alanınıza katılın", + "Just_invited_people_can_access_this_channel": "Yalnızca davet edilen kişiler bu kanala erişebilir", + "Language": "Dil", + "last_message": "son ileti", + "Leave_channel": "Kanaldan ayrıl", + "leaving_room": "odadan ayrılıyor", + "Leave": "Odadan ayrıl", + "leave": "ayrıl", + "Legal": "Yasal", + "Light": "Açık", + "License": "Lisans", + "Livechat": "Canlı Sohbet", + "Livechat_edit": "Canlı sohbeti düzenle", + "Login": "Oturum aç", + "Login_error": "Kimlik bilgileriniz reddedildi! Lütfen tekrar deneyin.", + "Login_with": "ile giriş", + "Logging_out": "Çıkış Yapılıyor", + "Logout": "Çıkış Yap", + "Max_number_of_uses": "Maksimum kullanım sayısı", + "Max_number_of_users_allowed_is_number": "İzin verilen maksimum kullanıcı sayısı {{maxUsers}}", + "members": "üyeler", + "Members": "Üyeler", + "Mentioned_Messages": "Bahsedilen İletiler", + "mentioned": "bahsedildi", + "Mentions": "Bahsetmeler", + "Message_accessibility": "{{user}} tarafından {{time}} itibarıyla ileti: {{message}}", + "Message_actions": "İleti işlemleri", + "Message_pinned": "İleti sabitlendi", + "Message_removed": "İleti kaldırıldı", + "Message_starred": "İletia yıldız eklendi", + "Message_unstarred": "İletiın yıldızı kaldırıldı", + "message": "ileti", + "messages": "iletiler", + "Message": "İleti", + "Messages": "İletiler", + "Message_Reported": "İleti bildirildi", + "Microphone_Permission_Message": "Rocket.Chat'in mikrofonunuza erişmesi gerekiyor, böylece sesli ileti gönderebilirsiniz.", + "Microphone_Permission": "Mikrofon İzni", + "Mute": "Sessize Al", + "muted": "sessize alındı", + "My_servers": "Sunucularım", + "N_people_reacted": "{{n}} kişi tepki verdi", + "N_users": "{{n}} kullanıcı", + "name": "isim", + "Name": "İsim", + "Navigation_history": "Gezinti geçmişi", + "Never": "Asla", + "New_Message": "Yeni İleti", + "New_Password": "Yeni Şifre", + "New_Server": "Yeni Sunucu", + "Next": "Sonraki", + "No_files": "Dosya yok", + "No_limit": "Limit yok", + "No_mentioned_messages": "Belirtilen ileti yok", + "No_pinned_messages": "Sabitlenmiş ileti yok", + "No_results_found": "Sonuç bulunamadı", + "No_starred_messages": "Yıldızlı ileti yok", + "No_thread_messages": "Konu iletisi yok", + "No_label_provided": "Hiç {{label}} sağlanmadı.", + "No_Message": "İleti Yok", + "No_messages_yet": "Şu ana kadar ileti yok", + "No_Reactions": "Tepki Yok", + "No_Read_Receipts": "Okundu Bilgisi Yok", + "Not_logged": "Kayıtlı değil", + "Not_RC_Server": "Bu bir Rocket.Chat sunucusu değil.\n{{contact}}", + "Nothing": "Hiçbir Şey", + "Nothing_to_save": "Kaydedilecek bir şey yok!", + "Notify_active_in_this_room": "Bu odadaki çevrimiçi kullanıcıları bilgilendir", + "Notify_all_in_this_room": "Bu odadaki herkesi bilgilendir", + "Notifications": "Bildirimler", + "Notification_Duration": "Bildirim Süresi", + "Notification_Preferences": "Bildirim Tercihleri", + "No_available_agents_to_transfer": "Aktarılacak yemsilci yok", + "Offline": "Çevrimdışı", + "Oops": "Ahh!", + "Omnichannel": "Çoklu Kanal", + "Open_Livechats": "Devam Eden Sohbetler", + "Omnichannel_enable_alert": "Çoklu Kanal'da mevcut değilsiniz. Erişilebilir olmak ister misiniz?", + "Onboarding_description": "Çalışma alanı, ekibinizin veya kuruluşunuzun işbirliği alanıdır. Çalışma alanı yöneticisinden bir ekibe katılmak veya bir ekip oluşturmak için yardım isteyin.", + "Onboarding_join_workspace": "Bir çalışma alanına katılın", + "Onboarding_subtitle": "Ekip İşbirliğinin Ötesinde", + "Onboarding_title": "Rocket.Chat'e hoş geldiniz", + "Onboarding_join_open_description": "Rocket.Chat ekibi ve topluluğu ile sohbet etmek için açık çalışma alanımıza katılın.", + "Onboarding_agree_terms": "Devam ederek Rocket.Chat'i kabul etmiş olursunuz", + "Onboarding_less_options": "Daha az seçenek", + "Onboarding_more_options": "Daha çok seçenek", + "Online": "Çevrimiçi", + "Only_authorized_users_can_write_new_messages": "Yalnızca yetkili kullanıcılar yeni ileti yazabilir", + "Open_emoji_selector": "Emoji seçiciyi aç", + "Open_Source_Communication": "Açık Kaynak İletişim", + "Open_your_authentication_app_and_enter_the_code": "Kimlik doğrulama uygulamanızı açın ve kodu girin.", + "OR": "OR", + "OS": "OS", + "Overwrites_the_server_configuration_and_use_room_config": "Sunucu yapılandırmasının üzerine yazar ve oda yapılandırmasını kullanır", + "Password": "Şifre", + "Parent_channel_or_group": "Üst kanal veya grup", + "Permalink_copied_to_clipboard": "Kalıcı bağlantı panoya kopyalandı!", + "Phone": "Telefon", + "Pin": "Sabitle", + "Pinned_Messages": "Sabitlenen İletiler", + "pinned": "sabitlendi", + "Pinned": "Sabitlendi", + "Please_add_a_comment": "Lütfen bir yorum ekleyin", + "Please_enter_your_password": "Lütfen şifrenizi giriniz", + "Please_wait": "Lütfen bekle.", + "Preferences": "Tercihler", + "Preferences_saved": "Tercihler kaydedildi!", + "Privacy_Policy": " Privacy Policy", + "Private_Channel": "Özel Kanal", + "Private": "Özel", + "Processing": "İşleniyor...", + "Profile_saved_successfully": "Profil başarıyla kaydedildi!", + "Profile": "Profil", + "Public_Channel": "Herkese Açık Kanal", + "Public": "Herkese Açık", + "Push_Notifications": "Anlık Bildirimler", + "Push_Notifications_Alert_Info": "Bu bildirimler, uygulama açık olmadığında size teslim edilir", + "Quote": "Alıntı", + "Reactions_are_disabled": "Tepkiler devre dışı bırakıldı", + "Reactions_are_enabled": "Tepkiler etkinleştirildi", + "Reactions": "Tepkiler", + "Read": "Oku", + "Read_External_Permission_Message": "Rocket.Chat'in cihazınızdaki fotoğraflara, medyaya ve dosyalara erişmesi gerekiyor", + "Read_External_Permission": "Medya Okuma İzni ", + "Read_Only_Channel": "Yazma Kısıtlı Kanal", + "Read_Only": "Yazma Kısıtlı", + "Read_Receipt": "Okundu Bilgisi", + "Receive_Group_Mentions": "Grup Bahsetmelerini Al", + "Receive_Group_Mentions_Info": "Grup bahsetmelerini al", + "Register": "Kayıt Ol", + "Repeat_Password": "Şifre Tekrarı", + "Replied_on": "Yanıtlandı:", + "replies": "yanıtlar", + "reply": "yanıtla", + "Reply": "Yanıtla", + "Report": "Bildir", + "Receive_Notification": "Bildirim Al", + "Receive_notifications_from": "{{name}} bildirimlerini alın", + "Resend": "Yeniden yolla", + "Reset_password": "Şifre sıfırla", + "resetting_password": "şifre sıfırlanıyor", + "RESET": "SIFIRLA", + "Return": "Geri dön", + "Review_app_title": "Uygulama hoşunuza gitti mi?", + "Review_app_desc": "{{store}} üzerinde bize 5 yıldız verin", + "Review_app_yes": "Elbette!", + "Review_app_no": "Hayır", + "Review_app_later": "Belki daha sonra", + "Review_app_unable_store": "{{store}} açılamıyor!", + "Review_this_app": "Bu uygulamayı değerlendirin", + "Remove": "Kaldır", + "Roles": "Roller", + "Room_actions": "Oda işlemleri", + "Room_changed_announcement": "Oda duyurusu, {{userBy}} tarafından {{announcement}} olarak değiştirildi", + "Room_changed_avatar": "Oda profil fotoğrafı {{userBy}} tarafından değiştirildi", + "Room_changed_description": "Oda açıklaması, {{userBy}} tarafından {{description}} olarak değiştirildi", + "Room_changed_topic": "Oda konusu, {{userBy}} tarafından {{topic}} olarak değiştirildi", + "Room_Files": "Oda Dosyaları", + "Room_Info_Edit": "Oda Bilgilerini Düzenle", + "Room_Info": "Oda Bilgisi", + "Room_Members": "Oda Üyeleri", + "Room_name_changed": "Oda adı, {{userBy}} tarafından {{name}} olarak değiştirildi", + "SAVE": "KAYDET", + "Save_Changes": "Değişiklikleri Kaydet", + "Save": "Kaydet", + "Saved": "Kaydedildi", + "saving_preferences": "tercihler kaydediliyor", + "saving_profile": "profil kaydediliyor", + "saving_settings": "ayarlar kaydediliyor", + "saved_to_gallery": "Galeriye kaydedildi", + "Save_Your_E2E_Password": "(E2E) Şifrenizi Kaydedin", + "Save_Your_Encryption_Password": "Şifreleme Parolanızı Kaydedin", + "Save_Your_Encryption_Password_warning": "Bu parola hiçbir yerde saklanmadığından başka bir yere dikkatlice kaydedin.", + "Save_Your_Encryption_Password_info": "Parolanızı kaybettiğinizde, kurtarmanın bir yolu olmadığını ve iletilerinize erişiminizi kaybedeceğinizi unutmayın.", + "Search_Messages": "İleti ara", + "Search": "Ara", + "Search_by": "Ara", + "Search_global_users": "Global kullanıcıları ara", + "Search_global_users_description": "Açarsanız, diğer şirketlerden veya sunuculardan herhangi bir kullanıcıyı arayabilirsiniz.", + "Seconds": "{{second}} saniye", + "Security_and_privacy": "Güvenlik ve gizlilik", + "Select_Avatar": "Profil resmi seç", + "Select_Server": "Sunucu seç", + "Select_Users": "Kullanıcıları seç", + "Select_a_Channel": "Kanal Seç", + "Select_a_Department": "Bölüm Seç", + "Select_an_option": "Bir seçenek seçin", + "Select_a_User": "Kullanıcı Seç", + "Send": "Yolla", + "Send_audio_message": "Sesli ileti gönder", + "Send_crash_report": "Çökme raporu gönder", + "Send_message": "İleti gönder", + "Send_me_the_code_again": "Kodu tekrar gönder", + "Send_to": "Gönderiliyor...", + "Sending_to": "Gönderiliyor:", + "Sent_an_attachment": "Bir ek gönderildi", + "Server": "Sunucu", + "Servers": "Sunucular", + "Server_version": "Sunucu versiyonu: {{version}}", + "Set_username_subtitle": "Kullanıcı adı başkalarının iletilerde sizden bahsetmesine izin vermek için kullanılır", + "Set_custom_status": "Özelleştirilmiş durumu ayarlayın", + "Set_status": "Durumu ayarla", + "Status_saved_successfully": "Durum başarıyla kaydedildi!", + "Settings": "Ayarlar", + "Settings_succesfully_changed": "Ayarlar başarıyla değiştirildi!", + "Share": "Paylaş", + "Share_Link": "Bağlantı paylaş", + "Share_this_app": "Bu uygulamayı paylaş", + "Show_more": "Daha fazla göster..", + "Show_Unread_Counter": "Okunmamış Sayacını Göster", + "Show_Unread_Counter_Info": "Okunmamış sayacı, listede kanalın sağ tarafında bir rozet olarak görüntülenir", + "Sign_in_your_server": "Sunucunuzda oturum açın", + "Sign_Up": "Kaydol", + "Some_field_is_invalid_or_empty": "Bazı alanlar geçersiz veya boş", + "Sorting_by": "{{key}} göre sıralanıyor", + "Sound": "Ses", + "Star_room": "Odayı Yıldızla", + "Star": "Yıldızla", + "Starred_Messages": "Yıldızlı İletiler", + "starred": "yıldızlandı", + "Starred": "Yıldızlandı", + "Start_of_conversation": "Konuşma başlangıcı", + "Start_a_Discussion": "Tartışma Başlat", + "Started_discussion": "Bir tartışma başlattı:", + "Started_call": "Arama {{userBy}} tarafından başlatıldı", + "Submit": "Kaydet", + "Table": "Tablo", + "Tags": "Etiketler", + "Take_a_photo": "Fotoğraf çek", + "Take_a_video": "Video çek", + "Take_it": "Al!", + "tap_to_change_status": "durumu değiştirmek için dokunun", + "Tap_to_view_servers_list": "Sunucu listesini görüntülemek için dokunun", + "Terms_of_Service": " Kullanım Şartları ", + "Theme": "Tema", + "The_user_wont_be_able_to_type_in_roomName": "Kullanıcı {{roomName}} içinde yazamayacak", + "The_user_will_be_able_to_type_in_roomName": "Kullanıcı {{roomName}} içinde yazabilecek", + "There_was_an_error_while_action": "{{action}} sırasında bir hata oluştu!", + "This_room_is_blocked": "Bu oda engellendi", + "This_room_is_read_only": "Bu oda yazma kısıtlı", + "Thread": "Başlık", + "Threads": "Başlıklar", + "Timezone": "Saat dilimi", + "To": "Kime", + "topic": "konu", + "Topic": "Konu", + "Translate": "Çevir", + "Try_again": "Tekrar deneyin", + "Two_Factor_Authentication": "İki faktörlü Kimlik Doğrulama", + "Type_the_channel_name_here": "Kanal adını buraya yazın", + "unarchive": "arşivden çıkar", + "UNARCHIVE": "ARŞİVDEN ÇIKAR", + "Unblock_user": "Kullanıcının engelini kaldır", + "Unfavorite": "Favorilerden Çıkar", + "Unfollowed_thread": "Takip edilmeyen başlık", + "Unmute": "Sesi Aç", + "unmuted": "Sesi Açıldı", + "Unpin": "Sabitlemeyi kaldır", + "unread_messages": "okunmamış", + "Unread": "Okunmamış", + "Unread_on_top": "Okunmamışlar üstte", + "Unstar": "Yıldızı kaldır", + "Updating": "Güncelleniyor...", + "Uploading": "Gönderiliyor", + "Upload_file_question_mark": "Dosya gönderilsin mi?", + "User": "Kullanıcı", + "Users": "Kullanıcılar", + "User_added_by": "{{userAdded}} adlı kullanıcı {{userBy}} tarafından eklendi", + "User_Info": "Kullanıcı bilgisi", + "User_has_been_key": "Kullanıcı {{key}} olmuştur", + "User_is_no_longer_role_by_": "{{user}} artık {{role}} değil ({{userBy}} tarafından)", + "User_muted_by": "{{userMuted}} adlı kullanıcının sesini {{userBy}} kapattı", + "User_removed_by": "{{userRemoved}} kullanıcısı {{userBy}} tarafından kaldırıldı", + "User_sent_an_attachment": "{{user}} bir ek gönderdi", + "User_unmuted_by": "{{userUnmuted}} kullanıcısının sesi {{userBy}} tarafından açıldı", + "User_was_set_role_by_": "{{user}}, {{userBy}} tarafından {{role}} ayarlandı", + "Username_is_empty": "Kullanıcı adı boş!", + "Username": "Kullanıcı adı", + "Username_or_email": "Kullanıcı adı ya da e-posta", + "Uses_server_configuration": "Sunucu yapılandırmasını kullanır", + "Validating": "Doğrulanıyor", + "Registration_Succeeded": "Kayıt Başarılı!", + "Verify": "Onayla", + "Verify_email_title": "Kaydınızı onaylayın!", + "Verify_email_desc": "Kaydınızı onaylamak için size bir e-posta gönderdik. Kısa süre içinde bir e-posta almazsanız, lütfen geri gelin ve tekrar deneyin.", + "Verify_your_email_for_the_code_we_sent": "Gönderdiğimiz kod için e-postanızı doğrulayın", + "Video_call": "Görüntülü arama", + "View_Original": "Orijinali Görüntüle", + "Voice_call": "Sesli arama", + "Waiting_for_network": "Ağ bağlantısı bekleniyor ...", + "Websocket_disabled": "Bu sunucu için Websocket devre dışı bırakıldı.\n{{contact}}", + "Welcome": "Hoşgeldiniz", + "What_are_you_doing_right_now": "Şu an ne yapıyorsun?", + "Whats_your_2fa": "2 faktör doğrulama (2FA) kodunuz nedir?", + "Without_Servers": "Sunucusuz", + "Workspaces": "Çalışma alanları", + "Would_you_like_to_return_the_inquiry": "Başvuruyu geri çevirmek ister misiniz?", + "Write_External_Permission_Message": "Rocket.Chat'in galerinize erişmesi gerekiyor, böylece resimleri kaydedebilirsiniz.", + "Write_External_Permission": "Galeri İzni", + "Yes": "Evet", + "Yes_action_it": "Evet, {{action}}!", + "Yesterday": "Dün", + "You_are_in_preview_mode": "İzleme modundasınız", + "You_are_offline": "Çevrimdışısınız", + "You_can_search_using_RegExp_eg": "Düzenli ifadeleri (Regular Expressions) kullanabilirsiniz. Örneğin: `/^text$/i`", + "You_colon": "Siz: ", + "you_were_mentioned": "senden bahsedildi", + "You_were_removed_from_channel": "{{channel}} kanalından çıkarıldınız", + "you": "siz", + "You": "Siz", + "Logged_out_by_server": "Sunucu tarafından çıkış yaptınız. Lütfen tekrar giriş yapın.", + "You_need_to_access_at_least_one_RocketChat_server_to_share_something": "Bir şeyler paylaşmak için Rocket.Chat sunucusuna erişmeniz gerekir.", + "You_need_to_verifiy_your_email_address_to_get_notications": "Bildirim almak için e-posta adresinizi doğrulamanız gerekiyor", + "Your_certificate": "Sertifikanız", + "Your_invite_link_will_expire_after__usesLeft__uses": "Davet bağlantınızın geçerliliği {{usesLeft}} kullanımdan sonra sona erecek.", + "Your_invite_link_will_expire_on__date__or_after__usesLeft__uses": "Davet bağlantınızın geçerliliği {{date}} tarihinde veya {{usesLeft}} kullanımdan sonra sona erecek.", + "Your_invite_link_will_expire_on__date__": "Davet bağlantınızın geçerlilik süresi {{date}} tarihinde sona erecek.", + "Your_invite_link_will_never_expire": "Davet bağlantınızın geçerlilik süresi asla dolmayacak.", + "Your_workspace": "Çalışma alanınız", + "Your_password_is": "Şifreniz", + "Version_no": "Versiyon: {{version}}", + "You_will_not_be_able_to_recover_this_message": "Bu iletiyi kurtaramayacaksınız!", + "You_will_unset_a_certificate_for_this_server": "Bu sunucu için bir sertifika ayarını kaldıracaksınız", + "Change_Language": "Dili değiştir", + "Crash_report_disclaimer": "Sohbetlerinizin içeriğini asla takip etmiyoruz. Çökme raporu ve analiz olayları, sorunları tanımlamak ve düzeltmek için yalnızca bizim için ilgili bilgileri içerir.", + "Type_message": "İleti yaz", + "Room_search": "Oda arama", + "Room_selection": "Oda seçimi 1...9", + "Next_room": "Sonraki oda", + "Previous_room": "Önceki oda", + "New_room": "Yeni oda", + "Upload_room": "Odaya yükle", + "Search_messages": "İletilerda ara", + "Scroll_messages": "İletilerı kaydır", + "Reply_latest": "Sonuncuyu yanıtla", + "Reply_in_Thread": "Konu içinde cevapla", + "Server_selection": "Sunucu seçimi", + "Server_selection_numbers": "Sunucu seçimi 1...9", + "Add_server": "Sunucu ekle", + "New_line": "Yeni satır", + "You_will_be_logged_out_of_this_application": "Bu uygulamadan çıkış yapacaksınız.", + "Clear": "Temizle", + "This_will_clear_all_your_offline_data": "Bu, tüm çevrimdışı verilerinizi temizleyecektir.", + "This_will_remove_all_data_from_this_server": "Bu, bu sunucudaki tüm verileri kaldıracaktır.", + "Mark_unread": "Okunmadı olarak işaretle", + "Wait_activation_warning": "Giriş yapmadan önce, hesabınız bir yönetici tarafından manuel olarak etkinleştirilmelidir.", + "Screen_lock": "Ekran kilidi", + "Local_authentication_biometry_title": "Doğrula", + "Local_authentication_biometry_fallback": "Parola kullan", + "Local_authentication_unlock_option": "Şifre ile Kilidi Açın", + "Local_authentication_change_passcode": "Parolayı Değiştir", + "Local_authentication_info": "Not: Parolayı unutursanız, uygulamayı silmeniz ve yeniden yüklemeniz gerekir.", + "Local_authentication_facial_recognition": "yüz tanıma", + "Local_authentication_fingerprint": "parmak izi", + "Local_authentication_unlock_with_label": "{{label}} ile kilidi açın", + "Local_authentication_auto_lock_60": "1 dakika sonra", + "Local_authentication_auto_lock_300": "5 dakika sonra", + "Local_authentication_auto_lock_900": "15 dakika sonra", + "Local_authentication_auto_lock_1800": "30 dakika sonra", + "Local_authentication_auto_lock_3600": "1 saat sonra", + "Passcode_enter_title": "Şifrenizi giriniz", + "Passcode_choose_title": "Yeni şifrenizi yazın", + "Passcode_choose_confirm_title": "Yeni şifrenizi onaylayın", + "Passcode_choose_error": "Parolalar eşleşmiyor. Tekrar deneyin.", + "Passcode_choose_force_set": "Yönetici tarafından istenen şifre", + "Passcode_app_locked_title": "Uygulama kilitlendi", + "Passcode_app_locked_subtitle": "{{timeLeft}} saniye içinde tekrar deneyin", + "After_seconds_set_by_admin": "{{seconds}} saniye sonra (yönetici tarafından belirlenir)", + "Dont_activate": "Şimdi etkinleştirme", + "Queued_chats": "Sıralı sohbetler", + "Queue_is_empty": "Sıra boş", + "Logout_from_other_logged_in_locations": "Giriş yapılan diğer konumlardan çıkış yap", + "You_will_be_logged_out_from_other_locations": "Diğer konumlardan çıkış yapacaksınız.", + "Logged_out_of_other_clients_successfully": "Diğer istemcilerden başarıyla çıkış yapıldı", + "Logout_failed": "Oturum kapatma başarısız oldu!", + "Log_analytics_events": "Olayları günlüğe kaydet", + "E2E_encryption_change_password_title": "Şifreleme Parolasını Değiştir", + "E2E_encryption_change_password_description": "Artık şifrelenmiş özel gruplar ve doğrudan iletiler oluşturabilirsiniz. Mevcut özel grupları veya özel iletileri şifreli olarak da değiştirebilirsiniz. \nBu uçtan uca şifrelemedir, bu nedenle iletilerinizi şifrelemek / çözmek için kullanılan anahtar sunucuya kaydedilmez. Bu nedenle şifrenizi güvenli bir yerde saklamanız gerekir. Uçtan uca şifrelemeyi (E2E) kullanmak istediğiniz diğer cihazlara girmeniz istenecektir.", + "E2E_encryption_change_password_error": "Uçtan uca şifreleme (E2E) anahtar şifresi değiştirilirken hata!", + "E2E_encryption_change_password_success": "Uçtan uca şifreleme (E2E) anahtar şifresi başarıyla değiştirildi!", + "E2E_encryption_change_password_message": "Dikkatlice kaydettiğinizden emin olun.", + "E2E_encryption_change_password_confirmation": "Evet, değiştir!", + "E2E_encryption_reset_title": "Uçtan uca şifreleme (E2E) Anahtarını Sıfırla", + "E2E_encryption_reset_description": "Bu seçenek mevcut uçtan uca şifreleme (E2E) anahtarınızı kaldıracak ve oturumu kapatacaktır. \nTekrar oturum açtığınızda, Rocket.Chat size yeni bir anahtar oluşturacak ve çevrimiçi olarak bir veya daha fazla üyesi olan şifreli herhangi bir odaya erişiminizi geri yükleyecektir. \nUçtan uca şifrelemenin (E2E) doğası gereği Rocket.Chat, çevrimiçi üyesi olmayan hiçbir şifreli odaya erişimi geri yükleyemez.", + "E2E_encryption_reset_button": "Uçtan uca şifreleme (E2E) anahtarını sıfırla", + "E2E_encryption_reset_error": "Uçtan uca şifreleme (E2E) anahtarı sıfırlanırken hata!", + "E2E_encryption_reset_message": "Oturumunuz kapatılacak!", + "E2E_encryption_reset_confirmation": "Evet, sıfırla", + "Following": "Takip ediliyor", + "Threads_displaying_all": "Tüm konular görüntüleniyor", + "Threads_displaying_following": "Takip edilen konular görüntüleniyor", + "Threads_displaying_unread": "Okunmamış konular görüntüleniyor", + "No_threads": "Konu yok", + "No_threads_following": "Herhangi bir konuyu takip etmiyorsunuz", + "No_threads_unread": "Okunmamış konu yok", + "Messagebox_Send_to_channel": "Kanala gönder", + "Remove_from_room": "Odadan çıkar", + "Ignore": "Yok say", + "Unignore": "Yok sayma", + "User_has_been_ignored": "Kullanıcı yok sayıldı.", + "User_has_been_unignored": "Kullanıcı artık yok sayılmıyor.", + "User_has_been_removed_from_s": "Kullanıcı {{s}} alanından kaldırıldı.", + "User__username__is_now_a_leader_of__room_name_": "{{username}} kullanıcısı artık {{room_name}} lideridir.", + "User__username__is_now_a_moderator_of__room_name_": "{{username}} kullanıcısı artık bir {{room_name}} moderatörüdür.", + "User__username__is_now_a_owner_of__room_name_": "{{username}} kullanıcısı artık {{room_name}} adlı odanın sahibidir.", + "User__username__removed_from__room_name__leaders": "{{username}} adlı kullanıcı, {{room_name}} liderlerinden kaldırıldı.", + "User__username__removed_from__room_name__moderators": "{{username}} adlı kullanıcı, {{room_name}} moderatörlerinden kaldırıldı.", + "User__username__removed_from__room_name__owners": "{{username}} adlı kullanıcı, {{room_name}} sahiplerinden kaldırıldı.", + "The_user_will_be_removed_from_s": "Kullanıcı, {{s}} alanından kaldırılacak!", + "Yes_remove_user": "Evet, kullanıcıyı kaldır!", + "Direct_message": "Özel ileti", + "Message_Ignored": "İleti yok sayıldı. Görüntülemek için dokunun.", + "Enter_workspace_URL": "Çalışma alanı URL'nizi girin", + "Workspace_URL_Example": "Örn. sirketiniz.rocket.chat", + "invalid-room": "Geçersiz oda" +} diff --git a/app/i18n/locales/zh-CN.json b/app/i18n/locales/zh-CN.json index 4836d9a83..fd026bfdd 100644 --- a/app/i18n/locales/zh-CN.json +++ b/app/i18n/locales/zh-CN.json @@ -1,681 +1,681 @@ { - "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}}", - "error-avatar-url-handling": "错误,无法将 {{username}} 的头像设置为URL({{url}})", - "error-cant-invite-for-direct-room": "无法邀请使用者进入私讯", - "error-could-not-change-email": "无法更改电子邮件", - "error-could-not-change-name": "无法更改名称", - "error-could-not-change-username": "无法更改使用者名称", - "error-could-not-change-status": "无法更改状态", - "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-save-video": "错误,无法保存視頻", - "error-field-unavailable": "{{field}} 已被使用 :(", - "error-file-too-large": "文件太大", - "error-importer-not-defined": "没有正确定义,它缺少汇入类型", - "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": "无效的电子邮件{{email}}", - "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": "无效的转址", - "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": "无效的 token", - "error-invalid-triggerWords": "无效的关键字", - "error-invalid-urls": "无效的网址", - "error-invalid-user": "无效的使用者", - "error-invalid-username": "无效的使用者名称", - "error-invalid-webhook-response": "webhook 网址以200以外的状态响应", - "error-message-deleting-blocked": "信息删除已停用", - "error-message-editing-blocked": "信息编辑已停用", - "error-message-size-exceeded": "信息大小超出上限", - "error-missing-unsubscribe-link": "您必须提供[取消订阅]链接。", - "error-no-tokens-for-this-user": "这个用户没有Token", - "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-disabled": "已停用用户注册", - "error-user-registration-secret": "只能透过加密网址进行用戶注册", - "error-you-are-last-owner": "您是最后的拥有者。请删除此人之前设置一个新的拥有者。", - "Actions": "操作", - "activity": "活动时间", - "Activity": "按活动时间排列", - "Add_Reaction": "增加表情貼", - "Add_Server": "創建服务器", - "Add_users": "創建用户", - "Admin_Panel": "仪表板", - "Agent": "代理", - "Alert": "警报", - "alert": "警报", - "alerts": "警报", - "All_users_in_the_channel_can_write_new_messages": "频道中的所有用户都可以发送新信息", - "A_meaningful_name_for_the_discussion_room": "取一个有意义的讨论区的名称", - "All": "所有", - "All_Messages": "全部信息", - "Allow_Reactions": "允许表情贴", - "Alphabetical": "以名称排序", - "and_more": "和更多的", - "and": "和", - "announcement": "公告", - "Announcement": "公告", - "Apply_Your_Certificate": "使用自己的凭证", - "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": "头像地址", - "Away": "离开", - "Back": "返回", - "Black": "黑色", - "Block_user": "屏蔽此用户", - "Browser": "浏览器", - "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": "通话已经结束!", - "Clear_cookies_alert": "是否清除所有 cookies?", - "Clear_cookies_desc": "本操作将清除所有登入 cookies,以登入其他帐号", - "Clear_cookies_yes": "是,清除 cookies", - "Clear_cookies_no": "否,保留 cookies", - "Click_to_join": "点击以参与", - "Close": "关闭", - "Close_emoji_selector": "关闭 emoji 选择器", - "Closing_chat": "结束聊天", - "Change_language_loading": "切换语言", - "Chat_closed_by_agent": "聊天已被客服关闭", - "Choose": "选择", - "Choose_from_library": "从媒体库选择", - "Choose_file": "选择文件", - "Choose_where_you_want_links_be_opened": "请选择您要将链接开启在", - "Code": "代码", - "Code_or_password_invalid": "验证码或密码不正确", - "Collaborative": "协作", - "Confirm": "确认", - "Connect": "连接", - "Connected": "已连接", - "connecting_server": "连接至服务器", - "Connecting": "连接中", - "Contact_us": "联系我们", - "Contact_your_server_admin": "请联络系统管理员", - "Continue_with": "继续采用", - "Copied_to_clipboard": "复制到剪贴板", - "Copy": "复制", - "Conversation": "对话", - "Permalink": "永久链接", - "Certificate_password": "凭证密码", - "Clear_cache": "清除本机资料", - "Clear_cache_loading": "清除快取", - "Whats_the_password_for_your_certificate": "您的凭证密码是?", - "Create_account": "创建账户", - "Create_Channel": "创建频道", - "Create_Direct_Messages": "新增私人讯息", - "Create_Discussion": "新增讨论区", - "Created_snippet": "新增程式码片段", - "Create_a_new_workspace": "创建一个新的工作区", - "Create": "创建", - "Custom_Status": "自订状态", - "Dark": "深色", - "Dark_level": "深色程度", - "Default": "默認", - "Default_browser": "预设浏览器", - "Delete_Room_Warning": "删除聊天室将连带删除其中所有信息。此操作将无法还原。", - "Department": "部门", - "delete": "删除", - "Delete": "删除", - "DELETE": "删除", - "deleting_room": "正在删除聊天室", - "description": "描述", - "Description": "描述", - "Desktop_Options": "桌面选项", - "Desktop_Notifications": "桌面通知", - "Desktop_Alert_info": "这些通知将发送至桌面", - "Directory": "目录", - "Direct_Messages": "私訊", - "Disable_notifications": "禁用信息通知", - "Discussions": "讨论区", - "Discussion_Desc": "帮助保持事态更新! 通过创建讨论,一个和所选频道双向关联的子频道将会被创建。", - "Discussion_name": "讨论区名称", - "Done": "完成", - "Dont_Have_An_Account": "还没有账号?", - "Do_you_have_an_account": "是否拥有帐号?", - "Do_you_have_a_certificate": "是否拥有凭证?", - "Do_you_really_want_to_key_this_room_question_mark": "您真的想要{{key}}这个聊天室吗?", - "E2E_Encryption": "E2E 加密", - "E2E_How_It_Works_info1": "您现在可以创建加密的专用组和直接消息。您也可以将现有的私人组或直接信息加密。", - "E2E_How_It_Works_info2": "这是點對點加密,因此编码/解码邮件的密钥不会保存在服务器上。因此,您需要将密码存储在安全的地方。您需要在希望使用點對點加密的其他设备上输入。", - "E2E_How_It_Works_info3": "如果继续,将自动生成一组 E2E 密码", - "E2E_How_It_Works_info4": "您也可以随时从已输入既有密码的任何浏览器设定新密码给您的加密金钥。", - "edit": "编辑", - "edited": "已编辑", - "Edit": "编辑", - "Edit_Status": "编辑状态", - "Edit_Invite": "编辑邀请", - "End_to_end_encrypted_room": "E2E 加密聊天室", - "end_to_end_encryption": "E2E 加密", - "Email_Notification_Mode_All": "每次被标记或私讯", - "Email_Notification_Mode_Disabled": "禁用", - "Email_or_password_field_is_empty": "邮件或密码字段为空", - "Email": "邮箱", - "email": "邮箱", - "Empty_title": "空白标题", - "Enable_Auto_Translate": "开启自动翻译", - "Enable_notifications": "开启信息通知", - "Encrypted": "已加密", - "Encrypted_message": "加密信息", - "Enter_Your_E2E_Password": "输入您的 E2E 密码", - "Enter_Your_Encryption_Password_desc1": "这将会允许您存取您的加密私人群组和私訊", - "Enter_Your_Encryption_Password_desc2": "您需要在任何使用此聊天的平台输入密码,以编码/解码您的信息", - "Encryption_error_title": "您的加密密码似乎有误", - "Encryption_error_desc": "无法使用汇入的加密密钥来解密", - "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_password_If_this_email_is_registered": "如果这邮箱已注册,我们将发送如何重置密码的说明。如果您没有在短时间内收到电子邮件,请再试一次。", - "Forgot_password": "忘记密码", - "Forgot_Password": "忘记密码", - "Forward": "转发", - "Forward_Chat": "转发聊天", - "Forward_to_department": "转发到部门", - "Forward_to_user": "转发给用戶", - "Full_table": "点击以查看完整表格", - "Generate_New_Link": "产生新的链接", - "Group_by_favorites": "收藏优先", - "Group_by_type": "以类型分组", - "Hide": "隐藏", - "Has_joined_the_channel": "已加入频道", - "Has_joined_the_conversation": "已经加入此对话", - "Has_left_the_channel": "已离开频道", - "Hide_System_Messages": "隐藏系统信息", - "Hide_type_messages": "隐藏 \"{{type}}\" 信息", - "How_It_Works": "运作方式", - "Message_HideType_uj": "隐藏“用戶加入”信息", - "Message_HideType_ul": "隐藏“用戶离开”信息", - "Message_HideType_ru": "隐藏“用戶已删除”信息", - "Message_HideType_au": "隐藏“用戶已增加”信息", - "Message_HideType_mute_unmute": "隐藏“用戶静音/取消静音”信息", - "Message_HideType_r": "隐藏“房间名称已更改”的信息", - "Message_HideType_ut": "隐藏“用戶已加入对话”的信息", - "Message_HideType_wm": "隐藏“欢迎”的信息", - "Message_HideType_rm": "隐藏“已删除信息”的信息", - "Message_HideType_subscription_role_added": "隐藏“已设置角色”的信息", - "Message_HideType_subscription_role_removed": "隐藏“不再定义的角色”的信息", - "Message_HideType_room_archived": "隐藏“聊天室已封存”的信息", - "Message_HideType_room_unarchived": "隐藏“聊天室未封存”的信息", - "I_Saved_My_E2E_Password": "保存我的 E2E 密码", - "IP": "IP", - "In_app": "App 内", - "In_App_And_Desktop": "App 内及桌面", - "In_App_and_Desktop_Alert_info": "打开 app 时,會在屏幕上方显示横幅;显示桌面通知", - "Invisible": "隐身", - "Invite": "邀请", - "is_a_valid_RocketChat_instance": "是一个有效的 Rocket.Chat 实例", - "is_not_a_valid_RocketChat_instance": "不是有效的 Rocket.Chat 实例", - "is_typing": "正在输入", - "Invalid_or_expired_invite_token": "无效或到期的邀请 token", - "Invalid_server_version": "此 App 版本已不支援您正在连线之服务器版本。当前版本: {{currentVersion}}.\\n\\n最低版本要求: {{minVersion}}", - "Invite_Link": "邀请链接", - "Invite_users": "邀请用戶", - "Join": "加入", - "Join_our_open_workspace": "加入开放工作区", - "Join_your_workspace": "加入您的工作区", - "Just_invited_people_can_access_this_channel": "仅有被邀请人能进入这个频道", - "Language": "语言", - "last_message": "最后一条信息", - "Leave_channel": "离开频道", - "leaving_room": "离开聊天室", - "leave": "离开", - "Legal": "合法", - "Light": "浅色", - "License": "授权条款", - "Livechat": "即时聊天", - "Livechat_edit": "即时聊天编辑", - "Login": "登陆", - "Login_error": "认证失败!请再试一次", - "Login_with": "登陆为", - "Logging_out": "正在登出", - "Logout": "注销", - "Max_number_of_uses": "最大使用次数", - "Max_number_of_users_allowed_is_number": "允许使用者上限数量{{maxUsers}}", - "members": "成员", - "Members": "成员", - "Mentioned_Messages": "被提及的信息", - "mentioned": "提到", - "Mentions": "被提及", - "Message_accessibility": "{{time}}来自{{user}}的消息: {{message}}", - "Message_actions": "信息操作", - "Message_pinned": "信息被钉选", - "Message_removed": "信息被删除", - "Message_starred": "信息被标注", - "Message_unstarred": "信息被取消标注", - "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": "名称", - "Navigation_history": "浏览历史记录", - "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_label_provided": "没有提供 {{label}}", - "No_Message": "没有信息", - "No_messages_yet": "当前未有信息", - "No_Reactions": "没有表情貼", - "No_Read_Receipts": "没有已读人员", - "Not_logged": "没有记录", - "Not_RC_Server": "这不是一个 Rocket.Chat server.\\n{{contact}}", - "Nothing": "无", - "Nothing_to_save": "什么都没有保存!", - "Notify_active_in_this_room": "通知这个聊天室的活跃用户", - "Notify_all_in_this_room": "通知这个聊天室的所有人", - "Notifications": "通知", - "Notification_Duration": "通知持续时间", - "Notification_Preferences": "通知偏好设置", - "No_available_agents_to_transfer": "没有可用的代理进行传输", - "Offline": "离线", - "Oops": "哎呀!", - "Omnichannel": "Omnichannel", - "Open_Livechats": "打开即时聊天", - "Omnichannel_enable_alert": "您尚未启用 Omnichannel,是否想要启用?", - "Onboarding_description": "工作区是团队或组织协作的空间。向工作区管理员询问要加入的地址或为您的团队创建一个。", - "Onboarding_join_workspace": "加入一个工作区", - "Onboarding_subtitle": "超越团队合作", - "Onboarding_title": "欢迎来到 Rocket.Chat", - "Onboarding_join_open_description": "加入我们的开放工作区以与 Rocket.Chat 团队及社群交谈", - "Onboarding_agree_terms": "继续,即表示您同意 Rocket.Chat", - "Onboarding_less_options": "较少选项", - "Onboarding_more_options": "较多选项", - "Online": "在线", - "Only_authorized_users_can_write_new_messages": "只有经过授权的用户才能写新信息", - "Open_emoji_selector": "打开 emoji 选择器", - "Open_Source_Communication": "开源沟通", - "Open_your_authentication_app_and_enter_the_code": "打开您的验证应用程式并输入代码。您也可以使用其中一个备用代码。", - "OR": "或", - "OS": "作业系统", - "Overwrites_the_server_configuration_and_use_room_config": "覆写服务器设置和使用聊天室设置", - "Password": "密码", - "Parent_channel_or_group": "父频道或群组", - "Permalink_copied_to_clipboard": "永久链接已复制到剪贴板!", - "Phone": "电话", - "Pin": "钉选", - "Pinned_Messages": "钉选信息", - "pinned": "已被钉选", - "Pinned": "被钉选", - "Please_add_a_comment": "请新增评论", - "Please_enter_your_password": "请输入密码", - "Please_wait": "请稍候", - "Preferences": "偏好设置", - "Preferences_saved": "偏好已保存!", - "Privacy_Policy": "隐私政策", - "Private_Channel": "私人频道", - "Private": "私有的", - "Processing": "处理中", - "Profile_saved_successfully": "个人资料保存成功!", - "Profile": "个人资料", - "Public_Channel": "公共频道", - "Public": "公共", - "Push_Notifications": "推送通知", - "Push_Notifications_Alert_Info": "这些通知将在未开启 App 时发送给您", - "Quote": "引用", - "Reactions_are_disabled": "表情貼被禁用", - "Reactions_are_enabled": "表情貼被启用", - "Reactions": "表情貼", - "Read": "读取", - "Read_External_Permission_Message": "Rocket.Chat 需要存取您装置上的相片、多媒体及文件", - "Read_External_Permission": "读取媒体权限", - "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": "重置", - "Return": "返回", - "Review_app_title": "对此 App 满意吗?", - "Review_app_desc": "请在 {{store}} 给予我们 5 星好评", - "Review_app_yes": "没问题", - "Review_app_no": "婉拒", - "Review_app_later": "之后再说", - "Review_app_unable_store": "无法开启 {{store}}", - "Review_this_app": "评分此 App", - "Remove": "移除", - "Roles": "角色", - "Room_actions": "聊天室操作", - "Room_changed_announcement": "{{userBy}}将聊天室通知改为:{{announcement}}", - "Room_changed_avatar": "Room avatar changed by {{userBy}}", - "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": "保存", - "Saved": "保存", - "saving_preferences": "保存偏好設置", - "saving_profile": "保存配置文件", - "saving_settings": "保存设置", - "saved_to_gallery": "储存至图片库", - "Save_Your_E2E_Password": "储存您的 E2E 密码", - "Save_Your_Encryption_Password": "储存您的加密密码", - "Save_Your_Encryption_Password_warning": "此密码未被储存在任何地方,为此您必须安全存放您的密码", - "Save_Your_Encryption_Password_info": "请记住,如果你遗失了您的密码,您将无法存取您的信息并不可恢复", - "Search_Messages": "搜索信息", - "Search": "搜索", - "Search_by": "搜寻", - "Search_global_users": "搜寻全域用户", - "Search_global_users_description": "如果启用,您将可以搜寻其他公司、服务器上的任何用戶", - "Seconds": "{{second}} 秒", - "Security_and_privacy": "安全与隐私", - "Select_Avatar": "选择头像", - "Select_Server": "选择服务器", - "Select_Users": "选择用户", - "Select_a_Channel": "选择一个频道", - "Select_a_Department": "选择一个部门", - "Select_an_option": "选择一个选项", - "Select_a_User": "选择一个用戶", - "Send": "发送", - "Send_audio_message": "发送音频信息", - "Send_crash_report": "送出当机报告", - "Send_message": "发送信息", - "Send_me_the_code_again": "再次发送代码给我", - "Send_to": "发送到", - "Sending_to": "正发送到", - "Sent_an_attachment": "发送附件", - "Server": "服务器", - "Servers": "服务器", - "Server_version": "服务器版本: {{version}}", - "Set_username_subtitle": "用户名是用來让其他人在信息中提到您", - "Set_custom_status": "设定自订状态", - "Set_status": "设定状态", - "Status_saved_successfully": "状态储存成功", - "Settings": "设置", - "Settings_succesfully_changed": "设置更改成功!", - "Share": "分享", - "Share_Link": "分享链接", - "Share_this_app": "分享此 app", - "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": "开始对话", - "Start_a_Discussion": "开始一个讨论", - "Started_discussion": "已开始的讨论", - "Started_call": "{{userBy}} 开始的通话", - "Submit": "提交", - "Table": "表格", - "Tags": "标签", - "Take_a_photo": "拍照", - "Take_a_video": "录影", - "Take_it": "拿去!", - "tap_to_change_status": "点按即可更改状态", - "Tap_to_view_servers_list": "点击查看服务器列表", - "Terms_of_Service": "服务条款", - "Theme": "布景主题", - "The_user_wont_be_able_to_type_in_roomName": "此用户将无法在 {{roomName}} 中输入", - "The_user_will_be_able_to_type_in_roomName": "此用户将可以在 {{roomName}} 中输入", - "There_was_an_error_while_action": "{{action}}出现错误!", - "This_room_is_blocked": "这个聊天室被锁了", - "This_room_is_read_only": "这个聊天室是只读的", - "Thread": "讨论串", - "Threads": "讨论串", - "Timezone": "时区", - "To": "到", - "topic": "主题", - "Topic": "主题", - "Translate": "翻译", - "Try_again": "再试一次", - "Two_Factor_Authentication": "双重认证", - "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": "上传文件?", - "User": "用戶", - "Users": "用戶", - "User_added_by": "由{{userBy}}添加的用户 {{userAdded}}", - "User_Info": "用戶资讯", - "User_has_been_key": "用户已被{{key}}", - "User_is_no_longer_role_by_": "{{userBy}}将角色 {{role}} 从用户 {{user}} 身上移除", - "User_muted_by": "用户 {{userMuted}} 被 {{userBy}} 静音", - "User_removed_by": "用户 {{userRemoved}} 被 {{userBy}} 移除", - "User_sent_an_attachment": "{{user}} 寄送了一个附件", - "User_unmuted_by": "用户 {{userUnmuted}} 被 {{userBy}} 取消静音", - "User_was_set_role_by_": "用户 {{user}} 被 {{userBy}} 设置角色 {{role}}", - "Username_is_empty": "用户名是空的", - "Username": "用户名", - "Username_or_email": "用户名或邮箱", - "Uses_server_configuration": "使用服务器设置", - "Validating": "正在验证", - "Registration_Succeeded": "注册成功", - "Verify": "验证", - "Verify_email_title": "注册成功", - "Verify_email_desc": "我们已经送出一封电子邮件,以确认您的注册。如果您没有很快收到,请再试一次。", - "Verify_your_email_for_the_code_we_sent": "检查您的电子邮件以取得我们发送的代码", - "Video_call": "视频电话", - "View_Original": "检视原文", - "Voice_call": "语音电话", - "Waiting_for_network": "等待网路连接", - "Websocket_disabled": "Websocket 已于此伺服器上禁用。 \\n{{contact}}", - "Welcome": "欢迎", - "What_are_you_doing_right_now": "现在在做些什么?", - "Whats_your_2fa": "您的 2FA 代码是?", - "Without_Servers": "未连接至服务器", - "Workspaces": "工作区", - "Would_you_like_to_return_the_inquiry": "你想回覆询问吗?", - "Write_External_Permission_Message": "Rocket.Chat 需要您图片库的存取权限以储存图片。", - "Write_External_Permission": "图片库权限", - "Yes": "是", - "Yes_action_it": "是的,{{action}}它!", - "Yesterday": "昨天", - "You_are_in_preview_mode": "您处于预览模式", - "You_are_offline": "您处于离线状态", - "You_can_search_using_RegExp_eg": "您可用 RegExp 进行搜索。 例如`/^text$/i`", - "You_colon": "你:", - "you_were_mentioned": "你被提到了", - "You_were_removed_from_channel": "您已从 {{channel}} 中被踢除", - "you": "你", - "You": "你", - "Logged_out_by_server": "服务器端已将你注销,请重新登入", - "You_need_to_access_at_least_one_RocketChat_server_to_share_something": "您需要访问至少一台Rocket.Chat服务器才能共享某些内容。", - "You_need_to_verifiy_your_email_address_to_get_notications": "您需要先验证您的邮箱以启用通知", - "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": "您的邀请链接永久有效。", - "Your_workspace": "您的工作区", - "Your_password_is": "您的密码", - "Version_no": "版本: {{version}}", - "You_will_not_be_able_to_recover_this_message": "您将无法恢复此信息!", - "You_will_unset_a_certificate_for_this_server": "您将取消此服务器的凭证设定", - "Change_Language": "切换语言", - "Crash_report_disclaimer": "我们从不记录您的聊天内容。 崩溃报告和分析事件仅与 Rocket.Chat 有关,以便识别和修复问题。", - "Type_message": "输入信息", - "Room_search": "搜索聊天室", - "Room_selection": "选择房间(输入 1...9)", - "Next_room": "下一个聊天室", - "Previous_room": "上一个聊天室", - "New_room": "新聊天室", - "Upload_room": "上传至聊天室", - "Search_messages": "搜索信息", - "Scroll_messages": "信息滚动", - "Reply_latest": "回覆最新信息", - "Reply_in_Thread": "讨论串回覆", - "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": "这将清除您的所有离线资料。", - "This_will_remove_all_data_from_this_server": "这将从该服务器中删除所有数据。", - "Mark_unread": "标记未读", - "Wait_activation_warning": "您的帐号必须由管理员手动启用后才能登入。", - "Screen_lock": "鎖屏", - "Local_authentication_biometry_title": "验证", - "Local_authentication_biometry_fallback": "使用通关密码", - "Local_authentication_unlock_option": "以通关密码解锁", - "Local_authentication_change_passcode": "变更通关密码", - "Local_authentication_info": "注: 如果您忘记了通关密码,将需要移除并重新安装此 App", - "Local_authentication_facial_recognition": "脸部辨识", - "Local_authentication_fingerprint": "指纹辨识", - "Local_authentication_unlock_with_label": "以 {{label}} 解锁", - "Local_authentication_auto_lock_60": "1分钟后", - "Local_authentication_auto_lock_300": "5分钟后", - "Local_authentication_auto_lock_900": "15分钟后", - "Local_authentication_auto_lock_1800": "半小时后", - "Local_authentication_auto_lock_3600": "一小时后", - "Passcode_enter_title": "请输入通关密码", - "Passcode_choose_title": "请输入新通关密码", - "Passcode_choose_confirm_title": "请确认新通关密码", - "Passcode_choose_error": "不正确的通关密码,请再试一次", - "Passcode_choose_force_set": "管理员设置必填", - "Passcode_app_locked_title": "App 已锁定", - "Passcode_app_locked_subtitle": "{{timeLeft}} 秒后再进行尝试", - "After_seconds_set_by_admin": "{{seconds}} 秒 (管理员设定)", - "Dont_activate": "現在不要激活", - "Queued_chats": "聊天队列", - "Queue_is_empty": "队列是空的", - "Logout_from_other_logged_in_locations": "注销其他已登陆的设备", - "You_will_be_logged_out_from_other_locations": "您将于其他设备上注销", - "Logged_out_of_other_clients_successfully": "成功登出其他用户端", - "Logout_failed": "注销失败", - "Log_analytics_events": "日志分析事件", - "E2E_encryption_change_password_title": "变更加密密码", - "E2E_encryption_change_password_description": "现在您可以建立加密私人群组和私讯。您也可以变更已存在的私人群组或是私讯来加密。 \\n\\n这是点对点的加密,所以用来加密/解密的金钥将不会储存到伺服器上。为此,您必须安全存放您的密码。如果您希望在其他装置上使用对点加密时,将会需要输入此密码。", - "E2E_encryption_change_password_error": "变更 E2E 密码时发生错误!", - "E2E_encryption_change_password_success": "E2E 密码已成功变更!", - "E2E_encryption_change_password_message": "请确定您已将其安全存放至别处", - "E2E_encryption_change_password_confirmation": "是,我要变更", - "E2E_encryption_reset_title": "重设 E2E 金钥", - "E2E_encryption_reset_description": "此选项将撤销您目前的*E2E 金钥*并将您登出。 \\n当您再次登入时,Rocket.Chat 将产生新的一组金钥并恢复您对任一有在线成员的加密聊天室之存取权限。 \\n由于 E2E 加密的特性,Rocket.Chat 无法恢复无在线成员之聊天室之存取权限。", - "E2E_encryption_reset_button": "重设", - "E2E_encryption_reset_error": "重设 E2E 金钥时发生错误!", - "E2E_encryption_reset_message": "您将被登出", - "E2E_encryption_reset_confirmation": "是,我要重设", - "Following": "正在追踪", - "Threads_displaying_all": "显示全部", - "Threads_displaying_following": "显示追踪中", - "Threads_displaying_unread": "显示未读", - "No_threads": "当前没有讨论串", - "No_threads_following": "当前没有正在追踪的讨论", - "No_threads_unread": "当前没有未读的讨论", - "Messagebox_Send_to_channel": "发送至频道" -} \ No newline at end of file + "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}}", + "error-avatar-url-handling": "错误,无法将 {{username}} 的头像设置为URL({{url}})", + "error-cant-invite-for-direct-room": "无法邀请使用者进入私讯", + "error-could-not-change-email": "无法更改电子邮件", + "error-could-not-change-name": "无法更改名称", + "error-could-not-change-username": "无法更改使用者名称", + "error-could-not-change-status": "无法更改状态", + "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-save-video": "错误,无法保存視頻", + "error-field-unavailable": "{{field}} 已被使用 :(", + "error-file-too-large": "文件太大", + "error-importer-not-defined": "没有正确定义,它缺少汇入类型", + "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": "无效的电子邮件{{email}}", + "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": "无效的转址", + "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": "无效的 token", + "error-invalid-triggerWords": "无效的关键字", + "error-invalid-urls": "无效的网址", + "error-invalid-user": "无效的使用者", + "error-invalid-username": "无效的使用者名称", + "error-invalid-webhook-response": "webhook 网址以200以外的状态响应", + "error-message-deleting-blocked": "信息删除已停用", + "error-message-editing-blocked": "信息编辑已停用", + "error-message-size-exceeded": "信息大小超出上限", + "error-missing-unsubscribe-link": "您必须提供[取消订阅]链接。", + "error-no-tokens-for-this-user": "这个用户没有Token", + "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-disabled": "已停用用户注册", + "error-user-registration-secret": "只能透过加密网址进行用戶注册", + "error-you-are-last-owner": "您是最后的拥有者。请删除此人之前设置一个新的拥有者。", + "Actions": "操作", + "activity": "活动时间", + "Activity": "按活动时间排列", + "Add_Reaction": "增加表情貼", + "Add_Server": "創建服务器", + "Add_users": "創建用户", + "Admin_Panel": "仪表板", + "Agent": "代理", + "Alert": "警报", + "alert": "警报", + "alerts": "警报", + "All_users_in_the_channel_can_write_new_messages": "频道中的所有用户都可以发送新信息", + "A_meaningful_name_for_the_discussion_room": "取一个有意义的讨论区的名称", + "All": "所有", + "All_Messages": "全部信息", + "Allow_Reactions": "允许表情贴", + "Alphabetical": "以名称排序", + "and_more": "和更多的", + "and": "和", + "announcement": "公告", + "Announcement": "公告", + "Apply_Your_Certificate": "使用自己的凭证", + "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": "头像地址", + "Away": "离开", + "Back": "返回", + "Black": "黑色", + "Block_user": "屏蔽此用户", + "Browser": "浏览器", + "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": "通话已经结束!", + "Clear_cookies_alert": "是否清除所有 cookies?", + "Clear_cookies_desc": "本操作将清除所有登入 cookies,以登入其他帐号", + "Clear_cookies_yes": "是,清除 cookies", + "Clear_cookies_no": "否,保留 cookies", + "Click_to_join": "点击以参与", + "Close": "关闭", + "Close_emoji_selector": "关闭 emoji 选择器", + "Closing_chat": "结束聊天", + "Change_language_loading": "切换语言", + "Chat_closed_by_agent": "聊天已被客服关闭", + "Choose": "选择", + "Choose_from_library": "从媒体库选择", + "Choose_file": "选择文件", + "Choose_where_you_want_links_be_opened": "请选择您要将链接开启在", + "Code": "代码", + "Code_or_password_invalid": "验证码或密码不正确", + "Collaborative": "协作", + "Confirm": "确认", + "Connect": "连接", + "Connected": "已连接", + "connecting_server": "连接至服务器", + "Connecting": "连接中", + "Contact_us": "联系我们", + "Contact_your_server_admin": "请联络系统管理员", + "Continue_with": "继续采用", + "Copied_to_clipboard": "复制到剪贴板", + "Copy": "复制", + "Conversation": "对话", + "Permalink": "永久链接", + "Certificate_password": "凭证密码", + "Clear_cache": "清除本机资料", + "Clear_cache_loading": "清除快取", + "Whats_the_password_for_your_certificate": "您的凭证密码是?", + "Create_account": "创建账户", + "Create_Channel": "创建频道", + "Create_Direct_Messages": "新增私人讯息", + "Create_Discussion": "新增讨论区", + "Created_snippet": "新增程式码片段", + "Create_a_new_workspace": "创建一个新的工作区", + "Create": "创建", + "Custom_Status": "自订状态", + "Dark": "深色", + "Dark_level": "深色程度", + "Default": "默認", + "Default_browser": "预设浏览器", + "Delete_Room_Warning": "删除聊天室将连带删除其中所有信息。此操作将无法还原。", + "Department": "部门", + "delete": "删除", + "Delete": "删除", + "DELETE": "删除", + "deleting_room": "正在删除聊天室", + "description": "描述", + "Description": "描述", + "Desktop_Options": "桌面选项", + "Desktop_Notifications": "桌面通知", + "Desktop_Alert_info": "这些通知将发送至桌面", + "Directory": "目录", + "Direct_Messages": "私訊", + "Disable_notifications": "禁用信息通知", + "Discussions": "讨论区", + "Discussion_Desc": "帮助保持事态更新! 通过创建讨论,一个和所选频道双向关联的子频道将会被创建。", + "Discussion_name": "讨论区名称", + "Done": "完成", + "Dont_Have_An_Account": "还没有账号?", + "Do_you_have_an_account": "是否拥有帐号?", + "Do_you_have_a_certificate": "是否拥有凭证?", + "Do_you_really_want_to_key_this_room_question_mark": "您真的想要{{key}}这个聊天室吗?", + "E2E_Encryption": "E2E 加密", + "E2E_How_It_Works_info1": "您现在可以创建加密的专用组和直接消息。您也可以将现有的私人组或直接信息加密。", + "E2E_How_It_Works_info2": "这是點對點加密,因此编码/解码邮件的密钥不会保存在服务器上。因此,您需要将密码存储在安全的地方。您需要在希望使用點對點加密的其他设备上输入。", + "E2E_How_It_Works_info3": "如果继续,将自动生成一组 E2E 密码", + "E2E_How_It_Works_info4": "您也可以随时从已输入既有密码的任何浏览器设定新密码给您的加密金钥。", + "edit": "编辑", + "edited": "已编辑", + "Edit": "编辑", + "Edit_Status": "编辑状态", + "Edit_Invite": "编辑邀请", + "End_to_end_encrypted_room": "E2E 加密聊天室", + "end_to_end_encryption": "E2E 加密", + "Email_Notification_Mode_All": "每次被标记或私讯", + "Email_Notification_Mode_Disabled": "禁用", + "Email_or_password_field_is_empty": "邮件或密码字段为空", + "Email": "邮箱", + "email": "邮箱", + "Empty_title": "空白标题", + "Enable_Auto_Translate": "开启自动翻译", + "Enable_notifications": "开启信息通知", + "Encrypted": "已加密", + "Encrypted_message": "加密信息", + "Enter_Your_E2E_Password": "输入您的 E2E 密码", + "Enter_Your_Encryption_Password_desc1": "这将会允许您存取您的加密私人群组和私訊", + "Enter_Your_Encryption_Password_desc2": "您需要在任何使用此聊天的平台输入密码,以编码/解码您的信息", + "Encryption_error_title": "您的加密密码似乎有误", + "Encryption_error_desc": "无法使用汇入的加密密钥来解密", + "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_password_If_this_email_is_registered": "如果这邮箱已注册,我们将发送如何重置密码的说明。如果您没有在短时间内收到电子邮件,请再试一次。", + "Forgot_password": "忘记密码", + "Forgot_Password": "忘记密码", + "Forward": "转发", + "Forward_Chat": "转发聊天", + "Forward_to_department": "转发到部门", + "Forward_to_user": "转发给用戶", + "Full_table": "点击以查看完整表格", + "Generate_New_Link": "产生新的链接", + "Group_by_favorites": "收藏优先", + "Group_by_type": "以类型分组", + "Hide": "隐藏", + "Has_joined_the_channel": "已加入频道", + "Has_joined_the_conversation": "已经加入此对话", + "Has_left_the_channel": "已离开频道", + "Hide_System_Messages": "隐藏系统信息", + "Hide_type_messages": "隐藏 \"{{type}}\" 信息", + "How_It_Works": "运作方式", + "Message_HideType_uj": "隐藏“用戶加入”信息", + "Message_HideType_ul": "隐藏“用戶离开”信息", + "Message_HideType_ru": "隐藏“用戶已删除”信息", + "Message_HideType_au": "隐藏“用戶已增加”信息", + "Message_HideType_mute_unmute": "隐藏“用戶静音/取消静音”信息", + "Message_HideType_r": "隐藏“房间名称已更改”的信息", + "Message_HideType_ut": "隐藏“用戶已加入对话”的信息", + "Message_HideType_wm": "隐藏“欢迎”的信息", + "Message_HideType_rm": "隐藏“已删除信息”的信息", + "Message_HideType_subscription_role_added": "隐藏“已设置角色”的信息", + "Message_HideType_subscription_role_removed": "隐藏“不再定义的角色”的信息", + "Message_HideType_room_archived": "隐藏“聊天室已封存”的信息", + "Message_HideType_room_unarchived": "隐藏“聊天室未封存”的信息", + "I_Saved_My_E2E_Password": "保存我的 E2E 密码", + "IP": "IP", + "In_app": "App 内", + "In_App_And_Desktop": "App 内及桌面", + "In_App_and_Desktop_Alert_info": "打开 app 时,會在屏幕上方显示横幅;显示桌面通知", + "Invisible": "隐身", + "Invite": "邀请", + "is_a_valid_RocketChat_instance": "是一个有效的 Rocket.Chat 实例", + "is_not_a_valid_RocketChat_instance": "不是有效的 Rocket.Chat 实例", + "is_typing": "正在输入", + "Invalid_or_expired_invite_token": "无效或到期的邀请 token", + "Invalid_server_version": "此 App 版本已不支援您正在连线之服务器版本。当前版本: {{currentVersion}}.\\n\\n最低版本要求: {{minVersion}}", + "Invite_Link": "邀请链接", + "Invite_users": "邀请用戶", + "Join": "加入", + "Join_our_open_workspace": "加入开放工作区", + "Join_your_workspace": "加入您的工作区", + "Just_invited_people_can_access_this_channel": "仅有被邀请人能进入这个频道", + "Language": "语言", + "last_message": "最后一条信息", + "Leave_channel": "离开频道", + "leaving_room": "离开聊天室", + "leave": "离开", + "Legal": "合法", + "Light": "浅色", + "License": "授权条款", + "Livechat": "即时聊天", + "Livechat_edit": "即时聊天编辑", + "Login": "登陆", + "Login_error": "认证失败!请再试一次", + "Login_with": "登陆为", + "Logging_out": "正在登出", + "Logout": "注销", + "Max_number_of_uses": "最大使用次数", + "Max_number_of_users_allowed_is_number": "允许使用者上限数量{{maxUsers}}", + "members": "成员", + "Members": "成员", + "Mentioned_Messages": "被提及的信息", + "mentioned": "提到", + "Mentions": "被提及", + "Message_accessibility": "{{time}}来自{{user}}的消息: {{message}}", + "Message_actions": "信息操作", + "Message_pinned": "信息被钉选", + "Message_removed": "信息被删除", + "Message_starred": "信息被标注", + "Message_unstarred": "信息被取消标注", + "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": "名称", + "Navigation_history": "浏览历史记录", + "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_label_provided": "没有提供 {{label}}", + "No_Message": "没有信息", + "No_messages_yet": "当前未有信息", + "No_Reactions": "没有表情貼", + "No_Read_Receipts": "没有已读人员", + "Not_logged": "没有记录", + "Not_RC_Server": "这不是一个 Rocket.Chat server.\\n{{contact}}", + "Nothing": "无", + "Nothing_to_save": "什么都没有保存!", + "Notify_active_in_this_room": "通知这个聊天室的活跃用户", + "Notify_all_in_this_room": "通知这个聊天室的所有人", + "Notifications": "通知", + "Notification_Duration": "通知持续时间", + "Notification_Preferences": "通知偏好设置", + "No_available_agents_to_transfer": "没有可用的代理进行传输", + "Offline": "离线", + "Oops": "哎呀!", + "Omnichannel": "Omnichannel", + "Open_Livechats": "打开即时聊天", + "Omnichannel_enable_alert": "您尚未启用 Omnichannel,是否想要启用?", + "Onboarding_description": "工作区是团队或组织协作的空间。向工作区管理员询问要加入的地址或为您的团队创建一个。", + "Onboarding_join_workspace": "加入一个工作区", + "Onboarding_subtitle": "超越团队合作", + "Onboarding_title": "欢迎来到 Rocket.Chat", + "Onboarding_join_open_description": "加入我们的开放工作区以与 Rocket.Chat 团队及社群交谈", + "Onboarding_agree_terms": "继续,即表示您同意 Rocket.Chat", + "Onboarding_less_options": "较少选项", + "Onboarding_more_options": "较多选项", + "Online": "在线", + "Only_authorized_users_can_write_new_messages": "只有经过授权的用户才能写新信息", + "Open_emoji_selector": "打开 emoji 选择器", + "Open_Source_Communication": "开源沟通", + "Open_your_authentication_app_and_enter_the_code": "打开您的验证应用程式并输入代码。您也可以使用其中一个备用代码。", + "OR": "或", + "OS": "作业系统", + "Overwrites_the_server_configuration_and_use_room_config": "覆写服务器设置和使用聊天室设置", + "Password": "密码", + "Parent_channel_or_group": "父频道或群组", + "Permalink_copied_to_clipboard": "永久链接已复制到剪贴板!", + "Phone": "电话", + "Pin": "钉选", + "Pinned_Messages": "钉选信息", + "pinned": "已被钉选", + "Pinned": "被钉选", + "Please_add_a_comment": "请新增评论", + "Please_enter_your_password": "请输入密码", + "Please_wait": "请稍候", + "Preferences": "偏好设置", + "Preferences_saved": "偏好已保存!", + "Privacy_Policy": "隐私政策", + "Private_Channel": "私人频道", + "Private": "私有的", + "Processing": "处理中", + "Profile_saved_successfully": "个人资料保存成功!", + "Profile": "个人资料", + "Public_Channel": "公共频道", + "Public": "公共", + "Push_Notifications": "推送通知", + "Push_Notifications_Alert_Info": "这些通知将在未开启 App 时发送给您", + "Quote": "引用", + "Reactions_are_disabled": "表情貼被禁用", + "Reactions_are_enabled": "表情貼被启用", + "Reactions": "表情貼", + "Read": "读取", + "Read_External_Permission_Message": "Rocket.Chat 需要存取您装置上的相片、多媒体及文件", + "Read_External_Permission": "读取媒体权限", + "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": "重置", + "Return": "返回", + "Review_app_title": "对此 App 满意吗?", + "Review_app_desc": "请在 {{store}} 给予我们 5 星好评", + "Review_app_yes": "没问题", + "Review_app_no": "婉拒", + "Review_app_later": "之后再说", + "Review_app_unable_store": "无法开启 {{store}}", + "Review_this_app": "评分此 App", + "Remove": "移除", + "Roles": "角色", + "Room_actions": "聊天室操作", + "Room_changed_announcement": "{{userBy}}将聊天室通知改为:{{announcement}}", + "Room_changed_avatar": "Room avatar changed by {{userBy}}", + "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": "保存", + "Saved": "保存", + "saving_preferences": "保存偏好設置", + "saving_profile": "保存配置文件", + "saving_settings": "保存设置", + "saved_to_gallery": "储存至图片库", + "Save_Your_E2E_Password": "储存您的 E2E 密码", + "Save_Your_Encryption_Password": "储存您的加密密码", + "Save_Your_Encryption_Password_warning": "此密码未被储存在任何地方,为此您必须安全存放您的密码", + "Save_Your_Encryption_Password_info": "请记住,如果你遗失了您的密码,您将无法存取您的信息并不可恢复", + "Search_Messages": "搜索信息", + "Search": "搜索", + "Search_by": "搜寻", + "Search_global_users": "搜寻全域用户", + "Search_global_users_description": "如果启用,您将可以搜寻其他公司、服务器上的任何用戶", + "Seconds": "{{second}} 秒", + "Security_and_privacy": "安全与隐私", + "Select_Avatar": "选择头像", + "Select_Server": "选择服务器", + "Select_Users": "选择用户", + "Select_a_Channel": "选择一个频道", + "Select_a_Department": "选择一个部门", + "Select_an_option": "选择一个选项", + "Select_a_User": "选择一个用戶", + "Send": "发送", + "Send_audio_message": "发送音频信息", + "Send_crash_report": "送出当机报告", + "Send_message": "发送信息", + "Send_me_the_code_again": "再次发送代码给我", + "Send_to": "发送到", + "Sending_to": "正发送到", + "Sent_an_attachment": "发送附件", + "Server": "服务器", + "Servers": "服务器", + "Server_version": "服务器版本: {{version}}", + "Set_username_subtitle": "用户名是用來让其他人在信息中提到您", + "Set_custom_status": "设定自订状态", + "Set_status": "设定状态", + "Status_saved_successfully": "状态储存成功", + "Settings": "设置", + "Settings_succesfully_changed": "设置更改成功!", + "Share": "分享", + "Share_Link": "分享链接", + "Share_this_app": "分享此 app", + "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": "开始对话", + "Start_a_Discussion": "开始一个讨论", + "Started_discussion": "已开始的讨论", + "Started_call": "{{userBy}} 开始的通话", + "Submit": "提交", + "Table": "表格", + "Tags": "标签", + "Take_a_photo": "拍照", + "Take_a_video": "录影", + "Take_it": "拿去!", + "tap_to_change_status": "点按即可更改状态", + "Tap_to_view_servers_list": "点击查看服务器列表", + "Terms_of_Service": "服务条款", + "Theme": "布景主题", + "The_user_wont_be_able_to_type_in_roomName": "此用户将无法在 {{roomName}} 中输入", + "The_user_will_be_able_to_type_in_roomName": "此用户将可以在 {{roomName}} 中输入", + "There_was_an_error_while_action": "{{action}}出现错误!", + "This_room_is_blocked": "这个聊天室被锁了", + "This_room_is_read_only": "这个聊天室是只读的", + "Thread": "讨论串", + "Threads": "讨论串", + "Timezone": "时区", + "To": "到", + "topic": "主题", + "Topic": "主题", + "Translate": "翻译", + "Try_again": "再试一次", + "Two_Factor_Authentication": "双重认证", + "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": "上传文件?", + "User": "用戶", + "Users": "用戶", + "User_added_by": "由{{userBy}}添加的用户 {{userAdded}}", + "User_Info": "用戶资讯", + "User_has_been_key": "用户已被{{key}}", + "User_is_no_longer_role_by_": "{{userBy}}将角色 {{role}} 从用户 {{user}} 身上移除", + "User_muted_by": "用户 {{userMuted}} 被 {{userBy}} 静音", + "User_removed_by": "用户 {{userRemoved}} 被 {{userBy}} 移除", + "User_sent_an_attachment": "{{user}} 寄送了一个附件", + "User_unmuted_by": "用户 {{userUnmuted}} 被 {{userBy}} 取消静音", + "User_was_set_role_by_": "用户 {{user}} 被 {{userBy}} 设置角色 {{role}}", + "Username_is_empty": "用户名是空的", + "Username": "用户名", + "Username_or_email": "用户名或邮箱", + "Uses_server_configuration": "使用服务器设置", + "Validating": "正在验证", + "Registration_Succeeded": "注册成功", + "Verify": "验证", + "Verify_email_title": "注册成功", + "Verify_email_desc": "我们已经送出一封电子邮件,以确认您的注册。如果您没有很快收到,请再试一次。", + "Verify_your_email_for_the_code_we_sent": "检查您的电子邮件以取得我们发送的代码", + "Video_call": "视频电话", + "View_Original": "检视原文", + "Voice_call": "语音电话", + "Waiting_for_network": "等待网路连接", + "Websocket_disabled": "Websocket 已于此伺服器上禁用。 \\n{{contact}}", + "Welcome": "欢迎", + "What_are_you_doing_right_now": "现在在做些什么?", + "Whats_your_2fa": "您的 2FA 代码是?", + "Without_Servers": "未连接至服务器", + "Workspaces": "工作区", + "Would_you_like_to_return_the_inquiry": "你想回覆询问吗?", + "Write_External_Permission_Message": "Rocket.Chat 需要您图片库的存取权限以储存图片。", + "Write_External_Permission": "图片库权限", + "Yes": "是", + "Yes_action_it": "是的,{{action}}它!", + "Yesterday": "昨天", + "You_are_in_preview_mode": "您处于预览模式", + "You_are_offline": "您处于离线状态", + "You_can_search_using_RegExp_eg": "您可用 RegExp 进行搜索。 例如`/^text$/i`", + "You_colon": "你:", + "you_were_mentioned": "你被提到了", + "You_were_removed_from_channel": "您已从 {{channel}} 中被踢除", + "you": "你", + "You": "你", + "Logged_out_by_server": "服务器端已将你注销,请重新登入", + "You_need_to_access_at_least_one_RocketChat_server_to_share_something": "您需要访问至少一台Rocket.Chat服务器才能共享某些内容。", + "You_need_to_verifiy_your_email_address_to_get_notications": "您需要先验证您的邮箱以启用通知", + "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": "您的邀请链接永久有效。", + "Your_workspace": "您的工作区", + "Your_password_is": "您的密码", + "Version_no": "版本: {{version}}", + "You_will_not_be_able_to_recover_this_message": "您将无法恢复此信息!", + "You_will_unset_a_certificate_for_this_server": "您将取消此服务器的凭证设定", + "Change_Language": "切换语言", + "Crash_report_disclaimer": "我们从不记录您的聊天内容。 崩溃报告和分析事件仅与 Rocket.Chat 有关,以便识别和修复问题。", + "Type_message": "输入信息", + "Room_search": "搜索聊天室", + "Room_selection": "选择房间(输入 1...9)", + "Next_room": "下一个聊天室", + "Previous_room": "上一个聊天室", + "New_room": "新聊天室", + "Upload_room": "上传至聊天室", + "Search_messages": "搜索信息", + "Scroll_messages": "信息滚动", + "Reply_latest": "回覆最新信息", + "Reply_in_Thread": "讨论串回覆", + "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": "这将清除您的所有离线资料。", + "This_will_remove_all_data_from_this_server": "这将从该服务器中删除所有数据。", + "Mark_unread": "标记未读", + "Wait_activation_warning": "您的帐号必须由管理员手动启用后才能登入。", + "Screen_lock": "鎖屏", + "Local_authentication_biometry_title": "验证", + "Local_authentication_biometry_fallback": "使用通关密码", + "Local_authentication_unlock_option": "以通关密码解锁", + "Local_authentication_change_passcode": "变更通关密码", + "Local_authentication_info": "注: 如果您忘记了通关密码,将需要移除并重新安装此 App", + "Local_authentication_facial_recognition": "脸部辨识", + "Local_authentication_fingerprint": "指纹辨识", + "Local_authentication_unlock_with_label": "以 {{label}} 解锁", + "Local_authentication_auto_lock_60": "1分钟后", + "Local_authentication_auto_lock_300": "5分钟后", + "Local_authentication_auto_lock_900": "15分钟后", + "Local_authentication_auto_lock_1800": "半小时后", + "Local_authentication_auto_lock_3600": "一小时后", + "Passcode_enter_title": "请输入通关密码", + "Passcode_choose_title": "请输入新通关密码", + "Passcode_choose_confirm_title": "请确认新通关密码", + "Passcode_choose_error": "不正确的通关密码,请再试一次", + "Passcode_choose_force_set": "管理员设置必填", + "Passcode_app_locked_title": "App 已锁定", + "Passcode_app_locked_subtitle": "{{timeLeft}} 秒后再进行尝试", + "After_seconds_set_by_admin": "{{seconds}} 秒 (管理员设定)", + "Dont_activate": "現在不要激活", + "Queued_chats": "聊天队列", + "Queue_is_empty": "队列是空的", + "Logout_from_other_logged_in_locations": "注销其他已登陆的设备", + "You_will_be_logged_out_from_other_locations": "您将于其他设备上注销", + "Logged_out_of_other_clients_successfully": "成功登出其他用户端", + "Logout_failed": "注销失败", + "Log_analytics_events": "日志分析事件", + "E2E_encryption_change_password_title": "变更加密密码", + "E2E_encryption_change_password_description": "现在您可以建立加密私人群组和私讯。您也可以变更已存在的私人群组或是私讯来加密。 \\n\\n这是点对点的加密,所以用来加密/解密的金钥将不会储存到伺服器上。为此,您必须安全存放您的密码。如果您希望在其他装置上使用对点加密时,将会需要输入此密码。", + "E2E_encryption_change_password_error": "变更 E2E 密码时发生错误!", + "E2E_encryption_change_password_success": "E2E 密码已成功变更!", + "E2E_encryption_change_password_message": "请确定您已将其安全存放至别处", + "E2E_encryption_change_password_confirmation": "是,我要变更", + "E2E_encryption_reset_title": "重设 E2E 金钥", + "E2E_encryption_reset_description": "此选项将撤销您目前的*E2E 金钥*并将您登出。 \\n当您再次登入时,Rocket.Chat 将产生新的一组金钥并恢复您对任一有在线成员的加密聊天室之存取权限。 \\n由于 E2E 加密的特性,Rocket.Chat 无法恢复无在线成员之聊天室之存取权限。", + "E2E_encryption_reset_button": "重设", + "E2E_encryption_reset_error": "重设 E2E 金钥时发生错误!", + "E2E_encryption_reset_message": "您将被登出", + "E2E_encryption_reset_confirmation": "是,我要重设", + "Following": "正在追踪", + "Threads_displaying_all": "显示全部", + "Threads_displaying_following": "显示追踪中", + "Threads_displaying_unread": "显示未读", + "No_threads": "当前没有讨论串", + "No_threads_following": "当前没有正在追踪的讨论", + "No_threads_unread": "当前没有未读的讨论", + "Messagebox_Send_to_channel": "发送至频道" +} diff --git a/app/i18n/locales/zh-TW.json b/app/i18n/locales/zh-TW.json index 29626191c..0725a12b4 100644 --- a/app/i18n/locales/zh-TW.json +++ b/app/i18n/locales/zh-TW.json @@ -1,684 +1,684 @@ { - "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}}", - "error-avatar-url-handling": "錯誤,無法將 {{username}} 的大頭貼設置為URL({{url}})", - "error-cant-invite-for-direct-room": "無法邀請使用者進入私訊", - "error-could-not-change-email": "無法更改電子郵件", - "error-could-not-change-name": "無法更改名稱", - "error-could-not-change-username": "無法更改使用者名稱", - "error-could-not-change-status": "無法更改狀態", - "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-save-video": "錯誤,無法儲存影片", - "error-field-unavailable": "{{field}} 已被使用 :(", - "error-file-too-large": "檔案太大", - "error-importer-not-defined": "沒有正確定義,它缺少匯入類型", - "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": "無效的電子郵件{{email}}", - "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": "無效的轉址", - "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": "無效的 token", - "error-invalid-triggerWords": "無效的關鍵字", - "error-invalid-urls": "無效的網址", - "error-invalid-user": "無效的使用者", - "error-invalid-username": "無效的使用者名稱", - "error-invalid-webhook-response": "webhook 網址以200以外的狀態響應", - "error-message-deleting-blocked": "訊息刪除已停用", - "error-message-editing-blocked": "訊息編輯已停用", - "error-message-size-exceeded": "訊息大小超出上限", - "error-missing-unsubscribe-link": "您必須提供[取消訂閱]連結。", - "error-no-tokens-for-this-user": "這名使用者沒有Token", - "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-disabled": "使用者註冊已停用", - "error-user-registration-secret": "只能透過加密網址進行使用者註冊", - "error-you-are-last-owner": "您是最後的擁有者。請刪除此人之前設置一個新的擁有者。", - "Actions": "操作", - "activity": "活動時間", - "Activity": "以活動時間排序", - "Add_Reaction": "增加表情貼", - "Add_Server": "新增伺服器", - "Add_users": "新增使用者", - "Admin_Panel": "管理者面板", - "Agent": "代理", - "Alert": "警報", - "alert": "警報", - "alerts": "警報", - "All_users_in_the_channel_can_write_new_messages": "頻道中的所有使用者都可以發送新訊息", - "A_meaningful_name_for_the_discussion_room": "取一個有意義的討論區名稱", - "All": "所有", - "All_Messages": "全部訊息", - "Allow_Reactions": "允許表情貼", - "Alphabetical": "以名稱排序", - "and_more": "和更多的", - "and": "和", - "announcement": "公告", - "Announcement": "公告", - "Apply_Your_Certificate": "使用自己的憑證", - "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": "大頭貼地址", - "Away": "離開", - "Back": "返回", - "Black": "黑色", - "Block_user": "封鎖此用戶", - "Browser": "瀏覽器", - "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": "通話已經結束!", - "Clear_cookies_alert": "是否清除所有 cookies?", - "Clear_cookies_desc": "本操作將清除所有登入 cookies,以登入其他帳號", - "Clear_cookies_yes": "是,清除 cookies", - "Clear_cookies_no": "否,保留 cookies", - "Click_to_join": "點擊以參與", - "Close": "關閉", - "Close_emoji_selector": "關閉 emoji 選擇器", - "Closing_chat": "結束聊天", - "Change_language_loading": "切換語言", - "Chat_closed_by_agent": "聊天已被客服關閉", - "Choose": "選擇", - "Choose_from_library": "從媒體庫選擇", - "Choose_file": "選擇檔案", - "Choose_where_you_want_links_be_opened": "請選擇您要將連結開啟在", - "Code": "程式碼", - "Code_or_password_invalid": "驗證碼或密碼不正確", - "Collaborative": "協作", - "Confirm": "確認", - "Connect": "連接", - "Connected": "已連接", - "connecting_server": "連線至伺服器", - "Connecting": "連接中", - "Contact_us": "聯絡我們", - "Contact_your_server_admin": "請聯絡系統管理員", - "Continue_with": "繼續採用", - "Copied_to_clipboard": "複製到剪貼簿", - "Copy": "複製", - "Conversation": "對話", - "Permalink": "永久連結", - "Certificate_password": "憑證密碼", - "Clear_cache": "清除本機資料", - "Clear_cache_loading": "清除快取", - "Whats_the_password_for_your_certificate": "您的憑證密碼是?", - "Create_account": "新建帳戶", - "Create_Channel": "新建頻道", - "Create_Direct_Messages": "新增私人訊息", - "Create_Discussion": "新增討論區", - "Created_snippet": "新增程式碼片段", - "Create_a_new_workspace": "建立一個新的工作區", - "Create": "建立", - "Custom_Status": "自訂狀態", - "Dark": "深色", - "Dark_level": "深色程度", - "Default": "預設", - "Default_browser": "預設瀏覽器", - "Delete_Room_Warning": "刪除聊天室將連帶刪除其中所有訊息。此操作將無法還原。", - "Department": "部門", - "delete": "刪除", - "Delete": "刪除", - "DELETE": "刪除", - "deleting_room": "正在刪除聊天室", - "description": "描述", - "Description": "描述", - "Desktop_Options": "桌面選項", - "Desktop_Notifications": "桌面通知", - "Desktop_Alert_info": "這些通知將發送至桌面", - "Directory": "目錄", - "Direct_Messages": "私訊", - "Disable_notifications": "禁用訊息通知", - "Discussions": "討論區", - "Discussion_Desc": "幫助保持事態更新!透過建立討論,一個和所選頻道雙向關聯的子頻道將會被建立。", - "Discussion_name": "討論區名稱", - "Done": "完成", - "Dont_Have_An_Account": "還未擁有帳號?", - "Do_you_have_an_account": "是否擁有帳號?", - "Do_you_have_a_certificate": "是否擁有憑證?", - "Do_you_really_want_to_key_this_room_question_mark": "您真的想要{{key}}這個聊天室嗎?", - "E2E_Encryption": "E2E 加密", - "E2E_How_It_Works_info1": "現在您可以建立加密私人群組和私訊。您也可以變更已存在的私人群組或是私訊來加密。", - "E2E_How_It_Works_info2": "這是*點對點的加密*,所以用來加密/解密的金鑰將不會儲存到伺服器上。為此,*您必須安全存放您的密碼*。如果您希望在其他裝置上使用對點加密時,會需要輸入此密碼。", - "E2E_How_It_Works_info3": "如果繼續,將自動產生一組 E2E 密碼", - "E2E_How_It_Works_info4": "您也可以隨時從已輸入既有密碼的任何瀏覽器設定新密碼給您的加密金鑰。", - "edit": "編輯", - "edited": "已編輯", - "Edit": "編輯", - "Edit_Status": "編輯狀態", - "Edit_Invite": "編輯邀請", - "End_to_end_encrypted_room": "E2E 加密聊天室", - "end_to_end_encryption": "E2E 加密", - "Email_Notification_Mode_All": "每次被標記或私訊", - "Email_Notification_Mode_Disabled": "禁用", - "Email_or_password_field_is_empty": "電子郵件或密碼欄位為空", - "Email": "電子郵件", - "email": "電子郵件", - "Empty_title": "空白標題", - "Enable_Auto_Translate": "開啟自動翻譯", - "Enable_notifications": "開啟訊息通知", - "Encrypted": "已加密", - "Encrypted_message": "加密訊息", - "Enter_Your_E2E_Password": "輸入您的 E2E 密碼", - "Enter_Your_Encryption_Password_desc1": "這將會允許您存取您的加密私人群組和私訊", - "Enter_Your_Encryption_Password_desc2": "您需要在任何使用此聊天的平台輸入密碼,以加/解密您的訊息", - "Encryption_error_title": "您的加密密碼似乎有誤", - "Encryption_error_desc": "無法使用匯入的加密金鑰來解密", - "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_password_If_this_email_is_registered": "如果此電子郵件已註冊,我們將發送有關如何重設密碼的說明。如果您未在短時間內收到電子郵件,請再試一次。", - "Forgot_password": "忘記密碼", - "Forgot_Password": "忘記密碼", - "Forward": "轉發", - "Forward_Chat": "轉發聊天", - "Forward_to_department": "轉發到部門", - "Forward_to_user": "轉發給使用者", - "Full_table": "點擊以查看完整表格", - "Generate_New_Link": "產生新的連結", - "Group_by_favorites": "我的最愛優先", - "Group_by_type": "以類型分組", - "Hide": "隱藏", - "Has_joined_the_channel": "已加入頻道", - "Has_joined_the_conversation": "已經加入此對話", - "Has_left_the_channel": "已離開頻道", - "Hide_System_Messages": "隱藏系統訊息", - "Hide_type_messages": "隱藏 '{{type}}' 訊息", - "How_It_Works": "運作方式", - "Message_HideType_uj": "隱藏“使用者加入”訊息", - "Message_HideType_ul": "隱藏“使用者離開”訊息", - "Message_HideType_ru": "隱藏“使用者已刪除”訊息", - "Message_HideType_au": "隱藏“使用者已增加”訊息", - "Message_HideType_mute_unmute": "隱藏“使用者靜音/取消靜音”訊息", - "Message_HideType_r": "隱藏“聊天室名稱已更改”的訊息", - "Message_HideType_ut": "隱藏“使用者已加入對話”的訊息", - "Message_HideType_wm": "隱藏“歡迎”的訊息", - "Message_HideType_rm": "隱藏“已刪除訊息”的訊息", - "Message_HideType_subscription_role_added": "隱藏“已設置角色”的訊息", - "Message_HideType_subscription_role_removed": "隱藏“不再定義的角色”的訊息", - "Message_HideType_room_archived": "隱藏“聊天室已封存”的訊息", - "Message_HideType_room_unarchived": "隱藏“聊天室未封存”的訊息", - "I_Saved_My_E2E_Password": "儲存我的 E2E 密碼", - "IP": "IP", - "In_app": "App 內", - "In_App_And_Desktop": "App 內及桌面", - "In_App_and_Desktop_Alert_info": "若 app 開啟時,會在螢幕上方顯示橫幅;顯示桌面通知", - "Invisible": "隱身", - "Invite": "邀請", - "is_a_valid_RocketChat_instance": "是一個有效的 Rocket.Chat 實例", - "is_not_a_valid_RocketChat_instance": "不是有效的 Rocket.Chat 實例", - "is_typing": "正在輸入", - "Invalid_or_expired_invite_token": "無效或到期的邀請 token", - "Invalid_server_version": "此 App 版本已不支援您正在連線之伺服器版本。當前版本: {{currentVersion}}.\\n\\n最低版本要求: {{minVersion}}", - "Invite_Link": "邀請連結", - "Invite_users": "邀請使用者", - "Join": "加入", - "Join_our_open_workspace": "加入開放工作區", - "Join_your_workspace": "加入您的工作區", - "Just_invited_people_can_access_this_channel": "僅有受邀者能存取此頻道", - "Language": "語言", - "last_message": "最後一則訊息", - "Leave_channel": "離開頻道", - "leaving_room": "離開聊天室", - "Leave": "離開", - "leave": "離開", - "Legal": "合法", - "Light": "淺色", - "License": "授權條款", - "Livechat": "即時聊天", - "Livechat_edit": "即時聊天編輯", - "Login": "登入", - "Login_error": "認證失敗!請再試一次", - "Login_with": "登入為", - "Logging_out": "正在登出", - "Logout": "登出", - "Max_number_of_uses": "最大使用次數", - "Max_number_of_users_allowed_is_number": "允許使用者上限數量 {{maxUsers}}", - "members": "成員", - "Members": "成員", - "Mentioned_Messages": "被提及的訊息", - "mentioned": "提到", - "Mentions": "被提及", - "Message_accessibility": "{{time}}來自{{user}}的訊息: {{message}}", - "Message_actions": "訊息操作", - "Message_pinned": "訊息被釘選", - "Message_removed": "訊息被刪除", - "Message_starred": "訊息被標註", - "Message_unstarred": "訊息被取消標註", - "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": "名稱", - "Navigation_history": "瀏覽歷史記錄", - "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_label_provided": "沒有提供 {{label}}", - "No_Message": "沒有訊息", - "No_messages_yet": "當前未有訊息", - "No_Reactions": "沒有表情貼", - "No_Read_Receipts": "沒有已讀人員", - "Not_logged": "沒有記錄", - "Not_RC_Server": "這不是一個 Rocket.Chat server.\\n{{contact}}", - "Nothing": "無", - "Nothing_to_save": "沒有東西可儲存!", - "Notify_active_in_this_room": "通知這個聊天室的活躍使用者", - "Notify_all_in_this_room": "通知這個聊天室的所有人", - "Notifications": "通知", - "Notification_Duration": "通知持續時間", - "Notification_Preferences": "通知偏好設定", - "No_available_agents_to_transfer": "沒有可用的代理進行傳輸", - "Offline": "離線", - "Oops": "哎呀!", - "Omnichannel": "Omnichannel", - "Open_Livechats": "打開即時聊天", - "Omnichannel_enable_alert": "您尚未啟用 Omnichannel,是否想要啟用?", - "Onboarding_description": "工作區是團隊或組織協作的空間。向工作區管理員詢問要加入的地址或為您的團隊創建一個。", - "Onboarding_join_workspace": "加入一個工作區", - "Onboarding_subtitle": "超越團隊合作", - "Onboarding_title": "歡迎來到 Rocket.Chat", - "Onboarding_join_open_description": "加入我們的開放工作區以與 Rocket.Chat 團隊及社群交談", - "Onboarding_agree_terms": "繼續,即表示您同意 Rocket.Chat", - "Onboarding_less_options": "較少選項", - "Onboarding_more_options": "較多選項", - "Online": "上線", - "Only_authorized_users_can_write_new_messages": "只有經過授權的使用者才能寫新訊息", - "Open_emoji_selector": "打開 emoji 選擇器", - "Open_Source_Communication": "開源溝通", - "Open_your_authentication_app_and_enter_the_code": "打開您的驗證應用程式並輸入代碼。您也可以使用其中一個備用代碼。", - "OR": "或", - "OS": "作業系統", - "Overwrites_the_server_configuration_and_use_room_config": "覆寫伺服器設置和使用聊天室設置", - "Password": "密碼", - "Parent_channel_or_group": "父頻道或群組", - "Permalink_copied_to_clipboard": "永久連結已複製到剪貼簿!", - "Phone": "電話", - "Pin": "釘選", - "Pinned_Messages": "釘選訊息", - "pinned": "已被釘選", - "Pinned": "被釘選", - "Please_add_a_comment": "請新增評論", - "Please_enter_your_password": "請輸入密碼", - "Please_wait": "請稍候", - "Preferences": "偏好設定", - "Preferences_saved": "偏好設定已被儲存!", - "Privacy_Policy": "隱私政策", - "Private_Channel": "私人頻道", - "Private": "私有的", - "Processing": "處理中", - "Profile_saved_successfully": "個人資料儲存成功!", - "Profile": "個人資料", - "Public_Channel": "公共頻道", - "Public": "公共", - "Push_Notifications": "推送通知", - "Push_Notifications_Alert_Info": "這些通知將在未開啟 App 時發送給您", - "Quote": "引用", - "Reactions_are_disabled": "表情貼被禁用", - "Reactions_are_enabled": "表情貼被啟用", - "Reactions": "表情貼", - "Read": "讀取", - "Read_External_Permission_Message": "Rocket.Chat 需要存取您裝置上的相片、多媒體及檔案", - "Read_External_Permission": "讀取媒體權限", - "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": "重置", - "Return": "返回", - "Review_app_title": "對此 App 滿意嗎?", - "Review_app_desc": "請在 {{store}} 給予我們 5 星好評", - "Review_app_yes": "沒問題", - "Review_app_no": "婉拒", - "Review_app_later": "之後再說", - "Review_app_unable_store": "無法開啟 {{store}}", - "Review_this_app": "評分此 App", - "Remove": "移除", - "Roles": "角色", - "Room_actions": "聊天室操作", - "Room_changed_announcement": "{{userBy}}將聊天室通知改為:{{announcement}}", - "Room_changed_avatar": "Room avatar changed by {{userBy}}", - "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": "儲存", - "Saved": "保存", - "saving_preferences": "儲存偏好設定", - "saving_profile": "儲存配置文件", - "saving_settings": "儲存設定", - "saved_to_gallery": "儲存至圖片庫", - "Save_Your_E2E_Password": "儲存您的 E2E 密碼", - "Save_Your_Encryption_Password": "儲存您的加密密碼", - "Save_Your_Encryption_Password_warning": "此密碼未被儲存在任何地方,為此您必須安全存放您的密碼", - "Save_Your_Encryption_Password_info": "請記住,如果你遺失了您的密碼,您將無法存取您的訊息並不可恢復", - "Search_Messages": "搜尋訊息", - "Search": "搜尋", - "Search_by": "搜尋", - "Search_global_users": "搜尋全域使用者", - "Search_global_users_description": "如果啟用,您將可以搜尋其他公司、伺服器上的任何使用者", - "Seconds": "{{second}} 秒", - "Security_and_privacy": "安全與隱私", - "Select_Avatar": "選擇大頭貼", - "Select_Server": "選擇伺服器", - "Select_Users": "選擇使用者", - "Select_a_Channel": "選擇一個頻道", - "Select_a_Department": "選擇一個部門", - "Select_an_option": "選擇一個選項", - "Select_a_User": "選擇一個使用者", - "Send": "發送", - "Send_audio_message": "發送語音訊息", - "Send_crash_report": "送出當機報告", - "Send_message": "發送訊息", - "Send_me_the_code_again": "再次發送代碼給我", - "Send_to": "發送到", - "Sending_to": "正發送到", - "Sent_an_attachment": "發送附件", - "Server": "伺服器", - "Servers": "伺服器", - "Server_version": "伺服器版本: {{version}}", - "Set_username_subtitle": "使用者名稱是用來讓其他使用者在訊息中提到您", - "Set_custom_status": "設定自訂狀態", - "Set_status": "設定狀態", - "Status_saved_successfully": "狀態儲存成功", - "Settings": "設定", - "Settings_succesfully_changed": "設定更改成功!", - "Share": "分享", - "Share_Link": "分享連結", - "Share_this_app": "分享此 app", - "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": "開始對話", - "Start_a_Discussion": "開始一個討論", - "Started_discussion": "已開始的討論", - "Started_call": "{{userBy}} 開始的通話", - "Submit": "送出", - "Table": "表格", - "Tags": "標籤", - "Take_a_photo": "拍照", - "Take_a_video": "錄影", - "Take_it": "拿去!", - "tap_to_change_status": "點擊即可更改狀態", - "Tap_to_view_servers_list": "點擊查看伺服器列表", - "Terms_of_Service": "服務條款", - "Theme": "佈景主題", - "The_user_wont_be_able_to_type_in_roomName": "此使用者將無法在 {{roomName}} 中輸入", - "The_user_will_be_able_to_type_in_roomName": "此使用者將可以在 {{roomName}} 中輸入", - "There_was_an_error_while_action": "{{action}}出現錯誤!", - "This_room_is_blocked": "這個聊天室已被鎖定", - "This_room_is_read_only": "這個聊天室是唯讀的", - "Thread": "討論串", - "Threads": "討論串", - "Timezone": "時區", - "To": "到", - "topic": "主題", - "Topic": "主題", - "Translate": "翻譯", - "Try_again": "再試一次", - "Two_Factor_Authentication": "雙重認證", - "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": "上傳文件?", - "User": "使用者", - "Users": "使用者", - "User_added_by": "由{{userBy}}添加的使用者 {{userAdded}}", - "User_Info": "使用者資訊", - "User_has_been_key": "使用者已被{{key}}", - "User_is_no_longer_role_by_": "{{userBy}}將角色 {{role}} 從使用者 {{user}} 身上移除", - "User_muted_by": "使用者 {{userMuted}} 被 {{userBy}} 靜音", - "User_removed_by": "使用者 {{userRemoved}} 被 {{userBy}} 移除", - "User_sent_an_attachment": "{{user}} 寄送了一個附件", - "User_unmuted_by": "使用者 {{userUnmuted}} 被 {{userBy}} 取消靜音", - "User_was_set_role_by_": "使用者 {{user}} 被 {{userBy}} 設置角色 {{role}}", - "Username_is_empty": "使用者名稱是空的", - "Username": "使用者名稱", - "Username_or_email": "使用者名稱或電子郵件", - "Uses_server_configuration": "使用伺服器設定", - "Validating": "正在驗證", - "Registration_Succeeded": "註冊成功", - "Verify": "驗證", - "Verify_email_title": "註冊成功", - "Verify_email_desc": "我們已經送出一封電子郵件,以確認您的註冊。如果您沒有很快收到,請再試一次。", - "Verify_your_email_for_the_code_we_sent": "檢查您的電子郵件以取得我們發送的代碼", - "Video_call": "視訊通話", - "View_Original": "檢視原文", - "Voice_call": "語音通話", - "Waiting_for_network": "等待網路連線", - "Websocket_disabled": "Websocket 已於此伺服器上禁用。\\n{{contact}}", - "Welcome": "歡迎", - "What_are_you_doing_right_now": "現在在做些什麼?", - "Whats_your_2fa": "您的 2FA 代碼是?", - "Without_Servers": "未連接至伺服器", - "Workspaces": "工作區", - "Would_you_like_to_return_the_inquiry": "你想回覆詢問嗎?", - "Write_External_Permission_Message": "Rocket.Chat 需要您圖片庫的存取權限以儲存圖片。", - "Write_External_Permission": "圖片庫權限", - "Yes": "是", - "Yes_action_it": "是的,{{action}}它!", - "Yesterday": "昨天", - "You_are_in_preview_mode": "您處於預覽模式", - "You_are_offline": "您處於離線狀態", - "You_can_search_using_RegExp_eg": "您可用 RegExp 進行搜尋。例如`/^text$/i`", - "You_colon": "你:", - "you_were_mentioned": "你被提到了", - "You_were_removed_from_channel": "您已從 {{channel}} 中被踢除", - "you": "你", - "You": "你", - "Logged_out_by_server": "伺服器端已將你登出,請重新登入", - "You_need_to_access_at_least_one_RocketChat_server_to_share_something": "您需要至少連接一個 Rocket.Chat 伺服器才能共享某些内容。", - "You_need_to_verifiy_your_email_address_to_get_notications": "您需要先驗證您的電子郵件以啟用通知", - "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": "您的邀請連結永久有效。", - "Your_workspace": "您的工作區", - "Your_password_is": "您的密碼", - "Version_no": "版本: {{version}}", - "You_will_not_be_able_to_recover_this_message": "您將無法恢復此訊息!", - "You_will_unset_a_certificate_for_this_server": "您將取消此伺服器的憑證設定", - "Change_Language": "切換語言", - "Crash_report_disclaimer": "我們絕不記錄您的聊天內容。 崩潰報告和分析事件僅與 Rocket.Chat 有關,以便識別和修復問題。", - "Type_message": "輸入訊息", - "Room_search": "搜索聊天室", - "Room_selection": "選擇房間(輸入 1...9)", - "Next_room": "下一個聊天室", - "Previous_room": "上一個聊天室", - "New_room": "新聊天室", - "Upload_room": "上傳至聊天室", - "Search_messages": "搜尋訊息", - "Scroll_messages": "訊息滾動", - "Reply_latest": "回覆最新訊息", - "Reply_in_Thread": "討論串回覆", - "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": "這將清除您的所有離線資料。", - "This_will_remove_all_data_from_this_server": "這將從此伺服器中刪除所有資料。", - "Mark_unread": "標記未讀", - "Wait_activation_warning": "您的帳號必須由管理員手動啟用後才能登入。", - "Screen_lock": "螢幕鎖定", - "Local_authentication_biometry_title": "驗證", - "Local_authentication_biometry_fallback": "使用通關密碼", - "Local_authentication_unlock_option": "以通關密碼解鎖", - "Local_authentication_change_passcode": "變更通關密碼", - "Local_authentication_info": "註: 如果您忘記了通關密碼,將需要移除並重新安裝此 App", - "Local_authentication_facial_recognition": "臉部辨識", - "Local_authentication_fingerprint": "指紋辨識", - "Local_authentication_unlock_with_label": "以 {{label}} 解鎖", - "Local_authentication_auto_lock_60": "1分鐘後", - "Local_authentication_auto_lock_300": "5分鐘後", - "Local_authentication_auto_lock_900": "15分鐘後", - "Local_authentication_auto_lock_1800": "半小時後", - "Local_authentication_auto_lock_3600": "一小時後", - "Passcode_enter_title": "請輸入通關密碼", - "Passcode_choose_title": "請輸入新通關密碼", - "Passcode_choose_confirm_title": "請確認新通關密碼", - "Passcode_choose_error": "不正確的通關密碼,請再試一次", - "Passcode_choose_force_set": "管理員設定必填", - "Passcode_app_locked_title": "App 已鎖定", - "Passcode_app_locked_subtitle": "{{timeLeft}} 秒後再進行嘗試", - "After_seconds_set_by_admin": "{{seconds}} 秒 (管理員設定)", - "Dont_activate": "現在不要啟用", - "Queued_chats": "聊天佇列", - "Queue_is_empty": "佇列是空的", - "Logout_from_other_logged_in_locations": "登出其他已登入的設備", - "You_will_be_logged_out_from_other_locations": "您將於其他設備上登出", - "Logged_out_of_other_clients_successfully": "成功登出其他用戶端", - "Logout_failed": "登出失敗", - "Log_analytics_events": "日誌分析事件", - "E2E_encryption_change_password_title": "變更加密密碼", - "E2E_encryption_change_password_description": "現在您可以建立加密私人群組和私訊。您也可以變更已存在的私人群組或是私訊來加密。\\n\\n這是點對點的加密,所以用來加密/解密的金鑰將不會儲存到伺服器上。為此,您必須安全存放您的密碼。如果您希望在其他裝置上使用對點加密時,將會需要輸入此密碼。", - "E2E_encryption_change_password_error": "變更 E2E 密碼時發生錯誤!", - "E2E_encryption_change_password_success": "E2E 密碼已成功變更!", - "E2E_encryption_change_password_message": "請確定您已將其安全存放至別處", - "E2E_encryption_change_password_confirmation": "是,我要變更", - "E2E_encryption_reset_title": "重設 E2E 金鑰", - "E2E_encryption_reset_description": "此選項將撤銷您目前的*E2E 金鑰*並將您登出。\\n當您再次登入時,Rocket.Chat 將產生新的一組金鑰並恢復您對任一有在線成員的加密聊天室之存取權限。\\n由於 E2E 加密的特性,Rocket.Chat 無法恢復無在線成員之聊天室之存取權限。", - "E2E_encryption_reset_button": "重設", - "E2E_encryption_reset_error": "重設 E2E 金鑰時發生錯誤!", - "E2E_encryption_reset_message": "您將被登出", - "E2E_encryption_reset_confirmation": "是,我要重設", - "Following": "正在追蹤", - "Threads_displaying_all": "顯示全部", - "Threads_displaying_following": "顯示追蹤中", - "Threads_displaying_unread": "顯示未讀", - "No_threads": "當前沒有討論串", - "No_threads_following": "當前沒有正在追蹤的討論", - "No_threads_unread": "當前沒有未讀的討論", - "Messagebox_Send_to_channel": "發送至頻道", - "Confirmation": "確認", - "invalid-room": "無效的房間" -} \ No newline at end of file + "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}}", + "error-avatar-url-handling": "錯誤,無法將 {{username}} 的大頭貼設置為URL({{url}})", + "error-cant-invite-for-direct-room": "無法邀請使用者進入私訊", + "error-could-not-change-email": "無法更改電子郵件", + "error-could-not-change-name": "無法更改名稱", + "error-could-not-change-username": "無法更改使用者名稱", + "error-could-not-change-status": "無法更改狀態", + "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-save-video": "錯誤,無法儲存影片", + "error-field-unavailable": "{{field}} 已被使用 :(", + "error-file-too-large": "檔案太大", + "error-importer-not-defined": "沒有正確定義,它缺少匯入類型", + "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": "無效的電子郵件{{email}}", + "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": "無效的轉址", + "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": "無效的 token", + "error-invalid-triggerWords": "無效的關鍵字", + "error-invalid-urls": "無效的網址", + "error-invalid-user": "無效的使用者", + "error-invalid-username": "無效的使用者名稱", + "error-invalid-webhook-response": "webhook 網址以200以外的狀態響應", + "error-message-deleting-blocked": "訊息刪除已停用", + "error-message-editing-blocked": "訊息編輯已停用", + "error-message-size-exceeded": "訊息大小超出上限", + "error-missing-unsubscribe-link": "您必須提供[取消訂閱]連結。", + "error-no-tokens-for-this-user": "這名使用者沒有Token", + "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-disabled": "使用者註冊已停用", + "error-user-registration-secret": "只能透過加密網址進行使用者註冊", + "error-you-are-last-owner": "您是最後的擁有者。請刪除此人之前設置一個新的擁有者。", + "Actions": "操作", + "activity": "活動時間", + "Activity": "以活動時間排序", + "Add_Reaction": "增加表情貼", + "Add_Server": "新增伺服器", + "Add_users": "新增使用者", + "Admin_Panel": "管理者面板", + "Agent": "代理", + "Alert": "警報", + "alert": "警報", + "alerts": "警報", + "All_users_in_the_channel_can_write_new_messages": "頻道中的所有使用者都可以發送新訊息", + "A_meaningful_name_for_the_discussion_room": "取一個有意義的討論區名稱", + "All": "所有", + "All_Messages": "全部訊息", + "Allow_Reactions": "允許表情貼", + "Alphabetical": "以名稱排序", + "and_more": "和更多的", + "and": "和", + "announcement": "公告", + "Announcement": "公告", + "Apply_Your_Certificate": "使用自己的憑證", + "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": "大頭貼地址", + "Away": "離開", + "Back": "返回", + "Black": "黑色", + "Block_user": "封鎖此用戶", + "Browser": "瀏覽器", + "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": "通話已經結束!", + "Clear_cookies_alert": "是否清除所有 cookies?", + "Clear_cookies_desc": "本操作將清除所有登入 cookies,以登入其他帳號", + "Clear_cookies_yes": "是,清除 cookies", + "Clear_cookies_no": "否,保留 cookies", + "Click_to_join": "點擊以參與", + "Close": "關閉", + "Close_emoji_selector": "關閉 emoji 選擇器", + "Closing_chat": "結束聊天", + "Change_language_loading": "切換語言", + "Chat_closed_by_agent": "聊天已被客服關閉", + "Choose": "選擇", + "Choose_from_library": "從媒體庫選擇", + "Choose_file": "選擇檔案", + "Choose_where_you_want_links_be_opened": "請選擇您要將連結開啟在", + "Code": "程式碼", + "Code_or_password_invalid": "驗證碼或密碼不正確", + "Collaborative": "協作", + "Confirm": "確認", + "Connect": "連接", + "Connected": "已連接", + "connecting_server": "連線至伺服器", + "Connecting": "連接中", + "Contact_us": "聯絡我們", + "Contact_your_server_admin": "請聯絡系統管理員", + "Continue_with": "繼續採用", + "Copied_to_clipboard": "複製到剪貼簿", + "Copy": "複製", + "Conversation": "對話", + "Permalink": "永久連結", + "Certificate_password": "憑證密碼", + "Clear_cache": "清除本機資料", + "Clear_cache_loading": "清除快取", + "Whats_the_password_for_your_certificate": "您的憑證密碼是?", + "Create_account": "新建帳戶", + "Create_Channel": "新建頻道", + "Create_Direct_Messages": "新增私人訊息", + "Create_Discussion": "新增討論區", + "Created_snippet": "新增程式碼片段", + "Create_a_new_workspace": "建立一個新的工作區", + "Create": "建立", + "Custom_Status": "自訂狀態", + "Dark": "深色", + "Dark_level": "深色程度", + "Default": "預設", + "Default_browser": "預設瀏覽器", + "Delete_Room_Warning": "刪除聊天室將連帶刪除其中所有訊息。此操作將無法還原。", + "Department": "部門", + "delete": "刪除", + "Delete": "刪除", + "DELETE": "刪除", + "deleting_room": "正在刪除聊天室", + "description": "描述", + "Description": "描述", + "Desktop_Options": "桌面選項", + "Desktop_Notifications": "桌面通知", + "Desktop_Alert_info": "這些通知將發送至桌面", + "Directory": "目錄", + "Direct_Messages": "私訊", + "Disable_notifications": "禁用訊息通知", + "Discussions": "討論區", + "Discussion_Desc": "幫助保持事態更新!透過建立討論,一個和所選頻道雙向關聯的子頻道將會被建立。", + "Discussion_name": "討論區名稱", + "Done": "完成", + "Dont_Have_An_Account": "還未擁有帳號?", + "Do_you_have_an_account": "是否擁有帳號?", + "Do_you_have_a_certificate": "是否擁有憑證?", + "Do_you_really_want_to_key_this_room_question_mark": "您真的想要{{key}}這個聊天室嗎?", + "E2E_Encryption": "E2E 加密", + "E2E_How_It_Works_info1": "現在您可以建立加密私人群組和私訊。您也可以變更已存在的私人群組或是私訊來加密。", + "E2E_How_It_Works_info2": "這是*點對點的加密*,所以用來加密/解密的金鑰將不會儲存到伺服器上。為此,*您必須安全存放您的密碼*。如果您希望在其他裝置上使用對點加密時,會需要輸入此密碼。", + "E2E_How_It_Works_info3": "如果繼續,將自動產生一組 E2E 密碼", + "E2E_How_It_Works_info4": "您也可以隨時從已輸入既有密碼的任何瀏覽器設定新密碼給您的加密金鑰。", + "edit": "編輯", + "edited": "已編輯", + "Edit": "編輯", + "Edit_Status": "編輯狀態", + "Edit_Invite": "編輯邀請", + "End_to_end_encrypted_room": "E2E 加密聊天室", + "end_to_end_encryption": "E2E 加密", + "Email_Notification_Mode_All": "每次被標記或私訊", + "Email_Notification_Mode_Disabled": "禁用", + "Email_or_password_field_is_empty": "電子郵件或密碼欄位為空", + "Email": "電子郵件", + "email": "電子郵件", + "Empty_title": "空白標題", + "Enable_Auto_Translate": "開啟自動翻譯", + "Enable_notifications": "開啟訊息通知", + "Encrypted": "已加密", + "Encrypted_message": "加密訊息", + "Enter_Your_E2E_Password": "輸入您的 E2E 密碼", + "Enter_Your_Encryption_Password_desc1": "這將會允許您存取您的加密私人群組和私訊", + "Enter_Your_Encryption_Password_desc2": "您需要在任何使用此聊天的平台輸入密碼,以加/解密您的訊息", + "Encryption_error_title": "您的加密密碼似乎有誤", + "Encryption_error_desc": "無法使用匯入的加密金鑰來解密", + "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_password_If_this_email_is_registered": "如果此電子郵件已註冊,我們將發送有關如何重設密碼的說明。如果您未在短時間內收到電子郵件,請再試一次。", + "Forgot_password": "忘記密碼", + "Forgot_Password": "忘記密碼", + "Forward": "轉發", + "Forward_Chat": "轉發聊天", + "Forward_to_department": "轉發到部門", + "Forward_to_user": "轉發給使用者", + "Full_table": "點擊以查看完整表格", + "Generate_New_Link": "產生新的連結", + "Group_by_favorites": "我的最愛優先", + "Group_by_type": "以類型分組", + "Hide": "隱藏", + "Has_joined_the_channel": "已加入頻道", + "Has_joined_the_conversation": "已經加入此對話", + "Has_left_the_channel": "已離開頻道", + "Hide_System_Messages": "隱藏系統訊息", + "Hide_type_messages": "隱藏 '{{type}}' 訊息", + "How_It_Works": "運作方式", + "Message_HideType_uj": "隱藏“使用者加入”訊息", + "Message_HideType_ul": "隱藏“使用者離開”訊息", + "Message_HideType_ru": "隱藏“使用者已刪除”訊息", + "Message_HideType_au": "隱藏“使用者已增加”訊息", + "Message_HideType_mute_unmute": "隱藏“使用者靜音/取消靜音”訊息", + "Message_HideType_r": "隱藏“聊天室名稱已更改”的訊息", + "Message_HideType_ut": "隱藏“使用者已加入對話”的訊息", + "Message_HideType_wm": "隱藏“歡迎”的訊息", + "Message_HideType_rm": "隱藏“已刪除訊息”的訊息", + "Message_HideType_subscription_role_added": "隱藏“已設置角色”的訊息", + "Message_HideType_subscription_role_removed": "隱藏“不再定義的角色”的訊息", + "Message_HideType_room_archived": "隱藏“聊天室已封存”的訊息", + "Message_HideType_room_unarchived": "隱藏“聊天室未封存”的訊息", + "I_Saved_My_E2E_Password": "儲存我的 E2E 密碼", + "IP": "IP", + "In_app": "App 內", + "In_App_And_Desktop": "App 內及桌面", + "In_App_and_Desktop_Alert_info": "若 app 開啟時,會在螢幕上方顯示橫幅;顯示桌面通知", + "Invisible": "隱身", + "Invite": "邀請", + "is_a_valid_RocketChat_instance": "是一個有效的 Rocket.Chat 實例", + "is_not_a_valid_RocketChat_instance": "不是有效的 Rocket.Chat 實例", + "is_typing": "正在輸入", + "Invalid_or_expired_invite_token": "無效或到期的邀請 token", + "Invalid_server_version": "此 App 版本已不支援您正在連線之伺服器版本。當前版本: {{currentVersion}}.\\n\\n最低版本要求: {{minVersion}}", + "Invite_Link": "邀請連結", + "Invite_users": "邀請使用者", + "Join": "加入", + "Join_our_open_workspace": "加入開放工作區", + "Join_your_workspace": "加入您的工作區", + "Just_invited_people_can_access_this_channel": "僅有受邀者能存取此頻道", + "Language": "語言", + "last_message": "最後一則訊息", + "Leave_channel": "離開頻道", + "leaving_room": "離開聊天室", + "Leave": "離開", + "leave": "離開", + "Legal": "合法", + "Light": "淺色", + "License": "授權條款", + "Livechat": "即時聊天", + "Livechat_edit": "即時聊天編輯", + "Login": "登入", + "Login_error": "認證失敗!請再試一次", + "Login_with": "登入為", + "Logging_out": "正在登出", + "Logout": "登出", + "Max_number_of_uses": "最大使用次數", + "Max_number_of_users_allowed_is_number": "允許使用者上限數量 {{maxUsers}}", + "members": "成員", + "Members": "成員", + "Mentioned_Messages": "被提及的訊息", + "mentioned": "提到", + "Mentions": "被提及", + "Message_accessibility": "{{time}}來自{{user}}的訊息: {{message}}", + "Message_actions": "訊息操作", + "Message_pinned": "訊息被釘選", + "Message_removed": "訊息被刪除", + "Message_starred": "訊息被標註", + "Message_unstarred": "訊息被取消標註", + "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": "名稱", + "Navigation_history": "瀏覽歷史記錄", + "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_label_provided": "沒有提供 {{label}}", + "No_Message": "沒有訊息", + "No_messages_yet": "當前未有訊息", + "No_Reactions": "沒有表情貼", + "No_Read_Receipts": "沒有已讀人員", + "Not_logged": "沒有記錄", + "Not_RC_Server": "這不是一個 Rocket.Chat server.\\n{{contact}}", + "Nothing": "無", + "Nothing_to_save": "沒有東西可儲存!", + "Notify_active_in_this_room": "通知這個聊天室的活躍使用者", + "Notify_all_in_this_room": "通知這個聊天室的所有人", + "Notifications": "通知", + "Notification_Duration": "通知持續時間", + "Notification_Preferences": "通知偏好設定", + "No_available_agents_to_transfer": "沒有可用的代理進行傳輸", + "Offline": "離線", + "Oops": "哎呀!", + "Omnichannel": "Omnichannel", + "Open_Livechats": "打開即時聊天", + "Omnichannel_enable_alert": "您尚未啟用 Omnichannel,是否想要啟用?", + "Onboarding_description": "工作區是團隊或組織協作的空間。向工作區管理員詢問要加入的地址或為您的團隊創建一個。", + "Onboarding_join_workspace": "加入一個工作區", + "Onboarding_subtitle": "超越團隊合作", + "Onboarding_title": "歡迎來到 Rocket.Chat", + "Onboarding_join_open_description": "加入我們的開放工作區以與 Rocket.Chat 團隊及社群交談", + "Onboarding_agree_terms": "繼續,即表示您同意 Rocket.Chat", + "Onboarding_less_options": "較少選項", + "Onboarding_more_options": "較多選項", + "Online": "上線", + "Only_authorized_users_can_write_new_messages": "只有經過授權的使用者才能寫新訊息", + "Open_emoji_selector": "打開 emoji 選擇器", + "Open_Source_Communication": "開源溝通", + "Open_your_authentication_app_and_enter_the_code": "打開您的驗證應用程式並輸入代碼。您也可以使用其中一個備用代碼。", + "OR": "或", + "OS": "作業系統", + "Overwrites_the_server_configuration_and_use_room_config": "覆寫伺服器設置和使用聊天室設置", + "Password": "密碼", + "Parent_channel_or_group": "父頻道或群組", + "Permalink_copied_to_clipboard": "永久連結已複製到剪貼簿!", + "Phone": "電話", + "Pin": "釘選", + "Pinned_Messages": "釘選訊息", + "pinned": "已被釘選", + "Pinned": "被釘選", + "Please_add_a_comment": "請新增評論", + "Please_enter_your_password": "請輸入密碼", + "Please_wait": "請稍候", + "Preferences": "偏好設定", + "Preferences_saved": "偏好設定已被儲存!", + "Privacy_Policy": "隱私政策", + "Private_Channel": "私人頻道", + "Private": "私有的", + "Processing": "處理中", + "Profile_saved_successfully": "個人資料儲存成功!", + "Profile": "個人資料", + "Public_Channel": "公共頻道", + "Public": "公共", + "Push_Notifications": "推送通知", + "Push_Notifications_Alert_Info": "這些通知將在未開啟 App 時發送給您", + "Quote": "引用", + "Reactions_are_disabled": "表情貼被禁用", + "Reactions_are_enabled": "表情貼被啟用", + "Reactions": "表情貼", + "Read": "讀取", + "Read_External_Permission_Message": "Rocket.Chat 需要存取您裝置上的相片、多媒體及檔案", + "Read_External_Permission": "讀取媒體權限", + "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": "重置", + "Return": "返回", + "Review_app_title": "對此 App 滿意嗎?", + "Review_app_desc": "請在 {{store}} 給予我們 5 星好評", + "Review_app_yes": "沒問題", + "Review_app_no": "婉拒", + "Review_app_later": "之後再說", + "Review_app_unable_store": "無法開啟 {{store}}", + "Review_this_app": "評分此 App", + "Remove": "移除", + "Roles": "角色", + "Room_actions": "聊天室操作", + "Room_changed_announcement": "{{userBy}}將聊天室通知改為:{{announcement}}", + "Room_changed_avatar": "Room avatar changed by {{userBy}}", + "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": "儲存", + "Saved": "保存", + "saving_preferences": "儲存偏好設定", + "saving_profile": "儲存配置文件", + "saving_settings": "儲存設定", + "saved_to_gallery": "儲存至圖片庫", + "Save_Your_E2E_Password": "儲存您的 E2E 密碼", + "Save_Your_Encryption_Password": "儲存您的加密密碼", + "Save_Your_Encryption_Password_warning": "此密碼未被儲存在任何地方,為此您必須安全存放您的密碼", + "Save_Your_Encryption_Password_info": "請記住,如果你遺失了您的密碼,您將無法存取您的訊息並不可恢復", + "Search_Messages": "搜尋訊息", + "Search": "搜尋", + "Search_by": "搜尋", + "Search_global_users": "搜尋全域使用者", + "Search_global_users_description": "如果啟用,您將可以搜尋其他公司、伺服器上的任何使用者", + "Seconds": "{{second}} 秒", + "Security_and_privacy": "安全與隱私", + "Select_Avatar": "選擇大頭貼", + "Select_Server": "選擇伺服器", + "Select_Users": "選擇使用者", + "Select_a_Channel": "選擇一個頻道", + "Select_a_Department": "選擇一個部門", + "Select_an_option": "選擇一個選項", + "Select_a_User": "選擇一個使用者", + "Send": "發送", + "Send_audio_message": "發送語音訊息", + "Send_crash_report": "送出當機報告", + "Send_message": "發送訊息", + "Send_me_the_code_again": "再次發送代碼給我", + "Send_to": "發送到", + "Sending_to": "正發送到", + "Sent_an_attachment": "發送附件", + "Server": "伺服器", + "Servers": "伺服器", + "Server_version": "伺服器版本: {{version}}", + "Set_username_subtitle": "使用者名稱是用來讓其他使用者在訊息中提到您", + "Set_custom_status": "設定自訂狀態", + "Set_status": "設定狀態", + "Status_saved_successfully": "狀態儲存成功", + "Settings": "設定", + "Settings_succesfully_changed": "設定更改成功!", + "Share": "分享", + "Share_Link": "分享連結", + "Share_this_app": "分享此 app", + "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": "開始對話", + "Start_a_Discussion": "開始一個討論", + "Started_discussion": "已開始的討論", + "Started_call": "{{userBy}} 開始的通話", + "Submit": "送出", + "Table": "表格", + "Tags": "標籤", + "Take_a_photo": "拍照", + "Take_a_video": "錄影", + "Take_it": "拿去!", + "tap_to_change_status": "點擊即可更改狀態", + "Tap_to_view_servers_list": "點擊查看伺服器列表", + "Terms_of_Service": "服務條款", + "Theme": "佈景主題", + "The_user_wont_be_able_to_type_in_roomName": "此使用者將無法在 {{roomName}} 中輸入", + "The_user_will_be_able_to_type_in_roomName": "此使用者將可以在 {{roomName}} 中輸入", + "There_was_an_error_while_action": "{{action}}出現錯誤!", + "This_room_is_blocked": "這個聊天室已被鎖定", + "This_room_is_read_only": "這個聊天室是唯讀的", + "Thread": "討論串", + "Threads": "討論串", + "Timezone": "時區", + "To": "到", + "topic": "主題", + "Topic": "主題", + "Translate": "翻譯", + "Try_again": "再試一次", + "Two_Factor_Authentication": "雙重認證", + "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": "上傳文件?", + "User": "使用者", + "Users": "使用者", + "User_added_by": "由{{userBy}}添加的使用者 {{userAdded}}", + "User_Info": "使用者資訊", + "User_has_been_key": "使用者已被{{key}}", + "User_is_no_longer_role_by_": "{{userBy}}將角色 {{role}} 從使用者 {{user}} 身上移除", + "User_muted_by": "使用者 {{userMuted}} 被 {{userBy}} 靜音", + "User_removed_by": "使用者 {{userRemoved}} 被 {{userBy}} 移除", + "User_sent_an_attachment": "{{user}} 寄送了一個附件", + "User_unmuted_by": "使用者 {{userUnmuted}} 被 {{userBy}} 取消靜音", + "User_was_set_role_by_": "使用者 {{user}} 被 {{userBy}} 設置角色 {{role}}", + "Username_is_empty": "使用者名稱是空的", + "Username": "使用者名稱", + "Username_or_email": "使用者名稱或電子郵件", + "Uses_server_configuration": "使用伺服器設定", + "Validating": "正在驗證", + "Registration_Succeeded": "註冊成功", + "Verify": "驗證", + "Verify_email_title": "註冊成功", + "Verify_email_desc": "我們已經送出一封電子郵件,以確認您的註冊。如果您沒有很快收到,請再試一次。", + "Verify_your_email_for_the_code_we_sent": "檢查您的電子郵件以取得我們發送的代碼", + "Video_call": "視訊通話", + "View_Original": "檢視原文", + "Voice_call": "語音通話", + "Waiting_for_network": "等待網路連線", + "Websocket_disabled": "Websocket 已於此伺服器上禁用。\\n{{contact}}", + "Welcome": "歡迎", + "What_are_you_doing_right_now": "現在在做些什麼?", + "Whats_your_2fa": "您的 2FA 代碼是?", + "Without_Servers": "未連接至伺服器", + "Workspaces": "工作區", + "Would_you_like_to_return_the_inquiry": "你想回覆詢問嗎?", + "Write_External_Permission_Message": "Rocket.Chat 需要您圖片庫的存取權限以儲存圖片。", + "Write_External_Permission": "圖片庫權限", + "Yes": "是", + "Yes_action_it": "是的,{{action}}它!", + "Yesterday": "昨天", + "You_are_in_preview_mode": "您處於預覽模式", + "You_are_offline": "您處於離線狀態", + "You_can_search_using_RegExp_eg": "您可用 RegExp 進行搜尋。例如`/^text$/i`", + "You_colon": "你:", + "you_were_mentioned": "你被提到了", + "You_were_removed_from_channel": "您已從 {{channel}} 中被踢除", + "you": "你", + "You": "你", + "Logged_out_by_server": "伺服器端已將你登出,請重新登入", + "You_need_to_access_at_least_one_RocketChat_server_to_share_something": "您需要至少連接一個 Rocket.Chat 伺服器才能共享某些内容。", + "You_need_to_verifiy_your_email_address_to_get_notications": "您需要先驗證您的電子郵件以啟用通知", + "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": "您的邀請連結永久有效。", + "Your_workspace": "您的工作區", + "Your_password_is": "您的密碼", + "Version_no": "版本: {{version}}", + "You_will_not_be_able_to_recover_this_message": "您將無法恢復此訊息!", + "You_will_unset_a_certificate_for_this_server": "您將取消此伺服器的憑證設定", + "Change_Language": "切換語言", + "Crash_report_disclaimer": "我們絕不記錄您的聊天內容。 崩潰報告和分析事件僅與 Rocket.Chat 有關,以便識別和修復問題。", + "Type_message": "輸入訊息", + "Room_search": "搜索聊天室", + "Room_selection": "選擇房間(輸入 1...9)", + "Next_room": "下一個聊天室", + "Previous_room": "上一個聊天室", + "New_room": "新聊天室", + "Upload_room": "上傳至聊天室", + "Search_messages": "搜尋訊息", + "Scroll_messages": "訊息滾動", + "Reply_latest": "回覆最新訊息", + "Reply_in_Thread": "討論串回覆", + "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": "這將清除您的所有離線資料。", + "This_will_remove_all_data_from_this_server": "這將從此伺服器中刪除所有資料。", + "Mark_unread": "標記未讀", + "Wait_activation_warning": "您的帳號必須由管理員手動啟用後才能登入。", + "Screen_lock": "螢幕鎖定", + "Local_authentication_biometry_title": "驗證", + "Local_authentication_biometry_fallback": "使用通關密碼", + "Local_authentication_unlock_option": "以通關密碼解鎖", + "Local_authentication_change_passcode": "變更通關密碼", + "Local_authentication_info": "註: 如果您忘記了通關密碼,將需要移除並重新安裝此 App", + "Local_authentication_facial_recognition": "臉部辨識", + "Local_authentication_fingerprint": "指紋辨識", + "Local_authentication_unlock_with_label": "以 {{label}} 解鎖", + "Local_authentication_auto_lock_60": "1分鐘後", + "Local_authentication_auto_lock_300": "5分鐘後", + "Local_authentication_auto_lock_900": "15分鐘後", + "Local_authentication_auto_lock_1800": "半小時後", + "Local_authentication_auto_lock_3600": "一小時後", + "Passcode_enter_title": "請輸入通關密碼", + "Passcode_choose_title": "請輸入新通關密碼", + "Passcode_choose_confirm_title": "請確認新通關密碼", + "Passcode_choose_error": "不正確的通關密碼,請再試一次", + "Passcode_choose_force_set": "管理員設定必填", + "Passcode_app_locked_title": "App 已鎖定", + "Passcode_app_locked_subtitle": "{{timeLeft}} 秒後再進行嘗試", + "After_seconds_set_by_admin": "{{seconds}} 秒 (管理員設定)", + "Dont_activate": "現在不要啟用", + "Queued_chats": "聊天佇列", + "Queue_is_empty": "佇列是空的", + "Logout_from_other_logged_in_locations": "登出其他已登入的設備", + "You_will_be_logged_out_from_other_locations": "您將於其他設備上登出", + "Logged_out_of_other_clients_successfully": "成功登出其他用戶端", + "Logout_failed": "登出失敗", + "Log_analytics_events": "日誌分析事件", + "E2E_encryption_change_password_title": "變更加密密碼", + "E2E_encryption_change_password_description": "現在您可以建立加密私人群組和私訊。您也可以變更已存在的私人群組或是私訊來加密。\\n\\n這是點對點的加密,所以用來加密/解密的金鑰將不會儲存到伺服器上。為此,您必須安全存放您的密碼。如果您希望在其他裝置上使用對點加密時,將會需要輸入此密碼。", + "E2E_encryption_change_password_error": "變更 E2E 密碼時發生錯誤!", + "E2E_encryption_change_password_success": "E2E 密碼已成功變更!", + "E2E_encryption_change_password_message": "請確定您已將其安全存放至別處", + "E2E_encryption_change_password_confirmation": "是,我要變更", + "E2E_encryption_reset_title": "重設 E2E 金鑰", + "E2E_encryption_reset_description": "此選項將撤銷您目前的*E2E 金鑰*並將您登出。\\n當您再次登入時,Rocket.Chat 將產生新的一組金鑰並恢復您對任一有在線成員的加密聊天室之存取權限。\\n由於 E2E 加密的特性,Rocket.Chat 無法恢復無在線成員之聊天室之存取權限。", + "E2E_encryption_reset_button": "重設", + "E2E_encryption_reset_error": "重設 E2E 金鑰時發生錯誤!", + "E2E_encryption_reset_message": "您將被登出", + "E2E_encryption_reset_confirmation": "是,我要重設", + "Following": "正在追蹤", + "Threads_displaying_all": "顯示全部", + "Threads_displaying_following": "顯示追蹤中", + "Threads_displaying_unread": "顯示未讀", + "No_threads": "當前沒有討論串", + "No_threads_following": "當前沒有正在追蹤的討論", + "No_threads_unread": "當前沒有未讀的討論", + "Messagebox_Send_to_channel": "發送至頻道", + "Confirmation": "確認", + "invalid-room": "無效的房間" +} diff --git a/app/index.js b/app/index.tsx similarity index 69% rename from app/index.js rename to app/index.tsx index 54985c3c5..31128db26 100644 --- a/app/index.js +++ b/app/index.tsx @@ -1,17 +1,12 @@ import React from 'react'; -import { Linking, Dimensions } from 'react-native'; +import { Dimensions, Linking } from 'react-native'; import { AppearanceProvider } from 'react-native-appearance'; import { Provider } from 'react-redux'; import { KeyCommandsEmitter } from 'react-native-keycommands'; import RNScreens from 'react-native-screens'; import { SafeAreaProvider, initialWindowMetrics } from 'react-native-safe-area-context'; -import { - defaultTheme, - newThemeState, - subscribeTheme, - unsubscribeTheme -} from './utils/theme'; +import { defaultTheme, newThemeState, subscribeTheme, unsubscribeTheme } from './utils/theme'; import UserPreferences from './lib/userPreferences'; import EventEmitter from './utils/events'; import { appInit, appInitLocalSettings, setMasterDetail as setMasterDetailAction } from './actions/app'; @@ -24,9 +19,7 @@ import { ThemeContext } from './theme'; import { DimensionsContext } from './dimensions'; import RocketChat, { THEME_PREFERENCES_KEY } from './lib/rocketchat'; import { MIN_WIDTH_MASTER_DETAIL_LAYOUT } from './constants/tablet'; -import { - isTablet, supportSystemTheme -} from './utils/deviceInfo'; +import { isTablet, supportSystemTheme } from './utils/deviceInfo'; import { KEY_COMMAND } from './commands'; import AppContainer from './AppContainer'; import TwoFactor from './containers/TwoFactor'; @@ -40,7 +33,26 @@ import { isFDroidBuild } from './constants/environment'; RNScreens.enableScreens(); -const parseDeepLinking = (url) => { +interface IDimensions { + width: number; + height: number; + scale: number; + fontScale: number; +} + +interface IState { + theme: string; + themePreferences: { + currentTheme: 'automatic' | 'light'; + darkLevel: string; + }; + width: number; + height: number; + scale: number; + fontScale: number; +} + +const parseDeepLinking = (url: string) => { if (url) { url = url.replace(/rocketchat:\/\/|https:\/\/go.rocket.chat\//, ''); const regex = /^(room|auth|invite)\?/; @@ -61,16 +73,18 @@ const parseDeepLinking = (url) => { return null; }; -export default class Root extends React.Component { - constructor(props) { +export default class Root extends React.Component<{}, IState> { + private listenerTimeout!: any; + + private onKeyCommands: any; + + constructor(props: any) { super(props); this.init(); if (!isFDroidBuild) { this.initCrashReport(); } - const { - width, height, scale, fontScale - } = Dimensions.get('window'); + const { width, height, scale, fontScale } = Dimensions.get('window'); this.state = { theme: defaultTheme(), themePreferences: { @@ -110,8 +124,8 @@ export default class Root extends React.Component { } } - init = async() => { - UserPreferences.getMapAsync(THEME_PREFERENCES_KEY).then(this.setTheme); + init = async () => { + UserPreferences.getMapAsync(THEME_PREFERENCES_KEY).then((theme: any) => this.setTheme(theme)); store.dispatch(appInitLocalSettings()); // Open app from push notification @@ -123,7 +137,7 @@ export default class Root extends React.Component { // Open app from deep linking const deepLinking = await Linking.getInitialURL(); - const parsedDeepLinkingURL = parseDeepLinking(deepLinking); + const parsedDeepLinkingURL = parseDeepLinking(deepLinking!); if (parsedDeepLinkingURL) { store.dispatch(deepLinkingOpen(parsedDeepLinkingURL)); return; @@ -131,75 +145,66 @@ export default class Root extends React.Component { // Open app from app icon store.dispatch(appInit()); - } + }; - getMasterDetail = (width) => { + getMasterDetail = (width: number) => { if (!isTablet) { return false; } return width > MIN_WIDTH_MASTER_DETAIL_LAYOUT; - } + }; - setMasterDetail = (width) => { + setMasterDetail = (width: number) => { const isMasterDetail = this.getMasterDetail(width); store.dispatch(setMasterDetailAction(isMasterDetail)); }; // Dimensions update fires twice - onDimensionsChange = debounce(({ - window: { - width, height, scale, fontScale - } - }) => { + onDimensionsChange = debounce(({ window: { width, height, scale, fontScale } }: { window: IDimensions }) => { this.setDimensions({ - width, height, scale, fontScale + width, + height, + scale, + fontScale }); this.setMasterDetail(width); - }) + }); setTheme = (newTheme = {}) => { // change theme state - this.setState(prevState => newThemeState(prevState, newTheme), () => { - const { themePreferences } = this.state; - // subscribe to Appearance changes - subscribeTheme(themePreferences, this.setTheme); - }); - } + this.setState( + prevState => newThemeState(prevState, newTheme), + () => { + const { themePreferences } = this.state; + // subscribe to Appearance changes + subscribeTheme(themePreferences, this.setTheme); + } + ); + }; - setDimensions = ({ - width, height, scale, fontScale - }) => { - this.setState({ - width, height, scale, fontScale - }); - } + setDimensions = ({ width, height, scale, fontScale }: IDimensions) => { + this.setState({ width, height, scale, fontScale }); + }; initTablet = () => { const { width } = this.state; this.setMasterDetail(width); - this.onKeyCommands = KeyCommandsEmitter.addListener( - 'onKeyCommand', - (command) => { - EventEmitter.emit(KEY_COMMAND, { event: command }); - } - ); - } + this.onKeyCommands = KeyCommandsEmitter.addListener('onKeyCommand', (command: unknown) => { + EventEmitter.emit(KEY_COMMAND, { event: command }); + }); + }; initCrashReport = () => { - RocketChat.getAllowCrashReport() - .then((allowCrashReport) => { - toggleCrashErrorsReport(allowCrashReport); - }); - RocketChat.getAllowAnalyticsEvents() - .then((allowAnalyticsEvents) => { - toggleAnalyticsEventsReport(allowAnalyticsEvents); - }); - } + RocketChat.getAllowCrashReport().then(allowCrashReport => { + toggleCrashErrorsReport(allowCrashReport); + }); + RocketChat.getAllowAnalyticsEvents().then(allowAnalyticsEvents => { + toggleAnalyticsEventsReport(allowAnalyticsEvents); + }); + }; render() { - const { - themePreferences, theme, width, height, scale, fontScale - } = this.state; + const { themePreferences, theme, width, height, scale, fontScale } = this.state; return ( <SafeAreaProvider initialMetrics={initialWindowMetrics}> <AppearanceProvider> @@ -209,8 +214,7 @@ export default class Root extends React.Component { theme, themePreferences, setTheme: this.setTheme - }} - > + }}> <DimensionsContext.Provider value={{ width, @@ -218,8 +222,7 @@ export default class Root extends React.Component { scale, fontScale, setDimensions: this.setDimensions - }} - > + }}> <ActionSheetProvider> <AppContainer /> <TwoFactor /> diff --git a/app/lib/Icons.js b/app/lib/Icons.js index 9ab432a72..839ccca87 100644 --- a/app/lib/Icons.js +++ b/app/lib/Icons.js @@ -2,10 +2,6 @@ import { createIconSetFromIcoMoon } from 'react-native-vector-icons'; import icoMoonConfig from './selection.json'; -const CustomIcon = createIconSetFromIcoMoon( - icoMoonConfig, - 'custom', - 'custom.ttf' -); +const CustomIcon = createIconSetFromIcoMoon(icoMoonConfig, 'custom', 'custom.ttf'); export { CustomIcon }; diff --git a/app/lib/Navigation.js b/app/lib/Navigation.js deleted file mode 100644 index 34aba1769..000000000 --- a/app/lib/Navigation.js +++ /dev/null @@ -1,25 +0,0 @@ -import * as React from 'react'; -import { CommonActions, StackActions } from '@react-navigation/native'; - -const navigationRef = React.createRef(); -const routeNameRef = React.createRef(); - -function navigate(name, params) { - navigationRef.current?.navigate(name, params); -} - -function back() { - navigationRef.current?.dispatch(CommonActions.goBack()); -} - -function replace(name, params) { - navigationRef.current?.dispatch(StackActions.replace(name, params)); -} - -export default { - navigationRef, - routeNameRef, - navigate, - back, - replace -}; diff --git a/app/lib/Navigation.ts b/app/lib/Navigation.ts new file mode 100644 index 000000000..28f2656cf --- /dev/null +++ b/app/lib/Navigation.ts @@ -0,0 +1,25 @@ +import * as React from 'react'; +import { CommonActions, NavigationContainerRef, StackActions } from '@react-navigation/native'; + +const navigationRef = React.createRef<NavigationContainerRef>(); +const routeNameRef: React.MutableRefObject<NavigationContainerRef | null> = React.createRef(); + +function navigate(name: string, params?: any) { + navigationRef.current?.navigate(name, params); +} + +function back() { + navigationRef.current?.dispatch(CommonActions.goBack()); +} + +function replace(name: string, params: any) { + navigationRef.current?.dispatch(StackActions.replace(name, params)); +} + +export default { + navigationRef, + routeNameRef, + navigate, + back, + replace +}; diff --git a/app/lib/ShareNavigation.js b/app/lib/ShareNavigation.js deleted file mode 100644 index 6cc77eac2..000000000 --- a/app/lib/ShareNavigation.js +++ /dev/null @@ -1,9 +0,0 @@ -import { createRef } from 'react'; - -const navigationRef = createRef(); -const routeNameRef = createRef(); - -export default { - navigationRef, - routeNameRef -}; diff --git a/app/lib/ShareNavigation.ts b/app/lib/ShareNavigation.ts new file mode 100644 index 000000000..5cc15c2e1 --- /dev/null +++ b/app/lib/ShareNavigation.ts @@ -0,0 +1,10 @@ +import * as React from 'react'; +import { NavigationContainerRef } from '@react-navigation/native'; + +const navigationRef = React.createRef<NavigationContainerRef>(); +const routeNameRef: React.MutableRefObject<NavigationContainerRef | null> = React.createRef(); + +export default { + navigationRef, + routeNameRef +}; diff --git a/app/lib/appStateMiddleware.js b/app/lib/appStateMiddleware.js index 7bc5375f0..93fdff95a 100644 --- a/app/lib/appStateMiddleware.js +++ b/app/lib/appStateMiddleware.js @@ -3,33 +3,35 @@ import { AppState } from 'react-native'; import { APP_STATE } from '../actions/actionsTypes'; -export default () => createStore => (...args) => { - const store = createStore(...args); +export default () => + createStore => + (...args) => { + const store = createStore(...args); - let currentState = ''; + let currentState = ''; - const handleAppStateChange = (nextAppState) => { - if (nextAppState !== 'inactive') { - if (currentState !== nextAppState) { - let type; - if (nextAppState === 'active') { - type = APP_STATE.FOREGROUND; - } else if (nextAppState === 'background') { - type = APP_STATE.BACKGROUND; - } - if (type) { - store.dispatch({ - type - }); + const handleAppStateChange = nextAppState => { + if (nextAppState !== 'inactive') { + if (currentState !== nextAppState) { + let type; + if (nextAppState === 'active') { + type = APP_STATE.FOREGROUND; + } else if (nextAppState === 'background') { + type = APP_STATE.BACKGROUND; + } + if (type) { + store.dispatch({ + type + }); + } } + currentState = nextAppState; } - currentState = nextAppState; - } + }; + + AppState.addEventListener('change', handleAppStateChange); + + // setTimeout to allow redux-saga to catch the initial state fired by redux-enhancer-react-native-appstate library + setTimeout(() => handleAppStateChange(AppState.currentState)); + return store; }; - - AppState.addEventListener('change', handleAppStateChange); - - // setTimeout to allow redux-saga to catch the initial state fired by redux-enhancer-react-native-appstate library - setTimeout(() => handleAppStateChange(AppState.currentState)); - return store; -}; diff --git a/app/lib/createStore.js b/app/lib/createStore.js index da52de600..a9608de6c 100644 --- a/app/lib/createStore.js +++ b/app/lib/createStore.js @@ -1,4 +1,4 @@ -import { createStore, applyMiddleware, compose } from 'redux'; +import { applyMiddleware, compose, createStore } from 'redux'; import createSagaMiddleware from 'redux-saga'; import reducers from '../reducers'; @@ -23,10 +23,7 @@ if (__DEV__) { ); } else { sagaMiddleware = createSagaMiddleware(); - enhancers = compose( - applyAppStateMiddleware(), - applyMiddleware(sagaMiddleware) - ); + enhancers = compose(applyAppStateMiddleware(), applyMiddleware(sagaMiddleware)); } const store = createStore(reducers, enhancers); diff --git a/app/lib/database/index.js b/app/lib/database/index.js index 67ae9782c..dfda88a98 100644 --- a/app/lib/database/index.js +++ b/app/lib/database/index.js @@ -2,6 +2,9 @@ import { Database } from '@nozbe/watermelondb'; import SQLiteAdapter from '@nozbe/watermelondb/adapters/sqlite'; import logger from '@nozbe/watermelondb/utils/common/logger'; +import { isIOS } from '../../utils/deviceInfo'; +import appGroup from '../../utils/appGroup'; +import { isOfficial } from '../../constants/environment'; import Subscription from './model/Subscription'; import Room from './model/Room'; import Message from './model/Message'; @@ -15,28 +18,21 @@ import Role from './model/Role'; import Permission from './model/Permission'; import SlashCommand from './model/SlashCommand'; import User from './model/User'; - import LoggedUser from './model/servers/User'; import Server from './model/servers/Server'; import ServersHistory from './model/ServersHistory'; - import serversSchema from './schema/servers'; import appSchema from './schema/app'; - import migrations from './model/migrations'; import serversMigrations from './model/servers/migrations'; -import { isIOS } from '../../utils/deviceInfo'; -import appGroup from '../../utils/appGroup'; -import { isOfficial } from '../../constants/environment'; - const appGroupPath = isIOS ? appGroup.path : ''; if (__DEV__ && isIOS) { console.log(appGroupPath); } -const getDatabasePath = name => `${ appGroupPath }${ name }${ isOfficial ? '' : '-experimental' }.db`; +const getDatabasePath = name => `${appGroupPath}${name}${isOfficial ? '' : '-experimental'}.db`; export const getDatabase = (database = '') => { const path = database.replace(/(^\w+:|^)\/\//, '').replace(/\//g, '.'); @@ -80,7 +76,7 @@ class DB { modelClasses: [Server, LoggedUser, ServersHistory], actionsEnabled: true }) - } + }; get active() { return this.databases.shareDB || this.databases.activeDB; diff --git a/app/lib/database/model/CustomEmoji.js b/app/lib/database/model/CustomEmoji.js index fdf5d2063..9f3e1b6a8 100644 --- a/app/lib/database/model/CustomEmoji.js +++ b/app/lib/database/model/CustomEmoji.js @@ -1,5 +1,5 @@ import { Model } from '@nozbe/watermelondb'; -import { field, date, json } from '@nozbe/watermelondb/decorators'; +import { date, field, json } from '@nozbe/watermelondb/decorators'; import { sanitizer } from '../utils'; diff --git a/app/lib/database/model/Message.js b/app/lib/database/model/Message.js index 52cf63f0c..a03902a26 100644 --- a/app/lib/database/model/Message.js +++ b/app/lib/database/model/Message.js @@ -1,7 +1,5 @@ import { Model } from '@nozbe/watermelondb'; -import { - field, relation, date, json -} from '@nozbe/watermelondb/decorators'; +import { date, field, json, relation } from '@nozbe/watermelondb/decorators'; import { sanitizer } from '../utils'; @@ -12,7 +10,7 @@ export default class Message extends Model { static associations = { subscriptions: { type: 'belongs_to', key: 'rid' } - } + }; @field('msg') msg; diff --git a/app/lib/database/model/Permission.js b/app/lib/database/model/Permission.js index 8771de4dc..5923f83dc 100644 --- a/app/lib/database/model/Permission.js +++ b/app/lib/database/model/Permission.js @@ -1,5 +1,5 @@ import { Model } from '@nozbe/watermelondb'; -import { json, date } from '@nozbe/watermelondb/decorators'; +import { date, json } from '@nozbe/watermelondb/decorators'; import { sanitizer } from '../utils'; diff --git a/app/lib/database/model/ServersHistory.js b/app/lib/database/model/ServersHistory.js index 469775f13..4bbef3e98 100644 --- a/app/lib/database/model/ServersHistory.js +++ b/app/lib/database/model/ServersHistory.js @@ -1,5 +1,5 @@ import { Model } from '@nozbe/watermelondb'; -import { field, date, readonly } from '@nozbe/watermelondb/decorators'; +import { date, field, readonly } from '@nozbe/watermelondb/decorators'; export default class ServersHistory extends Model { static table = 'servers_history'; @@ -8,5 +8,5 @@ export default class ServersHistory extends Model { @field('username') username; - @readonly @date('updated_at') updatedAt + @readonly @date('updated_at') updatedAt; } diff --git a/app/lib/database/model/Setting.js b/app/lib/database/model/Setting.js index 050e70981..753d965ba 100644 --- a/app/lib/database/model/Setting.js +++ b/app/lib/database/model/Setting.js @@ -1,5 +1,5 @@ import { Model } from '@nozbe/watermelondb'; -import { field, date, json } from '@nozbe/watermelondb/decorators'; +import { date, field, json } from '@nozbe/watermelondb/decorators'; import { sanitizer } from '../utils'; diff --git a/app/lib/database/model/SlashCommand.js b/app/lib/database/model/SlashCommand.js index 8d792f710..418e72144 100644 --- a/app/lib/database/model/SlashCommand.js +++ b/app/lib/database/model/SlashCommand.js @@ -4,13 +4,13 @@ import { field } from '@nozbe/watermelondb/decorators'; export default class SlashCommand extends Model { static table = 'slash_commands'; - @field('params') params; + @field('params') params; - @field('description') description; + @field('description') description; - @field('client_only') clientOnly; + @field('client_only') clientOnly; - @field('provides_preview') providesPreview; + @field('provides_preview') providesPreview; - @field('app_id') appId; + @field('app_id') appId; } diff --git a/app/lib/database/model/Subscription.js b/app/lib/database/model/Subscription.js index 275dae217..aab0e0bbb 100644 --- a/app/lib/database/model/Subscription.js +++ b/app/lib/database/model/Subscription.js @@ -1,7 +1,6 @@ import { Model } from '@nozbe/watermelondb'; -import { - field, date, json, children -} from '@nozbe/watermelondb/decorators'; +import { children, date, field, json } from '@nozbe/watermelondb/decorators'; + import { sanitizer } from '../utils'; export const TABLE_NAME = 'subscriptions'; @@ -14,7 +13,7 @@ export default class Subscription extends Model { threads: { type: 'has_many', foreignKey: 'rid' }, thread_messages: { type: 'has_many', foreignKey: 'subscription_id' }, uploads: { type: 'has_many', foreignKey: 'rid' } - } + }; @field('_id') _id; diff --git a/app/lib/database/model/Thread.js b/app/lib/database/model/Thread.js index 04e658392..5d1246af8 100644 --- a/app/lib/database/model/Thread.js +++ b/app/lib/database/model/Thread.js @@ -1,7 +1,5 @@ import { Model } from '@nozbe/watermelondb'; -import { - field, relation, date, json -} from '@nozbe/watermelondb/decorators'; +import { date, field, json, relation } from '@nozbe/watermelondb/decorators'; import { sanitizer } from '../utils'; @@ -12,7 +10,7 @@ export default class Thread extends Model { static associations = { subscriptions: { type: 'belongs_to', key: 'rid' } - } + }; @field('msg') msg; diff --git a/app/lib/database/model/ThreadMessage.js b/app/lib/database/model/ThreadMessage.js index 687e09f96..27ea0e850 100644 --- a/app/lib/database/model/ThreadMessage.js +++ b/app/lib/database/model/ThreadMessage.js @@ -1,7 +1,5 @@ import { Model } from '@nozbe/watermelondb'; -import { - field, relation, date, json -} from '@nozbe/watermelondb/decorators'; +import { date, field, json, relation } from '@nozbe/watermelondb/decorators'; import { sanitizer } from '../utils'; @@ -12,7 +10,7 @@ export default class ThreadMessage extends Model { static associations = { subscriptions: { type: 'belongs_to', key: 'subscription_id' } - } + }; @field('msg') msg; diff --git a/app/lib/database/model/Upload.js b/app/lib/database/model/Upload.js index 3a6074099..810fcd5b7 100644 --- a/app/lib/database/model/Upload.js +++ b/app/lib/database/model/Upload.js @@ -6,7 +6,7 @@ export default class Upload extends Model { static associations = { subscriptions: { type: 'belongs_to', key: 'rid' } - } + }; @field('path') path; diff --git a/app/lib/database/model/migrations.js b/app/lib/database/model/migrations.js index cdc65ef0f..fe32ec4f3 100644 --- a/app/lib/database/model/migrations.js +++ b/app/lib/database/model/migrations.js @@ -1,4 +1,4 @@ -import { schemaMigrations, addColumns, createTable } from '@nozbe/watermelondb/Schema/migrations'; +import { addColumns, createTable, schemaMigrations } from '@nozbe/watermelondb/Schema/migrations'; export default schemaMigrations({ migrations: [ @@ -7,9 +7,7 @@ export default schemaMigrations({ steps: [ addColumns({ table: 'subscriptions', - columns: [ - { name: 'jitsi_timeout', type: 'number', isOptional: true } - ] + columns: [{ name: 'jitsi_timeout', type: 'number', isOptional: true }] }) ] }, @@ -18,9 +16,7 @@ export default schemaMigrations({ steps: [ addColumns({ table: 'subscriptions', - columns: [ - { name: 'hide_unread_status', type: 'boolean', isOptional: true } - ] + columns: [{ name: 'hide_unread_status', type: 'boolean', isOptional: true }] }) ] }, @@ -29,15 +25,11 @@ export default schemaMigrations({ steps: [ addColumns({ table: 'messages', - columns: [ - { name: 'blocks', type: 'string', isOptional: true } - ] + columns: [{ name: 'blocks', type: 'string', isOptional: true }] }), addColumns({ table: 'slash_commands', - columns: [ - { name: 'app_id', type: 'string', isOptional: true } - ] + columns: [{ name: 'app_id', type: 'string', isOptional: true }] }) ] }, @@ -46,9 +38,7 @@ export default schemaMigrations({ steps: [ addColumns({ table: 'settings', - columns: [ - { name: 'value_as_array', type: 'string', isOptional: true } - ] + columns: [{ name: 'value_as_array', type: 'string', isOptional: true }] }) ] }, @@ -57,9 +47,7 @@ export default schemaMigrations({ steps: [ addColumns({ table: 'subscriptions', - columns: [ - { name: 'sys_mes', type: 'string', isOptional: true } - ] + columns: [{ name: 'sys_mes', type: 'string', isOptional: true }] }) ] }, @@ -80,21 +68,15 @@ export default schemaMigrations({ steps: [ addColumns({ table: 'messages', - columns: [ - { name: 'emoji', type: 'string', isOptional: true } - ] + columns: [{ name: 'emoji', type: 'string', isOptional: true }] }), addColumns({ table: 'thread_messages', - columns: [ - { name: 'emoji', type: 'string', isOptional: true } - ] + columns: [{ name: 'emoji', type: 'string', isOptional: true }] }), addColumns({ table: 'threads', - columns: [ - { name: 'emoji', type: 'string', isOptional: true } - ] + columns: [{ name: 'emoji', type: 'string', isOptional: true }] }), addColumns({ table: 'subscriptions', @@ -124,9 +106,7 @@ export default schemaMigrations({ steps: [ addColumns({ table: 'subscriptions', - columns: [ - { name: 'group_mentions', type: 'number', isOptional: true } - ] + columns: [{ name: 'group_mentions', type: 'number', isOptional: true }] }) ] }, @@ -143,27 +123,19 @@ export default schemaMigrations({ }), addColumns({ table: 'messages', - columns: [ - { name: 'e2e', type: 'string', isOptional: true } - ] + columns: [{ name: 'e2e', type: 'string', isOptional: true }] }), addColumns({ table: 'thread_messages', - columns: [ - { name: 'e2e', type: 'string', isOptional: true } - ] + columns: [{ name: 'e2e', type: 'string', isOptional: true }] }), addColumns({ table: 'threads', - columns: [ - { name: 'e2e', type: 'string', isOptional: true } - ] + columns: [{ name: 'e2e', type: 'string', isOptional: true }] }), addColumns({ table: 'rooms', - columns: [ - { name: 'e2e_key_id', type: 'string', isOptional: true } - ] + columns: [{ name: 'e2e_key_id', type: 'string', isOptional: true }] }) ] }, @@ -172,9 +144,7 @@ export default schemaMigrations({ steps: [ addColumns({ table: 'messages', - columns: [ - { name: 'tshow', type: 'boolean', isOptional: true } - ] + columns: [{ name: 'tshow', type: 'boolean', isOptional: true }] }), createTable({ name: 'users', @@ -196,9 +166,7 @@ export default schemaMigrations({ }), addColumns({ table: 'rooms', - columns: [ - { name: 'avatar_etag', type: 'string', isOptional: true } - ] + columns: [{ name: 'avatar_etag', type: 'string', isOptional: true }] }) ] }, @@ -207,9 +175,7 @@ export default schemaMigrations({ steps: [ addColumns({ table: 'subscriptions', - columns: [ - { name: 'ignored', type: 'string', isOptional: true } - ] + columns: [{ name: 'ignored', type: 'string', isOptional: true }] }) ] }, diff --git a/app/lib/database/model/servers/Server.js b/app/lib/database/model/servers/Server.js index df770a32e..0bff69ffe 100644 --- a/app/lib/database/model/servers/Server.js +++ b/app/lib/database/model/servers/Server.js @@ -1,5 +1,5 @@ import { Model } from '@nozbe/watermelondb'; -import { field, date } from '@nozbe/watermelondb/decorators'; +import { date, field } from '@nozbe/watermelondb/decorators'; export default class Server extends Model { static table = 'servers'; diff --git a/app/lib/database/model/servers/migrations.js b/app/lib/database/model/servers/migrations.js index 80954d475..51ec2b73b 100644 --- a/app/lib/database/model/servers/migrations.js +++ b/app/lib/database/model/servers/migrations.js @@ -1,4 +1,4 @@ -import { schemaMigrations, addColumns, createTable } from '@nozbe/watermelondb/Schema/migrations'; +import { addColumns, createTable, schemaMigrations } from '@nozbe/watermelondb/Schema/migrations'; export default schemaMigrations({ migrations: [ @@ -7,9 +7,7 @@ export default schemaMigrations({ steps: [ addColumns({ table: 'users', - columns: [ - { name: 'statusText', type: 'string', isOptional: true } - ] + columns: [{ name: 'statusText', type: 'string', isOptional: true }] }) ] }, @@ -32,9 +30,7 @@ export default schemaMigrations({ steps: [ addColumns({ table: 'servers', - columns: [ - { name: 'unique_id', type: 'string', isOptional: true } - ] + columns: [{ name: 'unique_id', type: 'string', isOptional: true }] }) ] }, @@ -43,9 +39,7 @@ export default schemaMigrations({ steps: [ addColumns({ table: 'servers', - columns: [ - { name: 'enterprise_modules', type: 'string', isOptional: true } - ] + columns: [{ name: 'enterprise_modules', type: 'string', isOptional: true }] }) ] }, @@ -54,9 +48,7 @@ export default schemaMigrations({ steps: [ addColumns({ table: 'users', - columns: [ - { name: 'login_email_password', type: 'boolean', isOptional: true } - ] + columns: [{ name: 'login_email_password', type: 'boolean', isOptional: true }] }) ] }, @@ -65,9 +57,7 @@ export default schemaMigrations({ steps: [ addColumns({ table: 'servers', - columns: [ - { name: 'e2e_enable', type: 'boolean', isOptional: true } - ] + columns: [{ name: 'e2e_enable', type: 'boolean', isOptional: true }] }) ] }, @@ -95,14 +85,13 @@ export default schemaMigrations({ ] }) ] - }, { + }, + { toVersion: 11, steps: [ addColumns({ table: 'users', - columns: [ - { name: 'is_from_webview', type: 'boolean', isOptional: true } - ] + columns: [{ name: 'is_from_webview', type: 'boolean', isOptional: true }] }) ] } diff --git a/app/lib/database/schema/app.js b/app/lib/database/schema/app.js index ac7b97e78..33bde61de 100644 --- a/app/lib/database/schema/app.js +++ b/app/lib/database/schema/app.js @@ -236,9 +236,7 @@ export default appSchema({ }), tableSchema({ name: 'roles', - columns: [ - { name: 'description', type: 'string', isOptional: true } - ] + columns: [{ name: 'description', type: 'string', isOptional: true }] }), tableSchema({ name: 'permissions', diff --git a/app/lib/database/services/Message.js b/app/lib/database/services/Message.js index 5999446ba..594513dec 100644 --- a/app/lib/database/services/Message.js +++ b/app/lib/database/services/Message.js @@ -3,7 +3,7 @@ import { TABLE_NAME } from '../model/Message'; const getCollection = db => db.get(TABLE_NAME); -export const getMessageById = async(messageId) => { +export const getMessageById = async messageId => { const db = database.active; const messageCollection = getCollection(db); try { diff --git a/app/lib/database/services/Subscription.js b/app/lib/database/services/Subscription.js index 925bb97e4..68bf8ac96 100644 --- a/app/lib/database/services/Subscription.js +++ b/app/lib/database/services/Subscription.js @@ -3,7 +3,7 @@ import { TABLE_NAME } from '../model/Subscription'; const getCollection = db => db.get(TABLE_NAME); -export const getSubscriptionByRoomId = async(rid) => { +export const getSubscriptionByRoomId = async rid => { const db = database.active; const subCollection = getCollection(db); try { diff --git a/app/lib/database/services/Thread.js b/app/lib/database/services/Thread.js index 4c4208609..9b362dcb1 100644 --- a/app/lib/database/services/Thread.js +++ b/app/lib/database/services/Thread.js @@ -3,7 +3,7 @@ import { TABLE_NAME } from '../model/Thread'; const getCollection = db => db.get(TABLE_NAME); -export const getThreadById = async(tmid) => { +export const getThreadById = async tmid => { const db = database.active; const threadCollection = getCollection(db); try { diff --git a/app/lib/database/services/ThreadMessage.js b/app/lib/database/services/ThreadMessage.js index ca1e5fc83..e9509774e 100644 --- a/app/lib/database/services/ThreadMessage.js +++ b/app/lib/database/services/ThreadMessage.js @@ -3,7 +3,7 @@ import { TABLE_NAME } from '../model/ThreadMessage'; const getCollection = db => db.get(TABLE_NAME); -export const getThreadMessageById = async(messageId) => { +export const getThreadMessageById = async messageId => { const db = database.active; const threadMessageCollection = getCollection(db); try { diff --git a/app/lib/database/utils.test.js b/app/lib/database/utils.test.js index cbeae7b8a..8c1d054e1 100644 --- a/app/lib/database/utils.test.js +++ b/app/lib/database/utils.test.js @@ -4,7 +4,8 @@ import * as utils from './utils'; describe('sanitizeLikeStringTester', () => { // example chars that shouldn't return const disallowedChars = ',./;[]!@#$%^&*()_-=+~'; - const sanitizeLikeStringTester = str => expect(utils.sanitizeLikeString(`${ str }${ disallowedChars }`)).toBe(`${ str }${ '_'.repeat(disallowedChars.length) }`); + const sanitizeLikeStringTester = str => + expect(utils.sanitizeLikeString(`${str}${disallowedChars}`)).toBe(`${str}${'_'.repeat(disallowedChars.length)}`); test('render empty', () => { expect(utils.sanitizeLikeString(null)).toBe(undefined); diff --git a/app/lib/encryption/encryption.js b/app/lib/encryption/encryption.js index e4a7f2c14..99a12bd8c 100644 --- a/app/lib/encryption/encryption.js +++ b/app/lib/encryption/encryption.js @@ -3,29 +3,23 @@ import SimpleCrypto from 'react-native-simple-crypto'; import { sanitizedRaw } from '@nozbe/watermelondb/RawRecord'; import { Q } from '@nozbe/watermelondb'; -import { - toString, - utf8ToBuffer, - splitVectorData, - joinVectorData, - randomPassword -} from './utils'; -import { - E2E_PUBLIC_KEY, - E2E_PRIVATE_KEY, - E2E_RANDOM_PASSWORD_KEY, - E2E_STATUS, - E2E_MESSAGE_TYPE, - E2E_BANNER_TYPE -} from './constants'; import RocketChat from '../rocketchat'; -import { EncryptionRoom } from './index'; import UserPreferences from '../userPreferences'; import database from '../database'; import protectedFunction from '../methods/helpers/protectedFunction'; import Deferred from '../../utils/deferred'; import log from '../../utils/log'; import store from '../createStore'; +import { + E2E_BANNER_TYPE, + E2E_MESSAGE_TYPE, + E2E_PRIVATE_KEY, + E2E_PUBLIC_KEY, + E2E_RANDOM_PASSWORD_KEY, + E2E_STATUS +} from './constants'; +import { joinVectorData, randomPassword, splitVectorData, toString, utf8ToBuffer } from './utils'; +import { EncryptionRoom } from './index'; class Encryption { constructor() { @@ -43,7 +37,7 @@ class Encryption { } // Initialize Encryption client - initialize = (userId) => { + initialize = userId => { this.userId = userId; this.roomInstances = {}; @@ -54,7 +48,7 @@ class Encryption { // Mark Encryption client as ready this.readyPromise.resolve(); - } + }; get establishing() { const { banner } = store.getState().encryption; @@ -85,10 +79,10 @@ class Encryption { .catch(() => { this.ready = false; }); - } + }; // When a new participant join and request a new room encryption key - provideRoomKeyToUser = async(keyId, rid) => { + provideRoomKeyToUser = async (keyId, rid) => { // If the client is not ready if (!this.ready) { try { @@ -103,17 +97,17 @@ class Encryption { const roomE2E = await this.getRoomInstance(rid); return roomE2E.provideKeyToUser(keyId); - } + }; // Persist keys on UserPreferences - persistKeys = async(server, publicKey, privateKey) => { + persistKeys = async (server, publicKey, privateKey) => { this.privateKey = await SimpleCrypto.RSA.importKey(EJSON.parse(privateKey)); - await UserPreferences.setStringAsync(`${ server }-${ E2E_PUBLIC_KEY }`, EJSON.stringify(publicKey)); - await UserPreferences.setStringAsync(`${ server }-${ E2E_PRIVATE_KEY }`, privateKey); - } + await UserPreferences.setStringAsync(`${server}-${E2E_PUBLIC_KEY}`, EJSON.stringify(publicKey)); + await UserPreferences.setStringAsync(`${server}-${E2E_PRIVATE_KEY}`, privateKey); + }; // Could not obtain public-private keypair from server. - createKeys = async(userId, server) => { + createKeys = async (userId, server) => { // Generate new keys const key = await SimpleCrypto.RSA.generateKeys(2048); @@ -135,38 +129,30 @@ class Encryption { // Request e2e keys of all encrypted rooms await RocketChat.e2eRequestSubscriptionKeys(); - } + }; // Encode a private key before send it to the server - encodePrivateKey = async(privateKey, password, userId) => { + encodePrivateKey = async (privateKey, password, userId) => { const masterKey = await this.generateMasterKey(password, userId); const vector = await SimpleCrypto.utils.randomBytes(16); - const data = await SimpleCrypto.AES.encrypt( - utf8ToBuffer(privateKey), - masterKey, - vector - ); + const data = await SimpleCrypto.AES.encrypt(utf8ToBuffer(privateKey), masterKey, vector); return EJSON.stringify(new Uint8Array(joinVectorData(vector, data))); - } + }; // Decode a private key fetched from server - decodePrivateKey = async(privateKey, password, userId) => { + decodePrivateKey = async (privateKey, password, userId) => { const masterKey = await this.generateMasterKey(password, userId); const [vector, cipherText] = splitVectorData(EJSON.parse(privateKey)); - const privKey = await SimpleCrypto.AES.decrypt( - cipherText, - masterKey, - vector - ); + const privKey = await SimpleCrypto.AES.decrypt(cipherText, masterKey, vector); return toString(privKey); - } + }; // Generate a user master key, this is based on userId and a password - generateMasterKey = async(password, userId) => { + generateMasterKey = async (password, userId) => { const iterations = 1000; const hash = 'SHA256'; const keyLen = 32; @@ -174,38 +160,32 @@ class Encryption { const passwordBuffer = utf8ToBuffer(password); const saltBuffer = utf8ToBuffer(userId); - const masterKey = await SimpleCrypto.PBKDF2.hash( - passwordBuffer, - saltBuffer, - iterations, - keyLen, - hash - ); + const masterKey = await SimpleCrypto.PBKDF2.hash(passwordBuffer, saltBuffer, iterations, keyLen, hash); return masterKey; - } + }; // Create a random password to local created keys - createRandomPassword = async(server) => { + createRandomPassword = async server => { const password = randomPassword(); - await UserPreferences.setStringAsync(`${ server }-${ E2E_RANDOM_PASSWORD_KEY }`, password); + await UserPreferences.setStringAsync(`${server}-${E2E_RANDOM_PASSWORD_KEY}`, password); return password; - } + }; - changePassword = async(server, password) => { + changePassword = async (server, password) => { // Cast key to the format server is expecting const privateKey = await SimpleCrypto.RSA.exportKey(this.privateKey); // Encode the private key const encodedPrivateKey = await this.encodePrivateKey(EJSON.stringify(privateKey), password, this.userId); - const publicKey = await UserPreferences.getStringAsync(`${ server }-${ E2E_PUBLIC_KEY }`); + const publicKey = await UserPreferences.getStringAsync(`${server}-${E2E_PUBLIC_KEY}`); // Send the new keys to the server await RocketChat.e2eSetUserPublicAndPrivateKeys(EJSON.stringify(publicKey), encodedPrivateKey); - } + }; // get a encryption room instance - getRoomInstance = async(rid) => { + getRoomInstance = async rid => { // Prevent handshake again if (this.roomInstances[rid]?.ready) { return this.roomInstances[rid]; @@ -222,11 +202,11 @@ class Encryption { await roomE2E.handshake(); return roomE2E; - } + }; // Logic to decrypt all pending messages/threads/threadMessages // after initialize the encryption client - decryptPendingMessages = async(roomId) => { + decryptPendingMessages = async roomId => { const db = database.active; const messagesCollection = db.get('messages'); @@ -234,13 +214,7 @@ class Encryption { const threadMessagesCollection = db.get('thread_messages'); // e2e status is null or 'pending' and message type is 'e2e' - const whereClause = [ - Q.where('t', E2E_MESSAGE_TYPE), - Q.or( - Q.where('e2e', null), - Q.where('e2e', E2E_STATUS.PENDING) - ) - ]; + const whereClause = [Q.where('t', E2E_MESSAGE_TYPE), Q.or(Q.where('e2e', null), Q.where('e2e', E2E_STATUS.PENDING))]; // decrypt messages of a room if (roomId) { @@ -255,36 +229,40 @@ class Encryption { // Concat messages/threads/threadMessages let toDecrypt = [...messagesToDecrypt, ...threadsToDecrypt, ...threadMessagesToDecrypt]; - toDecrypt = await Promise.all(toDecrypt.map(async(message) => { - const { t, msg, tmsg } = message; - const { id: rid } = message.subscription; - // WM Object -> Plain Object - const newMessage = await this.decryptMessage({ - t, - rid, - msg, - tmsg - }); - if (message._hasPendingUpdate) { - console.log(message); - return; - } - return message.prepareUpdate(protectedFunction((m) => { - Object.assign(m, newMessage); - })); - })); + toDecrypt = await Promise.all( + toDecrypt.map(async message => { + const { t, msg, tmsg } = message; + const { id: rid } = message.subscription; + // WM Object -> Plain Object + const newMessage = await this.decryptMessage({ + t, + rid, + msg, + tmsg + }); + if (message._hasPendingUpdate) { + console.log(message); + return; + } + return message.prepareUpdate( + protectedFunction(m => { + Object.assign(m, newMessage); + }) + ); + }) + ); - await db.action(async() => { + await db.action(async () => { await db.batch(...toDecrypt); }); } catch (e) { log(e); } - } + }; // Logic to decrypt all pending subscriptions // after initialize the encryption client - decryptPendingSubscriptions = async() => { + decryptPendingSubscriptions = async () => { const db = database.active; const subCollection = db.get('subscriptions'); try { @@ -292,34 +270,39 @@ class Encryption { // If we select only encrypted rooms we can miss some room that changed their encrypted status const subsEncrypted = await subCollection.query(Q.where('e2e_key_id', Q.notEq(null))).fetch(); // We can't do this on database level since lastMessage is not a database object - const subsToDecrypt = subsEncrypted.filter(sub => ( - // Encrypted message - sub?.lastMessage?.t === E2E_MESSAGE_TYPE - // Message pending decrypt - && sub?.lastMessage?.e2e === E2E_STATUS.PENDING - )); - await Promise.all(subsToDecrypt.map(async(sub) => { - const { rid, lastMessage } = sub; - const newSub = await this.decryptSubscription({ rid, lastMessage }); - if (sub._hasPendingUpdate) { - console.log(sub); - return; - } - return sub.prepareUpdate(protectedFunction((m) => { - Object.assign(m, newSub); - })); - })); + const subsToDecrypt = subsEncrypted.filter( + sub => + // Encrypted message + sub?.lastMessage?.t === E2E_MESSAGE_TYPE && + // Message pending decrypt + sub?.lastMessage?.e2e === E2E_STATUS.PENDING + ); + await Promise.all( + subsToDecrypt.map(async sub => { + const { rid, lastMessage } = sub; + const newSub = await this.decryptSubscription({ rid, lastMessage }); + if (sub._hasPendingUpdate) { + console.log(sub); + return; + } + return sub.prepareUpdate( + protectedFunction(m => { + Object.assign(m, newSub); + }) + ); + }) + ); - await db.action(async() => { + await db.action(async () => { await db.batch(...subsToDecrypt); }); } catch (e) { log(e); } - } + }; // Decrypt a subscription lastMessage - decryptSubscription = async(subscription) => { + decryptSubscription = async subscription => { // If the subscription doesn't have a lastMessage just return if (!subscription?.lastMessage) { return subscription; @@ -361,23 +344,27 @@ class Encryption { // If the subscription doesn't exists yet if (!subRecord) { // Let's create the subscription with the data received - batch.push(subCollection.prepareCreate((s) => { - s._raw = sanitizedRaw({ id: rid }, subCollection.schema); - Object.assign(s, subscription); - })); - // If the subscription already exists but doesn't have the E2EKey yet + batch.push( + subCollection.prepareCreate(s => { + s._raw = sanitizedRaw({ id: rid }, subCollection.schema); + Object.assign(s, subscription); + }) + ); + // If the subscription already exists but doesn't have the E2EKey yet } else if (!subRecord.E2EKey && subscription.E2EKey) { if (!subRecord._hasPendingUpdate) { // Let's update the subscription with the received E2EKey - batch.push(subRecord.prepareUpdate((s) => { - s.E2EKey = subscription.E2EKey; - })); + batch.push( + subRecord.prepareUpdate(s => { + s.E2EKey = subscription.E2EKey; + }) + ); } } // If batch has some operation if (batch.length) { - await db.action(async() => { + await db.action(async () => { await db.batch(...batch); }); } @@ -394,10 +381,10 @@ class Encryption { ...subscription, lastMessage: decryptedMessage }; - } + }; // Encrypt a message - encryptMessage = async(message) => { + encryptMessage = async message => { const { rid } = message; const db = database.active; const subCollection = db.get('subscriptions'); @@ -427,10 +414,10 @@ class Encryption { // Send a non encrypted message return message; - } + }; // Decrypt a message - decryptMessage = async(message) => { + decryptMessage = async message => { const { t, e2e } = message; // Prevent create a new instance if this room was encrypted sometime ago @@ -453,13 +440,13 @@ class Encryption { const { rid } = message; const roomE2E = await this.getRoomInstance(rid); return roomE2E.decrypt(message); - } + }; // Decrypt multiple messages - decryptMessages = messages => Promise.all(messages.map(m => this.decryptMessage(m))) + decryptMessages = messages => Promise.all(messages.map(m => this.decryptMessage(m))); // Decrypt multiple subscriptions - decryptSubscriptions = subscriptions => Promise.all(subscriptions.map(s => this.decryptSubscription(s))) + decryptSubscriptions = subscriptions => Promise.all(subscriptions.map(s => this.decryptSubscription(s))); } const encryption = new Encryption(); diff --git a/app/lib/encryption/room.js b/app/lib/encryption/room.js index 0aa1b932e..d61c7d75a 100644 --- a/app/lib/encryption/room.js +++ b/app/lib/encryption/room.js @@ -2,23 +2,23 @@ import EJSON from 'ejson'; import { Base64 } from 'js-base64'; import SimpleCrypto from 'react-native-simple-crypto'; -import { - toString, - b64ToBuffer, - bufferToUtf8, - bufferToB64, - bufferToB64URI, - utf8ToBuffer, - splitVectorData, - joinVectorData -} from './utils'; -import { E2E_MESSAGE_TYPE, E2E_STATUS } from './constants'; import RocketChat from '../rocketchat'; import Deferred from '../../utils/deferred'; import debounce from '../../utils/debounce'; -import { Encryption } from './index'; import database from '../database'; import log from '../../utils/log'; +import { E2E_MESSAGE_TYPE, E2E_STATUS } from './constants'; +import { + b64ToBuffer, + bufferToB64, + bufferToB64URI, + bufferToUtf8, + joinVectorData, + splitVectorData, + toString, + utf8ToBuffer +} from './utils'; +import { Encryption } from './index'; export default class EncryptionRoom { constructor(roomId, userId) { @@ -36,7 +36,7 @@ export default class EncryptionRoom { } // Initialize the E2E room - handshake = async() => { + handshake = async () => { // If it's already ready we don't need to handshake again if (this.ready) { return; @@ -79,10 +79,10 @@ export default class EncryptionRoom { } catch (e) { log(e); } - } + }; // Import roomKey as an AES Decrypt key - importRoomKey = async(E2EKey, privateKey) => { + importRoomKey = async (E2EKey, privateKey) => { const roomE2EKey = E2EKey.slice(12); const decryptedKey = await SimpleCrypto.RSA.decrypt(roomE2EKey, privateKey); @@ -96,10 +96,10 @@ export default class EncryptionRoom { // Reference: https://www.javadoc.io/doc/com.nimbusds/nimbus-jose-jwt/5.1/com/nimbusds/jose/jwk/OctetSequenceKey.html const { k } = EJSON.parse(this.sessionKeyExportedString); this.roomKey = b64ToBuffer(k); - } + }; // Create a key to a room - createRoomKey = async() => { + createRoomKey = async () => { const key = await SimpleCrypto.utils.randomBytes(16); this.roomKey = key; @@ -122,7 +122,7 @@ export default class EncryptionRoom { await RocketChat.e2eSetRoomKeyID(this.roomId, this.keyID); await this.encryptRoomKey(); - } + }; // Request a key to this room // We're debouncing this function to avoid multiple calls @@ -130,31 +130,35 @@ export default class EncryptionRoom { // can send the encryption key at the moment. // Each time you see a encrypted message of a room that you don't have a key // this will be called again and run once in 5 seconds - requestRoomKey = debounce(async(e2eKeyId) => { - await RocketChat.e2eRequestRoomKey(this.roomId, e2eKeyId); - }, 5000, true) + requestRoomKey = debounce( + async e2eKeyId => { + await RocketChat.e2eRequestRoomKey(this.roomId, e2eKeyId); + }, + 5000, + true + ); // Create an encrypted key for this room based on users - encryptRoomKey = async() => { + encryptRoomKey = async () => { const result = await RocketChat.e2eGetUsersOfRoomWithoutKey(this.roomId); if (result.success) { const { users } = result; await Promise.all(users.map(user => this.encryptRoomKeyForUser(user))); } - } + }; // Encrypt the room key to each user in - encryptRoomKeyForUser = async(user) => { + encryptRoomKeyForUser = async user => { if (user?.e2e?.public_key) { const { public_key: publicKey } = user.e2e; const userKey = await SimpleCrypto.RSA.importKey(EJSON.parse(publicKey)); const encryptedUserKey = await SimpleCrypto.RSA.encrypt(this.sessionKeyExportedString, userKey); await RocketChat.e2eUpdateGroupKey(user?._id, this.roomId, this.keyID + encryptedUserKey); } - } + }; // Provide this room key to a user - provideKeyToUser = async(keyId) => { + provideKeyToUser = async keyId => { // Don't provide a key if the keyId received // is different than the current one if (this.keyID !== keyId) { @@ -162,34 +166,32 @@ export default class EncryptionRoom { } await this.encryptRoomKey(); - } + }; // Encrypt text - encryptText = async(text) => { + encryptText = async text => { text = utf8ToBuffer(text); const vector = await SimpleCrypto.utils.randomBytes(16); - const data = await SimpleCrypto.AES.encrypt( - text, - this.roomKey, - vector - ); + const data = await SimpleCrypto.AES.encrypt(text, this.roomKey, vector); return this.keyID + bufferToB64(joinVectorData(vector, data)); - } + }; // Encrypt messages - encrypt = async(message) => { + encrypt = async message => { if (!this.ready) { return message; } try { - const msg = await this.encryptText(EJSON.stringify({ - _id: message._id, - text: message.msg, - userId: this.userId, - ts: new Date() - })); + const msg = await this.encryptText( + EJSON.stringify({ + _id: message._id, + text: message.msg, + userId: this.userId, + ts: new Date() + }) + ); return { ...message, @@ -202,26 +204,22 @@ export default class EncryptionRoom { } return message; - } + }; // Decrypt text - decryptText = async(msg) => { + decryptText = async msg => { msg = b64ToBuffer(msg.slice(12)); const [vector, cipherText] = splitVectorData(msg); - const decrypted = await SimpleCrypto.AES.decrypt( - cipherText, - this.roomKey, - vector - ); + const decrypted = await SimpleCrypto.AES.decrypt(cipherText, this.roomKey, vector); const m = EJSON.parse(bufferToUtf8(decrypted)); return m.text; - } + }; // Decrypt messages - decrypt = async(message) => { + decrypt = async message => { if (!this.ready) { return message; } @@ -252,5 +250,5 @@ export default class EncryptionRoom { } return message; - } + }; } diff --git a/app/lib/encryption/utils.js b/app/lib/encryption/utils.js index 492ea0066..e82e11cf3 100644 --- a/app/lib/encryption/utils.js +++ b/app/lib/encryption/utils.js @@ -12,7 +12,7 @@ export const utf8ToBuffer = SimpleCrypto.utils.convertUtf8ToArrayBuffer; export const bufferToB64 = arrayBuffer => fromByteArray(new Uint8Array(arrayBuffer)); // ArrayBuffer -> Base64 URI Safe // https://github.com/herrjemand/Base64URL-ArrayBuffer/blob/master/lib/base64url-arraybuffer.js -export const bufferToB64URI = (buffer) => { +export const bufferToB64URI = buffer => { const uintArray = new Uint8Array(buffer); const len = uintArray.length; let base64 = ''; @@ -24,7 +24,7 @@ export const bufferToB64URI = (buffer) => { base64 += BASE64URI[uintArray[i + 2] & 63]; } - if ((len % 3) === 2) { + if (len % 3 === 2) { base64 = base64.substring(0, base64.length - 1); } else if (len % 3 === 1) { base64 = base64.substring(0, base64.length - 2); @@ -33,13 +33,13 @@ export const bufferToB64URI = (buffer) => { return base64; }; // SimpleCrypto.utils.convertArrayBufferToUtf8 is not working with unicode emoji -export const bufferToUtf8 = (buffer) => { +export const bufferToUtf8 = buffer => { const uintArray = new Uint8Array(buffer); const encodedString = String.fromCharCode.apply(null, uintArray); const decodedString = decodeURIComponent(escape(encodedString)); return decodedString; }; -export const splitVectorData = (text) => { +export const splitVectorData = text => { const vector = text.slice(0, 16); const data = text.slice(16); return [vector, data]; @@ -50,11 +50,11 @@ export const joinVectorData = (vector, data) => { output.set(new Uint8Array(data), vector.byteLength); return output.buffer; }; -export const toString = (thing) => { +export const toString = thing => { if (typeof thing === 'string') { return thing; } // eslint-disable-next-line new-cap return new ByteBuffer.wrap(thing).toString('binary'); }; -export const randomPassword = () => `${ random(3) }-${ random(3) }-${ random(3) }`.toLowerCase(); +export const randomPassword = () => `${random(3)}-${random(3)}-${random(3)}`.toLowerCase(); diff --git a/app/lib/methods/actions.js b/app/lib/methods/actions.js index 65cae1a57..7dcdb6ae5 100644 --- a/app/lib/methods/actions.js +++ b/app/lib/methods/actions.js @@ -24,13 +24,13 @@ export const CONTAINER_TYPES = { const triggersId = new Map(); -const invalidateTriggerId = (id) => { +const invalidateTriggerId = id => { const appId = triggersId.get(id); triggersId.delete(id); return appId; }; -export const generateTriggerId = (appId) => { +export const generateTriggerId = appId => { const triggerId = random(17); triggersId.set(triggerId, appId); @@ -80,7 +80,6 @@ export const handlePayloadUserInteraction = (type, { triggerId, ...data }) => { return MODAL_ACTIONS.UPDATE; } - if ([MODAL_ACTIONS.OPEN].includes(type) || [MODAL_ACTIONS.MODAL].includes(type)) { Navigation.navigate('ModalBlockView', { data: { @@ -96,10 +95,8 @@ export const handlePayloadUserInteraction = (type, { triggerId, ...data }) => { return MODAL_ACTIONS.CLOSE; }; -export function triggerAction({ - type, actionId, appId, rid, mid, viewId, container, ...rest -}) { - return new Promise(async(resolve, reject) => { +export function triggerAction({ type, actionId, appId, rid, mid, viewId, container, ...rest }) { + return new Promise(async (resolve, reject) => { const triggerId = generateTriggerId(appId); const payload = rest.payload || rest; @@ -109,7 +106,7 @@ export function triggerAction({ const { host } = this.sdk.client; // we need to use fetch because this.sdk.post add /v1 to url - const result = await fetch(`${ host }/api/apps/ui.interaction/${ appId }/`, { + const result = await fetch(`${host}/api/apps/ui.interaction/${appId}/`, { method: 'POST', headers: { 'Content-Type': 'application/json', diff --git a/app/lib/methods/callJitsi.js b/app/lib/methods/callJitsi.js index 7275ef812..19263f135 100644 --- a/app/lib/methods/callJitsi.js +++ b/app/lib/methods/callJitsi.js @@ -1,6 +1,6 @@ import reduxStore from '../createStore'; import Navigation from '../Navigation'; -import { logEvent, events } from '../../utils/log'; +import { events, logEvent } from '../../utils/log'; async function jitsiURL({ room }) { const { settings } = reduxStore.getState(); @@ -10,11 +10,9 @@ async function jitsiURL({ room }) { return ''; } - const { - Jitsi_Domain, Jitsi_URL_Room_Prefix, Jitsi_SSL, Jitsi_Enabled_TokenAuth, uniqueID, Jitsi_URL_Room_Hash - } = settings; + const { Jitsi_Domain, Jitsi_URL_Room_Prefix, Jitsi_SSL, Jitsi_Enabled_TokenAuth, uniqueID, Jitsi_URL_Room_Hash } = settings; - const domain = `${ Jitsi_Domain }/`; + const domain = `${Jitsi_Domain}/`; const prefix = Jitsi_URL_Room_Prefix; const protocol = Jitsi_SSL ? 'https://' : 'http://'; @@ -22,7 +20,7 @@ async function jitsiURL({ room }) { if (Jitsi_Enabled_TokenAuth) { try { const accessToken = await this.methodCallWrapper('jitsi:generateAccessToken', room?.rid); - queryString = `?jwt=${ accessToken }`; + queryString = `?jwt=${accessToken}`; } catch { logEvent(events.RA_JITSI_F); } @@ -35,7 +33,7 @@ async function jitsiURL({ room }) { rname = encodeURIComponent(room.t === 'd' ? room?.usernames?.join?.(' x ') : room?.name); } - return `${ protocol }${ domain }${ prefix }${ rname }${ queryString }`; + return `${protocol}${domain}${prefix}${rname}${queryString}`; } async function callJitsi(room, onlyAudio = false) { diff --git a/app/lib/methods/canOpenRoom.js b/app/lib/methods/canOpenRoom.js index c233899b8..ca8835b9c 100644 --- a/app/lib/methods/canOpenRoom.js +++ b/app/lib/methods/canOpenRoom.js @@ -2,7 +2,9 @@ import database from '../database'; import store from '../createStore'; const restTypes = { - channel: 'channels', direct: 'im', group: 'groups' + channel: 'channels', + direct: 'im', + group: 'groups' }; async function open({ type, rid, name }) { @@ -24,7 +26,7 @@ async function open({ type, rid, name }) { if (type === 'group') { try { // RC 0.61.0 - await this.sdk.post(`${ restTypes[type] }.open`, params); + await this.sdk.post(`${restTypes[type]}.open`, params); } catch (e) { if (!(e.data && /is already open/.test(e.data.error))) { return false; @@ -36,7 +38,7 @@ async function open({ type, rid, name }) { // we'll get info from the room if ((type === 'channel' || type === 'group') && !rid) { // RC 0.72.0 - const result = await this.sdk.get(`${ restTypes[type] }.info`, params); + const result = await this.sdk.get(`${restTypes[type]}.info`, params); if (result.success) { const room = result[type]; room.rid = room._id; @@ -63,7 +65,7 @@ export default async function canOpenRoom({ rid, path, isCall }) { // Extract rid from a Jitsi URL // Eg.: [Jitsi_URL_Room_Prefix][uniqueID][rid][?jwt] const { Jitsi_URL_Room_Prefix, uniqueID } = store.getState().settings; - rid = path.replace(`${ Jitsi_URL_Room_Prefix }${ uniqueID }`, '').replace(/\?(.*)/g, ''); + rid = path.replace(`${Jitsi_URL_Room_Prefix}${uniqueID}`, '').replace(/\?(.*)/g, ''); } if (rid) { diff --git a/app/lib/methods/enterpriseModules.js b/app/lib/methods/enterpriseModules.js index d86ae26fd..312a99252 100644 --- a/app/lib/methods/enterpriseModules.js +++ b/app/lib/methods/enterpriseModules.js @@ -1,9 +1,8 @@ import { compareServerVersion, methods } from '../utils'; - import reduxStore from '../createStore'; import database from '../database'; import log from '../../utils/log'; -import { setEnterpriseModules as setEnterpriseModulesAction, clearEnterpriseModules } from '../../actions/enterpriseModules'; +import { clearEnterpriseModules, setEnterpriseModules as setEnterpriseModulesAction } from '../../actions/enterpriseModules'; export const LICENSE_OMNICHANNEL_MOBILE_ENTERPRISE = 'omnichannel-mobile-enterprise'; export const LICENSE_LIVECHAT_ENTERPRISE = 'livechat-enterprise'; @@ -30,7 +29,7 @@ export async function setEnterpriseModules() { } export function getEnterpriseModules() { - return new Promise(async(resolve) => { + return new Promise(async resolve => { try { const { version: serverVersion, server: serverId } = reduxStore.getState().server; if (compareServerVersion(serverVersion, '3.1.0', methods.greaterThanOrEqualTo)) { @@ -40,8 +39,8 @@ export function getEnterpriseModules() { const serversDB = database.servers; const serversCollection = serversDB.get('servers'); const server = await serversCollection.find(serverId); - await serversDB.action(async() => { - await server.update((s) => { + await serversDB.action(async () => { + await server.update(s => { s.enterpriseModules = enterpriseModules.join(','); }); }); diff --git a/app/lib/methods/getCustomEmojis.js b/app/lib/methods/getCustomEmojis.js index d56a20bac..5e8a3e745 100644 --- a/app/lib/methods/getCustomEmojis.js +++ b/app/lib/methods/getCustomEmojis.js @@ -7,15 +7,19 @@ import database from '../database'; import log from '../../utils/log'; import { setCustomEmojis as setCustomEmojisAction } from '../../actions/customEmojis'; -const getUpdatedSince = (allEmojis) => { +const getUpdatedSince = allEmojis => { if (!allEmojis.length) { return null; } - const ordered = orderBy(allEmojis.filter(item => item._updatedAt !== null), ['_updatedAt'], ['desc']); + const ordered = orderBy( + allEmojis.filter(item => item._updatedAt !== null), + ['_updatedAt'], + ['desc'] + ); return ordered && ordered[0]._updatedAt.toISOString(); }; -const updateEmojis = async({ update = [], remove = [], allRecords }) => { +const updateEmojis = async ({ update = [], remove = [], allRecords }) => { if (!((update && update.length) || (remove && remove.length))) { return; } @@ -29,13 +33,15 @@ const updateEmojis = async({ update = [], remove = [], allRecords }) => { if (update && update.length) { emojisToCreate = update.filter(i1 => !allRecords.find(i2 => i1._id === i2.id)); emojisToUpdate = allRecords.filter(i1 => update.find(i2 => i1.id === i2._id)); - emojisToCreate = emojisToCreate.map(emoji => emojisCollection.prepareCreate((e) => { - e._raw = sanitizedRaw({ id: emoji._id }, emojisCollection.schema); - Object.assign(e, emoji); - })); - emojisToUpdate = emojisToUpdate.map((emoji) => { + emojisToCreate = emojisToCreate.map(emoji => + emojisCollection.prepareCreate(e => { + e._raw = sanitizedRaw({ id: emoji._id }, emojisCollection.schema); + Object.assign(e, emoji); + }) + ); + emojisToUpdate = emojisToUpdate.map(emoji => { const newEmoji = update.find(e => e._id === emoji.id); - return emoji.prepareUpdate((e) => { + return emoji.prepareUpdate(e => { Object.assign(e, newEmoji); }); }); @@ -47,12 +53,8 @@ const updateEmojis = async({ update = [], remove = [], allRecords }) => { } try { - await db.action(async() => { - await db.batch( - ...emojisToCreate, - ...emojisToUpdate, - ...emojisToDelete - ); + await db.action(async () => { + await db.batch(...emojisToCreate, ...emojisToUpdate, ...emojisToDelete); }); return true; } catch (e) { @@ -69,7 +71,7 @@ export async function setCustomEmojis() { name: item.name, extension: item.extension }; - item.aliases.forEach((alias) => { + item.aliases.forEach(alias => { ret[alias] = { name: item.name, extension: item.extension @@ -81,7 +83,7 @@ export async function setCustomEmojis() { } export function getCustomEmojis() { - return new Promise(async(resolve) => { + return new Promise(async resolve => { try { const serverVersion = reduxStore.getState().server.version; const db = database.active; diff --git a/app/lib/methods/getPermissions.js b/app/lib/methods/getPermissions.js index 764b725e0..d23268f39 100644 --- a/app/lib/methods/getPermissions.js +++ b/app/lib/methods/getPermissions.js @@ -7,8 +7,8 @@ import database from '../database'; import log from '../../utils/log'; import reduxStore from '../createStore'; import RocketChat from '../rocketchat'; -import protectedFunction from './helpers/protectedFunction'; import { setPermissions as setPermissionsAction } from '../../actions/permissions'; +import protectedFunction from './helpers/protectedFunction'; const PERMISSIONS = [ 'add-user-to-any-c-room', @@ -50,7 +50,8 @@ const PERMISSIONS = [ 'view-all-team-channels', 'convert-team', 'edit-omnichannel-contact', - 'edit-livechat-room-customfields' + 'edit-livechat-room-customfields', + 'view-canned-responses' ]; export async function setPermissions() { @@ -62,12 +63,16 @@ export async function setPermissions() { reduxStore.dispatch(setPermissionsAction(parsed)); } -const getUpdatedSince = (allRecords) => { +const getUpdatedSince = allRecords => { try { if (!allRecords.length) { return null; } - const ordered = orderBy(allRecords.filter(item => item._updatedAt !== null), ['_updatedAt'], ['desc']); + const ordered = orderBy( + allRecords.filter(item => item._updatedAt !== null), + ['_updatedAt'], + ['desc'] + ); return ordered && ordered[0]._updatedAt.toISOString(); } catch (e) { log(e); @@ -75,7 +80,7 @@ const getUpdatedSince = (allRecords) => { return null; }; -const updatePermissions = async({ update = [], remove = [], allRecords }) => { +const updatePermissions = async ({ update = [], remove = [], allRecords }) => { if (!((update && update.length) || (remove && remove.length))) { return; } @@ -91,15 +96,21 @@ const updatePermissions = async({ update = [], remove = [], allRecords }) => { if (update && update.length) { permissionsToCreate = update.filter(i1 => !allRecords.find(i2 => i1._id === i2.id)); permissionsToUpdate = allRecords.filter(i1 => update.find(i2 => i1.id === i2._id)); - permissionsToCreate = permissionsToCreate.map(permission => permissionsCollection.prepareCreate(protectedFunction((p) => { - p._raw = sanitizedRaw({ id: permission._id }, permissionsCollection.schema); - Object.assign(p, permission); - }))); - permissionsToUpdate = permissionsToUpdate.map((permission) => { + permissionsToCreate = permissionsToCreate.map(permission => + permissionsCollection.prepareCreate( + protectedFunction(p => { + p._raw = sanitizedRaw({ id: permission._id }, permissionsCollection.schema); + Object.assign(p, permission); + }) + ) + ); + permissionsToUpdate = permissionsToUpdate.map(permission => { const newPermission = update.find(p => p._id === permission.id); - return permission.prepareUpdate(protectedFunction((p) => { - Object.assign(p, newPermission); - })); + return permission.prepareUpdate( + protectedFunction(p => { + Object.assign(p, newPermission); + }) + ); }); } @@ -109,14 +120,10 @@ const updatePermissions = async({ update = [], remove = [], allRecords }) => { permissionsToDelete = permissionsToDelete.map(permission => permission.prepareDestroyPermanently()); } - const batch = [ - ...permissionsToCreate, - ...permissionsToUpdate, - ...permissionsToDelete - ]; + const batch = [...permissionsToCreate, ...permissionsToUpdate, ...permissionsToDelete]; try { - await db.action(async() => { + await db.action(async () => { await db.batch(...batch); }); return true; @@ -126,7 +133,7 @@ const updatePermissions = async({ update = [], remove = [], allRecords }) => { }; export function getPermissions() { - return new Promise(async(resolve) => { + return new Promise(async resolve => { try { const serverVersion = reduxStore.getState().server.version; const db = database.active; diff --git a/app/lib/methods/getRoles.js b/app/lib/methods/getRoles.js index 98a3895e3..d8beb5785 100644 --- a/app/lib/methods/getRoles.js +++ b/app/lib/methods/getRoles.js @@ -3,10 +3,8 @@ import { sanitizedRaw } from '@nozbe/watermelondb/RawRecord'; import database from '../database'; import log from '../../utils/log'; import reduxStore from '../createStore'; +import { removeRoles, setRoles as setRolesAction, updateRoles } from '../../actions/roles'; import protectedFunction from './helpers/protectedFunction'; -import { - removeRoles, setRoles as setRolesAction, updateRoles -} from '../../actions/roles'; export async function setRoles() { const db = database.active; @@ -24,8 +22,8 @@ export async function onRolesChanged(ddpMessage) { try { const rolesRecord = await rolesCollection.find(_id); try { - await db.action(async() => { - await rolesRecord.update((u) => { + await db.action(async () => { + await rolesRecord.update(u => { u.description = description; }); }); @@ -35,8 +33,8 @@ export async function onRolesChanged(ddpMessage) { reduxStore.dispatch(updateRoles(_id, description)); } catch (err) { try { - await db.action(async() => { - await rolesCollection.create((post) => { + await db.action(async () => { + await rolesCollection.create(post => { post._raw = sanitizedRaw({ id: _id, description }, rolesCollection.schema); }); }); @@ -51,7 +49,7 @@ export async function onRolesChanged(ddpMessage) { const rolesCollection = db.get('roles'); try { const rolesRecord = await rolesCollection.find(_id); - await db.action(async() => { + await db.action(async () => { await rolesRecord.destroyPermanently(); }); reduxStore.dispatch(removeRoles(_id)); @@ -63,7 +61,7 @@ export async function onRolesChanged(ddpMessage) { export function getRoles() { const db = database.active; - return new Promise(async(resolve) => { + return new Promise(async resolve => { try { // RC 0.70.0 const result = await this.sdk.get('roles.list'); @@ -75,7 +73,7 @@ export function getRoles() { const { roles } = result; if (roles && roles.length) { - await db.action(async() => { + await db.action(async () => { const rolesCollections = db.get('roles'); const allRolesRecords = await rolesCollections.query().fetch(); @@ -84,23 +82,26 @@ export function getRoles() { let rolesToUpdate = allRolesRecords.filter(i1 => roles.find(i2 => i1.id === i2._id)); // Create - rolesToCreate = rolesToCreate.map(role => rolesCollections.prepareCreate(protectedFunction((r) => { - r._raw = sanitizedRaw({ id: role._id }, rolesCollections.schema); - Object.assign(r, role); - }))); + rolesToCreate = rolesToCreate.map(role => + rolesCollections.prepareCreate( + protectedFunction(r => { + r._raw = sanitizedRaw({ id: role._id }, rolesCollections.schema); + Object.assign(r, role); + }) + ) + ); // Update - rolesToUpdate = rolesToUpdate.map((role) => { + rolesToUpdate = rolesToUpdate.map(role => { const newRole = roles.find(r => r._id === role.id); - return role.prepareUpdate(protectedFunction((r) => { - Object.assign(r, newRole); - })); + return role.prepareUpdate( + protectedFunction(r => { + Object.assign(r, newRole); + }) + ); }); - const allRecords = [ - ...rolesToCreate, - ...rolesToUpdate - ]; + const allRecords = [...rolesToCreate, ...rolesToUpdate]; try { await db.batch(...allRecords); diff --git a/app/lib/methods/getRoomInfo.js b/app/lib/methods/getRoomInfo.js index 293d97c56..24c061aa0 100644 --- a/app/lib/methods/getRoomInfo.js +++ b/app/lib/methods/getRoomInfo.js @@ -1,7 +1,7 @@ import { getSubscriptionByRoomId } from '../database/services/Subscription'; import RocketChat from '../rocketchat'; -const getRoomInfo = async(rid) => { +const getRoomInfo = async rid => { let result; result = await getSubscriptionByRoomId(rid); if (result) { diff --git a/app/lib/methods/getRooms.js b/app/lib/methods/getRooms.js index 1278b8b1f..5004beda0 100644 --- a/app/lib/methods/getRooms.js +++ b/app/lib/methods/getRooms.js @@ -1,4 +1,4 @@ -export default function(updatedSince) { +export default function (updatedSince) { // subscriptions.get: Since RC 0.60.0 // rooms.get: Since RC 0.62.0 if (updatedSince) { diff --git a/app/lib/methods/getSettings.js b/app/lib/methods/getSettings.js index be6ef1ee3..9e1082938 100644 --- a/app/lib/methods/getSettings.js +++ b/app/lib/methods/getSettings.js @@ -7,9 +7,9 @@ import reduxStore from '../createStore'; import settings from '../../constants/settings'; import log from '../../utils/log'; import database from '../database'; -import protectedFunction from './helpers/protectedFunction'; import fetch from '../../utils/fetch'; import { DEFAULT_AUTO_LOCK } from '../../constants/localAuthentication'; +import protectedFunction from './helpers/protectedFunction'; const serverInfoKeys = [ 'Site_Name', @@ -41,7 +41,7 @@ const loginSettings = [ 'Accounts_Iframe_api_method' ]; -const serverInfoUpdate = async(serverInfo, iconSetting) => { +const serverInfoUpdate = async (serverInfo, iconSetting) => { const serversDB = database.servers; const serverId = reduxStore.getState().server.server; const serversCollection = serversDB.get('servers'); @@ -87,13 +87,13 @@ const serverInfoUpdate = async(serverInfo, iconSetting) => { }, {}); if (iconSetting) { - const iconURL = `${ serverId }/${ iconSetting.value.url || iconSetting.value.defaultUrl }`; + const iconURL = `${serverId}/${iconSetting.value.url || iconSetting.value.defaultUrl}`; info = { ...info, iconURL }; } - await serversDB.action(async() => { + await serversDB.action(async () => { try { - await server.update((record) => { + await server.update(record => { Object.assign(record, info); }); } catch (e) { @@ -105,7 +105,9 @@ const serverInfoUpdate = async(serverInfo, iconSetting) => { export async function getLoginSettings({ server }) { try { const settingsParams = JSON.stringify(loginSettings); - const result = await fetch(`${ server }/api/v1/settings.public?query={"_id":{"$in":${ settingsParams }}}`).then(response => response.json()); + 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(clearSettings()); @@ -135,13 +137,16 @@ export function subscribeSettings() { return RocketChat.subscribe('stream-notify-all', 'public-settings-changed'); } -export default async function() { +export default async function () { try { const db = database.active; const settingsParams = Object.keys(settings).filter(key => !loginSettings.includes(key)); // RC 0.60.0 - const result = await fetch(`${ this.sdk.client.host }/api/v1/settings.public?query={"_id":{"$in":${ JSON.stringify(settingsParams) }}}&count=${ settingsParams.length }`) - .then(response => response.json()); + const result = await fetch( + `${this.sdk.client.host}/api/v1/settings.public?query={"_id":{"$in":${JSON.stringify(settingsParams)}}}&count=${ + settingsParams.length + }` + ).then(response => response.json()); if (!result.success) { return; @@ -161,34 +166,35 @@ export default async function() { // Server not found } - await db.action(async() => { + await db.action(async () => { const settingsCollection = db.get('settings'); - const allSettingsRecords = await settingsCollection - .query(Q.where('id', Q.oneOf(filteredSettingsIds))) - .fetch(); + const allSettingsRecords = await settingsCollection.query(Q.where('id', Q.oneOf(filteredSettingsIds))).fetch(); // filter settings let settingsToCreate = filteredSettings.filter(i1 => !allSettingsRecords.find(i2 => i1._id === i2.id)); let settingsToUpdate = allSettingsRecords.filter(i1 => filteredSettings.find(i2 => i1.id === i2._id)); // Create - settingsToCreate = settingsToCreate.map(setting => settingsCollection.prepareCreate(protectedFunction((s) => { - s._raw = sanitizedRaw({ id: setting._id }, settingsCollection.schema); - Object.assign(s, setting); - }))); + settingsToCreate = settingsToCreate.map(setting => + settingsCollection.prepareCreate( + protectedFunction(s => { + s._raw = sanitizedRaw({ id: setting._id }, settingsCollection.schema); + Object.assign(s, setting); + }) + ) + ); // Update - settingsToUpdate = settingsToUpdate.map((setting) => { + settingsToUpdate = settingsToUpdate.map(setting => { const newSetting = filteredSettings.find(s => s._id === setting.id); - return setting.prepareUpdate(protectedFunction((s) => { - Object.assign(s, newSetting); - })); + return setting.prepareUpdate( + protectedFunction(s => { + Object.assign(s, newSetting); + }) + ); }); - const allRecords = [ - ...settingsToCreate, - ...settingsToUpdate - ]; + const allRecords = [...settingsToCreate, ...settingsToUpdate]; try { await db.batch(...allRecords); diff --git a/app/lib/methods/getSingleMessage.js b/app/lib/methods/getSingleMessage.js index 56ecb3e63..4c9b32a68 100644 --- a/app/lib/methods/getSingleMessage.js +++ b/app/lib/methods/getSingleMessage.js @@ -1,15 +1,16 @@ import RocketChat from '../rocketchat'; -const getSingleMessage = messageId => new Promise(async(resolve, reject) => { - try { - const result = await RocketChat.getSingleMessage(messageId); - if (result.success) { - return resolve(result.message); +const getSingleMessage = messageId => + new Promise(async (resolve, reject) => { + try { + const result = await RocketChat.getSingleMessage(messageId); + if (result.success) { + return resolve(result.message); + } + return reject(); + } catch (e) { + return reject(); } - return reject(); - } catch (e) { - return reject(); - } -}); + }); export default getSingleMessage; diff --git a/app/lib/methods/getSlashCommands.js b/app/lib/methods/getSlashCommands.js index c37e077f4..3b65c40eb 100644 --- a/app/lib/methods/getSlashCommands.js +++ b/app/lib/methods/getSlashCommands.js @@ -4,9 +4,9 @@ import database from '../database'; import log from '../../utils/log'; import protectedFunction from './helpers/protectedFunction'; -export default function() { +export default function () { const db = database.active; - return new Promise(async(resolve) => { + return new Promise(async resolve => { try { // RC 0.60.2 const result = await this.sdk.get('commands.list'); @@ -19,38 +19,41 @@ export default function() { const { commands } = result; if (commands && commands.length) { - await db.action(async() => { + await db.action(async () => { const slashCommandsCollection = db.get('slash_commands'); const allSlashCommandsRecords = await slashCommandsCollection.query().fetch(); // filter slash commands let slashCommandsToCreate = commands.filter(i1 => !allSlashCommandsRecords.find(i2 => i1.command === i2.id)); let slashCommandsToUpdate = allSlashCommandsRecords.filter(i1 => commands.find(i2 => i1.id === i2.command)); - let slashCommandsToDelete = allSlashCommandsRecords - .filter(i1 => !slashCommandsToCreate.find(i2 => i2.command === i1.id) && !slashCommandsToUpdate.find(i2 => i2.id === i1.id)); + let slashCommandsToDelete = allSlashCommandsRecords.filter( + i1 => !slashCommandsToCreate.find(i2 => i2.command === i1.id) && !slashCommandsToUpdate.find(i2 => i2.id === i1.id) + ); // Create - slashCommandsToCreate = slashCommandsToCreate.map(command => slashCommandsCollection.prepareCreate(protectedFunction((s) => { - s._raw = sanitizedRaw({ id: command.command }, slashCommandsCollection.schema); - Object.assign(s, command); - }))); + slashCommandsToCreate = slashCommandsToCreate.map(command => + slashCommandsCollection.prepareCreate( + protectedFunction(s => { + s._raw = sanitizedRaw({ id: command.command }, slashCommandsCollection.schema); + Object.assign(s, command); + }) + ) + ); // Update - slashCommandsToUpdate = slashCommandsToUpdate.map((command) => { + slashCommandsToUpdate = slashCommandsToUpdate.map(command => { const newCommand = commands.find(s => s.command === command.id); - return command.prepareUpdate(protectedFunction((s) => { - Object.assign(s, newCommand); - })); + return command.prepareUpdate( + protectedFunction(s => { + Object.assign(s, newCommand); + }) + ); }); // Delete slashCommandsToDelete = slashCommandsToDelete.map(command => command.prepareDestroyPermanently()); - const allRecords = [ - ...slashCommandsToCreate, - ...slashCommandsToUpdate, - ...slashCommandsToDelete - ]; + const allRecords = [...slashCommandsToCreate, ...slashCommandsToUpdate, ...slashCommandsToDelete]; try { await db.batch(...allRecords); diff --git a/app/lib/methods/getThreadName.js b/app/lib/methods/getThreadName.js index 1eb1fbc78..a490a7d40 100644 --- a/app/lib/methods/getThreadName.js +++ b/app/lib/methods/getThreadName.js @@ -4,12 +4,12 @@ import database from '../database'; import { getMessageById } from '../database/services/Message'; import { getThreadById } from '../database/services/Thread'; import log from '../../utils/log'; -import getSingleMessage from './getSingleMessage'; import { Encryption } from '../encryption'; +import getSingleMessage from './getSingleMessage'; const buildThreadName = thread => thread.msg || thread?.attachments?.[0]?.title; -const getThreadName = async(rid, tmid, messageId) => { +const getThreadName = async (rid, tmid, messageId) => { let tmsg; try { const db = database.active; @@ -18,8 +18,8 @@ const getThreadName = async(rid, tmid, messageId) => { const threadRecord = await getThreadById(tmid); if (threadRecord) { tmsg = buildThreadName(threadRecord); - await db.action(async() => { - await messageRecord?.update((m) => { + await db.action(async () => { + await messageRecord?.update(m => { m.tmsg = tmsg; }); }); @@ -27,14 +27,14 @@ const getThreadName = async(rid, tmid, messageId) => { let thread = await getSingleMessage(tmid); thread = await Encryption.decryptMessage(thread); tmsg = buildThreadName(thread); - await db.action(async() => { + await db.action(async () => { await db.batch( - threadCollection?.prepareCreate((t) => { + threadCollection?.prepareCreate(t => { t._raw = sanitizedRaw({ id: thread._id }, threadCollection.schema); t.subscription.id = rid; Object.assign(t, thread); }), - messageRecord?.prepareUpdate((m) => { + messageRecord?.prepareUpdate(m => { m.tmsg = tmsg; }) ); diff --git a/app/lib/methods/getUsersPresence.js b/app/lib/methods/getUsersPresence.js index ddc1771d2..07a7b985e 100644 --- a/app/lib/methods/getUsersPresence.js +++ b/app/lib/methods/getUsersPresence.js @@ -73,18 +73,18 @@ export default async function getUsersPresence() { const db = database.active; const userCollection = db.get('users'); - users.forEach(async(user) => { + users.forEach(async user => { try { const userRecord = await userCollection.find(user._id); - await db.action(async() => { - await userRecord.update((u) => { + await db.action(async () => { + await userRecord.update(u => { Object.assign(u, user); }); }); } catch (e) { // User not found - await db.action(async() => { - await userCollection.create((u) => { + await db.action(async () => { + await userCollection.create(u => { u._raw = sanitizedRaw({ id: user._id }, userCollection.schema); Object.assign(u, user); }); diff --git a/app/lib/methods/helpers/buildMessage.js b/app/lib/methods/helpers/buildMessage.js index a565046fa..e0c09e4a3 100644 --- a/app/lib/methods/helpers/buildMessage.js +++ b/app/lib/methods/helpers/buildMessage.js @@ -1,7 +1,7 @@ -import normalizeMessage from './normalizeMessage'; import messagesStatus from '../../../constants/messagesStatus'; +import normalizeMessage from './normalizeMessage'; -export default (message) => { +export default message => { message.status = messagesStatus.SENT; return normalizeMessage(message); }; diff --git a/app/lib/methods/helpers/findSubscriptionsRooms.js b/app/lib/methods/helpers/findSubscriptionsRooms.js index a64a88940..e931b3edd 100644 --- a/app/lib/methods/helpers/findSubscriptionsRooms.js +++ b/app/lib/methods/helpers/findSubscriptionsRooms.js @@ -2,7 +2,7 @@ import { Q } from '@nozbe/watermelondb'; import database from '../../database'; -export default async(subscriptions = [], rooms = []) => { +export default async (subscriptions = [], rooms = []) => { try { const db = database.active; const subCollection = db.get('subscriptions'); diff --git a/app/lib/methods/helpers/getFileUrlFromMessage.js b/app/lib/methods/helpers/getFileUrlFromMessage.js index 60995aee1..c5a34ba04 100644 --- a/app/lib/methods/helpers/getFileUrlFromMessage.js +++ b/app/lib/methods/helpers/getFileUrlFromMessage.js @@ -1,4 +1,4 @@ -export default function(message) { +export default function (message) { if (/image/.test(message.type)) { return { image_url: message.url }; } diff --git a/app/lib/methods/helpers/mergeSubscriptionsRooms.js b/app/lib/methods/helpers/mergeSubscriptionsRooms.js index 82dfc4c68..95a81001c 100644 --- a/app/lib/methods/helpers/mergeSubscriptionsRooms.js +++ b/app/lib/methods/helpers/mergeSubscriptionsRooms.js @@ -1,10 +1,10 @@ import EJSON from 'ejson'; -import normalizeMessage from './normalizeMessage'; -import findSubscriptionsRooms from './findSubscriptionsRooms'; import { Encryption } from '../../encryption'; import reduxStore from '../../createStore'; import { compareServerVersion, methods } from '../../utils'; +import findSubscriptionsRooms from './findSubscriptionsRooms'; +import normalizeMessage from './normalizeMessage'; // TODO: delete and update export const merge = (subscription, room) => { @@ -35,7 +35,9 @@ export const merge = (subscription, room) => { } else { // https://github.com/RocketChat/Rocket.Chat/blob/develop/app/ui-sidenav/client/roomList.js#L180 const lastRoomUpdate = room.lm || subscription.ts || subscription._updatedAt; - subscription.roomUpdatedAt = subscription.lr ? Math.max(new Date(subscription.lr), new Date(lastRoomUpdate)) : lastRoomUpdate; + subscription.roomUpdatedAt = subscription.lr + ? Math.max(new Date(subscription.lr), new Date(lastRoomUpdate)) + : lastRoomUpdate; } subscription.ro = room.ro; subscription.broadcast = room.broadcast; @@ -86,7 +88,7 @@ export const merge = (subscription, room) => { return subscription; }; -export default async(subscriptions = [], rooms = []) => { +export default async (subscriptions = [], rooms = []) => { if (subscriptions.update) { subscriptions = subscriptions.update; rooms = rooms.update; @@ -95,7 +97,7 @@ export default async(subscriptions = [], rooms = []) => { // Find missing rooms/subscriptions on local database ({ subscriptions, rooms } = await findSubscriptionsRooms(subscriptions, rooms)); // Merge each subscription into a room - subscriptions = subscriptions.map((s) => { + subscriptions = subscriptions.map(s => { const index = rooms.findIndex(({ _id }) => _id === s.rid); // Room not found if (index < 0) { diff --git a/app/lib/methods/helpers/normalizeMessage.js b/app/lib/methods/helpers/normalizeMessage.js index adca75cd3..bf34728e5 100644 --- a/app/lib/methods/helpers/normalizeMessage.js +++ b/app/lib/methods/helpers/normalizeMessage.js @@ -6,7 +6,7 @@ function normalizeAttachments(msg) { if (typeof msg.attachments !== typeof [] || !msg.attachments || !msg.attachments.length) { msg.attachments = []; } - msg.attachments = msg.attachments.map((att) => { + msg.attachments = msg.attachments.map(att => { att.fields = att.fields || []; if (att.ts) { att.ts = moment(att.ts).toDate(); @@ -17,7 +17,7 @@ function normalizeAttachments(msg) { return msg; } -export default (msg) => { +export default msg => { if (!msg) { return null; } @@ -31,10 +31,18 @@ export default (msg) => { // msg.reactions = Object.keys(msg.reactions).map(key => ({ emoji: key, usernames: msg.reactions[key].usernames.map(username => ({ value: username })) })); // } if (!Array.isArray(msg.reactions)) { - msg.reactions = Object.keys(msg.reactions).map(key => ({ _id: `${ msg._id }${ key }`, emoji: key, usernames: msg.reactions[key].usernames })); + msg.reactions = Object.keys(msg.reactions).map(key => ({ + _id: `${msg._id}${key}`, + emoji: key, + usernames: msg.reactions[key].usernames + })); } if (msg.translations && Object.keys(msg.translations).length) { - msg.translations = Object.keys(msg.translations).map(key => ({ _id: `${ msg._id }${ key }`, language: key, value: msg.translations[key] })); + msg.translations = Object.keys(msg.translations).map(key => ({ + _id: `${msg._id}${key}`, + language: key, + value: msg.translations[key] + })); msg.autoTranslate = true; } msg.urls = msg.urls ? parseUrls(msg.urls) : []; diff --git a/app/lib/methods/helpers/parseQuery.js b/app/lib/methods/helpers/parseQuery.js index d60610e8a..30f1bb3c6 100644 --- a/app/lib/methods/helpers/parseQuery.js +++ b/app/lib/methods/helpers/parseQuery.js @@ -1,9 +1,7 @@ -export default function(query) { - return (/^[?#]/.test(query) ? query.slice(1) : query) - .split('&') - .reduce((params, param) => { - const [key, value] = param.split('='); - params[key] = value ? decodeURIComponent(value.replace(/\+/g, ' ')) : ''; - return params; - }, { }); +export default function (query) { + return (/^[?#]/.test(query) ? query.slice(1) : query).split('&').reduce((params, param) => { + const [key, value] = param.split('='); + params[key] = value ? decodeURIComponent(value.replace(/\+/g, ' ')) : ''; + return params; + }, {}); } diff --git a/app/lib/methods/helpers/parseUrls.js b/app/lib/methods/helpers/parseUrls.js index d49c54c47..1d3dcff45 100644 --- a/app/lib/methods/helpers/parseUrls.js +++ b/app/lib/methods/helpers/parseUrls.js @@ -1,21 +1,24 @@ -export default urls => urls.filter(url => url.meta && !url.ignoreParse).map((url, index) => { - const tmp = {}; - const { meta } = url; - tmp._id = index; - tmp.title = meta.ogTitle || meta.twitterTitle || meta.title || meta.pageTitle || meta.oembedTitle; - tmp.description = meta.ogDescription || meta.twitterDescription || meta.description || meta.oembedAuthorName; - let decodedOgImage; - if (meta.ogImage) { - decodedOgImage = meta.ogImage.replace(/&/g, '&'); - } - tmp.image = decodedOgImage || meta.twitterImage || meta.oembedThumbnailUrl; - if (tmp.image) { - if (tmp.image.indexOf('//') === 0) { - tmp.image = `${ url.parsedUrl.protocol }${ tmp.image }`; - } else if (tmp.image.indexOf('/') === 0 && (url.parsedUrl && url.parsedUrl.host)) { - tmp.image = `${ url.parsedUrl.protocol }//${ url.parsedUrl.host }${ tmp.image }`; - } - } - tmp.url = url.url; - return tmp; -}); +export default urls => + urls + .filter(url => url.meta && !url.ignoreParse) + .map((url, index) => { + const tmp = {}; + const { meta } = url; + tmp._id = index; + tmp.title = meta.ogTitle || meta.twitterTitle || meta.title || meta.pageTitle || meta.oembedTitle; + tmp.description = meta.ogDescription || meta.twitterDescription || meta.description || meta.oembedAuthorName; + let decodedOgImage; + if (meta.ogImage) { + decodedOgImage = meta.ogImage.replace(/&/g, '&'); + } + tmp.image = decodedOgImage || meta.twitterImage || meta.oembedThumbnailUrl; + if (tmp.image) { + if (tmp.image.indexOf('//') === 0) { + tmp.image = `${url.parsedUrl.protocol}${tmp.image}`; + } else if (tmp.image.indexOf('/') === 0 && url.parsedUrl && url.parsedUrl.host) { + tmp.image = `${url.parsedUrl.protocol}//${url.parsedUrl.host}${tmp.image}`; + } + } + tmp.url = url.url; + return tmp; + }); diff --git a/app/lib/methods/helpers/protectedFunction.js b/app/lib/methods/helpers/protectedFunction.js index ce2076b3e..93ce93a21 100644 --- a/app/lib/methods/helpers/protectedFunction.js +++ b/app/lib/methods/helpers/protectedFunction.js @@ -1,7 +1,8 @@ -export default fn => (...params) => { - try { - fn(...params); - } catch (e) { - console.log(e); - } -}; +export default fn => + (...params) => { + try { + fn(...params); + } catch (e) { + console.log(e); + } + }; diff --git a/app/lib/methods/loadMessagesForRoom.js b/app/lib/methods/loadMessagesForRoom.js index a8dc733ac..4a11bcc8c 100644 --- a/app/lib/methods/loadMessagesForRoom.js +++ b/app/lib/methods/loadMessagesForRoom.js @@ -3,8 +3,8 @@ import moment from 'moment'; import { MESSAGE_TYPE_LOAD_MORE } from '../../constants/messageTypeLoad'; import log from '../../utils/log'; import { getMessageById } from '../database/services/Message'; -import updateMessages from './updateMessages'; import { generateLoadMoreId } from '../utils'; +import updateMessages from './updateMessages'; const COUNT = 50; @@ -20,7 +20,7 @@ async function load({ rid: roomId, latest, t }) { } // RC 0.48.0 - const data = await this.sdk.get(`${ apiType }.history`, params); + const data = await this.sdk.get(`${apiType}.history`, params); if (!data || data.status === 'error') { return []; } @@ -28,7 +28,7 @@ async function load({ rid: roomId, latest, t }) { } export default function loadMessagesForRoom(args) { - return new Promise(async(resolve, reject) => { + return new Promise(async (resolve, reject) => { try { const data = await load.call(this, args); if (data?.length) { diff --git a/app/lib/methods/loadMissedMessages.js b/app/lib/methods/loadMissedMessages.js index d1acaf0b4..7b2e8c978 100644 --- a/app/lib/methods/loadMissedMessages.js +++ b/app/lib/methods/loadMissedMessages.js @@ -2,7 +2,7 @@ import database from '../database'; import log from '../../utils/log'; import updateMessages from './updateMessages'; -const getLastUpdate = async(rid) => { +const getLastUpdate = async rid => { try { const db = database.active; const subsCollection = db.get('subscriptions'); @@ -27,9 +27,9 @@ async function load({ rid: roomId, lastOpen }) { } export default function loadMissedMessages(args) { - return new Promise(async(resolve, reject) => { + return new Promise(async (resolve, reject) => { try { - const data = (await load.call(this, { rid: args.rid, lastOpen: args.lastOpen })); + const data = await load.call(this, { rid: args.rid, lastOpen: args.lastOpen }); if (data) { const { updated, deleted } = data; diff --git a/app/lib/methods/loadNextMessages.js b/app/lib/methods/loadNextMessages.js index 3a5e5e6ff..3fe728512 100644 --- a/app/lib/methods/loadNextMessages.js +++ b/app/lib/methods/loadNextMessages.js @@ -3,15 +3,15 @@ import moment from 'moment'; import orderBy from 'lodash/orderBy'; import log from '../../utils/log'; -import updateMessages from './updateMessages'; import { getMessageById } from '../database/services/Message'; import { MESSAGE_TYPE_LOAD_NEXT_CHUNK } from '../../constants/messageTypeLoad'; import { generateLoadMoreId } from '../utils'; +import updateMessages from './updateMessages'; const COUNT = 50; export default function loadNextMessages(args) { - return new Promise(async(resolve, reject) => { + return new Promise(async (resolve, reject) => { try { const data = await this.methodCallWrapper('loadNextMessages', args.rid, args.ts, COUNT); let messages = EJSON.fromJSONValue(data?.messages); diff --git a/app/lib/methods/loadSurroundingMessages.js b/app/lib/methods/loadSurroundingMessages.js index 74c345c2f..bc25ae695 100644 --- a/app/lib/methods/loadSurroundingMessages.js +++ b/app/lib/methods/loadSurroundingMessages.js @@ -3,15 +3,15 @@ import moment from 'moment'; import orderBy from 'lodash/orderBy'; import log from '../../utils/log'; -import updateMessages from './updateMessages'; import { getMessageById } from '../database/services/Message'; import { MESSAGE_TYPE_LOAD_NEXT_CHUNK, MESSAGE_TYPE_LOAD_PREVIOUS_CHUNK } from '../../constants/messageTypeLoad'; import { generateLoadMoreId } from '../utils'; +import updateMessages from './updateMessages'; const COUNT = 50; export default function loadSurroundingMessages({ messageId, rid }) { - return new Promise(async(resolve, reject) => { + return new Promise(async (resolve, reject) => { try { const data = await this.methodCallWrapper('loadSurroundingMessages', { _id: messageId, rid }, COUNT); let messages = EJSON.fromJSONValue(data?.messages); diff --git a/app/lib/methods/loadThreadMessages.js b/app/lib/methods/loadThreadMessages.js index d170635e8..1a17f564d 100644 --- a/app/lib/methods/loadThreadMessages.js +++ b/app/lib/methods/loadThreadMessages.js @@ -2,11 +2,11 @@ import { Q } from '@nozbe/watermelondb'; import { sanitizedRaw } from '@nozbe/watermelondb/RawRecord'; import EJSON from 'ejson'; -import buildMessage from './helpers/buildMessage'; import database from '../database'; import log from '../../utils/log'; -import protectedFunction from './helpers/protectedFunction'; import { Encryption } from '../encryption'; +import protectedFunction from './helpers/protectedFunction'; +import buildMessage from './helpers/buildMessage'; async function load({ tmid }) { try { @@ -23,7 +23,7 @@ async function load({ tmid }) { } export default function loadThreadMessages({ tmid, rid }) { - return new Promise(async(resolve, reject) => { + return new Promise(async (resolve, reject) => { try { let data = await load.call(this, { tmid }); if (data && data.length) { @@ -36,28 +36,31 @@ export default function loadThreadMessages({ tmid, rid }) { let threadMessagesToCreate = data.filter(i1 => !allThreadMessagesRecords.find(i2 => i1._id === i2.id)); let threadMessagesToUpdate = allThreadMessagesRecords.filter(i1 => data.find(i2 => i1.id === i2._id)); - threadMessagesToCreate = threadMessagesToCreate.map(threadMessage => threadMessagesCollection.prepareCreate(protectedFunction((tm) => { - tm._raw = sanitizedRaw({ id: threadMessage._id }, threadMessagesCollection.schema); - Object.assign(tm, threadMessage); - tm.subscription.id = rid; - tm.rid = threadMessage.tmid; - delete threadMessage.tmid; - }))); + threadMessagesToCreate = threadMessagesToCreate.map(threadMessage => + threadMessagesCollection.prepareCreate( + protectedFunction(tm => { + tm._raw = sanitizedRaw({ id: threadMessage._id }, threadMessagesCollection.schema); + Object.assign(tm, threadMessage); + tm.subscription.id = rid; + tm.rid = threadMessage.tmid; + delete threadMessage.tmid; + }) + ) + ); - threadMessagesToUpdate = threadMessagesToUpdate.map((threadMessage) => { + threadMessagesToUpdate = threadMessagesToUpdate.map(threadMessage => { const newThreadMessage = data.find(t => t._id === threadMessage.id); - return threadMessage.prepareUpdate(protectedFunction((tm) => { - Object.assign(tm, newThreadMessage); - tm.rid = threadMessage.tmid; - delete threadMessage.tmid; - })); + return threadMessage.prepareUpdate( + protectedFunction(tm => { + Object.assign(tm, newThreadMessage); + tm.rid = threadMessage.tmid; + delete threadMessage.tmid; + }) + ); }); - await db.action(async() => { - await db.batch( - ...threadMessagesToCreate, - ...threadMessagesToUpdate - ); + await db.action(async () => { + await db.batch(...threadMessagesToCreate, ...threadMessagesToUpdate); }); } catch (e) { log(e); diff --git a/app/lib/methods/logout.js b/app/lib/methods/logout.js index 114b76207..c108a9965 100644 --- a/app/lib/methods/logout.js +++ b/app/lib/methods/logout.js @@ -7,20 +7,16 @@ import { BASIC_AUTH_KEY } from '../../utils/fetch'; import database, { getDatabase } from '../database'; import RocketChat from '../rocketchat'; import { useSsl } from '../../utils/url'; -import { - E2E_PUBLIC_KEY, - E2E_PRIVATE_KEY, - E2E_RANDOM_PASSWORD_KEY -} from '../encryption/constants'; +import { E2E_PRIVATE_KEY, E2E_PUBLIC_KEY, E2E_RANDOM_PASSWORD_KEY } from '../encryption/constants'; import UserPreferences from '../userPreferences'; async function removeServerKeys({ server, userId }) { - await UserPreferences.removeItem(`${ RocketChat.TOKEN_KEY }-${ server }`); - await UserPreferences.removeItem(`${ RocketChat.TOKEN_KEY }-${ userId }`); - await UserPreferences.removeItem(`${ BASIC_AUTH_KEY }-${ server }`); - await UserPreferences.removeItem(`${ server }-${ E2E_PUBLIC_KEY }`); - await UserPreferences.removeItem(`${ server }-${ E2E_PRIVATE_KEY }`); - await UserPreferences.removeItem(`${ server }-${ E2E_RANDOM_PASSWORD_KEY }`); + await UserPreferences.removeItem(`${RocketChat.TOKEN_KEY}-${server}`); + await UserPreferences.removeItem(`${RocketChat.TOKEN_KEY}-${userId}`); + await UserPreferences.removeItem(`${BASIC_AUTH_KEY}-${server}`); + await UserPreferences.removeItem(`${server}-${E2E_PUBLIC_KEY}`); + await UserPreferences.removeItem(`${server}-${E2E_PRIVATE_KEY}`); + await UserPreferences.removeItem(`${server}-${E2E_RANDOM_PASSWORD_KEY}`); } async function removeSharedCredentials({ server }) { @@ -40,7 +36,7 @@ async function removeServerData({ server }) { try { const batch = []; const serversDB = database.servers; - const userId = await UserPreferences.getStringAsync(`${ RocketChat.TOKEN_KEY }-${ server }`); + const userId = await UserPreferences.getStringAsync(`${RocketChat.TOKEN_KEY}-${server}`); const usersCollection = serversDB.get('users'); if (userId) { @@ -74,9 +70,9 @@ async function removeServerDatabase({ server }) { export async function removeServer({ server }) { try { - const userId = await UserPreferences.getStringAsync(`${ RocketChat.TOKEN_KEY }-${ server }`); + const userId = await UserPreferences.getStringAsync(`${RocketChat.TOKEN_KEY}-${server}`); if (userId) { - const resume = await UserPreferences.getStringAsync(`${ RocketChat.TOKEN_KEY }-${ userId }`); + const resume = await UserPreferences.getStringAsync(`${RocketChat.TOKEN_KEY}-${userId}`); const sdk = new RocketchatClient({ host: server, protocol: 'ddp', useSsl: useSsl(server) }); await sdk.login({ resume }); diff --git a/app/lib/methods/readMessages.js b/app/lib/methods/readMessages.js index 137612648..7d2d58de1 100644 --- a/app/lib/methods/readMessages.js +++ b/app/lib/methods/readMessages.js @@ -9,9 +9,9 @@ export default async function readMessages(rid, ls, updateLastOpen = false) { // RC 0.61.0 await this.sdk.post('subscriptions.read', { rid }); - await db.action(async() => { + await db.action(async () => { try { - await subscription.update((s) => { + await subscription.update(s => { s.open = true; s.alert = false; s.unread = 0; diff --git a/app/lib/methods/sendFileMessage.js b/app/lib/methods/sendFileMessage.js index 435e377cb..35d2815ca 100644 --- a/app/lib/methods/sendFileMessage.js +++ b/app/lib/methods/sendFileMessage.js @@ -20,7 +20,7 @@ export async function cancelUpload(item) { } try { const db = database.active; - await db.action(async() => { + await db.action(async () => { await item.destroyPermanently(); }); } catch (e) { @@ -31,11 +31,11 @@ export async function cancelUpload(item) { } export function sendFileMessage(rid, fileInfo, tmid, server, user) { - return new Promise(async(resolve, reject) => { + return new Promise(async (resolve, reject) => { try { const { id, token } = user; - const uploadUrl = `${ server }/api/v1/rooms.upload/${ rid }`; + const uploadUrl = `${server}/api/v1/rooms.upload/${rid}`; fileInfo.rid = rid; @@ -46,8 +46,8 @@ export function sendFileMessage(rid, fileInfo, tmid, server, user) { uploadRecord = await uploadsCollection.find(fileInfo.path); } catch (error) { try { - await db.action(async() => { - uploadRecord = await uploadsCollection.create((u) => { + await db.action(async () => { + uploadRecord = await uploadsCollection.create(u => { u._raw = sanitizedRaw({ id: fileInfo.path }, uploadsCollection.schema); Object.assign(u, fileInfo); u.subscription.id = rid; @@ -89,10 +89,10 @@ export function sendFileMessage(rid, fileInfo, tmid, server, user) { uploadQueue[fileInfo.path] = FileUpload.fetch('POST', uploadUrl, headers, formData); - uploadQueue[fileInfo.path].uploadProgress(async(loaded, total) => { + uploadQueue[fileInfo.path].uploadProgress(async (loaded, total) => { try { - await db.action(async() => { - await uploadRecord.update((u) => { + await db.action(async () => { + await uploadRecord.update(u => { u.progress = Math.floor((loaded / total) * 100); }); }); @@ -101,10 +101,11 @@ export function sendFileMessage(rid, fileInfo, tmid, server, user) { } }); - uploadQueue[fileInfo.path].then(async(response) => { - if (response.respInfo.status >= 200 && response.respInfo.status < 400) { // If response is all good... + uploadQueue[fileInfo.path].then(async response => { + if (response.respInfo.status >= 200 && response.respInfo.status < 400) { + // If response is all good... try { - await db.action(async() => { + await db.action(async () => { await uploadRecord.destroyPermanently(); }); resolve(response); @@ -113,8 +114,8 @@ export function sendFileMessage(rid, fileInfo, tmid, server, user) { } } else { try { - await db.action(async() => { - await uploadRecord.update((u) => { + await db.action(async () => { + await uploadRecord.update(u => { u.error = true; }); }); @@ -129,10 +130,10 @@ export function sendFileMessage(rid, fileInfo, tmid, server, user) { } }); - uploadQueue[fileInfo.path].catch(async(error) => { + uploadQueue[fileInfo.path].catch(async error => { try { - await db.action(async() => { - await uploadRecord.update((u) => { + await db.action(async () => { + await uploadRecord.update(u => { u.error = true; }); }); diff --git a/app/lib/methods/sendMessage.js b/app/lib/methods/sendMessage.js index 9b1bc6d10..1a1ff9934 100644 --- a/app/lib/methods/sendMessage.js +++ b/app/lib/methods/sendMessage.js @@ -7,14 +7,14 @@ import random from '../../utils/random'; import { Encryption } from '../encryption'; import { E2E_MESSAGE_TYPE, E2E_STATUS } from '../encryption/constants'; -const changeMessageStatus = async(id, tmid, status, message) => { +const changeMessageStatus = async (id, tmid, status, message) => { const db = database.active; const msgCollection = db.get('messages'); const threadMessagesCollection = db.get('thread_messages'); const successBatch = []; const messageRecord = await msgCollection.find(id); successBatch.push( - messageRecord.prepareUpdate((m) => { + messageRecord.prepareUpdate(m => { m.status = status; if (message) { m.mentions = message.mentions; @@ -26,7 +26,7 @@ const changeMessageStatus = async(id, tmid, status, message) => { if (tmid) { const threadMessageRecord = await threadMessagesCollection.find(id); successBatch.push( - threadMessageRecord.prepareUpdate((tm) => { + threadMessageRecord.prepareUpdate(tm => { tm.status = status; if (message) { tm.mentions = message.mentions; @@ -37,7 +37,7 @@ const changeMessageStatus = async(id, tmid, status, message) => { } try { - await db.action(async() => { + await db.action(async () => { await db.batch(...successBatch); }); } catch (error) { @@ -63,8 +63,8 @@ export async function sendMessageCall(message) { export async function resendMessage(message, tmid) { const db = database.active; try { - await db.action(async() => { - await message.update((m) => { + await db.action(async () => { + await message.update(m => { m.status = messagesStatus.TEMP; }); }); @@ -86,7 +86,7 @@ export async function resendMessage(message, tmid) { } } -export default async function(rid, msg, tmid, user, tshow) { +export default async function (rid, msg, tmid, user, tshow) { try { const db = database.active; const subsCollection = db.get('subscriptions'); @@ -97,7 +97,11 @@ export default async function(rid, msg, tmid, user, tshow) { const batch = []; let message = { - _id: messageId, rid, msg, tmid, tshow + _id: messageId, + rid, + msg, + tmid, + tshow }; message = await Encryption.encryptMessage(message); @@ -110,7 +114,7 @@ export default async function(rid, msg, tmid, user, tshow) { // Find thread message header in Messages collection tMessageRecord = await msgCollection.find(tmid); batch.push( - tMessageRecord.prepareUpdate((m) => { + tMessageRecord.prepareUpdate(m => { m.tlm = messageDate; m.tcount += 1; }) @@ -122,7 +126,7 @@ export default async function(rid, msg, tmid, user, tshow) { } catch (error) { // If there's no record, create one batch.push( - threadCollection.prepareCreate((tm) => { + threadCollection.prepareCreate(tm => { tm._raw = sanitizedRaw({ id: tmid }, threadCollection.schema); tm.subscription.id = rid; tm.tmid = tmid; @@ -141,7 +145,7 @@ export default async function(rid, msg, tmid, user, tshow) { // Create the message sent in ThreadMessages collection batch.push( - threadMessagesCollection.prepareCreate((tm) => { + threadMessagesCollection.prepareCreate(tm => { tm._raw = sanitizedRaw({ id: messageId }, threadMessagesCollection.schema); tm.subscription.id = rid; tm.rid = tmid; @@ -167,7 +171,7 @@ export default async function(rid, msg, tmid, user, tshow) { // Create the message sent in Messages collection batch.push( - msgCollection.prepareCreate((m) => { + msgCollection.prepareCreate(m => { m._raw = sanitizedRaw({ id: messageId }, msgCollection.schema); m.subscription.id = rid; m.msg = msg; @@ -196,7 +200,7 @@ export default async function(rid, msg, tmid, user, tshow) { const room = await subsCollection.find(rid); if (room.draftMessage) { batch.push( - room.prepareUpdate((r) => { + room.prepareUpdate(r => { r.draftMessage = null; }) ); @@ -206,7 +210,7 @@ export default async function(rid, msg, tmid, user, tshow) { } try { - await db.action(async() => { + await db.action(async () => { await db.batch(...batch); }); } catch (e) { diff --git a/app/lib/methods/subscriptions/room.js b/app/lib/methods/subscriptions/room.js index 2d9ba8f90..33ebb7df5 100644 --- a/app/lib/methods/subscriptions/room.js +++ b/app/lib/methods/subscriptions/room.js @@ -7,7 +7,7 @@ import protectedFunction from '../helpers/protectedFunction'; import buildMessage from '../helpers/buildMessage'; import database from '../../database'; import reduxStore from '../../createStore'; -import { addUserTyping, removeUserTyping, clearUserTyping } from '../../../actions/usersTyping'; +import { addUserTyping, clearUserTyping, removeUserTyping } from '../../../actions/usersTyping'; import debounce from '../../../utils/debounce'; import RocketChat from '../../rocketchat'; import { subscribeRoom, unsubscribeRoom } from '../../../actions/room'; @@ -26,8 +26,8 @@ export default class RoomSubscription { this.threadMessagesBatch = {}; } - subscribe = async() => { - console.log(`[RCRN] Subscribing to room ${ this.rid }`); + subscribe = async () => { + console.log(`[RCRN] Subscribing to room ${this.rid}`); if (this.promises) { await this.unsubscribe(); } @@ -42,15 +42,15 @@ export default class RoomSubscription { } reduxStore.dispatch(subscribeRoom(this.rid)); - } + }; - unsubscribe = async() => { - console.log(`[RCRN] Unsubscribing from room ${ this.rid }`); + unsubscribe = async () => { + console.log(`[RCRN] Unsubscribing from room ${this.rid}`); this.isAlive = false; reduxStore.dispatch(unsubscribeRoom(this.rid)); if (this.promises) { try { - const subscriptions = await this.promises || []; + const subscriptions = (await this.promises) || []; subscriptions.forEach(sub => sub.unsubscribe().catch(() => console.log('unsubscribeRoom'))); } catch (e) { // do nothing @@ -64,9 +64,9 @@ export default class RoomSubscription { if (this.timer) { clearTimeout(this.timer); } - } + }; - removeListener = async(promise) => { + removeListener = async promise => { if (promise) { try { const listener = await promise; @@ -82,7 +82,7 @@ export default class RoomSubscription { RocketChat.loadMissedMessages({ rid: this.rid }).catch(e => console.log(e)); }; - handleNotifyRoomReceived = protectedFunction((ddpMessage) => { + handleNotifyRoomReceived = protectedFunction(ddpMessage => { const [_rid, ev] = ddpMessage.fields.eventName.split('/'); if (this.rid !== _rid) { return; @@ -104,7 +104,7 @@ export default class RoomSubscription { } } } else if (ev === 'deleteMessage') { - InteractionManager.runAfterInteractions(async() => { + InteractionManager.runAfterInteractions(async () => { if (ddpMessage && ddpMessage.fields && ddpMessage.fields.args.length > 0) { try { const { _id } = ddpMessage.fields.args[0]; @@ -139,10 +139,8 @@ export default class RoomSubscription { } catch (e) { // Do nothing } - await db.action(async() => { - await db.batch( - deleteMessage, deleteThread, deleteThreadMessage - ); + await db.action(async () => { + await db.batch(deleteMessage, deleteThread, deleteThreadMessage); }); } catch (e) { log(e); @@ -152,12 +150,12 @@ export default class RoomSubscription { } }); - read = debounce((lastOpen) => { + read = debounce(lastOpen => { RocketChat.readMessages(this.rid, lastOpen); }, 300); - updateMessage = message => ( - new Promise(async(resolve) => { + updateMessage = message => + new Promise(async resolve => { if (this.rid !== message.rid) { return resolve(); } @@ -174,17 +172,21 @@ export default class RoomSubscription { try { const messageRecord = await msgCollection.find(message._id); if (!messageRecord._hasPendingUpdate) { - const update = messageRecord.prepareUpdate(protectedFunction((m) => { - Object.assign(m, message); - })); + const update = messageRecord.prepareUpdate( + protectedFunction(m => { + Object.assign(m, message); + }) + ); this._messagesBatch[message._id] = update; } } catch { - const create = 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; } @@ -193,17 +195,21 @@ export default class RoomSubscription { try { const threadRecord = await threadsCollection.find(message._id); if (!threadRecord._hasPendingUpdate) { - const updateThread = threadRecord.prepareUpdate(protectedFunction((t) => { - Object.assign(t, message); - })); + const updateThread = threadRecord.prepareUpdate( + protectedFunction(t => { + Object.assign(t, message); + }) + ); this._threadsBatch[message._id] = updateThread; } } catch { - const createThread = 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; } } @@ -213,32 +219,35 @@ export default class RoomSubscription { try { const threadMessageRecord = await threadMessagesCollection.find(message._id); if (!threadMessageRecord._hasPendingUpdate) { - const updateThreadMessage = 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; } } catch { - 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; - })); + 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; } } return resolve(); - }) - ) + }); - handleMessageReceived = (ddpMessage) => { + handleMessageReceived = ddpMessage => { if (!this.timer) { - this.timer = setTimeout(async() => { + 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]); @@ -262,7 +271,7 @@ export default class RoomSubscription { try { const db = database.active; - await db.action(async() => { + await db.action(async () => { await db.batch( ...Object.values(this._messagesBatch), ...Object.values(this._threadsBatch), diff --git a/app/lib/methods/subscriptions/rooms.js b/app/lib/methods/subscriptions/rooms.js index b79e422a6..4dd84adfc 100644 --- a/app/lib/methods/subscriptions/rooms.js +++ b/app/lib/methods/subscriptions/rooms.js @@ -29,7 +29,7 @@ let queue = {}; let subTimer = null; const WINDOW_TIME = 500; -const createOrUpdateSubscription = async(subscription, room) => { +const createOrUpdateSubscription = async (subscription, room) => { try { const db = database.active; const subCollection = db.get('subscriptions'); @@ -90,11 +90,13 @@ const createOrUpdateSubscription = async(subscription, room) => { }; } catch (error) { try { - await db.action(async() => { - await roomsCollection.create(protectedFunction((r) => { - r._raw = sanitizedRaw({ id: room._id }, roomsCollection.schema); - Object.assign(r, room); - })); + await db.action(async () => { + await roomsCollection.create( + protectedFunction(r => { + r._raw = sanitizedRaw({ id: room._id }, roomsCollection.schema); + Object.assign(r, room); + }) + ); }); } catch (e) { // Do nothing @@ -155,7 +157,7 @@ const createOrUpdateSubscription = async(subscription, room) => { const batch = []; if (sub) { try { - const update = sub.prepareUpdate((s) => { + const update = sub.prepareUpdate(s => { Object.assign(s, tmp); if (subscription.announcement) { if (subscription.announcement !== sub.announcement) { @@ -169,7 +171,7 @@ const createOrUpdateSubscription = async(subscription, room) => { } } else { try { - const create = subCollection.prepareCreate((s) => { + const create = subCollection.prepareCreate(s => { s._raw = sanitizedRaw({ id: tmp.rid }, subCollection.schema); Object.assign(s, tmp); if (s.roomUpdatedAt) { @@ -201,7 +203,7 @@ const createOrUpdateSubscription = async(subscription, room) => { ); } else { batch.push( - messagesCollection.prepareCreate((m) => { + messagesCollection.prepareCreate(m => { m._raw = sanitizedRaw({ id: lastMessage._id }, messagesCollection.schema); m.subscription.id = lastMessage.rid; return Object.assign(m, lastMessage); @@ -210,7 +212,7 @@ const createOrUpdateSubscription = async(subscription, room) => { } } - await db.action(async() => { + await db.action(async () => { await db.batch(...batch); }); } catch (e) { @@ -218,17 +220,17 @@ const createOrUpdateSubscription = async(subscription, room) => { } }; -const getSubQueueId = rid => `SUB-${ rid }`; +const getSubQueueId = rid => `SUB-${rid}`; -const getRoomQueueId = rid => `ROOM-${ rid }`; +const getRoomQueueId = rid => `ROOM-${rid}`; -const debouncedUpdate = (subscription) => { +const debouncedUpdate = subscription => { if (!subTimer) { subTimer = setTimeout(() => { const batch = queue; queue = {}; subTimer = null; - Object.keys(batch).forEach((key) => { + Object.keys(batch).forEach(key => { InteractionManager.runAfterInteractions(() => { if (batch[key]) { if (/SUB/.test(key)) { @@ -257,7 +259,7 @@ export default function subscribeRooms() { store.dispatch(roomsRequest()); }; - const handleStreamMessageReceived = protectedFunction(async(ddpMessage) => { + const handleStreamMessageReceived = protectedFunction(async ddpMessage => { const db = database.active; // check if the server from variable is the same as the js sdk client @@ -289,13 +291,8 @@ export default function subscribeRooms() { const messagesToDelete = messages.map(m => m.prepareDestroyPermanently()); const threadsToDelete = threads.map(m => m.prepareDestroyPermanently()); const threadMessagesToDelete = threadMessages.map(m => m.prepareDestroyPermanently()); - await db.action(async() => { - await db.batch( - sub.prepareDestroyPermanently(), - ...messagesToDelete, - ...threadsToDelete, - ...threadMessagesToDelete - ); + await db.action(async () => { + await db.batch(sub.prepareDestroyPermanently(), ...messagesToDelete, ...threadsToDelete, ...threadMessagesToDelete); }); const roomState = store.getState().room; @@ -336,12 +333,14 @@ export default function subscribeRooms() { }; try { const msgCollection = db.get('messages'); - await db.action(async() => { - await msgCollection.create(protectedFunction((m) => { - m._raw = sanitizedRaw({ id: message._id }, msgCollection.schema); - m.subscription.id = args.rid; - Object.assign(m, message); - })); + await db.action(async () => { + await msgCollection.create( + protectedFunction(m => { + m._raw = sanitizedRaw({ id: message._id }, msgCollection.schema); + m.subscription.id = args.rid; + Object.assign(m, message); + }) + ); }); } catch (e) { log(e); @@ -350,7 +349,9 @@ export default function subscribeRooms() { if (/notification/.test(ev)) { const [notification] = ddpMessage.fields.args; try { - const { payload: { rid, message, sender } } = notification; + const { + payload: { rid, message, sender } + } = notification; const room = await RocketChat.getRoom(rid); notification.title = RocketChat.getRoomTitle(room); notification.avatar = RocketChat.getRoomAvatar(room); @@ -362,9 +363,9 @@ export default function subscribeRooms() { // If it's a direct the content is the message decrypted if (room.t === 'd') { notification.text = msg; - // If it's a private group we should add the sender name + // If it's a private group we should add the sender name } else { - notification.text = `${ RocketChat.getSenderName(sender) }: ${ msg }`; + notification.text = `${RocketChat.getSenderName(sender)}: ${msg}`; } } } catch (e) { diff --git a/app/lib/methods/updateMessages.js b/app/lib/methods/updateMessages.js index 0b6b6c7c0..c39066b20 100644 --- a/app/lib/methods/updateMessages.js +++ b/app/lib/methods/updateMessages.js @@ -1,23 +1,21 @@ import { sanitizedRaw } from '@nozbe/watermelondb/RawRecord'; import { Q } from '@nozbe/watermelondb'; -import buildMessage from './helpers/buildMessage'; import log from '../../utils/log'; import database from '../database'; -import protectedFunction from './helpers/protectedFunction'; import { Encryption } from '../encryption'; import { MESSAGE_TYPE_ANY_LOAD } from '../../constants/messageTypeLoad'; import { generateLoadMoreId } from '../utils'; +import protectedFunction from './helpers/protectedFunction'; +import buildMessage from './helpers/buildMessage'; -export default function updateMessages({ - rid, update = [], remove = [], loaderItem -}) { +export default function updateMessages({ rid, update = [], remove = [], loaderItem }) { try { if (!((update && update.length) || (remove && remove.length))) { return; } const db = database.active; - return db.action(async() => { + return db.action(async () => { // Decrypt these messages update = await Encryption.decryptMessages(update); const subCollection = db.get('subscriptions'); @@ -34,17 +32,9 @@ export default function updateMessages({ const threadCollection = db.get('threads'); const threadMessagesCollection = db.get('thread_messages'); const allMessagesRecords = await msgCollection - .query( - Q.where('rid', rid), - Q.or( - Q.where('id', Q.oneOf(messagesIds)), - Q.where('t', Q.oneOf(MESSAGE_TYPE_ANY_LOAD)) - ) - ) - .fetch(); - const allThreadsRecords = await threadCollection - .query(Q.where('rid', rid), Q.where('id', Q.oneOf(messagesIds))) + .query(Q.where('rid', rid), Q.or(Q.where('id', Q.oneOf(messagesIds)), Q.where('t', Q.oneOf(MESSAGE_TYPE_ANY_LOAD)))) .fetch(); + const allThreadsRecords = await threadCollection.query(Q.where('rid', rid), Q.where('id', Q.oneOf(messagesIds))).fetch(); const allThreadMessagesRecords = await threadMessagesCollection .query(Q.where('subscription_id', rid), Q.where('id', Q.oneOf(messagesIds))) .fetch(); @@ -69,56 +59,74 @@ export default function updateMessages({ let loadersToDelete = allMessagesRecords.filter(i1 => update.find(i2 => i1.id === generateLoadMoreId(i2._id))); // Create - msgsToCreate = msgsToCreate.map(message => msgCollection.prepareCreate(protectedFunction((m) => { - m._raw = sanitizedRaw({ id: message._id }, msgCollection.schema); - m.subscription.id = sub.id; - Object.assign(m, message); - }))); - threadsToCreate = threadsToCreate.map(thread => threadCollection.prepareCreate(protectedFunction((t) => { - t._raw = sanitizedRaw({ id: thread._id }, threadCollection.schema); - t.subscription.id = sub.id; - Object.assign(t, thread); - }))); - threadMessagesToCreate = threadMessagesToCreate.map(threadMessage => threadMessagesCollection.prepareCreate(protectedFunction((tm) => { - tm._raw = sanitizedRaw({ id: threadMessage._id }, threadMessagesCollection.schema); - Object.assign(tm, threadMessage); - tm.subscription.id = sub.id; - tm.rid = threadMessage.tmid; - delete threadMessage.tmid; - }))); + msgsToCreate = msgsToCreate.map(message => + msgCollection.prepareCreate( + protectedFunction(m => { + m._raw = sanitizedRaw({ id: message._id }, msgCollection.schema); + m.subscription.id = sub.id; + Object.assign(m, message); + }) + ) + ); + threadsToCreate = threadsToCreate.map(thread => + threadCollection.prepareCreate( + protectedFunction(t => { + t._raw = sanitizedRaw({ id: thread._id }, threadCollection.schema); + t.subscription.id = sub.id; + Object.assign(t, thread); + }) + ) + ); + threadMessagesToCreate = threadMessagesToCreate.map(threadMessage => + threadMessagesCollection.prepareCreate( + protectedFunction(tm => { + tm._raw = sanitizedRaw({ id: threadMessage._id }, threadMessagesCollection.schema); + Object.assign(tm, threadMessage); + tm.subscription.id = sub.id; + tm.rid = threadMessage.tmid; + delete threadMessage.tmid; + }) + ) + ); // Update - msgsToUpdate = msgsToUpdate.map((message) => { + 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); - })); + return message.prepareUpdate( + protectedFunction(m => { + Object.assign(m, newMessage); + }) + ); }); - threadsToUpdate = threadsToUpdate.map((thread) => { + 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); - })); + return thread.prepareUpdate( + protectedFunction(t => { + Object.assign(t, newThread); + }) + ); }); - threadMessagesToUpdate = threadMessagesToUpdate.map((threadMessage) => { + 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); - tm.rid = threadMessage.tmid; - delete threadMessage.tmid; - })); + return threadMessage.prepareUpdate( + protectedFunction(tm => { + Object.assign(tm, newThreadMessage); + tm.rid = threadMessage.tmid; + delete threadMessage.tmid; + }) + ); }); // Delete diff --git a/app/lib/rocketchat.js b/app/lib/rocketchat.js index 59ee94fc5..4fd4bdb5a 100644 --- a/app/lib/rocketchat.js +++ b/app/lib/rocketchat.js @@ -1,71 +1,63 @@ import { InteractionManager } from 'react-native'; import EJSON from 'ejson'; -import { - Rocketchat as RocketchatClient, - settings as RocketChatSettings -} from '@rocket.chat/sdk'; +import { settings as RocketChatSettings, Rocketchat as RocketchatClient } from '@rocket.chat/sdk'; import { Q } from '@nozbe/watermelondb'; import AsyncStorage from '@react-native-community/async-storage'; import { sanitizedRaw } from '@nozbe/watermelondb/RawRecord'; import RNFetchBlob from 'rn-fetch-blob'; -import { compareServerVersion, methods } from './utils'; -import reduxStore from './createStore'; import defaultSettings from '../constants/settings'; -import database from './database'; import log from '../utils/log'; -import { isIOS, getBundleId } from '../utils/deviceInfo'; +import { getBundleId, isIOS } from '../utils/deviceInfo'; import fetch from '../utils/fetch'; import SSLPinning from '../utils/sslPinning'; - import { encryptionInit } from '../actions/encryption'; -import { setUser, setLoginServices, loginRequest } from '../actions/login'; -import { disconnect, connectSuccess, connectRequest } from '../actions/connect'; -import { shareSelectServer, shareSetUser, shareSetSettings } 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, { getLoginSettings, setSettings, subscribeSettings } from './methods/getSettings'; - -import getRooms from './methods/getRooms'; -import { setPermissions, getPermissions } from './methods/getPermissions'; -import { getCustomEmojis, setCustomEmojis } from './methods/getCustomEmojis'; -import { - getEnterpriseModules, setEnterpriseModules, hasLicense, isOmnichannelModuleAvailable -} from './methods/enterpriseModules'; -import getSlashCommands from './methods/getSlashCommands'; -import { getRoles, setRoles, onRolesChanged } from './methods/getRoles'; -import canOpenRoom from './methods/canOpenRoom'; -import triggerBlockAction, { triggerSubmitView, triggerCancel } from './methods/actions'; - -import loadMessagesForRoom from './methods/loadMessagesForRoom'; -import loadSurroundingMessages from './methods/loadSurroundingMessages'; -import loadNextMessages from './methods/loadNextMessages'; -import loadMissedMessages from './methods/loadMissedMessages'; -import loadThreadMessages from './methods/loadThreadMessages'; - -import sendMessage, { resendMessage } from './methods/sendMessage'; -import { sendFileMessage, cancelUpload, isUploadActive } from './methods/sendFileMessage'; - -import callJitsi from './methods/callJitsi'; -import logout, { removeServer } from './methods/logout'; - +import { loginRequest, setLoginServices, setUser } from '../actions/login'; +import { connectRequest, connectSuccess, disconnect } from '../actions/connect'; +import { shareSelectServer, shareSetSettings, shareSetUser } from '../actions/share'; import { getDeviceToken } from '../notifications/push'; import { setActiveUsers } from '../actions/activeUsers'; import I18n from '../i18n'; import { twoFactor } from '../utils/twoFactor'; import { selectServerFailure } from '../actions/server'; import { useSsl } from '../utils/url'; -import UserPreferences from './userPreferences'; -import { Encryption } from './encryption'; import EventEmitter from '../utils/events'; -import { sanitizeLikeString } from './database/utils'; import { updatePermission } from '../actions/permissions'; import { TEAM_TYPE } from '../definition/ITeam'; import { updateSettings } from '../actions/settings'; +import { compareServerVersion, methods } from './utils'; +import reduxStore from './createStore'; +import database from './database'; +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, { getLoginSettings, setSettings, subscribeSettings } from './methods/getSettings'; +import getRooms from './methods/getRooms'; +import { getPermissions, setPermissions } from './methods/getPermissions'; +import { getCustomEmojis, setCustomEmojis } from './methods/getCustomEmojis'; +import { + getEnterpriseModules, + hasLicense, + isOmnichannelModuleAvailable, + setEnterpriseModules +} from './methods/enterpriseModules'; +import getSlashCommands from './methods/getSlashCommands'; +import { getRoles, onRolesChanged, setRoles } from './methods/getRoles'; +import canOpenRoom from './methods/canOpenRoom'; +import triggerBlockAction, { triggerCancel, triggerSubmitView } from './methods/actions'; +import loadMessagesForRoom from './methods/loadMessagesForRoom'; +import loadSurroundingMessages from './methods/loadSurroundingMessages'; +import loadNextMessages from './methods/loadNextMessages'; +import loadMissedMessages from './methods/loadMissedMessages'; +import loadThreadMessages from './methods/loadThreadMessages'; +import sendMessage, { resendMessage } from './methods/sendMessage'; +import { cancelUpload, isUploadActive, sendFileMessage } from './methods/sendFileMessage'; +import callJitsi from './methods/callJitsi'; +import logout, { removeServer } from './methods/logout'; +import UserPreferences from './userPreferences'; +import { Encryption } from './encryption'; +import { sanitizeLikeString } from './database/utils'; const TOKEN_KEY = 'reactnativemeteor_usertoken'; const CURRENT_SERVER = 'currentServer'; @@ -99,9 +91,7 @@ const RocketChat = { } }, canOpenRoom, - createChannel({ - name, users, type, readOnly, broadcast, encrypted, teamId - }) { + createChannel({ name, users, type, readOnly, broadcast, encrypted, teamId }) { const params = { name, members: users, @@ -136,11 +126,11 @@ const RocketChat = { }, async getServerInfo(server) { try { - const response = await RNFetchBlob.fetch('GET', `${ server }/api/info`, { ...RocketChatSettings.customHeaders }); + const response = await RNFetchBlob.fetch('GET', `${server}/api/info`, { ...RocketChatSettings.customHeaders }); try { // Try to resolve as json const jsonRes = response.json(); - if (!(jsonRes?.success)) { + if (!jsonRes?.success) { return { success: false, message: I18n.t('Not_RC_Server', { contact: I18n.t('Contact_your_server_admin') }) @@ -198,7 +188,7 @@ const RocketChat = { this.sdk = null; }, connect({ server, user, logoutOnError = false }) { - return new Promise((resolve) => { + return new Promise(resolve => { if (this?.sdk?.client?.host === server) { return resolve(); } else { @@ -250,23 +240,25 @@ const RocketChat = { this.sdk = new RocketchatClient({ host: server, protocol: 'ddp', useSsl: useSsl(server) }); this.getSettings(); - const sdkConnect = () => this.sdk.connect() - .then(() => { - const { server: currentServer } = reduxStore.getState().server; - if (user && user.token && server === currentServer) { - reduxStore.dispatch(loginRequest({ resume: user.token }, logoutOnError)); - } - }) - .catch((err) => { - console.log('connect error', err); - - // when `connect` raises an error, we try again in 10 seconds - this.connectTimeout = setTimeout(() => { - if (this.sdk?.client?.host === server) { - sdkConnect(); + const sdkConnect = () => + this.sdk + .connect() + .then(() => { + const { server: currentServer } = reduxStore.getState().server; + if (user && user.token && server === currentServer) { + reduxStore.dispatch(loginRequest({ resume: user.token }, logoutOnError)); } - }, 10000); - }); + }) + .catch(err => { + console.log('connect error', err); + + // when `connect` raises an error, we try again in 10 seconds + this.connectTimeout = setTimeout(() => { + if (this.sdk?.client?.host === server) { + sdkConnect(); + } + }, 10000); + }); sdkConnect(); @@ -282,106 +274,118 @@ const RocketChat = { reduxStore.dispatch(disconnect()); }); - this.usersListener = this.sdk.onStreamData('users', protectedFunction(ddpMessage => RocketChat._setUser(ddpMessage))); + this.usersListener = this.sdk.onStreamData( + 'users', + protectedFunction(ddpMessage => RocketChat._setUser(ddpMessage)) + ); - this.notifyAllListener = this.sdk.onStreamData('stream-notify-all', protectedFunction(async(ddpMessage) => { - const { eventName } = ddpMessage.fields; - if (/public-settings-changed/.test(eventName)) { - const { _id, value } = ddpMessage.fields.args[1]; - const db = database.active; - const settingsCollection = db.get('settings'); - try { - const settingsRecord = await settingsCollection.find(_id); - const { type } = defaultSettings[_id]; - if (type) { - await db.action(async() => { - await settingsRecord.update((u) => { - u[type] = value; + this.notifyAllListener = this.sdk.onStreamData( + 'stream-notify-all', + protectedFunction(async ddpMessage => { + const { eventName } = ddpMessage.fields; + if (/public-settings-changed/.test(eventName)) { + const { _id, value } = ddpMessage.fields.args[1]; + const db = database.active; + const settingsCollection = db.get('settings'); + try { + const settingsRecord = await settingsCollection.find(_id); + const { type } = defaultSettings[_id]; + if (type) { + await db.action(async () => { + await settingsRecord.update(u => { + u[type] = value; + }); + }); + } + reduxStore.dispatch(updateSettings(_id, value)); + } catch (e) { + log(e); + } + } + }) + ); + + this.rolesListener = this.sdk.onStreamData( + 'stream-roles', + protectedFunction(ddpMessage => onRolesChanged(ddpMessage)) + ); + + this.notifyLoggedListener = this.sdk.onStreamData( + 'stream-notify-logged', + protectedFunction(async ddpMessage => { + const { eventName } = ddpMessage.fields; + if (/user-status/.test(eventName)) { + this.activeUsers = this.activeUsers || {}; + if (!this._setUserTimer) { + this._setUserTimer = setTimeout(() => { + const activeUsersBatch = this.activeUsers; + InteractionManager.runAfterInteractions(() => { + reduxStore.dispatch(setActiveUsers(activeUsersBatch)); + }); + this._setUserTimer = null; + return (this.activeUsers = {}); + }, 10000); + } + const userStatus = ddpMessage.fields.args[0]; + const [id, , status, statusText] = userStatus; + this.activeUsers[id] = { status: STATUSES[status], statusText }; + + const { user: loggedUser } = reduxStore.getState().login; + if (loggedUser && loggedUser.id === id) { + reduxStore.dispatch(setUser({ status: STATUSES[status], statusText })); + } + } else if (/updateAvatar/.test(eventName)) { + const { username, etag } = ddpMessage.fields.args[0]; + const db = database.active; + const userCollection = db.get('users'); + try { + const [userRecord] = await userCollection.query(Q.where('username', Q.eq(username))).fetch(); + await db.action(async () => { + await userRecord.update(u => { + u.avatarETag = etag; + }); + }); + } catch { + // We can't create a new record since we don't receive the user._id + } + } else if (/permissions-changed/.test(eventName)) { + const { _id, roles } = ddpMessage.fields.args[1]; + const db = database.active; + const permissionsCollection = db.get('permissions'); + try { + const permissionsRecord = await permissionsCollection.find(_id); + await db.action(async () => { + await permissionsRecord.update(u => { + u.roles = roles; + }); + }); + reduxStore.dispatch(updatePermission(_id, roles)); + } catch (err) { + // + } + } else if (/Users:NameChanged/.test(eventName)) { + const userNameChanged = ddpMessage.fields.args[0]; + const db = database.active; + const userCollection = db.get('users'); + try { + const userRecord = await userCollection.find(userNameChanged._id); + await db.action(async () => { + await userRecord.update(u => { + Object.assign(u, userNameChanged); + }); + }); + } catch { + // User not found + await db.action(async () => { + await userCollection.create(u => { + u._raw = sanitizedRaw({ id: userNameChanged._id }, userCollection.schema); + Object.assign(u, userNameChanged); }); }); } - reduxStore.dispatch(updateSettings(_id, value)); - } catch (e) { - log(e); } - } - })); - - this.rolesListener = this.sdk.onStreamData('stream-roles', protectedFunction(ddpMessage => onRolesChanged(ddpMessage))); - - this.notifyLoggedListener = this.sdk.onStreamData('stream-notify-logged', protectedFunction(async(ddpMessage) => { - const { eventName } = ddpMessage.fields; - if (/user-status/.test(eventName)) { - this.activeUsers = this.activeUsers || {}; - if (!this._setUserTimer) { - this._setUserTimer = setTimeout(() => { - const activeUsersBatch = this.activeUsers; - InteractionManager.runAfterInteractions(() => { - reduxStore.dispatch(setActiveUsers(activeUsersBatch)); - }); - this._setUserTimer = null; - return this.activeUsers = {}; - }, 10000); - } - const userStatus = ddpMessage.fields.args[0]; - const [id,, status, statusText] = userStatus; - this.activeUsers[id] = { status: STATUSES[status], statusText }; - - const { user: loggedUser } = reduxStore.getState().login; - if (loggedUser && loggedUser.id === id) { - reduxStore.dispatch(setUser({ status: STATUSES[status], statusText })); - } - } else if (/updateAvatar/.test(eventName)) { - const { username, etag } = ddpMessage.fields.args[0]; - const db = database.active; - const userCollection = db.get('users'); - try { - const [userRecord] = await userCollection.query(Q.where('username', Q.eq(username))).fetch(); - await db.action(async() => { - await userRecord.update((u) => { - u.avatarETag = etag; - }); - }); - } catch { - // We can't create a new record since we don't receive the user._id - } - } else if (/permissions-changed/.test(eventName)) { - const { _id, roles } = ddpMessage.fields.args[1]; - const db = database.active; - const permissionsCollection = db.get('permissions'); - try { - const permissionsRecord = await permissionsCollection.find(_id); - await db.action(async() => { - await permissionsRecord.update((u) => { - u.roles = roles; - }); - }); - reduxStore.dispatch(updatePermission(_id, roles)); - } catch (err) { - // - } - } else if (/Users:NameChanged/.test(eventName)) { - const userNameChanged = ddpMessage.fields.args[0]; - const db = database.active; - const userCollection = db.get('users'); - try { - const userRecord = await userCollection.find(userNameChanged._id); - await db.action(async() => { - await userRecord.update((u) => { - Object.assign(u, userNameChanged); - }); - }); - } catch { - // User not found - await db.action(async() => { - await userCollection.create((u) => { - u._raw = sanitizedRaw({ id: userNameChanged._id }, userCollection.schema); - Object.assign(u, userNameChanged); - }); - }); - } - } - })); + }) + ); resolve(); }); @@ -391,7 +395,7 @@ const RocketChat = { database.setShareDB(server); try { - const certificate = await UserPreferences.getStringAsync(`${ RocketChat.CERTIFICATE_KEY }-${ server }`); + const certificate = await UserPreferences.getStringAsync(`${RocketChat.CERTIFICATE_KEY}-${server}`); await SSLPinning.setCertificate(certificate, server); } catch { // Do nothing @@ -435,7 +439,7 @@ const RocketChat = { reduxStore.dispatch(shareSetSettings(this.parseSettings(parsed))); // set User info - const userId = await UserPreferences.getStringAsync(`${ RocketChat.TOKEN_KEY }-${ server }`); + const userId = await UserPreferences.getStringAsync(`${RocketChat.TOKEN_KEY}-${server}`); const userCollections = serversDB.get('users'); let user = null; if (userId) { @@ -502,7 +506,7 @@ const RocketChat = { }, e2eRequestRoomKey(rid, e2eKeyId) { // RC 0.70.0 - return this.methodCallWrapper('stream-notify-room-users', `${ rid }/e2ekeyRequest`, rid, e2eKeyId); + return this.methodCallWrapper('stream-notify-room-users', `${rid}/e2ekeyRequest`, rid, e2eKeyId); }, e2eResetOwnKey() { this.unsubscribeRooms(); @@ -527,7 +531,7 @@ const RocketChat = { }, loginTOTP(params, loginEmailPassword, isFromWebView = false) { - return new Promise(async(resolve, reject) => { + return new Promise(async (resolve, reject) => { try { const result = await this.login(params, isFromWebView); return resolve(result); @@ -551,14 +555,16 @@ const RocketChat = { return resolve(this.loginTOTP({ ...params, code: code?.twoFactorCode }, loginEmailPassword)); } - return resolve(this.loginTOTP({ - totp: { - login: { - ...params - }, - code: code?.twoFactorCode - } - })); + return resolve( + this.loginTOTP({ + totp: { + login: { + ...params + }, + code: code?.twoFactorCode + } + }) + ); } catch { // twoFactor was canceled return reject(); @@ -629,10 +635,10 @@ const RocketChat = { async clearCache({ server }) { try { const serversDB = database.servers; - await serversDB.action(async() => { + await serversDB.action(async () => { const serverCollection = serversDB.get('servers'); const serverRecord = await serverCollection.find(server); - await serverRecord.update((s) => { + await serverRecord.update(s => { s.roomsUpdatedAt = null; }); }); @@ -648,7 +654,7 @@ const RocketChat = { } }, registerPushToken() { - return new Promise(async(resolve) => { + return new Promise(async resolve => { const token = getDeviceToken(); if (token) { const type = isIOS ? 'apn' : 'gcm'; @@ -689,13 +695,13 @@ const RocketChat = { const searchText = text.trim(); const db = database.active; const likeString = sanitizeLikeString(searchText); - let data = await db.get('subscriptions').query( - Q.or( - Q.where('name', Q.like(`%${ likeString }%`)), - Q.where('fname', Q.like(`%${ likeString }%`)) - ), - Q.experimentalSortBy('room_updated_at', Q.desc) - ).fetch(); + let data = await db + .get('subscriptions') + .query( + Q.or(Q.where('name', Q.like(`%${likeString}%`)), Q.where('fname', Q.like(`%${likeString}%`))), + Q.experimentalSortBy('room_updated_at', Q.desc) + ) + .fetch(); if (filterUsers && !filterRooms) { data = data.filter(item => item.t === 'd' && !RocketChat.isGroupChat(item)); @@ -733,13 +739,13 @@ const RocketChat = { if (data.length < 7) { const { users, rooms } = await Promise.race([ RocketChat.spotlight(searchText, usernames, { users: filterUsers, rooms: filterRooms }), - new Promise((resolve, reject) => this.oldPromise = reject) + new Promise((resolve, reject) => (this.oldPromise = reject)) ]); if (filterUsers) { users .filter((item1, index) => users.findIndex(item2 => item2._id === item1._id) === index) // Remove duplicated data from response .filter(user => !data.some(sub => user.username === sub.name)) // Make sure to remove users already on local database - .forEach((user) => { + .forEach(user => { data.push({ ...user, rid: user.username, @@ -750,7 +756,7 @@ const RocketChat = { }); } if (filterRooms) { - rooms.forEach((room) => { + rooms.forEach(room => { // Check if it exists on local database const index = data.findIndex(item => item.rid === room._id); if (index === -1) { @@ -790,17 +796,18 @@ const RocketChat = { return this.post('im.create', { usernames }); }, - createDiscussion({ - prid, pmid, t_name, reply, users, encrypted - }) { + createDiscussion({ prid, pmid, t_name, reply, users, encrypted }) { // RC 1.0.0 return this.post('rooms.createDiscussion', { - prid, pmid, t_name, reply, users, encrypted + prid, + pmid, + t_name, + reply, + users, + encrypted }); }, - createTeam({ - name, users, type, readOnly, broadcast, encrypted - }) { + createTeam({ name, users, type, readOnly, broadcast, encrypted }) { const params = { name, users, @@ -828,12 +835,13 @@ const RocketChat = { // RC 3.13.0 return this.post('teams.leave', { teamName, rooms }); }, - removeTeamMember({ - teamId, teamName, userId, rooms - }) { + removeTeamMember({ teamId, teamName, userId, rooms }) { // RC 3.13.0 return this.post('teams.removeMember', { - teamId, teamName, userId, rooms + teamId, + teamName, + userId, + rooms }); }, updateTeamRoom({ roomId, isDefault }) { @@ -856,13 +864,13 @@ const RocketChat = { const params = { ...(type === 'c' ? { - channelId: rid, - channelName: name - } + channelId: rid, + channelName: name + } : { - roomId: rid, - roomName: name - }) + roomId: rid, + roomName: name + }) }; return this.sdk.post(type === 'c' ? 'channels.convertToTeam' : 'groups.convertToTeam', params); }, @@ -902,16 +910,19 @@ const RocketChat = { getSlashCommands, getRoles, setRoles, - parseSettings: settings => settings.reduce((ret, item) => { - 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; - }, {}), + parseSettings: settings => + settings.reduce((ret, item) => { + 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) { - return settings.map((setting) => { + return settings.map(setting => { setting[defaultSettings[setting._id].type] = setting.value; return setting; }); @@ -970,7 +981,7 @@ const RocketChat = { c: 'channel', d: 'direct' }[room.t]; - return `${ server }/${ roomType }/${ this.isGroupChat(room) ? room.rid : room.name }?msg=${ message.id }`; + return `${server}/${roomType}/${this.isGroupChat(room) ? room.rid : room.name}?msg=${message.id}`; }, getPermalinkChannel(channel) { const { server } = reduxStore.getState().server; @@ -979,7 +990,7 @@ const RocketChat = { c: 'channel', d: 'direct' }[channel.t]; - return `${ server }/${ roomType }/${ channel.name }`; + return `${server}/${roomType}/${channel.name}`; }, subscribe(...args) { return this.sdk.subscribe(...args); @@ -998,7 +1009,7 @@ const RocketChat = { const { UI_Use_Real_Name } = settings; const { user } = login; const name = UI_Use_Real_Name ? user.name : user.username; - return this.methodCall('stream-notify-room', `${ room }/typing`, name, typing); + return this.methodCall('stream-notify-room', `${room}/typing`, name, typing); }, setUserPresenceAway() { return this.methodCall('UserPresence:away'); @@ -1028,17 +1039,30 @@ const RocketChat = { } return this.post('subscriptions.read', { rid: roomId }); }, - getRoomMembers(rid, allUsers, skip = 0, limit = 10) { + async getRoomMembers({ rid, allUsers, roomType, type, filter, skip = 0, limit = 10 }) { + const serverVersion = reduxStore.getState().server.version; + if (compareServerVersion(serverVersion, '3.16.0', methods.greaterThanOrEqualTo)) { + const params = { + roomId: rid, + offset: skip, + count: limit, + ...(type !== 'all' && { 'status[]': type }), + ...(filter && { filter }) + }; + // RC 3.16.0 + const result = await this.sdk.get(`${this.roomTypeToApiType(roomType)}.members`, params); + return result?.members; + } // RC 0.42.0 - return this.methodCallWrapper('getUsersOfRoom', rid, allUsers, { skip, limit }); + const result = await this.methodCallWrapper('getUsersOfRoom', rid, allUsers, { skip, limit }); + return result?.records; }, - methodCallWrapper(method, ...params) { const { API_Use_REST_For_DDP_Calls } = reduxStore.getState().settings; if (API_Use_REST_For_DDP_Calls) { - return this.post(`method.call/${ method }`, { message: EJSON.stringify({ method, params }) }); + return this.post(`method.call/${method}`, { message: EJSON.stringify({ method, params }) }); } - const parsedParams = params.map((param) => { + const parsedParams = params.map(param => { if (param instanceof Date) { return { $date: new Date(param).getTime() }; } @@ -1053,7 +1077,7 @@ const RocketChat = { }, getRoomCounters(roomId, t) { // RC 0.65.0 - return this.sdk.get(`${ this.roomTypeToApiType(t) }.counters`, { roomId }); + return this.sdk.get(`${this.roomTypeToApiType(t)}.counters`, { roomId }); }, getChannelInfo(roomId) { // RC 0.48.0 @@ -1076,11 +1100,12 @@ const RocketChat = { // RC 2.3.0 return this.sdk.get('livechat/visitors.info', { visitorId }); }, - getTeamListRoom({ - teamId, count, offset, type, filter - }) { + getTeamListRoom({ teamId, count, offset, type, filter }) { const params = { - teamId, count, offset, type + teamId, + count, + offset, + type }; if (filter) { @@ -1107,11 +1132,11 @@ const RocketChat = { }, getPagesLivechat(rid, offset) { // RC 2.3.0 - return this.sdk.get(`livechat/visitors.pagesVisited/${ rid }?count=50&offset=${ offset }`); + return this.sdk.get(`livechat/visitors.pagesVisited/${rid}?count=50&offset=${offset}`); }, getDepartmentInfo(departmentId) { // RC 2.2.0 - return this.sdk.get(`livechat/department/${ departmentId }?includeAgents=false`); + return this.sdk.get(`livechat/department/${departmentId}?includeAgents=false`); }, getDepartments() { // RC 2.2.0 @@ -1131,13 +1156,26 @@ const RocketChat = { }, getAgentDepartments(uid) { // RC 2.4.0 - return this.sdk.get(`livechat/agents/${ uid }/departments?enabledDepartmentsOnly=true`); + return this.sdk.get(`livechat/agents/${uid}/departments?enabledDepartmentsOnly=true`); }, getCustomFields() { // RC 2.2.0 return this.sdk.get('livechat/custom-fields'); }, + getListCannedResponse({ scope = '', departmentId = '', offset = 0, count = 25, text = '' }) { + const params = { + offset, + count, + ...(departmentId && { departmentId }), + ...(text && { text }), + ...(scope && { scope }) + }; + + // RC 3.17.0 + return this.sdk.get('canned-responses', params); + }, + getUidDirectMessage(room) { const { id: userId } = reduxStore.getState().login.user; @@ -1180,11 +1218,11 @@ const RocketChat = { }, leaveRoom(roomId, t) { // RC 0.48.0 - return this.post(`${ this.roomTypeToApiType(t) }.leave`, { roomId }); + return this.post(`${this.roomTypeToApiType(t)}.leave`, { roomId }); }, deleteRoom(roomId, t) { // RC 0.49.0 - return this.post(`${ this.roomTypeToApiType(t) }.delete`, { roomId }); + return this.post(`${this.roomTypeToApiType(t)}.delete`, { roomId }); }, toggleMuteUserInRoom(rid, username, mute) { if (mute) { @@ -1194,41 +1232,33 @@ const RocketChat = { // RC 0.51.0 return this.methodCallWrapper('unmuteUserInRoom', { rid, username }); }, - toggleRoomOwner({ - roomId, t, userId, isOwner - }) { + toggleRoomOwner({ roomId, t, userId, isOwner }) { if (isOwner) { // RC 0.49.4 - return this.post(`${ this.roomTypeToApiType(t) }.addOwner`, { roomId, userId }); + return this.post(`${this.roomTypeToApiType(t)}.addOwner`, { roomId, userId }); } // RC 0.49.4 - return this.post(`${ this.roomTypeToApiType(t) }.removeOwner`, { roomId, userId }); + return this.post(`${this.roomTypeToApiType(t)}.removeOwner`, { roomId, userId }); }, - toggleRoomLeader({ - roomId, t, userId, isLeader - }) { + toggleRoomLeader({ roomId, t, userId, isLeader }) { if (isLeader) { // RC 0.58.0 - return this.post(`${ this.roomTypeToApiType(t) }.addLeader`, { roomId, userId }); + return this.post(`${this.roomTypeToApiType(t)}.addLeader`, { roomId, userId }); } // RC 0.58.0 - return this.post(`${ this.roomTypeToApiType(t) }.removeLeader`, { roomId, userId }); + return this.post(`${this.roomTypeToApiType(t)}.removeLeader`, { roomId, userId }); }, - toggleRoomModerator({ - roomId, t, userId, isModerator - }) { + toggleRoomModerator({ roomId, t, userId, isModerator }) { if (isModerator) { // RC 0.49.4 - return this.post(`${ this.roomTypeToApiType(t) }.addModerator`, { roomId, userId }); + return this.post(`${this.roomTypeToApiType(t)}.addModerator`, { roomId, userId }); } // RC 0.49.4 - return this.post(`${ this.roomTypeToApiType(t) }.removeModerator`, { roomId, userId }); + return this.post(`${this.roomTypeToApiType(t)}.removeModerator`, { roomId, userId }); }, - removeUserFromRoom({ - roomId, t, userId - }) { + removeUserFromRoom({ roomId, t, userId }) { // RC 0.48.0 - return this.post(`${ this.roomTypeToApiType(t) }.kick`, { roomId, userId }); + return this.post(`${this.roomTypeToApiType(t)}.kick`, { roomId, userId }); }, ignoreUser({ rid, userId, ignore }) { return this.sdk.get('chat.ignoreUser', { rid, userId, ignore }); @@ -1236,20 +1266,20 @@ const RocketChat = { toggleArchiveRoom(roomId, t, archive) { if (archive) { // RC 0.48.0 - return this.post(`${ this.roomTypeToApiType(t) }.archive`, { roomId }); + return this.post(`${this.roomTypeToApiType(t)}.archive`, { roomId }); } // RC 0.48.0 - return this.post(`${ this.roomTypeToApiType(t) }.unarchive`, { roomId }); + return this.post(`${this.roomTypeToApiType(t)}.unarchive`, { roomId }); }, hideRoom(roomId, t) { - return this.post(`${ this.roomTypeToApiType(t) }.close`, { roomId }); + return this.post(`${this.roomTypeToApiType(t)}.close`, { roomId }); }, saveRoomSettings(rid, params) { // RC 0.55.0 return this.methodCallWrapper('saveRoomSettings', rid, params); }, post(...args) { - return new Promise(async(resolve, reject) => { + return new Promise(async (resolve, reject) => { const isMethodCall = args[0]?.startsWith('method.call/'); try { const result = await this.sdk.post(...args); @@ -1286,7 +1316,7 @@ const RocketChat = { }); }, methodCall(...args) { - return new Promise(async(resolve, reject) => { + return new Promise(async (resolve, reject) => { try { const result = await this.sdk?.methodCall(...args, this.code || ''); return resolve(result); @@ -1337,13 +1367,13 @@ const RocketChat = { const shareUser = reduxStore.getState().share.user; const loginUser = reduxStore.getState().login.user; // get user roles on the server from redux - const userRoles = (shareUser?.roles || loginUser?.roles) || []; + const userRoles = shareUser?.roles || loginUser?.roles || []; return userRoles.indexOf(r => r === role) > -1; }, getRoomRoles(roomId, type) { // RC 0.65.0 - return this.sdk.get(`${ this.roomTypeToApiType(type) }.roles`, { roomId }); + return this.sdk.get(`${this.roomTypeToApiType(type)}.roles`, { roomId }); }, /** * Permissions: array of permissions' roles from redux. Example: [['owner', 'admin'], ['leader']] @@ -1367,7 +1397,7 @@ const RocketChat = { const shareUser = reduxStore.getState().share.user; const loginUser = reduxStore.getState().login.user; // get user roles on the server from redux - const userRoles = (shareUser?.roles || loginUser?.roles) || []; + const userRoles = shareUser?.roles || loginUser?.roles || []; const mergedRoles = [...new Set([...roomRoles, ...userRoles])]; return permissions.map(permission => permission?.some(r => mergedRoles.includes(r) ?? false)); } catch (e) { @@ -1412,7 +1442,7 @@ const RocketChat = { async getLoginServices(server) { try { let loginServices = []; - const loginServicesResult = await fetch(`${ server }/api/v1/settings.oauth`).then(response => response.json()); + const loginServicesResult = await fetch(`${server}/api/v1/settings.oauth`).then(response => response.json()); if (loginServicesResult.success && loginServicesResult.services) { const { services } = loginServicesResult; @@ -1438,9 +1468,7 @@ const RocketChat = { } }, _determineAuthType(services) { - const { - name, custom, showButton = true, service - } = services; + const { name, custom, showButton = true, service } = services; const authName = name || service; @@ -1470,24 +1498,33 @@ const RocketChat = { }, roomTypeToApiType(t) { const types = { - c: 'channels', d: 'im', p: 'groups', l: 'channels' + c: 'channels', + d: 'im', + p: 'groups', + l: 'channels' }; return types[t]; }, getFiles(roomId, type, offset) { // RC 0.59.0 - return this.sdk.get(`${ this.roomTypeToApiType(type) }.files`, { + return this.sdk.get(`${this.roomTypeToApiType(type)}.files`, { roomId, offset, sort: { uploadedAt: -1 }, fields: { - name: 1, description: 1, size: 1, type: 1, uploadedAt: 1, url: 1, userId: 1 + name: 1, + description: 1, + size: 1, + type: 1, + uploadedAt: 1, + url: 1, + userId: 1 } }); }, getMessages(roomId, type, query, offset) { // RC 0.59.0 - return this.sdk.get(`${ this.roomTypeToApiType(type) }.messages`, { + return this.sdk.get(`${this.roomTypeToApiType(type)}.messages`, { roomId, query, offset, @@ -1514,11 +1551,12 @@ const RocketChat = { } return this.post('chat.unfollowMessage', { mid }); }, - getThreadsList({ - rid, count, offset, text - }) { + getThreadsList({ rid, count, offset, text }) { const params = { - rid, count, offset, sort: { ts: -1 } + rid, + count, + offset, + sort: { ts: -1 } }; if (text) { params.text = text; @@ -1530,7 +1568,8 @@ const RocketChat = { getSyncThreadsList({ rid, updatedSince }) { // RC 1.0 return this.sdk.get('chat.syncThreadsList', { - rid, updatedSince + rid, + updatedSince }); }, readThreads(tmid) { @@ -1544,19 +1583,30 @@ const RocketChat = { runSlashCommand(command, roomId, params, triggerId, tmid) { // RC 0.60.2 return this.post('commands.run', { - command, roomId, params, triggerId, tmid + command, + roomId, + params, + triggerId, + tmid }); }, getCommandPreview(command, roomId, params) { // RC 0.65.0 return this.sdk.get('commands.preview', { - command, roomId, params + command, + roomId, + params }); }, executeCommandPreview(command, params, roomId, previewItem, triggerId, tmid) { // RC 0.65.0 return this.post('commands.preview', { - command, params, roomId, previewItem, triggerId, tmid + command, + params, + roomId, + previewItem, + triggerId, + tmid }); }, _setUser(ddpMessage) { @@ -1578,7 +1628,7 @@ const RocketChat = { reduxStore.dispatch(setActiveUsers(activeUsersBatch)); }); this._setUserTimer = null; - return this.activeUsers = {}; + return (this.activeUsers = {}); }, 10000); } @@ -1591,12 +1641,13 @@ const RocketChat = { getUsersPresence, getUserPresence, subscribeUsersPresence, - getDirectory({ - query, count, offset, sort - }) { + getDirectory({ query, count, offset, sort }) { // RC 1.0 return this.sdk.get('directory', { - query, count, offset, sort + query, + count, + offset, + sort }); }, canAutoTranslate() { @@ -1606,16 +1657,14 @@ const RocketChat = { return false; } const autoTranslatePermission = reduxStore.getState().permissions['auto-translate']; - const userRoles = (reduxStore.getState().login?.user?.roles) ?? []; + const userRoles = reduxStore.getState().login?.user?.roles ?? []; return autoTranslatePermission?.some(role => userRoles.includes(role)); } catch (e) { log(e); return false; } }, - saveAutoTranslate({ - rid, field, value, options - }) { + saveAutoTranslate({ rid, field, value, options }) { return this.methodCallWrapper('autoTranslate.saveSettings', rid, field, value, options); }, getSupportedLanguagesAutoTranslate() { @@ -1629,10 +1678,14 @@ const RocketChat = { return useRealName ? sender.name : sender.username; }, getRoomTitle(room) { - const { UI_Use_Real_Name: useRealName, UI_Allow_room_names_with_special_chars: allowSpecialChars } = reduxStore.getState().settings; + const { UI_Use_Real_Name: useRealName, UI_Allow_room_names_with_special_chars: allowSpecialChars } = + reduxStore.getState().settings; const { username } = reduxStore.getState().login.user; if (RocketChat.isGroupChat(room) && !(room.name && room.name.length)) { - return room.usernames.filter(u => u !== username).sort((u1, u2) => u1.localeCompare(u2)).join(', '); + return room.usernames + .filter(u => u !== username) + .sort((u1, u2) => u1.localeCompare(u2)) + .join(', '); } if (allowSpecialChars && room.t !== 'd') { return room.fname || room.name; diff --git a/app/lib/selection.json b/app/lib/selection.json index 09aff4ed8..033aea5fc 100644 --- a/app/lib/selection.json +++ b/app/lib/selection.json @@ -1 +1,4874 @@ -{"IcoMoonType":"selection","icons":[{"icon":{"paths":["M649.427 192c-26.454 0-52.016 10.804-71.005 30.343l-305.1 313.906c-31.48 32.387-49.322 76.506-49.322 122.694 0 46.186 17.842 90.304 49.322 122.694 31.451 32.355 73.91 50.362 117.984 50.362s86.531-18.006 117.981-50.362l305.101-313.907c12.317-12.672 32.576-12.96 45.248-0.643 12.675 12.317 12.963 32.576 0.646 45.251l-305.101 313.904c-43.302 44.554-102.23 69.757-163.875 69.757s-120.574-25.203-163.877-69.757c-43.273-44.522-67.428-104.717-67.428-167.299s24.155-122.781 67.428-167.302l305.1-313.905c30.845-31.735 72.874-49.737 116.899-49.737s86.054 18.002 116.899 49.737c30.816 31.704 47.971 74.514 47.971 118.968s-17.155 87.263-47.971 118.969l-305.43 313.904c-0.003 0.006 0.003-0.003 0 0-18.384 18.909-43.523 29.718-69.923 29.718-26.406 0-51.539-10.8-69.923-29.718-18.356-18.883-28.512-44.31-28.512-70.634 0-26.326 10.156-51.754 28.512-70.637l281.872-289.668c12.326-12.666 32.586-12.942 45.251-0.617s12.941 32.585 0.618 45.251l-281.846 289.638c-6.557 6.752-10.406 16.106-10.406 26.032 0 9.93 3.843 19.277 10.406 26.029 6.531 6.72 15.194 10.323 24.029 10.323s17.498-3.603 24.029-10.323l305.43-313.907c19.024-19.568 29.866-46.301 29.866-74.361 0-28.059-10.842-54.791-29.866-74.362-18.989-19.54-44.55-30.343-71.005-30.343z"],"attrs":[{"fill":"rgb(108, 114, 122)"}],"isMulticolor":false,"isMulticolor2":false,"grid":0,"tags":["attach"],"colorPermutations":{"1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101":[{"f":2}]}},"attrs":[{"fill":"rgb(108, 114, 122)"}],"properties":{"order":254,"id":205,"name":"attach","prevSize":32,"code":59676},"setIdx":0,"setId":4,"iconIdx":0},{"icon":{"paths":["M254.841 275.612l-13.972 14.271c-37.092 37.883-36.449 98.664 1.435 135.758l145.619 142.573c37.885 37.091 98.666 36.448 135.76-1.437l13.971-14.269c0.723-0.739 1.43-1.488 2.128-2.243l0.099 0.099c5.805-5.904 13.888-9.568 22.822-9.568 17.674 0 32 14.326 32 32 0 9.6-4.227 18.211-10.922 24.077l-0.397 0.41-13.974 14.269c-61.818 63.142-163.12 64.214-226.259 2.394l-145.622-142.576c-63.141-61.818-64.212-163.119-2.392-226.26l13.972-14.27c61.82-63.141 163.12-64.212 226.263-2.392l74.691 73.131c0.976 0.847 1.901 1.752 2.768 2.71l0.374 0.366-0.026 0.026c4.934 5.63 7.923 13.005 7.923 21.078 0 17.674-14.326 32-32 32-7.83 0-15.005-2.813-20.566-7.482l-0.106 0.109-77.834-76.206c-37.885-37.092-98.666-36.45-135.757 1.435zM790.566 768.003l13.971-14.269c37.091-37.885 36.448-98.666-1.437-135.757l-145.619-142.576c-37.885-37.091-98.666-36.448-135.757 1.437l-13.971 14.269c-0.723 0.739-1.434 1.488-2.128 2.243l-0.102-0.099c-5.805 5.907-13.885 9.568-22.822 9.568-17.67 0-32-14.326-32-32 0-9.6 4.227-18.211 10.922-24.077l0.4-0.406 13.971-14.272c61.821-63.142 163.12-64.211 226.262-2.39l145.619 142.573c63.142 61.821 64.211 163.12 2.394 226.262l-13.971 14.269c-61.821 63.142-163.123 64.211-226.262 2.394l-74.694-73.133c-0.976-0.845-1.901-1.75-2.768-2.71l-0.374-0.365 0.026-0.026c-4.931-5.629-7.923-13.005-7.923-21.078 0-17.674 14.326-32 32-32 7.83 0 15.005 2.813 20.566 7.482l0.106-0.109 77.837 76.208c37.885 37.091 98.662 36.448 135.757-1.437z"],"attrs":[{"fill":"rgb(108, 114, 122)"}],"width":1056,"isMulticolor":false,"isMulticolor2":false,"grid":0,"tags":["link"],"colorPermutations":{"1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101":[{"f":2}]}},"attrs":[{"fill":"rgb(108, 114, 122)"}],"properties":{"order":253,"id":204,"name":"link","prevSize":32,"code":59752},"setIdx":0,"setId":4,"iconIdx":1},{"icon":{"paths":["M512 896c-212.077 0-384-171.923-384-384s171.923-384 384-384c212.077 0 384 171.923 384 384s-171.923 384-384 384zM565.334 288c0-29.455-23.878-53.333-53.334-53.333s-53.334 23.878-53.334 53.333v249.632l180.016 144.013c23.002 18.403 56.563 14.672 74.963-8.326 18.403-23.002 14.672-56.563-8.326-74.963l-139.984-111.987v-198.368z"],"attrs":[{"fill":"rgb(243, 190, 8)"}],"isMulticolor":false,"isMulticolor2":false,"grid":0,"tags":["status-away"],"colorPermutations":{"1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101":[{"f":3}]}},"attrs":[{"fill":"rgb(243, 190, 8)"}],"properties":{"order":247,"id":203,"name":"status-away","prevSize":32,"code":59741},"setIdx":0,"setId":4,"iconIdx":2},{"icon":{"paths":["M512 896c-212.077 0-384-171.923-384-384s171.923-384 384-384c212.077 0 384 171.923 384 384s-171.923 384-384 384zM384 458.666c-29.456 0-53.334 23.875-53.334 53.331s23.878 53.334 53.334 53.334h256c29.456 0 53.334-23.878 53.334-53.334s-23.878-53.331-53.334-53.331h-256z"],"attrs":[{"fill":"rgb(245, 69, 92)"}],"isMulticolor":false,"isMulticolor2":false,"grid":0,"tags":["status-busy"],"colorPermutations":{"1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101":[{"f":6}]}},"attrs":[{"fill":"rgb(245, 69, 92)"}],"properties":{"order":248,"id":202,"name":"status-busy","prevSize":32,"code":59742},"setIdx":0,"setId":4,"iconIdx":3},{"icon":{"paths":["M888.691 586.941l-125.568-24.838c3.187-16.102 4.877-32.842 4.877-50.102s-1.69-34-4.877-50.102l125.568-24.838c4.794 24.237 7.309 49.296 7.309 74.941s-2.515 50.704-7.309 74.941zM831.322 298.644l-106.365 71.209c-18.726-27.971-42.838-52.083-70.81-70.81l71.21-106.364c41.875 28.035 77.93 64.089 105.965 105.965zM586.941 135.309l-24.838 125.566c-16.102-3.185-32.842-4.876-50.102-4.876s-34 1.69-50.102 4.876l-24.838-125.566c24.237-4.795 49.296-7.309 74.941-7.309s50.704 2.514 74.941 7.309zM298.644 192.679l71.209 106.364c-27.971 18.727-52.083 42.839-70.81 70.81l-106.364-71.209c28.035-41.876 64.089-77.93 105.965-105.964zM135.309 437.059c-4.795 24.237-7.309 49.296-7.309 74.941s2.514 50.704 7.309 74.941l125.566-24.838c-3.185-16.102-4.876-32.842-4.876-50.102s1.69-34 4.876-50.102l-125.566-24.838zM192.679 725.357l106.364-71.21c18.727 27.971 42.839 52.083 70.81 70.81l-71.209 106.365c-41.876-28.035-77.93-64.090-105.964-105.965zM437.059 888.691l24.838-125.568c16.102 3.187 32.842 4.877 50.102 4.877s34-1.69 50.102-4.877l24.838 125.568c-24.237 4.794-49.296 7.309-74.941 7.309s-50.704-2.515-74.941-7.309zM725.357 831.322l-71.21-106.365c27.971-18.726 52.083-42.838 70.81-70.81l106.365 71.21c-28.035 41.875-64.090 77.93-105.965 105.965z"],"attrs":[{"fill":"rgb(158, 162, 168)"}],"isMulticolor":false,"isMulticolor2":false,"grid":0,"tags":["status-loading"],"colorPermutations":{"1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101":[{"f":7}]}},"attrs":[{"fill":"rgb(158, 162, 168)"}],"properties":{"order":249,"id":201,"name":"status-loading","prevSize":32,"code":59743},"setIdx":0,"setId":4,"iconIdx":4},{"icon":{"paths":["M512 789.334c-153.168 0-277.333-124.166-277.333-277.334s124.165-277.333 277.333-277.333c153.168 0 277.334 124.165 277.334 277.333s-124.166 277.334-277.334 277.334zM512 896c212.077 0 384-171.923 384-384s-171.923-384-384-384c-212.077 0-384 171.923-384 384s171.923 384 384 384z"],"attrs":[{"fill":"rgb(158, 162, 168)"}],"isMulticolor":false,"isMulticolor2":false,"grid":0,"tags":["status-offline"],"colorPermutations":{"1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101":[{"f":7}]}},"attrs":[{"fill":"rgb(158, 162, 168)"}],"properties":{"order":250,"id":200,"name":"status-offline","prevSize":32,"code":59744},"setIdx":0,"setId":4,"iconIdx":5},{"icon":{"paths":["M896 512c0 212.077-171.923 384-384 384s-384-171.923-384-384c0-212.077 171.923-384 384-384s384 171.923 384 384z"],"attrs":[{"fill":"rgb(45, 224, 165)"}],"isMulticolor":false,"isMulticolor2":false,"grid":0,"tags":["status-online"],"colorPermutations":{"1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101":[{"f":4}]}},"attrs":[{"fill":"rgb(45, 224, 165)"}],"properties":{"order":251,"id":199,"name":"status-online","prevSize":32,"code":59745},"setIdx":0,"setId":4,"iconIdx":6},{"icon":{"paths":["M631.997 256h24v-56c0-57.437 46.563-104 104-104 57.44 0 104 46.562 104 104v56h24c17.674 0 32 14.327 32 32v192c0 17.674-14.326 32-32 32h-256c-17.67 0-32-14.326-32-32v-192c0-17.673 14.33-32 32-32zM759.997 160c-22.090 0-40 17.908-40 40v55.238h80v-55.238c0-22.092-17.907-40-40-40z","M527.997 224c10.096 0 19.882 1.336 29.184 3.84-13.251 16.46-21.184 37.383-21.184 60.16v0.664c-2.602-0.436-5.274-0.664-8-0.664-26.509 0-48 21.49-48 48s21.491 48 48 48c2.726 0 5.398-0.227 8-0.662v64.381c-2.64 0.186-5.309 0.282-8 0.282-61.856 0-112-50.144-112-112s50.144-112 112-112z","M492.941 500.282c14.883 5.709 30.883 7.283 46.355 4.758 6.182 22.938 20.646 42.474 39.987 55.206-35.123 13.318-74.019 13.309-109.261-0.208l-15.040-5.77c-12.838-4.925-27.104-4.589-39.699 0.931-19.008 8.333-31.286 27.12-31.286 47.872v132.928c0 17.674 14.33 32 32 32h224c17.674 0 32-14.326 32-32v-125.062c0-12.842-4.122-24.995-11.325-34.938h70.291c3.29 11.162 5.034 22.902 5.034 34.938v125.062c0 53.021-42.979 96-96 96h-224c-53.018 0-95.999-42.979-95.999-96v-132.928c0-46.163 27.311-87.955 69.592-106.49 28.019-12.278 59.747-13.024 88.31-2.067l15.040 5.766z","M887.997 576h-15.286v25.766c0 17.674-14.326 32-32 32h-72.714v64h72.714c53.021 0 96-42.979 96-96v-39.027c-14.278 8.426-30.931 13.261-48.714 13.261z","M281.182 416c53.020 0 95.999-42.979 95.999-96 0-53.019-42.979-96-95.999-96s-96 42.981-96 96c0 53.021 42.98 96 96 96zM281.182 352c-17.673 0-32-14.326-32-32s14.327-32 32-32c17.673 0 32 14.327 32 32s-14.327 32-32 32z","M625.267 640h-0.8c0.262 0.598 0.528 1.194 0.8 1.786v-1.786z","M357.59 464.582c2.531-1.107 5.091-2.122 7.674-3.043-17.907-5.155-37.011-4.832-54.823 1.018l-14.183 4.659c-14.887 4.893-30.998 4.554-45.668-0.954l-4.554-1.709c-21.067-7.91-44.203-8.394-65.581-1.37-40.565 13.325-67.986 51.197-67.986 93.894v44.688c0 53.021 42.98 96 96 96h79.53v-64h-79.53c-17.673 0-32-14.326-32-32v-44.688c0-15.050 9.664-28.394 23.96-33.091 7.534-2.474 15.688-2.304 23.112 0.483l4.554 1.709c21.238 7.974 44.041 10.333 66.238 7.027 10.398-30.173 32.992-55.357 63.258-68.624z","M423.914 640h0.797c-0.259 0.598-0.525 1.194-0.797 1.786v-1.786z"],"attrs":[{"fill":"rgb(108, 114, 122)"},{"fill":"rgb(108, 114, 122)"},{"fill":"rgb(108, 114, 122)"},{"fill":"rgb(108, 114, 122)"},{"fill":"rgb(108, 114, 122)"},{"fill":"rgb(108, 114, 122)"},{"fill":"rgb(108, 114, 122)"},{"fill":"rgb(108, 114, 122)"}],"isMulticolor":false,"isMulticolor2":false,"grid":0,"tags":["teams-private"],"colorPermutations":{"1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101":[{"f":2},{"f":2},{"f":2},{"f":2},{"f":2},{"f":2},{"f":2},{"f":2}]}},"attrs":[{"fill":"rgb(108, 114, 122)"},{"fill":"rgb(108, 114, 122)"},{"fill":"rgb(108, 114, 122)"},{"fill":"rgb(108, 114, 122)"},{"fill":"rgb(108, 114, 122)"},{"fill":"rgb(108, 114, 122)"},{"fill":"rgb(108, 114, 122)"},{"fill":"rgb(108, 114, 122)"}],"properties":{"order":252,"id":198,"name":"teams-private","prevSize":32,"code":59750},"setIdx":0,"setId":4,"iconIdx":7},{"icon":{"paths":["M368 182.4c0-17.673-14.326-32-32-32s-32 14.327-32 32v144h-144c-17.673 0-32 14.326-32 32s14.327 32 32 32h144v288h-144c-17.673 0-32 14.326-32 32s14.327 32 32 32h144v144c0 17.674 14.327 32 32 32s32-14.326 32-32v-144h288v144c0 17.674 14.326 32 32 32s32-14.326 32-32v-144h144c17.674 0 32-14.326 32-32s-14.326-32-32-32h-144v-80h-64v80h-288v-288h80v-64h-80v-144z","M640.515 327.283c-15.328-3.59-31.306-3.338-46.512 0.733-41.763 11.187-70.803 49.030-70.803 92.269v35.539c0 36.003 29.187 65.194 65.194 65.194h210.413c36.006 0 65.194-29.19 65.194-65.194v-28.336c0-47.667-33.040-88.957-79.27-99.792-16.413-3.846-33.613-3.603-49.946 0.771l-25.827 6.918c-10.381 2.781-21.286 2.95-31.747 0.499l-36.694-8.602z","M782.637 217.037c0 49.174-39.862 89.037-89.037 89.037s-89.037-39.863-89.037-89.037c0-49.174 39.862-89.037 89.037-89.037s89.037 39.863 89.037 89.037z"],"attrs":[{"fill":"rgb(108, 114, 122)"},{"fill":"rgb(108, 114, 122)"},{"fill":"rgb(108, 114, 122)"}],"isMulticolor":false,"isMulticolor2":false,"colorPermutations":{"1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101":[{"f":2},{"f":2},{"f":2}]},"tags":["channel-auto-join"],"grid":0},"attrs":[{"fill":"rgb(108, 114, 122)"},{"fill":"rgb(108, 114, 122)"},{"fill":"rgb(108, 114, 122)"}],"properties":{"order":2,"id":0,"name":"channel-auto-join","prevSize":32,"code":59746},"setIdx":0,"setId":4,"iconIdx":8},{"icon":{"paths":["M236.709 162.578c11.779-5.037 25.426-2.564 34.688 6.286l150.709 144c6.32 6.037 9.894 14.396 9.894 23.136s-3.574 17.101-9.894 23.136l-150.709 144c-9.262 8.851-22.909 11.325-34.688 6.288s-19.419-16.614-19.419-29.424v-112h-73.29c-17.673 0-32-14.326-32-32 0-17.672 14.327-31.999 32-31.999h73.29v-112c0-12.81 7.64-24.386 19.419-29.423zM320 368.179v-0.179h0.189l-0.189 0.179zM320.189 304.001h-0.189v-0.179l0.189 0.179z","M492.899 303.258c8.762-9.388 21.245-15.258 35.101-15.258 26.509 0 48 21.49 48 48s-21.491 48-48 48c-16.582 0-31.203-8.41-39.824-21.197l-43.098 48.483c20.49 22.554 50.051 36.714 82.922 36.714 61.856 0 112-50.144 112-112s-50.144-112-112-112c-25.549 0-49.098 8.554-67.942 22.955l32.842 56.303z","M145.615 483.229l32.174 64.349c-0.861 3.040-1.318 6.23-1.318 9.501v44.688c0 17.674 14.327 32 32 32h79.53v64h-79.53c-53.019 0-96-42.979-96-96v-44.688c0-28.848 12.515-55.488 33.144-73.85z","M625.267 640h-0.797c0.259 0.598 0.525 1.194 0.797 1.786v-1.786z","M424.714 640h-0.797v1.786c0.272-0.592 0.538-1.187 0.797-1.786z","M477.901 494.515c-28.56-10.957-60.291-10.211-88.307 2.067-42.282 18.534-69.594 60.326-69.594 106.49v132.928c0 53.021 42.979 96 96 96h224c53.021 0 96-42.979 96-96v-125.062c0-51.162-31.536-97.034-79.306-115.354-30.352-11.642-64.067-10.851-93.84 2.198l-2.070 0.909c-21.523 9.434-45.901 10.006-67.843 1.59l-15.040-5.766zM415.286 555.2c12.595-5.52 26.858-5.856 39.699-0.931l15.040 5.77c37.664 14.445 79.504 13.462 116.451-2.73l2.070-0.909c14.349-6.288 30.602-6.669 45.229-1.059 23.024 8.829 38.224 30.938 38.224 55.597v125.062c0 17.674-14.326 32-32 32h-224c-17.674 0-32-14.326-32-32v-132.928c0-20.752 12.278-39.539 31.286-47.872z","M864 320c0-53.019-42.979-96-96-96s-96 42.981-96 96c0 53.021 42.979 96 96 96s96-42.979 96-96zM800 320c0 17.674-14.326 32-32 32s-32-14.326-32-32c0-17.673 14.326-32 32-32s32 14.327 32 32z","M840.714 697.766h-72.714v-64h72.714c17.674 0 32-14.326 32-32v-44.688c0-15.050-9.664-28.394-23.958-33.091-7.536-2.474-15.69-2.304-23.114 0.483l-4.554 1.709c-19.77 7.421-40.89 9.978-61.619 7.632-12.438-31.683-37.722-57.549-70.774-70.227-1.754-0.672-3.52-1.302-5.296-1.894 18.058-5.312 37.36-5.040 55.344 0.867l14.182 4.659c14.886 4.893 30.998 4.554 45.667-0.954l4.554-1.709c21.069-7.91 44.205-8.394 65.581-1.37 40.566 13.325 67.987 51.197 67.987 93.894v44.688c0 53.021-42.982 96-96 96z"],"attrs":[{"fill":"rgb(108, 114, 122)"},{"fill":"rgb(108, 114, 122)"},{"fill":"rgb(108, 114, 122)"},{"fill":"rgb(108, 114, 122)"},{"fill":"rgb(108, 114, 122)"},{"fill":"rgb(108, 114, 122)"},{"fill":"rgb(108, 114, 122)"},{"fill":"rgb(108, 114, 122)"}],"isMulticolor":false,"isMulticolor2":false,"colorPermutations":{"1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101":[{"f":2},{"f":2},{"f":2},{"f":2},{"f":2},{"f":2},{"f":2},{"f":2}]},"tags":["channel-move-to-team"],"grid":0},"attrs":[{"fill":"rgb(108, 114, 122)"},{"fill":"rgb(108, 114, 122)"},{"fill":"rgb(108, 114, 122)"},{"fill":"rgb(108, 114, 122)"},{"fill":"rgb(108, 114, 122)"},{"fill":"rgb(108, 114, 122)"},{"fill":"rgb(108, 114, 122)"},{"fill":"rgb(108, 114, 122)"}],"properties":{"order":3,"id":1,"name":"channel-move-to-team","prevSize":32,"code":59747},"setIdx":0,"setId":4,"iconIdx":9},{"icon":{"paths":["M512 128c-97.203 0-176 78.798-176 176v142.477h-16c-53.020 0-96 42.979-96 96v257.523c0 53.021 42.981 96 96 96h384c53.021 0 96-42.979 96-96v-257.523c0-53.021-42.979-96-96-96h-16v-142.477c0-97.202-78.797-176-176-176zM624 304v142.477h-224v-142.477c0-61.856 50.144-112 112-112s112 50.144 112 112z"],"attrs":[{"fill":"rgb(108, 114, 122)"}],"isMulticolor":false,"isMulticolor2":false,"colorPermutations":{"1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101":[{"f":2}]},"tags":["lock-filled"],"grid":0},"attrs":[{"fill":"rgb(108, 114, 122)"}],"properties":{"order":4,"id":2,"name":"lock-filled","prevSize":32,"code":59748},"setIdx":0,"setId":4,"iconIdx":10},{"icon":{"paths":["M336 304c0-97.202 78.797-176 176-176s176 78.798 176 176v142.477h16c53.021 0 96 42.979 96 96v257.523c0 53.021-42.979 96-96 96h-384c-53.019 0-96-42.979-96-96v-257.523c0-53.021 42.981-96 96-96h16v-142.477zM400 446.477h224v-142.477c0-61.856-50.144-112-112-112s-112 50.144-112 112v142.477zM320 510.477c-17.673 0-32 14.326-32 32v257.523c0 17.674 14.327 32 32 32h384c17.674 0 32-14.326 32-32v-257.523c0-17.674-14.326-32-32-32h-384z"],"attrs":[{"fill":"rgb(108, 114, 122)"}],"isMulticolor":false,"isMulticolor2":false,"colorPermutations":{"1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101":[{"f":2}]},"tags":["locker"],"grid":0},"attrs":[{"fill":"rgb(108, 114, 122)"}],"properties":{"order":5,"id":3,"name":"locker","prevSize":32,"code":59749},"setIdx":0,"setId":4,"iconIdx":11},{"icon":{"paths":["M281.184 336c17.673 0 32-14.326 32-32s-14.327-32-32-32c-17.673 0-32 14.327-32 32s14.327 32 32 32zM281.184 400c-53.020 0-96-42.979-96-96 0-53.019 42.98-96 96-96 53.018 0 96 42.981 96 96 0 53.021-42.982 96-96 96zM576 320c0-26.51-21.491-48-48-48s-48 21.49-48 48c0 26.509 21.491 48 48 48s48-21.491 48-48zM640 320c0 61.856-50.144 112-112 112s-112-50.144-112-112c0-61.856 50.144-112 112-112s112 50.144 112 112zM477.901 478.515c-28.56-10.957-60.291-10.211-88.307 2.067-42.282 18.534-69.594 60.326-69.594 106.49v132.928c0 53.021 42.979 96 96 96h224c53.021 0 96-42.979 96-96v-125.062c0-51.162-31.536-97.034-79.306-115.354-30.352-11.642-64.067-10.851-93.84 2.198l-2.070 0.909c-21.523 9.434-45.901 10.006-67.843 1.59l-15.040-5.766zM415.286 539.2c12.595-5.52 26.858-5.856 39.699-0.931l15.040 5.77c37.664 14.445 79.504 13.462 116.451-2.73l2.070-0.909c14.349-6.288 30.602-6.669 45.229-1.059 23.024 8.829 38.224 30.938 38.224 55.597v125.062c0 17.674-14.326 32-32 32h-224c-17.674 0-32-14.326-32-32v-132.928c0-20.752 12.278-39.539 31.286-47.872zM768 336c-17.674 0-32-14.326-32-32s14.326-32 32-32c17.674 0 32 14.327 32 32s-14.326 32-32 32zM768 400c53.021 0 96-42.979 96-96 0-53.019-42.979-96-96-96s-96 42.981-96 96c0 53.021 42.979 96 96 96zM840.714 681.766h-72.714v-64h72.714c17.674 0 32-14.326 32-32v-44.688c0-15.050-9.664-28.394-23.958-33.091-7.536-2.474-15.69-2.304-23.114 0.483l-4.554 1.709c-19.77 7.421-40.89 9.978-61.619 7.632-12.438-31.683-37.722-57.549-70.774-70.227-1.754-0.672-3.52-1.302-5.296-1.894 18.058-5.312 37.36-5.040 55.344 0.867l14.182 4.659c14.886 4.893 30.998 4.554 45.667-0.954l4.554-1.709c21.069-7.91 44.205-8.394 65.581-1.37 40.566 13.325 67.987 51.197 67.987 93.894v44.688c0 53.021-42.982 96-96 96zM625.267 624h-0.797c0.259 0.598 0.525 1.194 0.797 1.786v-1.786zM357.594 448.582c2.528-1.107 5.088-2.122 7.674-3.043-17.907-5.155-37.014-4.832-54.824 1.018l-14.183 4.659c-14.887 4.893-30.998 4.554-45.668-0.954l-4.554-1.709c-21.067-7.91-44.203-8.394-65.581-1.37-40.565 13.325-67.986 51.197-67.986 93.894v44.688c0 53.021 42.981 96 96 96h79.53v-64h-79.53c-17.673 0-32-14.326-32-32v-44.688c0-15.050 9.664-28.394 23.96-33.091 7.534-2.474 15.688-2.304 23.112 0.483l4.554 1.709c21.238 7.974 44.041 10.333 66.238 7.027 10.398-30.173 32.994-55.357 63.259-68.624zM423.917 624h0.797c-0.259 0.598-0.525 1.194-0.797 1.786v-1.786z"],"attrs":[{"fill":"rgb(108, 114, 122)"}],"isMulticolor":false,"isMulticolor2":false,"colorPermutations":{"1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101":[{"f":2}]},"tags":["teams"],"grid":0},"attrs":[{"fill":"rgb(108, 114, 122)"}],"properties":{"order":7,"id":5,"name":"teams","prevSize":32,"code":59751},"setIdx":0,"setId":4,"iconIdx":12},{"icon":{"paths":["M502.627 142.69c12.915-3.408 26.509-3.289 39.363 0.345l245.194 69.317c29.165 8.245 51.254 33.818 53.504 65.14 24.506 341.145-184.394 520.422-272.611 580.214-33.053 22.403-75.28 22.474-108.416 0.259-88.758-59.504-300.1-238.589-276.51-579.794 2.205-31.89 24.96-57.772 54.753-65.633l264.724-69.849zM524.579 204.621c-1.837-0.519-3.779-0.536-5.622-0.049l-264.725 69.849c-4.4 1.161-6.999 4.775-7.233 8.165-21.305 308.166 168.214 468.531 248.301 522.218 11.507 7.715 25.434 7.677 36.87-0.077 79.379-53.798 266.835-214.288 244.685-522.649-0.243-3.358-2.787-6.925-7.082-8.139l-245.194-69.317z"],"attrs":[{"fill":"rgb(108, 114, 122)"}],"isMulticolor":false,"isMulticolor2":false,"colorPermutations":{"1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101":[{"f":2}]},"tags":["set-as-moderator"],"grid":0},"attrs":[{"fill":"rgb(108, 114, 122)"}],"properties":{"order":245,"id":6,"name":"shield","prevSize":32,"code":59661},"setIdx":0,"setId":4,"iconIdx":13},{"icon":{"paths":["M864 512c0-86.89-31.482-166.429-83.664-227.826l-496.162 496.162c61.397 52.182 140.937 83.664 227.826 83.664 194.403 0 352-157.597 352-352zM239.349 734.65l495.3-495.3c-60.662-49.597-138.182-79.349-222.65-79.349-194.404 0-352 157.596-352 352 0 84.467 29.753 161.987 79.349 222.65zM928 512c0 229.75-186.25 416-416 416s-416-186.25-416-416c0-229.75 186.25-416 416-416s416 186.25 416 416z"],"attrs":[{"fill":"rgb(108, 114, 122)"}],"isMulticolor":false,"isMulticolor2":false,"colorPermutations":{"1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101":[{"f":2}]},"tags":["ignore"],"grid":0},"attrs":[{"fill":"rgb(108, 114, 122)"}],"properties":{"order":246,"id":7,"name":"ignore","prevSize":32,"code":59740},"setIdx":0,"setId":4,"iconIdx":14},{"icon":{"paths":["M819.2 204.8v614.4h-614.4v-614.4h614.4zM204.8 128c-42.415 0-76.8 34.385-76.8 76.8v614.4c0 42.416 34.385 76.8 76.8 76.8h614.4c42.416 0 76.8-34.384 76.8-76.8v-614.4c0-42.415-34.384-76.8-76.8-76.8h-614.4z"],"attrs":[{"fill":"rgb(203, 206, 209)"}],"isMulticolor":false,"isMulticolor2":false,"colorPermutations":{"1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101":[{"f":8}]},"tags":["checkbox-unchecked"],"grid":0},"attrs":[{"fill":"rgb(203, 206, 209)"}],"properties":{"order":242,"id":8,"name":"checkbox-unchecked","prevSize":32,"code":59648},"setIdx":0,"setId":4,"iconIdx":15},{"icon":{"paths":["M204.8 128h614.4c42.416 0 76.8 34.385 76.8 76.8v614.4c0 42.416-34.384 76.8-76.8 76.8h-614.4c-42.415 0-76.8-34.384-76.8-76.8v-614.4c0-42.415 34.385-76.8 76.8-76.8zM769.062 336.88c9.322-9.424 9.238-24.619-0.182-33.941-9.424-9.322-24.621-9.241-33.942 0.182l-339.085 342.745-106.782-108.051c-9.317-9.43-24.513-9.52-33.94-0.202s-9.518 24.512-0.201 33.939l123.842 125.318c4.509 4.56 10.653 7.126 17.066 7.13s12.557-2.563 17.069-7.12l356.157-360z"],"attrs":[{"fill":"rgb(29, 116, 245)"}],"isMulticolor":false,"isMulticolor2":false,"colorPermutations":{"1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101":[{"f":5}]},"tags":["checkbox-checked"],"grid":0},"attrs":[{"fill":"rgb(29, 116, 245)"}],"properties":{"order":243,"id":9,"name":"checkbox-checked","prevSize":32,"code":59649},"setIdx":0,"setId":4,"iconIdx":16},{"icon":{"paths":["M862.454 324.045c-35.2-60.31-82.944-108.057-143.248-143.253-60.314-35.198-126.16-52.792-197.581-52.792-71.414 0-137.28 17.6-197.581 52.792-60.31 35.194-108.053 82.943-143.253 143.253-35.194 60.307-52.792 126.166-52.792 197.571 0 85.77 25.024 162.899 75.086 231.405 50.056 68.509 114.721 115.917 193.989 142.224 9.229 1.712 16.058 0.509 20.499-3.584 4.442-4.096 6.662-9.226 6.662-15.37 0-1.024-0.090-10.246-0.259-27.674-0.176-17.43-0.259-32.637-0.259-45.61l-11.789 2.038c-7.517 1.379-16.998 1.962-28.445 1.795-11.443-0.16-23.322-1.357-35.619-3.587-12.304-2.211-23.747-7.334-34.342-15.366-10.588-8.029-18.104-18.538-22.547-31.514l-5.125-11.795c-3.416-7.853-8.795-16.573-16.142-26.134-7.348-9.571-14.778-16.058-22.294-19.475l-3.588-2.57c-2.391-1.706-4.61-3.766-6.662-6.154-2.050-2.39-3.585-4.781-4.61-7.174-1.027-2.397-0.176-4.365 2.562-5.907 2.738-1.539 7.685-2.288 14.864-2.288l10.247 1.53c6.834 1.37 15.287 5.462 25.371 12.298 10.078 6.835 18.363 15.715 24.856 26.646 7.863 14.013 17.335 24.688 28.445 32.038 11.101 7.347 22.294 11.014 33.568 11.014s21.011-0.854 29.216-2.557c8.192-1.709 15.882-4.275 23.062-7.69 3.075-22.902 11.446-40.499 25.11-52.797-19.475-2.045-36.982-5.13-52.534-9.226-15.542-4.106-31.603-10.765-48.173-20-16.579-9.222-30.331-20.672-41.262-34.333-10.932-13.67-19.905-31.613-26.904-53.818-7.003-22.214-10.505-47.837-10.505-76.88 0-41.35 13.5-76.541 40.493-105.584-12.645-31.088-11.451-65.939 3.585-104.55 9.908-3.079 24.606-0.768 44.078 6.916 19.478 7.689 33.738 14.275 42.797 19.736 9.059 5.46 16.317 10.087 21.786 13.837 31.782-8.88 64.582-13.322 98.406-13.322s66.63 4.442 98.416 13.322l19.475-12.295c13.318-8.204 29.046-15.722 47.142-22.556 18.112-6.83 31.958-8.712 41.53-5.633 15.37 38.612 16.739 73.46 4.093 104.548 26.992 29.046 40.496 64.243 40.496 105.587 0 29.043-3.514 54.746-10.506 77.13-7.002 22.387-16.051 40.314-27.152 53.818-11.114 13.501-24.957 24.864-41.526 34.083-16.573 9.226-32.637 15.888-48.179 19.99-15.552 4.102-33.059 7.187-52.534 9.238 17.763 15.37 26.646 39.632 26.646 72.774v108.134c0 6.141 2.134 11.27 6.41 15.37 4.272 4.090 11.018 5.296 20.243 3.581 79.28-26.304 143.946-73.712 194-142.224 50.048-68.502 75.082-145.632 75.082-231.405-0.019-71.395-17.626-137.248-52.803-197.555z"],"attrs":[{"fill":"rgb(108, 114, 122)"}],"isMulticolor":false,"isMulticolor2":false,"colorPermutations":{"1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101":[{"f":2}]},"tags":["github-monochromatic"],"grid":0},"attrs":[{"fill":"rgb(108, 114, 122)"}],"properties":{"order":152,"id":10,"name":"github-monochromatic","prevSize":32,"code":59650},"setIdx":0,"setId":4,"iconIdx":17},{"icon":{"paths":["M133.618 423.61h215.092l-92.537-284.607c-4.74-14.67-25.504-14.67-30.244 0l-92.311 284.607zM86.899 567.171l46.72-143.546h737.133l46.72 143.546c4.288 13.088-0.451 27.533-11.51 35.434l-403.776 293.408-403.776-293.408c-11.060-7.901-15.799-22.346-11.511-35.434zM655.661 423.61h215.091l-92.31-284.607c-4.739-14.67-25.504-14.67-30.243 0l-92.538 284.607z"],"attrs":[{"fill":"rgb(108, 114, 122)"}],"isMulticolor":false,"isMulticolor2":false,"colorPermutations":{"1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101":[{"f":2}]},"tags":["gitlab-monochromatic"],"grid":0},"attrs":[{"fill":"rgb(108, 114, 122)"}],"properties":{"order":153,"id":11,"name":"gitlab-monochromatic","prevSize":32,"code":59651},"setIdx":0,"setId":4,"iconIdx":18},{"icon":{"paths":["M658.794 338.154c-39.798-38.052-90.416-57.426-146.794-57.426-100.016 0-184.669 67.548-214.865 158.313l-0.002-0.003c-7.679 23.040-12.042 47.648-12.042 72.957s4.364 49.92 12.044 72.96l0 0.003c30.196 90.765 114.849 158.314 214.865 158.314 51.664 0 95.651-13.613 130.035-36.653v-0.016c40.669-27.229 67.725-67.898 76.627-115.898h-206.662v-148.538h361.658c4.538 25.136 6.982 51.315 6.982 78.547 0 116.944-41.891 215.389-114.502 282.24v0.013c-63.533 58.646-150.458 93.030-254.138 93.030-150.109 0-279.971-86.048-343.156-211.549l-0-0.003c-26.007-51.84-40.844-110.49-40.844-172.451 0-61.965 14.836-120.611 40.844-172.451h0.004c63.187-125.495 193.047-211.542 343.153-211.542 103.504 0 190.429 38.051 256.931 100.014l-110.138 110.139z"],"width":1056,"attrs":[{"fill":"rgb(108, 114, 122)"}],"isMulticolor":false,"isMulticolor2":false,"colorPermutations":{"1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101":[{"f":2}]},"tags":["google-monochromatic"],"grid":0},"attrs":[{"fill":"rgb(108, 114, 122)"}],"properties":{"order":154,"id":12,"name":"google-monochromatic","prevSize":32,"code":59652},"setIdx":0,"setId":4,"iconIdx":19},{"icon":{"paths":["M840.541 128c31.318 0 56.813 24.79 56.813 55.383v657.212c0 30.592-25.494 55.427-56.813 55.427h-654.546c-31.254 0-56.642-24.835-56.642-55.427v-657.212c0-30.593 25.387-55.383 56.642-55.383h654.546zM300.196 233.75c-36.588 0-66.093 29.59-66.093 66.050 0 36.482 29.505 66.072 66.093 66.072 36.437 0 66.005-29.59 66.005-66.072 0-36.46-29.568-66.050-66.005-66.050zM243.148 782.461h114.029v-366.515h-114.029v366.515zM537.814 415.923h-109.187v366.515h113.773v-181.274c0-47.83 9.046-94.147 68.333-94.147 58.454 0 59.181 54.678 59.181 97.174v178.246h113.901v-201.008c0-98.714-21.312-174.598-136.666-174.598-55.402 0-92.566 30.381-107.757 59.203h-1.578v-50.112z"],"width":1056,"attrs":[{"fill":"rgb(108, 114, 122)"}],"isMulticolor":false,"isMulticolor2":false,"colorPermutations":{"1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101":[{"f":2}]},"tags":["linkedin-monochromatic"],"grid":0},"attrs":[{"fill":"rgb(108, 114, 122)"}],"properties":{"order":155,"id":13,"name":"linkedin-monochromatic","prevSize":32,"code":59653},"setIdx":0,"setId":4,"iconIdx":20},{"icon":{"paths":["M86.686 85.336l730.792 774.088c0 0 24.899 17.558 43.936-2.928 19.040-20.486 4.394-40.973 4.394-40.973l-779.122-730.187zM318.080 158.503l556.516 599.955c0 0 24.896 17.558 43.936-2.928 19.037-20.486 4.394-40.973 4.394-40.973l-604.845-556.054zM712.035 915.030l-556.517-599.955 604.843 556.054c0 0 14.646 20.486-4.39 40.973-19.040 20.486-43.936 2.928-43.936 2.928zM513.693 221.419l388.803 419.15c0 0 17.395 12.269 30.694-2.042 13.302-14.314 3.069-28.627 3.069-28.627l-422.566-388.482zM597.878 915.677l-388.805-419.152 422.568 388.48c0 0 10.234 14.314-3.069 28.627-13.302 14.31-30.694 2.045-30.694 2.045zM713.498 312.143l176.221 190.551c0 0 8.605 5.747 15.184-0.96 6.579-6.704 1.517-13.411 1.517-13.411l-192.922-176.18zM482.582 880.23l-176.219-190.55 192.923 176.179c0 0 5.059 6.707-1.52 13.414-6.579 6.704-15.184 0.957-15.184 0.957z"],"width":1056,"attrs":[{"fill":"rgb(108, 114, 122)"}],"isMulticolor":false,"isMulticolor2":false,"colorPermutations":{"1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101":[{"f":2}]},"tags":["meteor-monochromatic"],"grid":0},"attrs":[{"fill":"rgb(108, 114, 122)"}],"properties":{"order":156,"id":14,"name":"meteor-monochromatic","prevSize":32,"code":59654},"setIdx":0,"setId":4,"iconIdx":21},{"icon":{"paths":["M378.861 853.331c317.456 0 491.091-262.662 491.091-490.442 0-7.462 0-14.89-0.506-22.282 33.779-24.401 62.938-54.614 86.112-89.224-31.501 13.94-64.918 23.082-99.136 27.12 36.032-21.542 62.998-55.424 75.882-95.34-33.878 20.078-70.944 34.228-109.597 41.839-53.501-56.814-138.515-70.72-207.37-33.919-68.851 36.801-104.426 115.155-86.768 191.127-138.774-6.947-268.074-72.409-355.715-180.093-45.811 78.76-22.412 179.517 53.436 230.1-27.467-0.813-54.335-8.214-78.337-21.574 0 0.704 0 1.443 0 2.182 0.022 82.051 57.937 152.723 138.47 168.97-25.41 6.922-52.071 7.933-77.933 2.957 22.611 70.218 87.409 118.32 161.25 119.706-61.116 47.968-136.616 74.010-214.35 73.933-13.732-0.026-27.452-0.858-41.087-2.486 78.931 50.586 170.772 77.418 264.557 77.293z"],"width":1056,"attrs":[{"fill":"rgb(108, 114, 122)"}],"isMulticolor":false,"isMulticolor2":false,"colorPermutations":{"1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101":[{"f":2}]},"tags":["twitter-monochromatic"],"grid":0},"attrs":[{"fill":"rgb(108, 114, 122)"}],"properties":{"order":157,"id":15,"name":"twitter-monochromatic","prevSize":32,"code":59655},"setIdx":0,"setId":4,"iconIdx":22},{"icon":{"paths":["M512 928c-14.218 0-28.275-0.714-42.144-2.112-16.694-1.68-30.557-12.528-36.688-27.395l-40.538-98.304-98.162 40.838c-14.837 6.173-32.3 4.048-45.292-6.554-21.838-17.818-41.831-37.811-59.65-59.648-10.601-12.992-12.726-30.454-6.553-45.293l40.839-98.16-98.307-40.541c-14.864-6.131-25.714-19.994-27.395-36.688-1.396-13.869-2.111-27.926-2.111-42.144 0-14.214 0.714-28.275 2.11-42.141 1.681-16.694 12.531-30.557 27.395-36.688l98.308-40.541-40.84-98.163c-6.173-14.837-4.047-32.3 6.553-45.292 17.818-21.838 37.81-41.83 59.648-59.648 12.992-10.6 30.455-12.726 45.292-6.553l98.165 40.84 40.541-98.309c6.128-14.865 19.994-25.714 36.688-27.395 13.866-1.396 27.926-2.11 42.141-2.11s28.275 0.714 42.141 2.11c16.694 1.681 30.56 12.531 36.688 27.395l40.541 98.309 98.163-40.84c14.838-6.173 32.301-4.047 45.293 6.553 21.837 17.818 41.83 37.81 59.648 59.648 10.602 12.992 12.726 30.455 6.554 45.292l-40.842 98.163 98.31 40.541c14.864 6.131 25.712 19.994 27.392 36.688 1.398 13.866 2.112 27.926 2.112 42.141 0 14.218-0.714 28.275-2.112 42.144-1.68 16.694-12.528 30.557-27.395 36.688l-98.307 40.541 40.842 98.16c6.173 14.838 4.045 32.301-6.554 45.293-17.821 21.837-37.811 41.83-59.651 59.648-12.992 10.602-30.454 12.726-45.29 6.554l-98.163-40.838-40.538 98.304c-6.131 14.867-19.994 25.715-36.688 27.395-13.869 1.398-27.93 2.112-42.144 2.112zM444.451 757.984l43.386 105.2c7.981 0.541 16.038 0.816 24.163 0.816s16.182-0.275 24.163-0.816l43.382-105.2c9.456-22.925 35.731-33.808 58.627-24.285l105.056 43.709c12.157-10.602 23.578-22.022 34.179-34.179l-43.709-105.056c-9.526-22.893 1.36-49.171 24.282-58.624l105.203-43.386c0.541-7.978 0.816-16.038 0.816-24.163s-0.275-16.182-0.816-24.16l-105.203-43.386c-22.922-9.453-33.808-35.731-24.282-58.624l43.709-105.060c-10.602-12.156-22.022-23.577-34.176-34.177l-105.059 43.708c-22.896 9.525-49.171-1.359-58.627-24.283l-43.382-105.204c-7.981-0.54-16.038-0.815-24.163-0.815s-16.182 0.275-24.163 0.815l-43.386 105.204c-9.453 22.924-35.728 33.808-58.624 24.283l-105.058-43.708c-12.156 10.6-23.577 22.021-34.177 34.177l43.708 105.059c9.525 22.893-1.359 49.171-24.284 58.624l-105.203 43.386c-0.54 7.978-0.815 16.035-0.815 24.16 0 8.128 0.275 16.186 0.815 24.163l105.203 43.386c22.924 9.453 33.809 35.731 24.284 58.624l-43.708 105.056c10.601 12.157 22.022 23.578 34.178 34.179l105.056-43.709c22.896-9.523 49.171 1.36 58.624 24.285zM416 512c0-53.021 42.979-96 96-96s96 42.979 96 96c0 53.021-42.979 96-96 96s-96-42.979-96-96zM512 352c-88.365 0-160 71.635-160 160s71.635 160 160 160c88.365 0 160-71.635 160-160s-71.635-160-160-160z"],"attrs":[{"fill":"rgb(108, 114, 122)"}],"isMulticolor":false,"isMulticolor2":false,"colorPermutations":{"1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101":[{"f":2}]},"tags":["administration"],"grid":0},"attrs":[{"fill":"rgb(108, 114, 122)"}],"properties":{"order":159,"id":16,"name":"administration","prevSize":32,"code":59657},"setIdx":0,"setId":4,"iconIdx":23},{"icon":{"paths":["M878.739 567.622c-41.853-41.136-161.235-29.824-220.925-22.282-59.005-35.997-98.458-85.706-126.243-158.73 13.379-55.194 34.646-139.185 18.525-191.98-14.41-89.82-129.674-80.907-146.141-20.227-15.094 55.195-1.373 131.988 24.013 230.034-34.304 81.936-85.421 191.984-121.441 255.062-68.611 35.312-161.235 89.821-174.957 158.384-11.321 54.166 89.194 189.238 261.063-106.96 76.845-25.37 160.547-56.566 234.65-68.909 64.835 34.97 140.65 58.282 191.421 58.282 87.478 0 96.054-96.678 60.035-132.675zM199.152 834.342c17.496-46.97 84.048-101.133 104.288-119.99-65.18 103.875-104.288 122.39-104.288 119.99zM479.082 180.918c25.386 0 22.986 110.047 6.176 139.873-15.094-47.653-14.752-139.873-6.176-139.873zM395.379 649.216c33.274-57.936 61.747-126.845 84.733-187.526 28.474 51.766 64.838 93.251 103.258 121.706-71.354 14.739-133.446 44.909-187.99 65.821zM846.835 632.074c0 0-17.152 20.57-127.958-26.739 120.413-8.912 140.307 18.512 127.958 26.739z"],"width":1056,"attrs":[{"fill":"rgb(108, 114, 122)"}],"isMulticolor":false,"isMulticolor2":false,"colorPermutations":{"1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101":[{"f":2}]},"tags":["adobe-reader-monochromatic"],"grid":0},"attrs":[{"fill":"rgb(108, 114, 122)"}],"properties":{"order":161,"id":17,"name":"adobe-reader-monochromatic","prevSize":32,"code":59658},"setIdx":0,"setId":4,"iconIdx":24},{"icon":{"paths":["M448 188.865c0 33.615 27.251 60.865 60.864 60.865 33.616 0 60.867-27.25 60.867-60.865s-27.251-60.865-60.867-60.865c-33.613 0-60.864 27.25-60.864 60.865zM384 188.865c0 59.026 40.957 108.485 96 121.512v77.745c-44.314 11.69-78.986 47.13-89.578 91.878h-80.045c-13.027-55.043-62.486-96-121.512-96-68.961 0-124.865 55.904-124.865 124.864 0 68.963 55.904 124.867 124.865 124.867 56.762 0 104.678-37.875 119.854-89.731h83.361c12.227 41.773 45.696 74.47 87.92 85.61v77.744c-55.043 13.027-96 62.486-96 121.51 0 68.963 55.904 124.867 124.864 124.867 68.963 0 124.867-55.904 124.867-124.867 0-56.762-37.875-104.675-89.731-119.853v-79.437c42.163-11.171 75.578-43.846 87.789-85.574h77.222c15.178 51.856 63.091 89.731 119.853 89.731 68.963 0 124.867-55.904 124.867-124.867 0-68.96-55.904-124.864-124.867-124.864-59.024 0-108.483 40.957-121.51 96h-73.907c-10.579-44.707-45.194-80.122-89.446-91.843v-79.438c51.856-15.176 89.731-63.092 89.731-119.854 0-68.961-55.904-124.865-124.867-124.865-68.96 0-124.864 55.904-124.864 124.865zM828.864 569.731c-33.613 0-60.864-27.251-60.864-60.867 0-33.613 27.251-60.864 60.864-60.864 33.616 0 60.867 27.251 60.867 60.864 0 33.616-27.251 60.867-60.867 60.867zM188.865 569.731c-33.615 0-60.865-27.251-60.865-60.867 0-33.613 27.25-60.864 60.865-60.864s60.865 27.251 60.865 60.864c0 33.616-27.25 60.867-60.865 60.867zM451.069 508.864c0 33.616 27.251 60.867 60.867 60.867 33.613 0 60.864-27.251 60.864-60.867 0-33.613-27.251-60.864-60.864-60.864-33.616 0-60.867 27.251-60.867 60.864zM508.864 889.731c-33.613 0-60.864-27.251-60.864-60.867 0-33.613 27.251-60.864 60.864-60.864 33.616 0 60.867 27.251 60.867 60.864 0 33.616-27.251 60.867-60.867 60.867z"],"attrs":[{"fill":"rgb(108, 114, 122)"}],"isMulticolor":false,"isMulticolor2":false,"colorPermutations":{"1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101":[{"f":2}]},"tags":["all-contacts-in-channels"],"grid":0},"attrs":[{"fill":"rgb(108, 114, 122)"}],"properties":{"order":162,"id":18,"name":"all-contacts-in-channels","prevSize":32,"code":59659},"setIdx":0,"setId":4,"iconIdx":25},{"icon":{"paths":["M861.437 160c17.67 0 32 14.327 32 32s-14.33 32-32 32h-384c-17.674 0-32-14.327-32-32s14.326-32 32-32h384zM334.717 442.397c-24.962 0-45.197-20.237-45.197-45.2 0-24.96 20.236-45.197 45.197-45.197 24.963 0 45.2 20.237 45.2 45.197 0 24.963-20.237 45.2-45.2 45.2zM334.717 506.397c-60.308 0-109.197-48.89-109.197-109.2 0-60.307 48.89-109.197 109.197-109.197 60.31 0 109.2 48.89 109.2 109.197 0 60.31-48.89 109.2-109.2 109.2zM456.278 535.504c-18.288-4.899-37.504-5.2-55.939-0.88l-45.005 10.547c-13.194 3.091-26.947 2.877-40.036-0.63l-31.676-8.483c-19.669-5.27-40.384-5.562-60.154-0.928-55.68 13.050-95.468 62.781-95.468 120.179v34.752c0 42.906 34.783 77.686 77.689 77.686h258.057c42.906 0 77.69-34.781 77.69-77.686v-43.587c0-52-34.928-97.514-85.158-110.97zM414.944 596.934c8.166-1.914 16.675-1.779 24.774 0.39 22.246 5.958 37.718 26.118 37.718 49.149v43.587c0 7.558-6.131 13.686-13.69 13.686h-258.057c-7.56 0-13.689-6.128-13.689-13.686v-34.752c0-27.469 19.123-51.552 46.072-57.869 9.556-2.24 19.564-2.086 28.99 0.438l31.676 8.483c23.277 6.237 47.738 6.621 71.2 1.12l45.005-10.547zM893.437 512c0-17.674-14.33-32-32-32h-224c-17.674 0-32 14.326-32 32s14.326 32 32 32h224c17.67 0 32-14.326 32-32zM861.437 640c17.67 0 32 14.326 32 32s-14.33 32-32 32h-192c-17.674 0-32-14.326-32-32s14.326-32 32-32h192zM893.437 352c0-17.674-14.33-32-32-32h-288c-17.674 0-32 14.326-32 32s14.326 32 32 32h288c17.67 0 32-14.326 32-32zM861.437 800c17.67 0 32 14.326 32 32s-14.33 32-32 32h-256c-17.674 0-32-14.326-32-32s14.326-32 32-32h256z"],"attrs":[{"fill":"rgb(108, 114, 122)"}],"isMulticolor":false,"isMulticolor2":false,"colorPermutations":{"1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101":[{"f":2}]},"tags":["all-contacts-in-queue"],"grid":0},"attrs":[{"fill":"rgb(108, 114, 122)"}],"properties":{"order":163,"id":19,"name":"all-contacts-in-queue","prevSize":32,"code":59660},"setIdx":0,"setId":4,"iconIdx":26},{"icon":{"paths":["M523.571 295.385c33.133 0 74.669-22.133 99.402-51.645 22.4-26.745 38.733-64.095 38.733-101.445 0-5.072-0.467-10.144-1.402-14.294-36.864 1.383-81.2 24.439-107.798 55.334-21.002 23.517-40.134 60.406-40.134 98.218 0 5.533 0.934 11.067 1.402 12.911 2.333 0.461 6.067 0.922 9.798 0.922zM406.906 853.334c45.267 0 65.334-29.974 121.798-29.974 57.402 0 70 29.050 120.4 29.050 49.469 0 82.602-45.187 113.869-89.456 34.998-50.72 49.466-100.522 50.4-102.829-3.267-0.922-98-39.194-98-146.634 0-93.146 74.666-135.107 78.867-138.333-49.469-70.090-124.602-71.935-145.136-71.935-55.533 0-100.8 33.199-129.264 33.199-30.8 0-71.402-31.354-119.469-31.354-91.466 0-184.333 74.701-184.333 215.802 0 87.61 34.533 180.294 77 240.24 36.402 50.723 68.133 92.224 113.867 92.224z"],"width":1056,"attrs":[{"fill":"rgb(108, 114, 122)"}],"isMulticolor":false,"isMulticolor2":false,"colorPermutations":{"1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101":[{"f":2}]},"tags":["apple-monochromatic"],"grid":0},"attrs":[{"fill":"rgb(108, 114, 122)"}],"properties":{"order":236,"id":20,"name":"apple-monochromatic","prevSize":32,"code":59662},"setIdx":0,"setId":4,"iconIdx":27},{"icon":{"paths":["M498.694 141.561l-298.666 136.533c-11.39 5.207-18.696 16.58-18.696 29.103v386.844c0 11.818 6.513 22.675 16.941 28.237l298.667 159.286c9.411 5.021 20.707 5.021 30.118 0l298.666-159.286c10.429-5.562 16.941-16.419 16.941-28.237v-386.844c0-12.524-7.306-23.896-18.694-29.103l-298.666-136.533c-8.451-3.862-18.16-3.862-26.611 0zM245.333 357.011l234.667 107.277v335.709l-234.667-125.155v-317.83zM544 799.997l234.666-125.155v-317.83l-234.666 107.277v335.709zM512 408.544l-221.7-101.347 221.7-101.348 221.699 101.348-221.699 101.347z"],"attrs":[{"fill":"rgb(108, 114, 122)"}],"isMulticolor":false,"isMulticolor2":false,"colorPermutations":{"1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101":[{"f":2}]},"tags":["apps"],"grid":0},"attrs":[{"fill":"rgb(108, 114, 122)"}],"properties":{"order":166,"id":21,"name":"apps","prevSize":32,"code":59663},"setIdx":0,"setId":4,"iconIdx":28},{"icon":{"paths":["M374.627 297.372c-12.496-12.497-32.758-12.497-45.254 0l-192 192c-12.497 12.496-12.497 32.758 0 45.254l192 192c12.496 12.496 32.758 12.496 45.254 0s12.496-32.758 0-45.254l-137.372-137.373h578.745v128c0 17.674 14.326 32 32 32s32-14.326 32-32v-160c0-17.674-14.326-32-32-32h-610.745l137.372-137.373c12.496-12.496 12.496-32.758 0-45.255z"],"attrs":[{"fill":"rgb(108, 114, 122)"}],"isMulticolor":false,"isMulticolor2":false,"colorPermutations":{"1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101":[{"f":2}]},"tags":["arrow-back"],"grid":0},"attrs":[{"fill":"rgb(108, 114, 122)"}],"properties":{"order":167,"id":22,"name":"arrow-back","prevSize":32,"code":59664},"setIdx":0,"setId":4,"iconIdx":29},{"icon":{"paths":["M551.392 242.502c0.189-17.672 14.669-31.845 32.339-31.656 17.674 0.189 31.846 14.668 31.658 32.34l-1.318 123.489 193.44-193.439c12.496-12.497 32.755-12.497 45.254 0 12.496 12.497 12.496 32.758 0 45.255l-193.44 193.439 123.488-1.318c17.674-0.189 32.154 13.984 32.339 31.654 0.189 17.674-13.984 32.154-31.654 32.342l-201.92 2.154c-8.605 0.093-16.886-3.283-22.97-9.37-6.086-6.083-9.462-14.365-9.373-22.97l2.157-201.92zM475.981 782.147c-0.189 17.674-14.669 31.846-32.339 31.658-17.674-0.189-31.846-14.669-31.658-32.342l1.318-123.488-193.438 193.44c-12.497 12.496-32.758 12.496-45.255 0s-12.497-32.758 0-45.254l193.438-193.44-123.488 1.318c-17.672 0.189-32.151-13.984-32.34-31.654-0.189-17.674 13.984-32.154 31.656-32.342l201.922-2.154c8.605-0.093 16.883 3.283 22.966 9.37 6.086 6.083 9.462 14.365 9.373 22.97l-2.157 201.92z"],"width":1056,"attrs":[{"fill":"rgb(108, 114, 122)"}],"isMulticolor":false,"isMulticolor2":false,"colorPermutations":{"1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101":[{"f":2}]},"tags":["arrow-collapse"],"grid":0},"attrs":[{"fill":"rgb(108, 114, 122)"}],"properties":{"order":168,"id":23,"name":"arrow-collapse","prevSize":32,"code":59665},"setIdx":0,"setId":4,"iconIdx":30},{"icon":{"paths":["M576 672c-17.674 0-32 14.326-32 32s14.326 32 32 32h224c17.674 0 32-14.326 32-32v-208c0-17.674-14.326-32-32-32s-32 14.326-32 32v130.746l-233.373-233.373c-12.496-12.496-32.758-12.496-45.254 0l-73.373 73.373-169.372-169.373c-12.497-12.497-32.758-12.497-45.255 0s-12.497 32.759 0 45.255l192 192c12.496 12.496 32.758 12.496 45.254 0l73.373-73.373 210.746 210.746h-146.746z"],"attrs":[{"fill":"rgb(108, 114, 122)"}],"isMulticolor":false,"isMulticolor2":false,"colorPermutations":{"1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101":[{"f":2}]},"tags":["arrow-decrease"],"grid":0},"attrs":[{"fill":"rgb(108, 114, 122)"}],"properties":{"order":169,"id":24,"name":"arrow-decrease","prevSize":32,"code":59666},"setIdx":0,"setId":4,"iconIdx":31},{"icon":{"paths":["M329.373 550.627c-12.497-12.496-12.497-32.758 0-45.254s32.758-12.496 45.254 0l105.373 105.373v-418.746c0-17.673 14.326-32 32-32s32 14.327 32 32v418.746l105.373-105.373c12.496-12.496 32.758-12.496 45.254 0s12.496 32.758 0 45.254l-160 160c-12.496 12.496-32.758 12.496-45.254 0l-160-160zM112 864c0 17.674 14.327 32 32 32h768c17.674 0 32-14.326 32-32v-512c0-17.674-14.326-32-32-32h-96c-17.674 0-32 14.326-32 32s14.326 32 32 32h64v448h-704v-448h64c17.673 0 32-14.326 32-32s-14.327-32-32-32h-96c-17.673 0-32 14.326-32 32v512z"],"attrs":[{"fill":"rgb(108, 114, 122)"}],"isMulticolor":false,"isMulticolor2":false,"colorPermutations":{"1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101":[{"f":2}]},"tags":["arrow-down-box"],"grid":0},"attrs":[{"fill":"rgb(108, 114, 122)"}],"properties":{"order":170,"id":25,"name":"arrow-down-box","prevSize":32,"code":59667},"setIdx":0,"setId":4,"iconIdx":32},{"icon":{"paths":["M865.139 512c0-194.404-157.802-352-352.464-352-194.66 0-352.464 157.596-352.464 352s157.804 352 352.464 352c194.662 0 352.464-157.597 352.464-352zM929.226 512c0 229.75-186.496 416-416.55 416-230.053 0-416.548-186.25-416.548-416s186.495-416 416.548-416c230.054 0 416.55 186.25 416.55 416zM695.178 571.37l-160.182 155.715c-12.438 12.093-32.259 12.093-44.701 0l-160.179-155.715c-12.682-12.33-12.955-32.589-0.611-45.251 12.342-12.666 32.63-12.938 45.309-0.611l105.789 102.842 0.003-276.349c0-17.674 14.346-32 32.042-32s32.042 14.326 32.042 32v276.349l105.789-102.842c12.682-12.326 32.966-12.054 45.309 0.611 12.346 12.662 12.070 32.922-0.608 45.251z"],"width":1056,"attrs":[{"fill":"rgb(108, 114, 122)"}],"isMulticolor":false,"isMulticolor2":false,"colorPermutations":{"1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101":[{"f":2}]},"tags":["arrow-down-circle"],"grid":0},"attrs":[{"fill":"rgb(108, 114, 122)"}],"properties":{"order":171,"id":26,"name":"arrow-down-circle","prevSize":32,"code":59668},"setIdx":0,"setId":4,"iconIdx":33},{"icon":{"paths":["M726.88 526.064c12.355 12.637 12.128 32.896-0.509 45.254l-191.968 187.715c-12.438 12.163-32.31 12.163-44.746 0l-191.97-187.715c-12.636-12.358-12.863-32.618-0.507-45.254 12.356-12.634 32.615-12.861 45.252-0.506l137.597 134.55v-372.109c0-17.673 14.33-32 32-32 17.674 0 32 14.327 32 32v372.109l137.6-134.55c12.634-12.355 32.896-12.128 45.251 0.506z"],"attrs":[{"fill":"rgb(108, 114, 122)"}],"isMulticolor":false,"isMulticolor2":false,"colorPermutations":{"1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101":[{"f":2}]},"tags":["arrow-down"],"grid":0},"attrs":[{"fill":"rgb(108, 114, 122)"}],"properties":{"order":172,"id":27,"name":"arrow-down","prevSize":32,"code":59669},"setIdx":0,"setId":4,"iconIdx":34},{"icon":{"paths":["M859.978 398.15c-0.189 17.67-14.666 31.843-32.339 31.654-17.67-0.189-31.846-14.666-31.658-32.339l1.318-123.488-193.437 193.437c-12.496 12.499-32.758 12.499-45.254 0-12.496-12.496-12.496-32.755 0-45.254l193.437-193.437-123.488 1.318c-17.67 0.189-32.15-13.984-32.339-31.656s13.984-32.151 31.658-32.34l201.92-2.156c8.605-0.092 16.883 3.286 22.97 9.371 6.083 6.085 9.462 14.364 9.37 22.969l-2.157 201.922zM167.394 626.522c0.189-17.674 14.668-31.846 32.34-31.658s31.845 14.669 31.657 32.339l-1.319 123.488 193.438-193.437c12.496-12.499 32.758-12.499 45.254 0 12.496 12.496 12.496 32.758 0 45.254l-193.438 193.437 123.489-1.318c17.67-0.189 32.15 13.984 32.339 31.658 0.189 17.67-13.984 32.15-31.658 32.339l-201.92 2.157c-8.605 0.093-16.884-3.286-22.969-9.37-6.085-6.086-9.463-14.365-9.371-22.97l2.156-201.92z"],"width":1056,"attrs":[{"fill":"rgb(108, 114, 122)"}],"isMulticolor":false,"isMulticolor2":false,"colorPermutations":{"1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101":[{"f":2}]},"tags":["arrow-expand"],"grid":0},"attrs":[{"fill":"rgb(108, 114, 122)"}],"properties":{"order":173,"id":28,"name":"arrow-expand","prevSize":32,"code":59670},"setIdx":0,"setId":4,"iconIdx":35},{"icon":{"paths":["M576 352c-17.674 0-32-14.326-32-32s14.326-32 32-32h224c17.674 0 32 14.327 32 32v208c0 17.674-14.326 32-32 32s-32-14.326-32-32v-130.746l-233.373 233.373c-12.496 12.496-32.758 12.496-45.254 0l-73.373-73.373-169.372 169.373c-12.497 12.496-32.758 12.496-45.255 0s-12.497-32.758 0-45.254l192-192c12.496-12.496 32.758-12.496 45.254 0l73.373 73.373 210.746-210.746h-146.746z"],"attrs":[{"fill":"rgb(108, 114, 122)"}],"isMulticolor":false,"isMulticolor2":false,"colorPermutations":{"1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101":[{"f":2}]},"tags":["arrow-increase"],"grid":0},"attrs":[{"fill":"rgb(108, 114, 122)"}],"properties":{"order":174,"id":29,"name":"arrow-increase","prevSize":32,"code":59671},"setIdx":0,"setId":4,"iconIdx":36},{"icon":{"paths":["M297.766 105.372c12.513-12.497 32.801-12.497 45.316 0 12.512 12.497 12.512 32.758 0 45.255l-105.513 105.372h611.551c17.699 0 32.045 14.327 32.045 32v112c0 17.674-14.346 32-32.045 32-17.696 0-32.042-14.326-32.042-32v-80h-579.51l105.513 105.373c12.512 12.496 12.512 32.758 0 45.254-12.515 12.496-32.803 12.496-45.316 0l-160.212-160c-12.513-12.497-12.513-32.758 0-45.255l160.212-160zM711.568 918.627c-12.515 12.496-32.803 12.496-45.315 0-12.515-12.496-12.515-32.758 0-45.254l105.51-105.373h-611.552c-17.696 0-32.042-14.326-32.042-32v-112c0-17.674 14.346-32 32.042-32s32.042 14.326 32.042 32v80h579.509l-105.51-105.373c-12.515-12.496-12.515-32.758 0-45.254 12.512-12.496 32.8-12.496 45.315 0l160.211 160c12.512 12.496 12.512 32.758 0 45.254l-160.211 160z"],"width":1056,"attrs":[{"fill":"rgb(108, 114, 122)"}],"isMulticolor":false,"isMulticolor2":false,"colorPermutations":{"1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101":[{"f":2}]},"tags":["arrow-looping"],"grid":0},"attrs":[{"fill":"rgb(108, 114, 122)"}],"properties":{"order":175,"id":30,"name":"arrow-looping","prevSize":32,"code":59672},"setIdx":0,"setId":4,"iconIdx":37},{"icon":{"paths":["M374.627 769.296c-12.496 12.496-32.758 12.496-45.254 0l-192-192c-12.497-12.496-12.497-32.758 0-45.254l192-192c12.496-12.499 32.758-12.499 45.254 0 12.496 12.496 12.496 32.758 0 45.254l-137.372 137.373h578.745v-192h-192c-17.674 0-32-14.328-32-32.001s14.326-32 32-32h224c17.674 0 32 14.327 32 32v256.001c0 17.674-14.326 32-32 32h-610.745l137.372 137.373c12.496 12.496 12.496 32.758 0 45.254z"],"attrs":[{"fill":"rgb(108, 114, 122)"}],"isMulticolor":false,"isMulticolor2":false,"colorPermutations":{"1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101":[{"f":2}]},"tags":["arrow-return"],"grid":0},"attrs":[{"fill":"rgb(108, 114, 122)"}],"properties":{"order":176,"id":31,"name":"arrow-return","prevSize":32,"code":59673},"setIdx":0,"setId":4,"iconIdx":38},{"icon":{"paths":["M694.627 473.373c12.496 12.496 12.496 32.758 0 45.254s-32.758 12.496-45.254 0l-105.373-105.373v418.746c0 17.674-14.326 32-32 32s-32-14.326-32-32v-418.746l-105.373 105.373c-12.496 12.496-32.758 12.496-45.254 0s-12.497-32.758 0-45.254l160-160c12.496-12.497 32.758-12.497 45.254 0l160 160zM912 160c0-17.673-14.326-32-32-32h-768c-17.673 0-32 14.327-32 32v512c0 17.674 14.327 32 32 32h96c17.673 0 32-14.326 32-32s-14.327-32-32-32h-64v-448h704v448h-64c-17.674 0-32 14.326-32 32s14.326 32 32 32h96c17.674 0 32-14.326 32-32v-512z"],"attrs":[{"fill":"rgb(108, 114, 122)"}],"isMulticolor":false,"isMulticolor2":false,"colorPermutations":{"1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101":[{"f":2}]},"tags":["arrow-up-box"],"grid":0},"attrs":[{"fill":"rgb(108, 114, 122)"}],"properties":{"order":177,"id":32,"name":"arrow-up-box","prevSize":32,"code":59674},"setIdx":0,"setId":4,"iconIdx":39},{"icon":{"paths":["M297.181 498.090c-12.356-12.634-12.129-32.896 0.507-45.251l191.97-187.717c12.435-12.161 32.307-12.161 44.746 0l191.968 187.717c12.637 12.355 12.864 32.618 0.509 45.251-12.355 12.637-32.618 12.864-45.251 0.509l-137.6-134.55v372.109c0 17.674-14.326 32-32 32-17.67 0-32-14.326-32-32v-372.109l-137.597 134.55c-12.637 12.355-32.895 12.128-45.251-0.509z"],"attrs":[{"fill":"rgb(108, 114, 122)"}],"isMulticolor":false,"isMulticolor2":false,"colorPermutations":{"1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101":[{"f":2}]},"tags":["arrow-up"],"grid":0},"attrs":[{"fill":"rgb(108, 114, 122)"}],"properties":{"order":178,"id":33,"name":"arrow-up","prevSize":32,"code":59675},"setIdx":0,"setId":4,"iconIdx":40},{"icon":{"paths":["M866.128 153.127c-12.496-12.497-32.758-12.497-45.254 0l-668.246 668.245c-12.497 12.496-12.497 32.758 0 45.254s32.758 12.496 45.255 0l668.246-668.245c12.496-12.497 12.496-32.758 0-45.255zM674.643 480.358l55.92-55.923c16.259 79.565 14.515 161.866-5.229 240.842l-11.622 46.49c-4.288 17.146-21.661 27.571-38.806 23.283-17.146-4.285-27.568-21.661-23.283-38.806l11.622-46.486c13.875-55.507 17.677-112.874 11.398-169.398zM817.162 385.923l-11.312-36.771 51.203-51.206 21.28 69.155c33.344 108.368 31.994 224.445-3.859 332.010l-33.45 100.342c-5.587 16.765-23.709 25.827-40.477 20.24-16.765-5.59-25.827-23.712-20.237-40.48l33.446-100.339c31.635-94.912 32.826-197.334 3.405-292.95zM490.666 664.333l64-64v210.346c0 23.562-19.101 42.666-42.666 42.666h-85.334c-35.459 0-68.394-10.816-95.683-29.325l46.611-46.611c14.701 7.629 31.395 11.936 49.072 11.936h64v-125.011zM128 682.678c0 19.738 13.403 36.346 31.604 41.216l62.552-62.55h-30.156v-298.666h118.99l12.367-48.049c11.843-46.020 53.696-79.953 103.309-79.953h64v158.155l64-64v-115.488c0-23.564-19.101-42.667-42.666-42.667h-85.334c-79.523 0-146.343 54.39-165.289 128h-90.71c-23.564 0-42.667 19.102-42.667 42.667v341.334z"],"attrs":[{"fill":"rgb(108, 114, 122)"}],"isMulticolor":false,"isMulticolor2":false,"colorPermutations":{"1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101":[{"f":2}]},"tags":["audio-disabled"],"grid":0},"attrs":[{"fill":"rgb(108, 114, 122)"}],"properties":{"order":180,"id":35,"name":"audio-disabled","prevSize":32,"code":59677},"setIdx":0,"setId":4,"iconIdx":41},{"icon":{"paths":["M310.99 661.344h-118.99v-298.666h118.99l12.367-48.049c11.843-46.020 53.696-79.953 103.309-79.953h64v554.667h-64c-49.613 0-91.466-33.933-103.309-79.952l-12.367-48.048zM170.667 725.344h90.71c18.946 73.61 85.766 128 165.289 128h85.334c23.565 0 42.666-19.104 42.666-42.666v-597.335c0-23.564-19.101-42.667-42.666-42.667h-85.334c-79.523 0-146.343 54.39-165.289 128h-90.71c-23.564 0-42.667 19.102-42.667 42.667v341.334c0 23.562 19.103 42.666 42.667 42.666zM886.627 393.373c12.496 12.496 12.496 32.758 0 45.254l-73.373 73.373 73.373 73.373c12.496 12.496 12.496 32.758 0 45.254s-32.758 12.496-45.254 0l-73.373-73.373-73.373 73.373c-12.496 12.496-32.758 12.496-45.254 0s-12.496-32.758 0-45.254l73.373-73.373-73.373-73.373c-12.496-12.496-12.496-32.758 0-45.254s32.758-12.496 45.254 0l73.373 73.373 73.373-73.373c12.496-12.496 32.758-12.496 45.254 0z"],"attrs":[{"fill":"rgb(108, 114, 122)"}],"isMulticolor":false,"isMulticolor2":false,"colorPermutations":{"1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101":[{"f":2}]},"tags":["audio-unavailable"],"grid":0},"attrs":[{"fill":"rgb(108, 114, 122)"}],"properties":{"order":181,"id":36,"name":"audio-unavailable","prevSize":32,"code":59678},"setIdx":0,"setId":4,"iconIdx":42},{"icon":{"paths":["M300.1 631.12h-113.433v-280.89h113.433l12.366-48.048c11.054-42.953 50.123-74.619 96.423-74.619h58.666v526.223h-58.666c-46.301 0-85.37-31.667-96.423-74.621l-12.366-48.045zM163.556 695.12h86.931c18.156 70.541 82.192 122.666 158.403 122.666h81.776c22.582 0 40.89-18.307 40.89-40.89v-572.444c0-22.582-18.307-40.889-40.89-40.889h-81.776c-76.211 0-140.247 52.124-158.403 122.667h-86.931c-22.582 0-40.889 18.306-40.889 40.89v327.11c0 22.582 18.307 40.89 40.889 40.89zM646.461 316.515c17.146-4.286 34.518 6.138 38.806 23.284l11.136 44.55c20.81 83.229 20.81 170.301 0 253.533l-11.136 44.55c-4.288 17.146-21.661 27.568-38.806 23.283-17.146-4.288-27.571-21.661-23.283-38.806l11.136-44.55c18.262-73.040 18.262-149.45 0.003-222.486l-11.139-44.55c-4.288-17.146 6.138-34.522 23.283-38.807zM807.472 235.936c-5.197-16.892-23.104-26.372-39.994-21.174-16.893 5.197-26.371 23.104-21.174 39.996l35.536 115.489c28.112 91.37 26.976 189.242-3.254 279.933l-32.054 96.16c-5.59 16.765 3.472 34.886 20.237 40.477 16.768 5.587 34.89-3.472 40.48-20.24l32.051-96.16c34.448-103.344 35.747-214.87 3.709-318.989l-35.536-115.491z"],"attrs":[{"fill":"rgb(108, 114, 122)"}],"isMulticolor":false,"isMulticolor2":false,"colorPermutations":{"1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101":[{"f":2}]},"tags":["audio"],"grid":0},"attrs":[{"fill":"rgb(108, 114, 122)"}],"properties":{"order":182,"id":37,"name":"audio","prevSize":32,"code":59679},"setIdx":0,"setId":4,"iconIdx":43},{"icon":{"paths":["M418.704 128c-35.344 0-64 28.654-64 64v226.637c20.774-2.822 42.134-3.456 64-1.526v-225.11h176v180.707c0 35.344 28.656 64 64 64h176v331.293h-120.554c-17.155 22.179-36.323 43.869-57.312 64h177.866c35.347 0 64-28.653 64-64v-363.293c0-6.413-1.923-12.675-5.526-17.978l-156.63-230.681c-11.914-17.544-31.741-28.049-52.947-28.049h-264.896zM658.704 372.707v-180.707h24.896l122.698 180.707h-147.594zM167.939 668.547c120.524 156.138 218.24 174.586 285.133 158.739 73.293-17.36 139.232-81.648 183.466-151.763-115.155-155.869-211.581-174.589-279.149-158.915-74.362 17.251-142.707 81.683-189.45 151.939zM104.428 649.037c100.621-162.8 337.62-357.19 593.252 1.789 9.072 12.739 10.32 29.818 2.422 43.315-95.27 162.854-326.394 358.346-593.234-0.224-9.745-13.094-11.022-30.995-2.44-44.88zM401.664 735.994c31.514 0 60.269-26.858 60.269-64 0-37.146-28.755-64-60.269-64-31.51 0-60.266 26.854-60.266 64 0 37.142 28.755 64 60.266 64zM401.664 799.994c68.634 0 124.269-57.309 124.269-128 0-70.694-55.635-128-124.269-128-68.63 0-124.267 57.306-124.267 128 0 70.691 55.636 128 124.267 128z"],"width":1056,"attrs":[{"fill":"rgb(108, 114, 122)"}],"isMulticolor":false,"isMulticolor2":false,"colorPermutations":{"1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101":[{"f":2}]},"tags":["auditing"],"grid":0},"attrs":[{"fill":"rgb(108, 114, 122)"}],"properties":{"order":183,"id":38,"name":"auditing","prevSize":32,"code":59680},"setIdx":0,"setId":4,"iconIdx":44},{"icon":{"paths":["M224 224c-17.673 0-32 14.327-32 32v544c0 17.674 14.327 32 32 32h544c17.674 0 32-14.326 32-32v-544c0-17.673-14.326-32-32-32h-544zM128 256c0-53.019 42.981-96 96-96h544c53.021 0 96 42.981 96 96v544c0 53.021-42.979 96-96 96h-544c-53.019 0-96-42.979-96-96v-544zM608 460.813c0 41.798-26.714 77.357-64 90.538v133.462h-64v-133.462c-37.286-13.181-64-48.739-64-90.538 0-53.021 42.979-96 96-96s96 42.979 96 96zM608 588.826c38.861-29.19 64-75.667 64-128.013 0-88.365-71.635-160-160-160s-160 71.636-160 160c0 52.346 25.139 98.822 64 128.013v127.987c0 17.674 14.326 32 32 32h128c17.674 0 32-14.326 32-32v-127.987z"],"attrs":[{"fill":"rgb(108, 114, 122)"}],"isMulticolor":false,"isMulticolor2":false,"colorPermutations":{"1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101":[{"f":2}]},"tags":["auth"],"grid":0},"attrs":[{"fill":"rgb(108, 114, 122)"}],"properties":{"order":184,"id":39,"name":"auth","prevSize":32,"code":59681},"setIdx":0,"setId":4,"iconIdx":45},{"icon":{"paths":["M513.35 554.019c66.467 0 120.349-52.493 120.349-117.242 0-64.752-53.882-117.243-120.349-117.243-66.464 0-120.346 52.491-120.346 117.243 0 64.749 53.882 117.242 120.346 117.242zM513.35 490.019c-32.704 0-56.346-25.405-56.346-53.242 0-27.84 23.642-53.242 56.346-53.242 32.707 0 56.349 25.402 56.349 53.242 0 27.837-23.642 53.242-56.349 53.242z","M365.478 894.144h-172.126c-17.673 0-32-14.33-32-32v-702.144c0-17.673 14.327-32 32-32h640.001c17.674 0 32 14.327 32 32v702.144c0 17.67-14.326 32-32 32h-172.128c-6.24 1.274-12.701 1.942-19.318 1.942h-257.11c-6.618 0-13.078-0.669-19.318-1.942zM225.353 830.144h68.244c-3.114-9.456-4.799-19.558-4.799-30.058v-93.446c0-56.109 37.881-105.142 92.172-119.309 19.171-5.002 39.264-5.312 58.579-0.902l42.778 9.766c20.051 4.579 40.909 4.259 60.81-0.934l28.070-7.325c20.694-5.398 42.384-5.734 63.232-0.973 60.534 13.821 103.469 67.664 103.469 129.754v83.37c0 10.499-1.686 20.602-4.8 30.058h68.246v-638.144h-576.001v638.144zM652.918 830.144c12.246-4.49 20.989-16.253 20.989-30.058v-83.37c0-32.234-22.288-60.186-53.715-67.36-10.822-2.47-22.083-2.298-32.826 0.506l-28.070 7.325c-29.853 7.789-61.139 8.272-91.216 1.405l-42.774-9.766c-9.293-2.122-18.957-1.974-28.176 0.432-26.112 6.813-44.333 30.397-44.333 57.382v93.446c0 13.805 8.742 25.568 20.989 30.058h279.133z"],"width":1056,"attrs":[{"fill":"rgb(108, 114, 122)"},{"fill":"rgb(108, 114, 122)"}],"isMulticolor":false,"isMulticolor2":false,"colorPermutations":{"1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101":[{"f":2},{"f":2}]},"tags":["avatar"],"grid":0},"attrs":[{"fill":"rgb(108, 114, 122)"},{"fill":"rgb(108, 114, 122)"}],"properties":{"order":185,"id":40,"name":"avatar","prevSize":32,"code":59682},"setIdx":0,"setId":4,"iconIdx":46},{"icon":{"paths":["M737.779 361.376c12.499 12.496 12.499 32.758 0 45.254l-110.947 110.95 110.947 110.947c12.499 12.496 12.499 32.758 0 45.254-12.496 12.499-32.758 12.499-45.254 0l-110.947-110.947-110.95 110.947c-12.496 12.499-32.758 12.499-45.254 0-12.496-12.496-12.496-32.758 0-45.254l110.95-110.947-110.95-110.95c-12.496-12.496-12.496-32.758 0-45.254s32.758-12.496 45.254 0l110.95 110.95 110.947-110.95c12.496-12.496 32.758-12.496 45.254 0z","M312.246 218.073c12.061-16.393 31.2-26.073 51.552-26.073h468.202c35.347 0 64 28.654 64 64v512c0 35.347-28.653 64-64 64h-468.202c-20.352 0-39.491-9.68-51.552-26.074l-188.343-256c-16.598-22.56-16.598-53.293 0-75.853l188.343-256.001zM363.798 256l-188.343 256 188.343 256h468.202v-512h-468.202z"],"attrs":[{"fill":"rgb(108, 114, 122)"},{"fill":"rgb(108, 114, 122)"}],"isMulticolor":false,"isMulticolor2":false,"colorPermutations":{"1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101":[{"f":2},{"f":2}]},"tags":["backspace"],"grid":0},"attrs":[{"fill":"rgb(108, 114, 122)"},{"fill":"rgb(108, 114, 122)"}],"properties":{"order":186,"id":41,"name":"backspace","prevSize":32,"code":59683},"setIdx":0,"setId":4,"iconIdx":47},{"icon":{"paths":["M376 775.418c-13.254 0-24-10.746-24-24v-469.168c0-13.255 10.746-24 24-24h145.075c56.118 0 98.269 11.603 126.448 34.809 28.416 23.206 42.624 57.542 42.624 103.008 0 24.154-6.867 45.584-20.602 64.291-13.734 18.47-32.442 32.794-56.122 42.976 27.942 7.814 49.965 22.733 66.067 44.755 16.339 21.786 24.509 47.834 24.509 78.144 0 46.413-15.037 82.88-45.11 109.402s-72.579 39.782-127.517 39.782h-155.373zM420.198 533.526v186.125h112.595c31.731 0 56.714-8.17 74.947-24.509 18.47-16.576 27.706-39.309 27.706-68.198 0-62.278-33.862-93.418-101.587-93.418h-113.661zM420.198 478.829h103.008c29.834 0 53.632-7.459 71.392-22.378 17.997-14.918 26.995-35.165 26.995-60.739 0-28.416-8.288-49.018-24.864-61.805-16.576-13.024-41.795-19.536-75.654-19.536h-100.877v164.458z"],"attrs":[{"fill":"rgb(108, 114, 122)"}],"isMulticolor":false,"isMulticolor2":false,"colorPermutations":{"1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101":[{"f":2}]},"tags":["bold"],"grid":0},"attrs":[{"fill":"rgb(108, 114, 122)"}],"properties":{"order":187,"id":42,"name":"bold","prevSize":32,"code":59684},"setIdx":0,"setId":4,"iconIdx":48},{"icon":{"paths":["M352 445.437c0-17.67 14.326-32 32-32h256c17.674 0 32 14.33 32 32 0 17.674-14.326 32-32 32h-256c-17.674 0-32-14.326-32-32z","M352 322.557c0-17.672 14.326-31.999 32-31.999h256c17.674 0 32 14.327 32 31.999 0 17.674-14.326 32-32 32h-256c-17.674 0-32-14.326-32-32z","M864 679.68c0 17.674-14.326 32-32 32h-18.509c-8.218 40.547-8.218 82.333 0 122.88h19.789c16.966 0 30.72 13.754 30.72 30.72s-13.754 30.72-30.72 30.72h-545.28c-70.692 0-128-55.014-128-122.88v-453.12c0-106.038 85.961-192 192-192h480c17.674 0 32 14.327 32 32v519.68zM748.419 834.56c-6.787-40.678-6.787-82.202 0-122.88h-460.419c-35.346 0-64 27.507-64 61.44s28.654 61.44 64 61.44h460.419zM224 647.68h576v-455.68h-448c-70.692 0-128 57.308-128 128v327.68z"],"attrs":[{"fill":"rgb(108, 114, 122)"},{"fill":"rgb(108, 114, 122)"},{"fill":"rgb(108, 114, 122)"}],"isMulticolor":false,"isMulticolor2":false,"colorPermutations":{"1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101":[{"f":2},{"f":2},{"f":2}]},"tags":["book"],"grid":0},"attrs":[{"fill":"rgb(108, 114, 122)"},{"fill":"rgb(108, 114, 122)"},{"fill":"rgb(108, 114, 122)"}],"properties":{"order":188,"id":43,"name":"book","prevSize":32,"code":59685},"setIdx":0,"setId":4,"iconIdx":49},{"icon":{"paths":["M224 224h320v576h-96v-112c0-8.835-7.165-16-16-16h-96c-8.835 0-16 7.165-16 16v112h-96v-576zM608 448h192v352h-192v-352zM832 384h-224v-192c0-17.673-14.326-32-32-32h-384c-17.673 0-32 14.327-32 32v640c0 17.674 14.327 32 32 32h640c17.674 0 32-14.326 32-32v-416c0-17.674-14.326-32-32-32zM304 288c-8.836 0-16 7.164-16 16v32c0 8.835 7.164 16 16 16h32c8.835 0 16-7.165 16-16v-32c0-8.836-7.165-16-16-16h-32zM288 432c0-8.835 7.164-16 16-16h32c8.835 0 16 7.165 16 16v32c0 8.835-7.165 16-16 16h-32c-8.836 0-16-7.165-16-16v-32zM304 544c-8.836 0-16 7.165-16 16v32c0 8.835 7.164 16 16 16h32c8.835 0 16-7.165 16-16v-32c0-8.835-7.165-16-16-16h-32zM416 304c0-8.836 7.165-16 16-16h32c8.835 0 16 7.164 16 16v32c0 8.835-7.165 16-16 16h-32c-8.835 0-16-7.165-16-16v-32zM432 416c-8.835 0-16 7.165-16 16v32c0 8.835 7.165 16 16 16h32c8.835 0 16-7.165 16-16v-32c0-8.835-7.165-16-16-16h-32zM416 560c0-8.835 7.165-16 16-16h32c8.835 0 16 7.165 16 16v32c0 8.835-7.165 16-16 16h-32c-8.835 0-16-7.165-16-16v-32zM688 512c-8.835 0-16 7.165-16 16v32c0 8.835 7.165 16 16 16h32c8.835 0 16-7.165 16-16v-32c0-8.835-7.165-16-16-16h-32zM672 656c0-8.835 7.165-16 16-16h32c8.835 0 16 7.165 16 16v32c0 8.835-7.165 16-16 16h-32c-8.835 0-16-7.165-16-16v-32z"],"attrs":[{"fill":"rgb(108, 114, 122)"}],"isMulticolor":false,"isMulticolor2":false,"colorPermutations":{"1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101":[{"f":2}]},"tags":["business"],"grid":0},"attrs":[{"fill":"rgb(108, 114, 122)"}],"properties":{"order":189,"id":44,"name":"business","prevSize":32,"code":59686},"setIdx":0,"setId":4,"iconIdx":50},{"icon":{"paths":["M277.831 157.44c0-16.259 13.181-29.44 29.44-29.44s29.439 13.181 29.439 29.44v58.876h353.28v-58.876c0-16.259 13.181-29.44 29.44-29.44s29.44 13.181 29.44 29.44v58.876h85.76c17.674 0 32 14.327 32 32v583.68c0 17.674-14.326 32-32 32h-642.559c-17.673 0-32-14.326-32-32v-583.68c0-1.105 0.056-2.196 0.165-3.272 1.639-16.136 15.266-28.728 31.835-28.728h85.76v-58.876zM802.63 392.957h-578.559v407.040h578.559v-407.040zM425.034 644.474c0-8.835 7.162-16 16-16h26.88c8.835 0 16 7.165 16 16v26.88c0 8.838-7.165 16-16 16h-26.88c-8.838 0-16-7.162-16-16v-26.88zM323.27 510.72c-8.836 0-15.999 7.162-15.999 16v26.88c0 8.835 7.163 16 15.999 16h26.88c8.838 0 16-7.165 16-16v-26.88c0-8.838-7.162-16-16-16h-26.88zM542.79 526.72c0-8.835 7.165-16 16-16h26.88c8.838 0 16 7.165 16 16v26.88c0 8.835-7.162 16-16 16h-26.88c-8.835 0-16-7.165-16-16v-26.88zM323.27 628.474c-8.835 0-15.999 7.165-15.999 16v26.88c0 8.838 7.164 16 15.999 16h26.88c8.838 0 16-7.162 16-16v-26.88c0-8.835-7.162-16-16-16h-26.88zM542.79 644.474c0-8.835 7.165-16 16-16h26.88c8.838 0 16 7.165 16 16v26.88c0 8.838-7.162 16-16 16h-26.88c-8.835 0-16-7.162-16-16v-26.88zM441.030 510.72c-8.835 0-16 7.165-16 16v26.88c0 8.835 7.165 16 16 16h26.88c8.838 0 16-7.165 16-16v-26.88c0-8.835-7.162-16-16-16h-26.88zM660.55 526.72c0-8.835 7.165-16 16-16h26.88c8.838 0 16 7.165 16 16v26.88c0 8.835-7.162 16-16 16h-26.88c-8.835 0-16-7.165-16-16v-26.88zM676.55 628.474c-8.835 0-16 7.165-16 16v26.88c0 8.838 7.165 16 16 16h26.88c8.838 0 16-7.162 16-16v-26.88c0-8.835-7.162-16-16-16h-26.88z"],"width":1056,"attrs":[{"fill":"rgb(108, 114, 122)"}],"isMulticolor":false,"isMulticolor2":false,"colorPermutations":{"1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101":[{"f":2}]},"tags":["calendar"],"grid":0},"attrs":[{"fill":"rgb(108, 114, 122)"}],"properties":{"order":190,"id":45,"name":"calendar","prevSize":32,"code":59687},"setIdx":0,"setId":4,"iconIdx":51},{"icon":{"paths":["M866.128 153.126c-12.496-12.497-32.758-12.497-45.254 0l-668.246 668.246c-12.497 12.496-12.497 32.758 0 45.254s32.758 12.496 45.255 0l668.246-668.246c12.496-12.497 12.496-32.758 0-45.255zM298.667 170.671c-23.564 0-42.667 14.327-42.667 32s19.102 32 42.667 32h255.999c23.565 0 42.669-14.327 42.669-32s-19.104-32-42.669-32h-255.999zM574.173 309.327h-403.506c-41.237 0-74.667 33.428-74.667 74.667v384c0 6.010 0.71 11.856 2.051 17.453l61.949-61.949v-339.504c0-5.891 4.776-10.666 10.667-10.666h339.506l64-64.001zM376.339 778.659h306.326c5.891 0 10.669-4.774 10.669-10.666v-85.334c0-11.373 6.038-21.891 15.859-27.629s21.949-5.83 31.856-0.243l97.83 55.162c2.531 1.427 4.858 3.194 6.912 5.248 6.72 6.72 18.208 1.962 18.208-7.542v-263.322c0-9.501-11.488-14.262-18.208-7.542-2.054 2.054-4.381 3.821-6.912 5.248l-97.83 55.162c-9.907 5.587-22.035 5.494-31.856-0.243-9.821-5.734-15.859-16.256-15.859-27.629v-7.661l110.778-73.498c47.418-41.99 123.888-8.701 123.888 56.163v263.322c0 64.864-76.47 98.154-123.888 56.163l-46.778-26.378v30.554c0 41.238-33.43 74.666-74.669 74.666h-370.327l64.001-64z"],"attrs":[{"fill":"rgb(108, 114, 122)"}],"isMulticolor":false,"isMulticolor2":false,"colorPermutations":{"1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101":[{"f":2}]},"tags":["camera-disabled"],"grid":0},"attrs":[{"fill":"rgb(108, 114, 122)"}],"properties":{"order":191,"id":46,"name":"camera-disabled","prevSize":32,"code":59688},"setIdx":0,"setId":4,"iconIdx":52},{"icon":{"paths":["M288 224c-17.673 0-32 14.327-32 32s14.327 32 32 32h256c17.674 0 32-14.327 32-32s-14.326-32-32-32h-256z","M170.667 341.328c-23.564 0-42.667 19.104-42.667 42.666v384c0 23.565 19.102 42.669 42.667 42.669h511.999c23.565 0 42.669-19.104 42.669-42.669v-128l97.83 97.83c26.877 26.88 72.835 7.843 72.835-30.17v-263.318c0-38.013-45.958-57.050-72.835-30.173l-97.83 97.83v-128c0-23.562-19.104-42.666-42.669-42.666h-511.999z"],"attrs":[{"fill":"rgb(108, 114, 122)"},{"fill":"rgb(108, 114, 122)"}],"isMulticolor":false,"isMulticolor2":false,"colorPermutations":{"1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101":[{"f":2},{"f":2}]},"tags":["camera-filled"],"grid":0},"attrs":[{"fill":"rgb(108, 114, 122)"},{"fill":"rgb(108, 114, 122)"}],"properties":{"order":192,"id":47,"name":"camera-filled","prevSize":32,"code":59689},"setIdx":0,"setId":4,"iconIdx":53},{"icon":{"paths":["M196.731 191.997c0-15.807 12.814-28.622 28.622-28.622h128.001c15.805 0 28.621 12.814 28.621 28.622s-12.816 28.621-28.621 28.621h-128.001c-15.807 0-28.622-12.814-28.622-28.621z","M737.354 559.997c0 97.203-78.8 176-176 176-97.203 0-176-78.797-176-176s78.797-176 176-176c97.2 0 176 78.797 176 176zM673.354 559.997c0-61.856-50.144-112-112-112s-112 50.144-112 112c0 61.856 50.144 112 112 112s112-50.144 112-112z","M193.353 255.997c-35.346 0-64 28.654-64 64v448c0 35.347 28.654 64 64 64h640.001c35.344 0 64-28.653 64-64v-448c0-35.346-28.656-64-64-64h-640.001zM833.354 319.997v448h-640.001v-448h640.001z"],"width":1056,"attrs":[{"fill":"rgb(108, 114, 122)"},{"fill":"rgb(108, 114, 122)"},{"fill":"rgb(108, 114, 122)"}],"isMulticolor":false,"isMulticolor2":false,"colorPermutations":{"1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101":[{"f":2},{"f":2},{"f":2}]},"tags":["camera-photo"],"grid":0},"attrs":[{"fill":"rgb(108, 114, 122)"},{"fill":"rgb(108, 114, 122)"},{"fill":"rgb(108, 114, 122)"}],"properties":{"order":193,"id":48,"name":"camera-photo","prevSize":32,"code":59690},"setIdx":0,"setId":4,"iconIdx":54},{"icon":{"paths":["M298.667 170.672c-23.564 0-42.667 14.327-42.667 32s19.102 32 42.667 32h255.999c23.565 0 42.669-14.327 42.669-32s-19.104-32-42.669-32h-255.999z","M545.712 503.453c11.267-11.267 11.19-29.61-0.17-40.97s-29.706-11.437-40.973-0.17l-71.402 71.402-71.994-71.997c-11.363-11.36-29.706-11.437-40.973-0.17s-11.19 29.61 0.17 40.97l71.997 71.997-71.402 71.402c-11.266 11.267-11.19 29.61 0.17 40.97 11.363 11.36 29.706 11.437 40.973 0.17l71.402-71.402 71.997 71.997c11.36 11.36 29.702 11.437 40.97 0.17s11.19-29.61-0.17-40.97l-71.997-71.997 71.402-71.402z","M96 383.994c0-41.235 33.429-74.666 74.667-74.666h511.999c41.238 0 74.669 33.43 74.669 74.666v30.554l46.778-26.378c47.418-41.99 123.888-8.698 123.888 56.166v263.318c0 64.864-76.47 98.154-123.888 56.163l-46.778-26.374v30.55c0 41.238-33.43 74.669-74.669 74.669h-511.999c-41.237 0-74.667-33.43-74.667-74.669v-384zM170.667 373.328c-5.891 0-10.667 4.774-10.667 10.666v384c0 5.891 4.776 10.669 10.667 10.669h511.999c5.891 0 10.669-4.778 10.669-10.669v-85.331c0-11.376 6.038-21.894 15.859-27.632s21.949-5.83 31.856-0.243l97.83 55.165c2.531 1.427 4.858 3.19 6.912 5.245 6.72 6.72 18.208 1.962 18.208-7.542v-263.318c0-9.504-11.488-14.262-18.208-7.546-2.054 2.058-4.381 3.821-6.912 5.248l-97.83 55.165c-9.907 5.587-22.035 5.494-31.856-0.243s-15.859-16.256-15.859-27.632v-85.334c0-5.891-4.778-10.666-10.669-10.666h-511.999z"],"attrs":[{"fill":"rgb(108, 114, 122)"},{"fill":"rgb(108, 114, 122)"},{"fill":"rgb(108, 114, 122)"}],"isMulticolor":false,"isMulticolor2":false,"colorPermutations":{"1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101":[{"f":2},{"f":2},{"f":2}]},"tags":["camera-unavailable"],"grid":0},"attrs":[{"fill":"rgb(108, 114, 122)"},{"fill":"rgb(108, 114, 122)"},{"fill":"rgb(108, 114, 122)"}],"properties":{"order":194,"id":49,"name":"camera-unavailable","prevSize":32,"code":59691},"setIdx":0,"setId":4,"iconIdx":55},{"icon":{"paths":["M256 202.672c0-17.673 19.102-32 42.667-32h255.999c23.565 0 42.669 14.327 42.669 32s-19.104 32-42.669 32h-255.999c-23.564 0-42.667-14.327-42.667-32zM160 383.994c0-5.891 4.776-10.666 10.667-10.666h511.999c5.891 0 10.669 4.774 10.669 10.666v85.334c0 11.376 6.038 21.894 15.859 27.632s21.949 5.83 31.856 0.243l97.83-55.165c2.531-1.427 4.858-3.19 6.912-5.248 6.72-6.717 18.208-1.958 18.208 7.546v263.318c0 9.504-11.488 14.262-18.208 7.542-2.054-2.054-4.381-3.818-6.912-5.245l-97.83-55.165c-9.907-5.587-22.035-5.494-31.856 0.243s-15.859 16.256-15.859 27.632v85.331c0 5.891-4.778 10.669-10.669 10.669h-511.999c-5.891 0-10.667-4.778-10.667-10.669v-384zM170.667 309.328c-41.237 0-74.667 33.43-74.667 74.666v384c0 41.238 33.429 74.669 74.667 74.669h511.999c41.238 0 74.669-33.43 74.669-74.669v-30.55l46.778 26.374c47.418 41.99 123.888 8.701 123.888-56.163v-263.318c0-64.864-76.47-98.157-123.888-56.166l-46.778 26.378v-30.554c0-41.235-33.43-74.666-74.669-74.666h-511.999z"],"attrs":[{"fill":"rgb(108, 114, 122)"}],"isMulticolor":false,"isMulticolor2":false,"colorPermutations":{"1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101":[{"f":2}]},"tags":["camera"],"grid":0},"attrs":[{"fill":"rgb(108, 114, 122)"}],"properties":{"order":195,"id":50,"name":"camera","prevSize":32,"code":59692},"setIdx":0,"setId":4,"iconIdx":56},{"icon":{"paths":["M224.248 304.026c-49.642 68.947-66.502 152.646-66.502 207.968v1.453l-0.132 1.446c-9.702 106.717 28.59 181.93 90.135 234.006 62.938 53.254 152.152 83.731 244.197 93.958 91.965 10.218 193.27 1.446 273.306-15.482 40.022-8.461 73.616-18.733 97.443-29.075 9.245-4.013 16.438-7.786 21.734-11.126-2.464-1.565-5.446-3.306-9.002-5.197-9.222-4.912-19.779-9.578-30.733-14.368l-1.914-0.835c-9.667-4.221-20.47-8.941-28.656-13.517-14.835-8.298-29.389-17.734-40.006-27.776-5.222-4.941-11.165-11.571-15.146-19.827-4.208-8.742-7.226-21.792-1.306-35.597 31.978-74.621 47.178-115.494 54.538-142.483 6.874-25.2 6.874-37.888 6.874-58.064v-0.182c0-16.035-9.318-89.517-55.811-158.032-45.018-66.338-125.99-129.968-274.854-129.968-134.637 0-215.698 55.382-264.165 122.698zM172.309 266.63c60.333-83.796 160.606-149.302 316.103-149.302 171.136 0 271.494 75.037 327.811 158.032 54.842 80.819 66.854 167.338 66.854 193.968 0 22.384-0.022 41.696-9.126 75.088-8.131 29.805-23.485 70.928-51.946 137.923 5.261 4.166 13.072 9.306 23.36 15.062 5.315 2.97 13.485 6.55 24.963 11.568 10.653 4.659 23.437 10.266 35.174 16.515 11.258 5.994 24.416 14.029 34.202 24.538 10.234 10.992 20.973 29.846 13.299 52.864-5.056 15.168-16.794 25.939-26.576 33.094-10.63 7.776-23.818 14.762-38.253 21.030-29.008 12.589-67.030 23.962-109.683 32.982-85.309 18.038-193.581 27.587-293.616 16.474-99.955-11.107-202.74-44.634-278.468-108.71-76.833-65.011-123.811-159.994-112.66-287.222 0.286-65.549 19.841-162.349 78.561-243.904zM493.744 320c17.674 0 32 14.326 32 32v224c0 17.674-14.326 32-32 32s-32-14.326-32-32v-224c0-17.674 14.326-32 32-32zM525.744 672c0-17.674-14.326-32-32-32s-32 14.326-32 32c0 17.674 14.326 32 32 32s32-14.326 32-32z"],"attrs":[{"fill":"rgb(108, 114, 122)"}],"isMulticolor":false,"isMulticolor2":false,"colorPermutations":{"1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101":[{"f":2}]},"tags":["canned-response"],"grid":0},"attrs":[{"fill":"rgb(108, 114, 122)"}],"properties":{"order":196,"id":51,"name":"canned-response","prevSize":32,"code":59693},"setIdx":0,"setId":4,"iconIdx":57},{"icon":{"paths":["M193.352 181.331c-53.020 0-96 42.981-96 96v469.335c0 53.018 42.98 96 96 96h639.999c53.021 0 96-42.982 96-96v-469.335c0-53.019-42.979-96-96-96h-639.999zM161.352 277.331c0-17.673 14.327-32 32-32h639.999c17.674 0 32 14.327 32 32v202.673h-703.999v-202.673zM161.352 544.003h703.999v202.662c0 17.67-14.326 32-32 32h-639.999c-17.673 0-32-14.33-32-32v-202.662zM812.016 661.331c0-35.347-28.653-64-64-64-35.344 0-64 28.653-64 64s28.656 64 64 64c35.347 0 64-28.653 64-64z"],"width":1056,"attrs":[{"fill":"rgb(108, 114, 122)"}],"isMulticolor":false,"isMulticolor2":false,"colorPermutations":{"1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101":[{"f":2}]},"tags":["card"],"grid":0},"attrs":[{"fill":"rgb(108, 114, 122)"}],"properties":{"order":197,"id":52,"name":"card","prevSize":32,"code":59694},"setIdx":0,"setId":4,"iconIdx":58},{"icon":{"paths":["M368 160c0-17.673-14.326-32-32-32s-32 14.327-32 32v144h-144c-17.673 0-32 14.327-32 32s14.327 32 32 32h144v288h-144c-17.673 0-32 14.326-32 32s14.327 32 32 32h144v144c0 17.674 14.327 32 32 32s32-14.326 32-32v-144h288v144c0 17.674 14.326 32 32 32s32-14.326 32-32v-144h144c17.674 0 32-14.326 32-32s-14.326-32-32-32h-144v-80h-58.666c-1.786 0-3.565-0.042-5.334-0.118v80.118h-288v-288h168.79c-0.522-5.264-0.79-10.602-0.79-16v-48h-168v-144z","M760 96c-57.437 0-104 46.562-104 104v56h-24c-17.674 0-32 14.327-32 32v192c0 17.674 14.326 32 32 32h256c17.674 0 32-14.326 32-32v-192c0-17.673-14.326-32-32-32h-24v-56c0-57.438-46.563-104-104-104zM800 255.238h-80v-55.238c0-22.092 17.907-40 40-40s40 17.908 40 40v55.238z"],"width":1056,"attrs":[{"fill":"rgb(108, 114, 122)"},{"fill":"rgb(108, 114, 122)"}],"isMulticolor":false,"isMulticolor2":false,"colorPermutations":{"1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101":[{"f":2},{"f":2}]},"tags":["channel-private"],"grid":0},"attrs":[{"fill":"rgb(108, 114, 122)"},{"fill":"rgb(108, 114, 122)"}],"properties":{"order":198,"id":53,"name":"channel-private","prevSize":32,"code":59695},"setIdx":0,"setId":4,"iconIdx":59},{"icon":{"paths":["M336 128c17.674 0 32 14.327 32 32v144h288v-144c0-17.673 14.326-32 32-32s32 14.327 32 32v144h144c17.674 0 32 14.327 32 32s-14.326 32-32 32h-144v288h144c17.674 0 32 14.326 32 32s-14.326 32-32 32h-144v144c0 17.674-14.326 32-32 32s-32-14.326-32-32v-144h-288v144c0 17.674-14.326 32-32 32s-32-14.326-32-32v-144h-144c-17.673 0-32-14.326-32-32s14.327-32 32-32h144v-288h-144c-17.673 0-32-14.326-32-32s14.327-32 32-32h144v-144c0-17.673 14.327-32 32-32zM368 368v288h288v-288h-288z"],"attrs":[{"fill":"rgb(108, 114, 122)"}],"isMulticolor":false,"isMulticolor2":false,"colorPermutations":{"1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101":[{"f":2}]},"tags":["channel-public"],"grid":0},"attrs":[{"fill":"rgb(108, 114, 122)"}],"properties":{"order":199,"id":54,"name":"channel-public","prevSize":32,"code":59696},"setIdx":0,"setId":4,"iconIdx":60},{"icon":{"paths":["M886.627 105.372c12.496 12.497 12.496 32.758 0 45.255l-89.373 89.372 89.373 89.373c12.496 12.496 12.496 32.758 0 45.254s-32.758 12.496-45.254 0l-89.373-89.372-89.373 89.372c-12.496 12.496-32.758 12.496-45.254 0s-12.496-32.758 0-45.254l89.373-89.373-89.373-89.372c-12.496-12.497-12.496-32.758 0-45.255s32.758-12.497 45.254 0l89.373 89.373 89.373-89.373c12.496-12.497 32.758-12.497 45.254 0zM226.501 304.042c-49.642 68.947-66.502 152.646-66.502 207.968v1.453l-0.132 1.446c-9.701 106.717 28.59 181.93 90.135 234.006 62.938 53.254 152.151 83.731 244.196 93.958 91.968 10.218 193.27 1.446 273.309-15.482 40.019-8.461 73.613-18.733 97.44-29.075 9.245-4.013 16.438-7.786 21.738-11.126-2.467-1.565-5.446-3.306-9.005-5.2-9.222-4.909-19.776-9.574-30.733-14.365l-1.917-0.838c-9.667-4.221-20.47-8.938-28.653-13.517-14.835-8.294-29.389-17.731-40.003-27.773-5.226-4.941-11.168-11.571-15.146-19.827-4.211-8.742-7.226-21.792-1.309-35.6 42.086-98.205 54.957-137.722 59.219-163.318 2.906-17.434 19.392-29.21 36.822-26.307 17.434 2.906 29.213 19.389 26.307 36.822-5.453 32.749-20.333 76.41-58.006 165.088 5.261 4.166 13.069 9.306 23.357 15.059 5.315 2.973 13.485 6.554 24.963 11.571 10.656 4.656 23.437 10.266 35.174 16.515 11.261 5.994 24.419 14.029 34.202 24.538 10.234 10.992 20.973 29.846 13.299 52.864-5.053 15.168-16.794 25.939-26.576 33.094-10.627 7.776-23.814 14.762-38.253 21.027-29.005 12.592-67.027 23.965-109.68 32.986-85.312 18.038-193.584 27.587-293.616 16.47-99.955-11.104-202.742-44.63-278.471-108.707-76.833-65.014-123.811-159.997-112.66-287.222 0.286-65.549 19.841-162.349 78.561-243.904 60.333-83.796 160.605-149.302 316.103-149.302 17.674 0 32 14.327 32 32s-14.326 32-32 32c-134.637 0-215.697 55.382-264.164 122.698z"],"attrs":[{"fill":"rgb(108, 114, 122)"}],"isMulticolor":false,"isMulticolor2":false,"colorPermutations":{"1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101":[{"f":2}]},"tags":["chat-close"],"grid":0},"attrs":[{"fill":"rgb(108, 114, 122)"}],"properties":{"order":200,"id":55,"name":"chat-close","prevSize":32,"code":59697},"setIdx":0,"setId":4,"iconIdx":61},{"icon":{"paths":["M778 93.833c-11.795-13.162-32.026-14.271-45.187-2.477s-14.272 32.025-2.477 45.187l81.222 90.645h-216.358c-17.674 0-32 14.327-32 32s14.326 32 32 32h216.358l-81.222 90.646c-11.795 13.162-10.685 33.392 2.477 45.187 13.162 11.792 33.392 10.685 45.187-2.477l129.030-144.001c10.893-12.154 10.893-30.556 0-42.71l-129.030-144zM159.999 512.010c0-55.322 16.86-139.021 66.502-207.968 48.467-67.316 129.527-122.698 264.164-122.698 17.674 0 32-14.327 32-32s-14.326-32-32-32c-155.498 0-255.77 65.507-316.102 149.302-58.72 81.555-78.275 178.355-78.561 243.904-11.151 127.229 35.827 222.211 112.66 287.222 75.729 64.077 178.516 97.603 278.471 108.707 100.032 11.117 208.304 1.568 293.616-16.47 42.653-9.021 80.675-20.394 109.68-32.982 14.438-6.269 27.626-13.254 38.253-21.030 9.782-7.155 21.523-17.926 26.579-33.094 7.67-23.018-3.069-41.872-13.302-52.864-9.782-10.509-22.941-18.544-34.202-24.538-11.738-6.25-24.518-11.859-35.171-16.515-11.482-5.018-19.651-8.598-24.966-11.571-10.288-5.754-18.096-10.89-23.357-15.059 37.674-88.678 52.554-132.339 58.010-165.088 2.902-17.43-8.877-33.917-26.31-36.822-17.43-2.902-33.917 8.877-36.822 26.307-4.262 25.597-17.133 65.114-59.219 163.318-5.917 13.808-2.902 26.858 1.309 35.6 3.978 8.256 9.92 14.886 15.146 19.827 10.614 10.042 25.171 19.478 40.003 27.776 8.186 4.576 18.989 9.296 28.656 13.517v0l1.914 0.835c10.957 4.79 21.51 9.456 30.733 14.368 3.558 1.891 6.541 3.632 9.005 5.197-5.299 3.341-12.493 7.114-21.738 11.126-23.827 10.342-57.421 20.614-97.44 29.075-80.038 16.928-181.341 25.699-273.309 15.482-92.045-10.227-181.258-40.704-244.196-93.958-61.545-52.077-99.836-127.29-90.135-234.006l0.132-1.446v-1.453z"],"attrs":[{"fill":"rgb(108, 114, 122)"}],"isMulticolor":false,"isMulticolor2":false,"colorPermutations":{"1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101":[{"f":2}]},"tags":["chat-forward"],"grid":0},"attrs":[{"fill":"rgb(108, 114, 122)"}],"properties":{"order":201,"id":56,"name":"chat-forward","prevSize":32,"code":59698},"setIdx":0,"setId":4,"iconIdx":62},{"icon":{"paths":["M854.506 233.252c12.563 12.429 12.672 32.691 0.243 45.254l-474.877 480c-6.013 6.077-14.208 9.498-22.755 9.494-8.55-0.003-16.742-3.427-22.752-9.507l-165.126-167.088c-12.423-12.57-12.303-32.832 0.268-45.254s32.831-12.304 45.254 0.269l142.375 144.067 452.115-456.992c12.429-12.564 32.691-12.672 45.254-0.243z"],"attrs":[{"fill":"rgb(108, 114, 122)"}],"isMulticolor":false,"isMulticolor2":false,"colorPermutations":{"1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101":[{"f":2}]},"tags":["check"],"grid":0},"attrs":[{"fill":"rgb(108, 114, 122)"}],"properties":{"order":202,"id":57,"name":"check","prevSize":32,"code":59699},"setIdx":0,"setId":4,"iconIdx":63},{"icon":{"paths":["M281.372 436.042c12.497-12.499 32.758-12.499 45.255 0l185.373 185.373 185.373-185.373c12.496-12.499 32.758-12.499 45.254 0 12.496 12.496 12.496 32.758 0 45.254l-208 208c-12.496 12.496-32.758 12.496-45.254 0l-208-208c-12.497-12.496-12.497-32.758 0-45.254z"],"attrs":[{"fill":"rgb(108, 114, 122)"}],"isMulticolor":false,"isMulticolor2":false,"colorPermutations":{"1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101":[{"f":2}]},"tags":["chevron-down"],"grid":0},"attrs":[{"fill":"rgb(108, 114, 122)"}],"properties":{"order":203,"id":58,"name":"chevron-down","prevSize":32,"code":59700},"setIdx":0,"setId":4,"iconIdx":64},{"icon":{"paths":["M670.17 183.165c16.662 16.662 16.662 43.677 0 60.34l-268.496 268.495 268.496 268.499c16.662 16.662 16.662 43.677 0 60.339s-43.677 16.662-60.339 0l-298.668-298.669c-8.002-8-12.497-18.851-12.497-30.17 0-11.315 4.495-22.166 12.497-30.17l298.668-298.666c16.662-16.662 43.677-16.662 60.339 0z"],"attrs":[{"fill":"rgb(108, 114, 122)"}],"isMulticolor":false,"isMulticolor2":false,"colorPermutations":{"1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101":[{"f":2}]},"tags":["chevron-left-big"],"grid":0},"attrs":[{"fill":"rgb(108, 114, 122)"}],"properties":{"order":204,"id":59,"name":"chevron-left-big","prevSize":32,"code":59701},"setIdx":0,"setId":4,"iconIdx":65},{"icon":{"paths":["M587.962 281.372c12.496 12.497 12.496 32.758 0 45.255l-185.373 185.373 185.373 185.373c12.496 12.496 12.496 32.758 0 45.254-12.499 12.496-32.758 12.496-45.258 0l-208-208c-12.496-12.496-12.496-32.758 0-45.254l208-208c12.499-12.497 32.758-12.497 45.258 0z"],"attrs":[{"fill":"rgb(108, 114, 122)"}],"isMulticolor":false,"isMulticolor2":false,"colorPermutations":{"1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101":[{"f":2}]},"tags":["chevron-left"],"grid":0},"attrs":[{"fill":"rgb(108, 114, 122)"}],"properties":{"order":205,"id":60,"name":"chevron-left","prevSize":32,"code":59702},"setIdx":0,"setId":4,"iconIdx":66},{"icon":{"paths":["M436.038 742.627c-12.496-12.496-12.496-32.758 0-45.254l185.373-185.373-185.373-185.373c-12.496-12.497-12.496-32.758 0-45.254 12.499-12.497 32.758-12.497 45.254 0l208 208c12.499 12.496 12.499 32.758 0 45.254l-207.997 208c-12.499 12.496-32.758 12.496-45.258 0z"],"attrs":[{"fill":"rgb(108, 114, 122)"}],"isMulticolor":false,"isMulticolor2":false,"colorPermutations":{"1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101":[{"f":2}]},"tags":["chevron-right"],"grid":0},"attrs":[{"fill":"rgb(108, 114, 122)"}],"properties":{"order":206,"id":61,"name":"chevron-right","prevSize":32,"code":59703},"setIdx":0,"setId":4,"iconIdx":67},{"icon":{"paths":["M742.627 587.958c-12.496 12.499-32.758 12.499-45.254 0l-185.373-185.373-185.373 185.373c-12.496 12.499-32.758 12.499-45.254 0-12.497-12.496-12.497-32.758 0-45.254l208-208c12.496-12.496 32.758-12.496 45.254 0l208 208c12.496 12.496 12.496 32.758 0 45.254z"],"attrs":[{"fill":"rgb(108, 114, 122)"}],"isMulticolor":false,"isMulticolor2":false,"colorPermutations":{"1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101":[{"f":2}]},"tags":["chevron-up"],"grid":0},"attrs":[{"fill":"rgb(108, 114, 122)"}],"properties":{"order":207,"id":62,"name":"chevron-up","prevSize":32,"code":59704},"setIdx":0,"setId":4,"iconIdx":68},{"icon":{"paths":["M512 864c194.403 0 352-157.597 352-352 0-46.522-9.024-90.931-25.418-131.581l48.518-48.518c26.211 54.496 40.899 115.581 40.899 180.099 0 229.75-186.25 416-416 416s-416-186.25-416-416c0-229.75 186.25-416 416-416 95.376 0 183.254 32.096 253.424 86.076l-45.712 45.712c-58.221-42.623-130.029-67.788-207.712-67.788-194.404 0-352 157.596-352 352s157.596 352 352 352zM902.63 230.623c12.496-12.499 12.493-32.76-0.006-45.255s-32.762-12.491-45.254 0.008l-345.386 345.503-105.341-105.491c-12.486-12.506-32.749-12.522-45.254-0.032-12.506 12.486-12.522 32.749-0.032 45.254l127.971 128.157c6 6.006 14.144 9.386 22.634 9.389 8.493 0 16.637-3.373 22.64-9.376l368.029-368.157z"],"attrs":[{"fill":"rgb(108, 114, 122)"}],"isMulticolor":false,"isMulticolor2":false,"colorPermutations":{"1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101":[{"f":2}]},"tags":["circle-check"],"grid":0},"attrs":[{"fill":"rgb(108, 114, 122)"}],"properties":{"order":208,"id":63,"name":"circle-check","prevSize":32,"code":59705},"setIdx":0,"setId":4,"iconIdx":69},{"icon":{"paths":["M608 192c0-53.019-42.979-96-96-96s-96 42.981-96 96h-192c-17.673 0-32 14.327-32 32v672c0 17.674 14.327 32 32 32h576c17.674 0 32-14.326 32-32v-672c0-17.673-14.326-32-32-32h-192zM256 864v-608h96v64c0 17.674 14.326 32 32 32h256c17.674 0 32-14.326 32-32v-64h96v608h-512zM512 224c17.674 0 32-14.327 32-32s-14.326-32-32-32c-17.674 0-32 14.327-32 32s14.326 32 32 32z"],"attrs":[{"fill":"rgb(108, 114, 122)"}],"isMulticolor":false,"isMulticolor2":false,"colorPermutations":{"1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101":[{"f":2}]},"tags":["clipboard"],"grid":0},"attrs":[{"fill":"rgb(108, 114, 122)"}],"properties":{"order":209,"id":64,"name":"clipboard","prevSize":32,"code":59706},"setIdx":0,"setId":4,"iconIdx":70},{"icon":{"paths":["M864 512c0 194.403-157.597 352-352 352s-352-157.597-352-352c0-194.404 157.596-352 352-352s352 157.596 352 352zM928 512c0-229.75-186.25-416-416-416s-416 186.25-416 416c0 229.75 186.25 416 416 416s416-186.25 416-416zM544 288c0-17.673-14.326-32-32-32s-32 14.327-32 32v224c0 8.486 3.373 16.627 9.373 22.627l96 96c12.496 12.496 32.758 12.496 45.254 0s12.496-32.758 0-45.254l-86.627-86.627v-210.746z"],"attrs":[{"fill":"rgb(108, 114, 122)"}],"isMulticolor":false,"isMulticolor2":false,"colorPermutations":{"1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101":[{"f":2}]},"tags":["clock"],"grid":0},"attrs":[{"fill":"rgb(108, 114, 122)"}],"properties":{"order":210,"id":65,"name":"clock","prevSize":32,"code":59707},"setIdx":0,"setId":4,"iconIdx":71},{"icon":{"paths":["M806.627 262.628c12.496-12.497 12.496-32.758 0-45.255s-32.758-12.497-45.254 0l-249.373 249.373-249.372-249.373c-12.497-12.497-32.758-12.497-45.255 0s-12.497 32.758 0 45.255l249.373 249.372-249.373 249.373c-12.497 12.496-12.497 32.758 0 45.254s32.758 12.496 45.255 0l249.372-249.373 249.373 249.373c12.496 12.496 32.758 12.496 45.254 0s12.496-32.758 0-45.254l-249.373-249.373 249.373-249.372z"],"attrs":[{"fill":"rgb(108, 114, 122)"}],"isMulticolor":false,"isMulticolor2":false,"colorPermutations":{"1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101":[{"f":2}]},"tags":["close"],"grid":0},"attrs":[{"fill":"rgb(108, 114, 122)"}],"properties":{"order":211,"id":66,"name":"close","prevSize":32,"code":59708},"setIdx":0,"setId":4,"iconIdx":72},{"icon":{"paths":["M273.129 356.678c6.747-28.298 25.696-52.004 51.21-67.152 25.6-15.2 56.323-20.858 84.698-14.53 27.642 6.163 55.117 24.12 74.874 60.339l22.541 41.325 30.134-36.16c27.238-32.689 85.126-39.713 134.714-14.064 23.562 12.186 42.208 30.618 52.064 53.488 9.654 22.406 12.112 51.898-1.434 89.149l-15.616 42.938h45.686c53.606 0 82.419 15.882 97.642 33.75 15.651 18.374 21.898 44.646 18.557 74.714-3.344 30.083-16.054 60.518-33.456 82.89-17.939 23.066-36.646 32.646-50.742 32.646h-543.998c-18.791 0-37.068-10.362-52.195-31.578-15.217-21.344-24.978-51.050-25.818-81.312-0.84-30.227 7.237-57.914 23.733-77.491 15.796-18.746 42.268-33.619 86.279-33.619h59.791l-33.165-49.75c-27.882-41.824-32.118-77.814-25.498-105.581zM518.218 272.084c-26.339-32.073-59.667-51.619-95.251-59.554-45.626-10.174-92.902-0.833-131.301 21.965-38.487 22.85-69.538 60.142-80.791 107.342-8.278 34.72-5.369 72.758 10.731 111.77-35.67 8.464-64.099 26.186-84.825 50.784-29.004 34.422-39.927 78.733-38.766 120.506 1.159 41.738 14.399 84.032 37.682 116.688 23.373 32.784 59.097 58.426 104.306 58.426h543.999c41.907 0 77.2-26.419 101.261-57.354 24.598-31.629 41.888-73.197 46.544-115.114 4.659-41.933-3.094-87.658-33.443-123.283-24.106-28.301-59.504-46.774-105.616-53.456 5.83-35.194 1.686-67.674-10.605-96.205-16.646-38.628-46.998-67.197-81.437-85.010-54.902-28.397-128.733-32.652-182.486 2.495zM512 437.331c17.674 0 32 14.33 32 32v53.328h53.334c17.674 0 32 14.33 32 32 0 17.674-14.326 32-32 32h-53.334v53.341c0 17.674-14.326 32-32 32s-32-14.326-32-32v-53.341h-53.331c-17.674 0-32-14.326-32-32 0-17.67 14.326-32 32-32h53.331v-53.328c0-17.67 14.326-32 32-32z"],"attrs":[{"fill":"rgb(108, 114, 122)"}],"isMulticolor":false,"isMulticolor2":false,"colorPermutations":{"1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101":[{"f":2}]},"tags":["cloud-connectivity"],"grid":0},"attrs":[{"fill":"rgb(108, 114, 122)"}],"properties":{"order":212,"id":67,"name":"cloud-connectivity","prevSize":32,"code":59709},"setIdx":0,"setId":4,"iconIdx":73},{"icon":{"paths":["M630.154 204.798c16.496 6.344 24.723 24.859 18.381 41.355l-213.334 554.667c-6.342 16.496-24.858 24.723-41.354 18.381-16.496-6.346-24.723-24.861-18.381-41.357l213.334-554.666c6.346-16.495 24.858-24.724 41.354-18.38zM321.293 361.37c12.499 12.499 12.499 32.758 0 45.258l-105.371 105.37 105.371 105.373c12.499 12.499 12.499 32.758 0 45.258-12.495 12.496-32.756 12.496-45.253 0l-128-128c-12.497-12.499-12.497-32.758 0-45.258l128-128c12.497-12.496 32.758-12.496 45.253 0zM702.707 361.37c12.496-12.496 32.758-12.496 45.254 0l128 128c12.496 12.499 12.496 32.758 0 45.258l-128 128c-12.496 12.496-32.758 12.496-45.254 0-12.499-12.499-12.499-32.758 0-45.258l105.373-105.373-105.373-105.37c-12.499-12.499-12.499-32.758 0-45.258z"],"attrs":[{"fill":"rgb(108, 114, 122)"}],"isMulticolor":false,"isMulticolor2":false,"colorPermutations":{"1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101":[{"f":2}]},"tags":["code"],"grid":0},"attrs":[{"fill":"rgb(108, 114, 122)"}],"properties":{"order":213,"id":68,"name":"code","prevSize":32,"code":59710},"setIdx":0,"setId":4,"iconIdx":74},{"icon":{"paths":["M770.704 224c17.674 0 32 14.327 32 32v544c0 17.674-14.326 32-32 32h-543.999c-17.673 0-32-14.326-32-32v-128c17.673 0 32-14.326 32-32s-14.327-32-32-32v-192c17.673 0 32-14.326 32-32s-14.327-32-32-32v-96c0-17.673 14.327-32 32-32h543.999zM130.705 608c-17.673 0-32 14.326-32 32s14.327 32 32 32v128c0 53.021 42.981 96 96 96h543.999c53.021 0 96-42.979 96-96v-544c0-53.019-42.979-96-96-96h-543.999c-53.019 0-96 42.981-96 96v96c-17.673 0-32 14.326-32 32s14.327 32 32 32v192zM427.91 514.266c13.315-3.568 27.309-3.789 40.73-0.643l31.52 7.389c8.73 2.045 17.83 1.904 26.49-0.416l22.186-5.942c14.285-3.827 29.328-4.042 43.683-0.675 40.429 9.475 69.325 45.584 69.325 87.277v24.336c0 31.811-25.789 57.6-57.6 57.6h-180.739c-31.811 0-57.6-25.789-57.6-57.6v-30.525c0-37.862 25.434-71.005 62.006-80.8zM456.957 563.472c-5.206-1.219-10.634-1.133-15.798 0.25-14.189 3.798-24.054 16.656-24.054 31.344v30.525c0 3.536 2.867 6.4 6.4 6.4h180.739c3.533 0 6.4-2.864 6.4-6.4v-24.336c0-17.75-12.365-33.341-29.808-37.427-6.186-1.45-12.662-1.35-18.752 0.282l-22.186 5.942c-16.813 4.502-34.474 4.781-51.421 0.81l-31.52-7.389zM539.155 420.48c0-13.962-11.318-25.28-25.28-25.28s-25.283 11.318-25.283 25.28c0 13.962 11.322 25.28 25.283 25.28s25.28-11.318 25.28-25.28zM590.355 420.48c0 42.24-34.243 76.48-76.48 76.48-42.24 0-76.483-34.24-76.483-76.48s34.243-76.48 76.483-76.48c42.237 0 76.48 34.24 76.48 76.48z"],"width":1056,"attrs":[{"fill":"rgb(108, 114, 122)"}],"isMulticolor":false,"isMulticolor2":false,"colorPermutations":{"1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101":[{"f":2}]},"tags":["contacts"],"grid":0},"attrs":[{"fill":"rgb(108, 114, 122)"}],"properties":{"order":214,"id":69,"name":"contacts","prevSize":32,"code":59711},"setIdx":0,"setId":4,"iconIdx":75},{"icon":{"paths":["M464 272c-35.347 0-64 28.654-64 64v480c0 35.347 28.653 64 64 64h352c35.347 0 64-28.653 64-64v-304c0-6.509-1.984-12.864-5.69-18.214l-134.458-194.215c-11.952-17.267-31.619-27.571-52.621-27.571h-223.232zM464 336h144v144c0 35.347 28.653 64 64 64h144v272h-352v-480zM672 480v-144h15.232l99.693 144h-114.925zM144 208c0-35.346 28.654-64 64-64h241.844c18.032 0 35.229 7.607 47.357 20.949l39.136 43.051h-328.336v480h128v64h-128c-35.346 0-64-28.653-64-64v-480z"],"attrs":[{"fill":"rgb(108, 114, 122)"}],"isMulticolor":false,"isMulticolor2":false,"colorPermutations":{"1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101":[{"f":2}]},"tags":["copy"],"grid":0},"attrs":[{"fill":"rgb(108, 114, 122)"}],"properties":{"order":215,"id":70,"name":"copy","prevSize":32,"code":59712},"setIdx":0,"setId":4,"iconIdx":76},{"icon":{"paths":["M824.682 183.521c-37.19-37.986-98.202-38.583-136.131-1.333l-348.48 342.237c-12.739 12.512-21.733 28.32-25.976 45.654l-23.681 96.749c-6.94 28.355 16.754 54.845 45.747 51.142l90.691-11.578c20.224-2.582 39.104-11.52 53.907-25.52l360.486-340.922c38.986-36.868 40.173-98.482 2.637-136.82l-19.2-19.61zM733.485 227.821c12.643-12.417 32.979-12.218 45.376 0.444l19.2 19.61c12.512 12.78 12.115 33.317-0.88 45.607l-69.965 66.169-63.446-63.364 69.715-68.466zM618.074 341.162l62.592 62.512-243.971 230.73c-4.934 4.666-11.229 7.645-17.968 8.506l-58.307 7.443 15.926-65.075c1.414-5.779 4.413-11.046 8.659-15.219l233.069-228.896zM193.608 265.602c0-17.673 14.346-32 32.042-32h281.332c17.696 0 32.042-14.327 32.042-32s-14.346-32-32.042-32h-281.332c-53.089 0-96.127 42.981-96.127 96v534.402c0 53.018 43.037 95.997 96.127 95.997h529.764c53.091 0 96.128-42.979 96.128-96v-279.414c0-17.67-14.346-32-32.042-32s-32.042 14.33-32.042 32v279.414c0 17.674-14.346 32-32.045 32h-529.764c-17.696 0-32.042-14.326-32.042-31.997v-534.402z"],"width":1056,"attrs":[{"fill":"rgb(108, 114, 122)"}],"isMulticolor":false,"isMulticolor2":false,"colorPermutations":{"1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101":[{"f":2}]},"tags":["create"],"grid":0},"attrs":[{"fill":"rgb(108, 114, 122)"}],"properties":{"order":216,"id":71,"name":"create","prevSize":32,"code":59713},"setIdx":0,"setId":4,"iconIdx":77},{"icon":{"paths":["M717.373 298.663c0-17.673-14.326-32-32-32s-32 14.327-32 32v426.665c0 17.674 14.326 32 32 32s32-14.326 32-32v-426.665z","M514.704 394.672c17.674 0 32 14.326 32 32v298.666c0 17.674-14.326 32-32 32s-32-14.326-32-32v-298.666c0-17.674 14.326-32 32-32z","M376.038 554.672c0-17.674-14.326-32-32-32s-32 14.326-32 32v170.669c0 17.67 14.327 32 32 32s32-14.33 32-32v-170.669z","M130.705 199.556v624.889c0 39.52 32.036 71.555 71.556 71.555h624.888c39.52 0 71.555-32.035 71.555-71.555v-624.889c0-39.519-32.035-71.556-71.555-71.556h-624.888c-39.519 0-71.556 32.036-71.556 71.556zM202.26 192h624.888c4.173 0 7.555 3.383 7.555 7.556v624.889c0 4.173-3.382 7.555-7.555 7.555h-624.888c-4.173 0-7.556-3.382-7.556-7.555v-624.889c0-4.173 3.383-7.556 7.556-7.556z"],"width":1056,"attrs":[{"fill":"rgb(108, 114, 122)"},{"fill":"rgb(108, 114, 122)"},{"fill":"rgb(108, 114, 122)"},{"fill":"rgb(108, 114, 122)"}],"isMulticolor":false,"isMulticolor2":false,"colorPermutations":{"1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101":[{"f":2},{"f":2},{"f":2},{"f":2}]},"tags":["dashboard"],"grid":0},"attrs":[{"fill":"rgb(108, 114, 122)"},{"fill":"rgb(108, 114, 122)"},{"fill":"rgb(108, 114, 122)"},{"fill":"rgb(108, 114, 122)"}],"properties":{"order":217,"id":72,"name":"dashboard","prevSize":32,"code":59714},"setIdx":0,"setId":4,"iconIdx":78},{"icon":{"paths":["M739.68 864v-448h-448.593v448h448.593zM227.003 864v-448c-35.393 0-64.084-28.653-64.084-64v-128c0-35.346 28.692-64 64.084-64h224.297c0-35.346 28.691-64 64.083-64 35.395 0 64.086 28.654 64.086 64h224.294c35.395 0 64.086 28.654 64.086 64v128c0 35.347-28.691 64-64.086 64v448c0 35.347-28.691 64-64.083 64h-448.593c-35.393 0-64.085-28.653-64.085-64zM803.763 224h-576.761v128h576.761v-128zM419.258 544v192c0 17.674 14.346 32 32.042 32s32.042-14.326 32.042-32v-192c0-17.674-14.346-32-32.042-32s-32.042 14.326-32.042 32zM579.469 512c17.696 0 32.042 14.326 32.042 32v192c0 17.674-14.346 32-32.042 32s-32.042-14.326-32.042-32v-192c0-17.674 14.346-32 32.042-32z"],"width":1056,"attrs":[{"fill":"rgb(108, 114, 122)"}],"isMulticolor":false,"isMulticolor2":false,"colorPermutations":{"1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101":[{"f":2}]},"tags":["delete"],"grid":0},"attrs":[{"fill":"rgb(108, 114, 122)"}],"properties":{"order":218,"id":73,"name":"delete","prevSize":32,"code":59715},"setIdx":0,"setId":4,"iconIdx":79},{"icon":{"paths":["M341.334 778.672c-17.674 0-32 14.326-32 32s14.327 32 32 32h341.334c17.67 0 32-14.326 32-32s-14.33-32-32-32h-341.334z","M85.334 298.672c0-70.692 57.308-128 128-128h597.335c70.691 0 128 57.308 128 128v298.666c0 70.694-57.309 128-128 128h-597.335c-70.692 0-128-57.306-128-128v-298.666zM213.334 234.672c-35.346 0-64 28.654-64 64v298.666c0 35.347 28.654 64 64 64h597.335c35.344 0 64-28.653 64-64v-298.666c0-35.347-28.656-64-64-64h-597.335z"],"attrs":[{"fill":"rgb(108, 114, 122)"},{"fill":"rgb(108, 114, 122)"}],"isMulticolor":false,"isMulticolor2":false,"colorPermutations":{"1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101":[{"f":2},{"f":2}]},"tags":["desktop"],"grid":0},"attrs":[{"fill":"rgb(108, 114, 122)"},{"fill":"rgb(108, 114, 122)"}],"properties":{"order":219,"id":74,"name":"desktop","prevSize":32,"code":59716},"setIdx":0,"setId":4,"iconIdx":80},{"icon":{"paths":["M325.837 160c0-17.673-14.347-32-32.043-32h-64.085c-17.696 0-32.042 14.327-32.042 32v64c0 17.673 14.346 32 32.042 32h64.085c17.696 0 32.043-14.327 32.043-32v-64zM325.837 586.666c0-17.674-14.347-32-32.043-32h-64.085c-17.696 0-32.042 14.326-32.042 32v64c0 17.674 14.346 32 32.042 32h64.085c17.696 0 32.043-14.326 32.043-32v-64zM454.006 160c0-17.673 14.346-32 32.042-32h64.083c17.696 0 32.042 14.327 32.042 32v64c0 17.673-14.346 32-32.042 32h-64.083c-17.696 0-32.042-14.327-32.042-32v-64zM582.173 373.334c0-17.674-14.346-32-32.042-32h-64.083c-17.696 0-32.042 14.326-32.042 32v64c0 17.674 14.346 32 32.042 32h64.083c17.696 0 32.042-14.326 32.042-32v-64zM454.006 586.666c0-17.674 14.346-32 32.042-32h64.083c17.696 0 32.042 14.326 32.042 32v64c0 17.674-14.346 32-32.042 32h-64.083c-17.696 0-32.042-14.326-32.042-32v-64zM582.173 800c0-17.674-14.346-32-32.042-32h-64.083c-17.696 0-32.042 14.326-32.042 32v64c0 17.674 14.346 32 32.042 32h64.083c17.696 0 32.042-14.326 32.042-32v-64zM710.342 160c0-17.673 14.346-32 32.045-32h64.083c17.696 0 32.042 14.327 32.042 32v64c0 17.673-14.346 32-32.042 32h-64.083c-17.699 0-32.045-14.327-32.045-32v-64zM838.512 373.334c0-17.674-14.346-32-32.042-32h-64.083c-17.699 0-32.045 14.326-32.045 32v64c0 17.674 14.346 32 32.045 32h64.083c17.696 0 32.042-14.326 32.042-32v-64zM710.342 586.666c0-17.674 14.346-32 32.045-32h64.083c17.696 0 32.042 14.326 32.042 32v64c0 17.674-14.346 32-32.042 32h-64.083c-17.699 0-32.045-14.326-32.045-32v-64zM325.837 373.334c0-17.674-14.347-32-32.043-32h-64.085c-17.696 0-32.042 14.326-32.042 32v64c0 17.674 14.346 32 32.042 32h64.085c17.696 0 32.043-14.326 32.043-32v-64z"],"width":1056,"attrs":[{"fill":"rgb(108, 114, 122)"}],"isMulticolor":false,"isMulticolor2":false,"colorPermutations":{"1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101":[{"f":2}]},"tags":["dialpad"],"grid":0},"attrs":[{"fill":"rgb(108, 114, 122)"}],"properties":{"order":220,"id":75,"name":"dialpad","prevSize":32,"code":59717},"setIdx":0,"setId":4,"iconIdx":81},{"icon":{"paths":["M514.704 96c80.096 0 154.906 22.636 218.378 61.859l-46.746 46.746c-24.739-13.843-51.322-24.785-79.286-32.368 12.166 22.395 23.078 49.095 32.403 79.249l-51.997 51.999c-5.654-22.994-12.17-44.005-19.347-62.666-12.822-33.339-26.522-55.927-38.474-69.070-8.4-9.235-13.43-11.291-14.928-11.698-1.501 0.407-6.531 2.463-14.931 11.698-11.949 13.143-25.651 35.731-38.474 69.070-16.298 42.377-29.174 96.867-36.339 159.18h65.974l-63.997 64h-7.117c-0.115 2.47-0.221 4.95-0.32 7.437l-64.538 64.538c-0.173-7.933-0.259-15.926-0.259-23.974 0-16.234 0.355-32.25 1.053-48h-189.81c-2.14 15.696-3.245 31.718-3.245 48 0 33.28 4.619 65.485 13.251 96h106.984l-64 64h-17.852c2.004 3.92 4.078 7.798 6.223 11.629l-46.746 46.749c-39.223-63.475-61.859-138.282-61.859-218.378 0-229.75 186.25-416 415.999-416zM868.845 293.623l-46.746 46.748c10.56 18.87 19.434 38.816 26.413 59.629h-86.045l-64 64h164.992c2.141 15.696 3.245 31.718 3.245 48 0 33.28-4.618 65.485-13.248 96h-183.030c2.8-30.816 4.282-62.957 4.282-96 0-8.045-0.090-16.042-0.262-23.974l-64.538 64.534c-0.752 19.008-2.022 37.523-3.763 55.44h-51.677l-64 64h107.091c-7.37 42.458-17.488 80.083-29.45 111.184-12.822 33.338-26.522 55.926-38.474 69.069-8.397 9.235-13.43 11.29-14.928 11.699-1.501-0.41-6.531-2.464-14.931-11.699-11.949-13.142-25.651-35.731-38.474-69.069-7.178-18.662-13.693-39.677-19.347-62.669l-52 51.997c9.328 30.154 20.24 56.854 32.406 79.251-27.965-7.584-54.547-18.525-79.286-32.368l-46.747 46.746c63.348 39.146 137.985 61.77 217.899 61.859h0.957c229.53-0.259 415.52-186.41 415.52-416 0-80.096-22.634-154.902-61.859-218.377zM422.362 172.237c-113.55 30.788-204.31 116.98-241.464 227.763h179.676c10-93.226 32.176-173.252 61.789-227.763zM607.050 851.763c24.397-44.902 43.741-107.117 55.395-179.763h165.878c-44.653 87.35-124.723 153.584-221.274 179.763zM828.077 153.372c12.499-12.497 32.758-12.497 45.254 0 12.499 12.497 12.499 32.758 0 45.255l-671.999 672c-12.497 12.496-32.758 12.496-45.255 0s-12.497-32.758 0-45.254l671.999-672z"],"width":1056,"attrs":[{"fill":"rgb(108, 114, 122)"}],"isMulticolor":false,"isMulticolor2":false,"colorPermutations":{"1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101":[{"f":2}]},"tags":["directory-disabled"],"grid":0},"attrs":[{"fill":"rgb(108, 114, 122)"}],"properties":{"order":221,"id":76,"name":"directory-disabled","prevSize":32,"code":59718},"setIdx":0,"setId":4,"iconIdx":82},{"icon":{"paths":["M866.493 512c0 33.28-4.624 65.485-13.267 96h-183.27c2.803-30.816 4.285-62.957 4.285-96 0-16.234-0.358-32.25-1.053-48h190.058c2.141 15.696 3.248 31.718 3.248 48zM609.037 464c0.736 15.67 1.12 31.686 1.12 48 0 33.398-1.606 65.555-4.57 96h-183.117c-2.96-30.445-4.566-62.602-4.566-96 0-16.314 0.381-32.33 1.12-48h190.013zM668.368 400c-10.013-93.226-32.218-173.252-61.872-227.763 113.699 30.788 204.579 116.981 241.782 227.763h-179.91zM421.565 172.237c-29.654 54.51-51.859 134.537-61.872 227.763h-179.913c37.204-110.782 128.083-196.975 241.785-227.763zM424.17 400c7.171-62.314 20.067-116.804 36.387-159.18 12.838-33.339 26.557-55.927 38.525-69.070 8.41-9.235 13.446-11.291 14.947-11.698 1.501 0.407 6.541 2.463 14.95 11.698 11.965 13.143 25.683 35.731 38.525 69.070 16.32 42.377 29.213 96.867 36.387 159.18h-179.722zM354.874 464c-0.698 15.75-1.056 31.766-1.056 48 0 33.043 1.485 65.184 4.288 96h-183.274c-8.643-30.515-13.268-62.72-13.268-96 0-16.282 1.107-32.304 3.249-48h190.060zM366.096 672c11.667 72.646 31.040 134.861 55.466 179.763-96.675-26.179-176.853-92.413-221.565-179.763h166.099zM514.509 928c229.834-0.259 416.070-186.41 416.070-416 0-229.75-186.496-416-416.55-416s-416.549 186.25-416.549 416c0 229.59 186.237 415.741 416.073 416 0.16 0 0.317 0 0.477 0s0.32 0 0.48 0zM606.496 851.763c24.426-44.902 43.798-107.117 55.466-179.763h166.099c-44.71 87.35-124.886 153.584-221.565 179.763zM596.992 672c-7.379 42.458-17.51 80.083-29.488 111.184-12.842 33.338-26.56 55.926-38.525 69.069-8.41 9.235-13.45 11.29-14.95 11.699-1.501-0.41-6.538-2.464-14.947-11.699-11.968-13.142-25.686-35.731-38.525-69.069-11.978-31.101-22.112-68.726-29.491-111.184h165.926z"],"width":1056,"attrs":[{"fill":"rgb(108, 114, 122)"}],"isMulticolor":false,"isMulticolor2":false,"colorPermutations":{"1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101":[{"f":2}]},"tags":["directory"],"grid":0},"attrs":[{"fill":"rgb(108, 114, 122)"}],"properties":{"order":222,"id":77,"name":"directory","prevSize":32,"code":59719},"setIdx":0,"setId":4,"iconIdx":83},{"icon":{"paths":["M561.667 127.992c-121.696 0-200.973 50.509-248.813 115.801-46.216 63.073-61.638 137.752-61.933 188.616-8.705 98.746 28.601 172.896 89.422 223.469 59.686 49.626 140.202 75.258 217.85 83.738 77.75 8.486 161.715 1.197 227.856-12.547 33.066-6.87 62.774-15.581 85.642-25.331 11.366-4.848 22.029-10.368 30.797-16.669 7.875-5.661 18.541-14.976 23.242-28.835 7.286-21.475-3.133-38.784-11.965-48.102-8.41-8.877-19.427-15.405-28.24-20.016-9.306-4.867-19.379-9.206-27.533-12.707-8.973-3.856-14.858-6.4-18.56-8.432-4.845-2.662-8.842-5.088-12.026-7.203 20.256-47.142 31.549-77.043 37.696-99.19 7.293-26.272 7.315-41.725 7.315-58.909 0-21.523-9.603-88.542-52.81-151.108-44.701-64.73-124.074-122.573-257.939-122.573zM314.916 433.898c0-40.416 12.607-101.84 49.564-152.277 35.782-48.836 95.882-89.628 197.187-89.628 112.086 0 172.090 46.886 205.277 94.94 34.678 50.219 41.472 104.040 41.472 114.741v0.198c0 14.918 0 23.638-4.982 41.594-5.491 19.779-16.963 50.189-41.539 106.538-5.802 13.296-2.762 25.77 1.155 33.754 3.664 7.478 9.005 13.242 13.331 17.261 8.835 8.211 20.653 15.686 32.224 22.045 6.618 3.638 15.238 7.334 22.563 10.477l1.562 0.672c5.904 2.534 11.52 4.97 16.688 7.418-0.909 0.406-1.856 0.819-2.835 1.238-17.741 7.568-43.078 15.206-73.555 21.539-60.947 12.666-138.064 19.21-207.888 11.587-69.926-7.635-136.982-30.336-183.878-69.328-45.459-37.798-73.674-92.064-66.481-169.821l0.136-1.469v-1.478zM819.162 553.354l-0.074-0.086c0 0.003 0.010 0.013 0.029 0.035 0.010 0.013 0.026 0.029 0.045 0.051z","M178.552 502.474c7.496-11.258 16.26-22.259 26.436-32.592 0.876 31.747 6.169 61.226 15.216 88.358-15.094 30.877-18.374 59.315-18.374 65.357v0.186c0 11.382 0 17.536 3.524 30.701 3.993 14.918 12.441 38.211 30.867 82.022 5.256 12.496 2.48 24.099-0.985 31.43-3.249 6.874-7.925 12.058-11.512 15.514-7.319 7.053-16.852 13.254-25.75 18.326-5.008 2.854-11.333 5.706-16.546 8.029 11.182 3.939 24.98 7.814 40.802 11.226 44.977 9.693 101.833 14.669 153.062 8.87 51.28-5.808 99.776-23.014 133.35-51.965l0.608-0.528c14.794 2.784 29.53 4.938 44.051 6.525 10.886 1.187 21.862 2.086 32.877 2.72-10.122 14.797-22.163 28.045-35.741 39.754-46.362 39.974-108.547 60.362-167.946 67.088-59.45 6.73-123.405 0.947-173.744-9.901-25.17-5.424-48.056-12.352-65.901-20.243-8.86-3.92-17.458-8.502-24.686-13.891-6.431-4.794-15.831-13.171-20.001-25.92-6.422-19.632 2.792-35.443 10.458-43.834 7.193-7.872 16.405-13.462 23.248-17.174 7.309-3.965 15.158-7.466 21.235-10.173 6.907-3.078 10.854-4.858 13.183-6.186 1.349-0.768 2.591-1.501 3.727-2.195-13.937-33.885-21.976-56.118-26.48-72.947-5.68-21.222-5.7-33.875-5.7-47.434 0-17.712 7.436-71.139 40.721-121.123zM155.337 797.248c-0.012 0 0.084 0.102 0.317 0.301-0.19-0.202-0.306-0.301-0.317-0.301zM179.587 737.085c-0.004 0-0.052 0.051-0.137 0.15l0.112-0.118c0.019-0.022 0.027-0.032 0.025-0.032z"],"attrs":[{"fill":"rgb(108, 114, 122)"},{"fill":"rgb(108, 114, 122)"}],"isMulticolor":false,"isMulticolor2":false,"colorPermutations":{"1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101":[{"f":2},{"f":2}]},"tags":["discussions"],"grid":0},"attrs":[{"fill":"rgb(108, 114, 122)"},{"fill":"rgb(108, 114, 122)"}],"properties":{"order":223,"id":78,"name":"discussions","prevSize":32,"code":59720},"setIdx":0,"setId":4,"iconIdx":84},{"icon":{"paths":["M160 256c0-17.673 14.327-32 32-32h640c17.674 0 32 14.327 32 32s-14.326 32-32 32h-640c-17.673 0-32-14.327-32-32zM160 421.162c0-17.674 14.327-32 32-32h640c17.674 0 32 14.326 32 32s-14.326 32-32 32h-640c-17.673 0-32-14.326-32-32zM160 602.838c0-17.674 14.327-32 32-32h640c17.674 0 32 14.326 32 32s-14.326 32-32 32h-640c-17.673 0-32-14.326-32-32zM160 768c0-17.674 14.327-32 32-32h344.614c17.674 0 32 14.326 32 32s-14.326 32-32 32h-344.614c-17.673 0-32-14.326-32-32z"],"attrs":[{"fill":"rgb(108, 114, 122)"}],"isMulticolor":false,"isMulticolor2":false,"colorPermutations":{"1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101":[{"f":2}]},"tags":["document"],"grid":0},"attrs":[{"fill":"rgb(108, 114, 122)"}],"properties":{"order":224,"id":79,"name":"document","prevSize":32,"code":59721},"setIdx":0,"setId":4,"iconIdx":85},{"icon":{"paths":["M128.169 352c0-17.674 14.346-32 32.042-32h704.928c17.696 0 32.045 14.326 32.045 32s-14.349 32-32.045 32h-704.928c-17.696 0-32.042-14.326-32.042-32zM213.615 522.669c0-17.674 14.346-32 32.042-32h534.036c17.699 0 32.045 14.326 32.045 32s-14.346 32-32.045 32h-534.036c-17.697 0-32.042-14.326-32.042-32zM331.104 661.331c-17.698 0-32.043 14.326-32.043 32s14.346 32 32.043 32h363.146c17.696 0 32.042-14.326 32.042-32s-14.346-32-32.042-32h-363.146z"],"width":1056,"attrs":[{"fill":"rgb(108, 114, 122)"}],"isMulticolor":false,"isMulticolor2":false,"colorPermutations":{"1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101":[{"f":2}]},"tags":["donner"],"grid":0},"attrs":[{"fill":"rgb(108, 114, 122)"}],"properties":{"order":225,"id":80,"name":"donner","prevSize":32,"code":59722},"setIdx":0,"setId":4,"iconIdx":86},{"icon":{"paths":["M273.128 356.678c-6.62 27.766-2.384 63.757 25.498 105.581l21.374 32.061v17.69h-48c-44.011 0-70.483 14.874-86.278 33.619-16.496 19.578-24.573 47.264-23.734 77.491 0.841 30.262 10.601 59.968 25.818 81.312 15.127 21.216 33.404 31.578 52.195 31.578h79.999v64h-80c-45.209 0-80.932-25.642-104.306-58.426-23.283-32.656-36.522-74.95-37.682-116.688-1.16-41.773 9.763-86.083 38.766-120.506 20.726-24.598 49.155-42.32 84.825-50.784-16.1-39.011-19.009-77.050-10.731-111.77 11.253-47.2 42.304-84.492 80.791-107.342 38.4-22.798 85.677-32.139 131.303-21.965 35.584 7.935 68.909 27.48 95.251 59.554 53.75-35.147 127.584-30.892 182.483-2.495 34.438 17.812 64.794 46.382 81.437 85.010 12.294 28.531 16.438 61.011 10.608 96.205 46.112 6.682 81.507 25.155 105.613 53.456 30.349 35.626 38.106 81.35 33.446 123.283-4.659 41.917-21.946 83.485-46.544 115.114-24.061 30.934-59.354 57.354-101.261 57.354h-80v-64h80c14.093 0 32.8-9.581 50.742-32.646 17.398-22.371 30.112-52.806 33.453-82.89 3.341-30.067-2.902-56.339-18.554-74.714-15.222-17.869-44.035-33.75-97.642-33.75h-45.686l15.613-42.938c13.546-37.251 11.091-66.742 1.437-89.149-9.856-22.87-28.502-41.302-52.064-53.488-49.587-25.649-107.475-18.625-134.717 14.064l-30.134 36.16-22.541-41.325c-19.757-36.219-47.232-54.175-74.87-60.339-28.378-6.327-59.098-0.669-84.701 14.53-25.512 15.148-44.461 38.855-51.208 67.152zM630.979 787.222c12.298-12.691 11.981-32.95-0.707-45.251-12.691-12.298-32.95-11.981-45.251 0.707l-41.021 42.326v-273.005c0-17.674-14.326-32-32-32s-32 14.326-32 32v273.005l-41.021-42.326c-12.301-12.688-32.56-13.005-45.251-0.707-12.688 12.301-13.005 32.56-0.707 45.251l96 99.050c6.029 6.218 14.32 9.728 22.979 9.728s16.95-3.51 22.979-9.728l96-99.050z"],"attrs":[{"fill":"rgb(108, 114, 122)"}],"isMulticolor":false,"isMulticolor2":false,"colorPermutations":{"1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101":[{"f":2}]},"tags":["download"],"grid":0},"attrs":[{"fill":"rgb(108, 114, 122)"}],"properties":{"order":226,"id":81,"name":"download","prevSize":32,"code":59723},"setIdx":0,"setId":4,"iconIdx":87},{"icon":{"paths":["M795.648 132.004c-25.027-25.027-65.603-25.027-90.63-0l-530.467 530.469c-9.577 9.574-15.873 21.939-17.985 35.318l-19.611 124.186c-1.386 8.771-0.942 17.299 1.006 25.261 7.597 31.037 38.083 53.44 72.291 48.038l124.184-19.613c13.379-2.112 25.744-8.41 35.322-17.984l530.467-530.47c12.512-12.513 18.768-28.914 18.768-45.316 0-10.379-2.506-20.757-7.517-30.15-2.906-5.451-6.656-10.57-11.251-15.164l-104.576-104.575zM630.675 296.979l119.658-119.658 104.576 104.574-119.658 119.657-104.576-104.573zM200.255 831.974l19.611-124.186 365.494-365.494 104.576 104.573-365.494 365.494-124.187 19.613z"],"width":1056,"attrs":[{"fill":"rgb(108, 114, 122)"}],"isMulticolor":false,"isMulticolor2":false,"colorPermutations":{"1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101":[{"f":2}]},"tags":["edit"],"grid":0},"attrs":[{"fill":"rgb(108, 114, 122)"}],"properties":{"order":227,"id":82,"name":"edit","prevSize":32,"code":59724},"setIdx":0,"setId":4,"iconIdx":88},{"icon":{"paths":["M864 512c0-194.404-157.597-352-352-352s-352 157.596-352 352c0 194.403 157.596 352 352 352s352-157.597 352-352zM928 512c0 229.75-186.25 416-416 416s-416-186.25-416-416c0-229.75 186.25-416 416-416s416 186.25 416 416zM400.166 480c-35.347 0-64-28.653-64-64s28.653-64 64-64c35.347 0 64 28.653 64 64s-28.653 64-64 64zM688.166 416c0-35.347-28.653-64-64-64s-64 28.653-64 64c0 35.347 28.653 64 64 64s64-28.653 64-64zM353.226 720c8.963 0 17.526-3.53 24.272-9.437 21.603-18.918 42.64-31.958 62.701-40.509 36.563-15.59 71.805-17.107 104.794-9.939 37.632 8.176 72.659 27.808 102.109 51.341 6.726 5.376 14.995 8.544 23.606 8.544 30.218 0 45.616-34.256 22.397-53.594-36.912-30.742-82.79-57.59-134.522-68.832-44.794-9.734-93.67-7.632-143.485 13.606-28.79 12.275-57.232 30.653-85.024 55.933-21.572 19.619-6.010 52.886 23.152 52.886z"],"attrs":[{"fill":"rgb(108, 114, 122)"}],"isMulticolor":false,"isMulticolor2":false,"colorPermutations":{"1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101":[{"f":2}]},"tags":["emoji-bad-mood"],"grid":0},"attrs":[{"fill":"rgb(108, 114, 122)"}],"properties":{"order":228,"id":83,"name":"emoji-bad-mood","prevSize":32,"code":59725},"setIdx":0,"setId":4,"iconIdx":89},{"icon":{"paths":["M864 512c0-194.404-157.597-352-352-352s-352 157.596-352 352c0 194.403 157.596 352 352 352s352-157.597 352-352zM928 512c0 229.75-186.25 416-416 416s-416-186.25-416-416c0-229.75 186.25-416 416-416s416 186.25 416 416zM400.166 480c-35.347 0-64-28.653-64-64s28.653-64 64-64c35.347 0 64 28.653 64 64s-28.653 64-64 64zM688.166 416c0-35.347-28.653-64-64-64s-64 28.653-64 64c0 35.347 28.653 64 64 64s64-28.653 64-64zM384 640h256c17.674 0 32 14.326 32 32s-14.326 32-32 32h-256c-17.674 0-32-14.326-32-32s14.326-32 32-32z"],"attrs":[{"fill":"rgb(108, 114, 122)"}],"isMulticolor":false,"isMulticolor2":false,"colorPermutations":{"1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101":[{"f":2}]},"tags":["emoji-neutral-mood"],"grid":0},"attrs":[{"fill":"rgb(108, 114, 122)"}],"properties":{"order":229,"id":84,"name":"emoji-neutral-mood","prevSize":32,"code":59726},"setIdx":0,"setId":4,"iconIdx":90},{"icon":{"paths":["M330.074 644.886c-21.572-19.619-6.010-52.886 23.152-52.886 8.963 0 17.526 3.53 24.272 9.437 21.603 18.918 42.64 31.958 62.701 40.509 36.563 15.59 71.805 17.107 104.794 9.939 37.632-8.176 72.659-27.808 102.109-51.341 6.726-5.376 14.995-8.544 23.606-8.544 30.218 0 45.616 34.256 22.397 53.594-36.912 30.742-82.79 57.59-134.522 68.832-44.794 9.734-93.67 7.632-143.485-13.606-28.79-12.275-57.232-30.653-85.024-55.933z","M400.17 480c35.344 0 64-28.653 64-64s-28.656-64-64-64c-35.347 0-64 28.653-64 64s28.653 64 64 64z","M624.17 480c35.344 0 64-28.653 64-64s-28.656-64-64-64c-35.347 0-64 28.653-64 64s28.653 64 64 64z","M928 512c0 229.75-186.25 416-416 416s-416-186.25-416-416c0-229.75 186.25-416 416-416s416 186.25 416 416zM864 512c0-194.404-157.597-352-352-352s-352 157.596-352 352c0 194.403 157.596 352 352 352s352-157.597 352-352z"],"attrs":[{"fill":"rgb(108, 114, 122)"},{"fill":"rgb(108, 114, 122)"},{"fill":"rgb(108, 114, 122)"},{"fill":"rgb(108, 114, 122)"}],"isMulticolor":false,"isMulticolor2":false,"colorPermutations":{"1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101":[{"f":2},{"f":2},{"f":2},{"f":2}]},"tags":["emoji"],"grid":0},"attrs":[{"fill":"rgb(108, 114, 122)"},{"fill":"rgb(108, 114, 122)"},{"fill":"rgb(108, 114, 122)"},{"fill":"rgb(108, 114, 122)"}],"properties":{"order":230,"id":85,"name":"emoji","prevSize":32,"code":59727},"setIdx":0,"setId":4,"iconIdx":91},{"icon":{"paths":["M655.744 512c88.365 0 160-71.635 160-160s-71.635-160-160-160c-88.368 0-160 71.635-160 160 0 26.934 6.653 52.314 18.41 74.582l-11.914 11.914 0.128 0.131-299.135 299.136c-3.533 9.926-6.41 20.976-7.738 31.606-1.652 13.219-0.563 22.966 1.874 28.938 1.768 4.333 4.22 7.232 10.772 9.014 8.316 2.262 24.518 2.794 52.822-5.501 8.524-3.808 27.721-16.285 45.132-35.382 18.18-19.939 29.648-41.856 29.648-62.438 0-15.642 11.309-28.992 26.736-31.565l91.75-15.293c3.766-2.656 18.768-15.693 10.134-58.867-2.099-10.49 1.184-21.338 8.749-28.902l88.198-88.195c26.467 19.379 59.114 30.822 94.432 30.822zM439.571 410.915c-5.104-18.771-7.827-38.525-7.827-58.915 0-123.712 100.288-224 224-224 123.709 0 224 100.288 224 224s-100.291 224-224 224c-29.29 0-57.264-5.619-82.906-15.843l-43.008 43.008c7.158 65.494-23.99 104.534-55.968 115.194l-2.384 0.794-74.854 12.477c-7.085 31.405-25.174 58.122-43.235 77.933-23.123 25.357-50.958 44.627-69.762 52.15l-1.324 0.528-1.365 0.41c-34.714 10.416-64.73 13.19-89.594 6.429-26.782-7.286-44.33-24.784-53.228-46.586-8.23-20.163-8.473-42.282-6.126-61.062 2.402-19.216 7.899-37.958 14.042-53.315 1.61-4.022 4.020-7.677 7.084-10.742l286.456-286.458zM623.744 336c0 26.509 21.488 48 48 48 26.509 0 48-21.491 48-48s-21.491-48-48-48c-26.512 0-48 21.49-48 48z"],"width":1056,"attrs":[{"fill":"rgb(108, 114, 122)"}],"isMulticolor":false,"isMulticolor2":false,"colorPermutations":{"1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101":[{"f":2}]},"tags":["encrypted"],"grid":0},"attrs":[{"fill":"rgb(108, 114, 122)"}],"properties":{"order":231,"id":86,"name":"encrypted","prevSize":32,"code":59728},"setIdx":0,"setId":4,"iconIdx":92},{"icon":{"paths":["M536.89 64c233.677 0 423.11 189.433 423.11 423.11h-423.11v-423.11z","M64 536.89c0-233.678 189.433-423.112 423.11-423.112v423.112h423.11c0 233.677-189.43 423.11-423.11 423.11-233.677 0-423.11-189.434-423.11-423.11z"],"attrs":[{"fill":"rgb(108, 114, 122)"},{"fill":"rgb(108, 114, 122)"}],"isMulticolor":false,"isMulticolor2":false,"colorPermutations":{"1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101":[{"f":2},{"f":2}]},"tags":["engagement-dashboard"],"grid":0},"attrs":[{"fill":"rgb(108, 114, 122)"},{"fill":"rgb(108, 114, 122)"}],"properties":{"order":232,"id":87,"name":"engagement-dashboard","prevSize":32,"code":59729},"setIdx":0,"setId":4,"iconIdx":93},{"icon":{"paths":["M416 586.445v177.232l75.514-62.928c11.869-9.888 29.104-9.888 40.973 0l75.514 62.928v-177.232c-29.098 13.821-61.645 21.555-96 21.555s-66.902-7.734-96-21.555zM352 540.768v291.232c0 12.416 7.184 23.712 18.426 28.979 11.245 5.267 24.522 3.552 34.061-4.397l107.514-89.594 107.514 89.594c9.539 7.949 22.816 9.664 34.061 4.397 11.242-5.267 18.426-16.563 18.426-28.979v-291.232c39.59-40.403 64-95.734 64-156.768 0-123.712-100.288-224.001-224-224.001s-224 100.288-224 224.001c0 61.034 24.41 116.365 64 156.768zM416 512.013l-0.016-0.013c-38.854-29.19-63.984-75.661-63.984-128 0-88.366 71.635-160.001 160-160.001s160 71.635 160 160.001c0 52.339-25.13 98.81-63.984 128l-0.016 0.013c-26.742 20.083-59.981 31.987-96 31.987s-69.258-11.904-96-31.987z"],"attrs":[{"fill":"rgb(108, 114, 122)"}],"isMulticolor":false,"isMulticolor2":false,"colorPermutations":{"1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101":[{"f":2}]},"tags":["enterprise-feature"],"grid":0},"attrs":[{"fill":"rgb(108, 114, 122)"}],"properties":{"order":233,"id":88,"name":"enterprise-feature","prevSize":32,"code":59730},"setIdx":0,"setId":4,"iconIdx":94},{"icon":{"paths":["M500.64 128c213.35 0 386.336 172.985 386.336 386.336 0 192.87-141.254 352.707-325.974 381.664v-269.958h90.022l17.114-111.706h-107.136v-72.47c0-11.462 2.102-22.822 7.274-32.544 2.474-4.656 5.651-8.934 9.635-12.666 9.866-9.245 24.688-15.152 46.058-15.152h48.733v-95.080c0 0-44.224-7.552-86.493-7.552-84.698 0-141.258 49.313-145.651 138.907-0.182 3.738-0.275 7.546-0.275 11.424v85.133h-98.122v111.706h98.122v269.958c-184.721-29.011-325.978-188.848-325.978-381.664 0-213.351 172.985-386.336 386.336-386.336z"],"width":1056,"attrs":[{"fill":"rgb(108, 114, 122)"}],"isMulticolor":false,"isMulticolor2":false,"colorPermutations":{"1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101":[{"f":2}]},"tags":["facebook-monochromatic"],"grid":0},"attrs":[{"fill":"rgb(108, 114, 122)"}],"properties":{"order":30,"id":89,"name":"facebook-monochromatic","prevSize":32,"code":59731},"setIdx":0,"setId":4,"iconIdx":95},{"icon":{"paths":["M373.334 448c0-17.674 14.326-32 32-32h213.331c17.674 0 32 14.326 32 32s-14.326 32-32 32h-213.331c-17.674 0-32-14.326-32-32z","M405.334 544c-17.674 0-32 14.326-32 32s14.326 32 32 32h213.331c17.674 0 32-14.326 32-32s-14.326-32-32-32h-213.331z","M256 128c-17.673 0-32 14.327-32 32v704c0 17.674 14.327 32 32 32h512c17.674 0 32-14.326 32-32v-501.744c0-6.672-2.083-13.174-5.962-18.602l-144.467-202.254c-6.006-8.409-15.706-13.4-26.038-13.4h-367.533zM736 372.509v459.491h-448v-640h319.066l128.934 180.509z"],"attrs":[{"fill":"rgb(108, 114, 122)"},{"fill":"rgb(108, 114, 122)"},{"fill":"rgb(108, 114, 122)"}],"isMulticolor":false,"isMulticolor2":false,"colorPermutations":{"1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101":[{"f":2},{"f":2},{"f":2}]},"tags":["file-document"],"grid":0},"attrs":[{"fill":"rgb(108, 114, 122)"},{"fill":"rgb(108, 114, 122)"},{"fill":"rgb(108, 114, 122)"}],"properties":{"order":29,"id":90,"name":"file-document","prevSize":32,"code":59732},"setIdx":0,"setId":4,"iconIdx":96},{"icon":{"paths":["M128 192c-17.673 0-32 14.327-32 32v576.019c0 17.674 14.327 32 32 32h768c17.674 0 32-14.326 32-32v-576.019c0-17.673-14.326-32-32-32h-768zM160 378.301v-122.301h149.333v122.301h-149.333zM160 442.301h149.333v141.722h-149.333v-141.722zM160 648.022h149.333v119.997h-149.333v-119.997zM373.334 768.019v-119.997h490.666v119.997h-490.666zM864 584.022h-490.666v-141.722h490.666v141.722zM864 378.301h-490.666v-122.301h490.666v122.301z"],"attrs":[{"fill":"rgb(108, 114, 122)"}],"isMulticolor":false,"isMulticolor2":false,"colorPermutations":{"1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101":[{"f":2}]},"tags":["file-sheet"],"grid":0},"attrs":[{"fill":"rgb(108, 114, 122)"}],"properties":{"order":28,"id":91,"name":"file-sheet","prevSize":32,"code":59733},"setIdx":0,"setId":4,"iconIdx":97},{"icon":{"paths":["M206.667 293.692c-30.421-42.402-0.116-101.442 52.070-101.442h518.572c51.174 0 81.706 57.026 53.334 99.615l-180.678 271.207v220.762c0 46.352-47.693 77.373-90.067 58.582l-121.424-53.85c-23.168-10.275-38.102-33.238-38.102-58.582v-166.304l-193.704-269.988zM777.309 256.334h-518.572l193.705 269.989c7.811 10.89 12.013 23.955 12.013 37.357v166.304l121.424 53.85v-220.762c0-12.646 3.741-25.008 10.752-35.533l180.678-271.205z"],"width":1056,"attrs":[{"fill":"rgb(108, 114, 122)"}],"isMulticolor":false,"isMulticolor2":false,"colorPermutations":{"1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101":[{"f":2}]},"tags":["filter"],"grid":0},"attrs":[{"fill":"rgb(108, 114, 122)"}],"properties":{"order":27,"id":92,"name":"filter","prevSize":32,"code":59734},"setIdx":0,"setId":4,"iconIdx":98},{"icon":{"paths":["M623.091 896c-2.806 0-5.296-0.624-7.168-0.938-62-16.858-102.816-39.648-145.811-80.858-55.149-54.010-85.37-125.814-85.37-202.301 0-65.875 56.704-119.571 126.496-119.571s126.496 53.696 126.496 119.571c0 34.963 31.469 63.373 69.792 63.373s69.789-28.41 69.789-63.373c0-135.808-119.642-246.637-266.701-246.637-104.998 0-200.338 57.133-243.334 145.795-14.332 29.347-21.498 63.066-21.498 100.842 0 28.72 2.492 73.363 24.925 131.744 2.804 7.181 2.492 14.675-0.623 21.856-3.116 6.867-9.035 11.862-16.201 14.358-2.804 1.251-6.231 1.562-9.659 1.562-11.84 0-22.433-7.181-26.484-18.106-19.005-50.266-28.353-99.904-28.353-151.728 0-46.205 9.036-88.352 26.795-125.504 52.343-108.019 167.936-177.638 294.432-177.638 178.528 0 323.718 135.804 323.718 302.828 0 65.875-56.704 119.571-126.806 119.571s-126.81-53.696-126.81-119.571c0-34.963-31.469-63.373-69.792-63.373-38.634 0-69.789 28.41-69.789 63.373 0 61.504 24.301 119.261 68.544 162.342 35.206 34.029 68.858 53.072 120.576 67.123 7.168 1.872 13.398 6.557 17.136 13.11 3.741 6.557 4.675 14.362 2.806 21.229-2.806 11.866-14.022 20.918-27.107 20.918zM426.803 888.195c-7.789 0-15.267-3.123-20.253-8.742-33.338-32.781-51.718-54.010-77.891-100.525-26.795-47.142-41.127-105.21-41.127-167.338 0-116.448 100.948-211.357 224.951-211.357s224.954 94.909 224.954 211.357c0 15.61-12.464 28.096-28.352 28.096-15.891 0-28.666-12.173-28.666-28.096 0-85.542-75.398-155.162-168.246-155.162s-168.246 69.619-168.246 155.162c0 52.448 11.84 100.528 34.272 139.552 23.366 41.52 38.947 59.005 68.858 88.662 10.902 11.238 10.902 28.723 0 39.648-5.92 5.933-13.088 8.742-20.253 8.742zM699.738 818.886c-47.36 0-88.797-11.862-123.382-34.963-59.197-39.651-94.717-103.962-94.717-172.333 0-15.61 12.464-28.099 28.352-28.099 15.891 0 28.355 12.49 28.355 28.099 0 49.638 26.17 96.781 69.789 125.501 25.238 16.861 55.149 24.976 91.603 24.976 7.789 0 22.432-0.934 38.010-3.744 1.558-0.314 3.427-0.314 4.986-0.314 13.709 0 25.238 9.99 27.728 23.414 1.248 7.181-0.31 14.672-4.362 20.605-4.362 6.243-10.902 10.614-18.694 11.862-23.366 4.685-43.93 4.995-47.667 4.995zM188.765 435.824c-5.608 0-11.217-1.562-16.202-4.995-6.543-4.058-10.593-10.614-12.151-18.106-1.246-7.494 0.311-14.986 4.985-21.232 38.635-53.696 87.862-95.843 146.125-125.501 60.132-30.595 129.613-46.829 200.961-46.829 71.037 0 140.205 15.922 200.029 46.205 58.573 29.659 107.802 71.492 146.125 124.567 4.362 5.93 6.23 13.424 4.986 20.915-1.248 7.494-5.61 14.048-11.84 18.419-4.986 3.437-10.595 4.995-16.515 4.995-9.034 0-17.757-4.371-23.056-11.862-33.338-45.891-75.709-82.109-125.562-107.083-52.342-26.224-112.787-40.273-174.477-40.273-62.314 0-122.758 14.049-175.101 40.585-49.852 25.913-92.537 62.128-126.186 108.643-3.739 6.87-12.463 11.552-22.121 11.552zM733.696 239.141c-4.672 0-9.347-1.249-13.395-3.434-71.35-35.902-133.354-51.512-207.504-51.512-74.467 0-144.256 17.483-207.817 51.824-4.050 2.185-8.724 3.122-13.397 3.122-10.282 0-19.629-5.62-24.925-14.361-3.739-6.556-4.673-14.361-2.492-21.541s7.166-13.424 13.709-16.859c72.594-38.712 151.733-58.38 234.924-58.38 82.563 0 154.848 17.795 233.987 58.068 6.854 3.434 11.84 9.366 14.33 16.859 2.182 7.18 1.248 14.673-2.179 21.229-4.986 9.054-14.643 14.985-25.238 14.985z"],"attrs":[{"fill":"rgb(108, 114, 122)"}],"isMulticolor":false,"isMulticolor2":false,"colorPermutations":{"1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101":[{"f":2}]},"tags":["fingerprint"],"grid":0},"attrs":[{"fill":"rgb(108, 114, 122)"}],"properties":{"order":26,"id":93,"name":"fingerprint","prevSize":32,"code":59735},"setIdx":0,"setId":4,"iconIdx":99},{"icon":{"paths":["M266.667 170.664c0-17.673 14.327-32 32-32h500.623c12.122 0 23.2 6.848 28.621 17.689s4.253 23.814-3.021 33.511l-122.134 162.843 122.134 162.845c7.274 9.696 8.442 22.672 3.021 33.51-5.421 10.842-16.499 17.69-28.621 17.69h-468.624v286.579c0 17.674-14.325 32-31.999 32s-32-14.326-32-32v-682.667zM330.666 502.752h404.624l-98.134-130.845c-8.531-11.376-8.531-27.021 0-38.4l98.134-130.843h-404.624v300.088z"],"attrs":[{"fill":"rgb(108, 114, 122)"}],"isMulticolor":false,"isMulticolor2":false,"colorPermutations":{"1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101":[{"f":2}]},"tags":["flag"],"grid":0},"attrs":[{"fill":"rgb(108, 114, 122)"}],"properties":{"order":25,"id":94,"name":"flag","prevSize":32,"code":59736},"setIdx":0,"setId":4,"iconIdx":100},{"icon":{"paths":["M140.020 213.336c0-17.673 14.327-32 32-32h234.057c7.181 0 14.157 2.416 19.798 6.86l88.813 69.94h339.997c17.674 0 32 14.327 32 32v520.533c0 17.674-14.326 32-32 32h-682.665c-17.673 0-32-14.326-32-32v-597.333zM204.020 245.336v533.333h618.665v-456.534h-319.085c-7.181 0-14.154-2.415-19.798-6.858l-88.813-69.94h-190.969z"],"width":1056,"attrs":[{"fill":"rgb(108, 114, 122)"}],"isMulticolor":false,"isMulticolor2":false,"colorPermutations":{"1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101":[{"f":2}]},"tags":["folder"],"grid":0},"attrs":[{"fill":"rgb(108, 114, 122)"}],"properties":{"order":24,"id":95,"name":"folder","prevSize":32,"code":59737},"setIdx":0,"setId":4,"iconIdx":101},{"icon":{"paths":["M370.704 544c0-17.674-14.326-32-32-32s-31.999 14.326-31.999 32v32h-32c-17.673 0-32 14.326-32 32s14.327 32 32 32h32v32c0 17.674 14.325 32 31.999 32s32-14.326 32-32v-32h32c17.674 0 32-14.326 32-32s-14.326-32-32-32h-32v-32z","M746.704 624c30.928 0 56-25.072 56-56s-25.072-56-56-56c-30.928 0-56 25.072-56 56s25.072 56 56 56z","M674.704 664c0 30.928-25.072 56-56 56s-56-25.072-56-56c0-30.928 25.072-56 56-56s56 25.072 56 56z","M706.704 128c0-17.673-14.326-32-32-32s-32 14.327-32 32v96h-128c-17.674 0-32 14.327-32 32v96h-191.999c-106.038 0-192 85.962-192 192v128c0 106.038 85.961 192 192 192h447.999c106.038 0 192-85.962 192-192v-128c0-106.038-85.962-192-192-192h-192v-64h128c17.674 0 32-14.327 32-32v-128zM866.704 544v128c0 70.691-57.306 128-128 128h-447.999c-70.692 0-128-57.309-128-128v-128c0-70.691 57.308-128 128-128h447.999c70.694 0 128 57.309 128 128z"],"width":1056,"attrs":[{"fill":"rgb(108, 114, 122)"},{"fill":"rgb(108, 114, 122)"},{"fill":"rgb(108, 114, 122)"},{"fill":"rgb(108, 114, 122)"}],"isMulticolor":false,"isMulticolor2":false,"colorPermutations":{"1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101":[{"f":2},{"f":2},{"f":2},{"f":2}]},"tags":["game"],"grid":0},"attrs":[{"fill":"rgb(108, 114, 122)"},{"fill":"rgb(108, 114, 122)"},{"fill":"rgb(108, 114, 122)"},{"fill":"rgb(108, 114, 122)"}],"properties":{"order":23,"id":96,"name":"game","prevSize":32,"code":59738},"setIdx":0,"setId":4,"iconIdx":102},{"icon":{"paths":["M494.31 170.648l23.037 24.005 23.629-24.005 74.669 0.041v75.854h74.669v75.856h74.666v26.518h0.003v504.419h-522.678v-682.689h252.006zM540.976 246.503h-224.004v530.979h373.341v-379.229h-149.338v-151.75z"],"width":1056,"attrs":[{"fill":"rgb(108, 114, 122)"}],"isMulticolor":false,"isMulticolor2":false,"colorPermutations":{"1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101":[{"f":2}]},"tags":["giphy-monochromatic"],"grid":0},"attrs":[{"fill":"rgb(108, 114, 122)"}],"properties":{"order":21,"id":97,"name":"giphy-monochromatic","prevSize":32,"code":59739},"setIdx":0,"setId":4,"iconIdx":103},{"icon":{"paths":["M634.803 170.664h-256.112l-264.387 460.802 124.984 221.866h534.916l124.982-221.866-264.384-460.802zM367.814 631.466l138.931-239.789 138.934 239.789h-277.866z"],"width":1056,"attrs":[{"fill":"rgb(108, 114, 122)"}],"isMulticolor":false,"isMulticolor2":false,"colorPermutations":{"1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101":[{"f":2}]},"tags":["google-drive-monochromatic"],"grid":0},"attrs":[{"fill":"rgb(108, 114, 122)"}],"properties":{"order":19,"id":98,"name":"google-drive-monochromatic","prevSize":32,"code":59756},"setIdx":0,"setId":4,"iconIdx":104},{"icon":{"paths":["M129.354 272c0-17.673 14.327-32 32-32h704c17.674 0 32 14.327 32 32s-14.326 32-32 32h-704c-17.673 0-32-14.327-32-32zM289.354 432c0 17.674-14.327 32-32 32s-32-14.326-32-32c0-17.674 14.327-32 32-32s32 14.326 32 32zM289.354 752c0 17.674-14.327 32-32 32s-32-14.326-32-32c0-17.674 14.327-32 32-32s32 14.326 32 32zM449.354 624c17.674 0 32-14.326 32-32s-14.326-32-32-32c-17.674 0-32 14.326-32 32s14.326 32 32 32zM385.354 400c-17.674 0-32 14.326-32 32s14.326 32 32 32h480c17.674 0 32-14.326 32-32s-14.326-32-32-32h-480zM353.354 752c0-17.674 14.326-32 32-32h480c17.674 0 32 14.326 32 32s-14.326 32-32 32h-480c-17.674 0-32-14.326-32-32zM577.354 560c-17.674 0-32 14.326-32 32s14.326 32 32 32h288c17.674 0 32-14.326 32-32s-14.326-32-32-32h-288z"],"width":1056,"attrs":[{"fill":"rgb(108, 114, 122)"}],"isMulticolor":false,"isMulticolor2":false,"colorPermutations":{"1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101":[{"f":2}]},"tags":["group-by-type"],"grid":0},"attrs":[{"fill":"rgb(108, 114, 122)"}],"properties":{"order":150,"id":99,"name":"group-by-type","prevSize":32,"code":59757},"setIdx":0,"setId":4,"iconIdx":105},{"icon":{"paths":["M170.668 245.336c0-17.673 14.327-32 32-32h640.846c17.674 0 32 14.327 32 32s-14.326 32-32 32h-640.846c-17.673 0-32-14.327-32-32zM170.668 501.334c0-17.67 14.327-32 32-32h640.846c17.674 0 32 14.33 32 32 0 17.674-14.326 32-32 32h-640.846c-17.673 0-32-14.326-32-32zM170.668 757.334c0-17.67 14.327-32 32-32h640.846c17.674 0 32 14.33 32 32 0 17.674-14.326 32-32 32h-640.846c-17.673 0-32-14.326-32-32z"],"attrs":[{"fill":"rgb(108, 114, 122)"}],"isMulticolor":false,"isMulticolor2":false,"colorPermutations":{"1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101":[{"f":2}]},"tags":["hamburguer"],"grid":0},"attrs":[{"fill":"rgb(108, 114, 122)"}],"properties":{"order":148,"id":100,"name":"hamburguer","prevSize":32,"code":59758},"setIdx":0,"setId":4,"iconIdx":106},{"icon":{"paths":["M832 512c0 176.73-143.27 320-320 320s-320-143.27-320-320h-64c0 212.077 171.923 384 384 384s384-171.923 384-384c0-212.077-171.923-384-384-384-123.718 0-233.772 58.508-304 149.364v-101.364c0-17.673-14.327-32-32-32s-32 14.327-32 32v192c0 17.674 14.327 32 32 32h176c17.674 0 32-14.326 32-32s-14.326-32-32-32h-107.295c57.24-86.756 155.583-144 267.295-144 176.73 0 320 143.27 320 320z","M544 320c0-17.673-14.326-32-32-32s-32 14.327-32 32v224c0 8.486 3.373 16.627 9.373 22.627l96 96c12.496 12.496 32.758 12.496 45.254 0s12.496-32.758 0-45.254l-86.627-86.627v-210.746z"],"attrs":[{"fill":"rgb(108, 114, 122)"},{"fill":"rgb(108, 114, 122)"}],"isMulticolor":false,"isMulticolor2":false,"colorPermutations":{"1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101":[{"f":2},{"f":2}]},"tags":["history"],"grid":0},"attrs":[{"fill":"rgb(108, 114, 122)"},{"fill":"rgb(108, 114, 122)"}],"properties":{"order":145,"id":101,"name":"history","prevSize":32,"code":59759},"setIdx":0,"setId":4,"iconIdx":107},{"icon":{"paths":["M522.042 195.354l224.464 260.044v366.234c0 5.856-4.752 10.605-10.614 10.605h-136.416v-149.821c0-23.424-19.014-42.413-42.47-42.413h-85.955c-23.453 0-42.467 18.989-42.467 42.413v149.821h-136.41c-5.864 0-10.617-4.749-10.617-10.605v-366.307l224.403-259.971c4.237-4.907 11.85-4.907 16.083 0zM560.57 895.856h175.322c41.043 0 74.32-33.232 74.32-74.224v-244.205h56.227c12.454 0 23.766-7.251 28.954-18.557 5.19-11.309 3.302-24.602-4.829-34.022l-320.272-371.033c-29.648-34.347-82.934-34.347-112.582 0l-320.27 371.033c-8.132 9.421-10.020 22.714-4.831 34.022 5.188 11.306 16.501 18.557 28.956 18.557h56.288v244.205c0 40.992 33.274 74.224 74.32 74.224h175.316c1.174 0.096 2.362 0.147 3.562 0.147h85.955c1.2 0 2.39-0.051 3.565-0.147z"],"width":1056,"attrs":[{"fill":"rgb(108, 114, 122)"}],"isMulticolor":false,"isMulticolor2":false,"colorPermutations":{"1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101":[{"f":2}]},"tags":["home"],"grid":0},"attrs":[{"fill":"rgb(108, 114, 122)"}],"properties":{"order":146,"id":102,"name":"home","prevSize":32,"code":59760},"setIdx":0,"setId":4,"iconIdx":108},{"icon":{"paths":["M406.685 405.334c0 47.126-38.205 85.331-85.334 85.331-47.127 0-85.332-38.205-85.332-85.331 0-47.13 38.205-85.334 85.332-85.334 47.13 0 85.334 38.205 85.334 85.334z","M97.352 192c0-17.673 14.327-32 32-32h767.999c17.674 0 32 14.327 32 32v640c0 17.674-14.326 32-32 32h-767.999c-17.673 0-32-14.326-32-32v-640zM161.352 764.163l151.704-176.989c9.271-10.813 24.57-14.202 37.536-8.307l153.123 69.603 160.474-204.24c6.010-7.651 15.174-12.15 24.902-12.23s18.963 4.272 25.098 11.821l151.162 186.048v-405.869h-703.999v540.163zM214.927 800h650.423v-68.64l-175.581-216.099-166.781 212.269-176.989-80.448-131.073 152.918z"],"width":1056,"attrs":[{"fill":"rgb(108, 114, 122)"},{"fill":"rgb(108, 114, 122)"}],"isMulticolor":false,"isMulticolor2":false,"colorPermutations":{"1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101":[{"f":2},{"f":2}]},"tags":["image"],"grid":0},"attrs":[{"fill":"rgb(108, 114, 122)"},{"fill":"rgb(108, 114, 122)"}],"properties":{"order":147,"id":103,"name":"image","prevSize":32,"code":59761},"setIdx":0,"setId":4,"iconIdx":109},{"icon":{"paths":["M512 873.027c-199.389 0-361.026-161.635-361.026-361.024s161.636-361.026 361.026-361.026c199.389 0 361.024 161.637 361.024 361.026s-161.635 361.024-361.024 361.024zM512 938.669c235.642 0 426.666-191.024 426.666-426.666s-191.024-426.667-426.666-426.667c-235.642 0-426.667 191.025-426.667 426.667s191.025 426.666 426.667 426.666zM544.819 347.901c0 18.125-14.694 32.819-32.819 32.819-18.128 0-32.822-14.694-32.822-32.819 0-18.128 14.694-32.821 32.822-32.821 18.125 0 32.819 14.693 32.819 32.821zM512 413.542c-18.128 0-32.822 14.694-32.822 32.819v229.744c0 18.125 14.694 32.819 32.822 32.819 18.125 0 32.819-14.694 32.819-32.819v-229.744c0-18.125-14.694-32.819-32.819-32.819z"],"attrs":[{"fill":"rgb(108, 114, 122)"}],"isMulticolor":false,"isMulticolor2":false,"colorPermutations":{"1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101":[{"f":2}]},"tags":["info"],"grid":0},"attrs":[{"fill":"rgb(108, 114, 122)"}],"properties":{"order":144,"id":104,"name":"info","prevSize":32,"code":59762},"setIdx":0,"setId":4,"iconIdx":110},{"icon":{"paths":["M512 864c-194.404 0-352-157.597-352-352s157.596-352 352-352c194.403 0 352 157.596 352 352s-157.597 352-352 352zM512 928c229.75 0 416-186.25 416-416s-186.25-416-416-416c-229.75 0-416 186.25-416 416s186.25 416 416 416zM662.627 361.373c12.496 12.496 12.496 32.758 0 45.254l-105.373 105.373 105.373 105.373c12.496 12.496 12.496 32.758 0 45.254s-32.758 12.496-45.254 0l-105.373-105.373-105.373 105.373c-12.496 12.496-32.758 12.496-45.254 0s-12.496-32.758 0-45.254l105.373-105.373-105.373-105.373c-12.496-12.496-12.496-32.758 0-45.254s32.758-12.496 45.254 0l105.373 105.373 105.373-105.373c12.496-12.496 32.758-12.496 45.254 0z"],"attrs":[{"fill":"rgb(108, 114, 122)"}],"isMulticolor":false,"isMulticolor2":false,"colorPermutations":{"1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101":[{"f":2}]},"tags":["input-clear"],"grid":0},"attrs":[{"fill":"rgb(108, 114, 122)"}],"properties":{"order":143,"id":105,"name":"input-clear","prevSize":32,"code":59763},"setIdx":0,"setId":4,"iconIdx":111},{"icon":{"paths":["M384 288c-17.674 0-32 14.327-32 32s14.326 32 32 32h256c17.674 0 32-14.326 32-32s-14.326-32-32-32h-256z","M352 448c0-17.674 14.326-32 32-32h256c17.674 0 32 14.326 32 32s-14.326 32-32 32h-256c-17.674 0-32-14.326-32-32z","M512 640c-17.674 0-32 14.326-32 32s14.326 32 32 32c17.674 0 32-14.326 32-32s-14.326-32-32-32z","M224 224v576c0 35.347 28.654 64 64 64h448c35.347 0 64-28.653 64-64v-576c0-35.346-28.653-64-64-64h-448c-35.346 0-64 28.654-64 64zM288 800v-576h448v576h-448z"],"attrs":[{"fill":"rgb(108, 114, 122)"},{"fill":"rgb(108, 114, 122)"},{"fill":"rgb(108, 114, 122)"},{"fill":"rgb(108, 114, 122)"}],"isMulticolor":false,"isMulticolor2":false,"colorPermutations":{"1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101":[{"f":2},{"f":2},{"f":2},{"f":2}]},"tags":["instance"],"grid":0},"attrs":[{"fill":"rgb(108, 114, 122)"},{"fill":"rgb(108, 114, 122)"},{"fill":"rgb(108, 114, 122)"},{"fill":"rgb(108, 114, 122)"}],"properties":{"order":18,"id":106,"name":"instance","prevSize":32,"code":59764},"setIdx":0,"setId":4,"iconIdx":112},{"icon":{"paths":["M544 352c17.674 0 32-14.326 32-32s-14.326-32-32-32c-17.674 0-32 14.327-32 32s14.326 32 32 32zM559.61 436.995c2.755-17.456-9.162-33.843-26.618-36.598-17.456-2.758-33.843 9.162-36.598 26.618l-48 304c-2.758 17.456 9.158 33.843 26.618 36.598 17.456 2.755 33.84-9.162 36.598-26.618l48-304z"],"attrs":[{"fill":"rgb(108, 114, 122)"}],"isMulticolor":false,"isMulticolor2":false,"colorPermutations":{"1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101":[{"f":2}]},"tags":["italic"],"grid":0},"attrs":[{"fill":"rgb(108, 114, 122)"}],"properties":{"order":17,"id":107,"name":"italic","prevSize":32,"code":59765},"setIdx":0,"setId":4,"iconIdx":113},{"icon":{"paths":["M497.136 169.372c12.499 12.497 12.499 32.758 0 45.255l-35.958 35.96c1.078-0.016 2.163-0.025 3.245-0.025h191.152c115.11 0 208.426 93.316 208.426 208.426 0 115.107-93.315 208.422-208.426 208.422h-15.574v-64h15.574c79.763 0 144.426-64.659 144.426-144.422 0-79.766-64.662-144.426-144.426-144.426h-191.152c-1.030 0-2.058 0.011-3.082 0.032l35.795 35.796c12.499 12.499 12.499 32.758 0 45.258-12.496 12.496-32.758 12.496-45.254 0l-90.509-90.511c-12.496-12.497-12.496-32.758 0-45.255l90.509-90.51c12.496-12.497 32.758-12.497 45.254 0zM201.318 500.746h56.159v325.818h-59.023v-268.387h-1.909l-76.204 48.682v-54.090l80.977-52.022zM570.41 719.494c0 64.749-48.045 111.523-116.931 111.523-63.638 0-110.886-38.979-112.48-93.069h57.274c2.070 26.726 25.773 45.341 55.206 45.341 34.838 0 59.978-25.933 59.818-62.365 0.16-36.909-25.613-63.318-61.411-63.475-19.568-0.16-40.25 7.955-51.226 20.045l-53.296-8.749 17.024-168h188.998v49.318h-140.16l-9.386 86.384h1.91c12.090-14.317 35.475-24.816 61.885-24.816 59.184 0 102.774 45.181 102.774 107.862z"],"attrs":[{"fill":"rgb(108, 114, 122)"}],"isMulticolor":false,"isMulticolor2":false,"colorPermutations":{"1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101":[{"f":2}]},"tags":["jump-backward"],"grid":0},"attrs":[{"fill":"rgb(108, 114, 122)"}],"properties":{"order":16,"id":108,"name":"jump-backward","prevSize":32,"code":59766},"setIdx":0,"setId":4,"iconIdx":114},{"icon":{"paths":["M494.861 169.372c-12.496 12.497-12.496 32.758 0 45.255l35.962 35.96c-1.082-0.016-2.163-0.025-3.248-0.025h-191.149c-115.111 0-208.426 93.316-208.426 208.426 0 115.107 93.315 208.422 208.426 208.422h15.574v-64h-15.574c-79.764 0-144.426-64.659-144.426-144.422 0-79.766 64.661-144.426 144.426-144.426h191.149c1.030 0 2.061 0.011 3.085 0.032l-35.798 35.796c-12.496 12.499-12.496 32.758 0 45.258 12.499 12.496 32.758 12.496 45.258 0l90.509-90.511c12.496-12.497 12.496-32.758 0-45.255l-90.509-90.51c-12.499-12.497-32.758-12.497-45.258 0zM521.318 500.746h56.157v325.818h-59.021v-268.387h-1.91l-76.205 48.682v-54.090l80.979-52.022zM890.41 719.494c0 64.749-48.048 111.523-116.934 111.523-63.635 0-110.886-38.979-112.477-93.069h57.274c2.067 26.726 25.773 45.341 55.203 45.341 34.842 0 59.978-25.933 59.821-62.365 0.157-36.909-25.616-63.318-61.411-63.475-19.568-0.16-40.25 7.955-51.226 20.045l-53.296-8.749 17.021-168h189.002v49.318h-140.16l-9.386 86.384h1.91c12.090-14.317 35.475-24.816 61.885-24.816 59.181 0 102.774 45.181 102.774 107.862z"],"attrs":[{"fill":"rgb(108, 114, 122)"}],"isMulticolor":false,"isMulticolor2":false,"colorPermutations":{"1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101":[{"f":2}]},"tags":["jump-forward"],"grid":0},"attrs":[{"fill":"rgb(108, 114, 122)"}],"properties":{"order":15,"id":109,"name":"jump-forward","prevSize":32,"code":59767},"setIdx":0,"setId":4,"iconIdx":115},{"icon":{"paths":["M769.296 649.373c12.496 12.496 12.496 32.758 0 45.254l-192 192c-12.499 12.496-32.758 12.496-45.258 0l-192-192c-12.496-12.496-12.496-32.758 0-45.254 12.499-12.496 32.758-12.496 45.258 0l137.37 137.373v-578.746h-192v192c0 17.674-14.325 32-31.999 32s-32-14.326-32-32v-224c0-17.673 14.327-32 32-32h255.999c17.674 0 32 14.327 32 32v610.746l137.373-137.373c12.499-12.496 32.758-12.496 45.258 0z"],"attrs":[{"fill":"rgb(108, 114, 122)"}],"isMulticolor":false,"isMulticolor2":false,"colorPermutations":{"1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101":[{"f":2}]},"tags":["jump-to-message"],"grid":0},"attrs":[{"fill":"rgb(108, 114, 122)"}],"properties":{"order":14,"id":110,"name":"jump-to-message","prevSize":32,"code":59768},"setIdx":0,"setId":4,"iconIdx":116},{"icon":{"paths":["M576 256c0 35.346-28.653 64-64 64s-64-28.654-64-64c0-35.346 28.653-64 64-64s64 28.654 64 64z","M576 512c0 35.347-28.653 64-64 64s-64-28.653-64-64c0-35.347 28.653-64 64-64s64 28.653 64 64z","M576 768c0 35.347-28.653 64-64 64s-64-28.653-64-64c0-35.347 28.653-64 64-64s64 28.653 64 64z"],"attrs":[{"fill":"rgb(108, 114, 122)"},{"fill":"rgb(108, 114, 122)"},{"fill":"rgb(108, 114, 122)"}],"isMulticolor":false,"isMulticolor2":false,"colorPermutations":{"1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101":[{"f":2},{"f":2},{"f":2}]},"tags":["kebab"],"grid":0},"attrs":[{"fill":"rgb(108, 114, 122)"},{"fill":"rgb(108, 114, 122)"},{"fill":"rgb(108, 114, 122)"}],"properties":{"order":13,"id":111,"name":"kebab","prevSize":32,"code":59769},"setIdx":0,"setId":4,"iconIdx":117},{"icon":{"paths":["M160 224c-35.346 0-64 28.654-64 64v448c0 35.347 28.654 64 64 64h704c35.347 0 64-28.653 64-64v-448c0-35.346-28.653-64-64-64h-704zM160 288h704v448h-704v-448zM256 352c-17.673 0-32 14.326-32 32s14.327 32 32 32h64c17.674 0 32-14.326 32-32s-14.326-32-32-32h-64zM256 512c0-17.674 14.327-32 32-32h64c17.674 0 32 14.326 32 32s-14.326 32-32 32h-64c-17.673 0-32-14.326-32-32zM480 480c-17.674 0-32 14.326-32 32s14.326 32 32 32h64c17.674 0 32-14.326 32-32s-14.326-32-32-32h-64zM640 512c0-17.674 14.326-32 32-32h64c17.674 0 32 14.326 32 32s-14.326 32-32 32h-64c-17.674 0-32-14.326-32-32zM480 352c-17.674 0-32 14.326-32 32s14.326 32 32 32h64c17.674 0 32-14.326 32-32s-14.326-32-32-32h-64zM320 640c0-17.674 14.326-32 32-32h320c17.674 0 32 14.326 32 32s-14.326 32-32 32h-320c-17.674 0-32-14.326-32-32zM704 352c-17.674 0-32 14.326-32 32s14.326 32 32 32h64c17.674 0 32-14.326 32-32s-14.326-32-32-32h-64z"],"attrs":[{"fill":"rgb(108, 114, 122)"}],"isMulticolor":false,"isMulticolor2":false,"colorPermutations":{"1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101":[{"f":2}]},"tags":["keyboard"],"grid":0},"attrs":[{"fill":"rgb(108, 114, 122)"}],"properties":{"order":12,"id":112,"name":"keyboard","prevSize":32,"code":59770},"setIdx":0,"setId":4,"iconIdx":118},{"icon":{"paths":["M681.904 256c0-17.673-14.326-32-32-32s-32 14.327-32 32v56.875l-80.346 13.998c-17.411 3.034-29.069 19.61-26.035 37.021 3.034 17.408 19.61 29.066 37.021 26.032l69.36-12.086v69.373c-30.384 6.851-60.461 20.074-84.154 43.139-27.923 27.184-44.070 65.238-44.070 113.808 0 25.741 6.262 48.262 18.451 66.621 12.186 18.349 29.062 30.682 47.251 38 35.357 14.221 76.72 10.173 108.128-4.579l1.206-0.566c31.126-14.621 62.493-29.354 90.723-57.818 21.325-21.507 39.709-49.539 56.848-88.678 5.654 9.498 9.594 20.966 10.285 34.339 1.632 31.526-14.275 82.813-87.955 153.418-12.758 12.227-13.19 32.483-0.963 45.245 12.227 12.758 32.486 13.19 45.245 0.963 80.192-76.851 110.586-145.034 107.59-202.934-2.986-57.658-38.586-96.022-70.675-113.955-20.774-13.014-50.214-22.826-81.216-28.288-16.624-2.931-34.47-4.749-52.694-4.995v-74.243l101.718-17.722c17.411-3.034 29.066-19.61 26.032-37.019-3.034-17.411-19.606-29.066-37.018-26.032l-90.733 15.809v-45.724zM578.394 536.208c10.163-9.891 23.565-17.475 39.51-22.704v138.653c-13.443 2.547-27.485 1.731-38.634-2.752-7.712-3.104-13.658-7.757-17.824-14.029-4.157-6.262-7.766-15.997-7.766-31.216 0-33.35 10.563-54.176 24.714-67.952zM709.994 600.752c-8.682 8.755-17.782 15.706-28.090 22.074v-117.888c14.125 0.24 28.224 1.661 41.587 4.016 13.587 2.397 25.683 5.622 35.763 9.165-16.88 42.038-33.069 66.304-49.261 82.634zM247.704 565.334h98.386l-49.193-184.883-49.193 184.883zM390.851 733.562l-27.734-104.227h-132.442l-27.732 104.227c-4.544 17.078-22.073 27.242-39.152 22.694-17.079-4.544-27.24-22.070-22.696-39.152l106.323-399.598c13.494-50.714 85.462-50.713 98.956 0l106.323 399.598c4.544 17.082-5.616 34.608-22.694 39.152-17.078 4.547-34.608-5.616-39.152-22.694z"],"width":1056,"attrs":[{"fill":"rgb(108, 114, 122)"}],"isMulticolor":false,"isMulticolor2":false,"colorPermutations":{"1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101":[{"f":2}]},"tags":["language"],"grid":0},"attrs":[{"fill":"rgb(108, 114, 122)"}],"properties":{"order":11,"id":113,"name":"language","prevSize":32,"code":59771},"setIdx":0,"setId":4,"iconIdx":119},{"icon":{"paths":["M439.274 704.374c0-33.645 26.278-47.677 72.726-47.677s72.73 14.032 72.73 47.677c0 32.96-13.19 111.069-23.914 149.76-4.963 17.808-22.080 25.171-48.816 25.171s-43.85-7.363-48.813-25.174c-10.717-38.666-23.914-116.688-23.914-149.757zM616.778 870.374l0.026-0.090c6.176-22.291 12.566-53.869 17.386-83.453 4.688-28.787 8.72-60.707 8.72-82.458 0-35.891-15.811-67.802-46.797-87.114-26.035-16.227-57.152-19.926-84.112-19.926-26.957 0-58.077 3.699-84.112 19.926-30.982 19.312-46.797 51.222-46.797 87.114 0 21.805 4.035 53.728 8.726 82.515 4.819 29.578 11.21 61.123 17.379 83.392l0.026 0.086c7.402 26.557 24.982 45.664 46.87 56.467 19.472 9.61 40.426 11.834 57.907 11.834 17.485 0 38.435-2.224 57.907-11.83 21.888-10.8 39.469-29.907 46.87-56.464zM459.635 460.8c0-28.275 23.446-51.2 52.365-51.2s52.365 22.925 52.365 51.2c0 28.278-23.446 51.2-52.365 51.2s-52.365-22.922-52.365-51.2zM512 341.334c-67.478 0-122.182 53.488-122.182 119.466 0 65.981 54.704 119.469 122.182 119.469s122.182-53.488 122.182-119.469c0-65.978-54.704-119.466-122.182-119.466zM677.802 537.254c-6.662 13.792-3.882 31.11 6.278 42.573 14.086 15.894 39.85 17.939 50.205-0.602 19.642-35.174 30.806-75.526 30.806-118.426 0-136.672-113.315-247.465-253.091-247.465-139.779 0-253.092 110.793-253.092 247.465 0 42.899 11.164 83.251 30.806 118.426 10.354 18.541 36.119 16.496 50.205 0.602 10.16-11.462 12.938-28.781 6.275-42.573-11.203-23.187-17.469-49.104-17.469-76.454 0-98.97 82.054-179.199 183.274-179.199s183.27 80.229 183.27 179.199c0 27.35-6.262 53.267-17.469 76.454zM730.16 699.795c-0.304-10.659 3.542-21.072 10.928-28.762 52.771-54.957 85.094-128.902 85.094-210.234 0-169.661-140.666-307.199-314.182-307.199s-314.182 137.538-314.182 307.199c0 81.331 32.323 155.28 85.093 210.234 7.386 7.69 11.234 18.102 10.928 28.762-0.833 29.018-31.273 47.917-52.121 27.715-70.223-68.042-113.718-162.41-113.718-266.71 0-207.363 171.923-375.465 384-375.465s384 168.102 384 375.465c0 104.304-43.498 198.672-113.722 266.714-20.848 20.202-51.286 1.302-52.118-27.718z"],"attrs":[{"fill":"rgb(108, 114, 122)"}],"isMulticolor":false,"isMulticolor2":false,"colorPermutations":{"1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101":[{"f":2}]},"tags":["live-streaming"],"grid":0},"attrs":[{"fill":"rgb(108, 114, 122)"}],"properties":{"order":9,"id":115,"name":"live-streaming","prevSize":32,"code":59773},"setIdx":0,"setId":4,"iconIdx":120},{"icon":{"paths":["M700.144 361.891c4.685-24.973 34.704-34.093 49.13-13.171 31.994 46.403 50.726 102.653 50.726 163.28s-18.733 116.88-50.726 163.283c-14.426 20.922-44.445 11.802-49.13-13.174l-1.706-9.101c-1.581-8.429 0.384-17.082 4.858-24.403 20.749-33.965 32.704-73.888 32.704-116.605 0-42.714-11.955-82.637-32.704-116.605-4.474-7.318-6.438-15.971-4.858-24.4l1.706-9.104z","M320.704 395.395c-20.748 33.968-32.704 73.891-32.704 116.605 0 42.717 11.956 82.64 32.704 116.605 4.474 7.322 6.438 15.974 4.858 24.403l-1.706 9.101c-4.684 24.976-34.705 34.096-49.128 13.174-31.994-46.403-50.728-102.656-50.728-163.283s18.733-116.877 50.728-163.28c14.424-20.922 44.444-11.802 49.128 13.171l1.706 9.104c1.581 8.429-0.384 17.082-4.858 24.4z","M728.765 209.256l-0.515 2.747c-2.234 11.911 2.534 23.967 11.763 31.821 75.866 64.565 123.987 160.751 123.987 268.175 0 107.427-48.122 203.613-123.987 268.176-9.229 7.856-13.997 19.914-11.763 31.824l0.515 2.746c4.192 22.362 29.69 33.194 47.261 18.746 92.794-76.294 151.974-191.981 151.974-321.491 0-129.507-59.181-245.193-151.974-321.489-17.571-14.448-43.069-3.615-47.261 18.745z","M283.986 243.825c9.228-7.854 13.998-19.911 11.764-31.821l-0.515-2.747c-4.192-22.359-29.69-33.193-47.262-18.745-92.793 76.296-151.973 191.981-151.973 321.489 0 129.51 59.18 245.197 151.973 321.491 17.572 14.448 43.070 3.616 47.262-18.746l0.515-2.746c2.233-11.91-2.536-23.968-11.764-31.824-75.864-64.563-123.986-160.749-123.986-268.176 0-107.424 48.122-203.61 123.986-268.175z","M608 512c0 53.021-42.979 96-96 96s-96-42.979-96-96c0-53.018 42.979-96 96-96s96 42.982 96 96z"],"attrs":[{"fill":"rgb(108, 114, 122)"},{"fill":"rgb(108, 114, 122)"},{"fill":"rgb(108, 114, 122)"},{"fill":"rgb(108, 114, 122)"},{"fill":"rgb(108, 114, 122)"}],"isMulticolor":false,"isMulticolor2":false,"colorPermutations":{"1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101":[{"f":2},{"f":2},{"f":2},{"f":2},{"f":2}]},"tags":["live"],"grid":0},"attrs":[{"fill":"rgb(108, 114, 122)"},{"fill":"rgb(108, 114, 122)"},{"fill":"rgb(108, 114, 122)"},{"fill":"rgb(108, 114, 122)"},{"fill":"rgb(108, 114, 122)"}],"properties":{"order":8,"id":116,"name":"live","prevSize":32,"code":59774},"setIdx":0,"setId":4,"iconIdx":121},{"icon":{"paths":["M397.011 527.334c26.576 0 48.122-21.587 48.122-48.214s-21.546-48.214-48.122-48.214c-26.576 0-48.118 21.587-48.118 48.214s21.542 48.214 48.118 48.214z","M589.491 479.12c0 26.627-21.542 48.214-48.118 48.214s-48.122-21.587-48.122-48.214c0-26.627 21.546-48.214 48.122-48.214s48.118 21.587 48.118 48.214z","M733.853 479.12c0 26.627-21.542 48.214-48.118 48.214s-48.122-21.587-48.122-48.214c0-26.627 21.546-48.214 48.122-48.214s48.118 21.587 48.118 48.214z","M88.039 813.683l119.625 31.283c85.312 22.307 173.45-16.256 238.691-79.709 29.501 4.794 59.875 7.242 90.678 7.242 218.294 0 404.339-122.864 404.339-294.518 0-171.664-186.042-294.52-404.339-294.52-218.314 0-404.351 122.852-404.34 294.523 0 65.072 27.642 125.014 75.655 173.587-2.847 24.835-14.596 44.73-36.049 68.55l-84.261 93.562zM537.034 258.997c183.13 0 331.6 98.043 331.6 218.984 0 120.931-148.47 218.982-331.6 218.982-40.781 0-79.834-4.877-115.91-13.763-36.669 45.6-117.335 109.008-195.697 88.518 25.489-28.304 63.251-76.131 55.168-154.909-46.968-37.779-75.162-86.134-75.162-138.829-0.008-120.95 148.461-218.984 331.6-218.984z"],"width":1056,"attrs":[{"fill":"rgb(108, 114, 122)"},{"fill":"rgb(108, 114, 122)"},{"fill":"rgb(108, 114, 122)"},{"fill":"rgb(108, 114, 122)"}],"isMulticolor":false,"isMulticolor2":false,"colorPermutations":{"1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101":[{"f":2},{"f":2},{"f":2},{"f":2}]},"tags":["livechat-monochromatic"],"grid":0},"attrs":[{"fill":"rgb(108, 114, 122)"},{"fill":"rgb(108, 114, 122)"},{"fill":"rgb(108, 114, 122)"},{"fill":"rgb(108, 114, 122)"}],"properties":{"order":31,"id":117,"name":"livechat-monochromatic","prevSize":32,"code":59775},"setIdx":0,"setId":4,"iconIdx":122},{"icon":{"paths":["M341.334 778.672c-17.674 0-32 14.326-32 32s14.326 32 32 32h341.334c17.674 0 32-14.326 32-32s-14.326-32-32-32h-341.334zM85.335 298.672c0-70.692 57.308-128 128-128h597.334c70.691 0 128 57.308 128 128v298.666c0 70.694-57.309 128-128 128h-597.334c-70.692 0-128-57.306-128-128v-298.666zM213.335 234.672c-35.346 0-64 28.654-64 64v298.666c0 35.347 28.654 64 64 64h597.334c35.344 0 64-28.653 64-64v-298.666c0-35.347-28.656-64-64-64h-597.334zM256 320c0-11.782 9.551-21.333 21.333-21.333h170.667c11.782 0 21.334 9.551 21.334 21.333s-9.552 21.334-21.334 21.334h-170.667c-11.782 0-21.333-9.552-21.333-21.334zM277.333 554.666c-11.782 0-21.333 9.552-21.333 21.334s9.551 21.334 21.333 21.334h85.332c11.782 0 21.334-9.552 21.334-21.334s-9.552-21.334-21.334-21.334h-85.332zM554.666 320c0-11.782 9.552-21.333 21.334-21.333h170.666c11.782 0 21.334 9.551 21.334 21.333s-9.552 21.334-21.334 21.334h-170.666c-11.782 0-21.334-9.552-21.334-21.334zM448 554.666c-11.782 0-21.334 9.552-21.334 21.334s9.552 21.334 21.334 21.334h298.666c11.782 0 21.334-9.552 21.334-21.334s-9.552-21.334-21.334-21.334h-298.666zM256 448c0-11.782 9.551-21.334 21.333-21.334h213.332c11.782 0 21.334 9.552 21.334 21.334s-9.552 21.334-21.334 21.334h-213.332c-11.782 0-21.333-9.552-21.333-21.334zM618.666 426.666c-11.782 0-21.331 9.552-21.331 21.334s9.549 21.334 21.331 21.334h128c11.782 0 21.334-9.552 21.334-21.334s-9.552-21.334-21.334-21.334h-128z"],"attrs":[{"fill":"rgb(108, 114, 122)"}],"isMulticolor":false,"isMulticolor2":false,"colorPermutations":{"1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101":[{"f":2}]},"tags":["log-view"],"grid":0},"attrs":[{"fill":"rgb(108, 114, 122)"}],"properties":{"order":33,"id":119,"name":"log-view","prevSize":32,"code":59778},"setIdx":0,"setId":4,"iconIdx":123},{"icon":{"paths":["M176 96c-17.673 0-32 14.327-32 32v768c0 17.674 14.327 32 32 32h512c17.674 0 32-14.326 32-32v-96c0-17.674-14.326-32-32-32s-32 14.326-32 32v64h-448v-704h448v64c0 17.673 14.326 32 32 32s32-14.327 32-32v-96c0-17.673-14.326-32-32-32h-512zM521.373 329.373c12.496-12.497 32.758-12.497 45.254 0s12.496 32.758 0 45.254l-105.373 105.373h418.746c17.674 0 32 14.326 32 32s-14.326 32-32 32h-418.746l105.373 105.373c12.496 12.496 12.496 32.758 0 45.254s-32.758 12.496-45.254 0l-160-160c-12.496-12.496-12.496-32.758 0-45.254l160-160z"],"attrs":[{"fill":"rgb(108, 114, 122)"}],"isMulticolor":false,"isMulticolor2":false,"colorPermutations":{"1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101":[{"f":2}]},"tags":["login"],"grid":0},"attrs":[{"fill":"rgb(108, 114, 122)"}],"properties":{"order":34,"id":120,"name":"login","prevSize":32,"code":59779},"setIdx":0,"setId":4,"iconIdx":124},{"icon":{"paths":["M268.099 96c-17.673 0-32 14.327-32 32v768c0 17.674 14.327 32 32 32h512c17.674 0 32-14.326 32-32v-96c0-17.674-14.326-32-32-32s-32 14.326-32 32v64h-448v-704h448v64c0 17.673 14.326 32 32 32s32-14.327 32-32v-96c0-17.673-14.326-32-32-32h-512zM994.726 489.373l-160-160c-12.496-12.497-32.758-12.497-45.254 0s-12.496 32.758 0 45.254l105.373 105.373h-418.746c-17.674 0-32 14.326-32 32s14.326 32 32 32h418.746l-105.373 105.373c-12.496 12.496-12.496 32.758 0 45.254s32.758 12.496 45.254 0l160-160c12.496-12.496 12.496-32.758 0-45.254z"],"width":1056,"attrs":[{"fill":"rgb(108, 114, 122)"}],"isMulticolor":false,"isMulticolor2":false,"colorPermutations":{"1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101":[{"f":2}]},"tags":["logout"],"grid":0},"attrs":[{"fill":"rgb(108, 114, 122)"}],"properties":{"order":35,"id":121,"name":"logout","prevSize":32,"code":59780},"setIdx":0,"setId":4,"iconIdx":125},{"icon":{"paths":["M192 736h640v-448h-640v448zM128 256c0-17.673 14.327-32 32-32h704c17.674 0 32 14.327 32 32v512c0 17.674-14.326 32-32 32h-704c-17.673 0-32-14.326-32-32v-512zM305.304 389.082c-14.866-9.555-34.665-5.251-44.222 9.613-9.557 14.867-5.253 34.666 9.613 44.224l241.304 155.123 241.306-155.123c14.864-9.558 19.168-29.357 9.613-44.224-9.558-14.864-29.357-19.168-44.224-9.613l-206.694 132.877-206.696-132.877z"],"attrs":[{"fill":"rgb(108, 114, 122)"}],"isMulticolor":false,"isMulticolor2":false,"colorPermutations":{"1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101":[{"f":2}]},"tags":["mail"],"grid":0},"attrs":[{"fill":"rgb(108, 114, 122)"}],"properties":{"order":36,"id":122,"name":"mail","prevSize":32,"code":59781},"setIdx":0,"setId":4,"iconIdx":126},{"icon":{"paths":["M302.769 192h418.463l23.187 72.727h-0.211l23.792 63.999c0 41.27-31.306 69.818-64 69.818s-64-28.547-64-69.818c0-18.476-14.326-33.454-32-33.454s-32 14.978-32 33.454c0 41.27-31.306 69.818-64 69.818s-64-28.547-64-69.818c0-18.476-14.326-33.454-32-33.454s-32 14.978-32 33.454c0 41.27-31.306 69.818-64 69.818s-64-28.547-64-69.818l23.794-63.999h-0.213l23.188-72.727zM212.406 264.727l36.491-114.448c4.231-13.27 16.56-22.279 30.488-22.279h465.23c13.93 0 26.259 9.009 30.49 22.279l56.896 178.447c0 33.939-12.083 64.925-32 88.515v350.778c0 53.018-42.979 96-96 96h-384c-53.019 0-96-42.982-96-96v-350.778c-19.916-23.59-32-54.576-32-88.515l20.406-63.999zM288 458.33v309.69c0 17.67 14.327 32 32 32h128v-192.019h128v192.019h128c17.674 0 32-14.33 32-32v-309.69c-10.227 2.752-20.95 4.214-32 4.214-38.23 0-72.547-17.52-96-45.302-23.453 27.782-57.77 45.302-96 45.302s-72.547-17.52-96-45.302c-23.453 27.782-57.77 45.302-96 45.302-11.050 0-21.772-1.462-32-4.214z"],"attrs":[{"fill":"rgb(108, 114, 122)"}],"isMulticolor":false,"isMulticolor2":false,"colorPermutations":{"1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101":[{"f":2}]},"tags":["marketplace"],"grid":0},"attrs":[{"fill":"rgb(108, 114, 122)"}],"properties":{"order":37,"id":123,"name":"marketplace","prevSize":32,"code":59782},"setIdx":0,"setId":4,"iconIdx":127},{"icon":{"paths":["M773.072 576c-35.344 0-64-28.653-64-64s28.656-64 64-64c35.347 0 64 28.653 64 64s-28.653 64-64 64z","M517.072 576c-35.344 0-64-28.653-64-64s28.656-64 64-64c35.347 0 64 28.653 64 64s-28.653 64-64 64z","M261.073 576c-35.346 0-64-28.653-64-64s28.654-64 64-64c35.346 0 63.999 28.653 63.999 64s-28.652 64-63.999 64z"],"attrs":[{"fill":"rgb(108, 114, 122)"},{"fill":"rgb(108, 114, 122)"},{"fill":"rgb(108, 114, 122)"}],"isMulticolor":false,"isMulticolor2":false,"colorPermutations":{"1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101":[{"f":2},{"f":2},{"f":2}]},"tags":["meatballs"],"grid":0},"attrs":[{"fill":"rgb(108, 114, 122)"},{"fill":"rgb(108, 114, 122)"},{"fill":"rgb(108, 114, 122)"}],"properties":{"order":38,"id":124,"name":"meatballs","prevSize":32,"code":59783},"setIdx":0,"setId":4,"iconIdx":128},{"icon":{"paths":["M651.36 116.071c-57.242-19.989-105.792-20.069-138.358-20.071v64l-1.002-64c-92.006 0.342-297.345 47.824-381.242 242.995-17.897 38.97-34.758 103.344-34.758 173.005 0 69.958 17.020 146.285 52.216 207.875 23.229 40.653 92.799 131.376 191.179 173.536 94.861 40.656 206.368 52.349 296.49 16.301 16.41-6.563 24.39-25.187 17.827-41.597s-25.187-24.39-41.597-17.827c-69.878 27.952-163.171 20.445-247.51-15.699-80.819-34.64-141.384-112.451-160.821-146.464-28.804-50.41-43.784-115.418-43.784-176.125 0-60.765 15.020-116.182 29.055-146.589l0.184-0.4 0.173-0.406c69.507-162.181 243.823-204.604 323.589-204.605 31.594 0.002 70.877 0.296 117.258 16.493 46.147 16.114 101.389 48.779 161.824 116.767 43.658 49.115 63.533 114.977 69.389 177.713 5.891 63.12-2.854 118.189-11.546 142.093-6.4 17.6-20.429 45.44-59.392 45.699-18.259-0.806-72.822-14.672-83.12-69.568v-235.062c0-17.674-3.414-34.134-29.014-34.134-19.338 0-26.454 16.461-26.454 34.134v34.131c-35.181-39.859-95.402-68.266-152.746-68.266-106.038 0-192 85.962-192 192s85.962 192 192 192c62.179 0 117.658-29.555 152.746-75.386 25.715 71.078 102.57 93.027 137.014 94.134l0.515 0.016h0.512c82.643 0 111.715-64.806 120.086-87.83 12.64-34.762 21.674-99.696 15.12-169.907-6.589-70.595-29.379-151.401-85.277-214.287-66.902-75.265-131.075-114.597-188.557-134.67zM627.2 512c0 70.691-57.309 128-128 128s-128-57.309-128-128c0-70.691 57.309-128 128-128s128 57.309 128 128z"],"attrs":[{"fill":"rgb(108, 114, 122)"}],"isMulticolor":false,"isMulticolor2":false,"colorPermutations":{"1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101":[{"f":2}]},"tags":["mention"],"grid":0},"attrs":[{"fill":"rgb(108, 114, 122)"}],"properties":{"order":142,"id":125,"name":"mention","prevSize":32,"code":59784},"setIdx":0,"setId":4,"iconIdx":129},{"icon":{"paths":["M477.84 128c104.256 0 181.251 29.018 237.494 70.652l-44.317 44.317c-45.52-31.22-107.747-53.057-193.178-53.057-129.725 0-207.83 53.576-254.529 118.696-47.832 66.7-64.077 147.667-64.077 201.187v1.405l-0.127 1.398c-7.328 80.928 13.761 143.126 51.784 190.499l-43.879 43.878c-50.179-59.901-78.131-139.043-69.441-238.595 0.276-63.411 19.117-157.053 75.696-235.948 58.132-81.063 154.749-144.433 304.573-144.433zM822.848 332.499l-47.008 47.008c16.502 42.634 20.608 78.525 20.608 89.011v0.179c0 19.517 0 31.792-6.621 56.17-7.094 26.109-21.738 65.648-52.554 137.834-5.699 13.357-2.794 25.981 1.261 34.435 3.834 7.99 9.562 14.403 14.595 19.184 10.227 9.712 24.253 18.842 38.544 26.867 7.888 4.429 18.298 8.995 27.613 13.078l1.843 0.81c10.554 4.634 20.726 9.146 29.613 13.894 3.427 1.834 6.298 3.517 8.672 5.030-5.104 3.229-12.035 6.883-20.941 10.765-22.96 10.003-55.328 19.939-93.888 28.128-77.12 16.371-174.73 24.858-263.341 14.973-43.744-4.88-86.826-14.525-126.534-29.229l-47.542 47.542c52.771 23.018 110.492 36.886 167.267 43.222 96.387 10.752 200.71 1.517 282.909-15.936 41.098-8.726 77.731-19.731 105.68-31.907 13.91-6.064 26.618-12.822 36.858-20.342 9.427-6.925 20.739-17.344 25.61-32.016 7.392-22.269-2.957-40.509-12.816-51.139-9.427-10.166-22.106-17.939-32.954-23.738-11.309-6.048-23.626-11.472-33.891-15.978-11.059-4.854-18.934-8.317-24.054-11.194-9.914-5.565-17.437-10.538-22.506-14.57 27.421-64.81 42.214-104.589 50.048-133.424 8.774-32.304 8.797-50.986 8.797-72.64 0-20.106-7.053-75.501-35.267-136.019zM836.038 153.372c12.499-12.497 32.758-12.497 45.254 0 12.499 12.497 12.499 32.758 0 45.255l-682.665 682.665c-12.497 12.499-32.758 12.499-45.255 0-12.497-12.496-12.497-32.755 0-45.254l682.666-682.666z"],"attrs":[{"fill":"rgb(108, 114, 122)"}],"isMulticolor":false,"isMulticolor2":false,"colorPermutations":{"1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101":[{"f":2}]},"tags":["message-disabled"],"grid":0},"attrs":[{"fill":"rgb(108, 114, 122)"}],"properties":{"order":57,"id":126,"name":"message-disabled","prevSize":32,"code":59785},"setIdx":0,"setId":4,"iconIdx":130},{"icon":{"paths":["M223.311 308.609c-47.832 66.7-64.077 147.667-64.077 201.187v1.405l-0.127 1.398c-9.348 103.235 27.547 175.997 86.848 226.374 60.643 51.52 146.602 80.998 235.29 90.893 88.614 9.885 186.224 1.398 263.341-14.973 38.56-8.189 70.928-18.125 93.888-28.128 8.906-3.882 15.837-7.536 20.944-10.765-2.378-1.514-5.248-3.197-8.675-5.030-8.886-4.749-19.059-9.261-29.613-13.894l-1.843-0.81c-9.315-4.083-19.725-8.65-27.613-13.078-14.291-8.026-28.317-17.155-38.544-26.867-5.034-4.781-10.762-11.194-14.595-19.184-4.054-8.454-6.96-21.078-1.261-34.435 30.816-72.186 45.459-111.725 52.554-137.834 6.621-24.378 6.621-36.653 6.621-56.17v-0.179c0-15.51-8.979-86.595-53.776-152.876-43.376-64.174-121.395-125.729-264.832-125.729-129.725 0-207.83 53.576-254.529 118.696zM173.267 272.433c58.132-81.063 154.749-144.433 304.573-144.433 164.896 0 261.594 72.589 315.859 152.878 52.838 78.181 64.416 161.881 64.416 187.641 0 21.654-0.022 40.336-8.797 72.64-7.83 28.835-22.627 68.614-50.048 133.424 5.069 4.032 12.592 9.005 22.506 14.57 5.123 2.877 12.995 6.339 24.054 11.194 10.266 4.506 22.582 9.93 33.891 15.978 10.848 5.798 23.526 13.571 32.954 23.738 9.859 10.63 20.208 28.87 12.816 51.139-4.87 14.672-16.182 25.091-25.61 32.016-10.24 7.52-22.947 14.278-36.858 20.342-27.946 12.176-64.582 23.181-105.68 31.907-82.198 17.453-186.522 26.688-282.909 15.936-96.31-10.746-195.346-43.178-268.313-105.165-74.031-62.893-119.295-154.778-108.551-277.856 0.276-63.411 19.117-157.053 75.696-235.948z"],"attrs":[{"fill":"rgb(108, 114, 122)"}],"isMulticolor":false,"isMulticolor2":false,"colorPermutations":{"1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101":[{"f":2}]},"tags":["message"],"grid":0},"attrs":[{"fill":"rgb(108, 114, 122)"}],"properties":{"order":58,"id":127,"name":"message","prevSize":32,"code":59786},"setIdx":0,"setId":4,"iconIdx":131},{"icon":{"paths":["M512 128c75.546 0 138.861 52.356 155.645 122.762l-59.645 59.644v-22.405c0-53.019-42.979-96-96-96s-96 42.981-96 96v128c0 24.067 8.858 46.067 23.488 62.915l-45.322 45.325c-26.182-28.49-42.166-66.499-42.166-108.24v-128c0-88.365 71.635-160 160-160zM561.35 568.243l102.893-102.893c-15.763 48.672-54.221 87.13-102.893 102.893zM288 416c0 72.154 23.181 123.101 55.328 159.078l-45.304 45.302c-43.428-47.35-74.024-113.99-74.024-204.381 0-17.674 14.327-32 32-32s32 14.326 32 32zM478.659 650.938l-51.808 51.808c18.56 6.374 36.579 10.806 53.149 13.635v115.619h-160c-17.673 0-32 14.326-32 32s14.327 32 32 32h384c17.674 0 32-14.326 32-32s-14.326-32-32-32h-160v-115.619c44.64-7.619 99.818-26.896 147.722-64.381 60.704-47.501 108.278-123.286 108.278-236 0-17.674-14.326-32-32-32s-32 14.326-32 32c0 92.086-37.757 149.632-83.722 185.6-46.464 36.358-102.736 51.581-140.278 54.326-9.971-0.73-21.264-2.339-33.341-4.989zM825.373 153.372c12.496-12.497 32.758-12.497 45.254 0s12.496 32.758 0 45.255l-672 672c-12.497 12.496-32.758 12.496-45.255 0s-12.497-32.758 0-45.254l672-672z"],"attrs":[{"fill":"rgb(108, 114, 122)"}],"isMulticolor":false,"isMulticolor2":false,"colorPermutations":{"1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101":[{"f":2}]},"tags":["microphone-disabled"],"grid":0},"attrs":[{"fill":"rgb(108, 114, 122)"}],"properties":{"order":59,"id":128,"name":"microphone-disabled","prevSize":32,"code":59787},"setIdx":0,"setId":4,"iconIdx":132},{"icon":{"paths":["M608 288c0-53.019-42.979-96-96-96s-96 42.981-96 96v128c0 53.021 42.979 96 96 96s96-42.979 96-96v-128zM352 288c0-88.365 71.635-160 160-160s160 71.635 160 160v128c0 88.365-71.635 160-160 160s-160-71.635-160-160v-128zM256 384c17.673 0 32 14.326 32 32 0 92.086 37.757 149.632 83.722 185.6 46.464 36.358 102.736 51.581 140.278 54.326 37.542-2.746 93.814-17.968 140.278-54.326 45.962-35.968 83.722-93.514 83.722-185.6 0-17.674 14.326-32 32-32s32 14.326 32 32c0 112.714-47.574 188.499-108.278 236-47.904 37.485-103.082 56.762-147.722 64.381v115.619h160c17.674 0 32 14.326 32 32s-14.326 32-32 32h-384c-17.673 0-32-14.326-32-32s14.327-32 32-32h160v-115.619c-44.64-7.619-99.818-26.896-147.722-64.381-60.703-47.501-108.278-123.286-108.278-236 0-17.674 14.327-32 32-32z"],"attrs":[{"fill":"rgb(108, 114, 122)"}],"isMulticolor":false,"isMulticolor2":false,"colorPermutations":{"1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101":[{"f":2}]},"tags":["microphone"],"grid":0},"attrs":[{"fill":"rgb(108, 114, 122)"}],"properties":{"order":60,"id":129,"name":"microphone","prevSize":32,"code":59788},"setIdx":0,"setId":4,"iconIdx":133},{"icon":{"paths":["M234.666 170.661c0-47.128 38.205-85.333 85.333-85.333h384.001c47.126 0 85.331 38.205 85.331 85.333v682.667c0 47.13-38.205 85.334-85.331 85.334h-384.001c-47.128 0-85.333-38.205-85.333-85.334v-682.667zM298.666 170.661v682.667c0 11.782 9.551 21.334 21.333 21.334h384.001c11.782 0 21.331-9.552 21.331-21.334v-682.667c0-11.782-9.549-21.333-21.331-21.333h-96.291c-2.653 24.002-23.002 42.672-47.709 42.672h-96c-24.707 0-45.056-18.67-47.709-42.672h-96.292c-11.782 0-21.333 9.551-21.333 21.333z"],"attrs":[{"fill":"rgb(108, 114, 122)"}],"isMulticolor":false,"isMulticolor2":false,"colorPermutations":{"1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101":[{"f":2}]},"tags":["mobile"],"grid":0},"attrs":[{"fill":"rgb(108, 114, 122)"}],"properties":{"order":61,"id":130,"name":"mobile","prevSize":32,"code":59789},"setIdx":0,"setId":4,"iconIdx":134},{"icon":{"paths":["M319.247 231.566c-62.882 49.504-135.086 136.549-141.94 266.127-5.762 108.922 37.404 187.184 89.915 241.51 53.258 55.098 115.702 84.925 144.25 94.438 41.859 13.955 114.269 29.536 234.643-15.603 34.858-13.072 73.165-42.096 108.304-78.426 14.272-14.755 27.616-30.294 39.6-45.773-28.778 9.293-61.699 17.754-96.589 23.594-56.192 9.405-119.29 12.32-179.411-0.237-2.49-0.518-4.96-1.030-7.408-1.536-22.195-4.576-42.733-8.816-62.589-17.648-23.091-10.275-43.805-25.891-69.299-51.386-42.096-42.096-89.142-107.222-89.371-213.843-1.569-34.080 4.622-81.878 13.764-129.216 4.624-23.946 10.142-48.465 16.132-72.003zM345.562 138.217c32.8-17.15 63.709 15.843 53.875 45.907-12.234 37.414-24.586 85.511-33.482 131.58-9.024 46.732-13.949 88.633-12.643 114.7l0.038 0.797v0.8c0 84.915 36.173 134.918 70.627 169.373 22.509 22.509 36.835 32.278 50.064 38.166 13.229 5.885 26.832 8.717 51.085 13.763 1.923 0.4 3.917 0.816 5.978 1.248 49.914 10.426 104.534 8.336 155.76-0.237 65.434-10.954 122.384-31.981 152.374-47.386 17.562-9.021 34.992-2.47 44.477 5.965 9.661 8.589 19.376 27.152 8.886 46.954-20.637 38.95-53.645 84.419-92.182 124.259-38.211 39.51-84.317 76.038-131.834 93.859-135.626 50.858-223.216 34.438-277.354 16.394-36.515-12.173-108.392-46.912-170.026-110.675-62.381-64.534-114.684-159.427-107.81-289.373 10.79-203.979 157.583-317.098 232.165-356.093z"],"width":1056,"attrs":[{"fill":"rgb(108, 114, 122)"}],"isMulticolor":false,"isMulticolor2":false,"colorPermutations":{"1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101":[{"f":2}]},"tags":["moon"],"grid":0},"attrs":[{"fill":"rgb(108, 114, 122)"}],"properties":{"order":62,"id":131,"name":"moon","prevSize":32,"code":59790},"setIdx":0,"setId":4,"iconIdx":135},{"icon":{"paths":["M144 160c-17.673 0-32 14.327-32 32s14.327 32 32 32h192c17.674 0 32-14.327 32-32s-14.326-32-32-32h-192zM700.4 346.646c11.795-13.165 32.026-14.272 45.187-2.48 13.162 11.795 14.272 32.026 2.477 45.187l-81.222 90.646h216.358c17.674 0 32 14.326 32 32s-14.326 32-32 32h-216.358l81.222 90.646c11.795 13.162 10.685 33.392-2.477 45.187-13.162 11.792-33.392 10.685-45.187-2.48l-129.030-144c-10.893-12.154-10.893-30.554 0-42.707l129.030-144zM112 512c0-17.674 14.327-32 32-32h192c17.674 0 32 14.326 32 32s-14.326 32-32 32h-192c-17.673 0-32-14.326-32-32zM144 640c-17.673 0-32 14.326-32 32s14.327 32 32 32h192c17.674 0 32-14.326 32-32s-14.326-32-32-32h-192zM112 352c0-17.674 14.327-32 32-32h192c17.674 0 32 14.326 32 32s-14.326 32-32 32h-192c-17.673 0-32-14.326-32-32zM144 800c-17.673 0-32 14.326-32 32s14.327 32 32 32h192c17.674 0 32-14.326 32-32s-14.326-32-32-32h-192z"],"attrs":[{"fill":"rgb(108, 114, 122)"}],"isMulticolor":false,"isMulticolor2":false,"colorPermutations":{"1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101":[{"f":2}]},"tags":["move-to-the-queue"],"grid":0},"attrs":[{"fill":"rgb(108, 114, 122)"}],"properties":{"order":63,"id":132,"name":"move-to-the-queue","prevSize":32,"code":59791},"setIdx":0,"setId":4,"iconIdx":136},{"icon":{"paths":["M809.002 166.758c7.782 6.063 12.333 15.377 12.333 25.242v448h-0.112c0.074 1.734 0.112 3.478 0.112 5.229 0 69.053-57.309 125.030-128 125.030-70.694 0-128-55.978-128-125.030s57.306-125.030 128-125.030c23.312 0 45.171 6.090 64 16.73v-303.86l-384 96.798v409.136c0 69.053-57.309 125.037-128.001 125.037s-128-55.978-128-125.030c0-69.050 57.308-125.027 128-125.027 23.314 0 45.173 6.086 64 16.726v-325.777c0-14.66 9.962-27.446 24.177-31.029l448-112.93c9.568-2.411 19.709-0.276 27.491 5.788z"],"attrs":[{"fill":"rgb(108, 114, 122)"}],"isMulticolor":false,"isMulticolor2":false,"colorPermutations":{"1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101":[{"f":2}]},"tags":["musical-note"],"grid":0},"attrs":[{"fill":"rgb(108, 114, 122)"}],"properties":{"order":64,"id":133,"name":"musical-note","prevSize":32,"code":59792},"setIdx":0,"setId":4,"iconIdx":137},{"icon":{"paths":["M208 176c-35.346 0-64 28.654-64 64v576c0 35.347 28.654 64 64 64h576c35.347 0 64-28.653 64-64v-576c0-35.346-28.653-64-64-64h-576zM208 240h576v576h-576v-576zM698.627 558.15c-0.189 17.674-14.669 31.846-32.339 31.658-17.674-0.189-31.846-14.669-31.658-32.339l1.318-123.594-309.341 308.774c-12.51 12.483-32.771 12.464-45.256-0.042-12.485-12.509-12.467-32.771 0.042-45.258l309.195-308.624-123.382 1.315c-17.674 0.189-32.154-13.984-32.339-31.654-0.189-17.674 13.984-32.15 31.654-32.339l201.92-2.157c8.605-0.093 16.886 3.286 22.97 9.37 6.086 6.086 9.462 14.365 9.373 22.97l-2.157 201.92z"],"attrs":[{"fill":"rgb(108, 114, 122)"}],"isMulticolor":false,"isMulticolor2":false,"colorPermutations":{"1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101":[{"f":2}]},"tags":["new-window"],"grid":0},"attrs":[{"fill":"rgb(108, 114, 122)"}],"properties":{"order":65,"id":134,"name":"new-window","prevSize":32,"code":59793},"setIdx":0,"setId":4,"iconIdx":138},{"icon":{"paths":["M781.837 402.634c0-5.245-0.154-10.454-0.461-15.622l-63.539 63.539v265.363h-265.363l-66.979 66.979h425.363c12.944 0 24.611-7.795 29.565-19.754 4.954-11.955 2.214-25.722-6.938-34.874l-51.648-51.648v-273.984zM704.765 217.373l-45.254 45.255c-35.638-35.351-84.704-57.189-138.867-57.189-108.909 0-197.194 88.287-197.194 197.195v196.054l-64.001 64v-260.054c0-144.254 116.941-261.195 261.194-261.195 71.837 0 136.899 29.001 184.122 75.934zM433.578 808.934c0 48.086 38.982 87.066 87.066 87.066s87.066-38.979 87.066-87.066h-174.131zM854.275 190.982c-11.334-11.334-29.709-11.334-41.043 0l-612.732 612.733c-11.333 11.331-11.333 29.709 0 41.040 11.334 11.334 29.709 11.334 41.043 0l612.732-612.731c11.334-11.333 11.334-29.709 0-41.043z"],"attrs":[{"fill":"rgb(108, 114, 122)"}],"isMulticolor":false,"isMulticolor2":false,"colorPermutations":{"1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101":[{"f":2}]},"tags":["notification-disabled"],"grid":0},"attrs":[{"fill":"rgb(108, 114, 122)"}],"properties":{"order":66,"id":135,"name":"notification-disabled","prevSize":32,"code":59794},"setIdx":0,"setId":4,"iconIdx":139},{"icon":{"paths":["M601.331 808.928h-174.154c0.003 48.090 38.989 87.072 87.078 87.072s87.075-38.982 87.075-87.072zM815.597 731.677c7.274 9.696 8.442 22.669 3.021 33.51s-16.499 17.69-28.621 17.69h-565.996c-12.943 0-24.611-7.798-29.564-19.757-4.953-11.955-2.215-25.718 6.937-34.87l51.653-51.654v-274.032c0-144.272 116.957-261.228 261.229-261.228s261.229 116.956 261.229 261.228v275.629l40.112 53.485zM711.485 715.894v-313.331c0-108.926-88.304-197.228-197.229-197.228-108.928 0-197.229 88.302-197.229 197.228v313.331h394.458z"],"attrs":[{"fill":"rgb(108, 114, 122)"}],"isMulticolor":false,"isMulticolor2":false,"colorPermutations":{"1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101":[{"f":2}]},"tags":["notification"],"grid":0},"attrs":[{"fill":"rgb(108, 114, 122)"}],"properties":{"order":67,"id":136,"name":"notification","prevSize":32,"code":59795},"setIdx":0,"setId":4,"iconIdx":140},{"icon":{"paths":["M712.051 512.675v54.89c-0.195 76.736-43.741 143.283-107.44 176.445-5.53-25.67-28.362-44.906-55.683-44.906h-72.502c-31.459 0-56.963 25.501-56.963 56.963v41.427c0 31.462 25.504 56.963 56.963 56.963h72.502c28.509 0 52.128-20.944 56.307-48.288 69.261-26.829 123.962-82.883 148.966-153.030 4.723 1.27 9.69 1.946 14.813 1.946h28.483c31.459 0 56.963-25.504 56.963-56.963v-85.446c0-31.459-25.504-56.963-56.963-56.963h-28.483v-28.483c0-141.572-114.765-256.338-256.336-256.338s-256.339 114.766-256.339 256.338v23.302h-28.483c-31.46 0-56.964 25.504-56.964 56.966v103.571c0 31.459 25.504 56.963 56.964 56.963h28.482c31.46 0 56.964-25.504 56.964-56.963v-36.253h0.149c-0.099-2.576-0.148-5.165-0.148-7.766v-139.821c0-110.111 89.263-199.374 199.375-199.374s199.373 89.263 199.373 199.374v85.446z"],"width":1056,"attrs":[{"fill":"rgb(108, 114, 122)"}],"isMulticolor":false,"isMulticolor2":false,"colorPermutations":{"1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101":[{"f":2}]},"tags":["omnichannel"],"grid":0},"attrs":[{"fill":"rgb(108, 114, 122)"}],"properties":{"order":123,"id":137,"name":"omnichannel","prevSize":32,"code":59796},"setIdx":0,"setId":4,"iconIdx":141},{"icon":{"paths":["M705.578 633.158c9.507-9.235 24.701-9.018 33.939 0.486 9.238 9.507 9.021 24.701-0.486 33.939l-208.275 202.387c-9.312 9.050-24.138 9.050-33.45 0l-208.276-202.387c-9.506-9.238-9.724-24.432-0.487-33.939 9.237-9.504 24.432-9.722 33.937-0.486l191.549 186.134 191.549-186.134zM705.578 411.584c9.507 9.238 24.701 9.021 33.939-0.486s9.021-24.701-0.486-33.939l-208.275-202.385c-9.312-9.051-24.138-9.051-33.45 0l-208.276 202.385c-9.506 9.238-9.724 24.432-0.487 33.939 9.237 9.504 24.432 9.725 33.937 0.486l191.549-186.134 191.549 186.134z"],"width":1056,"attrs":[{"fill":"rgb(108, 114, 122)"}],"isMulticolor":false,"isMulticolor2":false,"colorPermutations":{"1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101":[{"f":2}]},"tags":["order"],"grid":0},"attrs":[{"fill":"rgb(108, 114, 122)"}],"properties":{"order":124,"id":138,"name":"order","prevSize":32,"code":59797},"setIdx":0,"setId":4,"iconIdx":142},{"icon":{"paths":["M742.317 652.566c12.666-12.323 12.944-32.582 0.618-45.248-12.323-12.669-32.582-12.944-45.251-0.621l44.634 45.869zM512 832.019l-22.317 22.934c12.422 12.086 32.211 12.086 44.634 0l-22.317-22.934zM326.317 606.698c-12.667-12.323-32.927-12.048-45.252 0.621-12.324 12.666-12.047 32.925 0.619 45.248l44.632-45.869zM697.683 606.698l-208 202.387 44.634 45.869 208-202.387-44.634-45.869zM534.317 809.085l-208-202.387-44.632 45.869 207.999 202.387 44.634-45.869z","M742.317 371.456c12.666 12.323 12.944 32.582 0.618 45.251-12.323 12.666-32.582 12.944-45.251 0.618l44.634-45.869zM512 192.004l-22.317-22.935c12.422-12.087 32.211-12.087 44.634 0l-22.317 22.935zM326.317 417.325c-12.668 12.326-32.927 12.048-45.252-0.618-12.325-12.669-12.048-32.928 0.619-45.251l44.633 45.869zM697.683 417.325l-208-202.386 44.634-45.87 208 202.387-44.634 45.869zM534.317 214.939l-208 202.386-44.633-45.869 207.999-202.387 44.634 45.87z"],"attrs":[{"fill":"rgb(108, 114, 122)"},{"fill":"rgb(158, 162, 168)","opacity":0.7}],"isMulticolor":true,"isMulticolor2":true,"colorPermutations":{"1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101":[{"f":2},{"f":7}]},"tags":["ordering-ascending"],"grid":0},"attrs":[{"fill":"rgb(108, 114, 122)"},{"fill":"rgb(158, 162, 168)","opacity":0.7}],"properties":{"order":125,"id":139,"name":"ordering-ascending","prevSize":32,"code":59798,"codes":[59798,59799]},"setIdx":0,"setId":4,"iconIdx":143},{"icon":{"paths":["M281.684 371.434c-12.667 12.323-12.944 32.582-0.619 45.248 12.324 12.669 32.584 12.944 45.252 0.621l-44.633-45.869zM512 191.981l22.317-22.935c-12.422-12.087-32.211-12.087-44.634 0l22.317 22.935zM697.683 417.302c12.669 12.323 32.928 12.048 45.251-0.621 12.326-12.666 12.048-32.925-0.618-45.248l-44.634 45.869zM326.317 417.302l208-202.387-44.634-45.869-207.999 202.387 44.633 45.869zM489.683 214.916l208 202.387 44.634-45.869-208-202.387-44.634 45.869z","M281.684 652.544c-12.667-12.323-12.944-32.582-0.619-45.251 12.324-12.666 32.584-12.944 45.252-0.618l-44.633 45.869zM512 831.997l22.317 22.934c-12.422 12.086-32.211 12.086-44.634 0l22.317-22.934zM697.683 606.675c12.669-12.326 32.928-12.048 45.251 0.618 12.326 12.669 12.048 32.928-0.618 45.251l-44.634-45.869zM326.317 606.675l208 202.384-44.634 45.872-207.999-202.387 44.633-45.869zM489.683 809.059l208-202.384 44.634 45.869-208 202.387-44.634-45.872z"],"attrs":[{"fill":"rgb(108, 114, 122)"},{"fill":"rgb(158, 162, 168)","opacity":0.7}],"isMulticolor":true,"isMulticolor2":true,"colorPermutations":{"1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101":[{"f":2},{"f":7}]},"tags":["ordering-descending"],"grid":0},"attrs":[{"fill":"rgb(108, 114, 122)"},{"fill":"rgb(158, 162, 168)","opacity":0.7}],"properties":{"order":126,"id":140,"name":"ordering-descending","prevSize":32,"code":59800,"codes":[59800,59801]},"setIdx":0,"setId":4,"iconIdx":144},{"icon":{"paths":["M512 928c229.75 0 416-186.25 416-416s-186.25-416-416-416c-229.752 0-416.001 186.25-416.001 416s186.25 416 416.001 416zM399.997 383.994c0-17.67 14.326-32 32-32s32 14.33 32 32v256c0 17.674-14.326 32-32 32s-32-14.326-32-32v-256zM559.997 383.994c0-17.67 14.326-32 32-32s32 14.33 32 32v256c0 17.674-14.326 32-32 32s-32-14.326-32-32v-256z"],"attrs":[{"fill":"rgb(108, 114, 122)"}],"isMulticolor":false,"isMulticolor2":false,"colorPermutations":{"1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101":[{"f":2}]},"tags":["pause-filled"],"grid":0},"attrs":[{"fill":"rgb(108, 114, 122)"}],"properties":{"order":128,"id":141,"name":"pause-filled","prevSize":32,"code":59802},"setIdx":0,"setId":4,"iconIdx":145},{"icon":{"paths":["M864 512c0-194.404-157.597-352-352-352-194.405 0-352.001 157.596-352.001 352s157.596 352 352.001 352c194.403 0 352-157.597 352-352zM928 512c0 229.75-186.25 416-416 416-229.752 0-416.001-186.25-416.001-416s186.25-416 416.001-416c229.75 0 416 186.25 416 416zM399.997 383.994v256c0 17.674 14.326 32 32 32s32-14.326 32-32v-256c0-17.67-14.326-32-32-32s-32 14.33-32 32zM559.997 383.994v256c0 17.674 14.326 32 32 32s32-14.326 32-32v-256c0-17.67-14.326-32-32-32s-32 14.33-32 32z"],"attrs":[{"fill":"rgb(108, 114, 122)"}],"isMulticolor":false,"isMulticolor2":false,"colorPermutations":{"1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101":[{"f":2}]},"tags":["pause"],"grid":0},"attrs":[{"fill":"rgb(108, 114, 122)"}],"properties":{"order":129,"id":142,"name":"pause","prevSize":32,"code":59803},"setIdx":0,"setId":4,"iconIdx":146},{"icon":{"paths":["M825.373 153.372c12.496-12.497 32.758-12.497 45.254 0s12.496 32.758 0 45.255l-672 672c-12.497 12.496-32.758 12.496-45.255 0s-12.497-32.758 0-45.254l672-672zM575.613 569.069l-44.707 44.707c7.846 5.904 15.846 11.453 23.533 16.016 9.2 5.459 22.838 12.579 36.973 13.222 16.224 0.736 31.446-5.302 42.47-10.976 11.741-6.042 22.79-13.824 31.744-20.944 1.053-0.838 2.134-1.232 2.883-1.341 0.403-0.061 0.611-0.032 0.688-0.013l121.094 59.075c-5.578 29.331-20.381 66.771-42.39 92.621-11.229 13.187-23.197 22.038-35.389 26.32-11.597 4.074-25.402 4.762-42.653-1.677-48.422-18.070-103.405-53.152-151.69-89.261-14.026-10.49-27.299-20.922-39.469-30.842l-44.387 44.39c14.186 11.683 29.84 24.061 46.445 36.48 49.808 37.248 110.448 76.56 167.261 97.76 30.275 11.299 59.149 11.238 85.197 2.086 25.45-8.938 46.090-25.786 62.246-44.755 31.866-37.424 50.634-88.144 57.059-126.246 4.72-27.987-11.776-51.77-33.136-62.192l-122.81-59.91c-24.41-11.907-51.462-6-69.834 8.611-7.008 5.571-14.544 10.739-21.443 14.291-5.574 2.867-8.931 3.741-10.314 4.003l-0.093-0.038c-0.992-0.4-3.731-1.514-8.57-4.387-3.277-1.942-6.87-4.301-10.71-7.002zM313.636 589.683l44.389-44.387c-9.92-12.17-20.349-25.44-30.838-39.466-36.109-48.285-71.192-103.267-89.263-151.69-6.437-17.251-5.748-31.056-1.675-42.652 4.283-12.192 13.134-24.16 26.319-35.388 25.849-22.012 63.291-36.814 92.622-42.392l59.075 121.095c0.019 0.077 0.048 0.285-0.013 0.688-0.109 0.752-0.502 1.83-1.341 2.883-7.12 8.954-14.902 20.003-20.944 31.744-5.674 11.024-11.715 26.246-10.976 42.47 0.643 14.134 7.763 27.773 13.222 36.973 4.563 7.686 10.109 15.683 16.013 23.53l44.707-44.707c-2.701-3.837-5.056-7.434-7.002-10.707-2.87-4.838-3.984-7.578-4.387-8.57l-0.038-0.093c0.266-1.382 1.139-4.739 4.006-10.314 3.552-6.899 8.72-14.435 14.291-21.443 14.611-18.371 20.518-45.424 8.611-69.834l-59.91-122.809c-10.422-21.362-34.205-37.856-62.192-33.137-38.103 6.425-88.824 25.194-126.246 57.060-18.972 16.155-35.817 36.795-44.757 62.246-9.15 26.048-9.211 54.921 2.087 85.196 21.202 56.813 60.513 117.453 97.761 167.261 12.417 16.605 24.795 32.256 36.478 46.442z"],"attrs":[{"fill":"rgb(108, 114, 122)"}],"isMulticolor":false,"isMulticolor2":false,"colorPermutations":{"1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101":[{"f":2}]},"tags":["phone-disabled"],"grid":0},"attrs":[{"fill":"rgb(108, 114, 122)"}],"properties":{"order":130,"id":143,"name":"phone-disabled","prevSize":32,"code":59804},"setIdx":0,"setId":4,"iconIdx":147},{"icon":{"paths":["M801.123 657.59c22.474 7.734 50.954 2.582 67.408-20.544 22.4-31.488 44.992-80.624 48.922-129.619 1.99-24.838-0.691-51.344-12.368-75.661-11.949-24.89-32.32-45.35-61.718-58.768-55.165-25.181-125.84-40.262-187.398-49.146-61.946-8.936-117.494-11.98-143.965-11.98v62.469c23.2 0 75.757 2.787 135.043 11.341 59.677 8.611 123.36 22.682 170.381 44.144 16.749 7.645 26.022 17.894 31.344 28.973 5.59 11.651 7.795 26.371 6.413 43.635-2.717 33.843-18.723 70.784-35.52 95.469l-127.398-43.853c-0.067-0.042-0.237-0.17-0.48-0.496-0.451-0.608-0.934-1.651-1.088-2.989-1.296-11.363-3.606-24.678-7.635-37.254-3.786-11.808-10.278-26.842-22.272-37.792-10.451-9.539-25.126-14.15-35.494-16.794-11.914-3.040-25.568-5.181-38.944-6.717-26.858-3.088-55.773-4.093-74.352-4.093v62.47c16.902 0 43.338 0.938 67.219 3.683 11.99 1.379 22.554 3.123 30.64 5.187 5.45 1.389 8.173 2.541 9.162 2.957l0.093 0.038c0.79 1.165 2.544 4.154 4.458 10.125 2.368 7.392 4.045 16.374 5.059 25.267 2.659 23.325 17.61 46.63 43.29 55.469l129.203 44.477zM222.875 657.584c-22.474 7.738-50.954 2.586-67.407-20.544-22.4-31.485-44.993-80.621-48.922-129.616-1.992-24.838 0.691-51.344 12.366-75.664 11.949-24.886 32.321-45.347 61.719-58.765 55.165-25.181 125.841-40.262 187.4-49.146 61.946-8.937 117.494-11.98 143.965-11.98v62.47c-23.2 0-75.757 2.787-135.046 11.341-59.674 8.608-123.358 22.682-170.378 44.144-16.748 7.645-26.024 17.894-31.343 28.973-5.593 11.651-7.797 26.371-6.412 43.635 2.714 33.843 18.721 70.784 35.518 95.469l127.4-43.856c0.067-0.038 0.234-0.166 0.477-0.493 0.454-0.611 0.938-1.651 1.091-2.989 1.296-11.363 3.606-24.682 7.635-37.254 3.786-11.808 10.275-26.842 22.272-37.792 10.448-9.542 25.126-14.15 35.491-16.794 11.917-3.040 25.568-5.181 38.947-6.717 26.858-3.088 55.77-4.093 74.349-4.093v62.47c-16.899 0-43.338 0.938-67.216 3.683-11.99 1.376-22.554 3.123-30.64 5.187-5.453 1.389-8.176 2.541-9.162 2.957l-0.093 0.038c-0.79 1.162-2.547 4.154-4.458 10.125-2.371 7.389-4.045 16.371-5.059 25.267-2.659 23.322-17.61 46.627-43.293 55.469l-129.202 44.474z"],"attrs":[{"fill":"rgb(108, 114, 122)"}],"isMulticolor":false,"isMulticolor2":false,"colorPermutations":{"1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101":[{"f":2}]},"tags":["phone-end"],"grid":0},"attrs":[{"fill":"rgb(108, 114, 122)"}],"properties":{"order":131,"id":144,"name":"phone-end","prevSize":32,"code":59805},"setIdx":0,"setId":4,"iconIdx":148},{"icon":{"paths":["M410.502 204.616c-10.419-21.362-34.202-37.856-62.189-33.137-38.104 6.425-88.825 25.194-126.247 57.060-18.972 16.155-35.817 36.795-44.757 62.246-9.15 26.048-9.211 54.919 2.087 85.195 21.202 56.816 60.513 117.456 97.761 167.264 37.482 50.122 74.609 91.552 93.329 110.269l44.173-44.173c-16.406-16.403-51.6-55.536-87.472-103.51-36.11-48.285-71.192-103.264-89.264-151.69-6.437-17.248-5.748-31.056-1.675-42.651 4.283-12.192 13.134-24.16 26.319-35.388 25.849-22.012 63.292-36.814 92.623-42.392l59.075 121.097c0.019 0.074 0.045 0.285-0.013 0.688-0.112 0.749-0.502 1.827-1.344 2.883-7.117 8.95-14.899 20-20.941 31.741-5.674 11.024-11.715 26.246-10.976 42.47 0.643 14.138 7.76 27.773 13.222 36.973 6.275 10.576 14.416 21.741 22.79 32.291 16.806 21.171 36.541 42.326 49.68 55.466l44.173-44.173c-11.952-11.952-29.981-31.309-44.928-50.134-7.504-9.453-13.738-18.157-17.997-25.334-2.87-4.838-3.984-7.578-4.387-8.57l-0.038-0.093c0.266-1.382 1.139-4.736 4.006-10.31 3.552-6.902 8.717-14.438 14.291-21.443 14.611-18.374 20.518-45.424 8.611-69.837l-59.914-122.808zM819.386 613.504c21.36 10.419 37.856 34.202 33.136 62.189-6.426 38.106-25.194 88.826-57.059 126.246-16.157 18.973-36.797 35.818-62.246 44.758-26.048 9.149-54.922 9.21-85.197-2.086-56.813-21.203-117.453-60.515-167.261-97.763-50.122-37.482-91.555-74.608-110.272-93.328l44.173-44.173c16.403 16.406 55.539 51.6 103.51 87.472 48.285 36.109 103.267 71.194 151.69 89.264 17.248 6.435 31.056 5.747 42.653 1.674 12.192-4.282 24.16-13.133 35.386-26.317 22.013-25.85 36.816-63.293 42.394-92.624l-121.094-59.075c-0.077-0.019-0.288-0.048-0.688 0.013-0.752 0.112-1.83 0.502-2.886 1.344-8.95 7.117-20 14.899-31.741 20.941-11.024 5.674-26.246 11.715-42.47 10.976-14.134-0.643-27.773-7.76-36.973-13.222-10.576-6.275-21.741-14.416-32.288-22.79-21.174-16.806-42.33-36.541-55.469-49.68l44.173-44.173c11.952 11.952 31.309 29.981 50.134 44.928 9.453 7.504 18.16 13.738 25.334 17.997 4.838 2.87 7.578 3.984 8.57 4.387l0.093 0.038c1.382-0.266 4.739-1.139 10.314-4.006 6.899-3.552 14.435-8.717 21.44-14.291 18.374-14.611 45.427-20.518 69.837-8.611l122.81 59.914z"],"attrs":[{"fill":"rgb(108, 114, 122)"}],"isMulticolor":false,"isMulticolor2":false,"colorPermutations":{"1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101":[{"f":2}]},"tags":["phone"],"grid":0},"attrs":[{"fill":"rgb(108, 114, 122)"}],"properties":{"order":68,"id":145,"name":"phone","prevSize":32,"code":59806},"setIdx":0,"setId":4,"iconIdx":149},{"icon":{"paths":["M512.608 128.019c-72.378-1.376-144.906 25.78-199.307 80.524-54.654 54.999-89.301 136.056-89.301 239.464 0 80.234 44.5 174.87 97.546 252.806 53.066 77.965 120.829 148.157 176.141 175.821l15.194 7.6 14.81-8.326c217.158-122.154 272.31-333.882 272.31-427.898 0-101.731-27.923-181.776-80.179-236.932-52.346-55.248-125.296-81.503-207.213-83.060zM288 448.006c0-88.589 29.353-152.745 70.698-194.351 41.6-41.862 97.072-62.705 152.694-61.648 68.992 1.311 124.038 23.054 161.968 63.088 38.019 40.126 62.64 102.649 62.64 192.914 0 74.55-44.691 253.677-224.176 363.030-39.683-25.61-92.186-79.853-137.37-146.237-50.954-74.864-86.454-156.221-86.454-216.797zM544 416c0-17.674-14.326-32-32-32s-32 14.326-32 32c0 17.674 14.326 32 32 32s32-14.326 32-32zM608 416c0 53.021-42.979 96-96 96s-96-42.979-96-96c0-53.021 42.979-96 96-96s96 42.979 96 96z"],"attrs":[{"fill":"rgb(108, 114, 122)"}],"isMulticolor":false,"isMulticolor2":false,"colorPermutations":{"1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101":[{"f":2}]},"tags":["pin-map"],"grid":0},"attrs":[{"fill":"rgb(108, 114, 122)"}],"properties":{"order":69,"id":146,"name":"pin-map","prevSize":32,"code":59807},"setIdx":0,"setId":4,"iconIdx":150},{"icon":{"paths":["M428.346 199.399c17.542-17.543 45.984-17.543 63.53 0 17.542 17.543 17.542 45.986 0 63.53l-43.661 43.659 177.83 177.828 60.339-60.339c24.992-24.995 65.517-24.995 90.509 0l45.254 45.254-331.869 331.869-45.254-45.254c-24.992-24.995-24.992-65.517 0-90.509l60.339-60.339-177.827-177.83-43.661 43.661c-17.543 17.542-45.987 17.542-63.53 0s-17.543-45.987 0-63.53l208.001-207.999zM175.090 362.144c-42.537 42.538-42.537 111.501 0 154.038 42.001 42.003 109.771 42.531 152.42 1.587l87.334 87.334-15.075 15.075c-49.987 49.987-49.987 131.034 0 181.021l67.882 67.882c12.496 12.496 32.758 12.496 45.254 0l167.936-167.933 88.013 92.003c12.218 12.771 32.474 13.219 45.245 1.002 12.771-12.214 13.219-32.47 1.002-45.242l-88.995-93.030 163.923-163.923c12.499-12.496 12.499-32.758 0-45.254l-67.882-67.882c-49.987-49.987-131.030-49.987-181.018 0l-15.907 15.907-87.325-87.323c41.766-42.596 41.51-110.983-0.768-153.262-42.538-42.537-111.504-42.537-154.042 0l-207.998 208z"],"width":1056,"attrs":[{"fill":"rgb(108, 114, 122)"}],"isMulticolor":false,"isMulticolor2":false,"colorPermutations":{"1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101":[{"f":2}]},"tags":["pin"],"grid":0},"attrs":[{"fill":"rgb(108, 114, 122)"}],"properties":{"order":70,"id":147,"name":"pin","prevSize":32,"code":59808},"setIdx":0,"setId":4,"iconIdx":151},{"icon":{"paths":["M513.354 160c17.674 0 32 13.133 32 29.333v645.332c0 16.202-14.326 29.334-32 29.334s-32-13.133-32-29.334v-645.332c0-16.2 14.326-29.333 32-29.333z"],"width":1056,"attrs":[{"fill":"rgb(108, 114, 122)"}],"isMulticolor":false,"isMulticolor2":false,"colorPermutations":{"1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101":[{"f":2}]},"tags":["Pipe"],"grid":0},"attrs":[{"fill":"rgb(108, 114, 122)"}],"properties":{"order":71,"id":148,"name":"Pipe","prevSize":32,"code":59809},"setIdx":0,"setId":4,"iconIdx":152},{"icon":{"paths":["M512 928.003c229.75 0 416-186.25 416-416s-186.25-415.999-416-415.999c-229.75 0-416 186.25-416 415.999s186.25 416 416 416zM451.846 357.19l195.258 136.768c14.205 9.952 18.496 30.874 9.578 46.73-2.429 4.323-5.706 7.978-9.578 10.691l-195.258 136.768c-14.205 9.952-32.95 5.165-41.866-10.691-3.037-5.398-4.646-11.645-4.646-18.019v-273.536c0-18.72 13.597-33.894 30.368-33.894 5.712 0 11.309 1.795 16.144 5.184z"],"attrs":[{"fill":"rgb(108, 114, 122)"}],"isMulticolor":false,"isMulticolor2":false,"colorPermutations":{"1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101":[{"f":2}]},"tags":["play-filled"],"grid":0},"attrs":[{"fill":"rgb(108, 114, 122)"}],"properties":{"order":72,"id":149,"name":"play-filled","prevSize":32,"code":59810},"setIdx":0,"setId":4,"iconIdx":153},{"icon":{"paths":["M864 512c0-194.404-157.597-352-352-352s-352 157.596-352 352c0 194.403 157.596 352 352 352s352-157.597 352-352zM928 512c0 229.75-186.25 416-416 416s-416-186.25-416-416c0-229.75 186.25-416 416-416s416 186.25 416 416zM451.85 357.187l195.254 136.768c14.208 9.949 18.496 30.87 9.581 46.726-2.432 4.326-5.706 7.981-9.581 10.694l-195.254 136.768c-14.208 9.952-32.954 5.165-41.869-10.694-3.037-5.398-4.646-11.642-4.646-18.016v-273.536c0-18.723 13.597-33.898 30.371-33.898 5.709 0 11.306 1.798 16.144 5.187z"],"attrs":[{"fill":"rgb(108, 114, 122)"}],"isMulticolor":false,"isMulticolor2":false,"colorPermutations":{"1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101":[{"f":2}]},"tags":["play"],"grid":0},"attrs":[{"fill":"rgb(108, 114, 122)"}],"properties":{"order":73,"id":150,"name":"play","prevSize":32,"code":59811},"setIdx":0,"setId":4,"iconIdx":154},{"icon":{"paths":["M325.686 430.88l224.49 224.49-126.118 126.118-224.491-224.49 126.12-126.118zM370.941 385.626l192.115-192.117 224.49 224.491-192.115 192.115-224.49-224.49zM585.683 125.627c-12.496-12.497-32.758-12.497-45.254 0l-408.745 408.745c-12.497 12.496-12.497 32.758 0 45.254l269.746 269.747c5.229 5.229 11.818 8.269 18.63 9.123v0.067h0.57c2.278 0.243 4.576 0.243 6.851 0h440.579c17.674 0 32-14.33 32-32 0-17.674-14.326-32.003-32-32.003h-366.566l353.936-353.933c12.496-12.496 12.496-32.758 0-45.254l-269.747-269.746z"],"width":1056,"attrs":[{"fill":"rgb(108, 114, 122)"}],"isMulticolor":false,"isMulticolor2":false,"colorPermutations":{"1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101":[{"f":2}]},"tags":["prune"],"grid":0},"attrs":[{"fill":"rgb(108, 114, 122)"}],"properties":{"order":77,"id":154,"name":"prune","prevSize":32,"code":59817},"setIdx":0,"setId":4,"iconIdx":155},{"icon":{"paths":["M230.486 636.688c-14.92-9.472-34.694-5.056-44.167 9.862-9.473 14.922-5.057 34.694 9.862 44.166l298.666 189.632c10.47 6.646 23.837 6.646 34.304 0l298.669-189.632c14.918-9.472 19.334-29.245 9.862-44.166-9.475-14.918-29.248-19.334-44.166-9.862l-281.517 178.739-281.514-178.739zM186.319 494.848c9.473-14.922 29.247-19.334 44.167-9.862l281.514 178.739 281.517-178.739c14.918-9.472 34.691-5.059 44.166 9.862 9.472 14.918 5.056 34.694-9.862 44.166l-298.669 189.632c-10.467 6.646-23.834 6.646-34.304 0l-298.666-189.632c-14.92-9.472-19.335-29.248-9.862-44.166zM529.152 143.657l298.669 189.629c9.245 5.872 14.848 16.064 14.848 27.014 0 10.954-5.603 21.146-14.848 27.014l-298.669 189.632c-10.467 6.646-23.834 6.646-34.304 0l-298.666-189.632c-9.246-5.869-14.848-16.061-14.848-27.014 0-10.95 5.602-21.142 14.848-27.014l298.666-189.629c10.47-6.647 23.837-6.647 34.304 0zM273.035 360.301l238.965 151.725 238.966-151.725-238.966-151.724-238.965 151.724z"],"attrs":[{"fill":"rgb(108, 114, 122)"}],"isMulticolor":false,"isMulticolor2":false,"colorPermutations":{"1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101":[{"f":2}]},"tags":["queue"],"grid":0},"attrs":[{"fill":"rgb(108, 114, 122)"}],"properties":{"order":78,"id":155,"name":"queue","prevSize":32,"code":59818},"setIdx":0,"setId":4,"iconIdx":156},{"icon":{"paths":["M161.352 800c-11.706 0-22.477-6.39-28.087-16.666s-5.161-22.79 1.169-32.64l112.304-174.694h-69.387c-17.673 0-32-14.326-32-32v-288c0-17.673 14.327-32 32-32h255.999c17.674 0 32 14.327 32 32v288c0 6.138-1.763 12.144-5.082 17.306l-143.999 224c-5.888 9.158-16.029 14.694-26.918 14.694h-128zM332.269 561.306l-112.304 174.694h51.916l129.469-201.398v-246.602h-191.999v224h96c11.706 0 22.479 6.39 28.085 16.666 5.61 10.275 5.162 22.79-1.168 32.64zM577.35 800c-11.706 0-22.477-6.39-28.086-16.666s-5.162-22.79 1.171-32.64l112.304-174.694h-69.389c-17.67 0-32-14.326-32-32v-288c0-17.673 14.33-32 32-32h256c17.674 0 32 14.327 32 32v288c0 6.138-1.763 12.144-5.082 17.306l-144 224c-5.888 9.158-16.029 14.694-26.918 14.694h-128zM748.269 561.306l-112.304 174.694h51.917l129.469-201.398v-246.602h-192v224h96c11.706 0 22.48 6.39 28.086 16.666 5.61 10.275 5.162 22.79-1.168 32.64z"],"width":1056,"attrs":[{"fill":"rgb(108, 114, 122)"}],"isMulticolor":false,"isMulticolor2":false,"colorPermutations":{"1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101":[{"f":2}]},"tags":["quote"],"grid":0},"attrs":[{"fill":"rgb(108, 114, 122)"}],"properties":{"order":79,"id":156,"name":"quote","prevSize":32,"code":59819},"setIdx":0,"setId":4,"iconIdx":157},{"icon":{"paths":["M800 560c0 176.73-143.27 320-320 320-176.731 0-320-143.27-320-320s143.269-320 320-320v-64c-212.077 0-384 171.923-384 384s171.923 384 384 384c212.077 0 384-171.923 384-384 0-10.778-0.445-21.45-1.315-32h-64.266c1.046 10.525 1.581 21.2 1.581 32zM800 128c0-17.673-14.326-32-32-32s-32 14.327-32 32v112h-112c-17.674 0-32 14.327-32 32s14.326 32 32 32h112v112c0 17.674 14.326 32 32 32s32-14.326 32-32v-112h112c17.674 0 32-14.327 32-32s-14.326-32-32-32h-112v-112zM384 528c35.347 0 64-28.653 64-64s-28.653-64-64-64c-35.347 0-64 28.653-64 64s28.653 64 64 64zM640 464c0 35.347-28.653 64-64 64s-64-28.653-64-64c0-35.347 28.653-64 64-64s64 28.653 64 64zM329.805 605.075c-10.451-14.25-30.477-17.331-44.728-6.88s-17.333 30.477-6.882 44.73c37.658 51.35 77.754 84.624 119.178 102.662 41.741 18.179 82.797 19.99 120.362 11.651 73.469-16.307 132.211-70.87 164.070-114.314 10.451-14.253 7.37-34.278-6.88-44.73-14.253-10.451-34.278-7.37-44.73 6.88-26.81 36.557-73.664 77.994-126.33 89.686-25.501 5.661-52.643 4.474-80.938-7.85-28.608-12.461-60.381-37.187-93.123-81.837z"],"attrs":[{"fill":"rgb(108, 114, 122)"}],"isMulticolor":false,"isMulticolor2":false,"colorPermutations":{"1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101":[{"f":2}]},"tags":["reaction-add"],"grid":0},"attrs":[{"fill":"rgb(108, 114, 122)"}],"properties":{"order":132,"id":157,"name":"reaction-add","prevSize":32,"code":59820},"setIdx":0,"setId":4,"iconIdx":158},{"icon":{"paths":["M832 512c0-176.73-143.27-320-320-320s-320 143.27-320 320c0 176.73 143.27 320 320 320s320-143.27 320-320zM896 512c0 212.077-171.923 384-384 384s-384-171.923-384-384c0-212.077 171.923-384 384-384s384 171.923 384 384zM512 704c-106.038 0-192-85.962-192-192s85.962-192 192-192c106.038 0 192 85.962 192 192s-85.962 192-192 192z"],"attrs":[{"fill":"rgb(108, 114, 122)"}],"isMulticolor":false,"isMulticolor2":false,"colorPermutations":{"1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101":[{"f":2}]},"tags":["record"],"grid":0},"attrs":[{"fill":"rgb(108, 114, 122)"}],"properties":{"order":133,"id":158,"name":"record","prevSize":32,"code":59821},"setIdx":0,"setId":4,"iconIdx":159},{"icon":{"paths":["M896 512h-63.984c0-175.414-145.754-320-328.518-320-115.27 0-215.818 57.513-274.362 144h110.768c17.674 0 32 14.326 32 32s-14.326 32-32 32h-179.904c-17.673 0-32-14.326-32-32v-192c0-17.673 14.327-32 32-32s32 14.327 32 32v102.32c71.751-91.401 184.589-150.32 311.498-150.32 216.781 0 392.502 171.923 392.502 384 0 1.114 0.010 2.227 0 3.338v-3.338z","M127.997 512h63.986c0 175.414 145.751 320 328.519 320 115.27 0 215.818-57.514 274.358-144h-110.768c-17.67 0-32-14.326-32-32s14.33-32 32-32h179.907c17.67 0 32 14.326 32 32v192c0 17.674-14.33 32-32 32-17.674 0-32-14.326-32-32v-102.32c-71.754 91.402-184.592 150.32-311.498 150.32-216.782 0-392.505-171.923-392.505-384 0-1.082-0.009-2.166 0-3.245v3.245z"],"attrs":[{"fill":"rgb(108, 114, 122)"},{"fill":"rgb(108, 114, 122)"}],"isMulticolor":false,"isMulticolor2":false,"colorPermutations":{"1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101":[{"f":2},{"f":2}]},"tags":["refresh"],"grid":0},"attrs":[{"fill":"rgb(108, 114, 122)"},{"fill":"rgb(108, 114, 122)"}],"properties":{"order":134,"id":159,"name":"refresh","prevSize":32,"code":59822},"setIdx":0,"setId":4,"iconIdx":160},{"icon":{"paths":["M713.718 450.362c0-142.689-115.824-258.362-258.701-258.362-142.878 0-258.704 115.673-258.704 258.362 0 142.691 115.826 258.365 258.704 258.365 142.877 0 258.701-115.674 258.701-258.365zM659.302 699.965c-55.645 45.475-126.774 72.762-204.285 72.762-178.271 0-322.788-144.326-322.788-322.365 0-178.035 144.517-322.362 322.788-322.362 178.269 0 322.787 144.327 322.787 322.362 0 77.408-27.318 148.442-72.854 204.013l186.838 186.592c12.608 12.589 12.608 33.002 0 45.59-12.605 12.589-33.043 12.589-45.648 0l-186.838-186.592z"],"width":1056,"attrs":[{"fill":"rgb(108, 114, 122)"}],"isMulticolor":false,"isMulticolor2":false,"colorPermutations":{"1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101":[{"f":2}]},"tags":["search"],"grid":0},"attrs":[{"fill":"rgb(108, 114, 122)"}],"properties":{"order":136,"id":160,"name":"search","prevSize":32,"code":59823},"setIdx":0,"setId":4,"iconIdx":161},{"icon":{"paths":["M891.494 238.96l-297.475 637.446c-16.854 36.115-69.622 31.459-79.891-7.050l-66-247.498 151.248-189.059-219.994 109.997-226.833-113.418c-35.43-17.715-29.696-69.949 8.733-79.555l681.197-170.3c34.842-8.71 64.202 26.892 49.014 59.436z"],"attrs":[{"fill":"rgb(108, 114, 122)"}],"isMulticolor":false,"isMulticolor2":false,"colorPermutations":{"1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101":[{"f":2}]},"tags":["send-filled"],"grid":0},"attrs":[{"fill":"rgb(108, 114, 122)"}],"properties":{"order":137,"id":161,"name":"send-filled","prevSize":32,"code":59824},"setIdx":0,"setId":4,"iconIdx":162},{"icon":{"paths":["M878.022 192.974c7.85 9.521 9.526 22.708 4.31 33.891l-298.669 640.002c-5.69 12.195-18.403 19.526-31.811 18.342-13.405-1.184-24.637-10.627-28.106-23.632l-81.619-306.070-285.772-142.886c-11.978-5.987-18.959-18.8-17.498-32.112s11.056-24.307 24.048-27.552l682.665-170.668c11.974-2.993 24.598 1.165 32.451 10.686zM505.821 545.968l57.082 214.051 233.027-499.35-533.58 133.395 203.606 101.802 69.51-52.131c14.141-10.605 34.198-7.741 44.8 6.4 10.605 14.138 7.741 34.195-6.4 44.8l-68.045 51.034z"],"attrs":[{"fill":"rgb(108, 114, 122)"}],"isMulticolor":false,"isMulticolor2":false,"colorPermutations":{"1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101":[{"f":2}]},"tags":["send"],"grid":0},"attrs":[{"fill":"rgb(108, 114, 122)"}],"properties":{"order":138,"id":162,"name":"send","prevSize":32,"code":59825},"setIdx":0,"setId":4,"iconIdx":163},{"icon":{"paths":["M421.334 256c0-44.183-35.818-80-80-80s-80 35.817-80 80c0 44.183 35.817 80 80 80s80-35.817 80-80zM465.302 288c-14.211 55.206-64.326 96-123.968 96-59.643 0-109.758-40.794-123.968-96h-89.367c-17.673 0-32-14.327-32-32s14.327-32 32-32h89.367c14.209-55.207 64.324-96 123.968-96 59.642 0 109.757 40.793 123.968 96h430.698c17.674 0 32 14.327 32 32s-14.326 32-32 32h-430.698zM96 768c0-17.674 14.327-32 32-32h89.367c14.209-55.206 64.324-96 123.968-96 60.781 0 111.67 42.365 124.742 99.181 4.208-2.038 8.934-3.181 13.923-3.181h416c17.674 0 32 14.326 32 32s-14.326 32-32 32h-416c-4.989 0-9.715-1.142-13.923-3.181-13.072 56.816-63.962 99.181-124.742 99.181-59.643 0-109.758-40.794-123.968-96h-89.367c-17.673 0-32-14.326-32-32zM341.334 848c44.182 0 80-35.818 80-80s-35.818-80-80-80c-44.183 0-80 35.818-80 80s35.817 80 80 80zM796.029 543.757c-14.122 55.331-64.298 96.243-124.029 96.243s-109.904-40.912-124.029-96.243c-1.302 0.16-2.627 0.243-3.971 0.243h-416c-17.673 0-32-14.326-32-32s14.327-32 32-32h416c1.344 0 2.669 0.083 3.971 0.243 14.125-55.331 64.298-96.243 124.029-96.243s109.907 40.912 124.029 96.243c1.302-0.16 2.627-0.243 3.971-0.243h96c17.674 0 32 14.326 32 32s-14.326 32-32 32h-96c-1.344 0-2.669-0.083-3.971-0.243zM752 512c0-44.182-35.818-80-80-80s-80 35.818-80 80c0 44.182 35.818 80 80 80s80-35.818 80-80z"],"attrs":[{"fill":"rgb(108, 114, 122)"}],"isMulticolor":false,"isMulticolor2":false,"colorPermutations":{"1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101":[{"f":2}]},"tags":["settings"],"grid":0},"attrs":[{"fill":"rgb(108, 114, 122)"}],"properties":{"order":139,"id":163,"name":"settings","prevSize":32,"code":59826},"setIdx":0,"setId":4,"iconIdx":164},{"icon":{"paths":["M690.176 301.708c11.645-11.977 11.645-31.396 0-43.374l-149.091-153.351c-11.645-11.977-30.525-11.977-42.17 0l-149.091 153.351c-11.645 11.977-11.645 31.397 0 43.374s30.525 11.978 42.17 0l98.189-100.993v401.343c0 16.938 13.35 30.669 29.818 30.669s29.818-13.731 29.818-30.669v-401.343l98.189 100.993c11.645 11.978 30.525 11.978 42.17 0z","M221.818 379.274h149.091v59.635h-119.272v387.635h536.728v-387.635h-119.274v-59.635h149.091c16.467 0 29.818 13.35 29.818 29.818v447.274c0 16.467-13.35 29.818-29.818 29.818h-596.364c-16.468 0-29.818-13.35-29.818-29.818v-447.274c0-16.467 13.35-29.818 29.818-29.818z"],"attrs":[{"fill":"rgb(108, 114, 122)"},{"fill":"rgb(108, 114, 122)"}],"isMulticolor":false,"isMulticolor2":false,"colorPermutations":{"1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101":[{"f":2},{"f":2}]},"tags":["share"],"grid":0},"attrs":[{"fill":"rgb(108, 114, 122)"},{"fill":"rgb(108, 114, 122)"}],"properties":{"order":244,"id":164,"name":"share","prevSize":32,"code":59827},"setIdx":0,"setId":4,"iconIdx":165},{"icon":{"paths":["M664.083 405.072c11.638-13.299 10.288-33.517-3.011-45.155s-33.517-10.288-45.155 3.011l-125.251 143.142-39.917-45.619c-11.638-13.299-31.853-14.646-45.155-3.011-13.299 11.638-14.646 31.856-3.008 45.155l64 73.142c6.077 6.944 14.854 10.928 24.080 10.928 9.229 0 18.006-3.984 24.083-10.928l149.334-170.666z","M541.99 143.050c-12.854-3.634-26.448-3.753-39.363-0.345l-264.724 69.849c-29.793 7.861-52.548 33.743-54.753 65.633-23.589 341.205 187.752 520.29 276.51 579.794 33.136 22.211 75.363 22.144 108.416-0.259 88.218-59.792 297.117-239.072 272.611-580.214-2.25-31.321-24.339-56.895-53.504-65.14l-245.194-69.318zM518.957 204.587c1.843-0.487 3.786-0.47 5.622 0.049l245.194 69.318c4.294 1.214 6.838 4.781 7.082 8.139 22.15 308.362-165.306 468.851-244.685 522.65-11.437 7.754-25.363 7.792-36.87 0.077-80.086-53.686-269.606-214.054-248.301-522.218 0.234-3.389 2.833-7.004 7.233-8.164l264.725-69.849z"],"attrs":[{"fill":"rgb(108, 114, 122)"},{"fill":"rgb(108, 114, 122)"}],"isMulticolor":false,"isMulticolor2":false,"colorPermutations":{"1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101":[{"f":2},{"f":2}]},"tags":["shield-check"],"grid":0},"attrs":[{"fill":"rgb(108, 114, 122)"},{"fill":"rgb(108, 114, 122)"}],"properties":{"order":80,"id":165,"name":"shield-check","prevSize":32,"code":59828},"setIdx":0,"setId":4,"iconIdx":166},{"icon":{"paths":["M513.302 142.705c12.915-3.408 26.509-3.289 39.36 0.345l245.197 69.318c29.165 8.245 51.254 33.818 53.504 65.14 24.506 341.142-184.397 520.422-272.611 580.214-33.053 22.403-75.28 22.47-108.416 0.259-88.762-59.504-300.101-238.589-276.511-579.794 2.205-31.89 24.96-57.772 54.753-65.633l264.725-69.849zM535.251 204.637c-1.834-0.519-3.776-0.536-5.622-0.050l-264.723 69.849c-4.4 1.161-6.999 4.775-7.234 8.164-21.305 308.164 168.216 468.532 248.299 522.218 11.507 7.715 25.434 7.677 36.874-0.077 79.379-53.798 266.832-214.288 244.682-522.65-0.24-3.358-2.784-6.925-7.078-8.139l-245.197-69.317z","M490.672 337.334c-19.76 4.672-47.245 12.31-81.149 24.554 20.81 108.771 53.219 187.014 81.149 238.832v-263.386zM492.867 271.316c33.77-6.898 61.805 19.742 61.805 51.125v360.963c0 17.024-10.333 31.747-25.642 37.52-15.67 5.91-33.779 1.331-44.861-12.675-32.656-41.267-106.323-152.95-141.19-354.563-3.242-18.746 7.066-37.695 25.328-44.726 56.186-21.63 99.094-32.442 124.56-37.644z"],"attrs":[{"fill":"rgb(108, 114, 122)"},{"fill":"rgb(108, 114, 122)"}],"isMulticolor":false,"isMulticolor2":false,"colorPermutations":{"1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101":[{"f":2},{"f":2}]},"tags":["shield"],"grid":0},"attrs":[{"fill":"rgb(108, 114, 122)"},{"fill":"rgb(108, 114, 122)"}],"properties":{"order":81,"id":166,"name":"shield-alt","prevSize":32,"code":59829},"setIdx":0,"setId":4,"iconIdx":167},{"icon":{"paths":["M778.64 213.336c-17.674 0-32 14.327-32 32v533.333c0 17.674 14.326 32 32 32s32-14.326 32-32v-533.333c0-17.673-14.326-32-32-32z","M600.87 341.334c-17.674 0-32 14.33-32 32v405.334c0 17.674 14.326 32 32 32s32-14.326 32-32v-405.334c0-17.67-14.326-32-32-32z","M423.104 810.669c-17.674 0-32-14.326-32-32v-277.334c0-17.67 14.326-32 32-32 17.67 0 32 14.33 32 32v277.334c0 17.674-14.33 32-32 32z","M245.333 597.334c-17.673 0-32 14.33-32 32v149.334c0 17.674 14.327 32 32 32s32-14.326 32-32v-149.334c0-17.67-14.327-32-32-32z"],"attrs":[{"fill":"rgb(108, 114, 122)"},{"fill":"rgb(108, 114, 122)"},{"fill":"rgb(108, 114, 122)"},{"fill":"rgb(108, 114, 122)"}],"isMulticolor":false,"isMulticolor2":false,"colorPermutations":{"1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101":[{"f":2},{"f":2},{"f":2},{"f":2}]},"tags":["signal"],"grid":0},"attrs":[{"fill":"rgb(108, 114, 122)"},{"fill":"rgb(108, 114, 122)"},{"fill":"rgb(108, 114, 122)"},{"fill":"rgb(108, 114, 122)"}],"properties":{"order":82,"id":167,"name":"signal","prevSize":32,"code":59830},"setIdx":0,"setId":4,"iconIdx":168},{"icon":{"paths":["M289.362 192c13.364 0 25.321 8.305 29.987 20.829l96.001 257.699c6.17 16.56-2.256 34.986-18.816 41.155-16.563 6.17-34.989-2.253-41.158-18.816l-20.477-54.963h-91.074l-20.476 54.963c-6.17 16.563-24.597 24.986-41.158 18.816s-24.986-24.595-18.816-41.155l96-257.699c4.666-12.524 16.622-20.829 29.987-20.829zM311.058 373.904l-21.695-58.238-21.695 58.238h43.391z","M522.131 626.435c-12.154 12.829-11.606 33.082 1.222 45.238l160 151.587c12.342 11.693 31.674 11.693 44.016 0l160-151.587c12.832-12.157 13.376-32.41 1.222-45.238s-32.41-13.376-45.238-1.222l-105.99 100.419v-501.632c0-17.673-14.326-32-32-32s-32 14.327-32 32v501.632l-105.994-100.419c-12.829-12.154-33.082-11.606-45.238 1.222z","M193.362 570.947c-17.673 0-32 14.326-32 32 0 17.67 14.327 32 32 32h116.145l-139.065 142.73c-8.979 9.216-11.565 22.915-6.564 34.771 5.001 11.853 16.617 19.562 29.484 19.562h192.001c17.674 0 32-14.326 32-32s-14.326-32-32-32h-116.146l139.064-142.733c8.979-9.216 11.565-22.915 6.563-34.768-4.998-11.856-16.614-19.562-29.482-19.562h-192.001z"],"width":1056,"attrs":[{"fill":"rgb(108, 114, 122)"},{"fill":"rgb(108, 114, 122)"},{"fill":"rgb(108, 114, 122)"}],"isMulticolor":false,"isMulticolor2":false,"colorPermutations":{"1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101":[{"f":2},{"f":2},{"f":2}]},"tags":["sort-az"],"grid":0},"attrs":[{"fill":"rgb(108, 114, 122)"},{"fill":"rgb(108, 114, 122)"},{"fill":"rgb(108, 114, 122)"}],"properties":{"order":83,"id":168,"name":"sort-az","prevSize":32,"code":59831},"setIdx":0,"setId":4,"iconIdx":169},{"icon":{"paths":["M919.258 667.229c12.605-12.253 12.877-32.394 0.605-44.982-12.269-12.589-32.435-12.858-45.040-0.605l-106.112 103.155v-500.8c0-17.568-14.259-31.81-31.853-31.81-17.59 0-31.85 14.242-31.85 31.81v500.8l-106.115-103.155c-12.605-12.253-32.768-11.984-45.040 0.605-12.269 12.589-11.997 32.73 0.608 44.982l160.179 155.718c12.368 12.019 32.070 12.019 44.435 0l160.182-155.718zM560.659 333.667c17.59 0 31.85-14.243 31.85-31.811s-14.259-31.809-31.85-31.809h-432.49c-17.591 0-31.852 14.242-31.852 31.809s14.26 31.811 31.852 31.811h432.49zM464.55 536.099c17.59 0 31.85-14.243 31.85-31.811 0-17.565-14.259-31.808-31.85-31.808h-336.381c-17.591 0-31.852 14.24-31.852 31.808s14.26 31.811 31.852 31.811h336.381zM384.458 722.96c17.594 0 31.853-14.24 31.853-31.808s-14.259-31.811-31.853-31.811h-256.289c-17.591 0-31.852 14.243-31.852 31.811s14.26 31.808 31.852 31.808h256.289z"],"width":1056,"attrs":[{"fill":"rgb(108, 114, 122)"}],"isMulticolor":false,"isMulticolor2":false,"colorPermutations":{"1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101":[{"f":2}]},"tags":["sort"],"grid":0},"attrs":[{"fill":"rgb(108, 114, 122)"}],"properties":{"order":84,"id":169,"name":"sort","prevSize":32,"code":59832},"setIdx":0,"setId":4,"iconIdx":170},{"icon":{"paths":["M593.939 170.244c-20.867-57.726-103.155-55.816-121.322 2.815l-58.976 190.348h-221.513c-58.947 0-86.549 72.944-42.37 111.968l157.591 139.206-53.209 216.928c-14.17 57.77 51.25 101.936 99.533 67.197l177.741-127.875 177.741 127.875c48.285 34.739 113.706-9.427 99.536-67.197l-53.386-217.651 147.888-139.968c42.032-39.779 13.878-110.483-43.99-110.483h-195.443l-69.821-193.164z"],"attrs":[{"fill":"rgb(108, 114, 122)"}],"isMulticolor":false,"isMulticolor2":false,"colorPermutations":{"1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101":[{"f":2}]},"tags":["star-filled"],"grid":0},"attrs":[{"fill":"rgb(108, 114, 122)"}],"properties":{"order":85,"id":170,"name":"star-filled","prevSize":32,"code":59833},"setIdx":0,"setId":4,"iconIdx":171},{"icon":{"paths":["M472.618 173.508c18.166-58.554 100.454-60.461 121.322-2.812l69.821 192.91h195.44c57.872 0 86.026 70.611 43.994 110.336l-147.888 139.782 53.386 217.363c14.17 57.696-51.251 101.805-99.536 67.11l-177.741-127.709-177.741 127.709c-48.283 34.691-113.703-9.414-99.533-67.11l53.209-216.64-157.591-139.024c-44.179-38.973-16.577-111.818 42.37-111.818h221.513l58.976-190.098zM603.574 385.334l-69.824-192.91-58.976 190.098c-8.304 26.758-33.085 45.002-61.133 45.002h-221.513l157.59 139.021c17.837 15.731 25.456 40.048 19.789 63.13l-53.209 216.643 177.74-127.709c22.33-16.045 52.426-16.045 74.755 0l177.738 127.709-53.386-217.363c-5.478-22.317 1.456-45.856 18.166-61.648l147.888-139.782h-195.44c-26.957 0-51.024-16.87-60.186-42.189z"],"attrs":[{"fill":"rgb(108, 114, 122)"}],"isMulticolor":false,"isMulticolor2":false,"colorPermutations":{"1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101":[{"f":2}]},"tags":["star"],"grid":0},"attrs":[{"fill":"rgb(108, 114, 122)"}],"properties":{"order":86,"id":171,"name":"star","prevSize":32,"code":59834},"setIdx":0,"setId":4,"iconIdx":172},{"icon":{"paths":["M499.952 539.082c2.714 0.819 5.462 1.629 8.25 2.429 45.341 13.12 76.643 26.47 93.904 40.048 17.264 13.35 25.894 32.339 25.894 56.966s-9.322 44.074-27.965 58.346c-18.643 14.269-45.456 21.405-80.442 21.405-37.514 0-67.552-8.749-90.106-26.24-15.216-12.077-25.245-27.094-30.090-45.050-3.942-14.605-15.664-27.104-30.794-27.104h-11.843c-15.13 0-27.722 12.378-24.893 27.242 3.514 18.47 10.736 35.514 21.667 51.126 16.339 23.018 39.472 41.2 69.392 54.547 29.92 13.12 62.144 19.68 96.666 19.68 53.168 0 95.632-12.198 127.395-36.595 31.76-24.627 47.642-57.309 47.642-98.048 0-25.549-5.754-47.757-17.261-66.63-6.947-11.626-16.25-22.33-27.91-32.122h99.962c15.13 0 27.395-12.266 27.395-27.395s-12.266-27.392-27.395-27.392h-217.398c-1.709-0.493-3.437-0.986-5.178-1.472-41.658-11.738-71.235-24.627-88.726-38.669-17.261-14.269-25.894-31.645-25.894-52.131 0-25.546 9.091-45.456 27.277-59.725 18.41-14.5 43.958-21.75 76.64-21.75 35.216 0 62.49 8.746 81.824 26.24 13.139 11.731 21.814 26.496 26.022 44.288 3.485 14.723 15.242 27.174 30.371 27.174h11.84c15.13 0 27.702-12.374 24.88-27.238-3.331-17.552-9.974-34.134-19.923-49.75-14.733-23.475-35.677-41.888-62.835-55.237-26.928-13.349-57.654-20.024-92.179-20.024-50.634 0-91.834 13.004-123.594 39.012-31.533 25.779-47.299 58.46-47.299 98.047 0 34.986 12.89 64.445 38.669 88.381 1.050 0.957 2.125 1.907 3.222 2.854h-112.56c-15.13 0-27.395 12.262-27.395 27.392s12.265 27.395 27.395 27.395h225.373z"],"attrs":[{"fill":"rgb(108, 114, 122)"}],"isMulticolor":false,"isMulticolor2":false,"colorPermutations":{"1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101":[{"f":2}]},"tags":["strike"],"grid":0},"attrs":[{"fill":"rgb(108, 114, 122)"}],"properties":{"order":94,"id":172,"name":"strike","prevSize":32,"code":59846},"setIdx":0,"setId":4,"iconIdx":173},{"icon":{"paths":["M481.35 117.336c0-17.673 14.326-32 32-32s32 14.327 32 32v106.667c0 17.673-14.326 32-32 32s-32-14.327-32-32v-106.667zM771.392 214.631c12.496-12.497 32.758-12.497 45.254 0s12.496 32.758 0 45.255l-75.411 75.413c-12.499 12.496-32.758 12.496-45.254 0-12.499-12.496-12.499-32.759 0-45.255l75.411-75.412zM336.73 694.63c-12.499-12.496-32.759-12.496-45.256 0l-75.495 75.498c-12.497 12.496-12.497 32.755 0 45.254 12.497 12.496 32.758 12.496 45.255 0l75.496-75.498c12.496-12.496 12.496-32.755 0-45.254zM481.35 800.003c0-17.674 14.326-32 32-32s32 14.326 32 32v106.669c0 17.67-14.326 32-32 32s-32-14.33-32-32v-106.669zM213.352 212.001c-12.497 12.497-12.497 32.758 0 45.255l75.349 75.348c12.497 12.496 32.758 12.496 45.254 0 12.499-12.496 12.496-32.757 0-45.254l-75.348-75.349c-12.497-12.497-32.758-12.497-45.255 0zM695.978 739.885c-12.496-12.496-12.496-32.758 0-45.254 12.499-12.496 32.758-12.496 45.254 0l75.331 75.328c12.496 12.496 12.496 32.758 0 45.254-12.499 12.496-32.758 12.496-45.258 0l-75.328-75.328zM87.751 512.003c0 17.674 14.327 32 32 32h106.667c17.673 0 32-14.326 32-32s-14.327-32-32-32h-106.667c-17.673 0-32 14.326-32 32zM801.35 544.003c-17.674 0-32-14.326-32-32s14.326-32 32-32h106.669c17.67 0 32 14.326 32 32s-14.33 32-32 32h-106.669zM668.17 512c0-85.504-69.315-154.816-154.819-154.816-85.507 0-154.819 69.312-154.819 154.816 0 85.507 69.312 154.819 154.819 154.819 85.504 0 154.819-69.312 154.819-154.819zM726.682 512c0 117.821-95.51 213.334-213.331 213.334s-213.335-95.514-213.335-213.334c0-117.821 95.514-213.332 213.335-213.332s213.331 95.511 213.331 213.332z"],"width":1056,"attrs":[{"fill":"rgb(108, 114, 122)"}],"isMulticolor":false,"isMulticolor2":false,"colorPermutations":{"1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101":[{"f":2}]},"tags":["sun"],"grid":0},"attrs":[{"fill":"rgb(108, 114, 122)"}],"properties":{"order":95,"id":173,"name":"sun","prevSize":32,"code":59847},"setIdx":0,"setId":4,"iconIdx":174},{"icon":{"paths":["M930.704 512c0 229.75-186.25 416-416 416s-415.999-186.25-415.999-416c0-229.75 186.25-416 415.999-416s416 186.25 416 416zM570.49 859.603l-41.827-156.102c-4.611 0.33-9.264 0.499-13.958 0.499-4.57 0-9.101-0.16-13.594-0.474l-41.837 156.134c18.058 2.854 36.573 4.339 55.43 4.339 18.986 0 37.616-1.504 55.786-4.397zM438.893 688.451c-47.549-20.454-85.181-59.571-103.674-108.125l-155.029 41.539c33.968 103.485 114.617 185.805 217.045 222.058l41.658-155.472zM162.705 512c0 16.090 1.080 31.926 3.17 47.443l156.906-42.042c-0.051-1.795-0.077-3.594-0.077-5.402 0-4.909 0.186-9.776 0.547-14.595l-156.051-41.814c-2.958 18.368-4.496 37.21-4.496 56.41zM396.31 180.407c-99.16 35.408-177.786 114.033-213.196 213.19l155.535 41.677c19.357-44.352 54.982-79.978 99.334-99.331l-41.674-155.536zM514.704 160c-19.197 0-38.035 1.537-56.4 4.494l41.814 156.053c4.813-0.362 9.68-0.547 14.586-0.547 5.030 0 10.019 0.192 14.95 0.573l41.808-156.021c-18.477-2.995-37.437-4.552-56.758-4.552zM632.512 843.802c102.659-36.451 183.392-119.194 217.094-223.12l-154.973-41.526c-18.291 48.982-56.010 88.493-103.786 109.155l41.664 155.491zM863.699 558.198c1.984-15.117 3.005-30.538 3.005-46.198 0-18.771-1.469-37.197-4.298-55.171l-156.157 41.84c0.301 4.406 0.454 8.851 0.454 13.331 0 1.376-0.013 2.752-0.042 4.122l157.037 42.077zM846.714 394.774c-35.165-99.6-113.885-178.641-213.277-214.247l-41.68 155.559c44.582 19.555 80.314 55.565 99.504 100.342l155.453-41.654zM642.704 512c0-70.691-57.306-128-128-128-70.691 0-128 57.309-128 128s57.309 128 128 128c70.694 0 128-57.309 128-128z"],"width":1056,"attrs":[{"fill":"rgb(108, 114, 122)"}],"isMulticolor":false,"isMulticolor2":false,"colorPermutations":{"1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101":[{"f":2}]},"tags":["support"],"grid":0},"attrs":[{"fill":"rgb(108, 114, 122)"}],"properties":{"order":96,"id":174,"name":"support","prevSize":32,"code":59848},"setIdx":0,"setId":4,"iconIdx":175},{"icon":{"paths":["M368 412.982c-53.592 0-96-42.877-96-94.491 0-51.616 42.408-94.491 96-94.491 53.594 0 96 42.876 96 94.491 0 51.614-42.406 94.491-96 94.491zM368 476.982c88.365 0 160-70.96 160-158.491s-71.635-158.491-160-158.491c-88.365 0-160 70.959-160 158.491s71.635 158.491 160 158.491zM713.6 397.133c-35.92 0-64-28.685-64-62.794s28.080-62.792 64-62.792c35.92 0 64 28.684 64 62.792s-28.080 62.794-64 62.794zM713.6 461.133c70.691 0 128-56.768 128-126.794s-57.309-126.792-128-126.792c-70.691 0-128 56.767-128 126.792s57.309 126.794 128 126.794zM197.459 527.267c27.344-8.707 56.67-9.242 84.319-1.539l48.491 13.51c24.205 6.742 49.882 6.275 73.824-1.347l30.099-9.584c29.475-9.386 61.085-9.962 90.89-1.658 67.962 18.934 114.918 80.333 114.918 150.269v91.987c0 52.518-42.979 95.094-96 95.094h-352c-53.019 0-96-42.576-96-95.094v-103.766c0-62.909 40.999-118.621 101.459-137.872zM264.451 586.758c-15.545-4.333-32.034-4.032-47.407 0.864-33.993 10.822-57.044 42.147-57.044 77.517v103.766c0 17.507 14.327 31.699 32 31.699h352c17.674 0 32-14.192 32-31.699v-91.987c0-41.533-27.885-77.997-68.246-89.242-17.699-4.931-36.474-4.589-53.978 0.986l-30.099 9.584c-35.91 11.434-74.426 12.138-110.737 2.019l-48.489-13.507zM691.2 778.717h140.8c53.021 0 96-42.979 96-96v-30.611c0-56.877-38.614-106.49-93.747-120.454-21.398-5.418-43.853-5.040-65.056 1.098l-16.4 4.746c-21.843 6.323-44.973 6.714-67.018 1.13l-29.805-7.549c-19.907-5.043-40.797-4.691-60.522 1.018-1.066 0.31-2.122 0.634-3.174 0.97 10.71 4.048 20.896 9.539 30.253 16.374l2.429 1.776 15.040 13.206c9.939 8.726 18.141 19.248 24.182 31.014l2.208 4.301 3.677 0.931c33.062 8.374 67.76 7.789 100.525-1.693l16.4-4.749c10.282-2.976 21.171-3.158 31.546-0.531 26.736 6.771 45.462 30.832 45.462 58.413v30.611c0 17.674-14.326 32-32 32h-140.8v64z"],"attrs":[{"fill":"rgb(108, 114, 122)"}],"isMulticolor":false,"isMulticolor2":false,"colorPermutations":{"1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101":[{"f":2}]},"tags":["team"],"grid":0},"attrs":[{"fill":"rgb(108, 114, 122)"}],"properties":{"order":97,"id":175,"name":"team","prevSize":32,"code":59849},"setIdx":0,"setId":4,"iconIdx":176},{"icon":{"paths":["M223.311 308.609c-47.832 66.7-64.077 147.667-64.077 201.187v1.405l-0.127 1.398c-9.348 103.235 27.547 175.997 86.848 226.374 60.642 51.52 146.602 80.998 235.29 90.893 88.611 9.885 186.221 1.398 263.341-14.973 38.56-8.189 70.928-18.125 93.888-28.128 8.906-3.882 15.837-7.536 20.941-10.765-2.374-1.514-5.245-3.197-8.672-5.030-8.886-4.749-19.059-9.261-29.613-13.894l-1.843-0.81c-9.315-4.083-19.725-8.65-27.613-13.078-14.291-8.026-28.317-17.155-38.544-26.867-5.034-4.781-10.762-11.194-14.595-19.184-4.054-8.454-6.96-21.078-1.261-34.435 30.816-72.186 45.459-111.725 52.554-137.834 6.621-24.378 6.621-36.653 6.621-56.17v-0.179c0-15.51-8.979-86.595-53.776-152.876-43.376-64.174-121.398-125.729-264.832-125.729-129.725 0-207.83 53.576-254.529 118.696zM173.267 272.433c58.132-81.063 154.749-144.433 304.573-144.433 164.896 0 261.594 72.589 315.859 152.878 52.838 78.181 64.416 161.881 64.416 187.641 0 21.654-0.022 40.336-8.797 72.64-7.834 28.835-22.627 68.614-50.048 133.424 5.069 4.032 12.592 9.005 22.506 14.57 5.12 2.877 12.995 6.339 24.054 11.194 10.266 4.506 22.582 9.93 33.891 15.978 10.848 5.798 23.526 13.571 32.954 23.738 9.859 10.63 20.208 28.87 12.816 51.139-4.87 14.672-16.182 25.091-25.61 32.016-10.24 7.52-22.947 14.278-36.858 20.342-27.949 12.176-64.582 23.181-105.68 31.907-82.198 17.453-186.522 26.688-282.909 15.936-96.31-10.746-195.346-43.178-268.313-105.165-74.031-62.893-119.295-154.778-108.551-277.856 0.276-63.411 19.117-157.053 75.696-235.948zM333.952 422.051c0-17.648 14.25-31.955 31.83-31.955h183.008c17.578 0 31.827 14.307 31.827 31.955s-14.25 31.955-31.827 31.955h-183.008c-17.581 0-31.83-14.307-31.83-31.955zM365.782 539.709c-17.581 0-31.83 14.307-31.83 31.955s14.25 31.955 31.83 31.955h224.118c17.578 0 31.827-14.307 31.827-31.955s-14.25-31.955-31.827-31.955h-224.118z"],"attrs":[{"fill":"rgb(108, 114, 122)"}],"isMulticolor":false,"isMulticolor2":false,"colorPermutations":{"1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101":[{"f":2}]},"tags":["threads"],"grid":0},"attrs":[{"fill":"rgb(108, 114, 122)"}],"properties":{"order":98,"id":176,"name":"threads","prevSize":32,"code":59850},"setIdx":0,"setId":4,"iconIdx":177},{"icon":{"paths":["M353.354 320c0-17.673 14.326-32 32-32h256c17.674 0 32 14.327 32 32s-14.326 32-32 32h-256c-17.674 0-32-14.326-32-32z","M385.354 416c-17.674 0-32 14.326-32 32s14.326 32 32 32c17.674 0 32-14.326 32-32s-14.326-32-32-32z","M481.354 448c0-17.674 14.326-32 32-32s32 14.326 32 32c0 17.674-14.326 32-32 32s-32-14.326-32-32z","M641.354 416c-17.674 0-32 14.326-32 32s14.326 32 32 32c17.674 0 32-14.326 32-32s-14.326-32-32-32z","M353.354 576c0-17.674 14.326-32 32-32s32 14.326 32 32c0 17.674-14.326 32-32 32s-32-14.326-32-32z","M513.354 544c-17.674 0-32 14.326-32 32s14.326 32 32 32c17.674 0 32-14.326 32-32s-14.326-32-32-32z","M609.354 576c0-17.674 14.326-32 32-32s32 14.326 32 32c0 17.674-14.326 32-32 32s-32-14.326-32-32z","M385.354 672c-17.674 0-32 14.326-32 32s14.326 32 32 32c17.674 0 32-14.326 32-32s-14.326-32-32-32z","M481.354 704c0-17.674 14.326-32 32-32s32 14.326 32 32c0 17.674-14.326 32-32 32s-32-14.326-32-32z","M641.354 672c-17.674 0-32 14.326-32 32s14.326 32 32 32c17.674 0 32-14.326 32-32s-14.326-32-32-32z","M289.353 160h448.001c35.344 0 64 28.654 64 64v576c0 35.347-28.656 64-64 64h-448.001c-35.346 0-64-28.653-64-64v-576c0-35.346 28.654-64 64-64zM289.353 224v576h448.001v-576h-448.001z"],"width":1056,"attrs":[{"fill":"rgb(108, 114, 122)"},{"fill":"rgb(108, 114, 122)"},{"fill":"rgb(108, 114, 122)"},{"fill":"rgb(108, 114, 122)"},{"fill":"rgb(108, 114, 122)"},{"fill":"rgb(108, 114, 122)"},{"fill":"rgb(108, 114, 122)"},{"fill":"rgb(108, 114, 122)"},{"fill":"rgb(108, 114, 122)"},{"fill":"rgb(108, 114, 122)"},{"fill":"rgb(108, 114, 122)"}],"isMulticolor":false,"isMulticolor2":false,"colorPermutations":{"1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101":[{"f":2},{"f":2},{"f":2},{"f":2},{"f":2},{"f":2},{"f":2},{"f":2},{"f":2},{"f":2},{"f":2}]},"tags":["total"],"grid":0},"attrs":[{"fill":"rgb(108, 114, 122)"},{"fill":"rgb(108, 114, 122)"},{"fill":"rgb(108, 114, 122)"},{"fill":"rgb(108, 114, 122)"},{"fill":"rgb(108, 114, 122)"},{"fill":"rgb(108, 114, 122)"},{"fill":"rgb(108, 114, 122)"},{"fill":"rgb(108, 114, 122)"},{"fill":"rgb(108, 114, 122)"},{"fill":"rgb(108, 114, 122)"},{"fill":"rgb(108, 114, 122)"}],"properties":{"order":99,"id":177,"name":"total","prevSize":32,"code":59851},"setIdx":0,"setId":4,"iconIdx":178},{"icon":{"paths":["M713.613 91.356c13.162-11.794 33.392-10.685 45.187 2.477l129.034 144c10.89 12.154 10.89 30.556 0 42.71l-129.034 144.001c-11.795 13.162-32.026 14.269-45.187 2.477-13.162-11.795-14.272-32.026-2.477-45.187l81.222-90.646h-216.358c-17.674 0-32-14.327-32-32s14.326-32 32-32h216.358l-81.222-90.645c-11.795-13.162-10.685-33.393 2.477-45.187zM340.026 464.461c8.586-15.45 28.067-21.018 43.514-12.432l128.461 71.366 128.461-71.366c15.446-8.586 34.928-3.018 43.514 12.432 8.582 15.45 3.014 34.931-12.435 43.514l-159.539 88.634-159.539-88.634c-15.45-8.582-21.018-28.064-12.435-43.514zM192 288v448h640v-192c0-17.674 14.326-32 32-32s32 14.326 32 32v224c0 17.674-14.326 32-32 32h-704c-17.673 0-32-14.326-32-32v-512c0-17.673 14.327-32 32-32h272c17.674 0 32 14.327 32 32s-14.326 32-32 32h-240z"],"attrs":[{"fill":"rgb(108, 114, 122)"}],"isMulticolor":false,"isMulticolor2":false,"colorPermutations":{"1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101":[{"f":2}]},"tags":["transcript"],"grid":0},"attrs":[{"fill":"rgb(108, 114, 122)"}],"properties":{"order":100,"id":178,"name":"transcript","prevSize":32,"code":59852},"setIdx":0,"setId":4,"iconIdx":179},{"icon":{"paths":["M384 320c0-17.673-14.326-32-32-32s-32 14.327-32 32v256c0 106.038 85.962 192 192 192s192-85.962 192-192v-256c0-17.673-14.326-32-32-32s-32 14.327-32 32v256c0 70.691-57.309 128-128 128s-128-57.309-128-128v-256zM352 856c-13.254 0-24 10.746-24 24s10.746 24 24 24h320c13.254 0 24-10.746 24-24s-10.746-24-24-24h-320z"],"attrs":[{"fill":"rgb(108, 114, 122)"}],"isMulticolor":false,"isMulticolor2":false,"colorPermutations":{"1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101":[{"f":2}]},"tags":["underline"],"grid":0},"attrs":[{"fill":"rgb(108, 114, 122)"}],"properties":{"order":101,"id":179,"name":"underline","prevSize":32,"code":59853},"setIdx":0,"setId":4,"iconIdx":180},{"icon":{"paths":["M512 832c176.73 0 320-143.27 320-320s-143.27-320-320-320c-111.712 0-210.056 57.244-267.295 144h107.295c17.674 0 32 14.326 32 32s-14.326 32-32 32h-176c-17.673 0-32-14.326-32-32v-192c0-17.673 14.327-32 32-32s32 14.327 32 32v101.364c70.228-90.856 180.282-149.364 304-149.364 212.077 0 384 171.923 384 384s-171.923 384-384 384c-212.077 0-384-171.923-384-384h64c0 176.73 143.27 320 320 320z"],"attrs":[{"fill":"rgb(108, 114, 122)"}],"isMulticolor":false,"isMulticolor2":false,"colorPermutations":{"1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101":[{"f":2}]},"tags":["undo"],"grid":0},"attrs":[{"fill":"rgb(108, 114, 122)"}],"properties":{"order":102,"id":180,"name":"undo","prevSize":32,"code":59854},"setIdx":0,"setId":4,"iconIdx":181},{"icon":{"paths":["M467.808 512c0-63.322-61.053-128-153.903-128s-153.905 64.678-153.905 128c0 63.322 61.054 128 153.905 128s153.903-64.678 153.903-128zM512 592.099c34.454 66.042 110.195 111.901 198.096 111.901 120.346 0 217.904-85.962 217.904-192s-97.558-192-217.904-192c-87.901 0-163.642 45.859-198.096 111.901-34.451-66.042-110.195-111.901-198.095-111.901-120.345 0-217.905 85.962-217.905 192s97.559 192 217.905 192c87.9 0 163.644-45.859 198.095-111.901zM864 512c0 63.322-61.053 128-153.904 128s-153.904-64.678-153.904-128c0-63.322 61.053-128 153.904-128s153.904 64.678 153.904 128z"],"attrs":[{"fill":"rgb(108, 114, 122)"}],"isMulticolor":false,"isMulticolor2":false,"colorPermutations":{"1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101":[{"f":2}]},"tags":["Unlimited"],"grid":0},"attrs":[{"fill":"rgb(108, 114, 122)"}],"properties":{"order":103,"id":181,"name":"Unlimited","prevSize":32,"code":59855},"setIdx":0,"setId":4,"iconIdx":182},{"icon":{"paths":["M829.168 153.372c12.515-12.497 32.803-12.497 45.315 0 12.515 12.497 12.515 32.758 0 45.255l-672.887 672c-12.513 12.496-32.801 12.496-45.315 0s-12.513-32.758 0-45.254l672.887-672zM110.155 480.051c101.936-153.699 303.797-327.676 542.114-225.436l-49.578 49.512c-56.528-19.209-108.077-19.504-153.526-9.726-114.906 24.721-215.653 118.392-280.79 213.871 38.633 48 75.969 86.822 111.862 117.885l-45.363 45.302c-39.684-34.736-80.189-77.437-121.317-129.181-14.248-17.923-16.056-43.146-3.402-62.227zM797.981 350.454l-45.328 45.27c35.546 31.427 72.336 70.947 110.186 120.080-61.546 95.187-158.656 188.682-272.125 213.626-46.752 10.278-100.624 9.734-160.592-11.603l-49.331 49.267c244.662 107.334 443.389-69.158 540.778-224.253 11.674-18.589 9.894-42.656-3.395-60.128-40.493-53.235-80.64-96.931-120.192-132.259zM514.982 347.43c13.709 0 27.037 1.571 39.802 4.541l-203.738 203.469c-3.901-13.837-5.981-28.403-5.981-43.44 0-90.89 76.074-164.57 169.917-164.57zM679.091 469.184l-203.264 202.998c12.57 2.87 25.68 4.39 39.155 4.39 93.843 0 169.917-73.68 169.917-164.573 0-14.81-2.019-29.162-5.808-42.816z"],"width":1056,"attrs":[{"fill":"rgb(108, 114, 122)"}],"isMulticolor":false,"isMulticolor2":false,"colorPermutations":{"1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101":[{"f":2}]},"tags":["unread-on-top-disabled"],"grid":0},"attrs":[{"fill":"rgb(108, 114, 122)"}],"properties":{"order":104,"id":182,"name":"unread-on-top-disabled","prevSize":32,"code":59856},"setIdx":0,"setId":4,"iconIdx":183},{"icon":{"paths":["M589.36 729.43c-107.366 23.603-252.288-9.875-422.339-221.158 65.137-95.478 165.885-189.15 280.79-213.871 108.314-23.302 251.286 10.604 413.674 221.404-61.546 95.187-158.653 188.682-272.125 213.626zM916.819 482.714c-347.59-456.963-669.742-211.156-808.018-2.662-12.655 19.082-10.846 44.304 3.402 62.227 362.786 456.419 677.038 209.142 808.011 0.563 11.674-18.589 9.894-42.656-3.395-60.128zM619.462 512c0-53.709-45.517-100.57-105.834-100.57s-105.834 46.861-105.834 100.57c0 53.709 45.517 100.573 105.834 100.573s105.834-46.864 105.834-100.573zM683.546 512c0 90.893-76.074 164.573-169.917 164.573s-169.917-73.68-169.917-164.573c0-90.89 76.074-164.57 169.917-164.57s169.917 73.68 169.917 164.57z"],"width":1056,"attrs":[{"fill":"rgb(108, 114, 122)"}],"isMulticolor":false,"isMulticolor2":false,"colorPermutations":{"1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101":[{"f":2}]},"tags":["unread-on-top"],"grid":0},"attrs":[{"fill":"rgb(108, 114, 122)"}],"properties":{"order":105,"id":183,"name":"unread-on-top","prevSize":32,"code":59857},"setIdx":0,"setId":4,"iconIdx":184},{"icon":{"paths":["M273.128 356.678c-6.62 27.766-2.384 63.757 25.498 105.581l33.166 49.75h-59.792c-44.011 0-70.484 14.874-86.279 33.619-16.496 19.578-24.573 47.264-23.734 77.491 0.841 30.262 10.601 59.968 25.818 81.312 15.127 21.216 33.404 31.578 52.195 31.578h143.999v64h-144c-45.209 0-80.932-25.642-104.306-58.426-23.283-32.656-36.522-74.95-37.682-116.688-1.16-41.773 9.763-86.083 38.766-120.506 20.726-24.598 49.155-42.32 84.825-50.784-16.1-39.011-19.009-77.050-10.731-111.77 11.253-47.2 42.305-84.492 80.791-107.342 38.4-22.798 85.677-32.139 131.303-21.965 35.584 7.935 68.909 27.48 95.251 59.554 53.75-35.147 127.584-30.892 182.483-2.495 34.438 17.812 64.794 46.382 81.437 85.010 12.294 28.531 16.438 61.011 10.608 96.205 46.112 6.682 81.507 25.155 105.616 53.456 30.346 35.626 38.102 81.35 33.443 123.283-4.659 41.917-21.946 83.485-46.544 115.114-24.061 30.934-59.354 57.354-101.261 57.354h-144v-64h144c14.093 0 32.8-9.581 50.742-32.646 17.398-22.371 30.112-52.806 33.453-82.89 3.341-30.067-2.902-56.339-18.554-74.714-15.222-17.869-44.035-33.75-97.642-33.75h-45.686l15.613-42.938c13.546-37.251 11.091-66.742 1.437-89.149-9.856-22.87-28.502-41.302-52.064-53.488-49.587-25.649-107.475-18.625-134.717 14.064l-30.134 36.16-22.541-41.325c-19.757-36.219-47.232-54.175-74.87-60.339-28.378-6.327-59.098-0.669-84.701 14.53-25.512 15.148-44.461 38.855-51.208 67.152zM630.979 588.778l-96-99.050c-6.029-6.218-14.32-9.728-22.979-9.728s-16.95 3.51-22.979 9.728l-96 99.050c-12.298 12.691-11.981 32.95 0.707 45.251 12.691 12.298 32.95 11.981 45.251-0.707l41.021-42.326v273.005c0 17.674 14.326 32 32 32s32-14.326 32-32v-273.005l41.021 42.326c12.301 12.688 32.56 13.005 45.251 0.707 12.691-12.301 13.005-32.56 0.707-45.251z"],"attrs":[{"fill":"rgb(108, 114, 122)"}],"isMulticolor":false,"isMulticolor2":false,"colorPermutations":{"1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101":[{"f":2}]},"tags":["upload"],"grid":0},"attrs":[{"fill":"rgb(108, 114, 122)"}],"properties":{"order":106,"id":184,"name":"upload","prevSize":32,"code":59858},"setIdx":0,"setId":4,"iconIdx":185},{"icon":{"paths":["M553.411 326.282c0 91.578-75.846 165.818-169.411 165.818-93.564 0-169.412-74.24-169.412-165.818 0-91.581 75.848-165.821 169.412-165.821 93.565 0 169.411 74.24 169.411 165.821zM485.648 326.282c0-54.949-45.51-99.493-101.648-99.493s-101.647 44.544-101.647 99.493c0 54.947 45.509 99.491 101.647 99.491s101.648-44.544 101.648-99.491z","M203.427 511.232c28.952-9.11 60.004-9.67 89.279-1.61l51.342 14.131c25.632 7.056 52.819 6.566 78.166-1.408l31.872-10.026c31.206-9.821 64.678-10.422 96.234-1.738 71.962 19.811 121.68 84.051 121.68 157.219v96.24c0 54.947-45.51 99.491-101.648 99.491h-372.705c-56.138 0-101.647-44.544-101.647-99.491v-108.566c0-65.814 43.411-124.106 107.427-144.243zM274.359 573.472c-16.46-4.531-33.918-4.218-50.196 0.906-35.992 11.322-60.399 44.093-60.399 81.098v108.566c0 18.317 15.17 33.165 33.882 33.165h372.705c18.714 0 33.882-14.848 33.882-33.165v-96.24c0-43.453-29.523-81.603-72.259-93.366-18.739-5.158-38.618-4.8-57.152 1.030l-31.872 10.026c-38.022 11.962-78.803 12.698-117.248 2.115l-51.343-14.134z","M797.091 189.321c0-15.913-13.024-28.812-29.091-28.812s-29.091 12.9-29.091 28.812v100.844h-101.818c-16.067 0-29.091 12.9-29.091 28.812s13.024 28.812 29.091 28.812h101.818v100.845c0 15.914 13.024 28.813 29.091 28.813s29.091-12.899 29.091-28.813v-100.845h101.818c16.067 0 29.091-12.899 29.091-28.812s-13.024-28.812-29.091-28.812h-101.818v-100.844z"],"attrs":[{"fill":"rgb(108, 114, 122)"},{"fill":"rgb(108, 114, 122)"},{"fill":"rgb(108, 114, 122)"}],"isMulticolor":false,"isMulticolor2":false,"colorPermutations":{"1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101":[{"f":2},{"f":2},{"f":2}]},"tags":["user-add"],"grid":0},"attrs":[{"fill":"rgb(108, 114, 122)"},{"fill":"rgb(108, 114, 122)"},{"fill":"rgb(108, 114, 122)"}],"properties":{"order":107,"id":185,"name":"user-add","prevSize":32,"code":59859},"setIdx":0,"setId":4,"iconIdx":186},{"icon":{"paths":["M464 318.747c0-51.548-42.406-94.366-96-94.366-53.592 0-96 42.819-96 94.366s42.408 94.367 96 94.367c53.594 0 96-42.819 96-94.367zM528 318.747c0 87.416-71.635 158.284-160 158.284s-160-70.867-160-158.284c0-87.417 71.635-158.282 160-158.282s160 70.865 160 158.282zM281.778 525.712c-27.649-7.693-56.976-7.158-84.319 1.536-60.46 19.226-101.459 74.864-101.459 137.69v103.629c0 52.451 42.981 94.97 96 94.97h352c3.606 0 7.165-0.195 10.666-0.579v-64.534c-3.334 1.168-6.925 1.802-10.666 1.802h-352c-17.673 0-32-14.173-32-31.658v-103.629c0-35.325 23.051-66.605 57.044-77.414 15.373-4.89 31.862-5.187 47.407-0.864l48.489 13.491c36.311 10.102 74.827 9.402 110.737-2.016l30.099-9.571c17.504-5.568 36.278-5.91 53.978-0.986 18.899 5.261 35.066 16.042 46.912 30.282v-79.712c-9.309-4.749-19.203-8.627-29.584-11.517-29.805-8.291-61.414-7.715-90.89 1.654l-30.099 9.574c-23.942 7.61-49.619 8.080-73.824 1.344l-48.491-13.491zM763.366 489.709c-12.326-12.646-32.586-12.918-45.248-0.608-12.666 12.31-12.938 32.544-0.611 45.19l102.842 105.51h-324.349c-17.674 0-32 14.307-32 31.958 0 17.648 14.326 31.958 32 31.958h324.349l-102.842 105.507c-12.326 12.65-12.054 32.88 0.611 45.194 12.662 12.31 32.922 12.038 45.248-0.611l155.718-159.757c12.093-12.406 12.093-32.176 0-44.582l-155.718-159.76z"],"attrs":[{"fill":"rgb(108, 114, 122)"}],"isMulticolor":false,"isMulticolor2":false,"colorPermutations":{"1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101":[{"f":2}]},"tags":["user-forward"],"grid":0},"attrs":[{"fill":"rgb(108, 114, 122)"}],"properties":{"order":108,"id":186,"name":"user-forward","prevSize":32,"code":59860},"setIdx":0,"setId":4,"iconIdx":187},{"icon":{"paths":["M609.354 336c0-53.019-42.979-96-96-96s-96 42.981-96 96c0 53.021 42.979 96 96 96s96-42.979 96-96zM673.354 336c0 88.365-71.635 160-160 160s-160-71.635-160-160c0-88.365 71.635-160 160-160s160 71.635 160 160zM413.805 551.802c-24.621-5.77-50.285-5.366-74.714 1.178-67.086 17.968-113.738 78.762-113.738 148.211v66.81c0 53.021 42.98 96 96 96h384c53.021 0 96-42.979 96-96v-58.531c0-74.301-51.149-138.826-123.488-155.779l-6.458-1.514c-25.674-6.016-52.435-5.594-77.907 1.229l-49.626 13.293c-20.378 5.456-41.789 5.795-62.326 0.979l-67.744-15.875zM355.651 614.8c14.237-3.814 29.197-4.051 43.549-0.688l67.744 15.878c30.806 7.219 62.925 6.714 93.488-1.472l49.629-13.293c15.283-4.096 31.341-4.349 46.742-0.736l6.458 1.51c43.402 10.173 74.093 48.89 74.093 93.469v58.531c0 17.674-14.326 32-32 32h-384c-17.673 0-32-14.326-32-32v-66.81c0-40.483 27.193-75.917 66.298-86.39z"],"width":1056,"attrs":[{"fill":"rgb(108, 114, 122)"}],"isMulticolor":false,"isMulticolor2":false,"colorPermutations":{"1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101":[{"f":2}]},"tags":["user"],"grid":0},"attrs":[{"fill":"rgb(108, 114, 122)"}],"properties":{"order":109,"id":187,"name":"user","prevSize":32,"code":59861},"setIdx":0,"setId":4,"iconIdx":188},{"icon":{"paths":["M201.583 672c35.346 0 64-28.653 64-64s-28.654-64-64-64c-35.346 0-64 28.653-64 64s28.654 64 64 64z","M329.584 576c-17.674 0-32.001 14.326-32.001 32s14.327 32 32.001 32h544c17.674 0 32-14.326 32-32s-14.326-32-32-32h-544z","M201.583 480c35.346 0 64-28.653 64-64s-28.654-64-64-64c-35.346 0-64 28.653-64 64s28.654 64 64 64z","M329.584 384c-17.674 0-32.001 14.326-32.001 32s14.327 32 32.001 32h544c17.674 0 32-14.326 32-32s-14.326-32-32-32h-544z","M201.583 288c35.346 0 64-28.654 64-64s-28.654-64-64-64c-35.346 0-64 28.654-64 64s28.654 64 64 64z","M329.584 192c-17.674 0-32.001 14.327-32.001 32s14.327 32 32.001 32h544c17.674 0 32-14.327 32-32s-14.326-32-32-32h-544z","M201.583 864c35.346 0 64-28.653 64-64s-28.654-64-64-64c-35.346 0-64 28.653-64 64s28.654 64 64 64z","M329.584 768c-17.674 0-32.001 14.326-32.001 32s14.327 32 32.001 32h544c17.674 0 32-14.326 32-32s-14.326-32-32-32h-544z"],"attrs":[{"fill":"rgb(108, 114, 122)"},{"fill":"rgb(108, 114, 122)"},{"fill":"rgb(108, 114, 122)"},{"fill":"rgb(108, 114, 122)"},{"fill":"rgb(108, 114, 122)"},{"fill":"rgb(108, 114, 122)"},{"fill":"rgb(108, 114, 122)"},{"fill":"rgb(108, 114, 122)"}],"isMulticolor":false,"isMulticolor2":false,"colorPermutations":{"1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101":[{"f":2},{"f":2},{"f":2},{"f":2},{"f":2},{"f":2},{"f":2},{"f":2}]},"tags":["view-condensed"],"grid":0},"attrs":[{"fill":"rgb(108, 114, 122)"},{"fill":"rgb(108, 114, 122)"},{"fill":"rgb(108, 114, 122)"},{"fill":"rgb(108, 114, 122)"},{"fill":"rgb(108, 114, 122)"},{"fill":"rgb(108, 114, 122)"},{"fill":"rgb(108, 114, 122)"},{"fill":"rgb(108, 114, 122)"}],"properties":{"order":110,"id":188,"name":"view-condensed","prevSize":32,"code":59862},"setIdx":0,"setId":4,"iconIdx":189},{"icon":{"paths":["M288 192c0-17.673 14.327-32 32-32h544c17.674 0 32 14.327 32 32s-14.326 32-32 32h-544c-17.673 0-32-14.327-32-32zM288 288c0-17.673 14.327-32 32-32h448c17.674 0 32 14.327 32 32s-14.326 32-32 32h-448c-17.673 0-32-14.327-32-32zM192 304c35.346 0 64-28.654 64-64s-28.654-64-64-64c-35.346 0-64 28.654-64 64s28.654 64 64 64z","M288 464c0-17.674 14.327-32 32-32h544c17.674 0 32 14.326 32 32s-14.326 32-32 32h-544c-17.673 0-32-14.326-32-32zM288 560c0-17.674 14.327-32 32-32h448c17.674 0 32 14.326 32 32s-14.326 32-32 32h-448c-17.673 0-32-14.326-32-32zM192 576c35.346 0 64-28.653 64-64s-28.654-64-64-64c-35.346 0-64 28.653-64 64s28.654 64 64 64z","M288 736c0-17.674 14.327-32 32-32h544c17.674 0 32 14.326 32 32s-14.326 32-32 32h-544c-17.673 0-32-14.326-32-32zM288 832c0-17.674 14.327-32 32-32h448c17.674 0 32 14.326 32 32s-14.326 32-32 32h-448c-17.673 0-32-14.326-32-32zM192 848c35.346 0 64-28.653 64-64s-28.654-64-64-64c-35.346 0-64 28.653-64 64s28.654 64 64 64z"],"attrs":[{"fill":"rgb(108, 114, 122)"},{"fill":"rgb(108, 114, 122)"},{"fill":"rgb(108, 114, 122)"}],"isMulticolor":false,"isMulticolor2":false,"colorPermutations":{"1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101":[{"f":2},{"f":2},{"f":2}]},"tags":["view-extended"],"grid":0},"attrs":[{"fill":"rgb(108, 114, 122)"},{"fill":"rgb(108, 114, 122)"},{"fill":"rgb(108, 114, 122)"}],"properties":{"order":111,"id":189,"name":"view-extended","prevSize":32,"code":59863},"setIdx":0,"setId":4,"iconIdx":190},{"icon":{"paths":["M192 320c35.346 0 64-28.654 64-64s-28.654-64-64-64c-35.346 0-64 28.654-64 64s28.654 64 64 64z","M320 224c-17.673 0-32 14.327-32 32s14.327 32 32 32h544c17.674 0 32-14.327 32-32s-14.326-32-32-32h-544z","M192 576c35.346 0 64-28.653 64-64s-28.654-64-64-64c-35.346 0-64 28.653-64 64s28.654 64 64 64z","M320 480c-17.673 0-32 14.326-32 32s14.327 32 32 32h544c17.674 0 32-14.326 32-32s-14.326-32-32-32h-544z","M192 832c35.346 0 64-28.653 64-64s-28.654-64-64-64c-35.346 0-64 28.653-64 64s28.654 64 64 64z","M320 736c-17.673 0-32 14.326-32 32s14.327 32 32 32h544c17.674 0 32-14.326 32-32s-14.326-32-32-32h-544z"],"attrs":[{"fill":"rgb(108, 114, 122)"},{"fill":"rgb(108, 114, 122)"},{"fill":"rgb(108, 114, 122)"},{"fill":"rgb(108, 114, 122)"},{"fill":"rgb(108, 114, 122)"},{"fill":"rgb(108, 114, 122)"}],"isMulticolor":false,"isMulticolor2":false,"colorPermutations":{"1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101":[{"f":2},{"f":2},{"f":2},{"f":2},{"f":2},{"f":2}]},"tags":["view-medium"],"grid":0},"attrs":[{"fill":"rgb(108, 114, 122)"},{"fill":"rgb(108, 114, 122)"},{"fill":"rgb(108, 114, 122)"},{"fill":"rgb(108, 114, 122)"},{"fill":"rgb(108, 114, 122)"},{"fill":"rgb(108, 114, 122)"}],"properties":{"order":112,"id":190,"name":"view-medium","prevSize":32,"code":59864},"setIdx":0,"setId":4,"iconIdx":191},{"icon":{"paths":["M640.854 256c26.512 0 48-21.49 48-48s-21.488-48-48-48c-26.509 0-48 21.49-48 48s21.491 48 48 48zM640.854 320c61.856 0 112-50.144 112-112s-50.144-112-112-112c-61.856 0-112 50.144-112 112s50.144 112 112 112zM592.854 416v256h-160v128h32v-96h224v-288h-96zM528.854 768h160c35.347 0 64-28.653 64-64v-288c0-35.347-28.653-64-64-64h-96c-35.344 0-64 28.653-64 64v192h-96c-35.344 0-64 28.653-64 64v128c0 35.347 28.656 64 64 64h32c35.347 0 64-28.653 64-64v-32zM784.854 448c0-17.674 14.326-32 32-32s32 14.326 32 32v384c0 17.674-14.326 32-32 32h-224c-17.674 0-32-14.326-32-32s14.326-32 32-32h192v-352zM368.403 401.52c9.677-9.059 10.176-24.246 1.117-33.923l-48.272-51.558v-47.925c0-13.255-10.746-24-24-24s-24 10.745-24 24v57.406c0 6.093 2.316 11.955 6.48 16.403l54.752 58.48c9.059 9.677 24.246 10.176 33.923 1.117zM170.974 414.278c53.308 74.218 156.687 91.171 230.908 37.862 10.765-7.731 25.76-5.274 33.494 5.491 7.731 10.768 5.274 25.763-5.494 33.494-95.75 68.771-229.121 46.902-297.894-48.848s-46.902-229.123 48.848-297.895c75.545-54.26 174.453-52.073 246.597-1.796 10.874 7.578 13.546 22.538 5.968 33.412s-22.538 13.547-33.411 5.968c-55.971-39.005-132.65-40.618-191.153 1.402-74.219 53.308-91.171 156.688-37.864 230.909z"],"attrs":[{"fill":"rgb(108, 114, 122)"}],"isMulticolor":false,"isMulticolor2":false,"colorPermutations":{"1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101":[{"f":2}]},"tags":["waiting-on-me"],"grid":0},"attrs":[{"fill":"rgb(108, 114, 122)"}],"properties":{"order":113,"id":191,"name":"waiting-on-me","prevSize":32,"code":59865},"setIdx":0,"setId":4,"iconIdx":192},{"icon":{"paths":["M512 352.003c17.674 0 32 14.33 32 32v224c0 17.674-14.326 32-32 32s-32-14.326-32-32v-224c0-17.67 14.326-32 32-32z","M512 672.003c17.674 0 32 14.33 32 32 0 17.674-14.326 32-32 32s-32-14.326-32-32c0-17.67 14.326-32 32-32z","M567.101 158.348c-24.774-41.922-85.427-41.922-110.202 0l-359.92 609.099c-25.21 42.662 5.544 96.557 55.099 96.557h719.845c49.555 0 80.307-53.894 55.098-96.557l-359.92-609.099zM512 190.906l359.923 609.097h-719.845l359.922-609.097z"],"attrs":[{"fill":"rgb(108, 114, 122)"},{"fill":"rgb(108, 114, 122)"},{"fill":"rgb(108, 114, 122)"}],"isMulticolor":false,"isMulticolor2":false,"colorPermutations":{"1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101":[{"f":2},{"f":2},{"f":2}]},"tags":["warning"],"grid":0},"attrs":[{"fill":"rgb(108, 114, 122)"},{"fill":"rgb(108, 114, 122)"},{"fill":"rgb(108, 114, 122)"}],"properties":{"order":114,"id":192,"name":"warning","prevSize":32,"code":59866},"setIdx":0,"setId":4,"iconIdx":193},{"icon":{"paths":["M784.131 240.143c-35.43-35.658-77.581-63.931-124.016-83.181s-96.227-29.094-146.493-28.961c-210.863 0-382.322 171.532-382.414 382.376-0.085 67.104 17.523 133.043 51.047 191.171l-54.256 198.154 202.72-53.174c56.064 30.531 118.883 46.531 182.717 46.538h0.166c210.752 0 382.32-171.552 382.394-382.394 0.16-50.25-9.645-100.029-28.848-146.464-19.2-46.436-47.418-88.604-83.018-124.065zM513.622 828.486h-0.186c-56.922 0.006-112.797-15.293-161.776-44.298l-11.606-6.896-120.302 31.555 32.124-117.347-7.573-12.029c-31.783-50.64-48.608-109.232-48.535-169.021 0-175.235 142.653-317.816 317.981-317.816 84.301 0.049 165.13 33.58 224.707 93.218 59.581 59.638 93.034 140.499 92.998 224.8-0.074 175.254-142.653 317.834-317.834 317.834zM687.958 590.451c-9.552-4.787-56.528-27.907-65.293-31.171s-15.126-4.768-21.491 4.784c-6.362 9.555-24.678 31.171-30.253 37.462-5.574 6.288-11.149 7.187-20.701 2.4-9.555-4.784-40.339-14.87-76.845-47.434-28.403-25.322-47.584-56.621-53.174-66.192-5.594-9.571-0.589-14.669 4.198-19.491 4.291-4.291 9.552-11.168 14.32-16.742s6.381-9.571 9.552-15.933c3.174-6.362 1.597-11.955-0.787-16.739-2.384-4.787-21.491-51.818-29.466-70.96-7.757-18.63-15.622-16.099-21.491-16.394-5.501-0.275-11.955-0.349-18.336-0.349-4.842 0.131-9.606 1.264-13.994 3.325-4.387 2.058-8.298 5.005-11.491 8.65-8.749 9.552-33.428 32.675-33.428 79.706s34.234 92.467 39.002 98.848c4.765 6.381 67.382 102.883 163.187 144.266 17.792 7.68 35.974 14.413 54.477 20.17 22.902 7.334 43.731 6.237 60.179 3.779 18.336-2.733 56.547-23.104 64.506-45.418 7.955-22.317 7.955-41.459 5.501-45.456-2.458-3.997-8.618-6.326-18.173-11.11z"],"attrs":[{"fill":"rgb(108, 114, 122)"}],"isMulticolor":false,"isMulticolor2":false,"colorPermutations":{"1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101":[{"f":2}]},"tags":["whatsapp-monochromatic"],"grid":0},"attrs":[{"fill":"rgb(108, 114, 122)"}],"properties":{"order":237,"id":193,"name":"whatsapp-monochromatic","prevSize":32,"code":59868},"setIdx":0,"setId":4,"iconIdx":194},{"icon":{"paths":["M354.006 399.123l14.029 0.627-0.541-16.726-152.878 1.578c49.916-116.491 165.64-198.133 300.366-198.133 99.645 0 188.893 44.659 248.81 115.065-3.712 0.171-7.491 0.492-11.526 1.068 0 0-63.76 14.898-13.302 118.708 0 0 31.085 84.541-19.306 198.774l-28.403 73.306-99.101-264.723c0 0-5.984-28.154 23.61-28.154l23.76-1.472 0.339-16.035h-228.368v14.458c0 0 39.83-0.288 56.15 27.149l40.762 103.779-73.014 174.403-108.608-272.4c0 0-3.389-31.779 27.222-31.27z","M841.603 513.168c0-33.862-5.235-66.643-14.726-97.357l-145.949 378.755c96.134-56.88 160.675-161.571 160.675-281.398z","M411.478 823.011c32.49 10.912 67.267 16.778 103.488 16.778 37.85 0 74.102-6.406 107.898-18.256l-99.709-258.502-111.677 259.981z","M188.279 513.149c0-32.083 4.712-63.066 13.339-92.336l1 2.051 152.656 375.229c-99.606-55.882-166.994-162.538-166.994-284.944z","M130.705 512.093c0-211.775 172.316-384.093 384.076-384.093 211.725 0 383.923 172.318 383.923 384.093 0 211.776-172.198 384.010-383.923 384.010-211.757 0-384.076-172.234-384.076-384.010zM514.781 879.254c202.384 0 367.11-164.742 367.11-367.178 0-202.486-164.726-367.145-367.11-367.161-202.489 0-367.213 164.673-367.213 367.161 0 202.435 164.724 367.178 367.213 367.178z"],"width":1056,"attrs":[{"fill":"rgb(108, 114, 122)"},{"fill":"rgb(108, 114, 122)"},{"fill":"rgb(108, 114, 122)"},{"fill":"rgb(108, 114, 122)"},{"fill":"rgb(108, 114, 122)"}],"isMulticolor":false,"isMulticolor2":false,"colorPermutations":{"1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101":[{"f":2},{"f":2},{"f":2},{"f":2},{"f":2}]},"tags":["wordpress-monochromatic"],"grid":0},"attrs":[{"fill":"rgb(108, 114, 122)"},{"fill":"rgb(108, 114, 122)"},{"fill":"rgb(108, 114, 122)"},{"fill":"rgb(108, 114, 122)"},{"fill":"rgb(108, 114, 122)"}],"properties":{"order":241,"id":194,"name":"wordpress-monochromatic","prevSize":32,"code":59656},"setIdx":0,"setId":4,"iconIdx":195},{"icon":{"paths":["M193.353 405.334h213.332v-213.334h-213.332v213.334zM129.353 170.667c0-23.564 19.102-42.667 42.667-42.667h256c23.565 0 42.666 19.102 42.666 42.667v255.999c0 23.565-19.101 42.669-42.666 42.669h-256c-23.564 0-42.667-19.104-42.667-42.669v-255.999zM620.019 405.334h213.334v-213.334h-213.334v213.334zM556.019 170.667c0-23.564 19.104-42.667 42.666-42.667h256c23.565 0 42.669 19.102 42.669 42.667v255.999c0 23.565-19.104 42.669-42.669 42.669h-256c-23.562 0-42.666-19.104-42.666-42.669v-255.999zM620.019 618.666h213.334v213.334h-213.334v-213.334zM598.685 554.666c-23.562 0-42.666 19.104-42.666 42.669v256c0 23.562 19.104 42.666 42.666 42.666h256c23.565 0 42.669-19.104 42.669-42.666v-256c0-23.565-19.104-42.669-42.669-42.669h-256zM193.353 832h213.332v-213.334h-213.332v213.334zM129.353 597.334c0-23.565 19.102-42.669 42.667-42.669h256c23.565 0 42.666 19.104 42.666 42.669v256c0 23.562-19.101 42.666-42.666 42.666h-256c-23.564 0-42.667-19.104-42.667-42.666v-256z"],"width":1056,"attrs":[{"fill":"rgb(108, 114, 122)"}],"isMulticolor":false,"isMulticolor2":false,"colorPermutations":{"1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101":[{"f":2}]},"tags":["workspaces"],"grid":0},"attrs":[{"fill":"rgb(108, 114, 122)"}],"properties":{"order":119,"id":195,"name":"workspaces","prevSize":32,"code":59870},"setIdx":0,"setId":4,"iconIdx":196},{"icon":{"paths":["M384.509 149.333c0-11.782 9.562-21.333 21.36-21.333h85.446c11.798 0 21.363 9.551 21.363 21.333v106.667h-106.81c-11.798 0-21.36-9.551-21.36-21.333v-85.333z","M512.678 256h106.806c11.798 0 21.36 9.551 21.36 21.333v85.332c0 11.782-9.562 21.334-21.36 21.334h-106.806v-128z","M384.509 405.334c0-11.782 9.562-21.334 21.36-21.334h106.81v128h-106.81c-11.798 0-21.36-9.552-21.36-21.334v-85.331z","M512.678 512h106.806c11.798 0 21.36 9.552 21.36 21.334v106.666h-128.166v-128z","M427.232 640c-23.597 0-42.723 19.104-42.723 42.666v170.669c0 23.562 19.126 42.666 42.723 42.666h170.89c23.597 0 42.723-19.104 42.723-42.666v-213.334h-213.613zM491.315 725.334c-11.798 0-21.36 9.549-21.36 21.331v42.669c0 11.782 9.562 21.331 21.36 21.331h42.723c11.798 0 21.36-9.549 21.36-21.331v-42.669c0-11.782-9.562-21.331-21.36-21.331h-42.723z"],"width":1056,"attrs":[{"fill":"rgb(108, 114, 122)"},{"fill":"rgb(108, 114, 122)"},{"fill":"rgb(108, 114, 122)"},{"fill":"rgb(108, 114, 122)"},{"fill":"rgb(108, 114, 122)"}],"isMulticolor":false,"isMulticolor2":false,"colorPermutations":{"1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101":[{"f":2},{"f":2},{"f":2},{"f":2},{"f":2}]},"tags":["zip"],"grid":0},"attrs":[{"fill":"rgb(108, 114, 122)"},{"fill":"rgb(108, 114, 122)"},{"fill":"rgb(108, 114, 122)"},{"fill":"rgb(108, 114, 122)"},{"fill":"rgb(108, 114, 122)"}],"properties":{"order":120,"id":196,"name":"zip","prevSize":32,"code":59871},"setIdx":0,"setId":4,"iconIdx":197},{"icon":{"paths":["M544 208c0-17.673-14.326-32-32-32s-32 14.327-32 32v271.997h-271.998c-17.673 0-32 14.326-32 32s14.327 32 32 32h271.998v272.003c0 17.674 14.326 32 32 32s32-14.326 32-32v-272.003l272-0.003c17.674 0 32-14.326 32-32s-14.326-32-32-32l-272 0.003v-271.997z"],"attrs":[{"fill":"rgb(108, 114, 122)"}],"isMulticolor":false,"isMulticolor2":false,"colorPermutations":{"1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101":[{"f":2}]},"tags":["add"],"grid":0},"attrs":[{"fill":"rgb(108, 114, 122)"}],"properties":{"order":240,"id":197,"name":"add","prevSize":32,"code":59872},"setIdx":0,"setId":4,"iconIdx":198}],"height":1024,"metadata":{"name":"custom"},"preferences":{"showGlyphs":true,"showQuickUse":true,"showQuickUse2":true,"showSVGs":true,"fontPref":{"prefix":"icon-","metadata":{"fontFamily":"custom","majorVersion":1,"minorVersion":0},"metrics":{"emSize":1024,"baseline":6.25,"whitespace":50},"embed":false},"imagePref":{"prefix":"icon-","png":true,"useClassSelector":true,"color":0,"bgColor":16777215,"classSelector":".icon"},"historySize":50,"showCodes":true,"gridSize":16}} \ No newline at end of file +{ + "IcoMoonType": "selection", + "icons": [ + { + "icon": { + "paths": [ + "M649.427 192c-26.454 0-52.016 10.804-71.005 30.343l-305.1 313.906c-31.48 32.387-49.322 76.506-49.322 122.694 0 46.186 17.842 90.304 49.322 122.694 31.451 32.355 73.91 50.362 117.984 50.362s86.531-18.006 117.981-50.362l305.101-313.907c12.317-12.672 32.576-12.96 45.248-0.643 12.675 12.317 12.963 32.576 0.646 45.251l-305.101 313.904c-43.302 44.554-102.23 69.757-163.875 69.757s-120.574-25.203-163.877-69.757c-43.273-44.522-67.428-104.717-67.428-167.299s24.155-122.781 67.428-167.302l305.1-313.905c30.845-31.735 72.874-49.737 116.899-49.737s86.054 18.002 116.899 49.737c30.816 31.704 47.971 74.514 47.971 118.968s-17.155 87.263-47.971 118.969l-305.43 313.904c-0.003 0.006 0.003-0.003 0 0-18.384 18.909-43.523 29.718-69.923 29.718-26.406 0-51.539-10.8-69.923-29.718-18.356-18.883-28.512-44.31-28.512-70.634 0-26.326 10.156-51.754 28.512-70.637l281.872-289.668c12.326-12.666 32.586-12.942 45.251-0.617s12.941 32.585 0.618 45.251l-281.846 289.638c-6.557 6.752-10.406 16.106-10.406 26.032 0 9.93 3.843 19.277 10.406 26.029 6.531 6.72 15.194 10.323 24.029 10.323s17.498-3.603 24.029-10.323l305.43-313.907c19.024-19.568 29.866-46.301 29.866-74.361 0-28.059-10.842-54.791-29.866-74.362-18.989-19.54-44.55-30.343-71.005-30.343z" + ], + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "isMulticolor": false, + "isMulticolor2": false, + "grid": 0, + "tags": ["attach"], + "colorPermutations": { + "1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101": [ + { "f": 2 } + ] + } + }, + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "properties": { "order": 254, "id": 205, "name": "attach", "prevSize": 32, "code": 59676 }, + "setIdx": 0, + "setId": 4, + "iconIdx": 0 + }, + { + "icon": { + "paths": [ + "M254.841 275.612l-13.972 14.271c-37.092 37.883-36.449 98.664 1.435 135.758l145.619 142.573c37.885 37.091 98.666 36.448 135.76-1.437l13.971-14.269c0.723-0.739 1.43-1.488 2.128-2.243l0.099 0.099c5.805-5.904 13.888-9.568 22.822-9.568 17.674 0 32 14.326 32 32 0 9.6-4.227 18.211-10.922 24.077l-0.397 0.41-13.974 14.269c-61.818 63.142-163.12 64.214-226.259 2.394l-145.622-142.576c-63.141-61.818-64.212-163.119-2.392-226.26l13.972-14.27c61.82-63.141 163.12-64.212 226.263-2.392l74.691 73.131c0.976 0.847 1.901 1.752 2.768 2.71l0.374 0.366-0.026 0.026c4.934 5.63 7.923 13.005 7.923 21.078 0 17.674-14.326 32-32 32-7.83 0-15.005-2.813-20.566-7.482l-0.106 0.109-77.834-76.206c-37.885-37.092-98.666-36.45-135.757 1.435zM790.566 768.003l13.971-14.269c37.091-37.885 36.448-98.666-1.437-135.757l-145.619-142.576c-37.885-37.091-98.666-36.448-135.757 1.437l-13.971 14.269c-0.723 0.739-1.434 1.488-2.128 2.243l-0.102-0.099c-5.805 5.907-13.885 9.568-22.822 9.568-17.67 0-32-14.326-32-32 0-9.6 4.227-18.211 10.922-24.077l0.4-0.406 13.971-14.272c61.821-63.142 163.12-64.211 226.262-2.39l145.619 142.573c63.142 61.821 64.211 163.12 2.394 226.262l-13.971 14.269c-61.821 63.142-163.123 64.211-226.262 2.394l-74.694-73.133c-0.976-0.845-1.901-1.75-2.768-2.71l-0.374-0.365 0.026-0.026c-4.931-5.629-7.923-13.005-7.923-21.078 0-17.674 14.326-32 32-32 7.83 0 15.005 2.813 20.566 7.482l0.106-0.109 77.837 76.208c37.885 37.091 98.662 36.448 135.757-1.437z" + ], + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "width": 1056, + "isMulticolor": false, + "isMulticolor2": false, + "grid": 0, + "tags": ["link"], + "colorPermutations": { + "1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101": [ + { "f": 2 } + ] + } + }, + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "properties": { "order": 253, "id": 204, "name": "link", "prevSize": 32, "code": 59752 }, + "setIdx": 0, + "setId": 4, + "iconIdx": 1 + }, + { + "icon": { + "paths": [ + "M512 896c-212.077 0-384-171.923-384-384s171.923-384 384-384c212.077 0 384 171.923 384 384s-171.923 384-384 384zM565.334 288c0-29.455-23.878-53.333-53.334-53.333s-53.334 23.878-53.334 53.333v249.632l180.016 144.013c23.002 18.403 56.563 14.672 74.963-8.326 18.403-23.002 14.672-56.563-8.326-74.963l-139.984-111.987v-198.368z" + ], + "attrs": [{ "fill": "rgb(243, 190, 8)" }], + "isMulticolor": false, + "isMulticolor2": false, + "grid": 0, + "tags": ["status-away"], + "colorPermutations": { + "1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101": [ + { "f": 3 } + ] + } + }, + "attrs": [{ "fill": "rgb(243, 190, 8)" }], + "properties": { "order": 247, "id": 203, "name": "status-away", "prevSize": 32, "code": 59741 }, + "setIdx": 0, + "setId": 4, + "iconIdx": 2 + }, + { + "icon": { + "paths": [ + "M512 896c-212.077 0-384-171.923-384-384s171.923-384 384-384c212.077 0 384 171.923 384 384s-171.923 384-384 384zM384 458.666c-29.456 0-53.334 23.875-53.334 53.331s23.878 53.334 53.334 53.334h256c29.456 0 53.334-23.878 53.334-53.334s-23.878-53.331-53.334-53.331h-256z" + ], + "attrs": [{ "fill": "rgb(245, 69, 92)" }], + "isMulticolor": false, + "isMulticolor2": false, + "grid": 0, + "tags": ["status-busy"], + "colorPermutations": { + "1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101": [ + { "f": 6 } + ] + } + }, + "attrs": [{ "fill": "rgb(245, 69, 92)" }], + "properties": { "order": 248, "id": 202, "name": "status-busy", "prevSize": 32, "code": 59742 }, + "setIdx": 0, + "setId": 4, + "iconIdx": 3 + }, + { + "icon": { + "paths": [ + "M888.691 586.941l-125.568-24.838c3.187-16.102 4.877-32.842 4.877-50.102s-1.69-34-4.877-50.102l125.568-24.838c4.794 24.237 7.309 49.296 7.309 74.941s-2.515 50.704-7.309 74.941zM831.322 298.644l-106.365 71.209c-18.726-27.971-42.838-52.083-70.81-70.81l71.21-106.364c41.875 28.035 77.93 64.089 105.965 105.965zM586.941 135.309l-24.838 125.566c-16.102-3.185-32.842-4.876-50.102-4.876s-34 1.69-50.102 4.876l-24.838-125.566c24.237-4.795 49.296-7.309 74.941-7.309s50.704 2.514 74.941 7.309zM298.644 192.679l71.209 106.364c-27.971 18.727-52.083 42.839-70.81 70.81l-106.364-71.209c28.035-41.876 64.089-77.93 105.965-105.964zM135.309 437.059c-4.795 24.237-7.309 49.296-7.309 74.941s2.514 50.704 7.309 74.941l125.566-24.838c-3.185-16.102-4.876-32.842-4.876-50.102s1.69-34 4.876-50.102l-125.566-24.838zM192.679 725.357l106.364-71.21c18.727 27.971 42.839 52.083 70.81 70.81l-71.209 106.365c-41.876-28.035-77.93-64.090-105.964-105.965zM437.059 888.691l24.838-125.568c16.102 3.187 32.842 4.877 50.102 4.877s34-1.69 50.102-4.877l24.838 125.568c-24.237 4.794-49.296 7.309-74.941 7.309s-50.704-2.515-74.941-7.309zM725.357 831.322l-71.21-106.365c27.971-18.726 52.083-42.838 70.81-70.81l106.365 71.21c-28.035 41.875-64.090 77.93-105.965 105.965z" + ], + "attrs": [{ "fill": "rgb(158, 162, 168)" }], + "isMulticolor": false, + "isMulticolor2": false, + "grid": 0, + "tags": ["status-loading"], + "colorPermutations": { + "1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101": [ + { "f": 7 } + ] + } + }, + "attrs": [{ "fill": "rgb(158, 162, 168)" }], + "properties": { "order": 249, "id": 201, "name": "status-loading", "prevSize": 32, "code": 59743 }, + "setIdx": 0, + "setId": 4, + "iconIdx": 4 + }, + { + "icon": { + "paths": [ + "M512 789.334c-153.168 0-277.333-124.166-277.333-277.334s124.165-277.333 277.333-277.333c153.168 0 277.334 124.165 277.334 277.333s-124.166 277.334-277.334 277.334zM512 896c212.077 0 384-171.923 384-384s-171.923-384-384-384c-212.077 0-384 171.923-384 384s171.923 384 384 384z" + ], + "attrs": [{ "fill": "rgb(158, 162, 168)" }], + "isMulticolor": false, + "isMulticolor2": false, + "grid": 0, + "tags": ["status-offline"], + "colorPermutations": { + "1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101": [ + { "f": 7 } + ] + } + }, + "attrs": [{ "fill": "rgb(158, 162, 168)" }], + "properties": { "order": 250, "id": 200, "name": "status-offline", "prevSize": 32, "code": 59744 }, + "setIdx": 0, + "setId": 4, + "iconIdx": 5 + }, + { + "icon": { + "paths": [ + "M896 512c0 212.077-171.923 384-384 384s-384-171.923-384-384c0-212.077 171.923-384 384-384s384 171.923 384 384z" + ], + "attrs": [{ "fill": "rgb(45, 224, 165)" }], + "isMulticolor": false, + "isMulticolor2": false, + "grid": 0, + "tags": ["status-online"], + "colorPermutations": { + "1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101": [ + { "f": 4 } + ] + } + }, + "attrs": [{ "fill": "rgb(45, 224, 165)" }], + "properties": { "order": 251, "id": 199, "name": "status-online", "prevSize": 32, "code": 59745 }, + "setIdx": 0, + "setId": 4, + "iconIdx": 6 + }, + { + "icon": { + "paths": [ + "M631.997 256h24v-56c0-57.437 46.563-104 104-104 57.44 0 104 46.562 104 104v56h24c17.674 0 32 14.327 32 32v192c0 17.674-14.326 32-32 32h-256c-17.67 0-32-14.326-32-32v-192c0-17.673 14.33-32 32-32zM759.997 160c-22.090 0-40 17.908-40 40v55.238h80v-55.238c0-22.092-17.907-40-40-40z", + "M527.997 224c10.096 0 19.882 1.336 29.184 3.84-13.251 16.46-21.184 37.383-21.184 60.16v0.664c-2.602-0.436-5.274-0.664-8-0.664-26.509 0-48 21.49-48 48s21.491 48 48 48c2.726 0 5.398-0.227 8-0.662v64.381c-2.64 0.186-5.309 0.282-8 0.282-61.856 0-112-50.144-112-112s50.144-112 112-112z", + "M492.941 500.282c14.883 5.709 30.883 7.283 46.355 4.758 6.182 22.938 20.646 42.474 39.987 55.206-35.123 13.318-74.019 13.309-109.261-0.208l-15.040-5.77c-12.838-4.925-27.104-4.589-39.699 0.931-19.008 8.333-31.286 27.12-31.286 47.872v132.928c0 17.674 14.33 32 32 32h224c17.674 0 32-14.326 32-32v-125.062c0-12.842-4.122-24.995-11.325-34.938h70.291c3.29 11.162 5.034 22.902 5.034 34.938v125.062c0 53.021-42.979 96-96 96h-224c-53.018 0-95.999-42.979-95.999-96v-132.928c0-46.163 27.311-87.955 69.592-106.49 28.019-12.278 59.747-13.024 88.31-2.067l15.040 5.766z", + "M887.997 576h-15.286v25.766c0 17.674-14.326 32-32 32h-72.714v64h72.714c53.021 0 96-42.979 96-96v-39.027c-14.278 8.426-30.931 13.261-48.714 13.261z", + "M281.182 416c53.020 0 95.999-42.979 95.999-96 0-53.019-42.979-96-95.999-96s-96 42.981-96 96c0 53.021 42.98 96 96 96zM281.182 352c-17.673 0-32-14.326-32-32s14.327-32 32-32c17.673 0 32 14.327 32 32s-14.327 32-32 32z", + "M625.267 640h-0.8c0.262 0.598 0.528 1.194 0.8 1.786v-1.786z", + "M357.59 464.582c2.531-1.107 5.091-2.122 7.674-3.043-17.907-5.155-37.011-4.832-54.823 1.018l-14.183 4.659c-14.887 4.893-30.998 4.554-45.668-0.954l-4.554-1.709c-21.067-7.91-44.203-8.394-65.581-1.37-40.565 13.325-67.986 51.197-67.986 93.894v44.688c0 53.021 42.98 96 96 96h79.53v-64h-79.53c-17.673 0-32-14.326-32-32v-44.688c0-15.050 9.664-28.394 23.96-33.091 7.534-2.474 15.688-2.304 23.112 0.483l4.554 1.709c21.238 7.974 44.041 10.333 66.238 7.027 10.398-30.173 32.992-55.357 63.258-68.624z", + "M423.914 640h0.797c-0.259 0.598-0.525 1.194-0.797 1.786v-1.786z" + ], + "attrs": [ + { "fill": "rgb(108, 114, 122)" }, + { "fill": "rgb(108, 114, 122)" }, + { "fill": "rgb(108, 114, 122)" }, + { "fill": "rgb(108, 114, 122)" }, + { "fill": "rgb(108, 114, 122)" }, + { "fill": "rgb(108, 114, 122)" }, + { "fill": "rgb(108, 114, 122)" }, + { "fill": "rgb(108, 114, 122)" } + ], + "isMulticolor": false, + "isMulticolor2": false, + "grid": 0, + "tags": ["teams-private"], + "colorPermutations": { + "1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101": [ + { "f": 2 }, + { "f": 2 }, + { "f": 2 }, + { "f": 2 }, + { "f": 2 }, + { "f": 2 }, + { "f": 2 }, + { "f": 2 } + ] + } + }, + "attrs": [ + { "fill": "rgb(108, 114, 122)" }, + { "fill": "rgb(108, 114, 122)" }, + { "fill": "rgb(108, 114, 122)" }, + { "fill": "rgb(108, 114, 122)" }, + { "fill": "rgb(108, 114, 122)" }, + { "fill": "rgb(108, 114, 122)" }, + { "fill": "rgb(108, 114, 122)" }, + { "fill": "rgb(108, 114, 122)" } + ], + "properties": { "order": 252, "id": 198, "name": "teams-private", "prevSize": 32, "code": 59750 }, + "setIdx": 0, + "setId": 4, + "iconIdx": 7 + }, + { + "icon": { + "paths": [ + "M368 182.4c0-17.673-14.326-32-32-32s-32 14.327-32 32v144h-144c-17.673 0-32 14.326-32 32s14.327 32 32 32h144v288h-144c-17.673 0-32 14.326-32 32s14.327 32 32 32h144v144c0 17.674 14.327 32 32 32s32-14.326 32-32v-144h288v144c0 17.674 14.326 32 32 32s32-14.326 32-32v-144h144c17.674 0 32-14.326 32-32s-14.326-32-32-32h-144v-80h-64v80h-288v-288h80v-64h-80v-144z", + "M640.515 327.283c-15.328-3.59-31.306-3.338-46.512 0.733-41.763 11.187-70.803 49.030-70.803 92.269v35.539c0 36.003 29.187 65.194 65.194 65.194h210.413c36.006 0 65.194-29.19 65.194-65.194v-28.336c0-47.667-33.040-88.957-79.27-99.792-16.413-3.846-33.613-3.603-49.946 0.771l-25.827 6.918c-10.381 2.781-21.286 2.95-31.747 0.499l-36.694-8.602z", + "M782.637 217.037c0 49.174-39.862 89.037-89.037 89.037s-89.037-39.863-89.037-89.037c0-49.174 39.862-89.037 89.037-89.037s89.037 39.863 89.037 89.037z" + ], + "attrs": [{ "fill": "rgb(108, 114, 122)" }, { "fill": "rgb(108, 114, 122)" }, { "fill": "rgb(108, 114, 122)" }], + "isMulticolor": false, + "isMulticolor2": false, + "colorPermutations": { + "1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101": [ + { "f": 2 }, + { "f": 2 }, + { "f": 2 } + ] + }, + "tags": ["channel-auto-join"], + "grid": 0 + }, + "attrs": [{ "fill": "rgb(108, 114, 122)" }, { "fill": "rgb(108, 114, 122)" }, { "fill": "rgb(108, 114, 122)" }], + "properties": { "order": 2, "id": 0, "name": "channel-auto-join", "prevSize": 32, "code": 59746 }, + "setIdx": 0, + "setId": 4, + "iconIdx": 8 + }, + { + "icon": { + "paths": [ + "M236.709 162.578c11.779-5.037 25.426-2.564 34.688 6.286l150.709 144c6.32 6.037 9.894 14.396 9.894 23.136s-3.574 17.101-9.894 23.136l-150.709 144c-9.262 8.851-22.909 11.325-34.688 6.288s-19.419-16.614-19.419-29.424v-112h-73.29c-17.673 0-32-14.326-32-32 0-17.672 14.327-31.999 32-31.999h73.29v-112c0-12.81 7.64-24.386 19.419-29.423zM320 368.179v-0.179h0.189l-0.189 0.179zM320.189 304.001h-0.189v-0.179l0.189 0.179z", + "M492.899 303.258c8.762-9.388 21.245-15.258 35.101-15.258 26.509 0 48 21.49 48 48s-21.491 48-48 48c-16.582 0-31.203-8.41-39.824-21.197l-43.098 48.483c20.49 22.554 50.051 36.714 82.922 36.714 61.856 0 112-50.144 112-112s-50.144-112-112-112c-25.549 0-49.098 8.554-67.942 22.955l32.842 56.303z", + "M145.615 483.229l32.174 64.349c-0.861 3.040-1.318 6.23-1.318 9.501v44.688c0 17.674 14.327 32 32 32h79.53v64h-79.53c-53.019 0-96-42.979-96-96v-44.688c0-28.848 12.515-55.488 33.144-73.85z", + "M625.267 640h-0.797c0.259 0.598 0.525 1.194 0.797 1.786v-1.786z", + "M424.714 640h-0.797v1.786c0.272-0.592 0.538-1.187 0.797-1.786z", + "M477.901 494.515c-28.56-10.957-60.291-10.211-88.307 2.067-42.282 18.534-69.594 60.326-69.594 106.49v132.928c0 53.021 42.979 96 96 96h224c53.021 0 96-42.979 96-96v-125.062c0-51.162-31.536-97.034-79.306-115.354-30.352-11.642-64.067-10.851-93.84 2.198l-2.070 0.909c-21.523 9.434-45.901 10.006-67.843 1.59l-15.040-5.766zM415.286 555.2c12.595-5.52 26.858-5.856 39.699-0.931l15.040 5.77c37.664 14.445 79.504 13.462 116.451-2.73l2.070-0.909c14.349-6.288 30.602-6.669 45.229-1.059 23.024 8.829 38.224 30.938 38.224 55.597v125.062c0 17.674-14.326 32-32 32h-224c-17.674 0-32-14.326-32-32v-132.928c0-20.752 12.278-39.539 31.286-47.872z", + "M864 320c0-53.019-42.979-96-96-96s-96 42.981-96 96c0 53.021 42.979 96 96 96s96-42.979 96-96zM800 320c0 17.674-14.326 32-32 32s-32-14.326-32-32c0-17.673 14.326-32 32-32s32 14.327 32 32z", + "M840.714 697.766h-72.714v-64h72.714c17.674 0 32-14.326 32-32v-44.688c0-15.050-9.664-28.394-23.958-33.091-7.536-2.474-15.69-2.304-23.114 0.483l-4.554 1.709c-19.77 7.421-40.89 9.978-61.619 7.632-12.438-31.683-37.722-57.549-70.774-70.227-1.754-0.672-3.52-1.302-5.296-1.894 18.058-5.312 37.36-5.040 55.344 0.867l14.182 4.659c14.886 4.893 30.998 4.554 45.667-0.954l4.554-1.709c21.069-7.91 44.205-8.394 65.581-1.37 40.566 13.325 67.987 51.197 67.987 93.894v44.688c0 53.021-42.982 96-96 96z" + ], + "attrs": [ + { "fill": "rgb(108, 114, 122)" }, + { "fill": "rgb(108, 114, 122)" }, + { "fill": "rgb(108, 114, 122)" }, + { "fill": "rgb(108, 114, 122)" }, + { "fill": "rgb(108, 114, 122)" }, + { "fill": "rgb(108, 114, 122)" }, + { "fill": "rgb(108, 114, 122)" }, + { "fill": "rgb(108, 114, 122)" } + ], + "isMulticolor": false, + "isMulticolor2": false, + "colorPermutations": { + "1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101": [ + { "f": 2 }, + { "f": 2 }, + { "f": 2 }, + { "f": 2 }, + { "f": 2 }, + { "f": 2 }, + { "f": 2 }, + { "f": 2 } + ] + }, + "tags": ["channel-move-to-team"], + "grid": 0 + }, + "attrs": [ + { "fill": "rgb(108, 114, 122)" }, + { "fill": "rgb(108, 114, 122)" }, + { "fill": "rgb(108, 114, 122)" }, + { "fill": "rgb(108, 114, 122)" }, + { "fill": "rgb(108, 114, 122)" }, + { "fill": "rgb(108, 114, 122)" }, + { "fill": "rgb(108, 114, 122)" }, + { "fill": "rgb(108, 114, 122)" } + ], + "properties": { "order": 3, "id": 1, "name": "channel-move-to-team", "prevSize": 32, "code": 59747 }, + "setIdx": 0, + "setId": 4, + "iconIdx": 9 + }, + { + "icon": { + "paths": [ + "M512 128c-97.203 0-176 78.798-176 176v142.477h-16c-53.020 0-96 42.979-96 96v257.523c0 53.021 42.981 96 96 96h384c53.021 0 96-42.979 96-96v-257.523c0-53.021-42.979-96-96-96h-16v-142.477c0-97.202-78.797-176-176-176zM624 304v142.477h-224v-142.477c0-61.856 50.144-112 112-112s112 50.144 112 112z" + ], + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "isMulticolor": false, + "isMulticolor2": false, + "colorPermutations": { + "1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101": [ + { "f": 2 } + ] + }, + "tags": ["lock-filled"], + "grid": 0 + }, + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "properties": { "order": 4, "id": 2, "name": "lock-filled", "prevSize": 32, "code": 59748 }, + "setIdx": 0, + "setId": 4, + "iconIdx": 10 + }, + { + "icon": { + "paths": [ + "M336 304c0-97.202 78.797-176 176-176s176 78.798 176 176v142.477h16c53.021 0 96 42.979 96 96v257.523c0 53.021-42.979 96-96 96h-384c-53.019 0-96-42.979-96-96v-257.523c0-53.021 42.981-96 96-96h16v-142.477zM400 446.477h224v-142.477c0-61.856-50.144-112-112-112s-112 50.144-112 112v142.477zM320 510.477c-17.673 0-32 14.326-32 32v257.523c0 17.674 14.327 32 32 32h384c17.674 0 32-14.326 32-32v-257.523c0-17.674-14.326-32-32-32h-384z" + ], + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "isMulticolor": false, + "isMulticolor2": false, + "colorPermutations": { + "1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101": [ + { "f": 2 } + ] + }, + "tags": ["locker"], + "grid": 0 + }, + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "properties": { "order": 5, "id": 3, "name": "locker", "prevSize": 32, "code": 59749 }, + "setIdx": 0, + "setId": 4, + "iconIdx": 11 + }, + { + "icon": { + "paths": [ + "M281.184 336c17.673 0 32-14.326 32-32s-14.327-32-32-32c-17.673 0-32 14.327-32 32s14.327 32 32 32zM281.184 400c-53.020 0-96-42.979-96-96 0-53.019 42.98-96 96-96 53.018 0 96 42.981 96 96 0 53.021-42.982 96-96 96zM576 320c0-26.51-21.491-48-48-48s-48 21.49-48 48c0 26.509 21.491 48 48 48s48-21.491 48-48zM640 320c0 61.856-50.144 112-112 112s-112-50.144-112-112c0-61.856 50.144-112 112-112s112 50.144 112 112zM477.901 478.515c-28.56-10.957-60.291-10.211-88.307 2.067-42.282 18.534-69.594 60.326-69.594 106.49v132.928c0 53.021 42.979 96 96 96h224c53.021 0 96-42.979 96-96v-125.062c0-51.162-31.536-97.034-79.306-115.354-30.352-11.642-64.067-10.851-93.84 2.198l-2.070 0.909c-21.523 9.434-45.901 10.006-67.843 1.59l-15.040-5.766zM415.286 539.2c12.595-5.52 26.858-5.856 39.699-0.931l15.040 5.77c37.664 14.445 79.504 13.462 116.451-2.73l2.070-0.909c14.349-6.288 30.602-6.669 45.229-1.059 23.024 8.829 38.224 30.938 38.224 55.597v125.062c0 17.674-14.326 32-32 32h-224c-17.674 0-32-14.326-32-32v-132.928c0-20.752 12.278-39.539 31.286-47.872zM768 336c-17.674 0-32-14.326-32-32s14.326-32 32-32c17.674 0 32 14.327 32 32s-14.326 32-32 32zM768 400c53.021 0 96-42.979 96-96 0-53.019-42.979-96-96-96s-96 42.981-96 96c0 53.021 42.979 96 96 96zM840.714 681.766h-72.714v-64h72.714c17.674 0 32-14.326 32-32v-44.688c0-15.050-9.664-28.394-23.958-33.091-7.536-2.474-15.69-2.304-23.114 0.483l-4.554 1.709c-19.77 7.421-40.89 9.978-61.619 7.632-12.438-31.683-37.722-57.549-70.774-70.227-1.754-0.672-3.52-1.302-5.296-1.894 18.058-5.312 37.36-5.040 55.344 0.867l14.182 4.659c14.886 4.893 30.998 4.554 45.667-0.954l4.554-1.709c21.069-7.91 44.205-8.394 65.581-1.37 40.566 13.325 67.987 51.197 67.987 93.894v44.688c0 53.021-42.982 96-96 96zM625.267 624h-0.797c0.259 0.598 0.525 1.194 0.797 1.786v-1.786zM357.594 448.582c2.528-1.107 5.088-2.122 7.674-3.043-17.907-5.155-37.014-4.832-54.824 1.018l-14.183 4.659c-14.887 4.893-30.998 4.554-45.668-0.954l-4.554-1.709c-21.067-7.91-44.203-8.394-65.581-1.37-40.565 13.325-67.986 51.197-67.986 93.894v44.688c0 53.021 42.981 96 96 96h79.53v-64h-79.53c-17.673 0-32-14.326-32-32v-44.688c0-15.050 9.664-28.394 23.96-33.091 7.534-2.474 15.688-2.304 23.112 0.483l4.554 1.709c21.238 7.974 44.041 10.333 66.238 7.027 10.398-30.173 32.994-55.357 63.259-68.624zM423.917 624h0.797c-0.259 0.598-0.525 1.194-0.797 1.786v-1.786z" + ], + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "isMulticolor": false, + "isMulticolor2": false, + "colorPermutations": { + "1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101": [ + { "f": 2 } + ] + }, + "tags": ["teams"], + "grid": 0 + }, + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "properties": { "order": 7, "id": 5, "name": "teams", "prevSize": 32, "code": 59751 }, + "setIdx": 0, + "setId": 4, + "iconIdx": 12 + }, + { + "icon": { + "paths": [ + "M502.627 142.69c12.915-3.408 26.509-3.289 39.363 0.345l245.194 69.317c29.165 8.245 51.254 33.818 53.504 65.14 24.506 341.145-184.394 520.422-272.611 580.214-33.053 22.403-75.28 22.474-108.416 0.259-88.758-59.504-300.1-238.589-276.51-579.794 2.205-31.89 24.96-57.772 54.753-65.633l264.724-69.849zM524.579 204.621c-1.837-0.519-3.779-0.536-5.622-0.049l-264.725 69.849c-4.4 1.161-6.999 4.775-7.233 8.165-21.305 308.166 168.214 468.531 248.301 522.218 11.507 7.715 25.434 7.677 36.87-0.077 79.379-53.798 266.835-214.288 244.685-522.649-0.243-3.358-2.787-6.925-7.082-8.139l-245.194-69.317z" + ], + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "isMulticolor": false, + "isMulticolor2": false, + "colorPermutations": { + "1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101": [ + { "f": 2 } + ] + }, + "tags": ["set-as-moderator"], + "grid": 0 + }, + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "properties": { "order": 245, "id": 6, "name": "shield", "prevSize": 32, "code": 59661 }, + "setIdx": 0, + "setId": 4, + "iconIdx": 13 + }, + { + "icon": { + "paths": [ + "M864 512c0-86.89-31.482-166.429-83.664-227.826l-496.162 496.162c61.397 52.182 140.937 83.664 227.826 83.664 194.403 0 352-157.597 352-352zM239.349 734.65l495.3-495.3c-60.662-49.597-138.182-79.349-222.65-79.349-194.404 0-352 157.596-352 352 0 84.467 29.753 161.987 79.349 222.65zM928 512c0 229.75-186.25 416-416 416s-416-186.25-416-416c0-229.75 186.25-416 416-416s416 186.25 416 416z" + ], + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "isMulticolor": false, + "isMulticolor2": false, + "colorPermutations": { + "1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101": [ + { "f": 2 } + ] + }, + "tags": ["ignore"], + "grid": 0 + }, + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "properties": { "order": 246, "id": 7, "name": "ignore", "prevSize": 32, "code": 59740 }, + "setIdx": 0, + "setId": 4, + "iconIdx": 14 + }, + { + "icon": { + "paths": [ + "M819.2 204.8v614.4h-614.4v-614.4h614.4zM204.8 128c-42.415 0-76.8 34.385-76.8 76.8v614.4c0 42.416 34.385 76.8 76.8 76.8h614.4c42.416 0 76.8-34.384 76.8-76.8v-614.4c0-42.415-34.384-76.8-76.8-76.8h-614.4z" + ], + "attrs": [{ "fill": "rgb(203, 206, 209)" }], + "isMulticolor": false, + "isMulticolor2": false, + "colorPermutations": { + "1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101": [ + { "f": 8 } + ] + }, + "tags": ["checkbox-unchecked"], + "grid": 0 + }, + "attrs": [{ "fill": "rgb(203, 206, 209)" }], + "properties": { "order": 242, "id": 8, "name": "checkbox-unchecked", "prevSize": 32, "code": 59648 }, + "setIdx": 0, + "setId": 4, + "iconIdx": 15 + }, + { + "icon": { + "paths": [ + "M204.8 128h614.4c42.416 0 76.8 34.385 76.8 76.8v614.4c0 42.416-34.384 76.8-76.8 76.8h-614.4c-42.415 0-76.8-34.384-76.8-76.8v-614.4c0-42.415 34.385-76.8 76.8-76.8zM769.062 336.88c9.322-9.424 9.238-24.619-0.182-33.941-9.424-9.322-24.621-9.241-33.942 0.182l-339.085 342.745-106.782-108.051c-9.317-9.43-24.513-9.52-33.94-0.202s-9.518 24.512-0.201 33.939l123.842 125.318c4.509 4.56 10.653 7.126 17.066 7.13s12.557-2.563 17.069-7.12l356.157-360z" + ], + "attrs": [{ "fill": "rgb(29, 116, 245)" }], + "isMulticolor": false, + "isMulticolor2": false, + "colorPermutations": { + "1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101": [ + { "f": 5 } + ] + }, + "tags": ["checkbox-checked"], + "grid": 0 + }, + "attrs": [{ "fill": "rgb(29, 116, 245)" }], + "properties": { "order": 243, "id": 9, "name": "checkbox-checked", "prevSize": 32, "code": 59649 }, + "setIdx": 0, + "setId": 4, + "iconIdx": 16 + }, + { + "icon": { + "paths": [ + "M862.454 324.045c-35.2-60.31-82.944-108.057-143.248-143.253-60.314-35.198-126.16-52.792-197.581-52.792-71.414 0-137.28 17.6-197.581 52.792-60.31 35.194-108.053 82.943-143.253 143.253-35.194 60.307-52.792 126.166-52.792 197.571 0 85.77 25.024 162.899 75.086 231.405 50.056 68.509 114.721 115.917 193.989 142.224 9.229 1.712 16.058 0.509 20.499-3.584 4.442-4.096 6.662-9.226 6.662-15.37 0-1.024-0.090-10.246-0.259-27.674-0.176-17.43-0.259-32.637-0.259-45.61l-11.789 2.038c-7.517 1.379-16.998 1.962-28.445 1.795-11.443-0.16-23.322-1.357-35.619-3.587-12.304-2.211-23.747-7.334-34.342-15.366-10.588-8.029-18.104-18.538-22.547-31.514l-5.125-11.795c-3.416-7.853-8.795-16.573-16.142-26.134-7.348-9.571-14.778-16.058-22.294-19.475l-3.588-2.57c-2.391-1.706-4.61-3.766-6.662-6.154-2.050-2.39-3.585-4.781-4.61-7.174-1.027-2.397-0.176-4.365 2.562-5.907 2.738-1.539 7.685-2.288 14.864-2.288l10.247 1.53c6.834 1.37 15.287 5.462 25.371 12.298 10.078 6.835 18.363 15.715 24.856 26.646 7.863 14.013 17.335 24.688 28.445 32.038 11.101 7.347 22.294 11.014 33.568 11.014s21.011-0.854 29.216-2.557c8.192-1.709 15.882-4.275 23.062-7.69 3.075-22.902 11.446-40.499 25.11-52.797-19.475-2.045-36.982-5.13-52.534-9.226-15.542-4.106-31.603-10.765-48.173-20-16.579-9.222-30.331-20.672-41.262-34.333-10.932-13.67-19.905-31.613-26.904-53.818-7.003-22.214-10.505-47.837-10.505-76.88 0-41.35 13.5-76.541 40.493-105.584-12.645-31.088-11.451-65.939 3.585-104.55 9.908-3.079 24.606-0.768 44.078 6.916 19.478 7.689 33.738 14.275 42.797 19.736 9.059 5.46 16.317 10.087 21.786 13.837 31.782-8.88 64.582-13.322 98.406-13.322s66.63 4.442 98.416 13.322l19.475-12.295c13.318-8.204 29.046-15.722 47.142-22.556 18.112-6.83 31.958-8.712 41.53-5.633 15.37 38.612 16.739 73.46 4.093 104.548 26.992 29.046 40.496 64.243 40.496 105.587 0 29.043-3.514 54.746-10.506 77.13-7.002 22.387-16.051 40.314-27.152 53.818-11.114 13.501-24.957 24.864-41.526 34.083-16.573 9.226-32.637 15.888-48.179 19.99-15.552 4.102-33.059 7.187-52.534 9.238 17.763 15.37 26.646 39.632 26.646 72.774v108.134c0 6.141 2.134 11.27 6.41 15.37 4.272 4.090 11.018 5.296 20.243 3.581 79.28-26.304 143.946-73.712 194-142.224 50.048-68.502 75.082-145.632 75.082-231.405-0.019-71.395-17.626-137.248-52.803-197.555z" + ], + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "isMulticolor": false, + "isMulticolor2": false, + "colorPermutations": { + "1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101": [ + { "f": 2 } + ] + }, + "tags": ["github-monochromatic"], + "grid": 0 + }, + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "properties": { "order": 152, "id": 10, "name": "github-monochromatic", "prevSize": 32, "code": 59650 }, + "setIdx": 0, + "setId": 4, + "iconIdx": 17 + }, + { + "icon": { + "paths": [ + "M133.618 423.61h215.092l-92.537-284.607c-4.74-14.67-25.504-14.67-30.244 0l-92.311 284.607zM86.899 567.171l46.72-143.546h737.133l46.72 143.546c4.288 13.088-0.451 27.533-11.51 35.434l-403.776 293.408-403.776-293.408c-11.060-7.901-15.799-22.346-11.511-35.434zM655.661 423.61h215.091l-92.31-284.607c-4.739-14.67-25.504-14.67-30.243 0l-92.538 284.607z" + ], + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "isMulticolor": false, + "isMulticolor2": false, + "colorPermutations": { + "1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101": [ + { "f": 2 } + ] + }, + "tags": ["gitlab-monochromatic"], + "grid": 0 + }, + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "properties": { "order": 153, "id": 11, "name": "gitlab-monochromatic", "prevSize": 32, "code": 59651 }, + "setIdx": 0, + "setId": 4, + "iconIdx": 18 + }, + { + "icon": { + "paths": [ + "M658.794 338.154c-39.798-38.052-90.416-57.426-146.794-57.426-100.016 0-184.669 67.548-214.865 158.313l-0.002-0.003c-7.679 23.040-12.042 47.648-12.042 72.957s4.364 49.92 12.044 72.96l0 0.003c30.196 90.765 114.849 158.314 214.865 158.314 51.664 0 95.651-13.613 130.035-36.653v-0.016c40.669-27.229 67.725-67.898 76.627-115.898h-206.662v-148.538h361.658c4.538 25.136 6.982 51.315 6.982 78.547 0 116.944-41.891 215.389-114.502 282.24v0.013c-63.533 58.646-150.458 93.030-254.138 93.030-150.109 0-279.971-86.048-343.156-211.549l-0-0.003c-26.007-51.84-40.844-110.49-40.844-172.451 0-61.965 14.836-120.611 40.844-172.451h0.004c63.187-125.495 193.047-211.542 343.153-211.542 103.504 0 190.429 38.051 256.931 100.014l-110.138 110.139z" + ], + "width": 1056, + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "isMulticolor": false, + "isMulticolor2": false, + "colorPermutations": { + "1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101": [ + { "f": 2 } + ] + }, + "tags": ["google-monochromatic"], + "grid": 0 + }, + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "properties": { "order": 154, "id": 12, "name": "google-monochromatic", "prevSize": 32, "code": 59652 }, + "setIdx": 0, + "setId": 4, + "iconIdx": 19 + }, + { + "icon": { + "paths": [ + "M840.541 128c31.318 0 56.813 24.79 56.813 55.383v657.212c0 30.592-25.494 55.427-56.813 55.427h-654.546c-31.254 0-56.642-24.835-56.642-55.427v-657.212c0-30.593 25.387-55.383 56.642-55.383h654.546zM300.196 233.75c-36.588 0-66.093 29.59-66.093 66.050 0 36.482 29.505 66.072 66.093 66.072 36.437 0 66.005-29.59 66.005-66.072 0-36.46-29.568-66.050-66.005-66.050zM243.148 782.461h114.029v-366.515h-114.029v366.515zM537.814 415.923h-109.187v366.515h113.773v-181.274c0-47.83 9.046-94.147 68.333-94.147 58.454 0 59.181 54.678 59.181 97.174v178.246h113.901v-201.008c0-98.714-21.312-174.598-136.666-174.598-55.402 0-92.566 30.381-107.757 59.203h-1.578v-50.112z" + ], + "width": 1056, + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "isMulticolor": false, + "isMulticolor2": false, + "colorPermutations": { + "1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101": [ + { "f": 2 } + ] + }, + "tags": ["linkedin-monochromatic"], + "grid": 0 + }, + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "properties": { "order": 155, "id": 13, "name": "linkedin-monochromatic", "prevSize": 32, "code": 59653 }, + "setIdx": 0, + "setId": 4, + "iconIdx": 20 + }, + { + "icon": { + "paths": [ + "M86.686 85.336l730.792 774.088c0 0 24.899 17.558 43.936-2.928 19.040-20.486 4.394-40.973 4.394-40.973l-779.122-730.187zM318.080 158.503l556.516 599.955c0 0 24.896 17.558 43.936-2.928 19.037-20.486 4.394-40.973 4.394-40.973l-604.845-556.054zM712.035 915.030l-556.517-599.955 604.843 556.054c0 0 14.646 20.486-4.39 40.973-19.040 20.486-43.936 2.928-43.936 2.928zM513.693 221.419l388.803 419.15c0 0 17.395 12.269 30.694-2.042 13.302-14.314 3.069-28.627 3.069-28.627l-422.566-388.482zM597.878 915.677l-388.805-419.152 422.568 388.48c0 0 10.234 14.314-3.069 28.627-13.302 14.31-30.694 2.045-30.694 2.045zM713.498 312.143l176.221 190.551c0 0 8.605 5.747 15.184-0.96 6.579-6.704 1.517-13.411 1.517-13.411l-192.922-176.18zM482.582 880.23l-176.219-190.55 192.923 176.179c0 0 5.059 6.707-1.52 13.414-6.579 6.704-15.184 0.957-15.184 0.957z" + ], + "width": 1056, + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "isMulticolor": false, + "isMulticolor2": false, + "colorPermutations": { + "1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101": [ + { "f": 2 } + ] + }, + "tags": ["meteor-monochromatic"], + "grid": 0 + }, + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "properties": { "order": 156, "id": 14, "name": "meteor-monochromatic", "prevSize": 32, "code": 59654 }, + "setIdx": 0, + "setId": 4, + "iconIdx": 21 + }, + { + "icon": { + "paths": [ + "M378.861 853.331c317.456 0 491.091-262.662 491.091-490.442 0-7.462 0-14.89-0.506-22.282 33.779-24.401 62.938-54.614 86.112-89.224-31.501 13.94-64.918 23.082-99.136 27.12 36.032-21.542 62.998-55.424 75.882-95.34-33.878 20.078-70.944 34.228-109.597 41.839-53.501-56.814-138.515-70.72-207.37-33.919-68.851 36.801-104.426 115.155-86.768 191.127-138.774-6.947-268.074-72.409-355.715-180.093-45.811 78.76-22.412 179.517 53.436 230.1-27.467-0.813-54.335-8.214-78.337-21.574 0 0.704 0 1.443 0 2.182 0.022 82.051 57.937 152.723 138.47 168.97-25.41 6.922-52.071 7.933-77.933 2.957 22.611 70.218 87.409 118.32 161.25 119.706-61.116 47.968-136.616 74.010-214.35 73.933-13.732-0.026-27.452-0.858-41.087-2.486 78.931 50.586 170.772 77.418 264.557 77.293z" + ], + "width": 1056, + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "isMulticolor": false, + "isMulticolor2": false, + "colorPermutations": { + "1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101": [ + { "f": 2 } + ] + }, + "tags": ["twitter-monochromatic"], + "grid": 0 + }, + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "properties": { "order": 157, "id": 15, "name": "twitter-monochromatic", "prevSize": 32, "code": 59655 }, + "setIdx": 0, + "setId": 4, + "iconIdx": 22 + }, + { + "icon": { + "paths": [ + "M512 928c-14.218 0-28.275-0.714-42.144-2.112-16.694-1.68-30.557-12.528-36.688-27.395l-40.538-98.304-98.162 40.838c-14.837 6.173-32.3 4.048-45.292-6.554-21.838-17.818-41.831-37.811-59.65-59.648-10.601-12.992-12.726-30.454-6.553-45.293l40.839-98.16-98.307-40.541c-14.864-6.131-25.714-19.994-27.395-36.688-1.396-13.869-2.111-27.926-2.111-42.144 0-14.214 0.714-28.275 2.11-42.141 1.681-16.694 12.531-30.557 27.395-36.688l98.308-40.541-40.84-98.163c-6.173-14.837-4.047-32.3 6.553-45.292 17.818-21.838 37.81-41.83 59.648-59.648 12.992-10.6 30.455-12.726 45.292-6.553l98.165 40.84 40.541-98.309c6.128-14.865 19.994-25.714 36.688-27.395 13.866-1.396 27.926-2.11 42.141-2.11s28.275 0.714 42.141 2.11c16.694 1.681 30.56 12.531 36.688 27.395l40.541 98.309 98.163-40.84c14.838-6.173 32.301-4.047 45.293 6.553 21.837 17.818 41.83 37.81 59.648 59.648 10.602 12.992 12.726 30.455 6.554 45.292l-40.842 98.163 98.31 40.541c14.864 6.131 25.712 19.994 27.392 36.688 1.398 13.866 2.112 27.926 2.112 42.141 0 14.218-0.714 28.275-2.112 42.144-1.68 16.694-12.528 30.557-27.395 36.688l-98.307 40.541 40.842 98.16c6.173 14.838 4.045 32.301-6.554 45.293-17.821 21.837-37.811 41.83-59.651 59.648-12.992 10.602-30.454 12.726-45.29 6.554l-98.163-40.838-40.538 98.304c-6.131 14.867-19.994 25.715-36.688 27.395-13.869 1.398-27.93 2.112-42.144 2.112zM444.451 757.984l43.386 105.2c7.981 0.541 16.038 0.816 24.163 0.816s16.182-0.275 24.163-0.816l43.382-105.2c9.456-22.925 35.731-33.808 58.627-24.285l105.056 43.709c12.157-10.602 23.578-22.022 34.179-34.179l-43.709-105.056c-9.526-22.893 1.36-49.171 24.282-58.624l105.203-43.386c0.541-7.978 0.816-16.038 0.816-24.163s-0.275-16.182-0.816-24.16l-105.203-43.386c-22.922-9.453-33.808-35.731-24.282-58.624l43.709-105.060c-10.602-12.156-22.022-23.577-34.176-34.177l-105.059 43.708c-22.896 9.525-49.171-1.359-58.627-24.283l-43.382-105.204c-7.981-0.54-16.038-0.815-24.163-0.815s-16.182 0.275-24.163 0.815l-43.386 105.204c-9.453 22.924-35.728 33.808-58.624 24.283l-105.058-43.708c-12.156 10.6-23.577 22.021-34.177 34.177l43.708 105.059c9.525 22.893-1.359 49.171-24.284 58.624l-105.203 43.386c-0.54 7.978-0.815 16.035-0.815 24.16 0 8.128 0.275 16.186 0.815 24.163l105.203 43.386c22.924 9.453 33.809 35.731 24.284 58.624l-43.708 105.056c10.601 12.157 22.022 23.578 34.178 34.179l105.056-43.709c22.896-9.523 49.171 1.36 58.624 24.285zM416 512c0-53.021 42.979-96 96-96s96 42.979 96 96c0 53.021-42.979 96-96 96s-96-42.979-96-96zM512 352c-88.365 0-160 71.635-160 160s71.635 160 160 160c88.365 0 160-71.635 160-160s-71.635-160-160-160z" + ], + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "isMulticolor": false, + "isMulticolor2": false, + "colorPermutations": { + "1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101": [ + { "f": 2 } + ] + }, + "tags": ["administration"], + "grid": 0 + }, + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "properties": { "order": 159, "id": 16, "name": "administration", "prevSize": 32, "code": 59657 }, + "setIdx": 0, + "setId": 4, + "iconIdx": 23 + }, + { + "icon": { + "paths": [ + "M878.739 567.622c-41.853-41.136-161.235-29.824-220.925-22.282-59.005-35.997-98.458-85.706-126.243-158.73 13.379-55.194 34.646-139.185 18.525-191.98-14.41-89.82-129.674-80.907-146.141-20.227-15.094 55.195-1.373 131.988 24.013 230.034-34.304 81.936-85.421 191.984-121.441 255.062-68.611 35.312-161.235 89.821-174.957 158.384-11.321 54.166 89.194 189.238 261.063-106.96 76.845-25.37 160.547-56.566 234.65-68.909 64.835 34.97 140.65 58.282 191.421 58.282 87.478 0 96.054-96.678 60.035-132.675zM199.152 834.342c17.496-46.97 84.048-101.133 104.288-119.99-65.18 103.875-104.288 122.39-104.288 119.99zM479.082 180.918c25.386 0 22.986 110.047 6.176 139.873-15.094-47.653-14.752-139.873-6.176-139.873zM395.379 649.216c33.274-57.936 61.747-126.845 84.733-187.526 28.474 51.766 64.838 93.251 103.258 121.706-71.354 14.739-133.446 44.909-187.99 65.821zM846.835 632.074c0 0-17.152 20.57-127.958-26.739 120.413-8.912 140.307 18.512 127.958 26.739z" + ], + "width": 1056, + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "isMulticolor": false, + "isMulticolor2": false, + "colorPermutations": { + "1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101": [ + { "f": 2 } + ] + }, + "tags": ["adobe-reader-monochromatic"], + "grid": 0 + }, + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "properties": { "order": 161, "id": 17, "name": "adobe-reader-monochromatic", "prevSize": 32, "code": 59658 }, + "setIdx": 0, + "setId": 4, + "iconIdx": 24 + }, + { + "icon": { + "paths": [ + "M448 188.865c0 33.615 27.251 60.865 60.864 60.865 33.616 0 60.867-27.25 60.867-60.865s-27.251-60.865-60.867-60.865c-33.613 0-60.864 27.25-60.864 60.865zM384 188.865c0 59.026 40.957 108.485 96 121.512v77.745c-44.314 11.69-78.986 47.13-89.578 91.878h-80.045c-13.027-55.043-62.486-96-121.512-96-68.961 0-124.865 55.904-124.865 124.864 0 68.963 55.904 124.867 124.865 124.867 56.762 0 104.678-37.875 119.854-89.731h83.361c12.227 41.773 45.696 74.47 87.92 85.61v77.744c-55.043 13.027-96 62.486-96 121.51 0 68.963 55.904 124.867 124.864 124.867 68.963 0 124.867-55.904 124.867-124.867 0-56.762-37.875-104.675-89.731-119.853v-79.437c42.163-11.171 75.578-43.846 87.789-85.574h77.222c15.178 51.856 63.091 89.731 119.853 89.731 68.963 0 124.867-55.904 124.867-124.867 0-68.96-55.904-124.864-124.867-124.864-59.024 0-108.483 40.957-121.51 96h-73.907c-10.579-44.707-45.194-80.122-89.446-91.843v-79.438c51.856-15.176 89.731-63.092 89.731-119.854 0-68.961-55.904-124.865-124.867-124.865-68.96 0-124.864 55.904-124.864 124.865zM828.864 569.731c-33.613 0-60.864-27.251-60.864-60.867 0-33.613 27.251-60.864 60.864-60.864 33.616 0 60.867 27.251 60.867 60.864 0 33.616-27.251 60.867-60.867 60.867zM188.865 569.731c-33.615 0-60.865-27.251-60.865-60.867 0-33.613 27.25-60.864 60.865-60.864s60.865 27.251 60.865 60.864c0 33.616-27.25 60.867-60.865 60.867zM451.069 508.864c0 33.616 27.251 60.867 60.867 60.867 33.613 0 60.864-27.251 60.864-60.867 0-33.613-27.251-60.864-60.864-60.864-33.616 0-60.867 27.251-60.867 60.864zM508.864 889.731c-33.613 0-60.864-27.251-60.864-60.867 0-33.613 27.251-60.864 60.864-60.864 33.616 0 60.867 27.251 60.867 60.864 0 33.616-27.251 60.867-60.867 60.867z" + ], + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "isMulticolor": false, + "isMulticolor2": false, + "colorPermutations": { + "1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101": [ + { "f": 2 } + ] + }, + "tags": ["all-contacts-in-channels"], + "grid": 0 + }, + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "properties": { "order": 162, "id": 18, "name": "all-contacts-in-channels", "prevSize": 32, "code": 59659 }, + "setIdx": 0, + "setId": 4, + "iconIdx": 25 + }, + { + "icon": { + "paths": [ + "M861.437 160c17.67 0 32 14.327 32 32s-14.33 32-32 32h-384c-17.674 0-32-14.327-32-32s14.326-32 32-32h384zM334.717 442.397c-24.962 0-45.197-20.237-45.197-45.2 0-24.96 20.236-45.197 45.197-45.197 24.963 0 45.2 20.237 45.2 45.197 0 24.963-20.237 45.2-45.2 45.2zM334.717 506.397c-60.308 0-109.197-48.89-109.197-109.2 0-60.307 48.89-109.197 109.197-109.197 60.31 0 109.2 48.89 109.2 109.197 0 60.31-48.89 109.2-109.2 109.2zM456.278 535.504c-18.288-4.899-37.504-5.2-55.939-0.88l-45.005 10.547c-13.194 3.091-26.947 2.877-40.036-0.63l-31.676-8.483c-19.669-5.27-40.384-5.562-60.154-0.928-55.68 13.050-95.468 62.781-95.468 120.179v34.752c0 42.906 34.783 77.686 77.689 77.686h258.057c42.906 0 77.69-34.781 77.69-77.686v-43.587c0-52-34.928-97.514-85.158-110.97zM414.944 596.934c8.166-1.914 16.675-1.779 24.774 0.39 22.246 5.958 37.718 26.118 37.718 49.149v43.587c0 7.558-6.131 13.686-13.69 13.686h-258.057c-7.56 0-13.689-6.128-13.689-13.686v-34.752c0-27.469 19.123-51.552 46.072-57.869 9.556-2.24 19.564-2.086 28.99 0.438l31.676 8.483c23.277 6.237 47.738 6.621 71.2 1.12l45.005-10.547zM893.437 512c0-17.674-14.33-32-32-32h-224c-17.674 0-32 14.326-32 32s14.326 32 32 32h224c17.67 0 32-14.326 32-32zM861.437 640c17.67 0 32 14.326 32 32s-14.33 32-32 32h-192c-17.674 0-32-14.326-32-32s14.326-32 32-32h192zM893.437 352c0-17.674-14.33-32-32-32h-288c-17.674 0-32 14.326-32 32s14.326 32 32 32h288c17.67 0 32-14.326 32-32zM861.437 800c17.67 0 32 14.326 32 32s-14.33 32-32 32h-256c-17.674 0-32-14.326-32-32s14.326-32 32-32h256z" + ], + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "isMulticolor": false, + "isMulticolor2": false, + "colorPermutations": { + "1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101": [ + { "f": 2 } + ] + }, + "tags": ["all-contacts-in-queue"], + "grid": 0 + }, + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "properties": { "order": 163, "id": 19, "name": "all-contacts-in-queue", "prevSize": 32, "code": 59660 }, + "setIdx": 0, + "setId": 4, + "iconIdx": 26 + }, + { + "icon": { + "paths": [ + "M523.571 295.385c33.133 0 74.669-22.133 99.402-51.645 22.4-26.745 38.733-64.095 38.733-101.445 0-5.072-0.467-10.144-1.402-14.294-36.864 1.383-81.2 24.439-107.798 55.334-21.002 23.517-40.134 60.406-40.134 98.218 0 5.533 0.934 11.067 1.402 12.911 2.333 0.461 6.067 0.922 9.798 0.922zM406.906 853.334c45.267 0 65.334-29.974 121.798-29.974 57.402 0 70 29.050 120.4 29.050 49.469 0 82.602-45.187 113.869-89.456 34.998-50.72 49.466-100.522 50.4-102.829-3.267-0.922-98-39.194-98-146.634 0-93.146 74.666-135.107 78.867-138.333-49.469-70.090-124.602-71.935-145.136-71.935-55.533 0-100.8 33.199-129.264 33.199-30.8 0-71.402-31.354-119.469-31.354-91.466 0-184.333 74.701-184.333 215.802 0 87.61 34.533 180.294 77 240.24 36.402 50.723 68.133 92.224 113.867 92.224z" + ], + "width": 1056, + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "isMulticolor": false, + "isMulticolor2": false, + "colorPermutations": { + "1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101": [ + { "f": 2 } + ] + }, + "tags": ["apple-monochromatic"], + "grid": 0 + }, + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "properties": { "order": 236, "id": 20, "name": "apple-monochromatic", "prevSize": 32, "code": 59662 }, + "setIdx": 0, + "setId": 4, + "iconIdx": 27 + }, + { + "icon": { + "paths": [ + "M498.694 141.561l-298.666 136.533c-11.39 5.207-18.696 16.58-18.696 29.103v386.844c0 11.818 6.513 22.675 16.941 28.237l298.667 159.286c9.411 5.021 20.707 5.021 30.118 0l298.666-159.286c10.429-5.562 16.941-16.419 16.941-28.237v-386.844c0-12.524-7.306-23.896-18.694-29.103l-298.666-136.533c-8.451-3.862-18.16-3.862-26.611 0zM245.333 357.011l234.667 107.277v335.709l-234.667-125.155v-317.83zM544 799.997l234.666-125.155v-317.83l-234.666 107.277v335.709zM512 408.544l-221.7-101.347 221.7-101.348 221.699 101.348-221.699 101.347z" + ], + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "isMulticolor": false, + "isMulticolor2": false, + "colorPermutations": { + "1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101": [ + { "f": 2 } + ] + }, + "tags": ["apps"], + "grid": 0 + }, + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "properties": { "order": 166, "id": 21, "name": "apps", "prevSize": 32, "code": 59663 }, + "setIdx": 0, + "setId": 4, + "iconIdx": 28 + }, + { + "icon": { + "paths": [ + "M374.627 297.372c-12.496-12.497-32.758-12.497-45.254 0l-192 192c-12.497 12.496-12.497 32.758 0 45.254l192 192c12.496 12.496 32.758 12.496 45.254 0s12.496-32.758 0-45.254l-137.372-137.373h578.745v128c0 17.674 14.326 32 32 32s32-14.326 32-32v-160c0-17.674-14.326-32-32-32h-610.745l137.372-137.373c12.496-12.496 12.496-32.758 0-45.255z" + ], + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "isMulticolor": false, + "isMulticolor2": false, + "colorPermutations": { + "1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101": [ + { "f": 2 } + ] + }, + "tags": ["arrow-back"], + "grid": 0 + }, + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "properties": { "order": 167, "id": 22, "name": "arrow-back", "prevSize": 32, "code": 59664 }, + "setIdx": 0, + "setId": 4, + "iconIdx": 29 + }, + { + "icon": { + "paths": [ + "M551.392 242.502c0.189-17.672 14.669-31.845 32.339-31.656 17.674 0.189 31.846 14.668 31.658 32.34l-1.318 123.489 193.44-193.439c12.496-12.497 32.755-12.497 45.254 0 12.496 12.497 12.496 32.758 0 45.255l-193.44 193.439 123.488-1.318c17.674-0.189 32.154 13.984 32.339 31.654 0.189 17.674-13.984 32.154-31.654 32.342l-201.92 2.154c-8.605 0.093-16.886-3.283-22.97-9.37-6.086-6.083-9.462-14.365-9.373-22.97l2.157-201.92zM475.981 782.147c-0.189 17.674-14.669 31.846-32.339 31.658-17.674-0.189-31.846-14.669-31.658-32.342l1.318-123.488-193.438 193.44c-12.497 12.496-32.758 12.496-45.255 0s-12.497-32.758 0-45.254l193.438-193.44-123.488 1.318c-17.672 0.189-32.151-13.984-32.34-31.654-0.189-17.674 13.984-32.154 31.656-32.342l201.922-2.154c8.605-0.093 16.883 3.283 22.966 9.37 6.086 6.083 9.462 14.365 9.373 22.97l-2.157 201.92z" + ], + "width": 1056, + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "isMulticolor": false, + "isMulticolor2": false, + "colorPermutations": { + "1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101": [ + { "f": 2 } + ] + }, + "tags": ["arrow-collapse"], + "grid": 0 + }, + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "properties": { "order": 168, "id": 23, "name": "arrow-collapse", "prevSize": 32, "code": 59665 }, + "setIdx": 0, + "setId": 4, + "iconIdx": 30 + }, + { + "icon": { + "paths": [ + "M576 672c-17.674 0-32 14.326-32 32s14.326 32 32 32h224c17.674 0 32-14.326 32-32v-208c0-17.674-14.326-32-32-32s-32 14.326-32 32v130.746l-233.373-233.373c-12.496-12.496-32.758-12.496-45.254 0l-73.373 73.373-169.372-169.373c-12.497-12.497-32.758-12.497-45.255 0s-12.497 32.759 0 45.255l192 192c12.496 12.496 32.758 12.496 45.254 0l73.373-73.373 210.746 210.746h-146.746z" + ], + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "isMulticolor": false, + "isMulticolor2": false, + "colorPermutations": { + "1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101": [ + { "f": 2 } + ] + }, + "tags": ["arrow-decrease"], + "grid": 0 + }, + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "properties": { "order": 169, "id": 24, "name": "arrow-decrease", "prevSize": 32, "code": 59666 }, + "setIdx": 0, + "setId": 4, + "iconIdx": 31 + }, + { + "icon": { + "paths": [ + "M329.373 550.627c-12.497-12.496-12.497-32.758 0-45.254s32.758-12.496 45.254 0l105.373 105.373v-418.746c0-17.673 14.326-32 32-32s32 14.327 32 32v418.746l105.373-105.373c12.496-12.496 32.758-12.496 45.254 0s12.496 32.758 0 45.254l-160 160c-12.496 12.496-32.758 12.496-45.254 0l-160-160zM112 864c0 17.674 14.327 32 32 32h768c17.674 0 32-14.326 32-32v-512c0-17.674-14.326-32-32-32h-96c-17.674 0-32 14.326-32 32s14.326 32 32 32h64v448h-704v-448h64c17.673 0 32-14.326 32-32s-14.327-32-32-32h-96c-17.673 0-32 14.326-32 32v512z" + ], + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "isMulticolor": false, + "isMulticolor2": false, + "colorPermutations": { + "1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101": [ + { "f": 2 } + ] + }, + "tags": ["arrow-down-box"], + "grid": 0 + }, + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "properties": { "order": 170, "id": 25, "name": "arrow-down-box", "prevSize": 32, "code": 59667 }, + "setIdx": 0, + "setId": 4, + "iconIdx": 32 + }, + { + "icon": { + "paths": [ + "M865.139 512c0-194.404-157.802-352-352.464-352-194.66 0-352.464 157.596-352.464 352s157.804 352 352.464 352c194.662 0 352.464-157.597 352.464-352zM929.226 512c0 229.75-186.496 416-416.55 416-230.053 0-416.548-186.25-416.548-416s186.495-416 416.548-416c230.054 0 416.55 186.25 416.55 416zM695.178 571.37l-160.182 155.715c-12.438 12.093-32.259 12.093-44.701 0l-160.179-155.715c-12.682-12.33-12.955-32.589-0.611-45.251 12.342-12.666 32.63-12.938 45.309-0.611l105.789 102.842 0.003-276.349c0-17.674 14.346-32 32.042-32s32.042 14.326 32.042 32v276.349l105.789-102.842c12.682-12.326 32.966-12.054 45.309 0.611 12.346 12.662 12.070 32.922-0.608 45.251z" + ], + "width": 1056, + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "isMulticolor": false, + "isMulticolor2": false, + "colorPermutations": { + "1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101": [ + { "f": 2 } + ] + }, + "tags": ["arrow-down-circle"], + "grid": 0 + }, + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "properties": { "order": 171, "id": 26, "name": "arrow-down-circle", "prevSize": 32, "code": 59668 }, + "setIdx": 0, + "setId": 4, + "iconIdx": 33 + }, + { + "icon": { + "paths": [ + "M726.88 526.064c12.355 12.637 12.128 32.896-0.509 45.254l-191.968 187.715c-12.438 12.163-32.31 12.163-44.746 0l-191.97-187.715c-12.636-12.358-12.863-32.618-0.507-45.254 12.356-12.634 32.615-12.861 45.252-0.506l137.597 134.55v-372.109c0-17.673 14.33-32 32-32 17.674 0 32 14.327 32 32v372.109l137.6-134.55c12.634-12.355 32.896-12.128 45.251 0.506z" + ], + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "isMulticolor": false, + "isMulticolor2": false, + "colorPermutations": { + "1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101": [ + { "f": 2 } + ] + }, + "tags": ["arrow-down"], + "grid": 0 + }, + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "properties": { "order": 172, "id": 27, "name": "arrow-down", "prevSize": 32, "code": 59669 }, + "setIdx": 0, + "setId": 4, + "iconIdx": 34 + }, + { + "icon": { + "paths": [ + "M859.978 398.15c-0.189 17.67-14.666 31.843-32.339 31.654-17.67-0.189-31.846-14.666-31.658-32.339l1.318-123.488-193.437 193.437c-12.496 12.499-32.758 12.499-45.254 0-12.496-12.496-12.496-32.755 0-45.254l193.437-193.437-123.488 1.318c-17.67 0.189-32.15-13.984-32.339-31.656s13.984-32.151 31.658-32.34l201.92-2.156c8.605-0.092 16.883 3.286 22.97 9.371 6.083 6.085 9.462 14.364 9.37 22.969l-2.157 201.922zM167.394 626.522c0.189-17.674 14.668-31.846 32.34-31.658s31.845 14.669 31.657 32.339l-1.319 123.488 193.438-193.437c12.496-12.499 32.758-12.499 45.254 0 12.496 12.496 12.496 32.758 0 45.254l-193.438 193.437 123.489-1.318c17.67-0.189 32.15 13.984 32.339 31.658 0.189 17.67-13.984 32.15-31.658 32.339l-201.92 2.157c-8.605 0.093-16.884-3.286-22.969-9.37-6.085-6.086-9.463-14.365-9.371-22.97l2.156-201.92z" + ], + "width": 1056, + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "isMulticolor": false, + "isMulticolor2": false, + "colorPermutations": { + "1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101": [ + { "f": 2 } + ] + }, + "tags": ["arrow-expand"], + "grid": 0 + }, + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "properties": { "order": 173, "id": 28, "name": "arrow-expand", "prevSize": 32, "code": 59670 }, + "setIdx": 0, + "setId": 4, + "iconIdx": 35 + }, + { + "icon": { + "paths": [ + "M576 352c-17.674 0-32-14.326-32-32s14.326-32 32-32h224c17.674 0 32 14.327 32 32v208c0 17.674-14.326 32-32 32s-32-14.326-32-32v-130.746l-233.373 233.373c-12.496 12.496-32.758 12.496-45.254 0l-73.373-73.373-169.372 169.373c-12.497 12.496-32.758 12.496-45.255 0s-12.497-32.758 0-45.254l192-192c12.496-12.496 32.758-12.496 45.254 0l73.373 73.373 210.746-210.746h-146.746z" + ], + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "isMulticolor": false, + "isMulticolor2": false, + "colorPermutations": { + "1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101": [ + { "f": 2 } + ] + }, + "tags": ["arrow-increase"], + "grid": 0 + }, + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "properties": { "order": 174, "id": 29, "name": "arrow-increase", "prevSize": 32, "code": 59671 }, + "setIdx": 0, + "setId": 4, + "iconIdx": 36 + }, + { + "icon": { + "paths": [ + "M297.766 105.372c12.513-12.497 32.801-12.497 45.316 0 12.512 12.497 12.512 32.758 0 45.255l-105.513 105.372h611.551c17.699 0 32.045 14.327 32.045 32v112c0 17.674-14.346 32-32.045 32-17.696 0-32.042-14.326-32.042-32v-80h-579.51l105.513 105.373c12.512 12.496 12.512 32.758 0 45.254-12.515 12.496-32.803 12.496-45.316 0l-160.212-160c-12.513-12.497-12.513-32.758 0-45.255l160.212-160zM711.568 918.627c-12.515 12.496-32.803 12.496-45.315 0-12.515-12.496-12.515-32.758 0-45.254l105.51-105.373h-611.552c-17.696 0-32.042-14.326-32.042-32v-112c0-17.674 14.346-32 32.042-32s32.042 14.326 32.042 32v80h579.509l-105.51-105.373c-12.515-12.496-12.515-32.758 0-45.254 12.512-12.496 32.8-12.496 45.315 0l160.211 160c12.512 12.496 12.512 32.758 0 45.254l-160.211 160z" + ], + "width": 1056, + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "isMulticolor": false, + "isMulticolor2": false, + "colorPermutations": { + "1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101": [ + { "f": 2 } + ] + }, + "tags": ["arrow-looping"], + "grid": 0 + }, + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "properties": { "order": 175, "id": 30, "name": "arrow-looping", "prevSize": 32, "code": 59672 }, + "setIdx": 0, + "setId": 4, + "iconIdx": 37 + }, + { + "icon": { + "paths": [ + "M374.627 769.296c-12.496 12.496-32.758 12.496-45.254 0l-192-192c-12.497-12.496-12.497-32.758 0-45.254l192-192c12.496-12.499 32.758-12.499 45.254 0 12.496 12.496 12.496 32.758 0 45.254l-137.372 137.373h578.745v-192h-192c-17.674 0-32-14.328-32-32.001s14.326-32 32-32h224c17.674 0 32 14.327 32 32v256.001c0 17.674-14.326 32-32 32h-610.745l137.372 137.373c12.496 12.496 12.496 32.758 0 45.254z" + ], + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "isMulticolor": false, + "isMulticolor2": false, + "colorPermutations": { + "1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101": [ + { "f": 2 } + ] + }, + "tags": ["arrow-return"], + "grid": 0 + }, + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "properties": { "order": 176, "id": 31, "name": "arrow-return", "prevSize": 32, "code": 59673 }, + "setIdx": 0, + "setId": 4, + "iconIdx": 38 + }, + { + "icon": { + "paths": [ + "M694.627 473.373c12.496 12.496 12.496 32.758 0 45.254s-32.758 12.496-45.254 0l-105.373-105.373v418.746c0 17.674-14.326 32-32 32s-32-14.326-32-32v-418.746l-105.373 105.373c-12.496 12.496-32.758 12.496-45.254 0s-12.497-32.758 0-45.254l160-160c12.496-12.497 32.758-12.497 45.254 0l160 160zM912 160c0-17.673-14.326-32-32-32h-768c-17.673 0-32 14.327-32 32v512c0 17.674 14.327 32 32 32h96c17.673 0 32-14.326 32-32s-14.327-32-32-32h-64v-448h704v448h-64c-17.674 0-32 14.326-32 32s14.326 32 32 32h96c17.674 0 32-14.326 32-32v-512z" + ], + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "isMulticolor": false, + "isMulticolor2": false, + "colorPermutations": { + "1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101": [ + { "f": 2 } + ] + }, + "tags": ["arrow-up-box"], + "grid": 0 + }, + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "properties": { "order": 177, "id": 32, "name": "arrow-up-box", "prevSize": 32, "code": 59674 }, + "setIdx": 0, + "setId": 4, + "iconIdx": 39 + }, + { + "icon": { + "paths": [ + "M297.181 498.090c-12.356-12.634-12.129-32.896 0.507-45.251l191.97-187.717c12.435-12.161 32.307-12.161 44.746 0l191.968 187.717c12.637 12.355 12.864 32.618 0.509 45.251-12.355 12.637-32.618 12.864-45.251 0.509l-137.6-134.55v372.109c0 17.674-14.326 32-32 32-17.67 0-32-14.326-32-32v-372.109l-137.597 134.55c-12.637 12.355-32.895 12.128-45.251-0.509z" + ], + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "isMulticolor": false, + "isMulticolor2": false, + "colorPermutations": { + "1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101": [ + { "f": 2 } + ] + }, + "tags": ["arrow-up"], + "grid": 0 + }, + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "properties": { "order": 178, "id": 33, "name": "arrow-up", "prevSize": 32, "code": 59675 }, + "setIdx": 0, + "setId": 4, + "iconIdx": 40 + }, + { + "icon": { + "paths": [ + "M866.128 153.127c-12.496-12.497-32.758-12.497-45.254 0l-668.246 668.245c-12.497 12.496-12.497 32.758 0 45.254s32.758 12.496 45.255 0l668.246-668.245c12.496-12.497 12.496-32.758 0-45.255zM674.643 480.358l55.92-55.923c16.259 79.565 14.515 161.866-5.229 240.842l-11.622 46.49c-4.288 17.146-21.661 27.571-38.806 23.283-17.146-4.285-27.568-21.661-23.283-38.806l11.622-46.486c13.875-55.507 17.677-112.874 11.398-169.398zM817.162 385.923l-11.312-36.771 51.203-51.206 21.28 69.155c33.344 108.368 31.994 224.445-3.859 332.010l-33.45 100.342c-5.587 16.765-23.709 25.827-40.477 20.24-16.765-5.59-25.827-23.712-20.237-40.48l33.446-100.339c31.635-94.912 32.826-197.334 3.405-292.95zM490.666 664.333l64-64v210.346c0 23.562-19.101 42.666-42.666 42.666h-85.334c-35.459 0-68.394-10.816-95.683-29.325l46.611-46.611c14.701 7.629 31.395 11.936 49.072 11.936h64v-125.011zM128 682.678c0 19.738 13.403 36.346 31.604 41.216l62.552-62.55h-30.156v-298.666h118.99l12.367-48.049c11.843-46.020 53.696-79.953 103.309-79.953h64v158.155l64-64v-115.488c0-23.564-19.101-42.667-42.666-42.667h-85.334c-79.523 0-146.343 54.39-165.289 128h-90.71c-23.564 0-42.667 19.102-42.667 42.667v341.334z" + ], + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "isMulticolor": false, + "isMulticolor2": false, + "colorPermutations": { + "1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101": [ + { "f": 2 } + ] + }, + "tags": ["audio-disabled"], + "grid": 0 + }, + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "properties": { "order": 180, "id": 35, "name": "audio-disabled", "prevSize": 32, "code": 59677 }, + "setIdx": 0, + "setId": 4, + "iconIdx": 41 + }, + { + "icon": { + "paths": [ + "M310.99 661.344h-118.99v-298.666h118.99l12.367-48.049c11.843-46.020 53.696-79.953 103.309-79.953h64v554.667h-64c-49.613 0-91.466-33.933-103.309-79.952l-12.367-48.048zM170.667 725.344h90.71c18.946 73.61 85.766 128 165.289 128h85.334c23.565 0 42.666-19.104 42.666-42.666v-597.335c0-23.564-19.101-42.667-42.666-42.667h-85.334c-79.523 0-146.343 54.39-165.289 128h-90.71c-23.564 0-42.667 19.102-42.667 42.667v341.334c0 23.562 19.103 42.666 42.667 42.666zM886.627 393.373c12.496 12.496 12.496 32.758 0 45.254l-73.373 73.373 73.373 73.373c12.496 12.496 12.496 32.758 0 45.254s-32.758 12.496-45.254 0l-73.373-73.373-73.373 73.373c-12.496 12.496-32.758 12.496-45.254 0s-12.496-32.758 0-45.254l73.373-73.373-73.373-73.373c-12.496-12.496-12.496-32.758 0-45.254s32.758-12.496 45.254 0l73.373 73.373 73.373-73.373c12.496-12.496 32.758-12.496 45.254 0z" + ], + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "isMulticolor": false, + "isMulticolor2": false, + "colorPermutations": { + "1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101": [ + { "f": 2 } + ] + }, + "tags": ["audio-unavailable"], + "grid": 0 + }, + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "properties": { "order": 181, "id": 36, "name": "audio-unavailable", "prevSize": 32, "code": 59678 }, + "setIdx": 0, + "setId": 4, + "iconIdx": 42 + }, + { + "icon": { + "paths": [ + "M300.1 631.12h-113.433v-280.89h113.433l12.366-48.048c11.054-42.953 50.123-74.619 96.423-74.619h58.666v526.223h-58.666c-46.301 0-85.37-31.667-96.423-74.621l-12.366-48.045zM163.556 695.12h86.931c18.156 70.541 82.192 122.666 158.403 122.666h81.776c22.582 0 40.89-18.307 40.89-40.89v-572.444c0-22.582-18.307-40.889-40.89-40.889h-81.776c-76.211 0-140.247 52.124-158.403 122.667h-86.931c-22.582 0-40.889 18.306-40.889 40.89v327.11c0 22.582 18.307 40.89 40.889 40.89zM646.461 316.515c17.146-4.286 34.518 6.138 38.806 23.284l11.136 44.55c20.81 83.229 20.81 170.301 0 253.533l-11.136 44.55c-4.288 17.146-21.661 27.568-38.806 23.283-17.146-4.288-27.571-21.661-23.283-38.806l11.136-44.55c18.262-73.040 18.262-149.45 0.003-222.486l-11.139-44.55c-4.288-17.146 6.138-34.522 23.283-38.807zM807.472 235.936c-5.197-16.892-23.104-26.372-39.994-21.174-16.893 5.197-26.371 23.104-21.174 39.996l35.536 115.489c28.112 91.37 26.976 189.242-3.254 279.933l-32.054 96.16c-5.59 16.765 3.472 34.886 20.237 40.477 16.768 5.587 34.89-3.472 40.48-20.24l32.051-96.16c34.448-103.344 35.747-214.87 3.709-318.989l-35.536-115.491z" + ], + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "isMulticolor": false, + "isMulticolor2": false, + "colorPermutations": { + "1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101": [ + { "f": 2 } + ] + }, + "tags": ["audio"], + "grid": 0 + }, + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "properties": { "order": 182, "id": 37, "name": "audio", "prevSize": 32, "code": 59679 }, + "setIdx": 0, + "setId": 4, + "iconIdx": 43 + }, + { + "icon": { + "paths": [ + "M418.704 128c-35.344 0-64 28.654-64 64v226.637c20.774-2.822 42.134-3.456 64-1.526v-225.11h176v180.707c0 35.344 28.656 64 64 64h176v331.293h-120.554c-17.155 22.179-36.323 43.869-57.312 64h177.866c35.347 0 64-28.653 64-64v-363.293c0-6.413-1.923-12.675-5.526-17.978l-156.63-230.681c-11.914-17.544-31.741-28.049-52.947-28.049h-264.896zM658.704 372.707v-180.707h24.896l122.698 180.707h-147.594zM167.939 668.547c120.524 156.138 218.24 174.586 285.133 158.739 73.293-17.36 139.232-81.648 183.466-151.763-115.155-155.869-211.581-174.589-279.149-158.915-74.362 17.251-142.707 81.683-189.45 151.939zM104.428 649.037c100.621-162.8 337.62-357.19 593.252 1.789 9.072 12.739 10.32 29.818 2.422 43.315-95.27 162.854-326.394 358.346-593.234-0.224-9.745-13.094-11.022-30.995-2.44-44.88zM401.664 735.994c31.514 0 60.269-26.858 60.269-64 0-37.146-28.755-64-60.269-64-31.51 0-60.266 26.854-60.266 64 0 37.142 28.755 64 60.266 64zM401.664 799.994c68.634 0 124.269-57.309 124.269-128 0-70.694-55.635-128-124.269-128-68.63 0-124.267 57.306-124.267 128 0 70.691 55.636 128 124.267 128z" + ], + "width": 1056, + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "isMulticolor": false, + "isMulticolor2": false, + "colorPermutations": { + "1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101": [ + { "f": 2 } + ] + }, + "tags": ["auditing"], + "grid": 0 + }, + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "properties": { "order": 183, "id": 38, "name": "auditing", "prevSize": 32, "code": 59680 }, + "setIdx": 0, + "setId": 4, + "iconIdx": 44 + }, + { + "icon": { + "paths": [ + "M224 224c-17.673 0-32 14.327-32 32v544c0 17.674 14.327 32 32 32h544c17.674 0 32-14.326 32-32v-544c0-17.673-14.326-32-32-32h-544zM128 256c0-53.019 42.981-96 96-96h544c53.021 0 96 42.981 96 96v544c0 53.021-42.979 96-96 96h-544c-53.019 0-96-42.979-96-96v-544zM608 460.813c0 41.798-26.714 77.357-64 90.538v133.462h-64v-133.462c-37.286-13.181-64-48.739-64-90.538 0-53.021 42.979-96 96-96s96 42.979 96 96zM608 588.826c38.861-29.19 64-75.667 64-128.013 0-88.365-71.635-160-160-160s-160 71.636-160 160c0 52.346 25.139 98.822 64 128.013v127.987c0 17.674 14.326 32 32 32h128c17.674 0 32-14.326 32-32v-127.987z" + ], + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "isMulticolor": false, + "isMulticolor2": false, + "colorPermutations": { + "1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101": [ + { "f": 2 } + ] + }, + "tags": ["auth"], + "grid": 0 + }, + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "properties": { "order": 184, "id": 39, "name": "auth", "prevSize": 32, "code": 59681 }, + "setIdx": 0, + "setId": 4, + "iconIdx": 45 + }, + { + "icon": { + "paths": [ + "M513.35 554.019c66.467 0 120.349-52.493 120.349-117.242 0-64.752-53.882-117.243-120.349-117.243-66.464 0-120.346 52.491-120.346 117.243 0 64.749 53.882 117.242 120.346 117.242zM513.35 490.019c-32.704 0-56.346-25.405-56.346-53.242 0-27.84 23.642-53.242 56.346-53.242 32.707 0 56.349 25.402 56.349 53.242 0 27.837-23.642 53.242-56.349 53.242z", + "M365.478 894.144h-172.126c-17.673 0-32-14.33-32-32v-702.144c0-17.673 14.327-32 32-32h640.001c17.674 0 32 14.327 32 32v702.144c0 17.67-14.326 32-32 32h-172.128c-6.24 1.274-12.701 1.942-19.318 1.942h-257.11c-6.618 0-13.078-0.669-19.318-1.942zM225.353 830.144h68.244c-3.114-9.456-4.799-19.558-4.799-30.058v-93.446c0-56.109 37.881-105.142 92.172-119.309 19.171-5.002 39.264-5.312 58.579-0.902l42.778 9.766c20.051 4.579 40.909 4.259 60.81-0.934l28.070-7.325c20.694-5.398 42.384-5.734 63.232-0.973 60.534 13.821 103.469 67.664 103.469 129.754v83.37c0 10.499-1.686 20.602-4.8 30.058h68.246v-638.144h-576.001v638.144zM652.918 830.144c12.246-4.49 20.989-16.253 20.989-30.058v-83.37c0-32.234-22.288-60.186-53.715-67.36-10.822-2.47-22.083-2.298-32.826 0.506l-28.070 7.325c-29.853 7.789-61.139 8.272-91.216 1.405l-42.774-9.766c-9.293-2.122-18.957-1.974-28.176 0.432-26.112 6.813-44.333 30.397-44.333 57.382v93.446c0 13.805 8.742 25.568 20.989 30.058h279.133z" + ], + "width": 1056, + "attrs": [{ "fill": "rgb(108, 114, 122)" }, { "fill": "rgb(108, 114, 122)" }], + "isMulticolor": false, + "isMulticolor2": false, + "colorPermutations": { + "1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101": [ + { "f": 2 }, + { "f": 2 } + ] + }, + "tags": ["avatar"], + "grid": 0 + }, + "attrs": [{ "fill": "rgb(108, 114, 122)" }, { "fill": "rgb(108, 114, 122)" }], + "properties": { "order": 185, "id": 40, "name": "avatar", "prevSize": 32, "code": 59682 }, + "setIdx": 0, + "setId": 4, + "iconIdx": 46 + }, + { + "icon": { + "paths": [ + "M737.779 361.376c12.499 12.496 12.499 32.758 0 45.254l-110.947 110.95 110.947 110.947c12.499 12.496 12.499 32.758 0 45.254-12.496 12.499-32.758 12.499-45.254 0l-110.947-110.947-110.95 110.947c-12.496 12.499-32.758 12.499-45.254 0-12.496-12.496-12.496-32.758 0-45.254l110.95-110.947-110.95-110.95c-12.496-12.496-12.496-32.758 0-45.254s32.758-12.496 45.254 0l110.95 110.95 110.947-110.95c12.496-12.496 32.758-12.496 45.254 0z", + "M312.246 218.073c12.061-16.393 31.2-26.073 51.552-26.073h468.202c35.347 0 64 28.654 64 64v512c0 35.347-28.653 64-64 64h-468.202c-20.352 0-39.491-9.68-51.552-26.074l-188.343-256c-16.598-22.56-16.598-53.293 0-75.853l188.343-256.001zM363.798 256l-188.343 256 188.343 256h468.202v-512h-468.202z" + ], + "attrs": [{ "fill": "rgb(108, 114, 122)" }, { "fill": "rgb(108, 114, 122)" }], + "isMulticolor": false, + "isMulticolor2": false, + "colorPermutations": { + "1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101": [ + { "f": 2 }, + { "f": 2 } + ] + }, + "tags": ["backspace"], + "grid": 0 + }, + "attrs": [{ "fill": "rgb(108, 114, 122)" }, { "fill": "rgb(108, 114, 122)" }], + "properties": { "order": 186, "id": 41, "name": "backspace", "prevSize": 32, "code": 59683 }, + "setIdx": 0, + "setId": 4, + "iconIdx": 47 + }, + { + "icon": { + "paths": [ + "M376 775.418c-13.254 0-24-10.746-24-24v-469.168c0-13.255 10.746-24 24-24h145.075c56.118 0 98.269 11.603 126.448 34.809 28.416 23.206 42.624 57.542 42.624 103.008 0 24.154-6.867 45.584-20.602 64.291-13.734 18.47-32.442 32.794-56.122 42.976 27.942 7.814 49.965 22.733 66.067 44.755 16.339 21.786 24.509 47.834 24.509 78.144 0 46.413-15.037 82.88-45.11 109.402s-72.579 39.782-127.517 39.782h-155.373zM420.198 533.526v186.125h112.595c31.731 0 56.714-8.17 74.947-24.509 18.47-16.576 27.706-39.309 27.706-68.198 0-62.278-33.862-93.418-101.587-93.418h-113.661zM420.198 478.829h103.008c29.834 0 53.632-7.459 71.392-22.378 17.997-14.918 26.995-35.165 26.995-60.739 0-28.416-8.288-49.018-24.864-61.805-16.576-13.024-41.795-19.536-75.654-19.536h-100.877v164.458z" + ], + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "isMulticolor": false, + "isMulticolor2": false, + "colorPermutations": { + "1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101": [ + { "f": 2 } + ] + }, + "tags": ["bold"], + "grid": 0 + }, + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "properties": { "order": 187, "id": 42, "name": "bold", "prevSize": 32, "code": 59684 }, + "setIdx": 0, + "setId": 4, + "iconIdx": 48 + }, + { + "icon": { + "paths": [ + "M352 445.437c0-17.67 14.326-32 32-32h256c17.674 0 32 14.33 32 32 0 17.674-14.326 32-32 32h-256c-17.674 0-32-14.326-32-32z", + "M352 322.557c0-17.672 14.326-31.999 32-31.999h256c17.674 0 32 14.327 32 31.999 0 17.674-14.326 32-32 32h-256c-17.674 0-32-14.326-32-32z", + "M864 679.68c0 17.674-14.326 32-32 32h-18.509c-8.218 40.547-8.218 82.333 0 122.88h19.789c16.966 0 30.72 13.754 30.72 30.72s-13.754 30.72-30.72 30.72h-545.28c-70.692 0-128-55.014-128-122.88v-453.12c0-106.038 85.961-192 192-192h480c17.674 0 32 14.327 32 32v519.68zM748.419 834.56c-6.787-40.678-6.787-82.202 0-122.88h-460.419c-35.346 0-64 27.507-64 61.44s28.654 61.44 64 61.44h460.419zM224 647.68h576v-455.68h-448c-70.692 0-128 57.308-128 128v327.68z" + ], + "attrs": [{ "fill": "rgb(108, 114, 122)" }, { "fill": "rgb(108, 114, 122)" }, { "fill": "rgb(108, 114, 122)" }], + "isMulticolor": false, + "isMulticolor2": false, + "colorPermutations": { + "1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101": [ + { "f": 2 }, + { "f": 2 }, + { "f": 2 } + ] + }, + "tags": ["book"], + "grid": 0 + }, + "attrs": [{ "fill": "rgb(108, 114, 122)" }, { "fill": "rgb(108, 114, 122)" }, { "fill": "rgb(108, 114, 122)" }], + "properties": { "order": 188, "id": 43, "name": "book", "prevSize": 32, "code": 59685 }, + "setIdx": 0, + "setId": 4, + "iconIdx": 49 + }, + { + "icon": { + "paths": [ + "M224 224h320v576h-96v-112c0-8.835-7.165-16-16-16h-96c-8.835 0-16 7.165-16 16v112h-96v-576zM608 448h192v352h-192v-352zM832 384h-224v-192c0-17.673-14.326-32-32-32h-384c-17.673 0-32 14.327-32 32v640c0 17.674 14.327 32 32 32h640c17.674 0 32-14.326 32-32v-416c0-17.674-14.326-32-32-32zM304 288c-8.836 0-16 7.164-16 16v32c0 8.835 7.164 16 16 16h32c8.835 0 16-7.165 16-16v-32c0-8.836-7.165-16-16-16h-32zM288 432c0-8.835 7.164-16 16-16h32c8.835 0 16 7.165 16 16v32c0 8.835-7.165 16-16 16h-32c-8.836 0-16-7.165-16-16v-32zM304 544c-8.836 0-16 7.165-16 16v32c0 8.835 7.164 16 16 16h32c8.835 0 16-7.165 16-16v-32c0-8.835-7.165-16-16-16h-32zM416 304c0-8.836 7.165-16 16-16h32c8.835 0 16 7.164 16 16v32c0 8.835-7.165 16-16 16h-32c-8.835 0-16-7.165-16-16v-32zM432 416c-8.835 0-16 7.165-16 16v32c0 8.835 7.165 16 16 16h32c8.835 0 16-7.165 16-16v-32c0-8.835-7.165-16-16-16h-32zM416 560c0-8.835 7.165-16 16-16h32c8.835 0 16 7.165 16 16v32c0 8.835-7.165 16-16 16h-32c-8.835 0-16-7.165-16-16v-32zM688 512c-8.835 0-16 7.165-16 16v32c0 8.835 7.165 16 16 16h32c8.835 0 16-7.165 16-16v-32c0-8.835-7.165-16-16-16h-32zM672 656c0-8.835 7.165-16 16-16h32c8.835 0 16 7.165 16 16v32c0 8.835-7.165 16-16 16h-32c-8.835 0-16-7.165-16-16v-32z" + ], + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "isMulticolor": false, + "isMulticolor2": false, + "colorPermutations": { + "1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101": [ + { "f": 2 } + ] + }, + "tags": ["business"], + "grid": 0 + }, + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "properties": { "order": 189, "id": 44, "name": "business", "prevSize": 32, "code": 59686 }, + "setIdx": 0, + "setId": 4, + "iconIdx": 50 + }, + { + "icon": { + "paths": [ + "M277.831 157.44c0-16.259 13.181-29.44 29.44-29.44s29.439 13.181 29.439 29.44v58.876h353.28v-58.876c0-16.259 13.181-29.44 29.44-29.44s29.44 13.181 29.44 29.44v58.876h85.76c17.674 0 32 14.327 32 32v583.68c0 17.674-14.326 32-32 32h-642.559c-17.673 0-32-14.326-32-32v-583.68c0-1.105 0.056-2.196 0.165-3.272 1.639-16.136 15.266-28.728 31.835-28.728h85.76v-58.876zM802.63 392.957h-578.559v407.040h578.559v-407.040zM425.034 644.474c0-8.835 7.162-16 16-16h26.88c8.835 0 16 7.165 16 16v26.88c0 8.838-7.165 16-16 16h-26.88c-8.838 0-16-7.162-16-16v-26.88zM323.27 510.72c-8.836 0-15.999 7.162-15.999 16v26.88c0 8.835 7.163 16 15.999 16h26.88c8.838 0 16-7.165 16-16v-26.88c0-8.838-7.162-16-16-16h-26.88zM542.79 526.72c0-8.835 7.165-16 16-16h26.88c8.838 0 16 7.165 16 16v26.88c0 8.835-7.162 16-16 16h-26.88c-8.835 0-16-7.165-16-16v-26.88zM323.27 628.474c-8.835 0-15.999 7.165-15.999 16v26.88c0 8.838 7.164 16 15.999 16h26.88c8.838 0 16-7.162 16-16v-26.88c0-8.835-7.162-16-16-16h-26.88zM542.79 644.474c0-8.835 7.165-16 16-16h26.88c8.838 0 16 7.165 16 16v26.88c0 8.838-7.162 16-16 16h-26.88c-8.835 0-16-7.162-16-16v-26.88zM441.030 510.72c-8.835 0-16 7.165-16 16v26.88c0 8.835 7.165 16 16 16h26.88c8.838 0 16-7.165 16-16v-26.88c0-8.835-7.162-16-16-16h-26.88zM660.55 526.72c0-8.835 7.165-16 16-16h26.88c8.838 0 16 7.165 16 16v26.88c0 8.835-7.162 16-16 16h-26.88c-8.835 0-16-7.165-16-16v-26.88zM676.55 628.474c-8.835 0-16 7.165-16 16v26.88c0 8.838 7.165 16 16 16h26.88c8.838 0 16-7.162 16-16v-26.88c0-8.835-7.162-16-16-16h-26.88z" + ], + "width": 1056, + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "isMulticolor": false, + "isMulticolor2": false, + "colorPermutations": { + "1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101": [ + { "f": 2 } + ] + }, + "tags": ["calendar"], + "grid": 0 + }, + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "properties": { "order": 190, "id": 45, "name": "calendar", "prevSize": 32, "code": 59687 }, + "setIdx": 0, + "setId": 4, + "iconIdx": 51 + }, + { + "icon": { + "paths": [ + "M866.128 153.126c-12.496-12.497-32.758-12.497-45.254 0l-668.246 668.246c-12.497 12.496-12.497 32.758 0 45.254s32.758 12.496 45.255 0l668.246-668.246c12.496-12.497 12.496-32.758 0-45.255zM298.667 170.671c-23.564 0-42.667 14.327-42.667 32s19.102 32 42.667 32h255.999c23.565 0 42.669-14.327 42.669-32s-19.104-32-42.669-32h-255.999zM574.173 309.327h-403.506c-41.237 0-74.667 33.428-74.667 74.667v384c0 6.010 0.71 11.856 2.051 17.453l61.949-61.949v-339.504c0-5.891 4.776-10.666 10.667-10.666h339.506l64-64.001zM376.339 778.659h306.326c5.891 0 10.669-4.774 10.669-10.666v-85.334c0-11.373 6.038-21.891 15.859-27.629s21.949-5.83 31.856-0.243l97.83 55.162c2.531 1.427 4.858 3.194 6.912 5.248 6.72 6.72 18.208 1.962 18.208-7.542v-263.322c0-9.501-11.488-14.262-18.208-7.542-2.054 2.054-4.381 3.821-6.912 5.248l-97.83 55.162c-9.907 5.587-22.035 5.494-31.856-0.243-9.821-5.734-15.859-16.256-15.859-27.629v-7.661l110.778-73.498c47.418-41.99 123.888-8.701 123.888 56.163v263.322c0 64.864-76.47 98.154-123.888 56.163l-46.778-26.378v30.554c0 41.238-33.43 74.666-74.669 74.666h-370.327l64.001-64z" + ], + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "isMulticolor": false, + "isMulticolor2": false, + "colorPermutations": { + "1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101": [ + { "f": 2 } + ] + }, + "tags": ["camera-disabled"], + "grid": 0 + }, + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "properties": { "order": 191, "id": 46, "name": "camera-disabled", "prevSize": 32, "code": 59688 }, + "setIdx": 0, + "setId": 4, + "iconIdx": 52 + }, + { + "icon": { + "paths": [ + "M288 224c-17.673 0-32 14.327-32 32s14.327 32 32 32h256c17.674 0 32-14.327 32-32s-14.326-32-32-32h-256z", + "M170.667 341.328c-23.564 0-42.667 19.104-42.667 42.666v384c0 23.565 19.102 42.669 42.667 42.669h511.999c23.565 0 42.669-19.104 42.669-42.669v-128l97.83 97.83c26.877 26.88 72.835 7.843 72.835-30.17v-263.318c0-38.013-45.958-57.050-72.835-30.173l-97.83 97.83v-128c0-23.562-19.104-42.666-42.669-42.666h-511.999z" + ], + "attrs": [{ "fill": "rgb(108, 114, 122)" }, { "fill": "rgb(108, 114, 122)" }], + "isMulticolor": false, + "isMulticolor2": false, + "colorPermutations": { + "1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101": [ + { "f": 2 }, + { "f": 2 } + ] + }, + "tags": ["camera-filled"], + "grid": 0 + }, + "attrs": [{ "fill": "rgb(108, 114, 122)" }, { "fill": "rgb(108, 114, 122)" }], + "properties": { "order": 192, "id": 47, "name": "camera-filled", "prevSize": 32, "code": 59689 }, + "setIdx": 0, + "setId": 4, + "iconIdx": 53 + }, + { + "icon": { + "paths": [ + "M196.731 191.997c0-15.807 12.814-28.622 28.622-28.622h128.001c15.805 0 28.621 12.814 28.621 28.622s-12.816 28.621-28.621 28.621h-128.001c-15.807 0-28.622-12.814-28.622-28.621z", + "M737.354 559.997c0 97.203-78.8 176-176 176-97.203 0-176-78.797-176-176s78.797-176 176-176c97.2 0 176 78.797 176 176zM673.354 559.997c0-61.856-50.144-112-112-112s-112 50.144-112 112c0 61.856 50.144 112 112 112s112-50.144 112-112z", + "M193.353 255.997c-35.346 0-64 28.654-64 64v448c0 35.347 28.654 64 64 64h640.001c35.344 0 64-28.653 64-64v-448c0-35.346-28.656-64-64-64h-640.001zM833.354 319.997v448h-640.001v-448h640.001z" + ], + "width": 1056, + "attrs": [{ "fill": "rgb(108, 114, 122)" }, { "fill": "rgb(108, 114, 122)" }, { "fill": "rgb(108, 114, 122)" }], + "isMulticolor": false, + "isMulticolor2": false, + "colorPermutations": { + "1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101": [ + { "f": 2 }, + { "f": 2 }, + { "f": 2 } + ] + }, + "tags": ["camera-photo"], + "grid": 0 + }, + "attrs": [{ "fill": "rgb(108, 114, 122)" }, { "fill": "rgb(108, 114, 122)" }, { "fill": "rgb(108, 114, 122)" }], + "properties": { "order": 193, "id": 48, "name": "camera-photo", "prevSize": 32, "code": 59690 }, + "setIdx": 0, + "setId": 4, + "iconIdx": 54 + }, + { + "icon": { + "paths": [ + "M298.667 170.672c-23.564 0-42.667 14.327-42.667 32s19.102 32 42.667 32h255.999c23.565 0 42.669-14.327 42.669-32s-19.104-32-42.669-32h-255.999z", + "M545.712 503.453c11.267-11.267 11.19-29.61-0.17-40.97s-29.706-11.437-40.973-0.17l-71.402 71.402-71.994-71.997c-11.363-11.36-29.706-11.437-40.973-0.17s-11.19 29.61 0.17 40.97l71.997 71.997-71.402 71.402c-11.266 11.267-11.19 29.61 0.17 40.97 11.363 11.36 29.706 11.437 40.973 0.17l71.402-71.402 71.997 71.997c11.36 11.36 29.702 11.437 40.97 0.17s11.19-29.61-0.17-40.97l-71.997-71.997 71.402-71.402z", + "M96 383.994c0-41.235 33.429-74.666 74.667-74.666h511.999c41.238 0 74.669 33.43 74.669 74.666v30.554l46.778-26.378c47.418-41.99 123.888-8.698 123.888 56.166v263.318c0 64.864-76.47 98.154-123.888 56.163l-46.778-26.374v30.55c0 41.238-33.43 74.669-74.669 74.669h-511.999c-41.237 0-74.667-33.43-74.667-74.669v-384zM170.667 373.328c-5.891 0-10.667 4.774-10.667 10.666v384c0 5.891 4.776 10.669 10.667 10.669h511.999c5.891 0 10.669-4.778 10.669-10.669v-85.331c0-11.376 6.038-21.894 15.859-27.632s21.949-5.83 31.856-0.243l97.83 55.165c2.531 1.427 4.858 3.19 6.912 5.245 6.72 6.72 18.208 1.962 18.208-7.542v-263.318c0-9.504-11.488-14.262-18.208-7.546-2.054 2.058-4.381 3.821-6.912 5.248l-97.83 55.165c-9.907 5.587-22.035 5.494-31.856-0.243s-15.859-16.256-15.859-27.632v-85.334c0-5.891-4.778-10.666-10.669-10.666h-511.999z" + ], + "attrs": [{ "fill": "rgb(108, 114, 122)" }, { "fill": "rgb(108, 114, 122)" }, { "fill": "rgb(108, 114, 122)" }], + "isMulticolor": false, + "isMulticolor2": false, + "colorPermutations": { + "1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101": [ + { "f": 2 }, + { "f": 2 }, + { "f": 2 } + ] + }, + "tags": ["camera-unavailable"], + "grid": 0 + }, + "attrs": [{ "fill": "rgb(108, 114, 122)" }, { "fill": "rgb(108, 114, 122)" }, { "fill": "rgb(108, 114, 122)" }], + "properties": { "order": 194, "id": 49, "name": "camera-unavailable", "prevSize": 32, "code": 59691 }, + "setIdx": 0, + "setId": 4, + "iconIdx": 55 + }, + { + "icon": { + "paths": [ + "M256 202.672c0-17.673 19.102-32 42.667-32h255.999c23.565 0 42.669 14.327 42.669 32s-19.104 32-42.669 32h-255.999c-23.564 0-42.667-14.327-42.667-32zM160 383.994c0-5.891 4.776-10.666 10.667-10.666h511.999c5.891 0 10.669 4.774 10.669 10.666v85.334c0 11.376 6.038 21.894 15.859 27.632s21.949 5.83 31.856 0.243l97.83-55.165c2.531-1.427 4.858-3.19 6.912-5.248 6.72-6.717 18.208-1.958 18.208 7.546v263.318c0 9.504-11.488 14.262-18.208 7.542-2.054-2.054-4.381-3.818-6.912-5.245l-97.83-55.165c-9.907-5.587-22.035-5.494-31.856 0.243s-15.859 16.256-15.859 27.632v85.331c0 5.891-4.778 10.669-10.669 10.669h-511.999c-5.891 0-10.667-4.778-10.667-10.669v-384zM170.667 309.328c-41.237 0-74.667 33.43-74.667 74.666v384c0 41.238 33.429 74.669 74.667 74.669h511.999c41.238 0 74.669-33.43 74.669-74.669v-30.55l46.778 26.374c47.418 41.99 123.888 8.701 123.888-56.163v-263.318c0-64.864-76.47-98.157-123.888-56.166l-46.778 26.378v-30.554c0-41.235-33.43-74.666-74.669-74.666h-511.999z" + ], + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "isMulticolor": false, + "isMulticolor2": false, + "colorPermutations": { + "1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101": [ + { "f": 2 } + ] + }, + "tags": ["camera"], + "grid": 0 + }, + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "properties": { "order": 195, "id": 50, "name": "camera", "prevSize": 32, "code": 59692 }, + "setIdx": 0, + "setId": 4, + "iconIdx": 56 + }, + { + "icon": { + "paths": [ + "M224.248 304.026c-49.642 68.947-66.502 152.646-66.502 207.968v1.453l-0.132 1.446c-9.702 106.717 28.59 181.93 90.135 234.006 62.938 53.254 152.152 83.731 244.197 93.958 91.965 10.218 193.27 1.446 273.306-15.482 40.022-8.461 73.616-18.733 97.443-29.075 9.245-4.013 16.438-7.786 21.734-11.126-2.464-1.565-5.446-3.306-9.002-5.197-9.222-4.912-19.779-9.578-30.733-14.368l-1.914-0.835c-9.667-4.221-20.47-8.941-28.656-13.517-14.835-8.298-29.389-17.734-40.006-27.776-5.222-4.941-11.165-11.571-15.146-19.827-4.208-8.742-7.226-21.792-1.306-35.597 31.978-74.621 47.178-115.494 54.538-142.483 6.874-25.2 6.874-37.888 6.874-58.064v-0.182c0-16.035-9.318-89.517-55.811-158.032-45.018-66.338-125.99-129.968-274.854-129.968-134.637 0-215.698 55.382-264.165 122.698zM172.309 266.63c60.333-83.796 160.606-149.302 316.103-149.302 171.136 0 271.494 75.037 327.811 158.032 54.842 80.819 66.854 167.338 66.854 193.968 0 22.384-0.022 41.696-9.126 75.088-8.131 29.805-23.485 70.928-51.946 137.923 5.261 4.166 13.072 9.306 23.36 15.062 5.315 2.97 13.485 6.55 24.963 11.568 10.653 4.659 23.437 10.266 35.174 16.515 11.258 5.994 24.416 14.029 34.202 24.538 10.234 10.992 20.973 29.846 13.299 52.864-5.056 15.168-16.794 25.939-26.576 33.094-10.63 7.776-23.818 14.762-38.253 21.030-29.008 12.589-67.030 23.962-109.683 32.982-85.309 18.038-193.581 27.587-293.616 16.474-99.955-11.107-202.74-44.634-278.468-108.71-76.833-65.011-123.811-159.994-112.66-287.222 0.286-65.549 19.841-162.349 78.561-243.904zM493.744 320c17.674 0 32 14.326 32 32v224c0 17.674-14.326 32-32 32s-32-14.326-32-32v-224c0-17.674 14.326-32 32-32zM525.744 672c0-17.674-14.326-32-32-32s-32 14.326-32 32c0 17.674 14.326 32 32 32s32-14.326 32-32z" + ], + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "isMulticolor": false, + "isMulticolor2": false, + "colorPermutations": { + "1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101": [ + { "f": 2 } + ] + }, + "tags": ["canned-response"], + "grid": 0 + }, + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "properties": { "order": 196, "id": 51, "name": "canned-response", "prevSize": 32, "code": 59693 }, + "setIdx": 0, + "setId": 4, + "iconIdx": 57 + }, + { + "icon": { + "paths": [ + "M193.352 181.331c-53.020 0-96 42.981-96 96v469.335c0 53.018 42.98 96 96 96h639.999c53.021 0 96-42.982 96-96v-469.335c0-53.019-42.979-96-96-96h-639.999zM161.352 277.331c0-17.673 14.327-32 32-32h639.999c17.674 0 32 14.327 32 32v202.673h-703.999v-202.673zM161.352 544.003h703.999v202.662c0 17.67-14.326 32-32 32h-639.999c-17.673 0-32-14.33-32-32v-202.662zM812.016 661.331c0-35.347-28.653-64-64-64-35.344 0-64 28.653-64 64s28.656 64 64 64c35.347 0 64-28.653 64-64z" + ], + "width": 1056, + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "isMulticolor": false, + "isMulticolor2": false, + "colorPermutations": { + "1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101": [ + { "f": 2 } + ] + }, + "tags": ["card"], + "grid": 0 + }, + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "properties": { "order": 197, "id": 52, "name": "card", "prevSize": 32, "code": 59694 }, + "setIdx": 0, + "setId": 4, + "iconIdx": 58 + }, + { + "icon": { + "paths": [ + "M368 160c0-17.673-14.326-32-32-32s-32 14.327-32 32v144h-144c-17.673 0-32 14.327-32 32s14.327 32 32 32h144v288h-144c-17.673 0-32 14.326-32 32s14.327 32 32 32h144v144c0 17.674 14.327 32 32 32s32-14.326 32-32v-144h288v144c0 17.674 14.326 32 32 32s32-14.326 32-32v-144h144c17.674 0 32-14.326 32-32s-14.326-32-32-32h-144v-80h-58.666c-1.786 0-3.565-0.042-5.334-0.118v80.118h-288v-288h168.79c-0.522-5.264-0.79-10.602-0.79-16v-48h-168v-144z", + "M760 96c-57.437 0-104 46.562-104 104v56h-24c-17.674 0-32 14.327-32 32v192c0 17.674 14.326 32 32 32h256c17.674 0 32-14.326 32-32v-192c0-17.673-14.326-32-32-32h-24v-56c0-57.438-46.563-104-104-104zM800 255.238h-80v-55.238c0-22.092 17.907-40 40-40s40 17.908 40 40v55.238z" + ], + "width": 1056, + "attrs": [{ "fill": "rgb(108, 114, 122)" }, { "fill": "rgb(108, 114, 122)" }], + "isMulticolor": false, + "isMulticolor2": false, + "colorPermutations": { + "1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101": [ + { "f": 2 }, + { "f": 2 } + ] + }, + "tags": ["channel-private"], + "grid": 0 + }, + "attrs": [{ "fill": "rgb(108, 114, 122)" }, { "fill": "rgb(108, 114, 122)" }], + "properties": { "order": 198, "id": 53, "name": "channel-private", "prevSize": 32, "code": 59695 }, + "setIdx": 0, + "setId": 4, + "iconIdx": 59 + }, + { + "icon": { + "paths": [ + "M336 128c17.674 0 32 14.327 32 32v144h288v-144c0-17.673 14.326-32 32-32s32 14.327 32 32v144h144c17.674 0 32 14.327 32 32s-14.326 32-32 32h-144v288h144c17.674 0 32 14.326 32 32s-14.326 32-32 32h-144v144c0 17.674-14.326 32-32 32s-32-14.326-32-32v-144h-288v144c0 17.674-14.326 32-32 32s-32-14.326-32-32v-144h-144c-17.673 0-32-14.326-32-32s14.327-32 32-32h144v-288h-144c-17.673 0-32-14.326-32-32s14.327-32 32-32h144v-144c0-17.673 14.327-32 32-32zM368 368v288h288v-288h-288z" + ], + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "isMulticolor": false, + "isMulticolor2": false, + "colorPermutations": { + "1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101": [ + { "f": 2 } + ] + }, + "tags": ["channel-public"], + "grid": 0 + }, + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "properties": { "order": 199, "id": 54, "name": "channel-public", "prevSize": 32, "code": 59696 }, + "setIdx": 0, + "setId": 4, + "iconIdx": 60 + }, + { + "icon": { + "paths": [ + "M886.627 105.372c12.496 12.497 12.496 32.758 0 45.255l-89.373 89.372 89.373 89.373c12.496 12.496 12.496 32.758 0 45.254s-32.758 12.496-45.254 0l-89.373-89.372-89.373 89.372c-12.496 12.496-32.758 12.496-45.254 0s-12.496-32.758 0-45.254l89.373-89.373-89.373-89.372c-12.496-12.497-12.496-32.758 0-45.255s32.758-12.497 45.254 0l89.373 89.373 89.373-89.373c12.496-12.497 32.758-12.497 45.254 0zM226.501 304.042c-49.642 68.947-66.502 152.646-66.502 207.968v1.453l-0.132 1.446c-9.701 106.717 28.59 181.93 90.135 234.006 62.938 53.254 152.151 83.731 244.196 93.958 91.968 10.218 193.27 1.446 273.309-15.482 40.019-8.461 73.613-18.733 97.44-29.075 9.245-4.013 16.438-7.786 21.738-11.126-2.467-1.565-5.446-3.306-9.005-5.2-9.222-4.909-19.776-9.574-30.733-14.365l-1.917-0.838c-9.667-4.221-20.47-8.938-28.653-13.517-14.835-8.294-29.389-17.731-40.003-27.773-5.226-4.941-11.168-11.571-15.146-19.827-4.211-8.742-7.226-21.792-1.309-35.6 42.086-98.205 54.957-137.722 59.219-163.318 2.906-17.434 19.392-29.21 36.822-26.307 17.434 2.906 29.213 19.389 26.307 36.822-5.453 32.749-20.333 76.41-58.006 165.088 5.261 4.166 13.069 9.306 23.357 15.059 5.315 2.973 13.485 6.554 24.963 11.571 10.656 4.656 23.437 10.266 35.174 16.515 11.261 5.994 24.419 14.029 34.202 24.538 10.234 10.992 20.973 29.846 13.299 52.864-5.053 15.168-16.794 25.939-26.576 33.094-10.627 7.776-23.814 14.762-38.253 21.027-29.005 12.592-67.027 23.965-109.68 32.986-85.312 18.038-193.584 27.587-293.616 16.47-99.955-11.104-202.742-44.63-278.471-108.707-76.833-65.014-123.811-159.997-112.66-287.222 0.286-65.549 19.841-162.349 78.561-243.904 60.333-83.796 160.605-149.302 316.103-149.302 17.674 0 32 14.327 32 32s-14.326 32-32 32c-134.637 0-215.697 55.382-264.164 122.698z" + ], + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "isMulticolor": false, + "isMulticolor2": false, + "colorPermutations": { + "1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101": [ + { "f": 2 } + ] + }, + "tags": ["chat-close"], + "grid": 0 + }, + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "properties": { "order": 200, "id": 55, "name": "chat-close", "prevSize": 32, "code": 59697 }, + "setIdx": 0, + "setId": 4, + "iconIdx": 61 + }, + { + "icon": { + "paths": [ + "M778 93.833c-11.795-13.162-32.026-14.271-45.187-2.477s-14.272 32.025-2.477 45.187l81.222 90.645h-216.358c-17.674 0-32 14.327-32 32s14.326 32 32 32h216.358l-81.222 90.646c-11.795 13.162-10.685 33.392 2.477 45.187 13.162 11.792 33.392 10.685 45.187-2.477l129.030-144.001c10.893-12.154 10.893-30.556 0-42.71l-129.030-144zM159.999 512.010c0-55.322 16.86-139.021 66.502-207.968 48.467-67.316 129.527-122.698 264.164-122.698 17.674 0 32-14.327 32-32s-14.326-32-32-32c-155.498 0-255.77 65.507-316.102 149.302-58.72 81.555-78.275 178.355-78.561 243.904-11.151 127.229 35.827 222.211 112.66 287.222 75.729 64.077 178.516 97.603 278.471 108.707 100.032 11.117 208.304 1.568 293.616-16.47 42.653-9.021 80.675-20.394 109.68-32.982 14.438-6.269 27.626-13.254 38.253-21.030 9.782-7.155 21.523-17.926 26.579-33.094 7.67-23.018-3.069-41.872-13.302-52.864-9.782-10.509-22.941-18.544-34.202-24.538-11.738-6.25-24.518-11.859-35.171-16.515-11.482-5.018-19.651-8.598-24.966-11.571-10.288-5.754-18.096-10.89-23.357-15.059 37.674-88.678 52.554-132.339 58.010-165.088 2.902-17.43-8.877-33.917-26.31-36.822-17.43-2.902-33.917 8.877-36.822 26.307-4.262 25.597-17.133 65.114-59.219 163.318-5.917 13.808-2.902 26.858 1.309 35.6 3.978 8.256 9.92 14.886 15.146 19.827 10.614 10.042 25.171 19.478 40.003 27.776 8.186 4.576 18.989 9.296 28.656 13.517v0l1.914 0.835c10.957 4.79 21.51 9.456 30.733 14.368 3.558 1.891 6.541 3.632 9.005 5.197-5.299 3.341-12.493 7.114-21.738 11.126-23.827 10.342-57.421 20.614-97.44 29.075-80.038 16.928-181.341 25.699-273.309 15.482-92.045-10.227-181.258-40.704-244.196-93.958-61.545-52.077-99.836-127.29-90.135-234.006l0.132-1.446v-1.453z" + ], + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "isMulticolor": false, + "isMulticolor2": false, + "colorPermutations": { + "1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101": [ + { "f": 2 } + ] + }, + "tags": ["chat-forward"], + "grid": 0 + }, + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "properties": { "order": 201, "id": 56, "name": "chat-forward", "prevSize": 32, "code": 59698 }, + "setIdx": 0, + "setId": 4, + "iconIdx": 62 + }, + { + "icon": { + "paths": [ + "M854.506 233.252c12.563 12.429 12.672 32.691 0.243 45.254l-474.877 480c-6.013 6.077-14.208 9.498-22.755 9.494-8.55-0.003-16.742-3.427-22.752-9.507l-165.126-167.088c-12.423-12.57-12.303-32.832 0.268-45.254s32.831-12.304 45.254 0.269l142.375 144.067 452.115-456.992c12.429-12.564 32.691-12.672 45.254-0.243z" + ], + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "isMulticolor": false, + "isMulticolor2": false, + "colorPermutations": { + "1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101": [ + { "f": 2 } + ] + }, + "tags": ["check"], + "grid": 0 + }, + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "properties": { "order": 202, "id": 57, "name": "check", "prevSize": 32, "code": 59699 }, + "setIdx": 0, + "setId": 4, + "iconIdx": 63 + }, + { + "icon": { + "paths": [ + "M281.372 436.042c12.497-12.499 32.758-12.499 45.255 0l185.373 185.373 185.373-185.373c12.496-12.499 32.758-12.499 45.254 0 12.496 12.496 12.496 32.758 0 45.254l-208 208c-12.496 12.496-32.758 12.496-45.254 0l-208-208c-12.497-12.496-12.497-32.758 0-45.254z" + ], + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "isMulticolor": false, + "isMulticolor2": false, + "colorPermutations": { + "1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101": [ + { "f": 2 } + ] + }, + "tags": ["chevron-down"], + "grid": 0 + }, + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "properties": { "order": 203, "id": 58, "name": "chevron-down", "prevSize": 32, "code": 59700 }, + "setIdx": 0, + "setId": 4, + "iconIdx": 64 + }, + { + "icon": { + "paths": [ + "M670.17 183.165c16.662 16.662 16.662 43.677 0 60.34l-268.496 268.495 268.496 268.499c16.662 16.662 16.662 43.677 0 60.339s-43.677 16.662-60.339 0l-298.668-298.669c-8.002-8-12.497-18.851-12.497-30.17 0-11.315 4.495-22.166 12.497-30.17l298.668-298.666c16.662-16.662 43.677-16.662 60.339 0z" + ], + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "isMulticolor": false, + "isMulticolor2": false, + "colorPermutations": { + "1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101": [ + { "f": 2 } + ] + }, + "tags": ["chevron-left-big"], + "grid": 0 + }, + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "properties": { "order": 204, "id": 59, "name": "chevron-left-big", "prevSize": 32, "code": 59701 }, + "setIdx": 0, + "setId": 4, + "iconIdx": 65 + }, + { + "icon": { + "paths": [ + "M587.962 281.372c12.496 12.497 12.496 32.758 0 45.255l-185.373 185.373 185.373 185.373c12.496 12.496 12.496 32.758 0 45.254-12.499 12.496-32.758 12.496-45.258 0l-208-208c-12.496-12.496-12.496-32.758 0-45.254l208-208c12.499-12.497 32.758-12.497 45.258 0z" + ], + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "isMulticolor": false, + "isMulticolor2": false, + "colorPermutations": { + "1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101": [ + { "f": 2 } + ] + }, + "tags": ["chevron-left"], + "grid": 0 + }, + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "properties": { "order": 205, "id": 60, "name": "chevron-left", "prevSize": 32, "code": 59702 }, + "setIdx": 0, + "setId": 4, + "iconIdx": 66 + }, + { + "icon": { + "paths": [ + "M436.038 742.627c-12.496-12.496-12.496-32.758 0-45.254l185.373-185.373-185.373-185.373c-12.496-12.497-12.496-32.758 0-45.254 12.499-12.497 32.758-12.497 45.254 0l208 208c12.499 12.496 12.499 32.758 0 45.254l-207.997 208c-12.499 12.496-32.758 12.496-45.258 0z" + ], + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "isMulticolor": false, + "isMulticolor2": false, + "colorPermutations": { + "1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101": [ + { "f": 2 } + ] + }, + "tags": ["chevron-right"], + "grid": 0 + }, + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "properties": { "order": 206, "id": 61, "name": "chevron-right", "prevSize": 32, "code": 59703 }, + "setIdx": 0, + "setId": 4, + "iconIdx": 67 + }, + { + "icon": { + "paths": [ + "M742.627 587.958c-12.496 12.499-32.758 12.499-45.254 0l-185.373-185.373-185.373 185.373c-12.496 12.499-32.758 12.499-45.254 0-12.497-12.496-12.497-32.758 0-45.254l208-208c12.496-12.496 32.758-12.496 45.254 0l208 208c12.496 12.496 12.496 32.758 0 45.254z" + ], + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "isMulticolor": false, + "isMulticolor2": false, + "colorPermutations": { + "1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101": [ + { "f": 2 } + ] + }, + "tags": ["chevron-up"], + "grid": 0 + }, + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "properties": { "order": 207, "id": 62, "name": "chevron-up", "prevSize": 32, "code": 59704 }, + "setIdx": 0, + "setId": 4, + "iconIdx": 68 + }, + { + "icon": { + "paths": [ + "M512 864c194.403 0 352-157.597 352-352 0-46.522-9.024-90.931-25.418-131.581l48.518-48.518c26.211 54.496 40.899 115.581 40.899 180.099 0 229.75-186.25 416-416 416s-416-186.25-416-416c0-229.75 186.25-416 416-416 95.376 0 183.254 32.096 253.424 86.076l-45.712 45.712c-58.221-42.623-130.029-67.788-207.712-67.788-194.404 0-352 157.596-352 352s157.596 352 352 352zM902.63 230.623c12.496-12.499 12.493-32.76-0.006-45.255s-32.762-12.491-45.254 0.008l-345.386 345.503-105.341-105.491c-12.486-12.506-32.749-12.522-45.254-0.032-12.506 12.486-12.522 32.749-0.032 45.254l127.971 128.157c6 6.006 14.144 9.386 22.634 9.389 8.493 0 16.637-3.373 22.64-9.376l368.029-368.157z" + ], + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "isMulticolor": false, + "isMulticolor2": false, + "colorPermutations": { + "1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101": [ + { "f": 2 } + ] + }, + "tags": ["circle-check"], + "grid": 0 + }, + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "properties": { "order": 208, "id": 63, "name": "circle-check", "prevSize": 32, "code": 59705 }, + "setIdx": 0, + "setId": 4, + "iconIdx": 69 + }, + { + "icon": { + "paths": [ + "M608 192c0-53.019-42.979-96-96-96s-96 42.981-96 96h-192c-17.673 0-32 14.327-32 32v672c0 17.674 14.327 32 32 32h576c17.674 0 32-14.326 32-32v-672c0-17.673-14.326-32-32-32h-192zM256 864v-608h96v64c0 17.674 14.326 32 32 32h256c17.674 0 32-14.326 32-32v-64h96v608h-512zM512 224c17.674 0 32-14.327 32-32s-14.326-32-32-32c-17.674 0-32 14.327-32 32s14.326 32 32 32z" + ], + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "isMulticolor": false, + "isMulticolor2": false, + "colorPermutations": { + "1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101": [ + { "f": 2 } + ] + }, + "tags": ["clipboard"], + "grid": 0 + }, + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "properties": { "order": 209, "id": 64, "name": "clipboard", "prevSize": 32, "code": 59706 }, + "setIdx": 0, + "setId": 4, + "iconIdx": 70 + }, + { + "icon": { + "paths": [ + "M864 512c0 194.403-157.597 352-352 352s-352-157.597-352-352c0-194.404 157.596-352 352-352s352 157.596 352 352zM928 512c0-229.75-186.25-416-416-416s-416 186.25-416 416c0 229.75 186.25 416 416 416s416-186.25 416-416zM544 288c0-17.673-14.326-32-32-32s-32 14.327-32 32v224c0 8.486 3.373 16.627 9.373 22.627l96 96c12.496 12.496 32.758 12.496 45.254 0s12.496-32.758 0-45.254l-86.627-86.627v-210.746z" + ], + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "isMulticolor": false, + "isMulticolor2": false, + "colorPermutations": { + "1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101": [ + { "f": 2 } + ] + }, + "tags": ["clock"], + "grid": 0 + }, + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "properties": { "order": 210, "id": 65, "name": "clock", "prevSize": 32, "code": 59707 }, + "setIdx": 0, + "setId": 4, + "iconIdx": 71 + }, + { + "icon": { + "paths": [ + "M806.627 262.628c12.496-12.497 12.496-32.758 0-45.255s-32.758-12.497-45.254 0l-249.373 249.373-249.372-249.373c-12.497-12.497-32.758-12.497-45.255 0s-12.497 32.758 0 45.255l249.373 249.372-249.373 249.373c-12.497 12.496-12.497 32.758 0 45.254s32.758 12.496 45.255 0l249.372-249.373 249.373 249.373c12.496 12.496 32.758 12.496 45.254 0s12.496-32.758 0-45.254l-249.373-249.373 249.373-249.372z" + ], + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "isMulticolor": false, + "isMulticolor2": false, + "colorPermutations": { + "1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101": [ + { "f": 2 } + ] + }, + "tags": ["close"], + "grid": 0 + }, + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "properties": { "order": 211, "id": 66, "name": "close", "prevSize": 32, "code": 59708 }, + "setIdx": 0, + "setId": 4, + "iconIdx": 72 + }, + { + "icon": { + "paths": [ + "M273.129 356.678c6.747-28.298 25.696-52.004 51.21-67.152 25.6-15.2 56.323-20.858 84.698-14.53 27.642 6.163 55.117 24.12 74.874 60.339l22.541 41.325 30.134-36.16c27.238-32.689 85.126-39.713 134.714-14.064 23.562 12.186 42.208 30.618 52.064 53.488 9.654 22.406 12.112 51.898-1.434 89.149l-15.616 42.938h45.686c53.606 0 82.419 15.882 97.642 33.75 15.651 18.374 21.898 44.646 18.557 74.714-3.344 30.083-16.054 60.518-33.456 82.89-17.939 23.066-36.646 32.646-50.742 32.646h-543.998c-18.791 0-37.068-10.362-52.195-31.578-15.217-21.344-24.978-51.050-25.818-81.312-0.84-30.227 7.237-57.914 23.733-77.491 15.796-18.746 42.268-33.619 86.279-33.619h59.791l-33.165-49.75c-27.882-41.824-32.118-77.814-25.498-105.581zM518.218 272.084c-26.339-32.073-59.667-51.619-95.251-59.554-45.626-10.174-92.902-0.833-131.301 21.965-38.487 22.85-69.538 60.142-80.791 107.342-8.278 34.72-5.369 72.758 10.731 111.77-35.67 8.464-64.099 26.186-84.825 50.784-29.004 34.422-39.927 78.733-38.766 120.506 1.159 41.738 14.399 84.032 37.682 116.688 23.373 32.784 59.097 58.426 104.306 58.426h543.999c41.907 0 77.2-26.419 101.261-57.354 24.598-31.629 41.888-73.197 46.544-115.114 4.659-41.933-3.094-87.658-33.443-123.283-24.106-28.301-59.504-46.774-105.616-53.456 5.83-35.194 1.686-67.674-10.605-96.205-16.646-38.628-46.998-67.197-81.437-85.010-54.902-28.397-128.733-32.652-182.486 2.495zM512 437.331c17.674 0 32 14.33 32 32v53.328h53.334c17.674 0 32 14.33 32 32 0 17.674-14.326 32-32 32h-53.334v53.341c0 17.674-14.326 32-32 32s-32-14.326-32-32v-53.341h-53.331c-17.674 0-32-14.326-32-32 0-17.67 14.326-32 32-32h53.331v-53.328c0-17.67 14.326-32 32-32z" + ], + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "isMulticolor": false, + "isMulticolor2": false, + "colorPermutations": { + "1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101": [ + { "f": 2 } + ] + }, + "tags": ["cloud-connectivity"], + "grid": 0 + }, + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "properties": { "order": 212, "id": 67, "name": "cloud-connectivity", "prevSize": 32, "code": 59709 }, + "setIdx": 0, + "setId": 4, + "iconIdx": 73 + }, + { + "icon": { + "paths": [ + "M630.154 204.798c16.496 6.344 24.723 24.859 18.381 41.355l-213.334 554.667c-6.342 16.496-24.858 24.723-41.354 18.381-16.496-6.346-24.723-24.861-18.381-41.357l213.334-554.666c6.346-16.495 24.858-24.724 41.354-18.38zM321.293 361.37c12.499 12.499 12.499 32.758 0 45.258l-105.371 105.37 105.371 105.373c12.499 12.499 12.499 32.758 0 45.258-12.495 12.496-32.756 12.496-45.253 0l-128-128c-12.497-12.499-12.497-32.758 0-45.258l128-128c12.497-12.496 32.758-12.496 45.253 0zM702.707 361.37c12.496-12.496 32.758-12.496 45.254 0l128 128c12.496 12.499 12.496 32.758 0 45.258l-128 128c-12.496 12.496-32.758 12.496-45.254 0-12.499-12.499-12.499-32.758 0-45.258l105.373-105.373-105.373-105.37c-12.499-12.499-12.499-32.758 0-45.258z" + ], + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "isMulticolor": false, + "isMulticolor2": false, + "colorPermutations": { + "1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101": [ + { "f": 2 } + ] + }, + "tags": ["code"], + "grid": 0 + }, + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "properties": { "order": 213, "id": 68, "name": "code", "prevSize": 32, "code": 59710 }, + "setIdx": 0, + "setId": 4, + "iconIdx": 74 + }, + { + "icon": { + "paths": [ + "M770.704 224c17.674 0 32 14.327 32 32v544c0 17.674-14.326 32-32 32h-543.999c-17.673 0-32-14.326-32-32v-128c17.673 0 32-14.326 32-32s-14.327-32-32-32v-192c17.673 0 32-14.326 32-32s-14.327-32-32-32v-96c0-17.673 14.327-32 32-32h543.999zM130.705 608c-17.673 0-32 14.326-32 32s14.327 32 32 32v128c0 53.021 42.981 96 96 96h543.999c53.021 0 96-42.979 96-96v-544c0-53.019-42.979-96-96-96h-543.999c-53.019 0-96 42.981-96 96v96c-17.673 0-32 14.326-32 32s14.327 32 32 32v192zM427.91 514.266c13.315-3.568 27.309-3.789 40.73-0.643l31.52 7.389c8.73 2.045 17.83 1.904 26.49-0.416l22.186-5.942c14.285-3.827 29.328-4.042 43.683-0.675 40.429 9.475 69.325 45.584 69.325 87.277v24.336c0 31.811-25.789 57.6-57.6 57.6h-180.739c-31.811 0-57.6-25.789-57.6-57.6v-30.525c0-37.862 25.434-71.005 62.006-80.8zM456.957 563.472c-5.206-1.219-10.634-1.133-15.798 0.25-14.189 3.798-24.054 16.656-24.054 31.344v30.525c0 3.536 2.867 6.4 6.4 6.4h180.739c3.533 0 6.4-2.864 6.4-6.4v-24.336c0-17.75-12.365-33.341-29.808-37.427-6.186-1.45-12.662-1.35-18.752 0.282l-22.186 5.942c-16.813 4.502-34.474 4.781-51.421 0.81l-31.52-7.389zM539.155 420.48c0-13.962-11.318-25.28-25.28-25.28s-25.283 11.318-25.283 25.28c0 13.962 11.322 25.28 25.283 25.28s25.28-11.318 25.28-25.28zM590.355 420.48c0 42.24-34.243 76.48-76.48 76.48-42.24 0-76.483-34.24-76.483-76.48s34.243-76.48 76.483-76.48c42.237 0 76.48 34.24 76.48 76.48z" + ], + "width": 1056, + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "isMulticolor": false, + "isMulticolor2": false, + "colorPermutations": { + "1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101": [ + { "f": 2 } + ] + }, + "tags": ["contacts"], + "grid": 0 + }, + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "properties": { "order": 214, "id": 69, "name": "contacts", "prevSize": 32, "code": 59711 }, + "setIdx": 0, + "setId": 4, + "iconIdx": 75 + }, + { + "icon": { + "paths": [ + "M464 272c-35.347 0-64 28.654-64 64v480c0 35.347 28.653 64 64 64h352c35.347 0 64-28.653 64-64v-304c0-6.509-1.984-12.864-5.69-18.214l-134.458-194.215c-11.952-17.267-31.619-27.571-52.621-27.571h-223.232zM464 336h144v144c0 35.347 28.653 64 64 64h144v272h-352v-480zM672 480v-144h15.232l99.693 144h-114.925zM144 208c0-35.346 28.654-64 64-64h241.844c18.032 0 35.229 7.607 47.357 20.949l39.136 43.051h-328.336v480h128v64h-128c-35.346 0-64-28.653-64-64v-480z" + ], + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "isMulticolor": false, + "isMulticolor2": false, + "colorPermutations": { + "1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101": [ + { "f": 2 } + ] + }, + "tags": ["copy"], + "grid": 0 + }, + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "properties": { "order": 215, "id": 70, "name": "copy", "prevSize": 32, "code": 59712 }, + "setIdx": 0, + "setId": 4, + "iconIdx": 76 + }, + { + "icon": { + "paths": [ + "M824.682 183.521c-37.19-37.986-98.202-38.583-136.131-1.333l-348.48 342.237c-12.739 12.512-21.733 28.32-25.976 45.654l-23.681 96.749c-6.94 28.355 16.754 54.845 45.747 51.142l90.691-11.578c20.224-2.582 39.104-11.52 53.907-25.52l360.486-340.922c38.986-36.868 40.173-98.482 2.637-136.82l-19.2-19.61zM733.485 227.821c12.643-12.417 32.979-12.218 45.376 0.444l19.2 19.61c12.512 12.78 12.115 33.317-0.88 45.607l-69.965 66.169-63.446-63.364 69.715-68.466zM618.074 341.162l62.592 62.512-243.971 230.73c-4.934 4.666-11.229 7.645-17.968 8.506l-58.307 7.443 15.926-65.075c1.414-5.779 4.413-11.046 8.659-15.219l233.069-228.896zM193.608 265.602c0-17.673 14.346-32 32.042-32h281.332c17.696 0 32.042-14.327 32.042-32s-14.346-32-32.042-32h-281.332c-53.089 0-96.127 42.981-96.127 96v534.402c0 53.018 43.037 95.997 96.127 95.997h529.764c53.091 0 96.128-42.979 96.128-96v-279.414c0-17.67-14.346-32-32.042-32s-32.042 14.33-32.042 32v279.414c0 17.674-14.346 32-32.045 32h-529.764c-17.696 0-32.042-14.326-32.042-31.997v-534.402z" + ], + "width": 1056, + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "isMulticolor": false, + "isMulticolor2": false, + "colorPermutations": { + "1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101": [ + { "f": 2 } + ] + }, + "tags": ["create"], + "grid": 0 + }, + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "properties": { "order": 216, "id": 71, "name": "create", "prevSize": 32, "code": 59713 }, + "setIdx": 0, + "setId": 4, + "iconIdx": 77 + }, + { + "icon": { + "paths": [ + "M717.373 298.663c0-17.673-14.326-32-32-32s-32 14.327-32 32v426.665c0 17.674 14.326 32 32 32s32-14.326 32-32v-426.665z", + "M514.704 394.672c17.674 0 32 14.326 32 32v298.666c0 17.674-14.326 32-32 32s-32-14.326-32-32v-298.666c0-17.674 14.326-32 32-32z", + "M376.038 554.672c0-17.674-14.326-32-32-32s-32 14.326-32 32v170.669c0 17.67 14.327 32 32 32s32-14.33 32-32v-170.669z", + "M130.705 199.556v624.889c0 39.52 32.036 71.555 71.556 71.555h624.888c39.52 0 71.555-32.035 71.555-71.555v-624.889c0-39.519-32.035-71.556-71.555-71.556h-624.888c-39.519 0-71.556 32.036-71.556 71.556zM202.26 192h624.888c4.173 0 7.555 3.383 7.555 7.556v624.889c0 4.173-3.382 7.555-7.555 7.555h-624.888c-4.173 0-7.556-3.382-7.556-7.555v-624.889c0-4.173 3.383-7.556 7.556-7.556z" + ], + "width": 1056, + "attrs": [ + { "fill": "rgb(108, 114, 122)" }, + { "fill": "rgb(108, 114, 122)" }, + { "fill": "rgb(108, 114, 122)" }, + { "fill": "rgb(108, 114, 122)" } + ], + "isMulticolor": false, + "isMulticolor2": false, + "colorPermutations": { + "1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101": [ + { "f": 2 }, + { "f": 2 }, + { "f": 2 }, + { "f": 2 } + ] + }, + "tags": ["dashboard"], + "grid": 0 + }, + "attrs": [ + { "fill": "rgb(108, 114, 122)" }, + { "fill": "rgb(108, 114, 122)" }, + { "fill": "rgb(108, 114, 122)" }, + { "fill": "rgb(108, 114, 122)" } + ], + "properties": { "order": 217, "id": 72, "name": "dashboard", "prevSize": 32, "code": 59714 }, + "setIdx": 0, + "setId": 4, + "iconIdx": 78 + }, + { + "icon": { + "paths": [ + "M739.68 864v-448h-448.593v448h448.593zM227.003 864v-448c-35.393 0-64.084-28.653-64.084-64v-128c0-35.346 28.692-64 64.084-64h224.297c0-35.346 28.691-64 64.083-64 35.395 0 64.086 28.654 64.086 64h224.294c35.395 0 64.086 28.654 64.086 64v128c0 35.347-28.691 64-64.086 64v448c0 35.347-28.691 64-64.083 64h-448.593c-35.393 0-64.085-28.653-64.085-64zM803.763 224h-576.761v128h576.761v-128zM419.258 544v192c0 17.674 14.346 32 32.042 32s32.042-14.326 32.042-32v-192c0-17.674-14.346-32-32.042-32s-32.042 14.326-32.042 32zM579.469 512c17.696 0 32.042 14.326 32.042 32v192c0 17.674-14.346 32-32.042 32s-32.042-14.326-32.042-32v-192c0-17.674 14.346-32 32.042-32z" + ], + "width": 1056, + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "isMulticolor": false, + "isMulticolor2": false, + "colorPermutations": { + "1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101": [ + { "f": 2 } + ] + }, + "tags": ["delete"], + "grid": 0 + }, + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "properties": { "order": 218, "id": 73, "name": "delete", "prevSize": 32, "code": 59715 }, + "setIdx": 0, + "setId": 4, + "iconIdx": 79 + }, + { + "icon": { + "paths": [ + "M341.334 778.672c-17.674 0-32 14.326-32 32s14.327 32 32 32h341.334c17.67 0 32-14.326 32-32s-14.33-32-32-32h-341.334z", + "M85.334 298.672c0-70.692 57.308-128 128-128h597.335c70.691 0 128 57.308 128 128v298.666c0 70.694-57.309 128-128 128h-597.335c-70.692 0-128-57.306-128-128v-298.666zM213.334 234.672c-35.346 0-64 28.654-64 64v298.666c0 35.347 28.654 64 64 64h597.335c35.344 0 64-28.653 64-64v-298.666c0-35.347-28.656-64-64-64h-597.335z" + ], + "attrs": [{ "fill": "rgb(108, 114, 122)" }, { "fill": "rgb(108, 114, 122)" }], + "isMulticolor": false, + "isMulticolor2": false, + "colorPermutations": { + "1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101": [ + { "f": 2 }, + { "f": 2 } + ] + }, + "tags": ["desktop"], + "grid": 0 + }, + "attrs": [{ "fill": "rgb(108, 114, 122)" }, { "fill": "rgb(108, 114, 122)" }], + "properties": { "order": 219, "id": 74, "name": "desktop", "prevSize": 32, "code": 59716 }, + "setIdx": 0, + "setId": 4, + "iconIdx": 80 + }, + { + "icon": { + "paths": [ + "M325.837 160c0-17.673-14.347-32-32.043-32h-64.085c-17.696 0-32.042 14.327-32.042 32v64c0 17.673 14.346 32 32.042 32h64.085c17.696 0 32.043-14.327 32.043-32v-64zM325.837 586.666c0-17.674-14.347-32-32.043-32h-64.085c-17.696 0-32.042 14.326-32.042 32v64c0 17.674 14.346 32 32.042 32h64.085c17.696 0 32.043-14.326 32.043-32v-64zM454.006 160c0-17.673 14.346-32 32.042-32h64.083c17.696 0 32.042 14.327 32.042 32v64c0 17.673-14.346 32-32.042 32h-64.083c-17.696 0-32.042-14.327-32.042-32v-64zM582.173 373.334c0-17.674-14.346-32-32.042-32h-64.083c-17.696 0-32.042 14.326-32.042 32v64c0 17.674 14.346 32 32.042 32h64.083c17.696 0 32.042-14.326 32.042-32v-64zM454.006 586.666c0-17.674 14.346-32 32.042-32h64.083c17.696 0 32.042 14.326 32.042 32v64c0 17.674-14.346 32-32.042 32h-64.083c-17.696 0-32.042-14.326-32.042-32v-64zM582.173 800c0-17.674-14.346-32-32.042-32h-64.083c-17.696 0-32.042 14.326-32.042 32v64c0 17.674 14.346 32 32.042 32h64.083c17.696 0 32.042-14.326 32.042-32v-64zM710.342 160c0-17.673 14.346-32 32.045-32h64.083c17.696 0 32.042 14.327 32.042 32v64c0 17.673-14.346 32-32.042 32h-64.083c-17.699 0-32.045-14.327-32.045-32v-64zM838.512 373.334c0-17.674-14.346-32-32.042-32h-64.083c-17.699 0-32.045 14.326-32.045 32v64c0 17.674 14.346 32 32.045 32h64.083c17.696 0 32.042-14.326 32.042-32v-64zM710.342 586.666c0-17.674 14.346-32 32.045-32h64.083c17.696 0 32.042 14.326 32.042 32v64c0 17.674-14.346 32-32.042 32h-64.083c-17.699 0-32.045-14.326-32.045-32v-64zM325.837 373.334c0-17.674-14.347-32-32.043-32h-64.085c-17.696 0-32.042 14.326-32.042 32v64c0 17.674 14.346 32 32.042 32h64.085c17.696 0 32.043-14.326 32.043-32v-64z" + ], + "width": 1056, + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "isMulticolor": false, + "isMulticolor2": false, + "colorPermutations": { + "1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101": [ + { "f": 2 } + ] + }, + "tags": ["dialpad"], + "grid": 0 + }, + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "properties": { "order": 220, "id": 75, "name": "dialpad", "prevSize": 32, "code": 59717 }, + "setIdx": 0, + "setId": 4, + "iconIdx": 81 + }, + { + "icon": { + "paths": [ + "M514.704 96c80.096 0 154.906 22.636 218.378 61.859l-46.746 46.746c-24.739-13.843-51.322-24.785-79.286-32.368 12.166 22.395 23.078 49.095 32.403 79.249l-51.997 51.999c-5.654-22.994-12.17-44.005-19.347-62.666-12.822-33.339-26.522-55.927-38.474-69.070-8.4-9.235-13.43-11.291-14.928-11.698-1.501 0.407-6.531 2.463-14.931 11.698-11.949 13.143-25.651 35.731-38.474 69.070-16.298 42.377-29.174 96.867-36.339 159.18h65.974l-63.997 64h-7.117c-0.115 2.47-0.221 4.95-0.32 7.437l-64.538 64.538c-0.173-7.933-0.259-15.926-0.259-23.974 0-16.234 0.355-32.25 1.053-48h-189.81c-2.14 15.696-3.245 31.718-3.245 48 0 33.28 4.619 65.485 13.251 96h106.984l-64 64h-17.852c2.004 3.92 4.078 7.798 6.223 11.629l-46.746 46.749c-39.223-63.475-61.859-138.282-61.859-218.378 0-229.75 186.25-416 415.999-416zM868.845 293.623l-46.746 46.748c10.56 18.87 19.434 38.816 26.413 59.629h-86.045l-64 64h164.992c2.141 15.696 3.245 31.718 3.245 48 0 33.28-4.618 65.485-13.248 96h-183.030c2.8-30.816 4.282-62.957 4.282-96 0-8.045-0.090-16.042-0.262-23.974l-64.538 64.534c-0.752 19.008-2.022 37.523-3.763 55.44h-51.677l-64 64h107.091c-7.37 42.458-17.488 80.083-29.45 111.184-12.822 33.338-26.522 55.926-38.474 69.069-8.397 9.235-13.43 11.29-14.928 11.699-1.501-0.41-6.531-2.464-14.931-11.699-11.949-13.142-25.651-35.731-38.474-69.069-7.178-18.662-13.693-39.677-19.347-62.669l-52 51.997c9.328 30.154 20.24 56.854 32.406 79.251-27.965-7.584-54.547-18.525-79.286-32.368l-46.747 46.746c63.348 39.146 137.985 61.77 217.899 61.859h0.957c229.53-0.259 415.52-186.41 415.52-416 0-80.096-22.634-154.902-61.859-218.377zM422.362 172.237c-113.55 30.788-204.31 116.98-241.464 227.763h179.676c10-93.226 32.176-173.252 61.789-227.763zM607.050 851.763c24.397-44.902 43.741-107.117 55.395-179.763h165.878c-44.653 87.35-124.723 153.584-221.274 179.763zM828.077 153.372c12.499-12.497 32.758-12.497 45.254 0 12.499 12.497 12.499 32.758 0 45.255l-671.999 672c-12.497 12.496-32.758 12.496-45.255 0s-12.497-32.758 0-45.254l671.999-672z" + ], + "width": 1056, + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "isMulticolor": false, + "isMulticolor2": false, + "colorPermutations": { + "1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101": [ + { "f": 2 } + ] + }, + "tags": ["directory-disabled"], + "grid": 0 + }, + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "properties": { "order": 221, "id": 76, "name": "directory-disabled", "prevSize": 32, "code": 59718 }, + "setIdx": 0, + "setId": 4, + "iconIdx": 82 + }, + { + "icon": { + "paths": [ + "M866.493 512c0 33.28-4.624 65.485-13.267 96h-183.27c2.803-30.816 4.285-62.957 4.285-96 0-16.234-0.358-32.25-1.053-48h190.058c2.141 15.696 3.248 31.718 3.248 48zM609.037 464c0.736 15.67 1.12 31.686 1.12 48 0 33.398-1.606 65.555-4.57 96h-183.117c-2.96-30.445-4.566-62.602-4.566-96 0-16.314 0.381-32.33 1.12-48h190.013zM668.368 400c-10.013-93.226-32.218-173.252-61.872-227.763 113.699 30.788 204.579 116.981 241.782 227.763h-179.91zM421.565 172.237c-29.654 54.51-51.859 134.537-61.872 227.763h-179.913c37.204-110.782 128.083-196.975 241.785-227.763zM424.17 400c7.171-62.314 20.067-116.804 36.387-159.18 12.838-33.339 26.557-55.927 38.525-69.070 8.41-9.235 13.446-11.291 14.947-11.698 1.501 0.407 6.541 2.463 14.95 11.698 11.965 13.143 25.683 35.731 38.525 69.070 16.32 42.377 29.213 96.867 36.387 159.18h-179.722zM354.874 464c-0.698 15.75-1.056 31.766-1.056 48 0 33.043 1.485 65.184 4.288 96h-183.274c-8.643-30.515-13.268-62.72-13.268-96 0-16.282 1.107-32.304 3.249-48h190.060zM366.096 672c11.667 72.646 31.040 134.861 55.466 179.763-96.675-26.179-176.853-92.413-221.565-179.763h166.099zM514.509 928c229.834-0.259 416.070-186.41 416.070-416 0-229.75-186.496-416-416.55-416s-416.549 186.25-416.549 416c0 229.59 186.237 415.741 416.073 416 0.16 0 0.317 0 0.477 0s0.32 0 0.48 0zM606.496 851.763c24.426-44.902 43.798-107.117 55.466-179.763h166.099c-44.71 87.35-124.886 153.584-221.565 179.763zM596.992 672c-7.379 42.458-17.51 80.083-29.488 111.184-12.842 33.338-26.56 55.926-38.525 69.069-8.41 9.235-13.45 11.29-14.95 11.699-1.501-0.41-6.538-2.464-14.947-11.699-11.968-13.142-25.686-35.731-38.525-69.069-11.978-31.101-22.112-68.726-29.491-111.184h165.926z" + ], + "width": 1056, + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "isMulticolor": false, + "isMulticolor2": false, + "colorPermutations": { + "1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101": [ + { "f": 2 } + ] + }, + "tags": ["directory"], + "grid": 0 + }, + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "properties": { "order": 222, "id": 77, "name": "directory", "prevSize": 32, "code": 59719 }, + "setIdx": 0, + "setId": 4, + "iconIdx": 83 + }, + { + "icon": { + "paths": [ + "M561.667 127.992c-121.696 0-200.973 50.509-248.813 115.801-46.216 63.073-61.638 137.752-61.933 188.616-8.705 98.746 28.601 172.896 89.422 223.469 59.686 49.626 140.202 75.258 217.85 83.738 77.75 8.486 161.715 1.197 227.856-12.547 33.066-6.87 62.774-15.581 85.642-25.331 11.366-4.848 22.029-10.368 30.797-16.669 7.875-5.661 18.541-14.976 23.242-28.835 7.286-21.475-3.133-38.784-11.965-48.102-8.41-8.877-19.427-15.405-28.24-20.016-9.306-4.867-19.379-9.206-27.533-12.707-8.973-3.856-14.858-6.4-18.56-8.432-4.845-2.662-8.842-5.088-12.026-7.203 20.256-47.142 31.549-77.043 37.696-99.19 7.293-26.272 7.315-41.725 7.315-58.909 0-21.523-9.603-88.542-52.81-151.108-44.701-64.73-124.074-122.573-257.939-122.573zM314.916 433.898c0-40.416 12.607-101.84 49.564-152.277 35.782-48.836 95.882-89.628 197.187-89.628 112.086 0 172.090 46.886 205.277 94.94 34.678 50.219 41.472 104.040 41.472 114.741v0.198c0 14.918 0 23.638-4.982 41.594-5.491 19.779-16.963 50.189-41.539 106.538-5.802 13.296-2.762 25.77 1.155 33.754 3.664 7.478 9.005 13.242 13.331 17.261 8.835 8.211 20.653 15.686 32.224 22.045 6.618 3.638 15.238 7.334 22.563 10.477l1.562 0.672c5.904 2.534 11.52 4.97 16.688 7.418-0.909 0.406-1.856 0.819-2.835 1.238-17.741 7.568-43.078 15.206-73.555 21.539-60.947 12.666-138.064 19.21-207.888 11.587-69.926-7.635-136.982-30.336-183.878-69.328-45.459-37.798-73.674-92.064-66.481-169.821l0.136-1.469v-1.478zM819.162 553.354l-0.074-0.086c0 0.003 0.010 0.013 0.029 0.035 0.010 0.013 0.026 0.029 0.045 0.051z", + "M178.552 502.474c7.496-11.258 16.26-22.259 26.436-32.592 0.876 31.747 6.169 61.226 15.216 88.358-15.094 30.877-18.374 59.315-18.374 65.357v0.186c0 11.382 0 17.536 3.524 30.701 3.993 14.918 12.441 38.211 30.867 82.022 5.256 12.496 2.48 24.099-0.985 31.43-3.249 6.874-7.925 12.058-11.512 15.514-7.319 7.053-16.852 13.254-25.75 18.326-5.008 2.854-11.333 5.706-16.546 8.029 11.182 3.939 24.98 7.814 40.802 11.226 44.977 9.693 101.833 14.669 153.062 8.87 51.28-5.808 99.776-23.014 133.35-51.965l0.608-0.528c14.794 2.784 29.53 4.938 44.051 6.525 10.886 1.187 21.862 2.086 32.877 2.72-10.122 14.797-22.163 28.045-35.741 39.754-46.362 39.974-108.547 60.362-167.946 67.088-59.45 6.73-123.405 0.947-173.744-9.901-25.17-5.424-48.056-12.352-65.901-20.243-8.86-3.92-17.458-8.502-24.686-13.891-6.431-4.794-15.831-13.171-20.001-25.92-6.422-19.632 2.792-35.443 10.458-43.834 7.193-7.872 16.405-13.462 23.248-17.174 7.309-3.965 15.158-7.466 21.235-10.173 6.907-3.078 10.854-4.858 13.183-6.186 1.349-0.768 2.591-1.501 3.727-2.195-13.937-33.885-21.976-56.118-26.48-72.947-5.68-21.222-5.7-33.875-5.7-47.434 0-17.712 7.436-71.139 40.721-121.123zM155.337 797.248c-0.012 0 0.084 0.102 0.317 0.301-0.19-0.202-0.306-0.301-0.317-0.301zM179.587 737.085c-0.004 0-0.052 0.051-0.137 0.15l0.112-0.118c0.019-0.022 0.027-0.032 0.025-0.032z" + ], + "attrs": [{ "fill": "rgb(108, 114, 122)" }, { "fill": "rgb(108, 114, 122)" }], + "isMulticolor": false, + "isMulticolor2": false, + "colorPermutations": { + "1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101": [ + { "f": 2 }, + { "f": 2 } + ] + }, + "tags": ["discussions"], + "grid": 0 + }, + "attrs": [{ "fill": "rgb(108, 114, 122)" }, { "fill": "rgb(108, 114, 122)" }], + "properties": { "order": 223, "id": 78, "name": "discussions", "prevSize": 32, "code": 59720 }, + "setIdx": 0, + "setId": 4, + "iconIdx": 84 + }, + { + "icon": { + "paths": [ + "M160 256c0-17.673 14.327-32 32-32h640c17.674 0 32 14.327 32 32s-14.326 32-32 32h-640c-17.673 0-32-14.327-32-32zM160 421.162c0-17.674 14.327-32 32-32h640c17.674 0 32 14.326 32 32s-14.326 32-32 32h-640c-17.673 0-32-14.326-32-32zM160 602.838c0-17.674 14.327-32 32-32h640c17.674 0 32 14.326 32 32s-14.326 32-32 32h-640c-17.673 0-32-14.326-32-32zM160 768c0-17.674 14.327-32 32-32h344.614c17.674 0 32 14.326 32 32s-14.326 32-32 32h-344.614c-17.673 0-32-14.326-32-32z" + ], + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "isMulticolor": false, + "isMulticolor2": false, + "colorPermutations": { + "1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101": [ + { "f": 2 } + ] + }, + "tags": ["document"], + "grid": 0 + }, + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "properties": { "order": 224, "id": 79, "name": "document", "prevSize": 32, "code": 59721 }, + "setIdx": 0, + "setId": 4, + "iconIdx": 85 + }, + { + "icon": { + "paths": [ + "M128.169 352c0-17.674 14.346-32 32.042-32h704.928c17.696 0 32.045 14.326 32.045 32s-14.349 32-32.045 32h-704.928c-17.696 0-32.042-14.326-32.042-32zM213.615 522.669c0-17.674 14.346-32 32.042-32h534.036c17.699 0 32.045 14.326 32.045 32s-14.346 32-32.045 32h-534.036c-17.697 0-32.042-14.326-32.042-32zM331.104 661.331c-17.698 0-32.043 14.326-32.043 32s14.346 32 32.043 32h363.146c17.696 0 32.042-14.326 32.042-32s-14.346-32-32.042-32h-363.146z" + ], + "width": 1056, + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "isMulticolor": false, + "isMulticolor2": false, + "colorPermutations": { + "1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101": [ + { "f": 2 } + ] + }, + "tags": ["donner"], + "grid": 0 + }, + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "properties": { "order": 225, "id": 80, "name": "donner", "prevSize": 32, "code": 59722 }, + "setIdx": 0, + "setId": 4, + "iconIdx": 86 + }, + { + "icon": { + "paths": [ + "M273.128 356.678c-6.62 27.766-2.384 63.757 25.498 105.581l21.374 32.061v17.69h-48c-44.011 0-70.483 14.874-86.278 33.619-16.496 19.578-24.573 47.264-23.734 77.491 0.841 30.262 10.601 59.968 25.818 81.312 15.127 21.216 33.404 31.578 52.195 31.578h79.999v64h-80c-45.209 0-80.932-25.642-104.306-58.426-23.283-32.656-36.522-74.95-37.682-116.688-1.16-41.773 9.763-86.083 38.766-120.506 20.726-24.598 49.155-42.32 84.825-50.784-16.1-39.011-19.009-77.050-10.731-111.77 11.253-47.2 42.304-84.492 80.791-107.342 38.4-22.798 85.677-32.139 131.303-21.965 35.584 7.935 68.909 27.48 95.251 59.554 53.75-35.147 127.584-30.892 182.483-2.495 34.438 17.812 64.794 46.382 81.437 85.010 12.294 28.531 16.438 61.011 10.608 96.205 46.112 6.682 81.507 25.155 105.613 53.456 30.349 35.626 38.106 81.35 33.446 123.283-4.659 41.917-21.946 83.485-46.544 115.114-24.061 30.934-59.354 57.354-101.261 57.354h-80v-64h80c14.093 0 32.8-9.581 50.742-32.646 17.398-22.371 30.112-52.806 33.453-82.89 3.341-30.067-2.902-56.339-18.554-74.714-15.222-17.869-44.035-33.75-97.642-33.75h-45.686l15.613-42.938c13.546-37.251 11.091-66.742 1.437-89.149-9.856-22.87-28.502-41.302-52.064-53.488-49.587-25.649-107.475-18.625-134.717 14.064l-30.134 36.16-22.541-41.325c-19.757-36.219-47.232-54.175-74.87-60.339-28.378-6.327-59.098-0.669-84.701 14.53-25.512 15.148-44.461 38.855-51.208 67.152zM630.979 787.222c12.298-12.691 11.981-32.95-0.707-45.251-12.691-12.298-32.95-11.981-45.251 0.707l-41.021 42.326v-273.005c0-17.674-14.326-32-32-32s-32 14.326-32 32v273.005l-41.021-42.326c-12.301-12.688-32.56-13.005-45.251-0.707-12.688 12.301-13.005 32.56-0.707 45.251l96 99.050c6.029 6.218 14.32 9.728 22.979 9.728s16.95-3.51 22.979-9.728l96-99.050z" + ], + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "isMulticolor": false, + "isMulticolor2": false, + "colorPermutations": { + "1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101": [ + { "f": 2 } + ] + }, + "tags": ["download"], + "grid": 0 + }, + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "properties": { "order": 226, "id": 81, "name": "download", "prevSize": 32, "code": 59723 }, + "setIdx": 0, + "setId": 4, + "iconIdx": 87 + }, + { + "icon": { + "paths": [ + "M795.648 132.004c-25.027-25.027-65.603-25.027-90.63-0l-530.467 530.469c-9.577 9.574-15.873 21.939-17.985 35.318l-19.611 124.186c-1.386 8.771-0.942 17.299 1.006 25.261 7.597 31.037 38.083 53.44 72.291 48.038l124.184-19.613c13.379-2.112 25.744-8.41 35.322-17.984l530.467-530.47c12.512-12.513 18.768-28.914 18.768-45.316 0-10.379-2.506-20.757-7.517-30.15-2.906-5.451-6.656-10.57-11.251-15.164l-104.576-104.575zM630.675 296.979l119.658-119.658 104.576 104.574-119.658 119.657-104.576-104.573zM200.255 831.974l19.611-124.186 365.494-365.494 104.576 104.573-365.494 365.494-124.187 19.613z" + ], + "width": 1056, + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "isMulticolor": false, + "isMulticolor2": false, + "colorPermutations": { + "1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101": [ + { "f": 2 } + ] + }, + "tags": ["edit"], + "grid": 0 + }, + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "properties": { "order": 227, "id": 82, "name": "edit", "prevSize": 32, "code": 59724 }, + "setIdx": 0, + "setId": 4, + "iconIdx": 88 + }, + { + "icon": { + "paths": [ + "M864 512c0-194.404-157.597-352-352-352s-352 157.596-352 352c0 194.403 157.596 352 352 352s352-157.597 352-352zM928 512c0 229.75-186.25 416-416 416s-416-186.25-416-416c0-229.75 186.25-416 416-416s416 186.25 416 416zM400.166 480c-35.347 0-64-28.653-64-64s28.653-64 64-64c35.347 0 64 28.653 64 64s-28.653 64-64 64zM688.166 416c0-35.347-28.653-64-64-64s-64 28.653-64 64c0 35.347 28.653 64 64 64s64-28.653 64-64zM353.226 720c8.963 0 17.526-3.53 24.272-9.437 21.603-18.918 42.64-31.958 62.701-40.509 36.563-15.59 71.805-17.107 104.794-9.939 37.632 8.176 72.659 27.808 102.109 51.341 6.726 5.376 14.995 8.544 23.606 8.544 30.218 0 45.616-34.256 22.397-53.594-36.912-30.742-82.79-57.59-134.522-68.832-44.794-9.734-93.67-7.632-143.485 13.606-28.79 12.275-57.232 30.653-85.024 55.933-21.572 19.619-6.010 52.886 23.152 52.886z" + ], + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "isMulticolor": false, + "isMulticolor2": false, + "colorPermutations": { + "1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101": [ + { "f": 2 } + ] + }, + "tags": ["emoji-bad-mood"], + "grid": 0 + }, + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "properties": { "order": 228, "id": 83, "name": "emoji-bad-mood", "prevSize": 32, "code": 59725 }, + "setIdx": 0, + "setId": 4, + "iconIdx": 89 + }, + { + "icon": { + "paths": [ + "M864 512c0-194.404-157.597-352-352-352s-352 157.596-352 352c0 194.403 157.596 352 352 352s352-157.597 352-352zM928 512c0 229.75-186.25 416-416 416s-416-186.25-416-416c0-229.75 186.25-416 416-416s416 186.25 416 416zM400.166 480c-35.347 0-64-28.653-64-64s28.653-64 64-64c35.347 0 64 28.653 64 64s-28.653 64-64 64zM688.166 416c0-35.347-28.653-64-64-64s-64 28.653-64 64c0 35.347 28.653 64 64 64s64-28.653 64-64zM384 640h256c17.674 0 32 14.326 32 32s-14.326 32-32 32h-256c-17.674 0-32-14.326-32-32s14.326-32 32-32z" + ], + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "isMulticolor": false, + "isMulticolor2": false, + "colorPermutations": { + "1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101": [ + { "f": 2 } + ] + }, + "tags": ["emoji-neutral-mood"], + "grid": 0 + }, + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "properties": { "order": 229, "id": 84, "name": "emoji-neutral-mood", "prevSize": 32, "code": 59726 }, + "setIdx": 0, + "setId": 4, + "iconIdx": 90 + }, + { + "icon": { + "paths": [ + "M330.074 644.886c-21.572-19.619-6.010-52.886 23.152-52.886 8.963 0 17.526 3.53 24.272 9.437 21.603 18.918 42.64 31.958 62.701 40.509 36.563 15.59 71.805 17.107 104.794 9.939 37.632-8.176 72.659-27.808 102.109-51.341 6.726-5.376 14.995-8.544 23.606-8.544 30.218 0 45.616 34.256 22.397 53.594-36.912 30.742-82.79 57.59-134.522 68.832-44.794 9.734-93.67 7.632-143.485-13.606-28.79-12.275-57.232-30.653-85.024-55.933z", + "M400.17 480c35.344 0 64-28.653 64-64s-28.656-64-64-64c-35.347 0-64 28.653-64 64s28.653 64 64 64z", + "M624.17 480c35.344 0 64-28.653 64-64s-28.656-64-64-64c-35.347 0-64 28.653-64 64s28.653 64 64 64z", + "M928 512c0 229.75-186.25 416-416 416s-416-186.25-416-416c0-229.75 186.25-416 416-416s416 186.25 416 416zM864 512c0-194.404-157.597-352-352-352s-352 157.596-352 352c0 194.403 157.596 352 352 352s352-157.597 352-352z" + ], + "attrs": [ + { "fill": "rgb(108, 114, 122)" }, + { "fill": "rgb(108, 114, 122)" }, + { "fill": "rgb(108, 114, 122)" }, + { "fill": "rgb(108, 114, 122)" } + ], + "isMulticolor": false, + "isMulticolor2": false, + "colorPermutations": { + "1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101": [ + { "f": 2 }, + { "f": 2 }, + { "f": 2 }, + { "f": 2 } + ] + }, + "tags": ["emoji"], + "grid": 0 + }, + "attrs": [ + { "fill": "rgb(108, 114, 122)" }, + { "fill": "rgb(108, 114, 122)" }, + { "fill": "rgb(108, 114, 122)" }, + { "fill": "rgb(108, 114, 122)" } + ], + "properties": { "order": 230, "id": 85, "name": "emoji", "prevSize": 32, "code": 59727 }, + "setIdx": 0, + "setId": 4, + "iconIdx": 91 + }, + { + "icon": { + "paths": [ + "M655.744 512c88.365 0 160-71.635 160-160s-71.635-160-160-160c-88.368 0-160 71.635-160 160 0 26.934 6.653 52.314 18.41 74.582l-11.914 11.914 0.128 0.131-299.135 299.136c-3.533 9.926-6.41 20.976-7.738 31.606-1.652 13.219-0.563 22.966 1.874 28.938 1.768 4.333 4.22 7.232 10.772 9.014 8.316 2.262 24.518 2.794 52.822-5.501 8.524-3.808 27.721-16.285 45.132-35.382 18.18-19.939 29.648-41.856 29.648-62.438 0-15.642 11.309-28.992 26.736-31.565l91.75-15.293c3.766-2.656 18.768-15.693 10.134-58.867-2.099-10.49 1.184-21.338 8.749-28.902l88.198-88.195c26.467 19.379 59.114 30.822 94.432 30.822zM439.571 410.915c-5.104-18.771-7.827-38.525-7.827-58.915 0-123.712 100.288-224 224-224 123.709 0 224 100.288 224 224s-100.291 224-224 224c-29.29 0-57.264-5.619-82.906-15.843l-43.008 43.008c7.158 65.494-23.99 104.534-55.968 115.194l-2.384 0.794-74.854 12.477c-7.085 31.405-25.174 58.122-43.235 77.933-23.123 25.357-50.958 44.627-69.762 52.15l-1.324 0.528-1.365 0.41c-34.714 10.416-64.73 13.19-89.594 6.429-26.782-7.286-44.33-24.784-53.228-46.586-8.23-20.163-8.473-42.282-6.126-61.062 2.402-19.216 7.899-37.958 14.042-53.315 1.61-4.022 4.020-7.677 7.084-10.742l286.456-286.458zM623.744 336c0 26.509 21.488 48 48 48 26.509 0 48-21.491 48-48s-21.491-48-48-48c-26.512 0-48 21.49-48 48z" + ], + "width": 1056, + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "isMulticolor": false, + "isMulticolor2": false, + "colorPermutations": { + "1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101": [ + { "f": 2 } + ] + }, + "tags": ["encrypted"], + "grid": 0 + }, + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "properties": { "order": 231, "id": 86, "name": "encrypted", "prevSize": 32, "code": 59728 }, + "setIdx": 0, + "setId": 4, + "iconIdx": 92 + }, + { + "icon": { + "paths": [ + "M536.89 64c233.677 0 423.11 189.433 423.11 423.11h-423.11v-423.11z", + "M64 536.89c0-233.678 189.433-423.112 423.11-423.112v423.112h423.11c0 233.677-189.43 423.11-423.11 423.11-233.677 0-423.11-189.434-423.11-423.11z" + ], + "attrs": [{ "fill": "rgb(108, 114, 122)" }, { "fill": "rgb(108, 114, 122)" }], + "isMulticolor": false, + "isMulticolor2": false, + "colorPermutations": { + "1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101": [ + { "f": 2 }, + { "f": 2 } + ] + }, + "tags": ["engagement-dashboard"], + "grid": 0 + }, + "attrs": [{ "fill": "rgb(108, 114, 122)" }, { "fill": "rgb(108, 114, 122)" }], + "properties": { "order": 232, "id": 87, "name": "engagement-dashboard", "prevSize": 32, "code": 59729 }, + "setIdx": 0, + "setId": 4, + "iconIdx": 93 + }, + { + "icon": { + "paths": [ + "M416 586.445v177.232l75.514-62.928c11.869-9.888 29.104-9.888 40.973 0l75.514 62.928v-177.232c-29.098 13.821-61.645 21.555-96 21.555s-66.902-7.734-96-21.555zM352 540.768v291.232c0 12.416 7.184 23.712 18.426 28.979 11.245 5.267 24.522 3.552 34.061-4.397l107.514-89.594 107.514 89.594c9.539 7.949 22.816 9.664 34.061 4.397 11.242-5.267 18.426-16.563 18.426-28.979v-291.232c39.59-40.403 64-95.734 64-156.768 0-123.712-100.288-224.001-224-224.001s-224 100.288-224 224.001c0 61.034 24.41 116.365 64 156.768zM416 512.013l-0.016-0.013c-38.854-29.19-63.984-75.661-63.984-128 0-88.366 71.635-160.001 160-160.001s160 71.635 160 160.001c0 52.339-25.13 98.81-63.984 128l-0.016 0.013c-26.742 20.083-59.981 31.987-96 31.987s-69.258-11.904-96-31.987z" + ], + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "isMulticolor": false, + "isMulticolor2": false, + "colorPermutations": { + "1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101": [ + { "f": 2 } + ] + }, + "tags": ["enterprise-feature"], + "grid": 0 + }, + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "properties": { "order": 233, "id": 88, "name": "enterprise-feature", "prevSize": 32, "code": 59730 }, + "setIdx": 0, + "setId": 4, + "iconIdx": 94 + }, + { + "icon": { + "paths": [ + "M500.64 128c213.35 0 386.336 172.985 386.336 386.336 0 192.87-141.254 352.707-325.974 381.664v-269.958h90.022l17.114-111.706h-107.136v-72.47c0-11.462 2.102-22.822 7.274-32.544 2.474-4.656 5.651-8.934 9.635-12.666 9.866-9.245 24.688-15.152 46.058-15.152h48.733v-95.080c0 0-44.224-7.552-86.493-7.552-84.698 0-141.258 49.313-145.651 138.907-0.182 3.738-0.275 7.546-0.275 11.424v85.133h-98.122v111.706h98.122v269.958c-184.721-29.011-325.978-188.848-325.978-381.664 0-213.351 172.985-386.336 386.336-386.336z" + ], + "width": 1056, + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "isMulticolor": false, + "isMulticolor2": false, + "colorPermutations": { + "1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101": [ + { "f": 2 } + ] + }, + "tags": ["facebook-monochromatic"], + "grid": 0 + }, + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "properties": { "order": 30, "id": 89, "name": "facebook-monochromatic", "prevSize": 32, "code": 59731 }, + "setIdx": 0, + "setId": 4, + "iconIdx": 95 + }, + { + "icon": { + "paths": [ + "M373.334 448c0-17.674 14.326-32 32-32h213.331c17.674 0 32 14.326 32 32s-14.326 32-32 32h-213.331c-17.674 0-32-14.326-32-32z", + "M405.334 544c-17.674 0-32 14.326-32 32s14.326 32 32 32h213.331c17.674 0 32-14.326 32-32s-14.326-32-32-32h-213.331z", + "M256 128c-17.673 0-32 14.327-32 32v704c0 17.674 14.327 32 32 32h512c17.674 0 32-14.326 32-32v-501.744c0-6.672-2.083-13.174-5.962-18.602l-144.467-202.254c-6.006-8.409-15.706-13.4-26.038-13.4h-367.533zM736 372.509v459.491h-448v-640h319.066l128.934 180.509z" + ], + "attrs": [{ "fill": "rgb(108, 114, 122)" }, { "fill": "rgb(108, 114, 122)" }, { "fill": "rgb(108, 114, 122)" }], + "isMulticolor": false, + "isMulticolor2": false, + "colorPermutations": { + "1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101": [ + { "f": 2 }, + { "f": 2 }, + { "f": 2 } + ] + }, + "tags": ["file-document"], + "grid": 0 + }, + "attrs": [{ "fill": "rgb(108, 114, 122)" }, { "fill": "rgb(108, 114, 122)" }, { "fill": "rgb(108, 114, 122)" }], + "properties": { "order": 29, "id": 90, "name": "file-document", "prevSize": 32, "code": 59732 }, + "setIdx": 0, + "setId": 4, + "iconIdx": 96 + }, + { + "icon": { + "paths": [ + "M128 192c-17.673 0-32 14.327-32 32v576.019c0 17.674 14.327 32 32 32h768c17.674 0 32-14.326 32-32v-576.019c0-17.673-14.326-32-32-32h-768zM160 378.301v-122.301h149.333v122.301h-149.333zM160 442.301h149.333v141.722h-149.333v-141.722zM160 648.022h149.333v119.997h-149.333v-119.997zM373.334 768.019v-119.997h490.666v119.997h-490.666zM864 584.022h-490.666v-141.722h490.666v141.722zM864 378.301h-490.666v-122.301h490.666v122.301z" + ], + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "isMulticolor": false, + "isMulticolor2": false, + "colorPermutations": { + "1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101": [ + { "f": 2 } + ] + }, + "tags": ["file-sheet"], + "grid": 0 + }, + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "properties": { "order": 28, "id": 91, "name": "file-sheet", "prevSize": 32, "code": 59733 }, + "setIdx": 0, + "setId": 4, + "iconIdx": 97 + }, + { + "icon": { + "paths": [ + "M206.667 293.692c-30.421-42.402-0.116-101.442 52.070-101.442h518.572c51.174 0 81.706 57.026 53.334 99.615l-180.678 271.207v220.762c0 46.352-47.693 77.373-90.067 58.582l-121.424-53.85c-23.168-10.275-38.102-33.238-38.102-58.582v-166.304l-193.704-269.988zM777.309 256.334h-518.572l193.705 269.989c7.811 10.89 12.013 23.955 12.013 37.357v166.304l121.424 53.85v-220.762c0-12.646 3.741-25.008 10.752-35.533l180.678-271.205z" + ], + "width": 1056, + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "isMulticolor": false, + "isMulticolor2": false, + "colorPermutations": { + "1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101": [ + { "f": 2 } + ] + }, + "tags": ["filter"], + "grid": 0 + }, + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "properties": { "order": 27, "id": 92, "name": "filter", "prevSize": 32, "code": 59734 }, + "setIdx": 0, + "setId": 4, + "iconIdx": 98 + }, + { + "icon": { + "paths": [ + "M623.091 896c-2.806 0-5.296-0.624-7.168-0.938-62-16.858-102.816-39.648-145.811-80.858-55.149-54.010-85.37-125.814-85.37-202.301 0-65.875 56.704-119.571 126.496-119.571s126.496 53.696 126.496 119.571c0 34.963 31.469 63.373 69.792 63.373s69.789-28.41 69.789-63.373c0-135.808-119.642-246.637-266.701-246.637-104.998 0-200.338 57.133-243.334 145.795-14.332 29.347-21.498 63.066-21.498 100.842 0 28.72 2.492 73.363 24.925 131.744 2.804 7.181 2.492 14.675-0.623 21.856-3.116 6.867-9.035 11.862-16.201 14.358-2.804 1.251-6.231 1.562-9.659 1.562-11.84 0-22.433-7.181-26.484-18.106-19.005-50.266-28.353-99.904-28.353-151.728 0-46.205 9.036-88.352 26.795-125.504 52.343-108.019 167.936-177.638 294.432-177.638 178.528 0 323.718 135.804 323.718 302.828 0 65.875-56.704 119.571-126.806 119.571s-126.81-53.696-126.81-119.571c0-34.963-31.469-63.373-69.792-63.373-38.634 0-69.789 28.41-69.789 63.373 0 61.504 24.301 119.261 68.544 162.342 35.206 34.029 68.858 53.072 120.576 67.123 7.168 1.872 13.398 6.557 17.136 13.11 3.741 6.557 4.675 14.362 2.806 21.229-2.806 11.866-14.022 20.918-27.107 20.918zM426.803 888.195c-7.789 0-15.267-3.123-20.253-8.742-33.338-32.781-51.718-54.010-77.891-100.525-26.795-47.142-41.127-105.21-41.127-167.338 0-116.448 100.948-211.357 224.951-211.357s224.954 94.909 224.954 211.357c0 15.61-12.464 28.096-28.352 28.096-15.891 0-28.666-12.173-28.666-28.096 0-85.542-75.398-155.162-168.246-155.162s-168.246 69.619-168.246 155.162c0 52.448 11.84 100.528 34.272 139.552 23.366 41.52 38.947 59.005 68.858 88.662 10.902 11.238 10.902 28.723 0 39.648-5.92 5.933-13.088 8.742-20.253 8.742zM699.738 818.886c-47.36 0-88.797-11.862-123.382-34.963-59.197-39.651-94.717-103.962-94.717-172.333 0-15.61 12.464-28.099 28.352-28.099 15.891 0 28.355 12.49 28.355 28.099 0 49.638 26.17 96.781 69.789 125.501 25.238 16.861 55.149 24.976 91.603 24.976 7.789 0 22.432-0.934 38.010-3.744 1.558-0.314 3.427-0.314 4.986-0.314 13.709 0 25.238 9.99 27.728 23.414 1.248 7.181-0.31 14.672-4.362 20.605-4.362 6.243-10.902 10.614-18.694 11.862-23.366 4.685-43.93 4.995-47.667 4.995zM188.765 435.824c-5.608 0-11.217-1.562-16.202-4.995-6.543-4.058-10.593-10.614-12.151-18.106-1.246-7.494 0.311-14.986 4.985-21.232 38.635-53.696 87.862-95.843 146.125-125.501 60.132-30.595 129.613-46.829 200.961-46.829 71.037 0 140.205 15.922 200.029 46.205 58.573 29.659 107.802 71.492 146.125 124.567 4.362 5.93 6.23 13.424 4.986 20.915-1.248 7.494-5.61 14.048-11.84 18.419-4.986 3.437-10.595 4.995-16.515 4.995-9.034 0-17.757-4.371-23.056-11.862-33.338-45.891-75.709-82.109-125.562-107.083-52.342-26.224-112.787-40.273-174.477-40.273-62.314 0-122.758 14.049-175.101 40.585-49.852 25.913-92.537 62.128-126.186 108.643-3.739 6.87-12.463 11.552-22.121 11.552zM733.696 239.141c-4.672 0-9.347-1.249-13.395-3.434-71.35-35.902-133.354-51.512-207.504-51.512-74.467 0-144.256 17.483-207.817 51.824-4.050 2.185-8.724 3.122-13.397 3.122-10.282 0-19.629-5.62-24.925-14.361-3.739-6.556-4.673-14.361-2.492-21.541s7.166-13.424 13.709-16.859c72.594-38.712 151.733-58.38 234.924-58.38 82.563 0 154.848 17.795 233.987 58.068 6.854 3.434 11.84 9.366 14.33 16.859 2.182 7.18 1.248 14.673-2.179 21.229-4.986 9.054-14.643 14.985-25.238 14.985z" + ], + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "isMulticolor": false, + "isMulticolor2": false, + "colorPermutations": { + "1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101": [ + { "f": 2 } + ] + }, + "tags": ["fingerprint"], + "grid": 0 + }, + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "properties": { "order": 26, "id": 93, "name": "fingerprint", "prevSize": 32, "code": 59735 }, + "setIdx": 0, + "setId": 4, + "iconIdx": 99 + }, + { + "icon": { + "paths": [ + "M266.667 170.664c0-17.673 14.327-32 32-32h500.623c12.122 0 23.2 6.848 28.621 17.689s4.253 23.814-3.021 33.511l-122.134 162.843 122.134 162.845c7.274 9.696 8.442 22.672 3.021 33.51-5.421 10.842-16.499 17.69-28.621 17.69h-468.624v286.579c0 17.674-14.325 32-31.999 32s-32-14.326-32-32v-682.667zM330.666 502.752h404.624l-98.134-130.845c-8.531-11.376-8.531-27.021 0-38.4l98.134-130.843h-404.624v300.088z" + ], + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "isMulticolor": false, + "isMulticolor2": false, + "colorPermutations": { + "1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101": [ + { "f": 2 } + ] + }, + "tags": ["flag"], + "grid": 0 + }, + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "properties": { "order": 25, "id": 94, "name": "flag", "prevSize": 32, "code": 59736 }, + "setIdx": 0, + "setId": 4, + "iconIdx": 100 + }, + { + "icon": { + "paths": [ + "M140.020 213.336c0-17.673 14.327-32 32-32h234.057c7.181 0 14.157 2.416 19.798 6.86l88.813 69.94h339.997c17.674 0 32 14.327 32 32v520.533c0 17.674-14.326 32-32 32h-682.665c-17.673 0-32-14.326-32-32v-597.333zM204.020 245.336v533.333h618.665v-456.534h-319.085c-7.181 0-14.154-2.415-19.798-6.858l-88.813-69.94h-190.969z" + ], + "width": 1056, + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "isMulticolor": false, + "isMulticolor2": false, + "colorPermutations": { + "1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101": [ + { "f": 2 } + ] + }, + "tags": ["folder"], + "grid": 0 + }, + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "properties": { "order": 24, "id": 95, "name": "folder", "prevSize": 32, "code": 59737 }, + "setIdx": 0, + "setId": 4, + "iconIdx": 101 + }, + { + "icon": { + "paths": [ + "M370.704 544c0-17.674-14.326-32-32-32s-31.999 14.326-31.999 32v32h-32c-17.673 0-32 14.326-32 32s14.327 32 32 32h32v32c0 17.674 14.325 32 31.999 32s32-14.326 32-32v-32h32c17.674 0 32-14.326 32-32s-14.326-32-32-32h-32v-32z", + "M746.704 624c30.928 0 56-25.072 56-56s-25.072-56-56-56c-30.928 0-56 25.072-56 56s25.072 56 56 56z", + "M674.704 664c0 30.928-25.072 56-56 56s-56-25.072-56-56c0-30.928 25.072-56 56-56s56 25.072 56 56z", + "M706.704 128c0-17.673-14.326-32-32-32s-32 14.327-32 32v96h-128c-17.674 0-32 14.327-32 32v96h-191.999c-106.038 0-192 85.962-192 192v128c0 106.038 85.961 192 192 192h447.999c106.038 0 192-85.962 192-192v-128c0-106.038-85.962-192-192-192h-192v-64h128c17.674 0 32-14.327 32-32v-128zM866.704 544v128c0 70.691-57.306 128-128 128h-447.999c-70.692 0-128-57.309-128-128v-128c0-70.691 57.308-128 128-128h447.999c70.694 0 128 57.309 128 128z" + ], + "width": 1056, + "attrs": [ + { "fill": "rgb(108, 114, 122)" }, + { "fill": "rgb(108, 114, 122)" }, + { "fill": "rgb(108, 114, 122)" }, + { "fill": "rgb(108, 114, 122)" } + ], + "isMulticolor": false, + "isMulticolor2": false, + "colorPermutations": { + "1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101": [ + { "f": 2 }, + { "f": 2 }, + { "f": 2 }, + { "f": 2 } + ] + }, + "tags": ["game"], + "grid": 0 + }, + "attrs": [ + { "fill": "rgb(108, 114, 122)" }, + { "fill": "rgb(108, 114, 122)" }, + { "fill": "rgb(108, 114, 122)" }, + { "fill": "rgb(108, 114, 122)" } + ], + "properties": { "order": 23, "id": 96, "name": "game", "prevSize": 32, "code": 59738 }, + "setIdx": 0, + "setId": 4, + "iconIdx": 102 + }, + { + "icon": { + "paths": [ + "M494.31 170.648l23.037 24.005 23.629-24.005 74.669 0.041v75.854h74.669v75.856h74.666v26.518h0.003v504.419h-522.678v-682.689h252.006zM540.976 246.503h-224.004v530.979h373.341v-379.229h-149.338v-151.75z" + ], + "width": 1056, + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "isMulticolor": false, + "isMulticolor2": false, + "colorPermutations": { + "1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101": [ + { "f": 2 } + ] + }, + "tags": ["giphy-monochromatic"], + "grid": 0 + }, + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "properties": { "order": 21, "id": 97, "name": "giphy-monochromatic", "prevSize": 32, "code": 59739 }, + "setIdx": 0, + "setId": 4, + "iconIdx": 103 + }, + { + "icon": { + "paths": [ + "M634.803 170.664h-256.112l-264.387 460.802 124.984 221.866h534.916l124.982-221.866-264.384-460.802zM367.814 631.466l138.931-239.789 138.934 239.789h-277.866z" + ], + "width": 1056, + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "isMulticolor": false, + "isMulticolor2": false, + "colorPermutations": { + "1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101": [ + { "f": 2 } + ] + }, + "tags": ["google-drive-monochromatic"], + "grid": 0 + }, + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "properties": { "order": 19, "id": 98, "name": "google-drive-monochromatic", "prevSize": 32, "code": 59756 }, + "setIdx": 0, + "setId": 4, + "iconIdx": 104 + }, + { + "icon": { + "paths": [ + "M129.354 272c0-17.673 14.327-32 32-32h704c17.674 0 32 14.327 32 32s-14.326 32-32 32h-704c-17.673 0-32-14.327-32-32zM289.354 432c0 17.674-14.327 32-32 32s-32-14.326-32-32c0-17.674 14.327-32 32-32s32 14.326 32 32zM289.354 752c0 17.674-14.327 32-32 32s-32-14.326-32-32c0-17.674 14.327-32 32-32s32 14.326 32 32zM449.354 624c17.674 0 32-14.326 32-32s-14.326-32-32-32c-17.674 0-32 14.326-32 32s14.326 32 32 32zM385.354 400c-17.674 0-32 14.326-32 32s14.326 32 32 32h480c17.674 0 32-14.326 32-32s-14.326-32-32-32h-480zM353.354 752c0-17.674 14.326-32 32-32h480c17.674 0 32 14.326 32 32s-14.326 32-32 32h-480c-17.674 0-32-14.326-32-32zM577.354 560c-17.674 0-32 14.326-32 32s14.326 32 32 32h288c17.674 0 32-14.326 32-32s-14.326-32-32-32h-288z" + ], + "width": 1056, + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "isMulticolor": false, + "isMulticolor2": false, + "colorPermutations": { + "1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101": [ + { "f": 2 } + ] + }, + "tags": ["group-by-type"], + "grid": 0 + }, + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "properties": { "order": 150, "id": 99, "name": "group-by-type", "prevSize": 32, "code": 59757 }, + "setIdx": 0, + "setId": 4, + "iconIdx": 105 + }, + { + "icon": { + "paths": [ + "M170.668 245.336c0-17.673 14.327-32 32-32h640.846c17.674 0 32 14.327 32 32s-14.326 32-32 32h-640.846c-17.673 0-32-14.327-32-32zM170.668 501.334c0-17.67 14.327-32 32-32h640.846c17.674 0 32 14.33 32 32 0 17.674-14.326 32-32 32h-640.846c-17.673 0-32-14.326-32-32zM170.668 757.334c0-17.67 14.327-32 32-32h640.846c17.674 0 32 14.33 32 32 0 17.674-14.326 32-32 32h-640.846c-17.673 0-32-14.326-32-32z" + ], + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "isMulticolor": false, + "isMulticolor2": false, + "colorPermutations": { + "1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101": [ + { "f": 2 } + ] + }, + "tags": ["hamburguer"], + "grid": 0 + }, + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "properties": { "order": 148, "id": 100, "name": "hamburguer", "prevSize": 32, "code": 59758 }, + "setIdx": 0, + "setId": 4, + "iconIdx": 106 + }, + { + "icon": { + "paths": [ + "M832 512c0 176.73-143.27 320-320 320s-320-143.27-320-320h-64c0 212.077 171.923 384 384 384s384-171.923 384-384c0-212.077-171.923-384-384-384-123.718 0-233.772 58.508-304 149.364v-101.364c0-17.673-14.327-32-32-32s-32 14.327-32 32v192c0 17.674 14.327 32 32 32h176c17.674 0 32-14.326 32-32s-14.326-32-32-32h-107.295c57.24-86.756 155.583-144 267.295-144 176.73 0 320 143.27 320 320z", + "M544 320c0-17.673-14.326-32-32-32s-32 14.327-32 32v224c0 8.486 3.373 16.627 9.373 22.627l96 96c12.496 12.496 32.758 12.496 45.254 0s12.496-32.758 0-45.254l-86.627-86.627v-210.746z" + ], + "attrs": [{ "fill": "rgb(108, 114, 122)" }, { "fill": "rgb(108, 114, 122)" }], + "isMulticolor": false, + "isMulticolor2": false, + "colorPermutations": { + "1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101": [ + { "f": 2 }, + { "f": 2 } + ] + }, + "tags": ["history"], + "grid": 0 + }, + "attrs": [{ "fill": "rgb(108, 114, 122)" }, { "fill": "rgb(108, 114, 122)" }], + "properties": { "order": 145, "id": 101, "name": "history", "prevSize": 32, "code": 59759 }, + "setIdx": 0, + "setId": 4, + "iconIdx": 107 + }, + { + "icon": { + "paths": [ + "M522.042 195.354l224.464 260.044v366.234c0 5.856-4.752 10.605-10.614 10.605h-136.416v-149.821c0-23.424-19.014-42.413-42.47-42.413h-85.955c-23.453 0-42.467 18.989-42.467 42.413v149.821h-136.41c-5.864 0-10.617-4.749-10.617-10.605v-366.307l224.403-259.971c4.237-4.907 11.85-4.907 16.083 0zM560.57 895.856h175.322c41.043 0 74.32-33.232 74.32-74.224v-244.205h56.227c12.454 0 23.766-7.251 28.954-18.557 5.19-11.309 3.302-24.602-4.829-34.022l-320.272-371.033c-29.648-34.347-82.934-34.347-112.582 0l-320.27 371.033c-8.132 9.421-10.020 22.714-4.831 34.022 5.188 11.306 16.501 18.557 28.956 18.557h56.288v244.205c0 40.992 33.274 74.224 74.32 74.224h175.316c1.174 0.096 2.362 0.147 3.562 0.147h85.955c1.2 0 2.39-0.051 3.565-0.147z" + ], + "width": 1056, + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "isMulticolor": false, + "isMulticolor2": false, + "colorPermutations": { + "1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101": [ + { "f": 2 } + ] + }, + "tags": ["home"], + "grid": 0 + }, + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "properties": { "order": 146, "id": 102, "name": "home", "prevSize": 32, "code": 59760 }, + "setIdx": 0, + "setId": 4, + "iconIdx": 108 + }, + { + "icon": { + "paths": [ + "M406.685 405.334c0 47.126-38.205 85.331-85.334 85.331-47.127 0-85.332-38.205-85.332-85.331 0-47.13 38.205-85.334 85.332-85.334 47.13 0 85.334 38.205 85.334 85.334z", + "M97.352 192c0-17.673 14.327-32 32-32h767.999c17.674 0 32 14.327 32 32v640c0 17.674-14.326 32-32 32h-767.999c-17.673 0-32-14.326-32-32v-640zM161.352 764.163l151.704-176.989c9.271-10.813 24.57-14.202 37.536-8.307l153.123 69.603 160.474-204.24c6.010-7.651 15.174-12.15 24.902-12.23s18.963 4.272 25.098 11.821l151.162 186.048v-405.869h-703.999v540.163zM214.927 800h650.423v-68.64l-175.581-216.099-166.781 212.269-176.989-80.448-131.073 152.918z" + ], + "width": 1056, + "attrs": [{ "fill": "rgb(108, 114, 122)" }, { "fill": "rgb(108, 114, 122)" }], + "isMulticolor": false, + "isMulticolor2": false, + "colorPermutations": { + "1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101": [ + { "f": 2 }, + { "f": 2 } + ] + }, + "tags": ["image"], + "grid": 0 + }, + "attrs": [{ "fill": "rgb(108, 114, 122)" }, { "fill": "rgb(108, 114, 122)" }], + "properties": { "order": 147, "id": 103, "name": "image", "prevSize": 32, "code": 59761 }, + "setIdx": 0, + "setId": 4, + "iconIdx": 109 + }, + { + "icon": { + "paths": [ + "M512 873.027c-199.389 0-361.026-161.635-361.026-361.024s161.636-361.026 361.026-361.026c199.389 0 361.024 161.637 361.024 361.026s-161.635 361.024-361.024 361.024zM512 938.669c235.642 0 426.666-191.024 426.666-426.666s-191.024-426.667-426.666-426.667c-235.642 0-426.667 191.025-426.667 426.667s191.025 426.666 426.667 426.666zM544.819 347.901c0 18.125-14.694 32.819-32.819 32.819-18.128 0-32.822-14.694-32.822-32.819 0-18.128 14.694-32.821 32.822-32.821 18.125 0 32.819 14.693 32.819 32.821zM512 413.542c-18.128 0-32.822 14.694-32.822 32.819v229.744c0 18.125 14.694 32.819 32.822 32.819 18.125 0 32.819-14.694 32.819-32.819v-229.744c0-18.125-14.694-32.819-32.819-32.819z" + ], + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "isMulticolor": false, + "isMulticolor2": false, + "colorPermutations": { + "1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101": [ + { "f": 2 } + ] + }, + "tags": ["info"], + "grid": 0 + }, + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "properties": { "order": 144, "id": 104, "name": "info", "prevSize": 32, "code": 59762 }, + "setIdx": 0, + "setId": 4, + "iconIdx": 110 + }, + { + "icon": { + "paths": [ + "M512 864c-194.404 0-352-157.597-352-352s157.596-352 352-352c194.403 0 352 157.596 352 352s-157.597 352-352 352zM512 928c229.75 0 416-186.25 416-416s-186.25-416-416-416c-229.75 0-416 186.25-416 416s186.25 416 416 416zM662.627 361.373c12.496 12.496 12.496 32.758 0 45.254l-105.373 105.373 105.373 105.373c12.496 12.496 12.496 32.758 0 45.254s-32.758 12.496-45.254 0l-105.373-105.373-105.373 105.373c-12.496 12.496-32.758 12.496-45.254 0s-12.496-32.758 0-45.254l105.373-105.373-105.373-105.373c-12.496-12.496-12.496-32.758 0-45.254s32.758-12.496 45.254 0l105.373 105.373 105.373-105.373c12.496-12.496 32.758-12.496 45.254 0z" + ], + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "isMulticolor": false, + "isMulticolor2": false, + "colorPermutations": { + "1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101": [ + { "f": 2 } + ] + }, + "tags": ["input-clear"], + "grid": 0 + }, + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "properties": { "order": 143, "id": 105, "name": "input-clear", "prevSize": 32, "code": 59763 }, + "setIdx": 0, + "setId": 4, + "iconIdx": 111 + }, + { + "icon": { + "paths": [ + "M384 288c-17.674 0-32 14.327-32 32s14.326 32 32 32h256c17.674 0 32-14.326 32-32s-14.326-32-32-32h-256z", + "M352 448c0-17.674 14.326-32 32-32h256c17.674 0 32 14.326 32 32s-14.326 32-32 32h-256c-17.674 0-32-14.326-32-32z", + "M512 640c-17.674 0-32 14.326-32 32s14.326 32 32 32c17.674 0 32-14.326 32-32s-14.326-32-32-32z", + "M224 224v576c0 35.347 28.654 64 64 64h448c35.347 0 64-28.653 64-64v-576c0-35.346-28.653-64-64-64h-448c-35.346 0-64 28.654-64 64zM288 800v-576h448v576h-448z" + ], + "attrs": [ + { "fill": "rgb(108, 114, 122)" }, + { "fill": "rgb(108, 114, 122)" }, + { "fill": "rgb(108, 114, 122)" }, + { "fill": "rgb(108, 114, 122)" } + ], + "isMulticolor": false, + "isMulticolor2": false, + "colorPermutations": { + "1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101": [ + { "f": 2 }, + { "f": 2 }, + { "f": 2 }, + { "f": 2 } + ] + }, + "tags": ["instance"], + "grid": 0 + }, + "attrs": [ + { "fill": "rgb(108, 114, 122)" }, + { "fill": "rgb(108, 114, 122)" }, + { "fill": "rgb(108, 114, 122)" }, + { "fill": "rgb(108, 114, 122)" } + ], + "properties": { "order": 18, "id": 106, "name": "instance", "prevSize": 32, "code": 59764 }, + "setIdx": 0, + "setId": 4, + "iconIdx": 112 + }, + { + "icon": { + "paths": [ + "M544 352c17.674 0 32-14.326 32-32s-14.326-32-32-32c-17.674 0-32 14.327-32 32s14.326 32 32 32zM559.61 436.995c2.755-17.456-9.162-33.843-26.618-36.598-17.456-2.758-33.843 9.162-36.598 26.618l-48 304c-2.758 17.456 9.158 33.843 26.618 36.598 17.456 2.755 33.84-9.162 36.598-26.618l48-304z" + ], + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "isMulticolor": false, + "isMulticolor2": false, + "colorPermutations": { + "1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101": [ + { "f": 2 } + ] + }, + "tags": ["italic"], + "grid": 0 + }, + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "properties": { "order": 17, "id": 107, "name": "italic", "prevSize": 32, "code": 59765 }, + "setIdx": 0, + "setId": 4, + "iconIdx": 113 + }, + { + "icon": { + "paths": [ + "M497.136 169.372c12.499 12.497 12.499 32.758 0 45.255l-35.958 35.96c1.078-0.016 2.163-0.025 3.245-0.025h191.152c115.11 0 208.426 93.316 208.426 208.426 0 115.107-93.315 208.422-208.426 208.422h-15.574v-64h15.574c79.763 0 144.426-64.659 144.426-144.422 0-79.766-64.662-144.426-144.426-144.426h-191.152c-1.030 0-2.058 0.011-3.082 0.032l35.795 35.796c12.499 12.499 12.499 32.758 0 45.258-12.496 12.496-32.758 12.496-45.254 0l-90.509-90.511c-12.496-12.497-12.496-32.758 0-45.255l90.509-90.51c12.496-12.497 32.758-12.497 45.254 0zM201.318 500.746h56.159v325.818h-59.023v-268.387h-1.909l-76.204 48.682v-54.090l80.977-52.022zM570.41 719.494c0 64.749-48.045 111.523-116.931 111.523-63.638 0-110.886-38.979-112.48-93.069h57.274c2.070 26.726 25.773 45.341 55.206 45.341 34.838 0 59.978-25.933 59.818-62.365 0.16-36.909-25.613-63.318-61.411-63.475-19.568-0.16-40.25 7.955-51.226 20.045l-53.296-8.749 17.024-168h188.998v49.318h-140.16l-9.386 86.384h1.91c12.090-14.317 35.475-24.816 61.885-24.816 59.184 0 102.774 45.181 102.774 107.862z" + ], + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "isMulticolor": false, + "isMulticolor2": false, + "colorPermutations": { + "1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101": [ + { "f": 2 } + ] + }, + "tags": ["jump-backward"], + "grid": 0 + }, + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "properties": { "order": 16, "id": 108, "name": "jump-backward", "prevSize": 32, "code": 59766 }, + "setIdx": 0, + "setId": 4, + "iconIdx": 114 + }, + { + "icon": { + "paths": [ + "M494.861 169.372c-12.496 12.497-12.496 32.758 0 45.255l35.962 35.96c-1.082-0.016-2.163-0.025-3.248-0.025h-191.149c-115.111 0-208.426 93.316-208.426 208.426 0 115.107 93.315 208.422 208.426 208.422h15.574v-64h-15.574c-79.764 0-144.426-64.659-144.426-144.422 0-79.766 64.661-144.426 144.426-144.426h191.149c1.030 0 2.061 0.011 3.085 0.032l-35.798 35.796c-12.496 12.499-12.496 32.758 0 45.258 12.499 12.496 32.758 12.496 45.258 0l90.509-90.511c12.496-12.497 12.496-32.758 0-45.255l-90.509-90.51c-12.499-12.497-32.758-12.497-45.258 0zM521.318 500.746h56.157v325.818h-59.021v-268.387h-1.91l-76.205 48.682v-54.090l80.979-52.022zM890.41 719.494c0 64.749-48.048 111.523-116.934 111.523-63.635 0-110.886-38.979-112.477-93.069h57.274c2.067 26.726 25.773 45.341 55.203 45.341 34.842 0 59.978-25.933 59.821-62.365 0.157-36.909-25.616-63.318-61.411-63.475-19.568-0.16-40.25 7.955-51.226 20.045l-53.296-8.749 17.021-168h189.002v49.318h-140.16l-9.386 86.384h1.91c12.090-14.317 35.475-24.816 61.885-24.816 59.181 0 102.774 45.181 102.774 107.862z" + ], + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "isMulticolor": false, + "isMulticolor2": false, + "colorPermutations": { + "1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101": [ + { "f": 2 } + ] + }, + "tags": ["jump-forward"], + "grid": 0 + }, + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "properties": { "order": 15, "id": 109, "name": "jump-forward", "prevSize": 32, "code": 59767 }, + "setIdx": 0, + "setId": 4, + "iconIdx": 115 + }, + { + "icon": { + "paths": [ + "M769.296 649.373c12.496 12.496 12.496 32.758 0 45.254l-192 192c-12.499 12.496-32.758 12.496-45.258 0l-192-192c-12.496-12.496-12.496-32.758 0-45.254 12.499-12.496 32.758-12.496 45.258 0l137.37 137.373v-578.746h-192v192c0 17.674-14.325 32-31.999 32s-32-14.326-32-32v-224c0-17.673 14.327-32 32-32h255.999c17.674 0 32 14.327 32 32v610.746l137.373-137.373c12.499-12.496 32.758-12.496 45.258 0z" + ], + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "isMulticolor": false, + "isMulticolor2": false, + "colorPermutations": { + "1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101": [ + { "f": 2 } + ] + }, + "tags": ["jump-to-message"], + "grid": 0 + }, + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "properties": { "order": 14, "id": 110, "name": "jump-to-message", "prevSize": 32, "code": 59768 }, + "setIdx": 0, + "setId": 4, + "iconIdx": 116 + }, + { + "icon": { + "paths": [ + "M576 256c0 35.346-28.653 64-64 64s-64-28.654-64-64c0-35.346 28.653-64 64-64s64 28.654 64 64z", + "M576 512c0 35.347-28.653 64-64 64s-64-28.653-64-64c0-35.347 28.653-64 64-64s64 28.653 64 64z", + "M576 768c0 35.347-28.653 64-64 64s-64-28.653-64-64c0-35.347 28.653-64 64-64s64 28.653 64 64z" + ], + "attrs": [{ "fill": "rgb(108, 114, 122)" }, { "fill": "rgb(108, 114, 122)" }, { "fill": "rgb(108, 114, 122)" }], + "isMulticolor": false, + "isMulticolor2": false, + "colorPermutations": { + "1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101": [ + { "f": 2 }, + { "f": 2 }, + { "f": 2 } + ] + }, + "tags": ["kebab"], + "grid": 0 + }, + "attrs": [{ "fill": "rgb(108, 114, 122)" }, { "fill": "rgb(108, 114, 122)" }, { "fill": "rgb(108, 114, 122)" }], + "properties": { "order": 13, "id": 111, "name": "kebab", "prevSize": 32, "code": 59769 }, + "setIdx": 0, + "setId": 4, + "iconIdx": 117 + }, + { + "icon": { + "paths": [ + "M160 224c-35.346 0-64 28.654-64 64v448c0 35.347 28.654 64 64 64h704c35.347 0 64-28.653 64-64v-448c0-35.346-28.653-64-64-64h-704zM160 288h704v448h-704v-448zM256 352c-17.673 0-32 14.326-32 32s14.327 32 32 32h64c17.674 0 32-14.326 32-32s-14.326-32-32-32h-64zM256 512c0-17.674 14.327-32 32-32h64c17.674 0 32 14.326 32 32s-14.326 32-32 32h-64c-17.673 0-32-14.326-32-32zM480 480c-17.674 0-32 14.326-32 32s14.326 32 32 32h64c17.674 0 32-14.326 32-32s-14.326-32-32-32h-64zM640 512c0-17.674 14.326-32 32-32h64c17.674 0 32 14.326 32 32s-14.326 32-32 32h-64c-17.674 0-32-14.326-32-32zM480 352c-17.674 0-32 14.326-32 32s14.326 32 32 32h64c17.674 0 32-14.326 32-32s-14.326-32-32-32h-64zM320 640c0-17.674 14.326-32 32-32h320c17.674 0 32 14.326 32 32s-14.326 32-32 32h-320c-17.674 0-32-14.326-32-32zM704 352c-17.674 0-32 14.326-32 32s14.326 32 32 32h64c17.674 0 32-14.326 32-32s-14.326-32-32-32h-64z" + ], + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "isMulticolor": false, + "isMulticolor2": false, + "colorPermutations": { + "1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101": [ + { "f": 2 } + ] + }, + "tags": ["keyboard"], + "grid": 0 + }, + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "properties": { "order": 12, "id": 112, "name": "keyboard", "prevSize": 32, "code": 59770 }, + "setIdx": 0, + "setId": 4, + "iconIdx": 118 + }, + { + "icon": { + "paths": [ + "M681.904 256c0-17.673-14.326-32-32-32s-32 14.327-32 32v56.875l-80.346 13.998c-17.411 3.034-29.069 19.61-26.035 37.021 3.034 17.408 19.61 29.066 37.021 26.032l69.36-12.086v69.373c-30.384 6.851-60.461 20.074-84.154 43.139-27.923 27.184-44.070 65.238-44.070 113.808 0 25.741 6.262 48.262 18.451 66.621 12.186 18.349 29.062 30.682 47.251 38 35.357 14.221 76.72 10.173 108.128-4.579l1.206-0.566c31.126-14.621 62.493-29.354 90.723-57.818 21.325-21.507 39.709-49.539 56.848-88.678 5.654 9.498 9.594 20.966 10.285 34.339 1.632 31.526-14.275 82.813-87.955 153.418-12.758 12.227-13.19 32.483-0.963 45.245 12.227 12.758 32.486 13.19 45.245 0.963 80.192-76.851 110.586-145.034 107.59-202.934-2.986-57.658-38.586-96.022-70.675-113.955-20.774-13.014-50.214-22.826-81.216-28.288-16.624-2.931-34.47-4.749-52.694-4.995v-74.243l101.718-17.722c17.411-3.034 29.066-19.61 26.032-37.019-3.034-17.411-19.606-29.066-37.018-26.032l-90.733 15.809v-45.724zM578.394 536.208c10.163-9.891 23.565-17.475 39.51-22.704v138.653c-13.443 2.547-27.485 1.731-38.634-2.752-7.712-3.104-13.658-7.757-17.824-14.029-4.157-6.262-7.766-15.997-7.766-31.216 0-33.35 10.563-54.176 24.714-67.952zM709.994 600.752c-8.682 8.755-17.782 15.706-28.090 22.074v-117.888c14.125 0.24 28.224 1.661 41.587 4.016 13.587 2.397 25.683 5.622 35.763 9.165-16.88 42.038-33.069 66.304-49.261 82.634zM247.704 565.334h98.386l-49.193-184.883-49.193 184.883zM390.851 733.562l-27.734-104.227h-132.442l-27.732 104.227c-4.544 17.078-22.073 27.242-39.152 22.694-17.079-4.544-27.24-22.070-22.696-39.152l106.323-399.598c13.494-50.714 85.462-50.713 98.956 0l106.323 399.598c4.544 17.082-5.616 34.608-22.694 39.152-17.078 4.547-34.608-5.616-39.152-22.694z" + ], + "width": 1056, + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "isMulticolor": false, + "isMulticolor2": false, + "colorPermutations": { + "1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101": [ + { "f": 2 } + ] + }, + "tags": ["language"], + "grid": 0 + }, + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "properties": { "order": 11, "id": 113, "name": "language", "prevSize": 32, "code": 59771 }, + "setIdx": 0, + "setId": 4, + "iconIdx": 119 + }, + { + "icon": { + "paths": [ + "M439.274 704.374c0-33.645 26.278-47.677 72.726-47.677s72.73 14.032 72.73 47.677c0 32.96-13.19 111.069-23.914 149.76-4.963 17.808-22.080 25.171-48.816 25.171s-43.85-7.363-48.813-25.174c-10.717-38.666-23.914-116.688-23.914-149.757zM616.778 870.374l0.026-0.090c6.176-22.291 12.566-53.869 17.386-83.453 4.688-28.787 8.72-60.707 8.72-82.458 0-35.891-15.811-67.802-46.797-87.114-26.035-16.227-57.152-19.926-84.112-19.926-26.957 0-58.077 3.699-84.112 19.926-30.982 19.312-46.797 51.222-46.797 87.114 0 21.805 4.035 53.728 8.726 82.515 4.819 29.578 11.21 61.123 17.379 83.392l0.026 0.086c7.402 26.557 24.982 45.664 46.87 56.467 19.472 9.61 40.426 11.834 57.907 11.834 17.485 0 38.435-2.224 57.907-11.83 21.888-10.8 39.469-29.907 46.87-56.464zM459.635 460.8c0-28.275 23.446-51.2 52.365-51.2s52.365 22.925 52.365 51.2c0 28.278-23.446 51.2-52.365 51.2s-52.365-22.922-52.365-51.2zM512 341.334c-67.478 0-122.182 53.488-122.182 119.466 0 65.981 54.704 119.469 122.182 119.469s122.182-53.488 122.182-119.469c0-65.978-54.704-119.466-122.182-119.466zM677.802 537.254c-6.662 13.792-3.882 31.11 6.278 42.573 14.086 15.894 39.85 17.939 50.205-0.602 19.642-35.174 30.806-75.526 30.806-118.426 0-136.672-113.315-247.465-253.091-247.465-139.779 0-253.092 110.793-253.092 247.465 0 42.899 11.164 83.251 30.806 118.426 10.354 18.541 36.119 16.496 50.205 0.602 10.16-11.462 12.938-28.781 6.275-42.573-11.203-23.187-17.469-49.104-17.469-76.454 0-98.97 82.054-179.199 183.274-179.199s183.27 80.229 183.27 179.199c0 27.35-6.262 53.267-17.469 76.454zM730.16 699.795c-0.304-10.659 3.542-21.072 10.928-28.762 52.771-54.957 85.094-128.902 85.094-210.234 0-169.661-140.666-307.199-314.182-307.199s-314.182 137.538-314.182 307.199c0 81.331 32.323 155.28 85.093 210.234 7.386 7.69 11.234 18.102 10.928 28.762-0.833 29.018-31.273 47.917-52.121 27.715-70.223-68.042-113.718-162.41-113.718-266.71 0-207.363 171.923-375.465 384-375.465s384 168.102 384 375.465c0 104.304-43.498 198.672-113.722 266.714-20.848 20.202-51.286 1.302-52.118-27.718z" + ], + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "isMulticolor": false, + "isMulticolor2": false, + "colorPermutations": { + "1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101": [ + { "f": 2 } + ] + }, + "tags": ["live-streaming"], + "grid": 0 + }, + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "properties": { "order": 9, "id": 115, "name": "live-streaming", "prevSize": 32, "code": 59773 }, + "setIdx": 0, + "setId": 4, + "iconIdx": 120 + }, + { + "icon": { + "paths": [ + "M700.144 361.891c4.685-24.973 34.704-34.093 49.13-13.171 31.994 46.403 50.726 102.653 50.726 163.28s-18.733 116.88-50.726 163.283c-14.426 20.922-44.445 11.802-49.13-13.174l-1.706-9.101c-1.581-8.429 0.384-17.082 4.858-24.403 20.749-33.965 32.704-73.888 32.704-116.605 0-42.714-11.955-82.637-32.704-116.605-4.474-7.318-6.438-15.971-4.858-24.4l1.706-9.104z", + "M320.704 395.395c-20.748 33.968-32.704 73.891-32.704 116.605 0 42.717 11.956 82.64 32.704 116.605 4.474 7.322 6.438 15.974 4.858 24.403l-1.706 9.101c-4.684 24.976-34.705 34.096-49.128 13.174-31.994-46.403-50.728-102.656-50.728-163.283s18.733-116.877 50.728-163.28c14.424-20.922 44.444-11.802 49.128 13.171l1.706 9.104c1.581 8.429-0.384 17.082-4.858 24.4z", + "M728.765 209.256l-0.515 2.747c-2.234 11.911 2.534 23.967 11.763 31.821 75.866 64.565 123.987 160.751 123.987 268.175 0 107.427-48.122 203.613-123.987 268.176-9.229 7.856-13.997 19.914-11.763 31.824l0.515 2.746c4.192 22.362 29.69 33.194 47.261 18.746 92.794-76.294 151.974-191.981 151.974-321.491 0-129.507-59.181-245.193-151.974-321.489-17.571-14.448-43.069-3.615-47.261 18.745z", + "M283.986 243.825c9.228-7.854 13.998-19.911 11.764-31.821l-0.515-2.747c-4.192-22.359-29.69-33.193-47.262-18.745-92.793 76.296-151.973 191.981-151.973 321.489 0 129.51 59.18 245.197 151.973 321.491 17.572 14.448 43.070 3.616 47.262-18.746l0.515-2.746c2.233-11.91-2.536-23.968-11.764-31.824-75.864-64.563-123.986-160.749-123.986-268.176 0-107.424 48.122-203.61 123.986-268.175z", + "M608 512c0 53.021-42.979 96-96 96s-96-42.979-96-96c0-53.018 42.979-96 96-96s96 42.982 96 96z" + ], + "attrs": [ + { "fill": "rgb(108, 114, 122)" }, + { "fill": "rgb(108, 114, 122)" }, + { "fill": "rgb(108, 114, 122)" }, + { "fill": "rgb(108, 114, 122)" }, + { "fill": "rgb(108, 114, 122)" } + ], + "isMulticolor": false, + "isMulticolor2": false, + "colorPermutations": { + "1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101": [ + { "f": 2 }, + { "f": 2 }, + { "f": 2 }, + { "f": 2 }, + { "f": 2 } + ] + }, + "tags": ["live"], + "grid": 0 + }, + "attrs": [ + { "fill": "rgb(108, 114, 122)" }, + { "fill": "rgb(108, 114, 122)" }, + { "fill": "rgb(108, 114, 122)" }, + { "fill": "rgb(108, 114, 122)" }, + { "fill": "rgb(108, 114, 122)" } + ], + "properties": { "order": 8, "id": 116, "name": "live", "prevSize": 32, "code": 59774 }, + "setIdx": 0, + "setId": 4, + "iconIdx": 121 + }, + { + "icon": { + "paths": [ + "M397.011 527.334c26.576 0 48.122-21.587 48.122-48.214s-21.546-48.214-48.122-48.214c-26.576 0-48.118 21.587-48.118 48.214s21.542 48.214 48.118 48.214z", + "M589.491 479.12c0 26.627-21.542 48.214-48.118 48.214s-48.122-21.587-48.122-48.214c0-26.627 21.546-48.214 48.122-48.214s48.118 21.587 48.118 48.214z", + "M733.853 479.12c0 26.627-21.542 48.214-48.118 48.214s-48.122-21.587-48.122-48.214c0-26.627 21.546-48.214 48.122-48.214s48.118 21.587 48.118 48.214z", + "M88.039 813.683l119.625 31.283c85.312 22.307 173.45-16.256 238.691-79.709 29.501 4.794 59.875 7.242 90.678 7.242 218.294 0 404.339-122.864 404.339-294.518 0-171.664-186.042-294.52-404.339-294.52-218.314 0-404.351 122.852-404.34 294.523 0 65.072 27.642 125.014 75.655 173.587-2.847 24.835-14.596 44.73-36.049 68.55l-84.261 93.562zM537.034 258.997c183.13 0 331.6 98.043 331.6 218.984 0 120.931-148.47 218.982-331.6 218.982-40.781 0-79.834-4.877-115.91-13.763-36.669 45.6-117.335 109.008-195.697 88.518 25.489-28.304 63.251-76.131 55.168-154.909-46.968-37.779-75.162-86.134-75.162-138.829-0.008-120.95 148.461-218.984 331.6-218.984z" + ], + "width": 1056, + "attrs": [ + { "fill": "rgb(108, 114, 122)" }, + { "fill": "rgb(108, 114, 122)" }, + { "fill": "rgb(108, 114, 122)" }, + { "fill": "rgb(108, 114, 122)" } + ], + "isMulticolor": false, + "isMulticolor2": false, + "colorPermutations": { + "1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101": [ + { "f": 2 }, + { "f": 2 }, + { "f": 2 }, + { "f": 2 } + ] + }, + "tags": ["livechat-monochromatic"], + "grid": 0 + }, + "attrs": [ + { "fill": "rgb(108, 114, 122)" }, + { "fill": "rgb(108, 114, 122)" }, + { "fill": "rgb(108, 114, 122)" }, + { "fill": "rgb(108, 114, 122)" } + ], + "properties": { "order": 31, "id": 117, "name": "livechat-monochromatic", "prevSize": 32, "code": 59775 }, + "setIdx": 0, + "setId": 4, + "iconIdx": 122 + }, + { + "icon": { + "paths": [ + "M341.334 778.672c-17.674 0-32 14.326-32 32s14.326 32 32 32h341.334c17.674 0 32-14.326 32-32s-14.326-32-32-32h-341.334zM85.335 298.672c0-70.692 57.308-128 128-128h597.334c70.691 0 128 57.308 128 128v298.666c0 70.694-57.309 128-128 128h-597.334c-70.692 0-128-57.306-128-128v-298.666zM213.335 234.672c-35.346 0-64 28.654-64 64v298.666c0 35.347 28.654 64 64 64h597.334c35.344 0 64-28.653 64-64v-298.666c0-35.347-28.656-64-64-64h-597.334zM256 320c0-11.782 9.551-21.333 21.333-21.333h170.667c11.782 0 21.334 9.551 21.334 21.333s-9.552 21.334-21.334 21.334h-170.667c-11.782 0-21.333-9.552-21.333-21.334zM277.333 554.666c-11.782 0-21.333 9.552-21.333 21.334s9.551 21.334 21.333 21.334h85.332c11.782 0 21.334-9.552 21.334-21.334s-9.552-21.334-21.334-21.334h-85.332zM554.666 320c0-11.782 9.552-21.333 21.334-21.333h170.666c11.782 0 21.334 9.551 21.334 21.333s-9.552 21.334-21.334 21.334h-170.666c-11.782 0-21.334-9.552-21.334-21.334zM448 554.666c-11.782 0-21.334 9.552-21.334 21.334s9.552 21.334 21.334 21.334h298.666c11.782 0 21.334-9.552 21.334-21.334s-9.552-21.334-21.334-21.334h-298.666zM256 448c0-11.782 9.551-21.334 21.333-21.334h213.332c11.782 0 21.334 9.552 21.334 21.334s-9.552 21.334-21.334 21.334h-213.332c-11.782 0-21.333-9.552-21.333-21.334zM618.666 426.666c-11.782 0-21.331 9.552-21.331 21.334s9.549 21.334 21.331 21.334h128c11.782 0 21.334-9.552 21.334-21.334s-9.552-21.334-21.334-21.334h-128z" + ], + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "isMulticolor": false, + "isMulticolor2": false, + "colorPermutations": { + "1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101": [ + { "f": 2 } + ] + }, + "tags": ["log-view"], + "grid": 0 + }, + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "properties": { "order": 33, "id": 119, "name": "log-view", "prevSize": 32, "code": 59778 }, + "setIdx": 0, + "setId": 4, + "iconIdx": 123 + }, + { + "icon": { + "paths": [ + "M176 96c-17.673 0-32 14.327-32 32v768c0 17.674 14.327 32 32 32h512c17.674 0 32-14.326 32-32v-96c0-17.674-14.326-32-32-32s-32 14.326-32 32v64h-448v-704h448v64c0 17.673 14.326 32 32 32s32-14.327 32-32v-96c0-17.673-14.326-32-32-32h-512zM521.373 329.373c12.496-12.497 32.758-12.497 45.254 0s12.496 32.758 0 45.254l-105.373 105.373h418.746c17.674 0 32 14.326 32 32s-14.326 32-32 32h-418.746l105.373 105.373c12.496 12.496 12.496 32.758 0 45.254s-32.758 12.496-45.254 0l-160-160c-12.496-12.496-12.496-32.758 0-45.254l160-160z" + ], + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "isMulticolor": false, + "isMulticolor2": false, + "colorPermutations": { + "1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101": [ + { "f": 2 } + ] + }, + "tags": ["login"], + "grid": 0 + }, + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "properties": { "order": 34, "id": 120, "name": "login", "prevSize": 32, "code": 59779 }, + "setIdx": 0, + "setId": 4, + "iconIdx": 124 + }, + { + "icon": { + "paths": [ + "M268.099 96c-17.673 0-32 14.327-32 32v768c0 17.674 14.327 32 32 32h512c17.674 0 32-14.326 32-32v-96c0-17.674-14.326-32-32-32s-32 14.326-32 32v64h-448v-704h448v64c0 17.673 14.326 32 32 32s32-14.327 32-32v-96c0-17.673-14.326-32-32-32h-512zM994.726 489.373l-160-160c-12.496-12.497-32.758-12.497-45.254 0s-12.496 32.758 0 45.254l105.373 105.373h-418.746c-17.674 0-32 14.326-32 32s14.326 32 32 32h418.746l-105.373 105.373c-12.496 12.496-12.496 32.758 0 45.254s32.758 12.496 45.254 0l160-160c12.496-12.496 12.496-32.758 0-45.254z" + ], + "width": 1056, + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "isMulticolor": false, + "isMulticolor2": false, + "colorPermutations": { + "1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101": [ + { "f": 2 } + ] + }, + "tags": ["logout"], + "grid": 0 + }, + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "properties": { "order": 35, "id": 121, "name": "logout", "prevSize": 32, "code": 59780 }, + "setIdx": 0, + "setId": 4, + "iconIdx": 125 + }, + { + "icon": { + "paths": [ + "M192 736h640v-448h-640v448zM128 256c0-17.673 14.327-32 32-32h704c17.674 0 32 14.327 32 32v512c0 17.674-14.326 32-32 32h-704c-17.673 0-32-14.326-32-32v-512zM305.304 389.082c-14.866-9.555-34.665-5.251-44.222 9.613-9.557 14.867-5.253 34.666 9.613 44.224l241.304 155.123 241.306-155.123c14.864-9.558 19.168-29.357 9.613-44.224-9.558-14.864-29.357-19.168-44.224-9.613l-206.694 132.877-206.696-132.877z" + ], + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "isMulticolor": false, + "isMulticolor2": false, + "colorPermutations": { + "1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101": [ + { "f": 2 } + ] + }, + "tags": ["mail"], + "grid": 0 + }, + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "properties": { "order": 36, "id": 122, "name": "mail", "prevSize": 32, "code": 59781 }, + "setIdx": 0, + "setId": 4, + "iconIdx": 126 + }, + { + "icon": { + "paths": [ + "M302.769 192h418.463l23.187 72.727h-0.211l23.792 63.999c0 41.27-31.306 69.818-64 69.818s-64-28.547-64-69.818c0-18.476-14.326-33.454-32-33.454s-32 14.978-32 33.454c0 41.27-31.306 69.818-64 69.818s-64-28.547-64-69.818c0-18.476-14.326-33.454-32-33.454s-32 14.978-32 33.454c0 41.27-31.306 69.818-64 69.818s-64-28.547-64-69.818l23.794-63.999h-0.213l23.188-72.727zM212.406 264.727l36.491-114.448c4.231-13.27 16.56-22.279 30.488-22.279h465.23c13.93 0 26.259 9.009 30.49 22.279l56.896 178.447c0 33.939-12.083 64.925-32 88.515v350.778c0 53.018-42.979 96-96 96h-384c-53.019 0-96-42.982-96-96v-350.778c-19.916-23.59-32-54.576-32-88.515l20.406-63.999zM288 458.33v309.69c0 17.67 14.327 32 32 32h128v-192.019h128v192.019h128c17.674 0 32-14.33 32-32v-309.69c-10.227 2.752-20.95 4.214-32 4.214-38.23 0-72.547-17.52-96-45.302-23.453 27.782-57.77 45.302-96 45.302s-72.547-17.52-96-45.302c-23.453 27.782-57.77 45.302-96 45.302-11.050 0-21.772-1.462-32-4.214z" + ], + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "isMulticolor": false, + "isMulticolor2": false, + "colorPermutations": { + "1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101": [ + { "f": 2 } + ] + }, + "tags": ["marketplace"], + "grid": 0 + }, + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "properties": { "order": 37, "id": 123, "name": "marketplace", "prevSize": 32, "code": 59782 }, + "setIdx": 0, + "setId": 4, + "iconIdx": 127 + }, + { + "icon": { + "paths": [ + "M773.072 576c-35.344 0-64-28.653-64-64s28.656-64 64-64c35.347 0 64 28.653 64 64s-28.653 64-64 64z", + "M517.072 576c-35.344 0-64-28.653-64-64s28.656-64 64-64c35.347 0 64 28.653 64 64s-28.653 64-64 64z", + "M261.073 576c-35.346 0-64-28.653-64-64s28.654-64 64-64c35.346 0 63.999 28.653 63.999 64s-28.652 64-63.999 64z" + ], + "attrs": [{ "fill": "rgb(108, 114, 122)" }, { "fill": "rgb(108, 114, 122)" }, { "fill": "rgb(108, 114, 122)" }], + "isMulticolor": false, + "isMulticolor2": false, + "colorPermutations": { + "1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101": [ + { "f": 2 }, + { "f": 2 }, + { "f": 2 } + ] + }, + "tags": ["meatballs"], + "grid": 0 + }, + "attrs": [{ "fill": "rgb(108, 114, 122)" }, { "fill": "rgb(108, 114, 122)" }, { "fill": "rgb(108, 114, 122)" }], + "properties": { "order": 38, "id": 124, "name": "meatballs", "prevSize": 32, "code": 59783 }, + "setIdx": 0, + "setId": 4, + "iconIdx": 128 + }, + { + "icon": { + "paths": [ + "M651.36 116.071c-57.242-19.989-105.792-20.069-138.358-20.071v64l-1.002-64c-92.006 0.342-297.345 47.824-381.242 242.995-17.897 38.97-34.758 103.344-34.758 173.005 0 69.958 17.020 146.285 52.216 207.875 23.229 40.653 92.799 131.376 191.179 173.536 94.861 40.656 206.368 52.349 296.49 16.301 16.41-6.563 24.39-25.187 17.827-41.597s-25.187-24.39-41.597-17.827c-69.878 27.952-163.171 20.445-247.51-15.699-80.819-34.64-141.384-112.451-160.821-146.464-28.804-50.41-43.784-115.418-43.784-176.125 0-60.765 15.020-116.182 29.055-146.589l0.184-0.4 0.173-0.406c69.507-162.181 243.823-204.604 323.589-204.605 31.594 0.002 70.877 0.296 117.258 16.493 46.147 16.114 101.389 48.779 161.824 116.767 43.658 49.115 63.533 114.977 69.389 177.713 5.891 63.12-2.854 118.189-11.546 142.093-6.4 17.6-20.429 45.44-59.392 45.699-18.259-0.806-72.822-14.672-83.12-69.568v-235.062c0-17.674-3.414-34.134-29.014-34.134-19.338 0-26.454 16.461-26.454 34.134v34.131c-35.181-39.859-95.402-68.266-152.746-68.266-106.038 0-192 85.962-192 192s85.962 192 192 192c62.179 0 117.658-29.555 152.746-75.386 25.715 71.078 102.57 93.027 137.014 94.134l0.515 0.016h0.512c82.643 0 111.715-64.806 120.086-87.83 12.64-34.762 21.674-99.696 15.12-169.907-6.589-70.595-29.379-151.401-85.277-214.287-66.902-75.265-131.075-114.597-188.557-134.67zM627.2 512c0 70.691-57.309 128-128 128s-128-57.309-128-128c0-70.691 57.309-128 128-128s128 57.309 128 128z" + ], + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "isMulticolor": false, + "isMulticolor2": false, + "colorPermutations": { + "1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101": [ + { "f": 2 } + ] + }, + "tags": ["mention"], + "grid": 0 + }, + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "properties": { "order": 142, "id": 125, "name": "mention", "prevSize": 32, "code": 59784 }, + "setIdx": 0, + "setId": 4, + "iconIdx": 129 + }, + { + "icon": { + "paths": [ + "M477.84 128c104.256 0 181.251 29.018 237.494 70.652l-44.317 44.317c-45.52-31.22-107.747-53.057-193.178-53.057-129.725 0-207.83 53.576-254.529 118.696-47.832 66.7-64.077 147.667-64.077 201.187v1.405l-0.127 1.398c-7.328 80.928 13.761 143.126 51.784 190.499l-43.879 43.878c-50.179-59.901-78.131-139.043-69.441-238.595 0.276-63.411 19.117-157.053 75.696-235.948 58.132-81.063 154.749-144.433 304.573-144.433zM822.848 332.499l-47.008 47.008c16.502 42.634 20.608 78.525 20.608 89.011v0.179c0 19.517 0 31.792-6.621 56.17-7.094 26.109-21.738 65.648-52.554 137.834-5.699 13.357-2.794 25.981 1.261 34.435 3.834 7.99 9.562 14.403 14.595 19.184 10.227 9.712 24.253 18.842 38.544 26.867 7.888 4.429 18.298 8.995 27.613 13.078l1.843 0.81c10.554 4.634 20.726 9.146 29.613 13.894 3.427 1.834 6.298 3.517 8.672 5.030-5.104 3.229-12.035 6.883-20.941 10.765-22.96 10.003-55.328 19.939-93.888 28.128-77.12 16.371-174.73 24.858-263.341 14.973-43.744-4.88-86.826-14.525-126.534-29.229l-47.542 47.542c52.771 23.018 110.492 36.886 167.267 43.222 96.387 10.752 200.71 1.517 282.909-15.936 41.098-8.726 77.731-19.731 105.68-31.907 13.91-6.064 26.618-12.822 36.858-20.342 9.427-6.925 20.739-17.344 25.61-32.016 7.392-22.269-2.957-40.509-12.816-51.139-9.427-10.166-22.106-17.939-32.954-23.738-11.309-6.048-23.626-11.472-33.891-15.978-11.059-4.854-18.934-8.317-24.054-11.194-9.914-5.565-17.437-10.538-22.506-14.57 27.421-64.81 42.214-104.589 50.048-133.424 8.774-32.304 8.797-50.986 8.797-72.64 0-20.106-7.053-75.501-35.267-136.019zM836.038 153.372c12.499-12.497 32.758-12.497 45.254 0 12.499 12.497 12.499 32.758 0 45.255l-682.665 682.665c-12.497 12.499-32.758 12.499-45.255 0-12.497-12.496-12.497-32.755 0-45.254l682.666-682.666z" + ], + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "isMulticolor": false, + "isMulticolor2": false, + "colorPermutations": { + "1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101": [ + { "f": 2 } + ] + }, + "tags": ["message-disabled"], + "grid": 0 + }, + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "properties": { "order": 57, "id": 126, "name": "message-disabled", "prevSize": 32, "code": 59785 }, + "setIdx": 0, + "setId": 4, + "iconIdx": 130 + }, + { + "icon": { + "paths": [ + "M223.311 308.609c-47.832 66.7-64.077 147.667-64.077 201.187v1.405l-0.127 1.398c-9.348 103.235 27.547 175.997 86.848 226.374 60.643 51.52 146.602 80.998 235.29 90.893 88.614 9.885 186.224 1.398 263.341-14.973 38.56-8.189 70.928-18.125 93.888-28.128 8.906-3.882 15.837-7.536 20.944-10.765-2.378-1.514-5.248-3.197-8.675-5.030-8.886-4.749-19.059-9.261-29.613-13.894l-1.843-0.81c-9.315-4.083-19.725-8.65-27.613-13.078-14.291-8.026-28.317-17.155-38.544-26.867-5.034-4.781-10.762-11.194-14.595-19.184-4.054-8.454-6.96-21.078-1.261-34.435 30.816-72.186 45.459-111.725 52.554-137.834 6.621-24.378 6.621-36.653 6.621-56.17v-0.179c0-15.51-8.979-86.595-53.776-152.876-43.376-64.174-121.395-125.729-264.832-125.729-129.725 0-207.83 53.576-254.529 118.696zM173.267 272.433c58.132-81.063 154.749-144.433 304.573-144.433 164.896 0 261.594 72.589 315.859 152.878 52.838 78.181 64.416 161.881 64.416 187.641 0 21.654-0.022 40.336-8.797 72.64-7.83 28.835-22.627 68.614-50.048 133.424 5.069 4.032 12.592 9.005 22.506 14.57 5.123 2.877 12.995 6.339 24.054 11.194 10.266 4.506 22.582 9.93 33.891 15.978 10.848 5.798 23.526 13.571 32.954 23.738 9.859 10.63 20.208 28.87 12.816 51.139-4.87 14.672-16.182 25.091-25.61 32.016-10.24 7.52-22.947 14.278-36.858 20.342-27.946 12.176-64.582 23.181-105.68 31.907-82.198 17.453-186.522 26.688-282.909 15.936-96.31-10.746-195.346-43.178-268.313-105.165-74.031-62.893-119.295-154.778-108.551-277.856 0.276-63.411 19.117-157.053 75.696-235.948z" + ], + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "isMulticolor": false, + "isMulticolor2": false, + "colorPermutations": { + "1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101": [ + { "f": 2 } + ] + }, + "tags": ["message"], + "grid": 0 + }, + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "properties": { "order": 58, "id": 127, "name": "message", "prevSize": 32, "code": 59786 }, + "setIdx": 0, + "setId": 4, + "iconIdx": 131 + }, + { + "icon": { + "paths": [ + "M512 128c75.546 0 138.861 52.356 155.645 122.762l-59.645 59.644v-22.405c0-53.019-42.979-96-96-96s-96 42.981-96 96v128c0 24.067 8.858 46.067 23.488 62.915l-45.322 45.325c-26.182-28.49-42.166-66.499-42.166-108.24v-128c0-88.365 71.635-160 160-160zM561.35 568.243l102.893-102.893c-15.763 48.672-54.221 87.13-102.893 102.893zM288 416c0 72.154 23.181 123.101 55.328 159.078l-45.304 45.302c-43.428-47.35-74.024-113.99-74.024-204.381 0-17.674 14.327-32 32-32s32 14.326 32 32zM478.659 650.938l-51.808 51.808c18.56 6.374 36.579 10.806 53.149 13.635v115.619h-160c-17.673 0-32 14.326-32 32s14.327 32 32 32h384c17.674 0 32-14.326 32-32s-14.326-32-32-32h-160v-115.619c44.64-7.619 99.818-26.896 147.722-64.381 60.704-47.501 108.278-123.286 108.278-236 0-17.674-14.326-32-32-32s-32 14.326-32 32c0 92.086-37.757 149.632-83.722 185.6-46.464 36.358-102.736 51.581-140.278 54.326-9.971-0.73-21.264-2.339-33.341-4.989zM825.373 153.372c12.496-12.497 32.758-12.497 45.254 0s12.496 32.758 0 45.255l-672 672c-12.497 12.496-32.758 12.496-45.255 0s-12.497-32.758 0-45.254l672-672z" + ], + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "isMulticolor": false, + "isMulticolor2": false, + "colorPermutations": { + "1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101": [ + { "f": 2 } + ] + }, + "tags": ["microphone-disabled"], + "grid": 0 + }, + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "properties": { "order": 59, "id": 128, "name": "microphone-disabled", "prevSize": 32, "code": 59787 }, + "setIdx": 0, + "setId": 4, + "iconIdx": 132 + }, + { + "icon": { + "paths": [ + "M608 288c0-53.019-42.979-96-96-96s-96 42.981-96 96v128c0 53.021 42.979 96 96 96s96-42.979 96-96v-128zM352 288c0-88.365 71.635-160 160-160s160 71.635 160 160v128c0 88.365-71.635 160-160 160s-160-71.635-160-160v-128zM256 384c17.673 0 32 14.326 32 32 0 92.086 37.757 149.632 83.722 185.6 46.464 36.358 102.736 51.581 140.278 54.326 37.542-2.746 93.814-17.968 140.278-54.326 45.962-35.968 83.722-93.514 83.722-185.6 0-17.674 14.326-32 32-32s32 14.326 32 32c0 112.714-47.574 188.499-108.278 236-47.904 37.485-103.082 56.762-147.722 64.381v115.619h160c17.674 0 32 14.326 32 32s-14.326 32-32 32h-384c-17.673 0-32-14.326-32-32s14.327-32 32-32h160v-115.619c-44.64-7.619-99.818-26.896-147.722-64.381-60.703-47.501-108.278-123.286-108.278-236 0-17.674 14.327-32 32-32z" + ], + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "isMulticolor": false, + "isMulticolor2": false, + "colorPermutations": { + "1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101": [ + { "f": 2 } + ] + }, + "tags": ["microphone"], + "grid": 0 + }, + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "properties": { "order": 60, "id": 129, "name": "microphone", "prevSize": 32, "code": 59788 }, + "setIdx": 0, + "setId": 4, + "iconIdx": 133 + }, + { + "icon": { + "paths": [ + "M234.666 170.661c0-47.128 38.205-85.333 85.333-85.333h384.001c47.126 0 85.331 38.205 85.331 85.333v682.667c0 47.13-38.205 85.334-85.331 85.334h-384.001c-47.128 0-85.333-38.205-85.333-85.334v-682.667zM298.666 170.661v682.667c0 11.782 9.551 21.334 21.333 21.334h384.001c11.782 0 21.331-9.552 21.331-21.334v-682.667c0-11.782-9.549-21.333-21.331-21.333h-96.291c-2.653 24.002-23.002 42.672-47.709 42.672h-96c-24.707 0-45.056-18.67-47.709-42.672h-96.292c-11.782 0-21.333 9.551-21.333 21.333z" + ], + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "isMulticolor": false, + "isMulticolor2": false, + "colorPermutations": { + "1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101": [ + { "f": 2 } + ] + }, + "tags": ["mobile"], + "grid": 0 + }, + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "properties": { "order": 61, "id": 130, "name": "mobile", "prevSize": 32, "code": 59789 }, + "setIdx": 0, + "setId": 4, + "iconIdx": 134 + }, + { + "icon": { + "paths": [ + "M319.247 231.566c-62.882 49.504-135.086 136.549-141.94 266.127-5.762 108.922 37.404 187.184 89.915 241.51 53.258 55.098 115.702 84.925 144.25 94.438 41.859 13.955 114.269 29.536 234.643-15.603 34.858-13.072 73.165-42.096 108.304-78.426 14.272-14.755 27.616-30.294 39.6-45.773-28.778 9.293-61.699 17.754-96.589 23.594-56.192 9.405-119.29 12.32-179.411-0.237-2.49-0.518-4.96-1.030-7.408-1.536-22.195-4.576-42.733-8.816-62.589-17.648-23.091-10.275-43.805-25.891-69.299-51.386-42.096-42.096-89.142-107.222-89.371-213.843-1.569-34.080 4.622-81.878 13.764-129.216 4.624-23.946 10.142-48.465 16.132-72.003zM345.562 138.217c32.8-17.15 63.709 15.843 53.875 45.907-12.234 37.414-24.586 85.511-33.482 131.58-9.024 46.732-13.949 88.633-12.643 114.7l0.038 0.797v0.8c0 84.915 36.173 134.918 70.627 169.373 22.509 22.509 36.835 32.278 50.064 38.166 13.229 5.885 26.832 8.717 51.085 13.763 1.923 0.4 3.917 0.816 5.978 1.248 49.914 10.426 104.534 8.336 155.76-0.237 65.434-10.954 122.384-31.981 152.374-47.386 17.562-9.021 34.992-2.47 44.477 5.965 9.661 8.589 19.376 27.152 8.886 46.954-20.637 38.95-53.645 84.419-92.182 124.259-38.211 39.51-84.317 76.038-131.834 93.859-135.626 50.858-223.216 34.438-277.354 16.394-36.515-12.173-108.392-46.912-170.026-110.675-62.381-64.534-114.684-159.427-107.81-289.373 10.79-203.979 157.583-317.098 232.165-356.093z" + ], + "width": 1056, + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "isMulticolor": false, + "isMulticolor2": false, + "colorPermutations": { + "1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101": [ + { "f": 2 } + ] + }, + "tags": ["moon"], + "grid": 0 + }, + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "properties": { "order": 62, "id": 131, "name": "moon", "prevSize": 32, "code": 59790 }, + "setIdx": 0, + "setId": 4, + "iconIdx": 135 + }, + { + "icon": { + "paths": [ + "M144 160c-17.673 0-32 14.327-32 32s14.327 32 32 32h192c17.674 0 32-14.327 32-32s-14.326-32-32-32h-192zM700.4 346.646c11.795-13.165 32.026-14.272 45.187-2.48 13.162 11.795 14.272 32.026 2.477 45.187l-81.222 90.646h216.358c17.674 0 32 14.326 32 32s-14.326 32-32 32h-216.358l81.222 90.646c11.795 13.162 10.685 33.392-2.477 45.187-13.162 11.792-33.392 10.685-45.187-2.48l-129.030-144c-10.893-12.154-10.893-30.554 0-42.707l129.030-144zM112 512c0-17.674 14.327-32 32-32h192c17.674 0 32 14.326 32 32s-14.326 32-32 32h-192c-17.673 0-32-14.326-32-32zM144 640c-17.673 0-32 14.326-32 32s14.327 32 32 32h192c17.674 0 32-14.326 32-32s-14.326-32-32-32h-192zM112 352c0-17.674 14.327-32 32-32h192c17.674 0 32 14.326 32 32s-14.326 32-32 32h-192c-17.673 0-32-14.326-32-32zM144 800c-17.673 0-32 14.326-32 32s14.327 32 32 32h192c17.674 0 32-14.326 32-32s-14.326-32-32-32h-192z" + ], + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "isMulticolor": false, + "isMulticolor2": false, + "colorPermutations": { + "1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101": [ + { "f": 2 } + ] + }, + "tags": ["move-to-the-queue"], + "grid": 0 + }, + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "properties": { "order": 63, "id": 132, "name": "move-to-the-queue", "prevSize": 32, "code": 59791 }, + "setIdx": 0, + "setId": 4, + "iconIdx": 136 + }, + { + "icon": { + "paths": [ + "M809.002 166.758c7.782 6.063 12.333 15.377 12.333 25.242v448h-0.112c0.074 1.734 0.112 3.478 0.112 5.229 0 69.053-57.309 125.030-128 125.030-70.694 0-128-55.978-128-125.030s57.306-125.030 128-125.030c23.312 0 45.171 6.090 64 16.73v-303.86l-384 96.798v409.136c0 69.053-57.309 125.037-128.001 125.037s-128-55.978-128-125.030c0-69.050 57.308-125.027 128-125.027 23.314 0 45.173 6.086 64 16.726v-325.777c0-14.66 9.962-27.446 24.177-31.029l448-112.93c9.568-2.411 19.709-0.276 27.491 5.788z" + ], + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "isMulticolor": false, + "isMulticolor2": false, + "colorPermutations": { + "1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101": [ + { "f": 2 } + ] + }, + "tags": ["musical-note"], + "grid": 0 + }, + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "properties": { "order": 64, "id": 133, "name": "musical-note", "prevSize": 32, "code": 59792 }, + "setIdx": 0, + "setId": 4, + "iconIdx": 137 + }, + { + "icon": { + "paths": [ + "M208 176c-35.346 0-64 28.654-64 64v576c0 35.347 28.654 64 64 64h576c35.347 0 64-28.653 64-64v-576c0-35.346-28.653-64-64-64h-576zM208 240h576v576h-576v-576zM698.627 558.15c-0.189 17.674-14.669 31.846-32.339 31.658-17.674-0.189-31.846-14.669-31.658-32.339l1.318-123.594-309.341 308.774c-12.51 12.483-32.771 12.464-45.256-0.042-12.485-12.509-12.467-32.771 0.042-45.258l309.195-308.624-123.382 1.315c-17.674 0.189-32.154-13.984-32.339-31.654-0.189-17.674 13.984-32.15 31.654-32.339l201.92-2.157c8.605-0.093 16.886 3.286 22.97 9.37 6.086 6.086 9.462 14.365 9.373 22.97l-2.157 201.92z" + ], + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "isMulticolor": false, + "isMulticolor2": false, + "colorPermutations": { + "1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101": [ + { "f": 2 } + ] + }, + "tags": ["new-window"], + "grid": 0 + }, + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "properties": { "order": 65, "id": 134, "name": "new-window", "prevSize": 32, "code": 59793 }, + "setIdx": 0, + "setId": 4, + "iconIdx": 138 + }, + { + "icon": { + "paths": [ + "M781.837 402.634c0-5.245-0.154-10.454-0.461-15.622l-63.539 63.539v265.363h-265.363l-66.979 66.979h425.363c12.944 0 24.611-7.795 29.565-19.754 4.954-11.955 2.214-25.722-6.938-34.874l-51.648-51.648v-273.984zM704.765 217.373l-45.254 45.255c-35.638-35.351-84.704-57.189-138.867-57.189-108.909 0-197.194 88.287-197.194 197.195v196.054l-64.001 64v-260.054c0-144.254 116.941-261.195 261.194-261.195 71.837 0 136.899 29.001 184.122 75.934zM433.578 808.934c0 48.086 38.982 87.066 87.066 87.066s87.066-38.979 87.066-87.066h-174.131zM854.275 190.982c-11.334-11.334-29.709-11.334-41.043 0l-612.732 612.733c-11.333 11.331-11.333 29.709 0 41.040 11.334 11.334 29.709 11.334 41.043 0l612.732-612.731c11.334-11.333 11.334-29.709 0-41.043z" + ], + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "isMulticolor": false, + "isMulticolor2": false, + "colorPermutations": { + "1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101": [ + { "f": 2 } + ] + }, + "tags": ["notification-disabled"], + "grid": 0 + }, + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "properties": { "order": 66, "id": 135, "name": "notification-disabled", "prevSize": 32, "code": 59794 }, + "setIdx": 0, + "setId": 4, + "iconIdx": 139 + }, + { + "icon": { + "paths": [ + "M601.331 808.928h-174.154c0.003 48.090 38.989 87.072 87.078 87.072s87.075-38.982 87.075-87.072zM815.597 731.677c7.274 9.696 8.442 22.669 3.021 33.51s-16.499 17.69-28.621 17.69h-565.996c-12.943 0-24.611-7.798-29.564-19.757-4.953-11.955-2.215-25.718 6.937-34.87l51.653-51.654v-274.032c0-144.272 116.957-261.228 261.229-261.228s261.229 116.956 261.229 261.228v275.629l40.112 53.485zM711.485 715.894v-313.331c0-108.926-88.304-197.228-197.229-197.228-108.928 0-197.229 88.302-197.229 197.228v313.331h394.458z" + ], + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "isMulticolor": false, + "isMulticolor2": false, + "colorPermutations": { + "1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101": [ + { "f": 2 } + ] + }, + "tags": ["notification"], + "grid": 0 + }, + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "properties": { "order": 67, "id": 136, "name": "notification", "prevSize": 32, "code": 59795 }, + "setIdx": 0, + "setId": 4, + "iconIdx": 140 + }, + { + "icon": { + "paths": [ + "M712.051 512.675v54.89c-0.195 76.736-43.741 143.283-107.44 176.445-5.53-25.67-28.362-44.906-55.683-44.906h-72.502c-31.459 0-56.963 25.501-56.963 56.963v41.427c0 31.462 25.504 56.963 56.963 56.963h72.502c28.509 0 52.128-20.944 56.307-48.288 69.261-26.829 123.962-82.883 148.966-153.030 4.723 1.27 9.69 1.946 14.813 1.946h28.483c31.459 0 56.963-25.504 56.963-56.963v-85.446c0-31.459-25.504-56.963-56.963-56.963h-28.483v-28.483c0-141.572-114.765-256.338-256.336-256.338s-256.339 114.766-256.339 256.338v23.302h-28.483c-31.46 0-56.964 25.504-56.964 56.966v103.571c0 31.459 25.504 56.963 56.964 56.963h28.482c31.46 0 56.964-25.504 56.964-56.963v-36.253h0.149c-0.099-2.576-0.148-5.165-0.148-7.766v-139.821c0-110.111 89.263-199.374 199.375-199.374s199.373 89.263 199.373 199.374v85.446z" + ], + "width": 1056, + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "isMulticolor": false, + "isMulticolor2": false, + "colorPermutations": { + "1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101": [ + { "f": 2 } + ] + }, + "tags": ["omnichannel"], + "grid": 0 + }, + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "properties": { "order": 123, "id": 137, "name": "omnichannel", "prevSize": 32, "code": 59796 }, + "setIdx": 0, + "setId": 4, + "iconIdx": 141 + }, + { + "icon": { + "paths": [ + "M705.578 633.158c9.507-9.235 24.701-9.018 33.939 0.486 9.238 9.507 9.021 24.701-0.486 33.939l-208.275 202.387c-9.312 9.050-24.138 9.050-33.45 0l-208.276-202.387c-9.506-9.238-9.724-24.432-0.487-33.939 9.237-9.504 24.432-9.722 33.937-0.486l191.549 186.134 191.549-186.134zM705.578 411.584c9.507 9.238 24.701 9.021 33.939-0.486s9.021-24.701-0.486-33.939l-208.275-202.385c-9.312-9.051-24.138-9.051-33.45 0l-208.276 202.385c-9.506 9.238-9.724 24.432-0.487 33.939 9.237 9.504 24.432 9.725 33.937 0.486l191.549-186.134 191.549 186.134z" + ], + "width": 1056, + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "isMulticolor": false, + "isMulticolor2": false, + "colorPermutations": { + "1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101": [ + { "f": 2 } + ] + }, + "tags": ["order"], + "grid": 0 + }, + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "properties": { "order": 124, "id": 138, "name": "order", "prevSize": 32, "code": 59797 }, + "setIdx": 0, + "setId": 4, + "iconIdx": 142 + }, + { + "icon": { + "paths": [ + "M742.317 652.566c12.666-12.323 12.944-32.582 0.618-45.248-12.323-12.669-32.582-12.944-45.251-0.621l44.634 45.869zM512 832.019l-22.317 22.934c12.422 12.086 32.211 12.086 44.634 0l-22.317-22.934zM326.317 606.698c-12.667-12.323-32.927-12.048-45.252 0.621-12.324 12.666-12.047 32.925 0.619 45.248l44.632-45.869zM697.683 606.698l-208 202.387 44.634 45.869 208-202.387-44.634-45.869zM534.317 809.085l-208-202.387-44.632 45.869 207.999 202.387 44.634-45.869z", + "M742.317 371.456c12.666 12.323 12.944 32.582 0.618 45.251-12.323 12.666-32.582 12.944-45.251 0.618l44.634-45.869zM512 192.004l-22.317-22.935c12.422-12.087 32.211-12.087 44.634 0l-22.317 22.935zM326.317 417.325c-12.668 12.326-32.927 12.048-45.252-0.618-12.325-12.669-12.048-32.928 0.619-45.251l44.633 45.869zM697.683 417.325l-208-202.386 44.634-45.87 208 202.387-44.634 45.869zM534.317 214.939l-208 202.386-44.633-45.869 207.999-202.387 44.634 45.87z" + ], + "attrs": [{ "fill": "rgb(108, 114, 122)" }, { "fill": "rgb(158, 162, 168)", "opacity": 0.7 }], + "isMulticolor": true, + "isMulticolor2": true, + "colorPermutations": { + "1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101": [ + { "f": 2 }, + { "f": 7 } + ] + }, + "tags": ["ordering-ascending"], + "grid": 0 + }, + "attrs": [{ "fill": "rgb(108, 114, 122)" }, { "fill": "rgb(158, 162, 168)", "opacity": 0.7 }], + "properties": { + "order": 125, + "id": 139, + "name": "ordering-ascending", + "prevSize": 32, + "code": 59798, + "codes": [59798, 59799] + }, + "setIdx": 0, + "setId": 4, + "iconIdx": 143 + }, + { + "icon": { + "paths": [ + "M281.684 371.434c-12.667 12.323-12.944 32.582-0.619 45.248 12.324 12.669 32.584 12.944 45.252 0.621l-44.633-45.869zM512 191.981l22.317-22.935c-12.422-12.087-32.211-12.087-44.634 0l22.317 22.935zM697.683 417.302c12.669 12.323 32.928 12.048 45.251-0.621 12.326-12.666 12.048-32.925-0.618-45.248l-44.634 45.869zM326.317 417.302l208-202.387-44.634-45.869-207.999 202.387 44.633 45.869zM489.683 214.916l208 202.387 44.634-45.869-208-202.387-44.634 45.869z", + "M281.684 652.544c-12.667-12.323-12.944-32.582-0.619-45.251 12.324-12.666 32.584-12.944 45.252-0.618l-44.633 45.869zM512 831.997l22.317 22.934c-12.422 12.086-32.211 12.086-44.634 0l22.317-22.934zM697.683 606.675c12.669-12.326 32.928-12.048 45.251 0.618 12.326 12.669 12.048 32.928-0.618 45.251l-44.634-45.869zM326.317 606.675l208 202.384-44.634 45.872-207.999-202.387 44.633-45.869zM489.683 809.059l208-202.384 44.634 45.869-208 202.387-44.634-45.872z" + ], + "attrs": [{ "fill": "rgb(108, 114, 122)" }, { "fill": "rgb(158, 162, 168)", "opacity": 0.7 }], + "isMulticolor": true, + "isMulticolor2": true, + "colorPermutations": { + "1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101": [ + { "f": 2 }, + { "f": 7 } + ] + }, + "tags": ["ordering-descending"], + "grid": 0 + }, + "attrs": [{ "fill": "rgb(108, 114, 122)" }, { "fill": "rgb(158, 162, 168)", "opacity": 0.7 }], + "properties": { + "order": 126, + "id": 140, + "name": "ordering-descending", + "prevSize": 32, + "code": 59800, + "codes": [59800, 59801] + }, + "setIdx": 0, + "setId": 4, + "iconIdx": 144 + }, + { + "icon": { + "paths": [ + "M512 928c229.75 0 416-186.25 416-416s-186.25-416-416-416c-229.752 0-416.001 186.25-416.001 416s186.25 416 416.001 416zM399.997 383.994c0-17.67 14.326-32 32-32s32 14.33 32 32v256c0 17.674-14.326 32-32 32s-32-14.326-32-32v-256zM559.997 383.994c0-17.67 14.326-32 32-32s32 14.33 32 32v256c0 17.674-14.326 32-32 32s-32-14.326-32-32v-256z" + ], + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "isMulticolor": false, + "isMulticolor2": false, + "colorPermutations": { + "1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101": [ + { "f": 2 } + ] + }, + "tags": ["pause-filled"], + "grid": 0 + }, + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "properties": { "order": 128, "id": 141, "name": "pause-filled", "prevSize": 32, "code": 59802 }, + "setIdx": 0, + "setId": 4, + "iconIdx": 145 + }, + { + "icon": { + "paths": [ + "M864 512c0-194.404-157.597-352-352-352-194.405 0-352.001 157.596-352.001 352s157.596 352 352.001 352c194.403 0 352-157.597 352-352zM928 512c0 229.75-186.25 416-416 416-229.752 0-416.001-186.25-416.001-416s186.25-416 416.001-416c229.75 0 416 186.25 416 416zM399.997 383.994v256c0 17.674 14.326 32 32 32s32-14.326 32-32v-256c0-17.67-14.326-32-32-32s-32 14.33-32 32zM559.997 383.994v256c0 17.674 14.326 32 32 32s32-14.326 32-32v-256c0-17.67-14.326-32-32-32s-32 14.33-32 32z" + ], + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "isMulticolor": false, + "isMulticolor2": false, + "colorPermutations": { + "1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101": [ + { "f": 2 } + ] + }, + "tags": ["pause"], + "grid": 0 + }, + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "properties": { "order": 129, "id": 142, "name": "pause", "prevSize": 32, "code": 59803 }, + "setIdx": 0, + "setId": 4, + "iconIdx": 146 + }, + { + "icon": { + "paths": [ + "M825.373 153.372c12.496-12.497 32.758-12.497 45.254 0s12.496 32.758 0 45.255l-672 672c-12.497 12.496-32.758 12.496-45.255 0s-12.497-32.758 0-45.254l672-672zM575.613 569.069l-44.707 44.707c7.846 5.904 15.846 11.453 23.533 16.016 9.2 5.459 22.838 12.579 36.973 13.222 16.224 0.736 31.446-5.302 42.47-10.976 11.741-6.042 22.79-13.824 31.744-20.944 1.053-0.838 2.134-1.232 2.883-1.341 0.403-0.061 0.611-0.032 0.688-0.013l121.094 59.075c-5.578 29.331-20.381 66.771-42.39 92.621-11.229 13.187-23.197 22.038-35.389 26.32-11.597 4.074-25.402 4.762-42.653-1.677-48.422-18.070-103.405-53.152-151.69-89.261-14.026-10.49-27.299-20.922-39.469-30.842l-44.387 44.39c14.186 11.683 29.84 24.061 46.445 36.48 49.808 37.248 110.448 76.56 167.261 97.76 30.275 11.299 59.149 11.238 85.197 2.086 25.45-8.938 46.090-25.786 62.246-44.755 31.866-37.424 50.634-88.144 57.059-126.246 4.72-27.987-11.776-51.77-33.136-62.192l-122.81-59.91c-24.41-11.907-51.462-6-69.834 8.611-7.008 5.571-14.544 10.739-21.443 14.291-5.574 2.867-8.931 3.741-10.314 4.003l-0.093-0.038c-0.992-0.4-3.731-1.514-8.57-4.387-3.277-1.942-6.87-4.301-10.71-7.002zM313.636 589.683l44.389-44.387c-9.92-12.17-20.349-25.44-30.838-39.466-36.109-48.285-71.192-103.267-89.263-151.69-6.437-17.251-5.748-31.056-1.675-42.652 4.283-12.192 13.134-24.16 26.319-35.388 25.849-22.012 63.291-36.814 92.622-42.392l59.075 121.095c0.019 0.077 0.048 0.285-0.013 0.688-0.109 0.752-0.502 1.83-1.341 2.883-7.12 8.954-14.902 20.003-20.944 31.744-5.674 11.024-11.715 26.246-10.976 42.47 0.643 14.134 7.763 27.773 13.222 36.973 4.563 7.686 10.109 15.683 16.013 23.53l44.707-44.707c-2.701-3.837-5.056-7.434-7.002-10.707-2.87-4.838-3.984-7.578-4.387-8.57l-0.038-0.093c0.266-1.382 1.139-4.739 4.006-10.314 3.552-6.899 8.72-14.435 14.291-21.443 14.611-18.371 20.518-45.424 8.611-69.834l-59.91-122.809c-10.422-21.362-34.205-37.856-62.192-33.137-38.103 6.425-88.824 25.194-126.246 57.060-18.972 16.155-35.817 36.795-44.757 62.246-9.15 26.048-9.211 54.921 2.087 85.196 21.202 56.813 60.513 117.453 97.761 167.261 12.417 16.605 24.795 32.256 36.478 46.442z" + ], + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "isMulticolor": false, + "isMulticolor2": false, + "colorPermutations": { + "1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101": [ + { "f": 2 } + ] + }, + "tags": ["phone-disabled"], + "grid": 0 + }, + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "properties": { "order": 130, "id": 143, "name": "phone-disabled", "prevSize": 32, "code": 59804 }, + "setIdx": 0, + "setId": 4, + "iconIdx": 147 + }, + { + "icon": { + "paths": [ + "M801.123 657.59c22.474 7.734 50.954 2.582 67.408-20.544 22.4-31.488 44.992-80.624 48.922-129.619 1.99-24.838-0.691-51.344-12.368-75.661-11.949-24.89-32.32-45.35-61.718-58.768-55.165-25.181-125.84-40.262-187.398-49.146-61.946-8.936-117.494-11.98-143.965-11.98v62.469c23.2 0 75.757 2.787 135.043 11.341 59.677 8.611 123.36 22.682 170.381 44.144 16.749 7.645 26.022 17.894 31.344 28.973 5.59 11.651 7.795 26.371 6.413 43.635-2.717 33.843-18.723 70.784-35.52 95.469l-127.398-43.853c-0.067-0.042-0.237-0.17-0.48-0.496-0.451-0.608-0.934-1.651-1.088-2.989-1.296-11.363-3.606-24.678-7.635-37.254-3.786-11.808-10.278-26.842-22.272-37.792-10.451-9.539-25.126-14.15-35.494-16.794-11.914-3.040-25.568-5.181-38.944-6.717-26.858-3.088-55.773-4.093-74.352-4.093v62.47c16.902 0 43.338 0.938 67.219 3.683 11.99 1.379 22.554 3.123 30.64 5.187 5.45 1.389 8.173 2.541 9.162 2.957l0.093 0.038c0.79 1.165 2.544 4.154 4.458 10.125 2.368 7.392 4.045 16.374 5.059 25.267 2.659 23.325 17.61 46.63 43.29 55.469l129.203 44.477zM222.875 657.584c-22.474 7.738-50.954 2.586-67.407-20.544-22.4-31.485-44.993-80.621-48.922-129.616-1.992-24.838 0.691-51.344 12.366-75.664 11.949-24.886 32.321-45.347 61.719-58.765 55.165-25.181 125.841-40.262 187.4-49.146 61.946-8.937 117.494-11.98 143.965-11.98v62.47c-23.2 0-75.757 2.787-135.046 11.341-59.674 8.608-123.358 22.682-170.378 44.144-16.748 7.645-26.024 17.894-31.343 28.973-5.593 11.651-7.797 26.371-6.412 43.635 2.714 33.843 18.721 70.784 35.518 95.469l127.4-43.856c0.067-0.038 0.234-0.166 0.477-0.493 0.454-0.611 0.938-1.651 1.091-2.989 1.296-11.363 3.606-24.682 7.635-37.254 3.786-11.808 10.275-26.842 22.272-37.792 10.448-9.542 25.126-14.15 35.491-16.794 11.917-3.040 25.568-5.181 38.947-6.717 26.858-3.088 55.77-4.093 74.349-4.093v62.47c-16.899 0-43.338 0.938-67.216 3.683-11.99 1.376-22.554 3.123-30.64 5.187-5.453 1.389-8.176 2.541-9.162 2.957l-0.093 0.038c-0.79 1.162-2.547 4.154-4.458 10.125-2.371 7.389-4.045 16.371-5.059 25.267-2.659 23.322-17.61 46.627-43.293 55.469l-129.202 44.474z" + ], + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "isMulticolor": false, + "isMulticolor2": false, + "colorPermutations": { + "1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101": [ + { "f": 2 } + ] + }, + "tags": ["phone-end"], + "grid": 0 + }, + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "properties": { "order": 131, "id": 144, "name": "phone-end", "prevSize": 32, "code": 59805 }, + "setIdx": 0, + "setId": 4, + "iconIdx": 148 + }, + { + "icon": { + "paths": [ + "M410.502 204.616c-10.419-21.362-34.202-37.856-62.189-33.137-38.104 6.425-88.825 25.194-126.247 57.060-18.972 16.155-35.817 36.795-44.757 62.246-9.15 26.048-9.211 54.919 2.087 85.195 21.202 56.816 60.513 117.456 97.761 167.264 37.482 50.122 74.609 91.552 93.329 110.269l44.173-44.173c-16.406-16.403-51.6-55.536-87.472-103.51-36.11-48.285-71.192-103.264-89.264-151.69-6.437-17.248-5.748-31.056-1.675-42.651 4.283-12.192 13.134-24.16 26.319-35.388 25.849-22.012 63.292-36.814 92.623-42.392l59.075 121.097c0.019 0.074 0.045 0.285-0.013 0.688-0.112 0.749-0.502 1.827-1.344 2.883-7.117 8.95-14.899 20-20.941 31.741-5.674 11.024-11.715 26.246-10.976 42.47 0.643 14.138 7.76 27.773 13.222 36.973 6.275 10.576 14.416 21.741 22.79 32.291 16.806 21.171 36.541 42.326 49.68 55.466l44.173-44.173c-11.952-11.952-29.981-31.309-44.928-50.134-7.504-9.453-13.738-18.157-17.997-25.334-2.87-4.838-3.984-7.578-4.387-8.57l-0.038-0.093c0.266-1.382 1.139-4.736 4.006-10.31 3.552-6.902 8.717-14.438 14.291-21.443 14.611-18.374 20.518-45.424 8.611-69.837l-59.914-122.808zM819.386 613.504c21.36 10.419 37.856 34.202 33.136 62.189-6.426 38.106-25.194 88.826-57.059 126.246-16.157 18.973-36.797 35.818-62.246 44.758-26.048 9.149-54.922 9.21-85.197-2.086-56.813-21.203-117.453-60.515-167.261-97.763-50.122-37.482-91.555-74.608-110.272-93.328l44.173-44.173c16.403 16.406 55.539 51.6 103.51 87.472 48.285 36.109 103.267 71.194 151.69 89.264 17.248 6.435 31.056 5.747 42.653 1.674 12.192-4.282 24.16-13.133 35.386-26.317 22.013-25.85 36.816-63.293 42.394-92.624l-121.094-59.075c-0.077-0.019-0.288-0.048-0.688 0.013-0.752 0.112-1.83 0.502-2.886 1.344-8.95 7.117-20 14.899-31.741 20.941-11.024 5.674-26.246 11.715-42.47 10.976-14.134-0.643-27.773-7.76-36.973-13.222-10.576-6.275-21.741-14.416-32.288-22.79-21.174-16.806-42.33-36.541-55.469-49.68l44.173-44.173c11.952 11.952 31.309 29.981 50.134 44.928 9.453 7.504 18.16 13.738 25.334 17.997 4.838 2.87 7.578 3.984 8.57 4.387l0.093 0.038c1.382-0.266 4.739-1.139 10.314-4.006 6.899-3.552 14.435-8.717 21.44-14.291 18.374-14.611 45.427-20.518 69.837-8.611l122.81 59.914z" + ], + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "isMulticolor": false, + "isMulticolor2": false, + "colorPermutations": { + "1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101": [ + { "f": 2 } + ] + }, + "tags": ["phone"], + "grid": 0 + }, + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "properties": { "order": 68, "id": 145, "name": "phone", "prevSize": 32, "code": 59806 }, + "setIdx": 0, + "setId": 4, + "iconIdx": 149 + }, + { + "icon": { + "paths": [ + "M512.608 128.019c-72.378-1.376-144.906 25.78-199.307 80.524-54.654 54.999-89.301 136.056-89.301 239.464 0 80.234 44.5 174.87 97.546 252.806 53.066 77.965 120.829 148.157 176.141 175.821l15.194 7.6 14.81-8.326c217.158-122.154 272.31-333.882 272.31-427.898 0-101.731-27.923-181.776-80.179-236.932-52.346-55.248-125.296-81.503-207.213-83.060zM288 448.006c0-88.589 29.353-152.745 70.698-194.351 41.6-41.862 97.072-62.705 152.694-61.648 68.992 1.311 124.038 23.054 161.968 63.088 38.019 40.126 62.64 102.649 62.64 192.914 0 74.55-44.691 253.677-224.176 363.030-39.683-25.61-92.186-79.853-137.37-146.237-50.954-74.864-86.454-156.221-86.454-216.797zM544 416c0-17.674-14.326-32-32-32s-32 14.326-32 32c0 17.674 14.326 32 32 32s32-14.326 32-32zM608 416c0 53.021-42.979 96-96 96s-96-42.979-96-96c0-53.021 42.979-96 96-96s96 42.979 96 96z" + ], + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "isMulticolor": false, + "isMulticolor2": false, + "colorPermutations": { + "1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101": [ + { "f": 2 } + ] + }, + "tags": ["pin-map"], + "grid": 0 + }, + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "properties": { "order": 69, "id": 146, "name": "pin-map", "prevSize": 32, "code": 59807 }, + "setIdx": 0, + "setId": 4, + "iconIdx": 150 + }, + { + "icon": { + "paths": [ + "M428.346 199.399c17.542-17.543 45.984-17.543 63.53 0 17.542 17.543 17.542 45.986 0 63.53l-43.661 43.659 177.83 177.828 60.339-60.339c24.992-24.995 65.517-24.995 90.509 0l45.254 45.254-331.869 331.869-45.254-45.254c-24.992-24.995-24.992-65.517 0-90.509l60.339-60.339-177.827-177.83-43.661 43.661c-17.543 17.542-45.987 17.542-63.53 0s-17.543-45.987 0-63.53l208.001-207.999zM175.090 362.144c-42.537 42.538-42.537 111.501 0 154.038 42.001 42.003 109.771 42.531 152.42 1.587l87.334 87.334-15.075 15.075c-49.987 49.987-49.987 131.034 0 181.021l67.882 67.882c12.496 12.496 32.758 12.496 45.254 0l167.936-167.933 88.013 92.003c12.218 12.771 32.474 13.219 45.245 1.002 12.771-12.214 13.219-32.47 1.002-45.242l-88.995-93.030 163.923-163.923c12.499-12.496 12.499-32.758 0-45.254l-67.882-67.882c-49.987-49.987-131.030-49.987-181.018 0l-15.907 15.907-87.325-87.323c41.766-42.596 41.51-110.983-0.768-153.262-42.538-42.537-111.504-42.537-154.042 0l-207.998 208z" + ], + "width": 1056, + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "isMulticolor": false, + "isMulticolor2": false, + "colorPermutations": { + "1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101": [ + { "f": 2 } + ] + }, + "tags": ["pin"], + "grid": 0 + }, + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "properties": { "order": 70, "id": 147, "name": "pin", "prevSize": 32, "code": 59808 }, + "setIdx": 0, + "setId": 4, + "iconIdx": 151 + }, + { + "icon": { + "paths": [ + "M513.354 160c17.674 0 32 13.133 32 29.333v645.332c0 16.202-14.326 29.334-32 29.334s-32-13.133-32-29.334v-645.332c0-16.2 14.326-29.333 32-29.333z" + ], + "width": 1056, + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "isMulticolor": false, + "isMulticolor2": false, + "colorPermutations": { + "1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101": [ + { "f": 2 } + ] + }, + "tags": ["Pipe"], + "grid": 0 + }, + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "properties": { "order": 71, "id": 148, "name": "Pipe", "prevSize": 32, "code": 59809 }, + "setIdx": 0, + "setId": 4, + "iconIdx": 152 + }, + { + "icon": { + "paths": [ + "M512 928.003c229.75 0 416-186.25 416-416s-186.25-415.999-416-415.999c-229.75 0-416 186.25-416 415.999s186.25 416 416 416zM451.846 357.19l195.258 136.768c14.205 9.952 18.496 30.874 9.578 46.73-2.429 4.323-5.706 7.978-9.578 10.691l-195.258 136.768c-14.205 9.952-32.95 5.165-41.866-10.691-3.037-5.398-4.646-11.645-4.646-18.019v-273.536c0-18.72 13.597-33.894 30.368-33.894 5.712 0 11.309 1.795 16.144 5.184z" + ], + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "isMulticolor": false, + "isMulticolor2": false, + "colorPermutations": { + "1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101": [ + { "f": 2 } + ] + }, + "tags": ["play-filled"], + "grid": 0 + }, + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "properties": { "order": 72, "id": 149, "name": "play-filled", "prevSize": 32, "code": 59810 }, + "setIdx": 0, + "setId": 4, + "iconIdx": 153 + }, + { + "icon": { + "paths": [ + "M864 512c0-194.404-157.597-352-352-352s-352 157.596-352 352c0 194.403 157.596 352 352 352s352-157.597 352-352zM928 512c0 229.75-186.25 416-416 416s-416-186.25-416-416c0-229.75 186.25-416 416-416s416 186.25 416 416zM451.85 357.187l195.254 136.768c14.208 9.949 18.496 30.87 9.581 46.726-2.432 4.326-5.706 7.981-9.581 10.694l-195.254 136.768c-14.208 9.952-32.954 5.165-41.869-10.694-3.037-5.398-4.646-11.642-4.646-18.016v-273.536c0-18.723 13.597-33.898 30.371-33.898 5.709 0 11.306 1.798 16.144 5.187z" + ], + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "isMulticolor": false, + "isMulticolor2": false, + "colorPermutations": { + "1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101": [ + { "f": 2 } + ] + }, + "tags": ["play"], + "grid": 0 + }, + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "properties": { "order": 73, "id": 150, "name": "play", "prevSize": 32, "code": 59811 }, + "setIdx": 0, + "setId": 4, + "iconIdx": 154 + }, + { + "icon": { + "paths": [ + "M325.686 430.88l224.49 224.49-126.118 126.118-224.491-224.49 126.12-126.118zM370.941 385.626l192.115-192.117 224.49 224.491-192.115 192.115-224.49-224.49zM585.683 125.627c-12.496-12.497-32.758-12.497-45.254 0l-408.745 408.745c-12.497 12.496-12.497 32.758 0 45.254l269.746 269.747c5.229 5.229 11.818 8.269 18.63 9.123v0.067h0.57c2.278 0.243 4.576 0.243 6.851 0h440.579c17.674 0 32-14.33 32-32 0-17.674-14.326-32.003-32-32.003h-366.566l353.936-353.933c12.496-12.496 12.496-32.758 0-45.254l-269.747-269.746z" + ], + "width": 1056, + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "isMulticolor": false, + "isMulticolor2": false, + "colorPermutations": { + "1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101": [ + { "f": 2 } + ] + }, + "tags": ["prune"], + "grid": 0 + }, + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "properties": { "order": 77, "id": 154, "name": "prune", "prevSize": 32, "code": 59817 }, + "setIdx": 0, + "setId": 4, + "iconIdx": 155 + }, + { + "icon": { + "paths": [ + "M230.486 636.688c-14.92-9.472-34.694-5.056-44.167 9.862-9.473 14.922-5.057 34.694 9.862 44.166l298.666 189.632c10.47 6.646 23.837 6.646 34.304 0l298.669-189.632c14.918-9.472 19.334-29.245 9.862-44.166-9.475-14.918-29.248-19.334-44.166-9.862l-281.517 178.739-281.514-178.739zM186.319 494.848c9.473-14.922 29.247-19.334 44.167-9.862l281.514 178.739 281.517-178.739c14.918-9.472 34.691-5.059 44.166 9.862 9.472 14.918 5.056 34.694-9.862 44.166l-298.669 189.632c-10.467 6.646-23.834 6.646-34.304 0l-298.666-189.632c-14.92-9.472-19.335-29.248-9.862-44.166zM529.152 143.657l298.669 189.629c9.245 5.872 14.848 16.064 14.848 27.014 0 10.954-5.603 21.146-14.848 27.014l-298.669 189.632c-10.467 6.646-23.834 6.646-34.304 0l-298.666-189.632c-9.246-5.869-14.848-16.061-14.848-27.014 0-10.95 5.602-21.142 14.848-27.014l298.666-189.629c10.47-6.647 23.837-6.647 34.304 0zM273.035 360.301l238.965 151.725 238.966-151.725-238.966-151.724-238.965 151.724z" + ], + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "isMulticolor": false, + "isMulticolor2": false, + "colorPermutations": { + "1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101": [ + { "f": 2 } + ] + }, + "tags": ["queue"], + "grid": 0 + }, + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "properties": { "order": 78, "id": 155, "name": "queue", "prevSize": 32, "code": 59818 }, + "setIdx": 0, + "setId": 4, + "iconIdx": 156 + }, + { + "icon": { + "paths": [ + "M161.352 800c-11.706 0-22.477-6.39-28.087-16.666s-5.161-22.79 1.169-32.64l112.304-174.694h-69.387c-17.673 0-32-14.326-32-32v-288c0-17.673 14.327-32 32-32h255.999c17.674 0 32 14.327 32 32v288c0 6.138-1.763 12.144-5.082 17.306l-143.999 224c-5.888 9.158-16.029 14.694-26.918 14.694h-128zM332.269 561.306l-112.304 174.694h51.916l129.469-201.398v-246.602h-191.999v224h96c11.706 0 22.479 6.39 28.085 16.666 5.61 10.275 5.162 22.79-1.168 32.64zM577.35 800c-11.706 0-22.477-6.39-28.086-16.666s-5.162-22.79 1.171-32.64l112.304-174.694h-69.389c-17.67 0-32-14.326-32-32v-288c0-17.673 14.33-32 32-32h256c17.674 0 32 14.327 32 32v288c0 6.138-1.763 12.144-5.082 17.306l-144 224c-5.888 9.158-16.029 14.694-26.918 14.694h-128zM748.269 561.306l-112.304 174.694h51.917l129.469-201.398v-246.602h-192v224h96c11.706 0 22.48 6.39 28.086 16.666 5.61 10.275 5.162 22.79-1.168 32.64z" + ], + "width": 1056, + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "isMulticolor": false, + "isMulticolor2": false, + "colorPermutations": { + "1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101": [ + { "f": 2 } + ] + }, + "tags": ["quote"], + "grid": 0 + }, + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "properties": { "order": 79, "id": 156, "name": "quote", "prevSize": 32, "code": 59819 }, + "setIdx": 0, + "setId": 4, + "iconIdx": 157 + }, + { + "icon": { + "paths": [ + "M800 560c0 176.73-143.27 320-320 320-176.731 0-320-143.27-320-320s143.269-320 320-320v-64c-212.077 0-384 171.923-384 384s171.923 384 384 384c212.077 0 384-171.923 384-384 0-10.778-0.445-21.45-1.315-32h-64.266c1.046 10.525 1.581 21.2 1.581 32zM800 128c0-17.673-14.326-32-32-32s-32 14.327-32 32v112h-112c-17.674 0-32 14.327-32 32s14.326 32 32 32h112v112c0 17.674 14.326 32 32 32s32-14.326 32-32v-112h112c17.674 0 32-14.327 32-32s-14.326-32-32-32h-112v-112zM384 528c35.347 0 64-28.653 64-64s-28.653-64-64-64c-35.347 0-64 28.653-64 64s28.653 64 64 64zM640 464c0 35.347-28.653 64-64 64s-64-28.653-64-64c0-35.347 28.653-64 64-64s64 28.653 64 64zM329.805 605.075c-10.451-14.25-30.477-17.331-44.728-6.88s-17.333 30.477-6.882 44.73c37.658 51.35 77.754 84.624 119.178 102.662 41.741 18.179 82.797 19.99 120.362 11.651 73.469-16.307 132.211-70.87 164.070-114.314 10.451-14.253 7.37-34.278-6.88-44.73-14.253-10.451-34.278-7.37-44.73 6.88-26.81 36.557-73.664 77.994-126.33 89.686-25.501 5.661-52.643 4.474-80.938-7.85-28.608-12.461-60.381-37.187-93.123-81.837z" + ], + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "isMulticolor": false, + "isMulticolor2": false, + "colorPermutations": { + "1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101": [ + { "f": 2 } + ] + }, + "tags": ["reaction-add"], + "grid": 0 + }, + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "properties": { "order": 132, "id": 157, "name": "reaction-add", "prevSize": 32, "code": 59820 }, + "setIdx": 0, + "setId": 4, + "iconIdx": 158 + }, + { + "icon": { + "paths": [ + "M832 512c0-176.73-143.27-320-320-320s-320 143.27-320 320c0 176.73 143.27 320 320 320s320-143.27 320-320zM896 512c0 212.077-171.923 384-384 384s-384-171.923-384-384c0-212.077 171.923-384 384-384s384 171.923 384 384zM512 704c-106.038 0-192-85.962-192-192s85.962-192 192-192c106.038 0 192 85.962 192 192s-85.962 192-192 192z" + ], + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "isMulticolor": false, + "isMulticolor2": false, + "colorPermutations": { + "1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101": [ + { "f": 2 } + ] + }, + "tags": ["record"], + "grid": 0 + }, + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "properties": { "order": 133, "id": 158, "name": "record", "prevSize": 32, "code": 59821 }, + "setIdx": 0, + "setId": 4, + "iconIdx": 159 + }, + { + "icon": { + "paths": [ + "M896 512h-63.984c0-175.414-145.754-320-328.518-320-115.27 0-215.818 57.513-274.362 144h110.768c17.674 0 32 14.326 32 32s-14.326 32-32 32h-179.904c-17.673 0-32-14.326-32-32v-192c0-17.673 14.327-32 32-32s32 14.327 32 32v102.32c71.751-91.401 184.589-150.32 311.498-150.32 216.781 0 392.502 171.923 392.502 384 0 1.114 0.010 2.227 0 3.338v-3.338z", + "M127.997 512h63.986c0 175.414 145.751 320 328.519 320 115.27 0 215.818-57.514 274.358-144h-110.768c-17.67 0-32-14.326-32-32s14.33-32 32-32h179.907c17.67 0 32 14.326 32 32v192c0 17.674-14.33 32-32 32-17.674 0-32-14.326-32-32v-102.32c-71.754 91.402-184.592 150.32-311.498 150.32-216.782 0-392.505-171.923-392.505-384 0-1.082-0.009-2.166 0-3.245v3.245z" + ], + "attrs": [{ "fill": "rgb(108, 114, 122)" }, { "fill": "rgb(108, 114, 122)" }], + "isMulticolor": false, + "isMulticolor2": false, + "colorPermutations": { + "1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101": [ + { "f": 2 }, + { "f": 2 } + ] + }, + "tags": ["refresh"], + "grid": 0 + }, + "attrs": [{ "fill": "rgb(108, 114, 122)" }, { "fill": "rgb(108, 114, 122)" }], + "properties": { "order": 134, "id": 159, "name": "refresh", "prevSize": 32, "code": 59822 }, + "setIdx": 0, + "setId": 4, + "iconIdx": 160 + }, + { + "icon": { + "paths": [ + "M713.718 450.362c0-142.689-115.824-258.362-258.701-258.362-142.878 0-258.704 115.673-258.704 258.362 0 142.691 115.826 258.365 258.704 258.365 142.877 0 258.701-115.674 258.701-258.365zM659.302 699.965c-55.645 45.475-126.774 72.762-204.285 72.762-178.271 0-322.788-144.326-322.788-322.365 0-178.035 144.517-322.362 322.788-322.362 178.269 0 322.787 144.327 322.787 322.362 0 77.408-27.318 148.442-72.854 204.013l186.838 186.592c12.608 12.589 12.608 33.002 0 45.59-12.605 12.589-33.043 12.589-45.648 0l-186.838-186.592z" + ], + "width": 1056, + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "isMulticolor": false, + "isMulticolor2": false, + "colorPermutations": { + "1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101": [ + { "f": 2 } + ] + }, + "tags": ["search"], + "grid": 0 + }, + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "properties": { "order": 136, "id": 160, "name": "search", "prevSize": 32, "code": 59823 }, + "setIdx": 0, + "setId": 4, + "iconIdx": 161 + }, + { + "icon": { + "paths": [ + "M891.494 238.96l-297.475 637.446c-16.854 36.115-69.622 31.459-79.891-7.050l-66-247.498 151.248-189.059-219.994 109.997-226.833-113.418c-35.43-17.715-29.696-69.949 8.733-79.555l681.197-170.3c34.842-8.71 64.202 26.892 49.014 59.436z" + ], + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "isMulticolor": false, + "isMulticolor2": false, + "colorPermutations": { + "1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101": [ + { "f": 2 } + ] + }, + "tags": ["send-filled"], + "grid": 0 + }, + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "properties": { "order": 137, "id": 161, "name": "send-filled", "prevSize": 32, "code": 59824 }, + "setIdx": 0, + "setId": 4, + "iconIdx": 162 + }, + { + "icon": { + "paths": [ + "M878.022 192.974c7.85 9.521 9.526 22.708 4.31 33.891l-298.669 640.002c-5.69 12.195-18.403 19.526-31.811 18.342-13.405-1.184-24.637-10.627-28.106-23.632l-81.619-306.070-285.772-142.886c-11.978-5.987-18.959-18.8-17.498-32.112s11.056-24.307 24.048-27.552l682.665-170.668c11.974-2.993 24.598 1.165 32.451 10.686zM505.821 545.968l57.082 214.051 233.027-499.35-533.58 133.395 203.606 101.802 69.51-52.131c14.141-10.605 34.198-7.741 44.8 6.4 10.605 14.138 7.741 34.195-6.4 44.8l-68.045 51.034z" + ], + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "isMulticolor": false, + "isMulticolor2": false, + "colorPermutations": { + "1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101": [ + { "f": 2 } + ] + }, + "tags": ["send"], + "grid": 0 + }, + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "properties": { "order": 138, "id": 162, "name": "send", "prevSize": 32, "code": 59825 }, + "setIdx": 0, + "setId": 4, + "iconIdx": 163 + }, + { + "icon": { + "paths": [ + "M421.334 256c0-44.183-35.818-80-80-80s-80 35.817-80 80c0 44.183 35.817 80 80 80s80-35.817 80-80zM465.302 288c-14.211 55.206-64.326 96-123.968 96-59.643 0-109.758-40.794-123.968-96h-89.367c-17.673 0-32-14.327-32-32s14.327-32 32-32h89.367c14.209-55.207 64.324-96 123.968-96 59.642 0 109.757 40.793 123.968 96h430.698c17.674 0 32 14.327 32 32s-14.326 32-32 32h-430.698zM96 768c0-17.674 14.327-32 32-32h89.367c14.209-55.206 64.324-96 123.968-96 60.781 0 111.67 42.365 124.742 99.181 4.208-2.038 8.934-3.181 13.923-3.181h416c17.674 0 32 14.326 32 32s-14.326 32-32 32h-416c-4.989 0-9.715-1.142-13.923-3.181-13.072 56.816-63.962 99.181-124.742 99.181-59.643 0-109.758-40.794-123.968-96h-89.367c-17.673 0-32-14.326-32-32zM341.334 848c44.182 0 80-35.818 80-80s-35.818-80-80-80c-44.183 0-80 35.818-80 80s35.817 80 80 80zM796.029 543.757c-14.122 55.331-64.298 96.243-124.029 96.243s-109.904-40.912-124.029-96.243c-1.302 0.16-2.627 0.243-3.971 0.243h-416c-17.673 0-32-14.326-32-32s14.327-32 32-32h416c1.344 0 2.669 0.083 3.971 0.243 14.125-55.331 64.298-96.243 124.029-96.243s109.907 40.912 124.029 96.243c1.302-0.16 2.627-0.243 3.971-0.243h96c17.674 0 32 14.326 32 32s-14.326 32-32 32h-96c-1.344 0-2.669-0.083-3.971-0.243zM752 512c0-44.182-35.818-80-80-80s-80 35.818-80 80c0 44.182 35.818 80 80 80s80-35.818 80-80z" + ], + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "isMulticolor": false, + "isMulticolor2": false, + "colorPermutations": { + "1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101": [ + { "f": 2 } + ] + }, + "tags": ["settings"], + "grid": 0 + }, + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "properties": { "order": 139, "id": 163, "name": "settings", "prevSize": 32, "code": 59826 }, + "setIdx": 0, + "setId": 4, + "iconIdx": 164 + }, + { + "icon": { + "paths": [ + "M690.176 301.708c11.645-11.977 11.645-31.396 0-43.374l-149.091-153.351c-11.645-11.977-30.525-11.977-42.17 0l-149.091 153.351c-11.645 11.977-11.645 31.397 0 43.374s30.525 11.978 42.17 0l98.189-100.993v401.343c0 16.938 13.35 30.669 29.818 30.669s29.818-13.731 29.818-30.669v-401.343l98.189 100.993c11.645 11.978 30.525 11.978 42.17 0z", + "M221.818 379.274h149.091v59.635h-119.272v387.635h536.728v-387.635h-119.274v-59.635h149.091c16.467 0 29.818 13.35 29.818 29.818v447.274c0 16.467-13.35 29.818-29.818 29.818h-596.364c-16.468 0-29.818-13.35-29.818-29.818v-447.274c0-16.467 13.35-29.818 29.818-29.818z" + ], + "attrs": [{ "fill": "rgb(108, 114, 122)" }, { "fill": "rgb(108, 114, 122)" }], + "isMulticolor": false, + "isMulticolor2": false, + "colorPermutations": { + "1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101": [ + { "f": 2 }, + { "f": 2 } + ] + }, + "tags": ["share"], + "grid": 0 + }, + "attrs": [{ "fill": "rgb(108, 114, 122)" }, { "fill": "rgb(108, 114, 122)" }], + "properties": { "order": 244, "id": 164, "name": "share", "prevSize": 32, "code": 59827 }, + "setIdx": 0, + "setId": 4, + "iconIdx": 165 + }, + { + "icon": { + "paths": [ + "M664.083 405.072c11.638-13.299 10.288-33.517-3.011-45.155s-33.517-10.288-45.155 3.011l-125.251 143.142-39.917-45.619c-11.638-13.299-31.853-14.646-45.155-3.011-13.299 11.638-14.646 31.856-3.008 45.155l64 73.142c6.077 6.944 14.854 10.928 24.080 10.928 9.229 0 18.006-3.984 24.083-10.928l149.334-170.666z", + "M541.99 143.050c-12.854-3.634-26.448-3.753-39.363-0.345l-264.724 69.849c-29.793 7.861-52.548 33.743-54.753 65.633-23.589 341.205 187.752 520.29 276.51 579.794 33.136 22.211 75.363 22.144 108.416-0.259 88.218-59.792 297.117-239.072 272.611-580.214-2.25-31.321-24.339-56.895-53.504-65.14l-245.194-69.318zM518.957 204.587c1.843-0.487 3.786-0.47 5.622 0.049l245.194 69.318c4.294 1.214 6.838 4.781 7.082 8.139 22.15 308.362-165.306 468.851-244.685 522.65-11.437 7.754-25.363 7.792-36.87 0.077-80.086-53.686-269.606-214.054-248.301-522.218 0.234-3.389 2.833-7.004 7.233-8.164l264.725-69.849z" + ], + "attrs": [{ "fill": "rgb(108, 114, 122)" }, { "fill": "rgb(108, 114, 122)" }], + "isMulticolor": false, + "isMulticolor2": false, + "colorPermutations": { + "1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101": [ + { "f": 2 }, + { "f": 2 } + ] + }, + "tags": ["shield-check"], + "grid": 0 + }, + "attrs": [{ "fill": "rgb(108, 114, 122)" }, { "fill": "rgb(108, 114, 122)" }], + "properties": { "order": 80, "id": 165, "name": "shield-check", "prevSize": 32, "code": 59828 }, + "setIdx": 0, + "setId": 4, + "iconIdx": 166 + }, + { + "icon": { + "paths": [ + "M513.302 142.705c12.915-3.408 26.509-3.289 39.36 0.345l245.197 69.318c29.165 8.245 51.254 33.818 53.504 65.14 24.506 341.142-184.397 520.422-272.611 580.214-33.053 22.403-75.28 22.47-108.416 0.259-88.762-59.504-300.101-238.589-276.511-579.794 2.205-31.89 24.96-57.772 54.753-65.633l264.725-69.849zM535.251 204.637c-1.834-0.519-3.776-0.536-5.622-0.050l-264.723 69.849c-4.4 1.161-6.999 4.775-7.234 8.164-21.305 308.164 168.216 468.532 248.299 522.218 11.507 7.715 25.434 7.677 36.874-0.077 79.379-53.798 266.832-214.288 244.682-522.65-0.24-3.358-2.784-6.925-7.078-8.139l-245.197-69.317z", + "M490.672 337.334c-19.76 4.672-47.245 12.31-81.149 24.554 20.81 108.771 53.219 187.014 81.149 238.832v-263.386zM492.867 271.316c33.77-6.898 61.805 19.742 61.805 51.125v360.963c0 17.024-10.333 31.747-25.642 37.52-15.67 5.91-33.779 1.331-44.861-12.675-32.656-41.267-106.323-152.95-141.19-354.563-3.242-18.746 7.066-37.695 25.328-44.726 56.186-21.63 99.094-32.442 124.56-37.644z" + ], + "attrs": [{ "fill": "rgb(108, 114, 122)" }, { "fill": "rgb(108, 114, 122)" }], + "isMulticolor": false, + "isMulticolor2": false, + "colorPermutations": { + "1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101": [ + { "f": 2 }, + { "f": 2 } + ] + }, + "tags": ["shield"], + "grid": 0 + }, + "attrs": [{ "fill": "rgb(108, 114, 122)" }, { "fill": "rgb(108, 114, 122)" }], + "properties": { "order": 81, "id": 166, "name": "shield-alt", "prevSize": 32, "code": 59829 }, + "setIdx": 0, + "setId": 4, + "iconIdx": 167 + }, + { + "icon": { + "paths": [ + "M778.64 213.336c-17.674 0-32 14.327-32 32v533.333c0 17.674 14.326 32 32 32s32-14.326 32-32v-533.333c0-17.673-14.326-32-32-32z", + "M600.87 341.334c-17.674 0-32 14.33-32 32v405.334c0 17.674 14.326 32 32 32s32-14.326 32-32v-405.334c0-17.67-14.326-32-32-32z", + "M423.104 810.669c-17.674 0-32-14.326-32-32v-277.334c0-17.67 14.326-32 32-32 17.67 0 32 14.33 32 32v277.334c0 17.674-14.33 32-32 32z", + "M245.333 597.334c-17.673 0-32 14.33-32 32v149.334c0 17.674 14.327 32 32 32s32-14.326 32-32v-149.334c0-17.67-14.327-32-32-32z" + ], + "attrs": [ + { "fill": "rgb(108, 114, 122)" }, + { "fill": "rgb(108, 114, 122)" }, + { "fill": "rgb(108, 114, 122)" }, + { "fill": "rgb(108, 114, 122)" } + ], + "isMulticolor": false, + "isMulticolor2": false, + "colorPermutations": { + "1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101": [ + { "f": 2 }, + { "f": 2 }, + { "f": 2 }, + { "f": 2 } + ] + }, + "tags": ["signal"], + "grid": 0 + }, + "attrs": [ + { "fill": "rgb(108, 114, 122)" }, + { "fill": "rgb(108, 114, 122)" }, + { "fill": "rgb(108, 114, 122)" }, + { "fill": "rgb(108, 114, 122)" } + ], + "properties": { "order": 82, "id": 167, "name": "signal", "prevSize": 32, "code": 59830 }, + "setIdx": 0, + "setId": 4, + "iconIdx": 168 + }, + { + "icon": { + "paths": [ + "M289.362 192c13.364 0 25.321 8.305 29.987 20.829l96.001 257.699c6.17 16.56-2.256 34.986-18.816 41.155-16.563 6.17-34.989-2.253-41.158-18.816l-20.477-54.963h-91.074l-20.476 54.963c-6.17 16.563-24.597 24.986-41.158 18.816s-24.986-24.595-18.816-41.155l96-257.699c4.666-12.524 16.622-20.829 29.987-20.829zM311.058 373.904l-21.695-58.238-21.695 58.238h43.391z", + "M522.131 626.435c-12.154 12.829-11.606 33.082 1.222 45.238l160 151.587c12.342 11.693 31.674 11.693 44.016 0l160-151.587c12.832-12.157 13.376-32.41 1.222-45.238s-32.41-13.376-45.238-1.222l-105.99 100.419v-501.632c0-17.673-14.326-32-32-32s-32 14.327-32 32v501.632l-105.994-100.419c-12.829-12.154-33.082-11.606-45.238 1.222z", + "M193.362 570.947c-17.673 0-32 14.326-32 32 0 17.67 14.327 32 32 32h116.145l-139.065 142.73c-8.979 9.216-11.565 22.915-6.564 34.771 5.001 11.853 16.617 19.562 29.484 19.562h192.001c17.674 0 32-14.326 32-32s-14.326-32-32-32h-116.146l139.064-142.733c8.979-9.216 11.565-22.915 6.563-34.768-4.998-11.856-16.614-19.562-29.482-19.562h-192.001z" + ], + "width": 1056, + "attrs": [{ "fill": "rgb(108, 114, 122)" }, { "fill": "rgb(108, 114, 122)" }, { "fill": "rgb(108, 114, 122)" }], + "isMulticolor": false, + "isMulticolor2": false, + "colorPermutations": { + "1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101": [ + { "f": 2 }, + { "f": 2 }, + { "f": 2 } + ] + }, + "tags": ["sort-az"], + "grid": 0 + }, + "attrs": [{ "fill": "rgb(108, 114, 122)" }, { "fill": "rgb(108, 114, 122)" }, { "fill": "rgb(108, 114, 122)" }], + "properties": { "order": 83, "id": 168, "name": "sort-az", "prevSize": 32, "code": 59831 }, + "setIdx": 0, + "setId": 4, + "iconIdx": 169 + }, + { + "icon": { + "paths": [ + "M919.258 667.229c12.605-12.253 12.877-32.394 0.605-44.982-12.269-12.589-32.435-12.858-45.040-0.605l-106.112 103.155v-500.8c0-17.568-14.259-31.81-31.853-31.81-17.59 0-31.85 14.242-31.85 31.81v500.8l-106.115-103.155c-12.605-12.253-32.768-11.984-45.040 0.605-12.269 12.589-11.997 32.73 0.608 44.982l160.179 155.718c12.368 12.019 32.070 12.019 44.435 0l160.182-155.718zM560.659 333.667c17.59 0 31.85-14.243 31.85-31.811s-14.259-31.809-31.85-31.809h-432.49c-17.591 0-31.852 14.242-31.852 31.809s14.26 31.811 31.852 31.811h432.49zM464.55 536.099c17.59 0 31.85-14.243 31.85-31.811 0-17.565-14.259-31.808-31.85-31.808h-336.381c-17.591 0-31.852 14.24-31.852 31.808s14.26 31.811 31.852 31.811h336.381zM384.458 722.96c17.594 0 31.853-14.24 31.853-31.808s-14.259-31.811-31.853-31.811h-256.289c-17.591 0-31.852 14.243-31.852 31.811s14.26 31.808 31.852 31.808h256.289z" + ], + "width": 1056, + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "isMulticolor": false, + "isMulticolor2": false, + "colorPermutations": { + "1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101": [ + { "f": 2 } + ] + }, + "tags": ["sort"], + "grid": 0 + }, + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "properties": { "order": 84, "id": 169, "name": "sort", "prevSize": 32, "code": 59832 }, + "setIdx": 0, + "setId": 4, + "iconIdx": 170 + }, + { + "icon": { + "paths": [ + "M593.939 170.244c-20.867-57.726-103.155-55.816-121.322 2.815l-58.976 190.348h-221.513c-58.947 0-86.549 72.944-42.37 111.968l157.591 139.206-53.209 216.928c-14.17 57.77 51.25 101.936 99.533 67.197l177.741-127.875 177.741 127.875c48.285 34.739 113.706-9.427 99.536-67.197l-53.386-217.651 147.888-139.968c42.032-39.779 13.878-110.483-43.99-110.483h-195.443l-69.821-193.164z" + ], + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "isMulticolor": false, + "isMulticolor2": false, + "colorPermutations": { + "1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101": [ + { "f": 2 } + ] + }, + "tags": ["star-filled"], + "grid": 0 + }, + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "properties": { "order": 85, "id": 170, "name": "star-filled", "prevSize": 32, "code": 59833 }, + "setIdx": 0, + "setId": 4, + "iconIdx": 171 + }, + { + "icon": { + "paths": [ + "M472.618 173.508c18.166-58.554 100.454-60.461 121.322-2.812l69.821 192.91h195.44c57.872 0 86.026 70.611 43.994 110.336l-147.888 139.782 53.386 217.363c14.17 57.696-51.251 101.805-99.536 67.11l-177.741-127.709-177.741 127.709c-48.283 34.691-113.703-9.414-99.533-67.11l53.209-216.64-157.591-139.024c-44.179-38.973-16.577-111.818 42.37-111.818h221.513l58.976-190.098zM603.574 385.334l-69.824-192.91-58.976 190.098c-8.304 26.758-33.085 45.002-61.133 45.002h-221.513l157.59 139.021c17.837 15.731 25.456 40.048 19.789 63.13l-53.209 216.643 177.74-127.709c22.33-16.045 52.426-16.045 74.755 0l177.738 127.709-53.386-217.363c-5.478-22.317 1.456-45.856 18.166-61.648l147.888-139.782h-195.44c-26.957 0-51.024-16.87-60.186-42.189z" + ], + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "isMulticolor": false, + "isMulticolor2": false, + "colorPermutations": { + "1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101": [ + { "f": 2 } + ] + }, + "tags": ["star"], + "grid": 0 + }, + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "properties": { "order": 86, "id": 171, "name": "star", "prevSize": 32, "code": 59834 }, + "setIdx": 0, + "setId": 4, + "iconIdx": 172 + }, + { + "icon": { + "paths": [ + "M499.952 539.082c2.714 0.819 5.462 1.629 8.25 2.429 45.341 13.12 76.643 26.47 93.904 40.048 17.264 13.35 25.894 32.339 25.894 56.966s-9.322 44.074-27.965 58.346c-18.643 14.269-45.456 21.405-80.442 21.405-37.514 0-67.552-8.749-90.106-26.24-15.216-12.077-25.245-27.094-30.090-45.050-3.942-14.605-15.664-27.104-30.794-27.104h-11.843c-15.13 0-27.722 12.378-24.893 27.242 3.514 18.47 10.736 35.514 21.667 51.126 16.339 23.018 39.472 41.2 69.392 54.547 29.92 13.12 62.144 19.68 96.666 19.68 53.168 0 95.632-12.198 127.395-36.595 31.76-24.627 47.642-57.309 47.642-98.048 0-25.549-5.754-47.757-17.261-66.63-6.947-11.626-16.25-22.33-27.91-32.122h99.962c15.13 0 27.395-12.266 27.395-27.395s-12.266-27.392-27.395-27.392h-217.398c-1.709-0.493-3.437-0.986-5.178-1.472-41.658-11.738-71.235-24.627-88.726-38.669-17.261-14.269-25.894-31.645-25.894-52.131 0-25.546 9.091-45.456 27.277-59.725 18.41-14.5 43.958-21.75 76.64-21.75 35.216 0 62.49 8.746 81.824 26.24 13.139 11.731 21.814 26.496 26.022 44.288 3.485 14.723 15.242 27.174 30.371 27.174h11.84c15.13 0 27.702-12.374 24.88-27.238-3.331-17.552-9.974-34.134-19.923-49.75-14.733-23.475-35.677-41.888-62.835-55.237-26.928-13.349-57.654-20.024-92.179-20.024-50.634 0-91.834 13.004-123.594 39.012-31.533 25.779-47.299 58.46-47.299 98.047 0 34.986 12.89 64.445 38.669 88.381 1.050 0.957 2.125 1.907 3.222 2.854h-112.56c-15.13 0-27.395 12.262-27.395 27.392s12.265 27.395 27.395 27.395h225.373z" + ], + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "isMulticolor": false, + "isMulticolor2": false, + "colorPermutations": { + "1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101": [ + { "f": 2 } + ] + }, + "tags": ["strike"], + "grid": 0 + }, + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "properties": { "order": 94, "id": 172, "name": "strike", "prevSize": 32, "code": 59846 }, + "setIdx": 0, + "setId": 4, + "iconIdx": 173 + }, + { + "icon": { + "paths": [ + "M481.35 117.336c0-17.673 14.326-32 32-32s32 14.327 32 32v106.667c0 17.673-14.326 32-32 32s-32-14.327-32-32v-106.667zM771.392 214.631c12.496-12.497 32.758-12.497 45.254 0s12.496 32.758 0 45.255l-75.411 75.413c-12.499 12.496-32.758 12.496-45.254 0-12.499-12.496-12.499-32.759 0-45.255l75.411-75.412zM336.73 694.63c-12.499-12.496-32.759-12.496-45.256 0l-75.495 75.498c-12.497 12.496-12.497 32.755 0 45.254 12.497 12.496 32.758 12.496 45.255 0l75.496-75.498c12.496-12.496 12.496-32.755 0-45.254zM481.35 800.003c0-17.674 14.326-32 32-32s32 14.326 32 32v106.669c0 17.67-14.326 32-32 32s-32-14.33-32-32v-106.669zM213.352 212.001c-12.497 12.497-12.497 32.758 0 45.255l75.349 75.348c12.497 12.496 32.758 12.496 45.254 0 12.499-12.496 12.496-32.757 0-45.254l-75.348-75.349c-12.497-12.497-32.758-12.497-45.255 0zM695.978 739.885c-12.496-12.496-12.496-32.758 0-45.254 12.499-12.496 32.758-12.496 45.254 0l75.331 75.328c12.496 12.496 12.496 32.758 0 45.254-12.499 12.496-32.758 12.496-45.258 0l-75.328-75.328zM87.751 512.003c0 17.674 14.327 32 32 32h106.667c17.673 0 32-14.326 32-32s-14.327-32-32-32h-106.667c-17.673 0-32 14.326-32 32zM801.35 544.003c-17.674 0-32-14.326-32-32s14.326-32 32-32h106.669c17.67 0 32 14.326 32 32s-14.33 32-32 32h-106.669zM668.17 512c0-85.504-69.315-154.816-154.819-154.816-85.507 0-154.819 69.312-154.819 154.816 0 85.507 69.312 154.819 154.819 154.819 85.504 0 154.819-69.312 154.819-154.819zM726.682 512c0 117.821-95.51 213.334-213.331 213.334s-213.335-95.514-213.335-213.334c0-117.821 95.514-213.332 213.335-213.332s213.331 95.511 213.331 213.332z" + ], + "width": 1056, + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "isMulticolor": false, + "isMulticolor2": false, + "colorPermutations": { + "1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101": [ + { "f": 2 } + ] + }, + "tags": ["sun"], + "grid": 0 + }, + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "properties": { "order": 95, "id": 173, "name": "sun", "prevSize": 32, "code": 59847 }, + "setIdx": 0, + "setId": 4, + "iconIdx": 174 + }, + { + "icon": { + "paths": [ + "M930.704 512c0 229.75-186.25 416-416 416s-415.999-186.25-415.999-416c0-229.75 186.25-416 415.999-416s416 186.25 416 416zM570.49 859.603l-41.827-156.102c-4.611 0.33-9.264 0.499-13.958 0.499-4.57 0-9.101-0.16-13.594-0.474l-41.837 156.134c18.058 2.854 36.573 4.339 55.43 4.339 18.986 0 37.616-1.504 55.786-4.397zM438.893 688.451c-47.549-20.454-85.181-59.571-103.674-108.125l-155.029 41.539c33.968 103.485 114.617 185.805 217.045 222.058l41.658-155.472zM162.705 512c0 16.090 1.080 31.926 3.17 47.443l156.906-42.042c-0.051-1.795-0.077-3.594-0.077-5.402 0-4.909 0.186-9.776 0.547-14.595l-156.051-41.814c-2.958 18.368-4.496 37.21-4.496 56.41zM396.31 180.407c-99.16 35.408-177.786 114.033-213.196 213.19l155.535 41.677c19.357-44.352 54.982-79.978 99.334-99.331l-41.674-155.536zM514.704 160c-19.197 0-38.035 1.537-56.4 4.494l41.814 156.053c4.813-0.362 9.68-0.547 14.586-0.547 5.030 0 10.019 0.192 14.95 0.573l41.808-156.021c-18.477-2.995-37.437-4.552-56.758-4.552zM632.512 843.802c102.659-36.451 183.392-119.194 217.094-223.12l-154.973-41.526c-18.291 48.982-56.010 88.493-103.786 109.155l41.664 155.491zM863.699 558.198c1.984-15.117 3.005-30.538 3.005-46.198 0-18.771-1.469-37.197-4.298-55.171l-156.157 41.84c0.301 4.406 0.454 8.851 0.454 13.331 0 1.376-0.013 2.752-0.042 4.122l157.037 42.077zM846.714 394.774c-35.165-99.6-113.885-178.641-213.277-214.247l-41.68 155.559c44.582 19.555 80.314 55.565 99.504 100.342l155.453-41.654zM642.704 512c0-70.691-57.306-128-128-128-70.691 0-128 57.309-128 128s57.309 128 128 128c70.694 0 128-57.309 128-128z" + ], + "width": 1056, + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "isMulticolor": false, + "isMulticolor2": false, + "colorPermutations": { + "1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101": [ + { "f": 2 } + ] + }, + "tags": ["support"], + "grid": 0 + }, + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "properties": { "order": 96, "id": 174, "name": "support", "prevSize": 32, "code": 59848 }, + "setIdx": 0, + "setId": 4, + "iconIdx": 175 + }, + { + "icon": { + "paths": [ + "M368 412.982c-53.592 0-96-42.877-96-94.491 0-51.616 42.408-94.491 96-94.491 53.594 0 96 42.876 96 94.491 0 51.614-42.406 94.491-96 94.491zM368 476.982c88.365 0 160-70.96 160-158.491s-71.635-158.491-160-158.491c-88.365 0-160 70.959-160 158.491s71.635 158.491 160 158.491zM713.6 397.133c-35.92 0-64-28.685-64-62.794s28.080-62.792 64-62.792c35.92 0 64 28.684 64 62.792s-28.080 62.794-64 62.794zM713.6 461.133c70.691 0 128-56.768 128-126.794s-57.309-126.792-128-126.792c-70.691 0-128 56.767-128 126.792s57.309 126.794 128 126.794zM197.459 527.267c27.344-8.707 56.67-9.242 84.319-1.539l48.491 13.51c24.205 6.742 49.882 6.275 73.824-1.347l30.099-9.584c29.475-9.386 61.085-9.962 90.89-1.658 67.962 18.934 114.918 80.333 114.918 150.269v91.987c0 52.518-42.979 95.094-96 95.094h-352c-53.019 0-96-42.576-96-95.094v-103.766c0-62.909 40.999-118.621 101.459-137.872zM264.451 586.758c-15.545-4.333-32.034-4.032-47.407 0.864-33.993 10.822-57.044 42.147-57.044 77.517v103.766c0 17.507 14.327 31.699 32 31.699h352c17.674 0 32-14.192 32-31.699v-91.987c0-41.533-27.885-77.997-68.246-89.242-17.699-4.931-36.474-4.589-53.978 0.986l-30.099 9.584c-35.91 11.434-74.426 12.138-110.737 2.019l-48.489-13.507zM691.2 778.717h140.8c53.021 0 96-42.979 96-96v-30.611c0-56.877-38.614-106.49-93.747-120.454-21.398-5.418-43.853-5.040-65.056 1.098l-16.4 4.746c-21.843 6.323-44.973 6.714-67.018 1.13l-29.805-7.549c-19.907-5.043-40.797-4.691-60.522 1.018-1.066 0.31-2.122 0.634-3.174 0.97 10.71 4.048 20.896 9.539 30.253 16.374l2.429 1.776 15.040 13.206c9.939 8.726 18.141 19.248 24.182 31.014l2.208 4.301 3.677 0.931c33.062 8.374 67.76 7.789 100.525-1.693l16.4-4.749c10.282-2.976 21.171-3.158 31.546-0.531 26.736 6.771 45.462 30.832 45.462 58.413v30.611c0 17.674-14.326 32-32 32h-140.8v64z" + ], + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "isMulticolor": false, + "isMulticolor2": false, + "colorPermutations": { + "1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101": [ + { "f": 2 } + ] + }, + "tags": ["team"], + "grid": 0 + }, + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "properties": { "order": 97, "id": 175, "name": "team", "prevSize": 32, "code": 59849 }, + "setIdx": 0, + "setId": 4, + "iconIdx": 176 + }, + { + "icon": { + "paths": [ + "M223.311 308.609c-47.832 66.7-64.077 147.667-64.077 201.187v1.405l-0.127 1.398c-9.348 103.235 27.547 175.997 86.848 226.374 60.642 51.52 146.602 80.998 235.29 90.893 88.611 9.885 186.221 1.398 263.341-14.973 38.56-8.189 70.928-18.125 93.888-28.128 8.906-3.882 15.837-7.536 20.941-10.765-2.374-1.514-5.245-3.197-8.672-5.030-8.886-4.749-19.059-9.261-29.613-13.894l-1.843-0.81c-9.315-4.083-19.725-8.65-27.613-13.078-14.291-8.026-28.317-17.155-38.544-26.867-5.034-4.781-10.762-11.194-14.595-19.184-4.054-8.454-6.96-21.078-1.261-34.435 30.816-72.186 45.459-111.725 52.554-137.834 6.621-24.378 6.621-36.653 6.621-56.17v-0.179c0-15.51-8.979-86.595-53.776-152.876-43.376-64.174-121.398-125.729-264.832-125.729-129.725 0-207.83 53.576-254.529 118.696zM173.267 272.433c58.132-81.063 154.749-144.433 304.573-144.433 164.896 0 261.594 72.589 315.859 152.878 52.838 78.181 64.416 161.881 64.416 187.641 0 21.654-0.022 40.336-8.797 72.64-7.834 28.835-22.627 68.614-50.048 133.424 5.069 4.032 12.592 9.005 22.506 14.57 5.12 2.877 12.995 6.339 24.054 11.194 10.266 4.506 22.582 9.93 33.891 15.978 10.848 5.798 23.526 13.571 32.954 23.738 9.859 10.63 20.208 28.87 12.816 51.139-4.87 14.672-16.182 25.091-25.61 32.016-10.24 7.52-22.947 14.278-36.858 20.342-27.949 12.176-64.582 23.181-105.68 31.907-82.198 17.453-186.522 26.688-282.909 15.936-96.31-10.746-195.346-43.178-268.313-105.165-74.031-62.893-119.295-154.778-108.551-277.856 0.276-63.411 19.117-157.053 75.696-235.948zM333.952 422.051c0-17.648 14.25-31.955 31.83-31.955h183.008c17.578 0 31.827 14.307 31.827 31.955s-14.25 31.955-31.827 31.955h-183.008c-17.581 0-31.83-14.307-31.83-31.955zM365.782 539.709c-17.581 0-31.83 14.307-31.83 31.955s14.25 31.955 31.83 31.955h224.118c17.578 0 31.827-14.307 31.827-31.955s-14.25-31.955-31.827-31.955h-224.118z" + ], + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "isMulticolor": false, + "isMulticolor2": false, + "colorPermutations": { + "1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101": [ + { "f": 2 } + ] + }, + "tags": ["threads"], + "grid": 0 + }, + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "properties": { "order": 98, "id": 176, "name": "threads", "prevSize": 32, "code": 59850 }, + "setIdx": 0, + "setId": 4, + "iconIdx": 177 + }, + { + "icon": { + "paths": [ + "M353.354 320c0-17.673 14.326-32 32-32h256c17.674 0 32 14.327 32 32s-14.326 32-32 32h-256c-17.674 0-32-14.326-32-32z", + "M385.354 416c-17.674 0-32 14.326-32 32s14.326 32 32 32c17.674 0 32-14.326 32-32s-14.326-32-32-32z", + "M481.354 448c0-17.674 14.326-32 32-32s32 14.326 32 32c0 17.674-14.326 32-32 32s-32-14.326-32-32z", + "M641.354 416c-17.674 0-32 14.326-32 32s14.326 32 32 32c17.674 0 32-14.326 32-32s-14.326-32-32-32z", + "M353.354 576c0-17.674 14.326-32 32-32s32 14.326 32 32c0 17.674-14.326 32-32 32s-32-14.326-32-32z", + "M513.354 544c-17.674 0-32 14.326-32 32s14.326 32 32 32c17.674 0 32-14.326 32-32s-14.326-32-32-32z", + "M609.354 576c0-17.674 14.326-32 32-32s32 14.326 32 32c0 17.674-14.326 32-32 32s-32-14.326-32-32z", + "M385.354 672c-17.674 0-32 14.326-32 32s14.326 32 32 32c17.674 0 32-14.326 32-32s-14.326-32-32-32z", + "M481.354 704c0-17.674 14.326-32 32-32s32 14.326 32 32c0 17.674-14.326 32-32 32s-32-14.326-32-32z", + "M641.354 672c-17.674 0-32 14.326-32 32s14.326 32 32 32c17.674 0 32-14.326 32-32s-14.326-32-32-32z", + "M289.353 160h448.001c35.344 0 64 28.654 64 64v576c0 35.347-28.656 64-64 64h-448.001c-35.346 0-64-28.653-64-64v-576c0-35.346 28.654-64 64-64zM289.353 224v576h448.001v-576h-448.001z" + ], + "width": 1056, + "attrs": [ + { "fill": "rgb(108, 114, 122)" }, + { "fill": "rgb(108, 114, 122)" }, + { "fill": "rgb(108, 114, 122)" }, + { "fill": "rgb(108, 114, 122)" }, + { "fill": "rgb(108, 114, 122)" }, + { "fill": "rgb(108, 114, 122)" }, + { "fill": "rgb(108, 114, 122)" }, + { "fill": "rgb(108, 114, 122)" }, + { "fill": "rgb(108, 114, 122)" }, + { "fill": "rgb(108, 114, 122)" }, + { "fill": "rgb(108, 114, 122)" } + ], + "isMulticolor": false, + "isMulticolor2": false, + "colorPermutations": { + "1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101": [ + { "f": 2 }, + { "f": 2 }, + { "f": 2 }, + { "f": 2 }, + { "f": 2 }, + { "f": 2 }, + { "f": 2 }, + { "f": 2 }, + { "f": 2 }, + { "f": 2 }, + { "f": 2 } + ] + }, + "tags": ["total"], + "grid": 0 + }, + "attrs": [ + { "fill": "rgb(108, 114, 122)" }, + { "fill": "rgb(108, 114, 122)" }, + { "fill": "rgb(108, 114, 122)" }, + { "fill": "rgb(108, 114, 122)" }, + { "fill": "rgb(108, 114, 122)" }, + { "fill": "rgb(108, 114, 122)" }, + { "fill": "rgb(108, 114, 122)" }, + { "fill": "rgb(108, 114, 122)" }, + { "fill": "rgb(108, 114, 122)" }, + { "fill": "rgb(108, 114, 122)" }, + { "fill": "rgb(108, 114, 122)" } + ], + "properties": { "order": 99, "id": 177, "name": "total", "prevSize": 32, "code": 59851 }, + "setIdx": 0, + "setId": 4, + "iconIdx": 178 + }, + { + "icon": { + "paths": [ + "M713.613 91.356c13.162-11.794 33.392-10.685 45.187 2.477l129.034 144c10.89 12.154 10.89 30.556 0 42.71l-129.034 144.001c-11.795 13.162-32.026 14.269-45.187 2.477-13.162-11.795-14.272-32.026-2.477-45.187l81.222-90.646h-216.358c-17.674 0-32-14.327-32-32s14.326-32 32-32h216.358l-81.222-90.645c-11.795-13.162-10.685-33.393 2.477-45.187zM340.026 464.461c8.586-15.45 28.067-21.018 43.514-12.432l128.461 71.366 128.461-71.366c15.446-8.586 34.928-3.018 43.514 12.432 8.582 15.45 3.014 34.931-12.435 43.514l-159.539 88.634-159.539-88.634c-15.45-8.582-21.018-28.064-12.435-43.514zM192 288v448h640v-192c0-17.674 14.326-32 32-32s32 14.326 32 32v224c0 17.674-14.326 32-32 32h-704c-17.673 0-32-14.326-32-32v-512c0-17.673 14.327-32 32-32h272c17.674 0 32 14.327 32 32s-14.326 32-32 32h-240z" + ], + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "isMulticolor": false, + "isMulticolor2": false, + "colorPermutations": { + "1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101": [ + { "f": 2 } + ] + }, + "tags": ["transcript"], + "grid": 0 + }, + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "properties": { "order": 100, "id": 178, "name": "transcript", "prevSize": 32, "code": 59852 }, + "setIdx": 0, + "setId": 4, + "iconIdx": 179 + }, + { + "icon": { + "paths": [ + "M384 320c0-17.673-14.326-32-32-32s-32 14.327-32 32v256c0 106.038 85.962 192 192 192s192-85.962 192-192v-256c0-17.673-14.326-32-32-32s-32 14.327-32 32v256c0 70.691-57.309 128-128 128s-128-57.309-128-128v-256zM352 856c-13.254 0-24 10.746-24 24s10.746 24 24 24h320c13.254 0 24-10.746 24-24s-10.746-24-24-24h-320z" + ], + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "isMulticolor": false, + "isMulticolor2": false, + "colorPermutations": { + "1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101": [ + { "f": 2 } + ] + }, + "tags": ["underline"], + "grid": 0 + }, + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "properties": { "order": 101, "id": 179, "name": "underline", "prevSize": 32, "code": 59853 }, + "setIdx": 0, + "setId": 4, + "iconIdx": 180 + }, + { + "icon": { + "paths": [ + "M512 832c176.73 0 320-143.27 320-320s-143.27-320-320-320c-111.712 0-210.056 57.244-267.295 144h107.295c17.674 0 32 14.326 32 32s-14.326 32-32 32h-176c-17.673 0-32-14.326-32-32v-192c0-17.673 14.327-32 32-32s32 14.327 32 32v101.364c70.228-90.856 180.282-149.364 304-149.364 212.077 0 384 171.923 384 384s-171.923 384-384 384c-212.077 0-384-171.923-384-384h64c0 176.73 143.27 320 320 320z" + ], + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "isMulticolor": false, + "isMulticolor2": false, + "colorPermutations": { + "1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101": [ + { "f": 2 } + ] + }, + "tags": ["undo"], + "grid": 0 + }, + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "properties": { "order": 102, "id": 180, "name": "undo", "prevSize": 32, "code": 59854 }, + "setIdx": 0, + "setId": 4, + "iconIdx": 181 + }, + { + "icon": { + "paths": [ + "M467.808 512c0-63.322-61.053-128-153.903-128s-153.905 64.678-153.905 128c0 63.322 61.054 128 153.905 128s153.903-64.678 153.903-128zM512 592.099c34.454 66.042 110.195 111.901 198.096 111.901 120.346 0 217.904-85.962 217.904-192s-97.558-192-217.904-192c-87.901 0-163.642 45.859-198.096 111.901-34.451-66.042-110.195-111.901-198.095-111.901-120.345 0-217.905 85.962-217.905 192s97.559 192 217.905 192c87.9 0 163.644-45.859 198.095-111.901zM864 512c0 63.322-61.053 128-153.904 128s-153.904-64.678-153.904-128c0-63.322 61.053-128 153.904-128s153.904 64.678 153.904 128z" + ], + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "isMulticolor": false, + "isMulticolor2": false, + "colorPermutations": { + "1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101": [ + { "f": 2 } + ] + }, + "tags": ["Unlimited"], + "grid": 0 + }, + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "properties": { "order": 103, "id": 181, "name": "Unlimited", "prevSize": 32, "code": 59855 }, + "setIdx": 0, + "setId": 4, + "iconIdx": 182 + }, + { + "icon": { + "paths": [ + "M829.168 153.372c12.515-12.497 32.803-12.497 45.315 0 12.515 12.497 12.515 32.758 0 45.255l-672.887 672c-12.513 12.496-32.801 12.496-45.315 0s-12.513-32.758 0-45.254l672.887-672zM110.155 480.051c101.936-153.699 303.797-327.676 542.114-225.436l-49.578 49.512c-56.528-19.209-108.077-19.504-153.526-9.726-114.906 24.721-215.653 118.392-280.79 213.871 38.633 48 75.969 86.822 111.862 117.885l-45.363 45.302c-39.684-34.736-80.189-77.437-121.317-129.181-14.248-17.923-16.056-43.146-3.402-62.227zM797.981 350.454l-45.328 45.27c35.546 31.427 72.336 70.947 110.186 120.080-61.546 95.187-158.656 188.682-272.125 213.626-46.752 10.278-100.624 9.734-160.592-11.603l-49.331 49.267c244.662 107.334 443.389-69.158 540.778-224.253 11.674-18.589 9.894-42.656-3.395-60.128-40.493-53.235-80.64-96.931-120.192-132.259zM514.982 347.43c13.709 0 27.037 1.571 39.802 4.541l-203.738 203.469c-3.901-13.837-5.981-28.403-5.981-43.44 0-90.89 76.074-164.57 169.917-164.57zM679.091 469.184l-203.264 202.998c12.57 2.87 25.68 4.39 39.155 4.39 93.843 0 169.917-73.68 169.917-164.573 0-14.81-2.019-29.162-5.808-42.816z" + ], + "width": 1056, + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "isMulticolor": false, + "isMulticolor2": false, + "colorPermutations": { + "1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101": [ + { "f": 2 } + ] + }, + "tags": ["unread-on-top-disabled"], + "grid": 0 + }, + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "properties": { "order": 104, "id": 182, "name": "unread-on-top-disabled", "prevSize": 32, "code": 59856 }, + "setIdx": 0, + "setId": 4, + "iconIdx": 183 + }, + { + "icon": { + "paths": [ + "M589.36 729.43c-107.366 23.603-252.288-9.875-422.339-221.158 65.137-95.478 165.885-189.15 280.79-213.871 108.314-23.302 251.286 10.604 413.674 221.404-61.546 95.187-158.653 188.682-272.125 213.626zM916.819 482.714c-347.59-456.963-669.742-211.156-808.018-2.662-12.655 19.082-10.846 44.304 3.402 62.227 362.786 456.419 677.038 209.142 808.011 0.563 11.674-18.589 9.894-42.656-3.395-60.128zM619.462 512c0-53.709-45.517-100.57-105.834-100.57s-105.834 46.861-105.834 100.57c0 53.709 45.517 100.573 105.834 100.573s105.834-46.864 105.834-100.573zM683.546 512c0 90.893-76.074 164.573-169.917 164.573s-169.917-73.68-169.917-164.573c0-90.89 76.074-164.57 169.917-164.57s169.917 73.68 169.917 164.57z" + ], + "width": 1056, + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "isMulticolor": false, + "isMulticolor2": false, + "colorPermutations": { + "1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101": [ + { "f": 2 } + ] + }, + "tags": ["unread-on-top"], + "grid": 0 + }, + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "properties": { "order": 105, "id": 183, "name": "unread-on-top", "prevSize": 32, "code": 59857 }, + "setIdx": 0, + "setId": 4, + "iconIdx": 184 + }, + { + "icon": { + "paths": [ + "M273.128 356.678c-6.62 27.766-2.384 63.757 25.498 105.581l33.166 49.75h-59.792c-44.011 0-70.484 14.874-86.279 33.619-16.496 19.578-24.573 47.264-23.734 77.491 0.841 30.262 10.601 59.968 25.818 81.312 15.127 21.216 33.404 31.578 52.195 31.578h143.999v64h-144c-45.209 0-80.932-25.642-104.306-58.426-23.283-32.656-36.522-74.95-37.682-116.688-1.16-41.773 9.763-86.083 38.766-120.506 20.726-24.598 49.155-42.32 84.825-50.784-16.1-39.011-19.009-77.050-10.731-111.77 11.253-47.2 42.305-84.492 80.791-107.342 38.4-22.798 85.677-32.139 131.303-21.965 35.584 7.935 68.909 27.48 95.251 59.554 53.75-35.147 127.584-30.892 182.483-2.495 34.438 17.812 64.794 46.382 81.437 85.010 12.294 28.531 16.438 61.011 10.608 96.205 46.112 6.682 81.507 25.155 105.616 53.456 30.346 35.626 38.102 81.35 33.443 123.283-4.659 41.917-21.946 83.485-46.544 115.114-24.061 30.934-59.354 57.354-101.261 57.354h-144v-64h144c14.093 0 32.8-9.581 50.742-32.646 17.398-22.371 30.112-52.806 33.453-82.89 3.341-30.067-2.902-56.339-18.554-74.714-15.222-17.869-44.035-33.75-97.642-33.75h-45.686l15.613-42.938c13.546-37.251 11.091-66.742 1.437-89.149-9.856-22.87-28.502-41.302-52.064-53.488-49.587-25.649-107.475-18.625-134.717 14.064l-30.134 36.16-22.541-41.325c-19.757-36.219-47.232-54.175-74.87-60.339-28.378-6.327-59.098-0.669-84.701 14.53-25.512 15.148-44.461 38.855-51.208 67.152zM630.979 588.778l-96-99.050c-6.029-6.218-14.32-9.728-22.979-9.728s-16.95 3.51-22.979 9.728l-96 99.050c-12.298 12.691-11.981 32.95 0.707 45.251 12.691 12.298 32.95 11.981 45.251-0.707l41.021-42.326v273.005c0 17.674 14.326 32 32 32s32-14.326 32-32v-273.005l41.021 42.326c12.301 12.688 32.56 13.005 45.251 0.707 12.691-12.301 13.005-32.56 0.707-45.251z" + ], + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "isMulticolor": false, + "isMulticolor2": false, + "colorPermutations": { + "1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101": [ + { "f": 2 } + ] + }, + "tags": ["upload"], + "grid": 0 + }, + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "properties": { "order": 106, "id": 184, "name": "upload", "prevSize": 32, "code": 59858 }, + "setIdx": 0, + "setId": 4, + "iconIdx": 185 + }, + { + "icon": { + "paths": [ + "M553.411 326.282c0 91.578-75.846 165.818-169.411 165.818-93.564 0-169.412-74.24-169.412-165.818 0-91.581 75.848-165.821 169.412-165.821 93.565 0 169.411 74.24 169.411 165.821zM485.648 326.282c0-54.949-45.51-99.493-101.648-99.493s-101.647 44.544-101.647 99.493c0 54.947 45.509 99.491 101.647 99.491s101.648-44.544 101.648-99.491z", + "M203.427 511.232c28.952-9.11 60.004-9.67 89.279-1.61l51.342 14.131c25.632 7.056 52.819 6.566 78.166-1.408l31.872-10.026c31.206-9.821 64.678-10.422 96.234-1.738 71.962 19.811 121.68 84.051 121.68 157.219v96.24c0 54.947-45.51 99.491-101.648 99.491h-372.705c-56.138 0-101.647-44.544-101.647-99.491v-108.566c0-65.814 43.411-124.106 107.427-144.243zM274.359 573.472c-16.46-4.531-33.918-4.218-50.196 0.906-35.992 11.322-60.399 44.093-60.399 81.098v108.566c0 18.317 15.17 33.165 33.882 33.165h372.705c18.714 0 33.882-14.848 33.882-33.165v-96.24c0-43.453-29.523-81.603-72.259-93.366-18.739-5.158-38.618-4.8-57.152 1.030l-31.872 10.026c-38.022 11.962-78.803 12.698-117.248 2.115l-51.343-14.134z", + "M797.091 189.321c0-15.913-13.024-28.812-29.091-28.812s-29.091 12.9-29.091 28.812v100.844h-101.818c-16.067 0-29.091 12.9-29.091 28.812s13.024 28.812 29.091 28.812h101.818v100.845c0 15.914 13.024 28.813 29.091 28.813s29.091-12.899 29.091-28.813v-100.845h101.818c16.067 0 29.091-12.899 29.091-28.812s-13.024-28.812-29.091-28.812h-101.818v-100.844z" + ], + "attrs": [{ "fill": "rgb(108, 114, 122)" }, { "fill": "rgb(108, 114, 122)" }, { "fill": "rgb(108, 114, 122)" }], + "isMulticolor": false, + "isMulticolor2": false, + "colorPermutations": { + "1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101": [ + { "f": 2 }, + { "f": 2 }, + { "f": 2 } + ] + }, + "tags": ["user-add"], + "grid": 0 + }, + "attrs": [{ "fill": "rgb(108, 114, 122)" }, { "fill": "rgb(108, 114, 122)" }, { "fill": "rgb(108, 114, 122)" }], + "properties": { "order": 107, "id": 185, "name": "user-add", "prevSize": 32, "code": 59859 }, + "setIdx": 0, + "setId": 4, + "iconIdx": 186 + }, + { + "icon": { + "paths": [ + "M464 318.747c0-51.548-42.406-94.366-96-94.366-53.592 0-96 42.819-96 94.366s42.408 94.367 96 94.367c53.594 0 96-42.819 96-94.367zM528 318.747c0 87.416-71.635 158.284-160 158.284s-160-70.867-160-158.284c0-87.417 71.635-158.282 160-158.282s160 70.865 160 158.282zM281.778 525.712c-27.649-7.693-56.976-7.158-84.319 1.536-60.46 19.226-101.459 74.864-101.459 137.69v103.629c0 52.451 42.981 94.97 96 94.97h352c3.606 0 7.165-0.195 10.666-0.579v-64.534c-3.334 1.168-6.925 1.802-10.666 1.802h-352c-17.673 0-32-14.173-32-31.658v-103.629c0-35.325 23.051-66.605 57.044-77.414 15.373-4.89 31.862-5.187 47.407-0.864l48.489 13.491c36.311 10.102 74.827 9.402 110.737-2.016l30.099-9.571c17.504-5.568 36.278-5.91 53.978-0.986 18.899 5.261 35.066 16.042 46.912 30.282v-79.712c-9.309-4.749-19.203-8.627-29.584-11.517-29.805-8.291-61.414-7.715-90.89 1.654l-30.099 9.574c-23.942 7.61-49.619 8.080-73.824 1.344l-48.491-13.491zM763.366 489.709c-12.326-12.646-32.586-12.918-45.248-0.608-12.666 12.31-12.938 32.544-0.611 45.19l102.842 105.51h-324.349c-17.674 0-32 14.307-32 31.958 0 17.648 14.326 31.958 32 31.958h324.349l-102.842 105.507c-12.326 12.65-12.054 32.88 0.611 45.194 12.662 12.31 32.922 12.038 45.248-0.611l155.718-159.757c12.093-12.406 12.093-32.176 0-44.582l-155.718-159.76z" + ], + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "isMulticolor": false, + "isMulticolor2": false, + "colorPermutations": { + "1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101": [ + { "f": 2 } + ] + }, + "tags": ["user-forward"], + "grid": 0 + }, + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "properties": { "order": 108, "id": 186, "name": "user-forward", "prevSize": 32, "code": 59860 }, + "setIdx": 0, + "setId": 4, + "iconIdx": 187 + }, + { + "icon": { + "paths": [ + "M609.354 336c0-53.019-42.979-96-96-96s-96 42.981-96 96c0 53.021 42.979 96 96 96s96-42.979 96-96zM673.354 336c0 88.365-71.635 160-160 160s-160-71.635-160-160c0-88.365 71.635-160 160-160s160 71.635 160 160zM413.805 551.802c-24.621-5.77-50.285-5.366-74.714 1.178-67.086 17.968-113.738 78.762-113.738 148.211v66.81c0 53.021 42.98 96 96 96h384c53.021 0 96-42.979 96-96v-58.531c0-74.301-51.149-138.826-123.488-155.779l-6.458-1.514c-25.674-6.016-52.435-5.594-77.907 1.229l-49.626 13.293c-20.378 5.456-41.789 5.795-62.326 0.979l-67.744-15.875zM355.651 614.8c14.237-3.814 29.197-4.051 43.549-0.688l67.744 15.878c30.806 7.219 62.925 6.714 93.488-1.472l49.629-13.293c15.283-4.096 31.341-4.349 46.742-0.736l6.458 1.51c43.402 10.173 74.093 48.89 74.093 93.469v58.531c0 17.674-14.326 32-32 32h-384c-17.673 0-32-14.326-32-32v-66.81c0-40.483 27.193-75.917 66.298-86.39z" + ], + "width": 1056, + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "isMulticolor": false, + "isMulticolor2": false, + "colorPermutations": { + "1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101": [ + { "f": 2 } + ] + }, + "tags": ["user"], + "grid": 0 + }, + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "properties": { "order": 109, "id": 187, "name": "user", "prevSize": 32, "code": 59861 }, + "setIdx": 0, + "setId": 4, + "iconIdx": 188 + }, + { + "icon": { + "paths": [ + "M201.583 672c35.346 0 64-28.653 64-64s-28.654-64-64-64c-35.346 0-64 28.653-64 64s28.654 64 64 64z", + "M329.584 576c-17.674 0-32.001 14.326-32.001 32s14.327 32 32.001 32h544c17.674 0 32-14.326 32-32s-14.326-32-32-32h-544z", + "M201.583 480c35.346 0 64-28.653 64-64s-28.654-64-64-64c-35.346 0-64 28.653-64 64s28.654 64 64 64z", + "M329.584 384c-17.674 0-32.001 14.326-32.001 32s14.327 32 32.001 32h544c17.674 0 32-14.326 32-32s-14.326-32-32-32h-544z", + "M201.583 288c35.346 0 64-28.654 64-64s-28.654-64-64-64c-35.346 0-64 28.654-64 64s28.654 64 64 64z", + "M329.584 192c-17.674 0-32.001 14.327-32.001 32s14.327 32 32.001 32h544c17.674 0 32-14.327 32-32s-14.326-32-32-32h-544z", + "M201.583 864c35.346 0 64-28.653 64-64s-28.654-64-64-64c-35.346 0-64 28.653-64 64s28.654 64 64 64z", + "M329.584 768c-17.674 0-32.001 14.326-32.001 32s14.327 32 32.001 32h544c17.674 0 32-14.326 32-32s-14.326-32-32-32h-544z" + ], + "attrs": [ + { "fill": "rgb(108, 114, 122)" }, + { "fill": "rgb(108, 114, 122)" }, + { "fill": "rgb(108, 114, 122)" }, + { "fill": "rgb(108, 114, 122)" }, + { "fill": "rgb(108, 114, 122)" }, + { "fill": "rgb(108, 114, 122)" }, + { "fill": "rgb(108, 114, 122)" }, + { "fill": "rgb(108, 114, 122)" } + ], + "isMulticolor": false, + "isMulticolor2": false, + "colorPermutations": { + "1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101": [ + { "f": 2 }, + { "f": 2 }, + { "f": 2 }, + { "f": 2 }, + { "f": 2 }, + { "f": 2 }, + { "f": 2 }, + { "f": 2 } + ] + }, + "tags": ["view-condensed"], + "grid": 0 + }, + "attrs": [ + { "fill": "rgb(108, 114, 122)" }, + { "fill": "rgb(108, 114, 122)" }, + { "fill": "rgb(108, 114, 122)" }, + { "fill": "rgb(108, 114, 122)" }, + { "fill": "rgb(108, 114, 122)" }, + { "fill": "rgb(108, 114, 122)" }, + { "fill": "rgb(108, 114, 122)" }, + { "fill": "rgb(108, 114, 122)" } + ], + "properties": { "order": 110, "id": 188, "name": "view-condensed", "prevSize": 32, "code": 59862 }, + "setIdx": 0, + "setId": 4, + "iconIdx": 189 + }, + { + "icon": { + "paths": [ + "M288 192c0-17.673 14.327-32 32-32h544c17.674 0 32 14.327 32 32s-14.326 32-32 32h-544c-17.673 0-32-14.327-32-32zM288 288c0-17.673 14.327-32 32-32h448c17.674 0 32 14.327 32 32s-14.326 32-32 32h-448c-17.673 0-32-14.327-32-32zM192 304c35.346 0 64-28.654 64-64s-28.654-64-64-64c-35.346 0-64 28.654-64 64s28.654 64 64 64z", + "M288 464c0-17.674 14.327-32 32-32h544c17.674 0 32 14.326 32 32s-14.326 32-32 32h-544c-17.673 0-32-14.326-32-32zM288 560c0-17.674 14.327-32 32-32h448c17.674 0 32 14.326 32 32s-14.326 32-32 32h-448c-17.673 0-32-14.326-32-32zM192 576c35.346 0 64-28.653 64-64s-28.654-64-64-64c-35.346 0-64 28.653-64 64s28.654 64 64 64z", + "M288 736c0-17.674 14.327-32 32-32h544c17.674 0 32 14.326 32 32s-14.326 32-32 32h-544c-17.673 0-32-14.326-32-32zM288 832c0-17.674 14.327-32 32-32h448c17.674 0 32 14.326 32 32s-14.326 32-32 32h-448c-17.673 0-32-14.326-32-32zM192 848c35.346 0 64-28.653 64-64s-28.654-64-64-64c-35.346 0-64 28.653-64 64s28.654 64 64 64z" + ], + "attrs": [{ "fill": "rgb(108, 114, 122)" }, { "fill": "rgb(108, 114, 122)" }, { "fill": "rgb(108, 114, 122)" }], + "isMulticolor": false, + "isMulticolor2": false, + "colorPermutations": { + "1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101": [ + { "f": 2 }, + { "f": 2 }, + { "f": 2 } + ] + }, + "tags": ["view-extended"], + "grid": 0 + }, + "attrs": [{ "fill": "rgb(108, 114, 122)" }, { "fill": "rgb(108, 114, 122)" }, { "fill": "rgb(108, 114, 122)" }], + "properties": { "order": 111, "id": 189, "name": "view-extended", "prevSize": 32, "code": 59863 }, + "setIdx": 0, + "setId": 4, + "iconIdx": 190 + }, + { + "icon": { + "paths": [ + "M192 320c35.346 0 64-28.654 64-64s-28.654-64-64-64c-35.346 0-64 28.654-64 64s28.654 64 64 64z", + "M320 224c-17.673 0-32 14.327-32 32s14.327 32 32 32h544c17.674 0 32-14.327 32-32s-14.326-32-32-32h-544z", + "M192 576c35.346 0 64-28.653 64-64s-28.654-64-64-64c-35.346 0-64 28.653-64 64s28.654 64 64 64z", + "M320 480c-17.673 0-32 14.326-32 32s14.327 32 32 32h544c17.674 0 32-14.326 32-32s-14.326-32-32-32h-544z", + "M192 832c35.346 0 64-28.653 64-64s-28.654-64-64-64c-35.346 0-64 28.653-64 64s28.654 64 64 64z", + "M320 736c-17.673 0-32 14.326-32 32s14.327 32 32 32h544c17.674 0 32-14.326 32-32s-14.326-32-32-32h-544z" + ], + "attrs": [ + { "fill": "rgb(108, 114, 122)" }, + { "fill": "rgb(108, 114, 122)" }, + { "fill": "rgb(108, 114, 122)" }, + { "fill": "rgb(108, 114, 122)" }, + { "fill": "rgb(108, 114, 122)" }, + { "fill": "rgb(108, 114, 122)" } + ], + "isMulticolor": false, + "isMulticolor2": false, + "colorPermutations": { + "1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101": [ + { "f": 2 }, + { "f": 2 }, + { "f": 2 }, + { "f": 2 }, + { "f": 2 }, + { "f": 2 } + ] + }, + "tags": ["view-medium"], + "grid": 0 + }, + "attrs": [ + { "fill": "rgb(108, 114, 122)" }, + { "fill": "rgb(108, 114, 122)" }, + { "fill": "rgb(108, 114, 122)" }, + { "fill": "rgb(108, 114, 122)" }, + { "fill": "rgb(108, 114, 122)" }, + { "fill": "rgb(108, 114, 122)" } + ], + "properties": { "order": 112, "id": 190, "name": "view-medium", "prevSize": 32, "code": 59864 }, + "setIdx": 0, + "setId": 4, + "iconIdx": 191 + }, + { + "icon": { + "paths": [ + "M640.854 256c26.512 0 48-21.49 48-48s-21.488-48-48-48c-26.509 0-48 21.49-48 48s21.491 48 48 48zM640.854 320c61.856 0 112-50.144 112-112s-50.144-112-112-112c-61.856 0-112 50.144-112 112s50.144 112 112 112zM592.854 416v256h-160v128h32v-96h224v-288h-96zM528.854 768h160c35.347 0 64-28.653 64-64v-288c0-35.347-28.653-64-64-64h-96c-35.344 0-64 28.653-64 64v192h-96c-35.344 0-64 28.653-64 64v128c0 35.347 28.656 64 64 64h32c35.347 0 64-28.653 64-64v-32zM784.854 448c0-17.674 14.326-32 32-32s32 14.326 32 32v384c0 17.674-14.326 32-32 32h-224c-17.674 0-32-14.326-32-32s14.326-32 32-32h192v-352zM368.403 401.52c9.677-9.059 10.176-24.246 1.117-33.923l-48.272-51.558v-47.925c0-13.255-10.746-24-24-24s-24 10.745-24 24v57.406c0 6.093 2.316 11.955 6.48 16.403l54.752 58.48c9.059 9.677 24.246 10.176 33.923 1.117zM170.974 414.278c53.308 74.218 156.687 91.171 230.908 37.862 10.765-7.731 25.76-5.274 33.494 5.491 7.731 10.768 5.274 25.763-5.494 33.494-95.75 68.771-229.121 46.902-297.894-48.848s-46.902-229.123 48.848-297.895c75.545-54.26 174.453-52.073 246.597-1.796 10.874 7.578 13.546 22.538 5.968 33.412s-22.538 13.547-33.411 5.968c-55.971-39.005-132.65-40.618-191.153 1.402-74.219 53.308-91.171 156.688-37.864 230.909z" + ], + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "isMulticolor": false, + "isMulticolor2": false, + "colorPermutations": { + "1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101": [ + { "f": 2 } + ] + }, + "tags": ["waiting-on-me"], + "grid": 0 + }, + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "properties": { "order": 113, "id": 191, "name": "waiting-on-me", "prevSize": 32, "code": 59865 }, + "setIdx": 0, + "setId": 4, + "iconIdx": 192 + }, + { + "icon": { + "paths": [ + "M512 352.003c17.674 0 32 14.33 32 32v224c0 17.674-14.326 32-32 32s-32-14.326-32-32v-224c0-17.67 14.326-32 32-32z", + "M512 672.003c17.674 0 32 14.33 32 32 0 17.674-14.326 32-32 32s-32-14.326-32-32c0-17.67 14.326-32 32-32z", + "M567.101 158.348c-24.774-41.922-85.427-41.922-110.202 0l-359.92 609.099c-25.21 42.662 5.544 96.557 55.099 96.557h719.845c49.555 0 80.307-53.894 55.098-96.557l-359.92-609.099zM512 190.906l359.923 609.097h-719.845l359.922-609.097z" + ], + "attrs": [{ "fill": "rgb(108, 114, 122)" }, { "fill": "rgb(108, 114, 122)" }, { "fill": "rgb(108, 114, 122)" }], + "isMulticolor": false, + "isMulticolor2": false, + "colorPermutations": { + "1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101": [ + { "f": 2 }, + { "f": 2 }, + { "f": 2 } + ] + }, + "tags": ["warning"], + "grid": 0 + }, + "attrs": [{ "fill": "rgb(108, 114, 122)" }, { "fill": "rgb(108, 114, 122)" }, { "fill": "rgb(108, 114, 122)" }], + "properties": { "order": 114, "id": 192, "name": "warning", "prevSize": 32, "code": 59866 }, + "setIdx": 0, + "setId": 4, + "iconIdx": 193 + }, + { + "icon": { + "paths": [ + "M784.131 240.143c-35.43-35.658-77.581-63.931-124.016-83.181s-96.227-29.094-146.493-28.961c-210.863 0-382.322 171.532-382.414 382.376-0.085 67.104 17.523 133.043 51.047 191.171l-54.256 198.154 202.72-53.174c56.064 30.531 118.883 46.531 182.717 46.538h0.166c210.752 0 382.32-171.552 382.394-382.394 0.16-50.25-9.645-100.029-28.848-146.464-19.2-46.436-47.418-88.604-83.018-124.065zM513.622 828.486h-0.186c-56.922 0.006-112.797-15.293-161.776-44.298l-11.606-6.896-120.302 31.555 32.124-117.347-7.573-12.029c-31.783-50.64-48.608-109.232-48.535-169.021 0-175.235 142.653-317.816 317.981-317.816 84.301 0.049 165.13 33.58 224.707 93.218 59.581 59.638 93.034 140.499 92.998 224.8-0.074 175.254-142.653 317.834-317.834 317.834zM687.958 590.451c-9.552-4.787-56.528-27.907-65.293-31.171s-15.126-4.768-21.491 4.784c-6.362 9.555-24.678 31.171-30.253 37.462-5.574 6.288-11.149 7.187-20.701 2.4-9.555-4.784-40.339-14.87-76.845-47.434-28.403-25.322-47.584-56.621-53.174-66.192-5.594-9.571-0.589-14.669 4.198-19.491 4.291-4.291 9.552-11.168 14.32-16.742s6.381-9.571 9.552-15.933c3.174-6.362 1.597-11.955-0.787-16.739-2.384-4.787-21.491-51.818-29.466-70.96-7.757-18.63-15.622-16.099-21.491-16.394-5.501-0.275-11.955-0.349-18.336-0.349-4.842 0.131-9.606 1.264-13.994 3.325-4.387 2.058-8.298 5.005-11.491 8.65-8.749 9.552-33.428 32.675-33.428 79.706s34.234 92.467 39.002 98.848c4.765 6.381 67.382 102.883 163.187 144.266 17.792 7.68 35.974 14.413 54.477 20.17 22.902 7.334 43.731 6.237 60.179 3.779 18.336-2.733 56.547-23.104 64.506-45.418 7.955-22.317 7.955-41.459 5.501-45.456-2.458-3.997-8.618-6.326-18.173-11.11z" + ], + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "isMulticolor": false, + "isMulticolor2": false, + "colorPermutations": { + "1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101": [ + { "f": 2 } + ] + }, + "tags": ["whatsapp-monochromatic"], + "grid": 0 + }, + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "properties": { "order": 237, "id": 193, "name": "whatsapp-monochromatic", "prevSize": 32, "code": 59868 }, + "setIdx": 0, + "setId": 4, + "iconIdx": 194 + }, + { + "icon": { + "paths": [ + "M354.006 399.123l14.029 0.627-0.541-16.726-152.878 1.578c49.916-116.491 165.64-198.133 300.366-198.133 99.645 0 188.893 44.659 248.81 115.065-3.712 0.171-7.491 0.492-11.526 1.068 0 0-63.76 14.898-13.302 118.708 0 0 31.085 84.541-19.306 198.774l-28.403 73.306-99.101-264.723c0 0-5.984-28.154 23.61-28.154l23.76-1.472 0.339-16.035h-228.368v14.458c0 0 39.83-0.288 56.15 27.149l40.762 103.779-73.014 174.403-108.608-272.4c0 0-3.389-31.779 27.222-31.27z", + "M841.603 513.168c0-33.862-5.235-66.643-14.726-97.357l-145.949 378.755c96.134-56.88 160.675-161.571 160.675-281.398z", + "M411.478 823.011c32.49 10.912 67.267 16.778 103.488 16.778 37.85 0 74.102-6.406 107.898-18.256l-99.709-258.502-111.677 259.981z", + "M188.279 513.149c0-32.083 4.712-63.066 13.339-92.336l1 2.051 152.656 375.229c-99.606-55.882-166.994-162.538-166.994-284.944z", + "M130.705 512.093c0-211.775 172.316-384.093 384.076-384.093 211.725 0 383.923 172.318 383.923 384.093 0 211.776-172.198 384.010-383.923 384.010-211.757 0-384.076-172.234-384.076-384.010zM514.781 879.254c202.384 0 367.11-164.742 367.11-367.178 0-202.486-164.726-367.145-367.11-367.161-202.489 0-367.213 164.673-367.213 367.161 0 202.435 164.724 367.178 367.213 367.178z" + ], + "width": 1056, + "attrs": [ + { "fill": "rgb(108, 114, 122)" }, + { "fill": "rgb(108, 114, 122)" }, + { "fill": "rgb(108, 114, 122)" }, + { "fill": "rgb(108, 114, 122)" }, + { "fill": "rgb(108, 114, 122)" } + ], + "isMulticolor": false, + "isMulticolor2": false, + "colorPermutations": { + "1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101": [ + { "f": 2 }, + { "f": 2 }, + { "f": 2 }, + { "f": 2 }, + { "f": 2 } + ] + }, + "tags": ["wordpress-monochromatic"], + "grid": 0 + }, + "attrs": [ + { "fill": "rgb(108, 114, 122)" }, + { "fill": "rgb(108, 114, 122)" }, + { "fill": "rgb(108, 114, 122)" }, + { "fill": "rgb(108, 114, 122)" }, + { "fill": "rgb(108, 114, 122)" } + ], + "properties": { "order": 241, "id": 194, "name": "wordpress-monochromatic", "prevSize": 32, "code": 59656 }, + "setIdx": 0, + "setId": 4, + "iconIdx": 195 + }, + { + "icon": { + "paths": [ + "M193.353 405.334h213.332v-213.334h-213.332v213.334zM129.353 170.667c0-23.564 19.102-42.667 42.667-42.667h256c23.565 0 42.666 19.102 42.666 42.667v255.999c0 23.565-19.101 42.669-42.666 42.669h-256c-23.564 0-42.667-19.104-42.667-42.669v-255.999zM620.019 405.334h213.334v-213.334h-213.334v213.334zM556.019 170.667c0-23.564 19.104-42.667 42.666-42.667h256c23.565 0 42.669 19.102 42.669 42.667v255.999c0 23.565-19.104 42.669-42.669 42.669h-256c-23.562 0-42.666-19.104-42.666-42.669v-255.999zM620.019 618.666h213.334v213.334h-213.334v-213.334zM598.685 554.666c-23.562 0-42.666 19.104-42.666 42.669v256c0 23.562 19.104 42.666 42.666 42.666h256c23.565 0 42.669-19.104 42.669-42.666v-256c0-23.565-19.104-42.669-42.669-42.669h-256zM193.353 832h213.332v-213.334h-213.332v213.334zM129.353 597.334c0-23.565 19.102-42.669 42.667-42.669h256c23.565 0 42.666 19.104 42.666 42.669v256c0 23.562-19.101 42.666-42.666 42.666h-256c-23.564 0-42.667-19.104-42.667-42.666v-256z" + ], + "width": 1056, + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "isMulticolor": false, + "isMulticolor2": false, + "colorPermutations": { + "1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101": [ + { "f": 2 } + ] + }, + "tags": ["workspaces"], + "grid": 0 + }, + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "properties": { "order": 119, "id": 195, "name": "workspaces", "prevSize": 32, "code": 59870 }, + "setIdx": 0, + "setId": 4, + "iconIdx": 196 + }, + { + "icon": { + "paths": [ + "M384.509 149.333c0-11.782 9.562-21.333 21.36-21.333h85.446c11.798 0 21.363 9.551 21.363 21.333v106.667h-106.81c-11.798 0-21.36-9.551-21.36-21.333v-85.333z", + "M512.678 256h106.806c11.798 0 21.36 9.551 21.36 21.333v85.332c0 11.782-9.562 21.334-21.36 21.334h-106.806v-128z", + "M384.509 405.334c0-11.782 9.562-21.334 21.36-21.334h106.81v128h-106.81c-11.798 0-21.36-9.552-21.36-21.334v-85.331z", + "M512.678 512h106.806c11.798 0 21.36 9.552 21.36 21.334v106.666h-128.166v-128z", + "M427.232 640c-23.597 0-42.723 19.104-42.723 42.666v170.669c0 23.562 19.126 42.666 42.723 42.666h170.89c23.597 0 42.723-19.104 42.723-42.666v-213.334h-213.613zM491.315 725.334c-11.798 0-21.36 9.549-21.36 21.331v42.669c0 11.782 9.562 21.331 21.36 21.331h42.723c11.798 0 21.36-9.549 21.36-21.331v-42.669c0-11.782-9.562-21.331-21.36-21.331h-42.723z" + ], + "width": 1056, + "attrs": [ + { "fill": "rgb(108, 114, 122)" }, + { "fill": "rgb(108, 114, 122)" }, + { "fill": "rgb(108, 114, 122)" }, + { "fill": "rgb(108, 114, 122)" }, + { "fill": "rgb(108, 114, 122)" } + ], + "isMulticolor": false, + "isMulticolor2": false, + "colorPermutations": { + "1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101": [ + { "f": 2 }, + { "f": 2 }, + { "f": 2 }, + { "f": 2 }, + { "f": 2 } + ] + }, + "tags": ["zip"], + "grid": 0 + }, + "attrs": [ + { "fill": "rgb(108, 114, 122)" }, + { "fill": "rgb(108, 114, 122)" }, + { "fill": "rgb(108, 114, 122)" }, + { "fill": "rgb(108, 114, 122)" }, + { "fill": "rgb(108, 114, 122)" } + ], + "properties": { "order": 120, "id": 196, "name": "zip", "prevSize": 32, "code": 59871 }, + "setIdx": 0, + "setId": 4, + "iconIdx": 197 + }, + { + "icon": { + "paths": [ + "M544 208c0-17.673-14.326-32-32-32s-32 14.327-32 32v271.997h-271.998c-17.673 0-32 14.326-32 32s14.327 32 32 32h271.998v272.003c0 17.674 14.326 32 32 32s32-14.326 32-32v-272.003l272-0.003c17.674 0 32-14.326 32-32s-14.326-32-32-32l-272 0.003v-271.997z" + ], + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "isMulticolor": false, + "isMulticolor2": false, + "colorPermutations": { + "1081141221158162168120320620912282312341243190812456992125519219212552552551291162451452241651699010019902101": [ + { "f": 2 } + ] + }, + "tags": ["add"], + "grid": 0 + }, + "attrs": [{ "fill": "rgb(108, 114, 122)" }], + "properties": { "order": 240, "id": 197, "name": "add", "prevSize": 32, "code": 59872 }, + "setIdx": 0, + "setId": 4, + "iconIdx": 198 + } + ], + "height": 1024, + "metadata": { "name": "custom" }, + "preferences": { + "showGlyphs": true, + "showQuickUse": true, + "showQuickUse2": true, + "showSVGs": true, + "fontPref": { + "prefix": "icon-", + "metadata": { "fontFamily": "custom", "majorVersion": 1, "minorVersion": 0 }, + "metrics": { "emSize": 1024, "baseline": 6.25, "whitespace": 50 }, + "embed": false + }, + "imagePref": { + "prefix": "icon-", + "png": true, + "useClassSelector": true, + "color": 0, + "bgColor": 16777215, + "classSelector": ".icon" + }, + "historySize": 50, + "showCodes": true, + "gridSize": 16 + } +} diff --git a/app/lib/utils.js b/app/lib/utils.js index 615b353ae..5776a3389 100644 --- a/app/lib/utils.js +++ b/app/lib/utils.js @@ -1,15 +1,13 @@ -import { - lt, lte, gt, gte, coerce -} from 'semver'; +import { coerce, gt, gte, lt, lte } from 'semver'; export const formatAttachmentUrl = (attachmentUrl, userId, token, server) => { if (attachmentUrl.startsWith('http')) { if (attachmentUrl.includes('rc_token')) { return encodeURI(attachmentUrl); } - return encodeURI(`${ attachmentUrl }?rc_uid=${ userId }&rc_token=${ token }`); + return encodeURI(`${attachmentUrl}?rc_uid=${userId}&rc_token=${token}`); } - return encodeURI(`${ server }${ attachmentUrl }?rc_uid=${ userId }&rc_token=${ token }`); + return encodeURI(`${server}${attachmentUrl}?rc_uid=${userId}&rc_token=${token}`); }; export const methods = { @@ -19,6 +17,7 @@ export const methods = { greaterThanOrEqualTo: gte }; -export const compareServerVersion = (currentServerVersion, versionToCompare, func) => currentServerVersion && func(coerce(currentServerVersion), versionToCompare); +export const compareServerVersion = (currentServerVersion, versionToCompare, func) => + currentServerVersion && func(coerce(currentServerVersion), versionToCompare); -export const generateLoadMoreId = id => `load-more-${ id }`; +export const generateLoadMoreId = id => `load-more-${id}`; diff --git a/app/notifications/push/index.js b/app/notifications/push/index.js index 13e929164..074e22bf3 100644 --- a/app/notifications/push/index.js +++ b/app/notifications/push/index.js @@ -1,20 +1,22 @@ import EJSON from 'ejson'; -import PushNotification from './push'; + import store from '../../lib/createStore'; import { deepLinkingOpen } from '../../actions/deepLinking'; import { isFDroidBuild } from '../../constants/environment'; +import PushNotification from './push'; -export const onNotification = (notification) => { +export const onNotification = notification => { if (notification) { const data = notification.getData(); if (data) { try { - const { - rid, name, sender, type, host, messageType, messageId - } = EJSON.parse(data.ejson); + const { rid, name, sender, type, host, messageType, messageId } = EJSON.parse(data.ejson); const types = { - c: 'channel', d: 'direct', p: 'group', l: 'channels' + c: 'channel', + d: 'direct', + p: 'group', + l: 'channels' }; let roomName = type === 'd' ? sender.username : name; if (type === 'l') { @@ -25,7 +27,7 @@ export const onNotification = (notification) => { host, rid, messageId, - path: `${ types[type] }/${ roomName }`, + path: `${types[type]}/${roomName}`, isCall: messageType === 'jitsi_call_started' }; store.dispatch(deepLinkingOpen(params)); diff --git a/app/notifications/push/push.android.js b/app/notifications/push/push.android.js index d83184594..51e767ad3 100644 --- a/app/notifications/push/push.android.js +++ b/app/notifications/push/push.android.js @@ -6,11 +6,11 @@ class PushNotification { this.onNotification = null; this.deviceToken = null; - NotificationsAndroid.setRegistrationTokenUpdateListener((deviceToken) => { + NotificationsAndroid.setRegistrationTokenUpdateListener(deviceToken => { this.deviceToken = deviceToken; }); - NotificationsAndroid.setNotificationOpenedListener((notification) => { + NotificationsAndroid.setNotificationOpenedListener(notification => { this.onNotification(notification); }); } @@ -19,7 +19,7 @@ class PushNotification { return this.deviceToken; } - setBadgeCount = () => {} + setBadgeCount = () => {}; configure(params) { this.onRegister = params.onRegister; diff --git a/app/notifications/push/push.ios.js b/app/notifications/push/push.ios.js index 5207e843d..068be4ee9 100644 --- a/app/notifications/push/push.ios.js +++ b/app/notifications/push/push.ios.js @@ -19,7 +19,7 @@ class PushNotification { this.onNotification = null; this.deviceToken = null; - NotificationsIOS.addEventListener('remoteNotificationsRegistered', (deviceToken) => { + NotificationsIOS.addEventListener('remoteNotificationsRegistered', deviceToken => { this.deviceToken = deviceToken; }); @@ -32,10 +32,12 @@ class PushNotification { }); const actions = []; - actions.push(new NotificationCategory({ - identifier: 'MESSAGE', - actions: [replyAction] - })); + actions.push( + new NotificationCategory({ + identifier: 'MESSAGE', + actions: [replyAction] + }) + ); NotificationsIOS.requestPermissions(actions); } @@ -45,7 +47,7 @@ class PushNotification { setBadgeCount = (count = 0) => { NotificationsIOS.setBadgesCount(count); - } + }; async configure(params) { this.onRegister = params.onRegister; diff --git a/app/presentation/DirectoryItem/index.js b/app/presentation/DirectoryItem/index.js deleted file mode 100644 index 9f98969af..000000000 --- a/app/presentation/DirectoryItem/index.js +++ /dev/null @@ -1,68 +0,0 @@ -import React from 'react'; -import { Text, View } from 'react-native'; -import PropTypes from 'prop-types'; - -import Touch from '../../utils/touch'; -import Avatar from '../../containers/Avatar'; -import RoomTypeIcon from '../../containers/RoomTypeIcon'; -import styles, { ROW_HEIGHT } from './styles'; -import { themes } from '../../constants/colors'; - -export { ROW_HEIGHT }; - -const DirectoryItemLabel = React.memo(({ text, theme }) => { - if (!text) { - return null; - } - return <Text style={[styles.directoryItemLabel, { color: themes[theme].auxiliaryText }]}>{text}</Text>; -}); - -const DirectoryItem = ({ - title, description, avatar, onPress, testID, style, rightLabel, type, rid, theme, teamMain -}) => ( - <Touch - onPress={onPress} - style={{ backgroundColor: themes[theme].backgroundColor }} - testID={testID} - theme={theme} - > - <View style={[styles.directoryItemContainer, styles.directoryItemButton, style]}> - <Avatar - text={avatar} - size={30} - type={type} - rid={rid} - style={styles.directoryItemAvatar} - /> - <View style={styles.directoryItemTextContainer}> - <View style={styles.directoryItemTextTitle}> - <RoomTypeIcon type={type} teamMain={teamMain} theme={theme} /> - <Text style={[styles.directoryItemName, { color: themes[theme].titleText }]} numberOfLines={1}>{title}</Text> - </View> - { description ? <Text style={[styles.directoryItemUsername, { color: themes[theme].auxiliaryText }]} numberOfLines={1}>{description}</Text> : null } - </View> - <DirectoryItemLabel text={rightLabel} theme={theme} /> - </View> - </Touch> -); - -DirectoryItem.propTypes = { - title: PropTypes.string.isRequired, - description: PropTypes.string, - avatar: PropTypes.string, - type: PropTypes.string, - onPress: PropTypes.func.isRequired, - testID: PropTypes.string.isRequired, - style: PropTypes.any, - rightLabel: PropTypes.string, - rid: PropTypes.string, - theme: PropTypes.string, - teamMain: PropTypes.bool -}; - -DirectoryItemLabel.propTypes = { - text: PropTypes.string, - theme: PropTypes.string -}; - -export default DirectoryItem; diff --git a/app/presentation/DirectoryItem/index.tsx b/app/presentation/DirectoryItem/index.tsx new file mode 100644 index 000000000..b8d9811a8 --- /dev/null +++ b/app/presentation/DirectoryItem/index.tsx @@ -0,0 +1,72 @@ +import React from 'react'; +import { Text, View } from 'react-native'; + +import Touch from '../../utils/touch'; +import Avatar from '../../containers/Avatar'; +import RoomTypeIcon from '../../containers/RoomTypeIcon'; +import styles, { ROW_HEIGHT } from './styles'; +import { themes } from '../../constants/colors'; + +export { ROW_HEIGHT }; + +interface IDirectoryItemLabel { + text: string; + theme: string; +} + +interface IDirectoryItem { + title: string; + description: string; + avatar: string; + type: string; + onPress(): void; + testID: string; + style: any; + rightLabel: string; + rid: string; + theme: string; + teamMain?: boolean; +} + +const DirectoryItemLabel = React.memo(({ text, theme }: IDirectoryItemLabel) => { + if (!text) { + return null; + } + return <Text style={[styles.directoryItemLabel, { color: themes[theme!].auxiliaryText }]}>{text}</Text>; +}); + +const DirectoryItem = ({ + title, + description, + avatar, + onPress, + testID, + style, + rightLabel, + type, + rid, + theme, + teamMain +}: IDirectoryItem): JSX.Element => ( + <Touch onPress={onPress} style={{ backgroundColor: themes[theme].backgroundColor }} testID={testID} theme={theme}> + <View style={[styles.directoryItemContainer, styles.directoryItemButton, style]}> + <Avatar text={avatar} size={30} type={type} rid={rid} style={styles.directoryItemAvatar} /> + <View style={styles.directoryItemTextContainer}> + <View style={styles.directoryItemTextTitle}> + <RoomTypeIcon type={type} teamMain={teamMain} theme={theme} /> + <Text style={[styles.directoryItemName, { color: themes[theme].titleText }]} numberOfLines={1}> + {title} + </Text> + </View> + {description ? ( + <Text style={[styles.directoryItemUsername, { color: themes[theme].auxiliaryText }]} numberOfLines={1}> + {description} + </Text> + ) : null} + </View> + <DirectoryItemLabel text={rightLabel} theme={theme} /> + </View> + </Touch> +); + +export default DirectoryItem; diff --git a/app/presentation/DirectoryItem/styles.js b/app/presentation/DirectoryItem/styles.ts similarity index 100% rename from app/presentation/DirectoryItem/styles.js rename to app/presentation/DirectoryItem/styles.ts diff --git a/app/presentation/ImageViewer/ImageComponent.js b/app/presentation/ImageViewer/ImageComponent.ts similarity index 85% rename from app/presentation/ImageViewer/ImageComponent.js rename to app/presentation/ImageViewer/ImageComponent.ts index cf00e4322..47249811f 100644 --- a/app/presentation/ImageViewer/ImageComponent.js +++ b/app/presentation/ImageViewer/ImageComponent.ts @@ -1,6 +1,6 @@ import { types } from './types'; -export const ImageComponent = (type) => { +export const ImageComponent = (type: string) => { let Component; if (type === types.REACT_NATIVE_IMAGE) { const { Image } = require('react-native'); diff --git a/app/presentation/ImageViewer/ImageViewer.android.js b/app/presentation/ImageViewer/ImageViewer.android.tsx similarity index 65% rename from app/presentation/ImageViewer/ImageViewer.android.js rename to app/presentation/ImageViewer/ImageViewer.android.tsx index 2cd8578bb..4c8cf5959 100644 --- a/app/presentation/ImageViewer/ImageViewer.android.js +++ b/app/presentation/ImageViewer/ImageViewer.android.tsx @@ -1,12 +1,8 @@ import React from 'react'; import { StyleSheet, View } from 'react-native'; -import PropTypes from 'prop-types'; -import { - PanGestureHandler, - State, - PinchGestureHandler -} from 'react-native-gesture-handler'; +import { PanGestureHandler, PinchGestureHandler, State } from 'react-native-gesture-handler'; import Animated, { Easing } from 'react-native-reanimated'; + import { ImageComponent } from './ImageComponent'; import { themes } from '../../constants/colors'; @@ -44,35 +40,28 @@ const { event } = Animated; -function scaleDiff(value) { +function scaleDiff(value: any) { const tmp = new Value(1); const prev = new Value(1); return [set(tmp, divide(value, prev)), set(prev, value), tmp]; } -function dragDiff(value, updating) { +function dragDiff(value: any, updating: any) { const tmp = new Value(0); const prev = new Value(0); - return cond( - updating, - [set(tmp, sub(value, prev)), set(prev, value), tmp], - set(prev, 0) - ); + return cond(updating, [set(tmp, sub(value, prev)), set(prev, value), tmp], set(prev, 0)); } // returns linear friction coeff. When `value` is 0 coeff is 1 (no friction), then // it grows linearly until it reaches `MAX_FRICTION` when `value` is equal // to `MAX_VALUE` -function friction(value) { +function friction(value: any) { const MAX_FRICTION = 5; const MAX_VALUE = 100; - return max( - 1, - min(MAX_FRICTION, add(1, multiply(value, (MAX_FRICTION - 1) / MAX_VALUE))) - ); + return max(1, min(MAX_FRICTION, add(1, multiply(value, (MAX_FRICTION - 1) / MAX_VALUE)))); } -function speed(value) { +function speed(value: any) { const clock = new Clock(); const dt = diff(clock); return cond(lessThan(dt, 1), 0, multiply(1000, divide(diff(value), dt))); @@ -81,31 +70,20 @@ function speed(value) { const MIN_SCALE = 1; const MAX_SCALE = 2; -function scaleRest(value) { - return cond( - lessThan(value, MIN_SCALE), - MIN_SCALE, - cond(lessThan(MAX_SCALE, value), MAX_SCALE, value) - ); +function scaleRest(value: any) { + return cond(lessThan(value, MIN_SCALE), MIN_SCALE, cond(lessThan(MAX_SCALE, value), MAX_SCALE, value)); } -function scaleFriction(value, rest, delta) { +function scaleFriction(value: any, rest: any, delta: any) { const MAX_FRICTION = 20; const MAX_VALUE = 0.5; const res = multiply(value, delta); const howFar = abs(sub(rest, value)); - const f = max( - 1, - min(MAX_FRICTION, add(1, multiply(howFar, (MAX_FRICTION - 1) / MAX_VALUE))) - ); - return cond( - lessThan(0, howFar), - multiply(value, add(1, divide(add(delta, -1), f))), - res - ); + const f = max(1, min(MAX_FRICTION, add(1, multiply(howFar, (MAX_FRICTION - 1) / MAX_VALUE)))); + return cond(lessThan(0, howFar), multiply(value, add(1, divide(add(delta, -1), f))), res); } -function runTiming(clock, value, dest, startStopClock = true) { +function runTiming(clock: any, value: any, dest: any, startStopClock: any = true) { const state = { finished: new Value(0), position: new Value(0), @@ -134,7 +112,7 @@ function runTiming(clock, value, dest, startStopClock = true) { ]; } -function runDecay(clock, value, velocity) { +function runDecay(clock: any, value: any, velocity: any) { const state = { finished: new Value(0), velocity: new Value(0), @@ -160,28 +138,20 @@ function runDecay(clock, value, velocity) { } function bouncyPinch( - value, - gesture, - gestureActive, - focalX, - displacementX, - focalY, - displacementY + value: any, + gesture: any, + gestureActive: any, + focalX: any, + displacementX: any, + focalY: any, + displacementY: any ) { const clock = new Clock(); const delta = scaleDiff(gesture); const rest = scaleRest(value); - const focalXRest = cond( - lessThan(value, 1), - 0, - sub(displacementX, multiply(focalX, add(-1, divide(rest, value)))) - ); - const focalYRest = cond( - lessThan(value, 1), - 0, - sub(displacementY, multiply(focalY, add(-1, divide(rest, value)))) - ); + const focalXRest = cond(lessThan(value, 1), 0, sub(displacementX, multiply(focalX, add(-1, divide(rest, value))))); + const focalYRest = cond(lessThan(value, 1), 0, sub(displacementY, multiply(focalY, add(-1, divide(rest, value))))); const nextScale = new Value(1); return cond( @@ -189,14 +159,8 @@ function bouncyPinch( [ stopClock(clock), set(nextScale, scaleFriction(value, rest, delta)), - set( - displacementX, - sub(displacementX, multiply(focalX, add(-1, divide(nextScale, value)))) - ), - set( - displacementY, - sub(displacementY, multiply(focalY, add(-1, divide(nextScale, value)))) - ), + set(displacementX, sub(displacementX, multiply(focalX, add(-1, divide(nextScale, value))))), + set(displacementY, sub(displacementY, multiply(focalY, add(-1, divide(nextScale, value))))), nextScale ], cond( @@ -211,48 +175,26 @@ function bouncyPinch( ); } -function bouncy( - value, - gestureDiv, - gestureActive, - lowerBound, - upperBound, - f -) { +function bouncy(value: any, gestureDiv: any, gestureActive: any, lowerBound: any, upperBound: any, f: any) { const timingClock = new Clock(); const decayClock = new Clock(); const velocity = speed(value); // did value go beyond the limits (lower, upper) - const isOutOfBounds = or( - lessThan(value, lowerBound), - lessThan(upperBound, value) - ); + const isOutOfBounds = or(lessThan(value, lowerBound), lessThan(upperBound, value)); // position to snap to (upper or lower is beyond or the current value elsewhere) - const rest = cond( - lessThan(value, lowerBound), - lowerBound, - cond(lessThan(upperBound, value), upperBound, value) - ); + const rest = cond(lessThan(value, lowerBound), lowerBound, cond(lessThan(upperBound, value), upperBound, value)); // how much the value exceeds the bounds, this is used to calculate friction const outOfBounds = abs(sub(rest, value)); return cond( [gestureDiv, velocity, gestureActive], - [ - stopClock(timingClock), - stopClock(decayClock), - add(value, divide(gestureDiv, f(outOfBounds))) - ], + [stopClock(timingClock), stopClock(decayClock), add(value, divide(gestureDiv, f(outOfBounds)))], cond( or(clockRunning(timingClock), isOutOfBounds), [stopClock(decayClock), runTiming(timingClock, value, rest)], - cond( - or(clockRunning(decayClock), lessThan(5, abs(velocity))), - runDecay(decayClock, value, velocity), - value - ) + cond(or(clockRunning(decayClock), lessThan(5, abs(velocity))), runDecay(decayClock, value, velocity), value) ) ); } @@ -260,13 +202,13 @@ function bouncy( const WIDTH = 300; const HEIGHT = 300; -class Image extends React.PureComponent { - static propTypes = { - imageComponentType: PropTypes.string - } +interface IImageProps { + imageComponentType: string; +} +class Image extends React.PureComponent<IImageProps, any> { render() { - const { imageComponentType } = this.props; + const { imageComponentType }: any = this.props; const Component = ImageComponent(imageComponentType); @@ -277,16 +219,31 @@ const AnimatedImage = Animated.createAnimatedComponent(Image); // it was picked from https://github.com/software-mansion/react-native-reanimated/tree/master/Example/imageViewer // and changed to use FastImage animated component -export class ImageViewer extends React.Component { - static propTypes = { - uri: PropTypes.string, - width: PropTypes.number, - height: PropTypes.number, - theme: PropTypes.string, - imageComponentType: PropTypes.string - } - constructor(props) { +interface IImageViewerProps { + uri: string; + width: number; + height: number; + theme: string; + imageComponentType: string; +} + +export class ImageViewer extends React.Component<IImageViewerProps, any> { + private _onPinchEvent: any; + + private _focalDisplacementX: any; + + private _focalDisplacementY: any; + + private _scale: any; + + private _onPanEvent: any; + + private _panTransX: any; + + private _panTransY: any; + + constructor(props: IImageViewerProps) { super(props); // DECLARE TRANSX @@ -314,15 +271,9 @@ export class ImageViewer extends React.Component { const scale = new Value(1); const pinchActive = eq(pinchState, State.ACTIVE); this._focalDisplacementX = new Value(0); - const relativeFocalX = sub( - pinchFocalX, - add(panTransX, this._focalDisplacementX) - ); + const relativeFocalX = sub(pinchFocalX, add(panTransX, this._focalDisplacementX)); this._focalDisplacementY = new Value(0); - const relativeFocalY = sub( - pinchFocalY, - add(panTransY, this._focalDisplacementY) - ); + const relativeFocalY = sub(pinchFocalY, add(panTransY, this._focalDisplacementY)); this._scale = set( scale, bouncyPinch( @@ -351,47 +302,22 @@ export class ImageViewer extends React.Component { ]); const panActive = eq(panState, State.ACTIVE); - const panFriction = value => friction(value); + const panFriction = (value: any) => friction(value); // X - const panUpX = cond( - lessThan(this._scale, 1), - 0, - multiply(-1, this._focalDisplacementX) - ); + const panUpX = cond(lessThan(this._scale, 1), 0, multiply(-1, this._focalDisplacementX)); const panLowX = add(panUpX, multiply(-WIDTH, add(max(1, this._scale), -1))); this._panTransX = set( panTransX, - bouncy( - panTransX, - dragDiff(dragX, panActive), - or(panActive, pinchActive), - panLowX, - panUpX, - panFriction - ) + bouncy(panTransX, dragDiff(dragX, panActive), or(panActive, pinchActive), panLowX, panUpX, panFriction) ); // Y - const panUpY = cond( - lessThan(this._scale, 1), - 0, - multiply(-1, this._focalDisplacementY) - ); - const panLowY = add( - panUpY, - multiply(-HEIGHT, add(max(1, this._scale), -1)) - ); + const panUpY = cond(lessThan(this._scale, 1), 0, multiply(-1, this._focalDisplacementY)); + const panLowY = add(panUpY, multiply(-HEIGHT, add(max(1, this._scale), -1))); this._panTransY = set( panTransY, - bouncy( - panTransY, - dragDiff(dragY, panActive), - or(panActive, pinchActive), - panLowY, - panUpY, - panFriction - ) + bouncy(panTransY, dragDiff(dragY, panActive), or(panActive, pinchActive), panLowY, panUpY, panFriction) ); } @@ -400,9 +326,7 @@ export class ImageViewer extends React.Component { panRef = React.createRef(); render() { - const { - uri, width, height, imageComponentType, theme, ...props - } = this.props; + const { uri, width, height, imageComponentType, theme, ...props } = this.props; // The below two animated values makes it so that scale appears to be done // from the top left corner of the image view instead of its center. This @@ -417,8 +341,7 @@ export class ImageViewer extends React.Component { ref={this.pinchRef} simultaneousHandlers={this.panRef} onGestureEvent={this._onPinchEvent} - onHandlerStateChange={this._onPinchEvent} - > + onHandlerStateChange={this._onPinchEvent}> <Animated.View> <PanGestureHandler ref={this.panRef} @@ -426,8 +349,7 @@ export class ImageViewer extends React.Component { avgTouches simultaneousHandlers={this.pinchRef} onGestureEvent={this._onPanEvent} - onHandlerStateChange={this._onPanEvent} - > + onHandlerStateChange={this._onPanEvent}> <AnimatedImage style={[ styles.image, diff --git a/app/presentation/ImageViewer/ImageViewer.ios.js b/app/presentation/ImageViewer/ImageViewer.ios.tsx similarity index 51% rename from app/presentation/ImageViewer/ImageViewer.ios.js rename to app/presentation/ImageViewer/ImageViewer.ios.tsx index 100bf3e6b..a666ee38a 100644 --- a/app/presentation/ImageViewer/ImageViewer.ios.js +++ b/app/presentation/ImageViewer/ImageViewer.ios.tsx @@ -1,6 +1,5 @@ import React from 'react'; import { ScrollView, StyleSheet } from 'react-native'; -import PropTypes from 'prop-types'; import { ImageComponent } from './ImageComponent'; import { themes } from '../../constants/colors'; @@ -15,37 +14,26 @@ const styles = StyleSheet.create({ } }); -export const ImageViewer = ({ - uri, imageComponentType, theme, width, height, ...props -}) => { +interface IImageViewer { + uri: string; + imageComponentType: string; + width: number; + height: number; + theme: string; +} + +export const ImageViewer = ({ uri, imageComponentType, theme, width, height, ...props }: IImageViewer) => { const backgroundColor = themes[theme].previewBackground; const Component = ImageComponent(imageComponentType); return ( + // @ts-ignore <ScrollView style={{ backgroundColor }} - contentContainerStyle={[ - styles.scrollContent, - width && { width }, - height && { height } - ]} + contentContainerStyle={[styles.scrollContent, width && { width }, height && { height }]} showsHorizontalScrollIndicator={false} showsVerticalScrollIndicator={false} - maximumZoomScale={2} - > - <Component - style={styles.image} - resizeMode='contain' - source={{ uri }} - {...props} - /> + maximumZoomScale={2}> + <Component style={styles.image} resizeMode='contain' source={{ uri }} {...props} /> </ScrollView> ); }; - -ImageViewer.propTypes = { - uri: PropTypes.string, - imageComponentType: PropTypes.string, - width: PropTypes.number, - height: PropTypes.number, - theme: PropTypes.string -}; diff --git a/app/presentation/ImageViewer/index.js b/app/presentation/ImageViewer/index.ts similarity index 58% rename from app/presentation/ImageViewer/index.js rename to app/presentation/ImageViewer/index.ts index bf629ae84..d8ae350b5 100644 --- a/app/presentation/ImageViewer/index.js +++ b/app/presentation/ImageViewer/index.ts @@ -1,3 +1,5 @@ +// @ts-ignore +// eslint-disable-next-line import/no-unresolved export * from './ImageViewer'; export * from './types'; export * from './ImageComponent'; diff --git a/app/presentation/ImageViewer/types.js b/app/presentation/ImageViewer/types.ts similarity index 100% rename from app/presentation/ImageViewer/types.js rename to app/presentation/ImageViewer/types.ts diff --git a/app/presentation/KeyboardView.js b/app/presentation/KeyboardView.js deleted file mode 100644 index 628213825..000000000 --- a/app/presentation/KeyboardView.js +++ /dev/null @@ -1,37 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import { KeyboardAwareScrollView } from '@codler/react-native-keyboard-aware-scroll-view'; -import scrollPersistTaps from '../utils/scrollPersistTaps'; - -export default class KeyboardView extends React.PureComponent { - static propTypes = { - style: PropTypes.any, - contentContainerStyle: PropTypes.any, - keyboardVerticalOffset: PropTypes.number, - scrollEnabled: PropTypes.bool, - children: PropTypes.oneOfType([ - PropTypes.arrayOf(PropTypes.node), - PropTypes.node - ]) - } - - render() { - const { - style, contentContainerStyle, scrollEnabled, keyboardVerticalOffset, children - } = this.props; - - return ( - <KeyboardAwareScrollView - {...scrollPersistTaps} - style={style} - contentContainerStyle={contentContainerStyle} - scrollEnabled={scrollEnabled} - alwaysBounceVertical={false} - extraHeight={keyboardVerticalOffset} - behavior='position' - > - {children} - </KeyboardAwareScrollView> - ); - } -} diff --git a/app/presentation/KeyboardView.tsx b/app/presentation/KeyboardView.tsx new file mode 100644 index 000000000..e5a4d3910 --- /dev/null +++ b/app/presentation/KeyboardView.tsx @@ -0,0 +1,32 @@ +import React from 'react'; +import { KeyboardAwareScrollView } from '@codler/react-native-keyboard-aware-scroll-view'; + +import scrollPersistTaps from '../utils/scrollPersistTaps'; + +interface IKeyboardViewProps { + style: any; + contentContainerStyle: any; + keyboardVerticalOffset: number; + scrollEnabled: boolean; + children: JSX.Element; +} + +export default class KeyboardView extends React.PureComponent<IKeyboardViewProps, any> { + render() { + const { style, contentContainerStyle, scrollEnabled, keyboardVerticalOffset, children } = this.props; + + return ( + <KeyboardAwareScrollView + {...scrollPersistTaps} + style={style} + contentContainerStyle={contentContainerStyle} + scrollEnabled={scrollEnabled} + alwaysBounceVertical={false} + extraHeight={keyboardVerticalOffset} + // @ts-ignore + behavior='position'> + {children} + </KeyboardAwareScrollView> + ); + } +} diff --git a/app/presentation/RoomItem/Actions.js b/app/presentation/RoomItem/Actions.tsx similarity index 75% rename from app/presentation/RoomItem/Actions.js rename to app/presentation/RoomItem/Actions.tsx index 4a557d20e..92fbf98b5 100644 --- a/app/presentation/RoomItem/Actions.js +++ b/app/presentation/RoomItem/Actions.tsx @@ -1,18 +1,32 @@ import React from 'react'; -import { Animated, View, Text } from 'react-native'; +import { Animated, Text, View } from 'react-native'; import { RectButton } from 'react-native-gesture-handler'; -import PropTypes from 'prop-types'; import I18n, { isRTL } from '../../i18n'; import styles, { ACTION_WIDTH, LONG_SWIPE } from './styles'; import { CustomIcon } from '../../lib/Icons'; import { themes } from '../../constants/colors'; +interface ILeftActions { + theme: string; + transX: any; + isRead: boolean; + width: number; + onToggleReadPress(): void; +} + +interface IRightActions { + theme: string; + transX: any; + favorite: boolean; + width: number; + toggleFav(): void; + onHidePress(): void; +} + const reverse = new Animated.Value(isRTL() ? -1 : 1); -export const LeftActions = React.memo(({ - theme, transX, isRead, width, onToggleReadPress -}) => { +export const LeftActions = React.memo(({ theme, transX, isRead, width, onToggleReadPress }: ILeftActions) => { const translateX = Animated.multiply( transX.interpolate({ inputRange: [0, ACTION_WIDTH], @@ -21,10 +35,7 @@ export const LeftActions = React.memo(({ reverse ); return ( - <View - style={[styles.actionsContainer, styles.actionLeftContainer]} - pointerEvents='box-none' - > + <View style={[styles.actionsContainer, styles.actionLeftContainer]} pointerEvents='box-none'> <Animated.View style={[ styles.actionLeftButtonContainer, @@ -34,8 +45,7 @@ export const LeftActions = React.memo(({ transform: [{ translateX }], backgroundColor: themes[theme].tintColor } - ]} - > + ]}> <View style={styles.actionLeftButtonContainer}> <RectButton style={styles.actionButton} onPress={onToggleReadPress}> <> @@ -49,9 +59,7 @@ export const LeftActions = React.memo(({ ); }); -export const RightActions = React.memo(({ - transX, favorite, width, toggleFav, onHidePress, theme -}) => { +export const RightActions = React.memo(({ transX, favorite, width, toggleFav, onHidePress, theme }: IRightActions) => { const translateXFav = Animated.multiply( transX.interpolate({ inputRange: [-width / 2, -ACTION_WIDTH * 2, 0], @@ -75,8 +83,7 @@ export const RightActions = React.memo(({ height: 75, flexDirection: 'row' }} - pointerEvents='box-none' - > + pointerEvents='box-none'> <Animated.View style={[ styles.actionRightButtonContainer, @@ -85,12 +92,13 @@ export const RightActions = React.memo(({ transform: [{ translateX: translateXFav }], backgroundColor: themes[theme].hideBackground } - ]} - > + ]}> <RectButton style={[styles.actionButton, { backgroundColor: themes[theme].favoriteBackground }]} onPress={toggleFav}> <> <CustomIcon size={20} name={favorite ? 'star-filled' : 'star'} color={themes[theme].buttonText} /> - <Text style={[styles.actionText, { color: themes[theme].buttonText }]}>{I18n.t(favorite ? 'Unfavorite' : 'Favorite')}</Text> + <Text style={[styles.actionText, { color: themes[theme].buttonText }]}> + {I18n.t(favorite ? 'Unfavorite' : 'Favorite')} + </Text> </> </RectButton> </Animated.View> @@ -101,8 +109,7 @@ export const RightActions = React.memo(({ width, transform: [{ translateX: translateXHide }] } - ]} - > + ]}> <RectButton style={[styles.actionButton, { backgroundColor: themes[theme].hideBackground }]} onPress={onHidePress}> <> <CustomIcon size={20} name='unread-on-top-disabled' color={themes[theme].buttonText} /> @@ -113,20 +120,3 @@ export const RightActions = React.memo(({ </View> ); }); - -LeftActions.propTypes = { - theme: PropTypes.string, - transX: PropTypes.object, - isRead: PropTypes.bool, - width: PropTypes.number, - onToggleReadPress: PropTypes.func -}; - -RightActions.propTypes = { - theme: PropTypes.string, - transX: PropTypes.object, - favorite: PropTypes.bool, - width: PropTypes.number, - toggleFav: PropTypes.func, - onHidePress: PropTypes.func -}; diff --git a/app/presentation/RoomItem/LastMessage.js b/app/presentation/RoomItem/LastMessage.tsx similarity index 51% rename from app/presentation/RoomItem/LastMessage.js rename to app/presentation/RoomItem/LastMessage.tsx index 25fc2c08d..a6ae19513 100644 --- a/app/presentation/RoomItem/LastMessage.js +++ b/app/presentation/RoomItem/LastMessage.tsx @@ -1,5 +1,4 @@ import React from 'react'; -import PropTypes from 'prop-types'; import { dequal } from 'dequal'; import I18n from '../../i18n'; @@ -8,9 +7,24 @@ import Markdown from '../../containers/markdown'; import { themes } from '../../constants/colors'; import { E2E_MESSAGE_TYPE, E2E_STATUS } from '../../lib/encryption/constants'; -const formatMsg = ({ - lastMessage, type, showLastMessage, username, useRealName -}) => { +interface ILastMessage { + theme: string; + lastMessage: { + u: any; + pinned: boolean; + t: string; + attachments: any; + msg: string; + e2e: string; + }; + type: string; + showLastMessage: boolean; + username: string; + useRealName: boolean; + alert: boolean; +} + +const formatMsg = ({ lastMessage, type, showLastMessage, username, useRealName }: Partial<ILastMessage>) => { if (!showLastMessage) { return ''; } @@ -37,40 +51,38 @@ const formatMsg = ({ if (isLastMessageSentByMe) { prefix = I18n.t('You_colon'); - } else if (type !== 'd') { - const { u: { name } } = lastMessage; - prefix = `${ useRealName ? name : lastMessage.u.username }: `; + } else if (type !== 'd') { + const { + u: { name } + } = lastMessage; + prefix = `${useRealName ? name : lastMessage.u.username}: `; } - return `${ prefix }${ lastMessage.msg }`; + return `${prefix}${lastMessage.msg}`; }; -const arePropsEqual = (oldProps, newProps) => dequal(oldProps, newProps); +const arePropsEqual = (oldProps: any, newProps: any) => dequal(oldProps, newProps); -const LastMessage = React.memo(({ - lastMessage, type, showLastMessage, username, alert, useRealName, theme -}) => ( - <Markdown - msg={formatMsg({ - 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} - /> -), arePropsEqual); - -LastMessage.propTypes = { - theme: PropTypes.string, - lastMessage: PropTypes.object, - type: PropTypes.string, - showLastMessage: PropTypes.bool, - username: PropTypes.string, - useRealName: PropTypes.bool, - alert: PropTypes.bool -}; +const LastMessage = React.memo( + ({ lastMessage, type, showLastMessage, username, alert, useRealName, theme }: ILastMessage) => ( + // @ts-ignore + <Markdown + msg={formatMsg({ + 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} + /> + ), + arePropsEqual +); export default LastMessage; diff --git a/app/presentation/RoomItem/RoomItem.js b/app/presentation/RoomItem/RoomItem.js deleted file mode 100644 index 2ac73a0bb..000000000 --- a/app/presentation/RoomItem/RoomItem.js +++ /dev/null @@ -1,206 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import { View } from 'react-native'; - -import styles from './styles'; -import Wrapper from './Wrapper'; -import UnreadBadge from '../UnreadBadge'; -import TypeIcon from './TypeIcon'; -import LastMessage from './LastMessage'; -import Title from './Title'; -import UpdatedAt from './UpdatedAt'; -import Touchable from './Touchable'; -import Tag from './Tag'; -import I18n from '../../i18n'; - -const RoomItem = ({ - rid, - type, - prid, - name, - avatar, - width, - avatarSize, - username, - showLastMessage, - status, - useRealName, - theme, - isFocused, - isGroupChat, - isRead, - date, - accessibilityLabel, - favorite, - lastMessage, - alert, - hideUnreadStatus, - unread, - userMentions, - groupMentions, - tunread, - tunreadUser, - tunreadGroup, - testID, - swipeEnabled, - onPress, - onLongPress, - toggleFav, - toggleRead, - hideChannel, - teamMain, - autoJoin -}) => ( - <Touchable - onPress={onPress} - onLongPress={onLongPress} - width={width} - favorite={favorite} - toggleFav={toggleFav} - isRead={isRead} - rid={rid} - toggleRead={toggleRead} - hideChannel={hideChannel} - testID={testID} - type={type} - theme={theme} - isFocused={isFocused} - swipeEnabled={swipeEnabled} - > - <Wrapper - accessibilityLabel={accessibilityLabel} - avatar={avatar} - avatarSize={avatarSize} - type={type} - theme={theme} - rid={rid} - > - {showLastMessage - ? ( - <> - <View style={styles.titleContainer}> - <TypeIcon - type={type} - prid={prid} - status={status} - isGroupChat={isGroupChat} - theme={theme} - teamMain={teamMain} - /> - <Title - name={name} - theme={theme} - hideUnreadStatus={hideUnreadStatus} - alert={alert} - /> - { - autoJoin ? <Tag testID='auto-join-tag' name={I18n.t('Auto-join')} /> : null - } - <UpdatedAt - date={date} - theme={theme} - hideUnreadStatus={hideUnreadStatus} - alert={alert} - /> - </View> - <View style={styles.row}> - <LastMessage - lastMessage={lastMessage} - type={type} - showLastMessage={showLastMessage} - username={username} - alert={alert && !hideUnreadStatus} - useRealName={useRealName} - theme={theme} - /> - <UnreadBadge - unread={unread} - userMentions={userMentions} - groupMentions={groupMentions} - tunread={tunread} - tunreadUser={tunreadUser} - tunreadGroup={tunreadGroup} - /> - </View> - </> - ) - : ( - <View style={[styles.titleContainer, styles.flex]}> - <TypeIcon - type={type} - prid={prid} - status={status} - isGroupChat={isGroupChat} - theme={theme} - teamMain={teamMain} - /> - <Title - name={name} - theme={theme} - hideUnreadStatus={hideUnreadStatus} - alert={alert} - /> - { - autoJoin ? <Tag name={I18n.t('Auto-join')} /> : null - } - <UnreadBadge - unread={unread} - userMentions={userMentions} - groupMentions={groupMentions} - tunread={tunread} - tunreadUser={tunreadUser} - tunreadGroup={tunreadGroup} - /> - </View> - ) - } - </Wrapper> - </Touchable> -); - -RoomItem.propTypes = { - rid: PropTypes.string.isRequired, - type: PropTypes.string.isRequired, - prid: PropTypes.string, - name: PropTypes.string.isRequired, - avatar: PropTypes.string.isRequired, - showLastMessage: PropTypes.bool, - username: PropTypes.string, - avatarSize: PropTypes.number, - testID: PropTypes.string, - width: PropTypes.number, - status: PropTypes.string, - useRealName: PropTypes.bool, - theme: PropTypes.string, - isFocused: PropTypes.bool, - isGroupChat: PropTypes.bool, - isRead: PropTypes.bool, - teamMain: PropTypes.bool, - date: PropTypes.string, - accessibilityLabel: PropTypes.string, - lastMessage: PropTypes.object, - favorite: PropTypes.bool, - alert: PropTypes.bool, - hideUnreadStatus: PropTypes.bool, - unread: PropTypes.number, - userMentions: PropTypes.number, - groupMentions: PropTypes.number, - tunread: PropTypes.array, - tunreadUser: PropTypes.array, - tunreadGroup: PropTypes.array, - swipeEnabled: PropTypes.bool, - toggleFav: PropTypes.func, - toggleRead: PropTypes.func, - onPress: PropTypes.func, - onLongPress: PropTypes.func, - hideChannel: PropTypes.func, - autoJoin: PropTypes.bool -}; - -RoomItem.defaultProps = { - avatarSize: 48, - status: 'offline', - swipeEnabled: true -}; - -export default RoomItem; diff --git a/app/presentation/RoomItem/RoomItem.tsx b/app/presentation/RoomItem/RoomItem.tsx new file mode 100644 index 000000000..36f8921fd --- /dev/null +++ b/app/presentation/RoomItem/RoomItem.tsx @@ -0,0 +1,163 @@ +import React from 'react'; +import { View } from 'react-native'; + +import styles from './styles'; +import Wrapper from './Wrapper'; +import UnreadBadge from '../UnreadBadge'; +import TypeIcon from './TypeIcon'; +import LastMessage from './LastMessage'; +import Title from './Title'; +import UpdatedAt from './UpdatedAt'; +import Touchable from './Touchable'; +import Tag from './Tag'; +import I18n from '../../i18n'; + +interface IRoomItem { + rid: string; + type: string; + prid: string; + name: string; + avatar: string; + showLastMessage: boolean; + username: string; + avatarSize: number; + testID: string; + width: number; + status: string; + useRealName: boolean; + theme: string; + isFocused: boolean; + isGroupChat: boolean; + isRead: boolean; + teamMain: boolean; + date: string; + accessibilityLabel: string; + lastMessage: { + u: any; + pinned: boolean; + t: string; + attachments: any; + msg: string; + e2e: string; + }; + favorite: boolean; + alert: boolean; + hideUnreadStatus: boolean; + unread: number; + userMentions: number; + groupMentions: number; + tunread: []; + tunreadUser: []; + tunreadGroup: []; + swipeEnabled: boolean; + toggleFav(): void; + toggleRead(): void; + onPress(): void; + onLongPress(): void; + hideChannel(): void; + autoJoin: boolean; + size?: number; +} + +const RoomItem = ({ + rid, + type, + prid, + name, + avatar, + width, + avatarSize = 48, + username, + showLastMessage, + status = 'offline', + useRealName, + theme, + isFocused, + isGroupChat, + isRead, + date, + accessibilityLabel, + favorite, + lastMessage, + alert, + hideUnreadStatus, + unread, + userMentions, + groupMentions, + tunread, + tunreadUser, + tunreadGroup, + testID, + swipeEnabled = true, + onPress, + onLongPress, + toggleFav, + toggleRead, + hideChannel, + teamMain, + autoJoin +}: IRoomItem) => ( + <Touchable + onPress={onPress} + onLongPress={onLongPress} + width={width} + favorite={favorite} + toggleFav={toggleFav} + isRead={isRead} + rid={rid} + toggleRead={toggleRead} + hideChannel={hideChannel} + testID={testID} + type={type} + theme={theme} + isFocused={isFocused} + swipeEnabled={swipeEnabled}> + <Wrapper accessibilityLabel={accessibilityLabel} avatar={avatar} avatarSize={avatarSize} type={type} theme={theme} rid={rid}> + {showLastMessage ? ( + <> + <View style={styles.titleContainer}> + <TypeIcon type={type} prid={prid} status={status} isGroupChat={isGroupChat} theme={theme} teamMain={teamMain} /> + <Title name={name} theme={theme} hideUnreadStatus={hideUnreadStatus} alert={alert} /> + {autoJoin ? <Tag testID='auto-join-tag' name={I18n.t('Auto-join')} /> : null} + <UpdatedAt date={date} theme={theme} hideUnreadStatus={hideUnreadStatus} alert={alert} /> + </View> + <View style={styles.row}> + <LastMessage + lastMessage={lastMessage} + type={type} + showLastMessage={showLastMessage} + username={username} + alert={alert && !hideUnreadStatus} + useRealName={useRealName} + theme={theme} + /> + <UnreadBadge + unread={unread} + userMentions={userMentions} + groupMentions={groupMentions} + tunread={tunread} + tunreadUser={tunreadUser} + tunreadGroup={tunreadGroup} + /> + </View> + </> + ) : ( + <View style={[styles.titleContainer, styles.flex]}> + <TypeIcon type={type} prid={prid} status={status} isGroupChat={isGroupChat} theme={theme} teamMain={teamMain} /> + <Title name={name} theme={theme} hideUnreadStatus={hideUnreadStatus} alert={alert} /> + {autoJoin ? <Tag name={I18n.t('Auto-join')} /> : null} + <UnreadBadge + unread={unread} + userMentions={userMentions} + groupMentions={groupMentions} + tunread={tunread} + tunreadUser={tunreadUser} + tunreadGroup={tunreadGroup} + /> + </View> + )} + </Wrapper> + </Touchable> +); + +export default RoomItem; diff --git a/app/presentation/RoomItem/Tag.js b/app/presentation/RoomItem/Tag.tsx similarity index 52% rename from app/presentation/RoomItem/Tag.js rename to app/presentation/RoomItem/Tag.tsx index 6484b3085..084e03e88 100644 --- a/app/presentation/RoomItem/Tag.js +++ b/app/presentation/RoomItem/Tag.tsx @@ -1,32 +1,25 @@ import React from 'react'; import { Text, View } from 'react-native'; -import PropTypes from 'prop-types'; import { themes } from '../../constants/colors'; import { useTheme } from '../../theme'; import styles from './styles'; -const Tag = React.memo(({ name, testID }) => { - const { theme } = useTheme(); +interface ITag { + name: string; + testID?: string; +} + +const Tag = React.memo(({ name, testID }: ITag) => { + const { theme }: any = useTheme(); return ( <View style={[styles.tagContainer, { backgroundColor: themes[theme].borderColor }]}> - <Text - style={[ - styles.tagText, { color: themes[theme].infoText } - ]} - numberOfLines={1} - testID={testID} - > + <Text style={[styles.tagText, { color: themes[theme].infoText }]} numberOfLines={1} testID={testID}> {name} </Text> </View> ); }); -Tag.propTypes = { - name: PropTypes.string, - testID: PropTypes.string -}; - export default Tag; diff --git a/app/presentation/RoomItem/Title.js b/app/presentation/RoomItem/Title.js deleted file mode 100644 index 6f4f848d6..000000000 --- a/app/presentation/RoomItem/Title.js +++ /dev/null @@ -1,31 +0,0 @@ -import React from 'react'; -import { Text } from 'react-native'; -import PropTypes from 'prop-types'; - -import styles from './styles'; -import { themes } from '../../constants/colors'; - -const Title = React.memo(({ - name, theme, hideUnreadStatus, alert -}) => ( - <Text - style={[ - styles.title, - alert && !hideUnreadStatus && styles.alert, - { color: themes[theme].titleText } - ]} - ellipsizeMode='tail' - numberOfLines={1} - > - {name} - </Text> -)); - -Title.propTypes = { - name: PropTypes.string, - theme: PropTypes.string, - hideUnreadStatus: PropTypes.bool, - alert: PropTypes.bool -}; - -export default Title; diff --git a/app/presentation/RoomItem/Title.tsx b/app/presentation/RoomItem/Title.tsx new file mode 100644 index 000000000..93f74bf21 --- /dev/null +++ b/app/presentation/RoomItem/Title.tsx @@ -0,0 +1,23 @@ +import React from 'react'; +import { Text } from 'react-native'; + +import styles from './styles'; +import { themes } from '../../constants/colors'; + +interface ITitle { + name: string; + theme: string; + hideUnreadStatus: boolean; + alert: boolean; +} + +const Title = React.memo(({ name, theme, hideUnreadStatus, alert }: ITitle) => ( + <Text + style={[styles.title, alert && !hideUnreadStatus && styles.alert, { color: themes[theme].titleText }]} + ellipsizeMode='tail' + numberOfLines={1}> + {name} + </Text> +)); + +export default Title; diff --git a/app/presentation/RoomItem/Touchable.js b/app/presentation/RoomItem/Touchable.js deleted file mode 100644 index 52f2c094f..000000000 --- a/app/presentation/RoomItem/Touchable.js +++ /dev/null @@ -1,284 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import { Animated } from 'react-native'; -import { - LongPressGestureHandler, PanGestureHandler, State -} from 'react-native-gesture-handler'; - -import Touch from '../../utils/touch'; -import { - ACTION_WIDTH, - SMALL_SWIPE, - LONG_SWIPE -} from './styles'; -import { isRTL } from '../../i18n'; -import { themes } from '../../constants/colors'; -import { LeftActions, RightActions } from './Actions'; - -class Touchable extends React.Component { - static propTypes = { - type: PropTypes.string.isRequired, - onPress: PropTypes.func, - onLongPress: PropTypes.func, - testID: PropTypes.string, - width: PropTypes.number, - favorite: PropTypes.bool, - isRead: PropTypes.bool, - rid: PropTypes.string, - toggleFav: PropTypes.func, - toggleRead: PropTypes.func, - hideChannel: PropTypes.func, - children: PropTypes.element, - theme: PropTypes.string, - isFocused: PropTypes.bool, - swipeEnabled: PropTypes.bool - } - - constructor(props) { - super(props); - this.dragX = new Animated.Value(0); - this.rowOffSet = new Animated.Value(0); - this.reverse = new Animated.Value(isRTL() ? -1 : 1); - this.transX = Animated.add( - this.rowOffSet, - this.dragX - ); - this.transXReverse = Animated.multiply( - this.transX, - this.reverse - ); - this.state = { - rowState: 0 // 0: closed, 1: right opened, -1: left opened - }; - this._onGestureEvent = Animated.event( - [{ nativeEvent: { translationX: this.dragX } }], { useNativeDriver: true } - ); - this._value = 0; - } - - _onHandlerStateChange = ({ nativeEvent }) => { - if (nativeEvent.oldState === State.ACTIVE) { - this._handleRelease(nativeEvent); - } - } - - onLongPressHandlerStateChange = ({ nativeEvent }) => { - if (nativeEvent.state === State.ACTIVE) { - this.onLongPress(); - } - } - - - _handleRelease = (nativeEvent) => { - const { translationX } = nativeEvent; - const { rowState } = this.state; - this._value += translationX; - - let toValue = 0; - if (rowState === 0) { // if no option is opened - if (translationX > 0 && translationX < LONG_SWIPE) { - // open leading option if he swipe right but not enough to trigger action - if (isRTL()) { - toValue = 2 * ACTION_WIDTH; - } else { - toValue = ACTION_WIDTH; - } - this.setState({ rowState: -1 }); - } else if (translationX >= LONG_SWIPE) { - toValue = 0; - if (isRTL()) { - this.hideChannel(); - } else { - this.toggleRead(); - } - } else if (translationX < 0 && translationX > -LONG_SWIPE) { - // open trailing option if he swipe left - if (isRTL()) { - toValue = -ACTION_WIDTH; - } else { - toValue = -2 * ACTION_WIDTH; - } - this.setState({ rowState: 1 }); - } else if (translationX <= -LONG_SWIPE) { - toValue = 0; - this.setState({ rowState: 0 }); - if (isRTL()) { - this.toggleRead(); - } else { - this.hideChannel(); - } - } else { - toValue = 0; - } - } - - if (rowState === -1) { // if left option is opened - if (this._value < SMALL_SWIPE) { - toValue = 0; - this.setState({ rowState: 0 }); - } else if (this._value > LONG_SWIPE) { - toValue = 0; - this.setState({ rowState: 0 }); - if (isRTL()) { - this.hideChannel(); - } else { - this.toggleRead(); - } - } else if (isRTL()) { - toValue = 2 * ACTION_WIDTH; - } else { - toValue = ACTION_WIDTH; - } - } - - if (rowState === 1) { // if right option is opened - if (this._value > -2 * SMALL_SWIPE) { - toValue = 0; - this.setState({ rowState: 0 }); - } else if (this._value < -LONG_SWIPE) { - toValue = 0; - this.setState({ rowState: 0 }); - if (isRTL()) { - this.toggleRead(); - } else { - this.hideChannel(); - } - } else if (isRTL()) { - toValue = -ACTION_WIDTH; - } else { - toValue = -2 * ACTION_WIDTH; - } - } - this._animateRow(toValue); - } - - _animateRow = (toValue) => { - this.rowOffSet.setValue(this._value); - this._value = toValue; - this.dragX.setValue(0); - Animated.spring(this.rowOffSet, { - toValue, - bounciness: 0, - useNativeDriver: true - }).start(); - } - - close = () => { - this.setState({ rowState: 0 }); - this._animateRow(0); - } - - toggleFav = () => { - const { toggleFav, rid, favorite } = this.props; - if (toggleFav) { - toggleFav(rid, favorite); - } - this.close(); - }; - - toggleRead = () => { - const { toggleRead, rid, isRead } = this.props; - - if (toggleRead) { - toggleRead(rid, isRead); - } - }; - - hideChannel = () => { - const { hideChannel, rid, type } = this.props; - if (hideChannel) { - hideChannel(rid, type); - } - }; - - onToggleReadPress = () => { - this.toggleRead(); - this.close(); - }; - - onHidePress = () => { - this.hideChannel(); - this.close(); - }; - - onPress = () => { - const { rowState } = this.state; - if (rowState !== 0) { - this.close(); - return; - } - const { onPress } = this.props; - if (onPress) { - onPress(); - } - }; - - onLongPress = () => { - const { rowState } = this.state; - const { onLongPress } = this.props; - if (rowState !== 0) { - this.close(); - return; - } - - if (onLongPress) { - onLongPress(); - } - }; - - render() { - const { - testID, isRead, width, favorite, children, theme, isFocused, swipeEnabled - } = this.props; - - return ( - <LongPressGestureHandler onHandlerStateChange={this.onLongPressHandlerStateChange}> - <Animated.View> - <PanGestureHandler - minDeltaX={20} - onGestureEvent={this._onGestureEvent} - onHandlerStateChange={this._onHandlerStateChange} - enabled={swipeEnabled} - > - <Animated.View> - <LeftActions - transX={this.transXReverse} - isRead={isRead} - width={width} - onToggleReadPress={this.onToggleReadPress} - theme={theme} - /> - <RightActions - transX={this.transXReverse} - favorite={favorite} - width={width} - toggleFav={this.toggleFav} - onHidePress={this.onHidePress} - theme={theme} - /> - <Animated.View - style={{ - transform: [{ translateX: this.transX }] - }} - > - <Touch - onPress={this.onPress} - theme={theme} - testID={testID} - style={{ - backgroundColor: isFocused ? themes[theme].chatComponentBackground : themes[theme].backgroundColor - }} - > - {children} - </Touch> - </Animated.View> - </Animated.View> - - </PanGestureHandler> - </Animated.View> - </LongPressGestureHandler> - ); - } -} - -export default Touchable; diff --git a/app/presentation/RoomItem/Touchable.tsx b/app/presentation/RoomItem/Touchable.tsx new file mode 100644 index 000000000..84071bbef --- /dev/null +++ b/app/presentation/RoomItem/Touchable.tsx @@ -0,0 +1,278 @@ +import React from 'react'; +import { Animated } from 'react-native'; +import { LongPressGestureHandler, PanGestureHandler, State } from 'react-native-gesture-handler'; + +import Touch from '../../utils/touch'; +import { ACTION_WIDTH, LONG_SWIPE, SMALL_SWIPE } from './styles'; +import { isRTL } from '../../i18n'; +import { themes } from '../../constants/colors'; +import { LeftActions, RightActions } from './Actions'; + +interface ITouchableProps { + children: JSX.Element; + type: string; + onPress(): void; + onLongPress(): void; + testID: string; + width: number; + favorite: boolean; + isRead: boolean; + rid: string; + toggleFav: Function; + toggleRead: Function; + hideChannel: Function; + theme: string; + isFocused: boolean; + swipeEnabled: boolean; +} + +class Touchable extends React.Component<ITouchableProps, any> { + private dragX: Animated.Value; + + private rowOffSet: Animated.Value; + + private reverse: Animated.Value; + + private transX: Animated.AnimatedAddition; + + private transXReverse: Animated.AnimatedMultiplication; + + private _onGestureEvent: (...args: any[]) => void; + + private _value: number; + + constructor(props: ITouchableProps) { + super(props); + this.dragX = new Animated.Value(0); + this.rowOffSet = new Animated.Value(0); + this.reverse = new Animated.Value(isRTL() ? -1 : 1); + this.transX = Animated.add(this.rowOffSet, this.dragX); + this.transXReverse = Animated.multiply(this.transX, this.reverse); + this.state = { + rowState: 0 // 0: closed, 1: right opened, -1: left opened + }; + this._onGestureEvent = Animated.event([{ nativeEvent: { translationX: this.dragX } }], { useNativeDriver: true }); + this._value = 0; + } + + _onHandlerStateChange = ({ nativeEvent }: any) => { + if (nativeEvent.oldState === State.ACTIVE) { + this._handleRelease(nativeEvent); + } + }; + + onLongPressHandlerStateChange = ({ nativeEvent }: any) => { + if (nativeEvent.state === State.ACTIVE) { + this.onLongPress(); + } + }; + + _handleRelease = (nativeEvent: any) => { + const { translationX } = nativeEvent; + const { rowState } = this.state; + this._value += translationX; + + let toValue = 0; + if (rowState === 0) { + // if no option is opened + if (translationX > 0 && translationX < LONG_SWIPE) { + // open leading option if he swipe right but not enough to trigger action + if (isRTL()) { + toValue = 2 * ACTION_WIDTH; + } else { + toValue = ACTION_WIDTH; + } + this.setState({ rowState: -1 }); + } else if (translationX >= LONG_SWIPE) { + toValue = 0; + if (isRTL()) { + this.hideChannel(); + } else { + this.toggleRead(); + } + } else if (translationX < 0 && translationX > -LONG_SWIPE) { + // open trailing option if he swipe left + if (isRTL()) { + toValue = -ACTION_WIDTH; + } else { + toValue = -2 * ACTION_WIDTH; + } + this.setState({ rowState: 1 }); + } else if (translationX <= -LONG_SWIPE) { + toValue = 0; + this.setState({ rowState: 0 }); + if (isRTL()) { + this.toggleRead(); + } else { + this.hideChannel(); + } + } else { + toValue = 0; + } + } + + if (rowState === -1) { + // if left option is opened + if (this._value < SMALL_SWIPE) { + toValue = 0; + this.setState({ rowState: 0 }); + } else if (this._value > LONG_SWIPE) { + toValue = 0; + this.setState({ rowState: 0 }); + if (isRTL()) { + this.hideChannel(); + } else { + this.toggleRead(); + } + } else if (isRTL()) { + toValue = 2 * ACTION_WIDTH; + } else { + toValue = ACTION_WIDTH; + } + } + + if (rowState === 1) { + // if right option is opened + if (this._value > -2 * SMALL_SWIPE) { + toValue = 0; + this.setState({ rowState: 0 }); + } else if (this._value < -LONG_SWIPE) { + toValue = 0; + this.setState({ rowState: 0 }); + if (isRTL()) { + this.toggleRead(); + } else { + this.hideChannel(); + } + } else if (isRTL()) { + toValue = -ACTION_WIDTH; + } else { + toValue = -2 * ACTION_WIDTH; + } + } + this._animateRow(toValue); + }; + + _animateRow = (toValue: any) => { + this.rowOffSet.setValue(this._value); + this._value = toValue; + this.dragX.setValue(0); + Animated.spring(this.rowOffSet, { + toValue, + bounciness: 0, + useNativeDriver: true + }).start(); + }; + + close = () => { + this.setState({ rowState: 0 }); + this._animateRow(0); + }; + + toggleFav = () => { + const { toggleFav, rid, favorite } = this.props; + if (toggleFav) { + toggleFav(rid, favorite); + } + this.close(); + }; + + toggleRead = () => { + const { toggleRead, rid, isRead } = this.props; + if (toggleRead) { + toggleRead(rid, isRead); + } + }; + + hideChannel = () => { + const { hideChannel, rid, type } = this.props; + if (hideChannel) { + hideChannel(rid, type); + } + }; + + onToggleReadPress = () => { + this.toggleRead(); + this.close(); + }; + + onHidePress = () => { + this.hideChannel(); + this.close(); + }; + + onPress = () => { + const { rowState } = this.state; + if (rowState !== 0) { + this.close(); + return; + } + const { onPress } = this.props; + if (onPress) { + onPress(); + } + }; + + onLongPress = () => { + const { rowState } = this.state; + const { onLongPress } = this.props; + if (rowState !== 0) { + this.close(); + return; + } + + if (onLongPress) { + onLongPress(); + } + }; + + render() { + const { testID, isRead, width, favorite, children, theme, isFocused, swipeEnabled } = this.props; + + return ( + <LongPressGestureHandler onHandlerStateChange={this.onLongPressHandlerStateChange}> + <Animated.View> + <PanGestureHandler + minDeltaX={20} + onGestureEvent={this._onGestureEvent} + onHandlerStateChange={this._onHandlerStateChange} + enabled={swipeEnabled}> + <Animated.View> + <LeftActions + transX={this.transXReverse} + isRead={isRead} + width={width} + onToggleReadPress={this.onToggleReadPress} + theme={theme} + /> + <RightActions + transX={this.transXReverse} + favorite={favorite} + width={width} + toggleFav={this.toggleFav} + onHidePress={this.onHidePress} + theme={theme} + /> + <Animated.View + style={{ + transform: [{ translateX: this.transX }] + }}> + <Touch + onPress={this.onPress} + theme={theme} + testID={testID} + style={{ + backgroundColor: isFocused ? themes[theme].chatComponentBackground : themes[theme].backgroundColor + }}> + {children} + </Touch> + </Animated.View> + </Animated.View> + </PanGestureHandler> + </Animated.View> + </LongPressGestureHandler> + ); + } +} + +export default Touchable; diff --git a/app/presentation/RoomItem/TypeIcon.js b/app/presentation/RoomItem/TypeIcon.js deleted file mode 100644 index 425ee6db4..000000000 --- a/app/presentation/RoomItem/TypeIcon.js +++ /dev/null @@ -1,18 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; - -import RoomTypeIcon from '../../containers/RoomTypeIcon'; - -const TypeIcon = React.memo(({ - type, prid, status, isGroupChat, teamMain -}) => <RoomTypeIcon type={prid ? 'discussion' : type} isGroupChat={isGroupChat} status={status} teamMain={teamMain} />); - -TypeIcon.propTypes = { - type: PropTypes.string, - status: PropTypes.string, - prid: PropTypes.string, - isGroupChat: PropTypes.bool, - teamMain: PropTypes.bool -}; - -export default TypeIcon; diff --git a/app/presentation/RoomItem/TypeIcon.tsx b/app/presentation/RoomItem/TypeIcon.tsx new file mode 100644 index 000000000..414c16724 --- /dev/null +++ b/app/presentation/RoomItem/TypeIcon.tsx @@ -0,0 +1,18 @@ +import React from 'react'; + +import RoomTypeIcon from '../../containers/RoomTypeIcon'; + +interface ITypeIcon { + type: string; + status: string; + prid: string; + isGroupChat: boolean; + teamMain: boolean; + theme?: string; +} + +const TypeIcon = React.memo(({ type, prid, status, isGroupChat, teamMain }: ITypeIcon) => ( + <RoomTypeIcon type={prid ? 'discussion' : type} isGroupChat={isGroupChat} status={status} teamMain={teamMain} /> +)); + +export default TypeIcon; diff --git a/app/presentation/RoomItem/UpdatedAt.js b/app/presentation/RoomItem/UpdatedAt.js deleted file mode 100644 index b8bc9e69a..000000000 --- a/app/presentation/RoomItem/UpdatedAt.js +++ /dev/null @@ -1,48 +0,0 @@ -import React from 'react'; -import { Text } from 'react-native'; -import PropTypes from 'prop-types'; - -import styles from './styles'; -import { themes } from '../../constants/colors'; -import { capitalize } from '../../utils/room'; - -const UpdatedAt = React.memo(({ - date, theme, hideUnreadStatus, alert -}) => { - if (!date) { - return null; - } - return ( - <Text - style={[ - styles.date, - { - color: - themes[theme] - .auxiliaryText - }, - alert && !hideUnreadStatus && [ - styles.updateAlert, - { - color: - themes[theme] - .tintColor - } - ] - ]} - ellipsizeMode='tail' - numberOfLines={1} - > - {capitalize(date)} - </Text> - ); -}); - -UpdatedAt.propTypes = { - date: PropTypes.string, - theme: PropTypes.string, - hideUnreadStatus: PropTypes.bool, - alert: PropTypes.bool -}; - -export default UpdatedAt; diff --git a/app/presentation/RoomItem/UpdatedAt.tsx b/app/presentation/RoomItem/UpdatedAt.tsx new file mode 100644 index 000000000..eed4416aa --- /dev/null +++ b/app/presentation/RoomItem/UpdatedAt.tsx @@ -0,0 +1,41 @@ +import React from 'react'; +import { Text } from 'react-native'; + +import styles from './styles'; +import { themes } from '../../constants/colors'; +import { capitalize } from '../../utils/room'; + +interface IUpdatedAt { + date: string; + theme: string; + hideUnreadStatus: boolean; + alert: boolean; +} + +const UpdatedAt = React.memo(({ date, theme, hideUnreadStatus, alert }: IUpdatedAt) => { + if (!date) { + return null; + } + return ( + <Text + style={[ + styles.date, + { + color: themes[theme].auxiliaryText + }, + alert && + !hideUnreadStatus && [ + styles.updateAlert, + { + color: themes[theme].tintColor + } + ] + ]} + ellipsizeMode='tail' + numberOfLines={1}> + {capitalize(date)} + </Text> + ); +}); + +export default UpdatedAt; diff --git a/app/presentation/RoomItem/Wrapper.js b/app/presentation/RoomItem/Wrapper.js deleted file mode 100644 index 52aa0f8a8..000000000 --- a/app/presentation/RoomItem/Wrapper.js +++ /dev/null @@ -1,52 +0,0 @@ -import React from 'react'; -import { View } from 'react-native'; -import PropTypes from 'prop-types'; - -import styles from './styles'; -import { themes } from '../../constants/colors'; -import Avatar from '../../containers/Avatar'; - -const Wrapper = ({ - accessibilityLabel, - avatar, - avatarSize, - type, - theme, - rid, - children -}) => ( - <View - style={styles.container} - accessibilityLabel={accessibilityLabel} - > - <Avatar - text={avatar} - size={avatarSize} - type={type} - style={styles.avatar} - rid={rid} - /> - <View - style={[ - styles.centerContainer, - { - borderColor: themes[theme].separatorColor - } - ]} - > - {children} - </View> - </View> -); - -Wrapper.propTypes = { - accessibilityLabel: PropTypes.string, - avatar: PropTypes.string, - avatarSize: PropTypes.number, - type: PropTypes.string, - theme: PropTypes.string, - rid: PropTypes.string, - children: PropTypes.element -}; - -export default Wrapper; diff --git a/app/presentation/RoomItem/Wrapper.tsx b/app/presentation/RoomItem/Wrapper.tsx new file mode 100644 index 000000000..903a5edca --- /dev/null +++ b/app/presentation/RoomItem/Wrapper.tsx @@ -0,0 +1,33 @@ +import React from 'react'; +import { View } from 'react-native'; + +import styles from './styles'; +import { themes } from '../../constants/colors'; +import Avatar from '../../containers/Avatar'; + +interface IWrapper { + accessibilityLabel: string; + avatar: string; + avatarSize: number; + type: string; + theme: string; + rid: string; + children: JSX.Element; +} + +const Wrapper = ({ accessibilityLabel, avatar, avatarSize, type, theme, rid, children }: IWrapper) => ( + <View style={styles.container} accessibilityLabel={accessibilityLabel}> + <Avatar text={avatar} size={avatarSize} type={type} style={styles.avatar} rid={rid} /> + <View + style={[ + styles.centerContainer, + { + borderColor: themes[theme].separatorColor + } + ]}> + {children} + </View> + </View> +); + +export default Wrapper; diff --git a/app/presentation/RoomItem/index.js b/app/presentation/RoomItem/index.tsx similarity index 70% rename from app/presentation/RoomItem/index.js rename to app/presentation/RoomItem/index.tsx index 0b64437c6..eedc1f4ff 100644 --- a/app/presentation/RoomItem/index.js +++ b/app/presentation/RoomItem/index.tsx @@ -1,5 +1,4 @@ import React from 'react'; -import PropTypes from 'prop-types'; import { connect } from 'react-redux'; import I18n from '../../i18n'; @@ -9,43 +8,38 @@ import RoomItem from './RoomItem'; export { ROW_HEIGHT }; -const attrs = [ - 'width', - 'status', - 'connected', - 'theme', - 'isFocused', - 'forceUpdate', - 'showLastMessage', - 'autoJoin' -]; +interface IRoomItemContainerProps { + item: any; + showLastMessage: boolean; + id: string; + onPress: Function; + onLongPress: Function; + username: string; + avatarSize: number; + width: number; + status: string; + toggleFav(): void; + toggleRead(): void; + hideChannel(): void; + useRealName: boolean; + getUserPresence: Function; + connected: boolean; + theme: string; + isFocused: boolean; + getRoomTitle: Function; + getRoomAvatar: Function; + getIsGroupChat: Function; + getIsRead: Function; + swipeEnabled: boolean; + autoJoin: boolean; +} -class RoomItemContainer extends React.Component { - static propTypes = { - item: PropTypes.object.isRequired, - showLastMessage: PropTypes.bool, - id: PropTypes.string, - onPress: PropTypes.func, - onLongPress: PropTypes.func, - username: PropTypes.string, - avatarSize: PropTypes.number, - width: PropTypes.number, - status: PropTypes.string, - toggleFav: PropTypes.func, - toggleRead: PropTypes.func, - hideChannel: PropTypes.func, - useRealName: PropTypes.bool, - getUserPresence: PropTypes.func, - connected: PropTypes.bool, - theme: PropTypes.string, - isFocused: PropTypes.bool, - getRoomTitle: PropTypes.func, - getRoomAvatar: PropTypes.func, - getIsGroupChat: PropTypes.func, - getIsRead: PropTypes.func, - swipeEnabled: PropTypes.bool, - autoJoin: PropTypes.bool - }; +const attrs = ['width', 'status', 'connected', 'theme', 'isFocused', 'forceUpdate', 'showLastMessage', 'autoJoin']; + +class RoomItemContainer extends React.Component<IRoomItemContainerProps, any> { + private mounted: boolean; + + private roomSubscription: any; static defaultProps = { avatarSize: 48, @@ -56,9 +50,9 @@ class RoomItemContainer extends React.Component { getIsGroupChat: () => false, getIsRead: () => false, swipeEnabled: true - } + }; - constructor(props) { + constructor(props: IRoomItemContainerProps) { super(props); this.mounted = false; this.init(); @@ -72,12 +66,12 @@ class RoomItemContainer extends React.Component { } } - shouldComponentUpdate(nextProps) { - const { props } = this; + shouldComponentUpdate(nextProps: any) { + const { props }: any = this; return !attrs.every(key => props[key] === nextProps[key]); } - componentDidUpdate(prevProps) { + componentDidUpdate(prevProps: any) { const { connected, getUserPresence, id } = this.props; if (prevProps.connected !== connected && connected && this.isDirect) { getUserPresence(id); @@ -96,7 +90,10 @@ class RoomItemContainer extends React.Component { } get isDirect() { - const { item: { t }, id } = this.props; + const { + item: { t }, + id + }: any = this.props; return t === 'd' && id && !this.isGroupChat; } @@ -108,19 +105,19 @@ class RoomItemContainer extends React.Component { this.forceUpdate(); }); } - } + }; onPress = () => { const { item, onPress } = this.props; return onPress(item); - } + }; onLongPress = () => { const { item, onLongPress } = this.props; if (onLongPress) { return onLongPress(item); } - } + }; render() { const { @@ -143,28 +140,29 @@ class RoomItemContainer extends React.Component { autoJoin } = this.props; const name = getRoomTitle(item); - const testID = `rooms-list-view-item-${ name }`; + const testID = `rooms-list-view-item-${name}`; const avatar = getRoomAvatar(item); const isRead = getIsRead(item); const date = item.roomUpdatedAt && formatDate(item.roomUpdatedAt); - const alert = (item.alert || item.tunread?.length); + const alert = item.alert || item.tunread?.length; let accessibilityLabel = name; if (item.unread === 1) { - accessibilityLabel += `, ${ item.unread } ${ I18n.t('alert') }`; + accessibilityLabel += `, ${item.unread} ${I18n.t('alert')}`; } else if (item.unread > 1) { - accessibilityLabel += `, ${ item.unread } ${ I18n.t('alerts') }`; + accessibilityLabel += `, ${item.unread} ${I18n.t('alerts')}`; } if (item.userMentions > 0) { - accessibilityLabel += `, ${ I18n.t('you_were_mentioned') }`; + accessibilityLabel += `, ${I18n.t('you_were_mentioned')}`; } if (date) { - accessibilityLabel += `, ${ I18n.t('last_message') } ${ date }`; + accessibilityLabel += `, ${I18n.t('last_message')} ${date}`; } return ( + // @ts-ignore <RoomItem name={name} avatar={avatar} @@ -207,7 +205,7 @@ class RoomItemContainer extends React.Component { } } -const mapStateToProps = (state, ownProps) => { +const mapStateToProps = (state: any, ownProps: any) => { let status = 'loading'; const { id, type, visitor = {} } = ownProps; if (state.meteor.connected) { diff --git a/app/presentation/RoomItem/styles.js b/app/presentation/RoomItem/styles.ts similarity index 95% rename from app/presentation/RoomItem/styles.js rename to app/presentation/RoomItem/styles.ts index 787546c76..953217a27 100644 --- a/app/presentation/RoomItem/styles.js +++ b/app/presentation/RoomItem/styles.ts @@ -1,4 +1,4 @@ -import { StyleSheet, PixelRatio } from 'react-native'; +import { PixelRatio, StyleSheet } from 'react-native'; import sharedStyles from '../../views/Styles'; @@ -7,7 +7,7 @@ export const ACTION_WIDTH = 80; export const SMALL_SWIPE = ACTION_WIDTH / 2; export const LONG_SWIPE = ACTION_WIDTH * 3; -export default StyleSheet.create({ +export default StyleSheet.create<any>({ flex: { flex: 1 }, diff --git a/app/presentation/ServerItem/index.js b/app/presentation/ServerItem/index.js deleted file mode 100644 index ab69020f2..000000000 --- a/app/presentation/ServerItem/index.js +++ /dev/null @@ -1,69 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import { View, Text, Pressable } from 'react-native'; -import FastImage from '@rocket.chat/react-native-fast-image'; - -import Check from '../../containers/Check'; -import styles, { ROW_HEIGHT } from './styles'; -import { themes } from '../../constants/colors'; -import { isIOS } from '../../utils/deviceInfo'; -import { withTheme } from '../../theme'; - -export { ROW_HEIGHT }; - -const defaultLogo = require('../../static/images/logo.png'); - -const ServerItem = React.memo(({ - item, onPress, onLongPress, hasCheck, theme -}) => ( - <Pressable - onPress={onPress} - onLongPress={() => onLongPress?.()} - testID={`rooms-list-header-server-${ item.id }`} - android_ripple={{ - color: themes[theme].bannerBackground - }} - style={({ pressed }) => ({ - backgroundColor: isIOS && pressed - ? themes[theme].bannerBackground - : themes[theme].backgroundColor - })} - > - <View style={styles.serverItemContainer}> - {item.iconURL - ? ( - <FastImage - source={{ - uri: item.iconURL, - priority: FastImage.priority.high - }} - defaultSource={defaultLogo} - style={styles.serverIcon} - onError={() => console.log('err_loading_server_icon')} - /> - ) - : ( - <FastImage - source={defaultLogo} - style={styles.serverIcon} - /> - ) - } - <View style={styles.serverTextContainer}> - <Text numberOfLines={1} style={[styles.serverName, { color: themes[theme].titleText }]}>{item.name || item.id}</Text> - <Text numberOfLines={1} style={[styles.serverUrl, { color: themes[theme].auxiliaryText }]}>{item.id}</Text> - </View> - {hasCheck ? <Check theme={theme} /> : null} - </View> - </Pressable> -)); - -ServerItem.propTypes = { - item: PropTypes.object.isRequired, - onPress: PropTypes.func.isRequired, - onLongPress: PropTypes.func, - hasCheck: PropTypes.bool, - theme: PropTypes.string -}; - -export default withTheme(ServerItem); diff --git a/app/presentation/ServerItem/index.tsx b/app/presentation/ServerItem/index.tsx new file mode 100644 index 000000000..772fac212 --- /dev/null +++ b/app/presentation/ServerItem/index.tsx @@ -0,0 +1,67 @@ +import React from 'react'; +// @ts-ignore +import { Pressable, Text, View } from 'react-native'; +import FastImage from '@rocket.chat/react-native-fast-image'; + +import Check from '../../containers/Check'; +import styles, { ROW_HEIGHT } from './styles'; +import { themes } from '../../constants/colors'; +import { isIOS } from '../../utils/deviceInfo'; +import { withTheme } from '../../theme'; + +export { ROW_HEIGHT }; + +interface IServerItem { + item: { + id: string; + iconURL: string; + name: string; + }; + onPress(): void; + onLongPress(): void; + hasCheck: boolean; + theme: string; +} + +const defaultLogo = require('../../static/images/logo.png'); + +const ServerItem = React.memo(({ item, onPress, onLongPress, hasCheck, theme }: IServerItem) => ( + <Pressable + onPress={onPress} + onLongPress={() => onLongPress?.()} + testID={`rooms-list-header-server-${item.id}`} + android_ripple={{ + color: themes[theme].bannerBackground + }} + style={({ pressed }: any) => ({ + backgroundColor: isIOS && pressed ? themes[theme].bannerBackground : themes[theme].backgroundColor + })}> + <View style={styles.serverItemContainer}> + {item.iconURL ? ( + <FastImage + source={{ + uri: item.iconURL, + priority: FastImage.priority.high + }} + // @ts-ignore + defaultSource={defaultLogo} + style={styles.serverIcon} + onError={() => console.log('err_loading_server_icon')} + /> + ) : ( + <FastImage source={defaultLogo} style={styles.serverIcon} /> + )} + <View style={styles.serverTextContainer}> + <Text numberOfLines={1} style={[styles.serverName, { color: themes[theme].titleText }]}> + {item.name || item.id} + </Text> + <Text numberOfLines={1} style={[styles.serverUrl, { color: themes[theme].auxiliaryText }]}> + {item.id} + </Text> + </View> + {hasCheck ? <Check theme={theme} /> : null} + </View> + </Pressable> +)); + +export default withTheme(ServerItem); diff --git a/app/presentation/ServerItem/styles.js b/app/presentation/ServerItem/styles.ts similarity index 100% rename from app/presentation/ServerItem/styles.js rename to app/presentation/ServerItem/styles.ts diff --git a/app/presentation/TextInput.js b/app/presentation/TextInput.tsx similarity index 61% rename from app/presentation/TextInput.js rename to app/presentation/TextInput.tsx index 8034bac5a..b3f18676c 100644 --- a/app/presentation/TextInput.js +++ b/app/presentation/TextInput.tsx @@ -1,18 +1,20 @@ import React from 'react'; -import { TextInput, StyleSheet, I18nManager } from 'react-native'; -import PropTypes from 'prop-types'; +import { I18nManager, StyleSheet, TextInput, TextInputProps } from 'react-native'; import { themes } from '../constants/colors'; const styles = StyleSheet.create({ input: { - ...I18nManager.isRTL - ? { textAlign: 'right' } - : { textAlign: 'auto' } + ...(I18nManager.isRTL ? { textAlign: 'right' } : { textAlign: 'auto' }) } }); -const ThemedTextInput = React.forwardRef(({ style, theme, ...props }, ref) => ( +interface IThemedTextInput extends TextInputProps { + style: object; + theme: string; +} + +const ThemedTextInput = React.forwardRef(({ style, theme, ...props }: IThemedTextInput, ref: any) => ( <TextInput ref={ref} style={[{ color: themes[theme].titleText }, style, styles.input]} @@ -22,9 +24,4 @@ const ThemedTextInput = React.forwardRef(({ style, theme, ...props }, ref) => ( /> )); -ThemedTextInput.propTypes = { - style: PropTypes.object, - theme: PropTypes.string -}; - export default ThemedTextInput; diff --git a/app/presentation/UnreadBadge/getUnreadStyle.test.js b/app/presentation/UnreadBadge/getUnreadStyle.test.js index a0704b138..8ac3ea047 100644 --- a/app/presentation/UnreadBadge/getUnreadStyle.test.js +++ b/app/presentation/UnreadBadge/getUnreadStyle.test.js @@ -2,7 +2,7 @@ import { themes } from '../../constants/colors'; import { getUnreadStyle } from './getUnreadStyle'; -const testsForTheme = (theme) => { +const testsForTheme = theme => { const getUnreadStyleUtil = ({ ...props }) => getUnreadStyle({ theme, ...props }); test('render empty', () => { @@ -10,65 +10,79 @@ const testsForTheme = (theme) => { }); test('render unread', () => { - expect(getUnreadStyleUtil({ - unread: 1 - })).toEqual({ + expect( + getUnreadStyleUtil({ + unread: 1 + }) + ).toEqual({ backgroundColor: themes[theme].unreadColor, color: themes[theme].buttonText }); }); test('render thread unread', () => { - expect(getUnreadStyleUtil({ - tunread: [1] - })).toEqual({ + expect( + getUnreadStyleUtil({ + tunread: [1] + }) + ).toEqual({ backgroundColor: themes[theme].tunreadColor, color: themes[theme].buttonText }); }); test('render user mention', () => { - expect(getUnreadStyleUtil({ - unread: 1, - userMentions: 1 - })).toEqual({ + expect( + getUnreadStyleUtil({ + unread: 1, + userMentions: 1 + }) + ).toEqual({ backgroundColor: themes[theme].mentionMeColor, color: themes[theme].buttonText }); }); test('render group mention', () => { - expect(getUnreadStyleUtil({ - unread: 1, - groupMentions: 1 - })).toEqual({ + expect( + getUnreadStyleUtil({ + unread: 1, + groupMentions: 1 + }) + ).toEqual({ backgroundColor: themes[theme].mentionGroupColor, color: themes[theme].buttonText }); }); test('mentions priority', () => { - expect(getUnreadStyleUtil({ - unread: 1, - userMentions: 1, - groupMentions: 1, - tunread: [1] - })).toEqual({ + expect( + getUnreadStyleUtil({ + unread: 1, + userMentions: 1, + groupMentions: 1, + tunread: [1] + }) + ).toEqual({ backgroundColor: themes[theme].mentionMeColor, color: themes[theme].buttonText }); - expect(getUnreadStyleUtil({ - unread: 1, - groupMentions: 1, - tunread: [1] - })).toEqual({ + expect( + getUnreadStyleUtil({ + unread: 1, + groupMentions: 1, + tunread: [1] + }) + ).toEqual({ backgroundColor: themes[theme].mentionGroupColor, color: themes[theme].buttonText }); - expect(getUnreadStyleUtil({ - unread: 1, - tunread: [1] - })).toEqual({ + expect( + getUnreadStyleUtil({ + unread: 1, + tunread: [1] + }) + ).toEqual({ backgroundColor: themes[theme].tunreadColor, color: themes[theme].buttonText }); diff --git a/app/presentation/UnreadBadge/getUnreadStyle.js b/app/presentation/UnreadBadge/getUnreadStyle.ts similarity index 69% rename from app/presentation/UnreadBadge/getUnreadStyle.js rename to app/presentation/UnreadBadge/getUnreadStyle.ts index 0036d64f4..94e7acca4 100644 --- a/app/presentation/UnreadBadge/getUnreadStyle.js +++ b/app/presentation/UnreadBadge/getUnreadStyle.ts @@ -1,9 +1,7 @@ import { themes } from '../../constants/colors'; -export const getUnreadStyle = ({ - unread, userMentions, groupMentions, theme, tunread, tunreadUser, tunreadGroup -}) => { - if ((!unread || unread <= 0) && (!tunread?.length)) { +export const getUnreadStyle = ({ unread, userMentions, groupMentions, theme, tunread, tunreadUser, tunreadGroup }: any) => { + if ((!unread || unread <= 0) && !tunread?.length) { return {}; } @@ -18,6 +16,7 @@ export const getUnreadStyle = ({ } return { - backgroundColor, color + backgroundColor, + color }; }; diff --git a/app/presentation/UnreadBadge/index.js b/app/presentation/UnreadBadge/index.js deleted file mode 100644 index e60325279..000000000 --- a/app/presentation/UnreadBadge/index.js +++ /dev/null @@ -1,93 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import { View, Text, StyleSheet } from 'react-native'; - -import sharedStyles from '../../views/Styles'; -import { getUnreadStyle } from './getUnreadStyle'; -import { withTheme } from '../../theme'; - -const styles = StyleSheet.create({ - unreadNumberContainerNormal: { - height: 21, - paddingVertical: 3, - paddingHorizontal: 5, - borderRadius: 10.5, - alignItems: 'center', - justifyContent: 'center', - marginLeft: 10 - }, - unreadNumberContainerSmall: { - borderRadius: 10.5, - alignItems: 'center', - justifyContent: 'center' - }, - unreadText: { - fontSize: 13, - ...sharedStyles.textSemibold - }, - textSmall: { - fontSize: 10 - } -}); - -const UnreadBadge = React.memo(({ - theme, unread, userMentions, groupMentions, style, tunread, tunreadUser, tunreadGroup, small -}) => { - if ((!unread || unread <= 0) && (!tunread?.length)) { - return null; - } - const { backgroundColor, color } = getUnreadStyle({ - theme, unread, userMentions, groupMentions, tunread, tunreadUser, tunreadGroup - }); - - if (!backgroundColor) { - return null; - } - let text = unread || tunread?.length; - if (small && text >= 100) { - text = '+99'; - } - if (!small && text >= 1000) { - text = '+999'; - } - text = text.toString(); - - let minWidth = 21; - if (small) { - minWidth = 11 + text.length * 5; - } - - return ( - <View - style={[ - small ? styles.unreadNumberContainerSmall : styles.unreadNumberContainerNormal, - { backgroundColor, minWidth }, - style - ]} - > - <Text - style={[ - styles.unreadText, - small && styles.textSmall, - { color } - ]} - numberOfLines={1} - >{text} - </Text> - </View> - ); -}); - -UnreadBadge.propTypes = { - theme: PropTypes.string, - unread: PropTypes.number, - userMentions: PropTypes.number, - groupMentions: PropTypes.number, - style: PropTypes.object, - tunread: PropTypes.array, - tunreadUser: PropTypes.array, - tunreadGroup: PropTypes.array, - small: PropTypes.bool -}; - -export default withTheme(UnreadBadge); diff --git a/app/presentation/UnreadBadge/index.tsx b/app/presentation/UnreadBadge/index.tsx new file mode 100644 index 000000000..609278b92 --- /dev/null +++ b/app/presentation/UnreadBadge/index.tsx @@ -0,0 +1,91 @@ +import React from 'react'; +import { StyleSheet, Text, View } from 'react-native'; + +import sharedStyles from '../../views/Styles'; +import { getUnreadStyle } from './getUnreadStyle'; +import { withTheme } from '../../theme'; + +const styles = StyleSheet.create({ + unreadNumberContainerNormal: { + height: 21, + paddingVertical: 3, + paddingHorizontal: 5, + borderRadius: 10.5, + alignItems: 'center', + justifyContent: 'center', + marginLeft: 10 + }, + unreadNumberContainerSmall: { + borderRadius: 10.5, + alignItems: 'center', + justifyContent: 'center' + }, + unreadText: { + fontSize: 13, + ...sharedStyles.textSemibold + }, + textSmall: { + fontSize: 10 + } +}); + +interface IUnreadBadge { + theme: string; + unread: number; + userMentions: number; + groupMentions: number; + style: object; + tunread: []; + tunreadUser: []; + tunreadGroup: []; + small: boolean; +} + +const UnreadBadge = React.memo( + ({ theme, unread, userMentions, groupMentions, style, tunread, tunreadUser, tunreadGroup, small }: IUnreadBadge) => { + if ((!unread || unread <= 0) && !tunread?.length) { + return null; + } + const { backgroundColor, color } = getUnreadStyle({ + theme, + unread, + userMentions, + groupMentions, + tunread, + tunreadUser, + tunreadGroup + }); + + if (!backgroundColor) { + return null; + } + let text: any = unread || tunread?.length; + if (small && text >= 100) { + text = '+99'; + } + if (!small && text >= 1000) { + text = '+999'; + } + text = text.toString(); + + let minWidth = 21; + if (small) { + minWidth = 11 + text.length * 5; + } + + return ( + <View + style={[ + small ? styles.unreadNumberContainerSmall : styles.unreadNumberContainerNormal, + { backgroundColor, minWidth }, + style + ]}> + <Text style={[styles.unreadText, small && styles.textSmall, { color }]} numberOfLines={1}> + {text} + </Text> + </View> + ); + } +); + +export default withTheme(UnreadBadge); diff --git a/app/presentation/UserItem.js b/app/presentation/UserItem.tsx similarity index 65% rename from app/presentation/UserItem.js rename to app/presentation/UserItem.tsx index 5ffc4c6a2..56e6b9fcb 100644 --- a/app/presentation/UserItem.js +++ b/app/presentation/UserItem.tsx @@ -1,8 +1,6 @@ import React from 'react'; -import { - Text, View, StyleSheet, Pressable -} from 'react-native'; -import PropTypes from 'prop-types'; +// @ts-ignore +import { Pressable, StyleSheet, Text, View } from 'react-native'; import Avatar from '../containers/Avatar'; import { CustomIcon } from '../lib/Icons'; @@ -41,9 +39,18 @@ const styles = StyleSheet.create({ } }); -const UserItem = ({ - name, username, onPress, testID, onLongPress, style, icon, theme -}) => ( +interface IUserItem { + name: string; + username: string; + onPress(): void; + testID: string; + onLongPress(): void; + style: any; + icon: string; + theme: string; +} + +const UserItem = ({ name, username, onPress, testID, onLongPress, style, icon, theme }: IUserItem) => ( <Pressable onPress={onPress} onLongPress={onLongPress} @@ -51,32 +58,22 @@ const UserItem = ({ android_ripple={{ color: themes[theme].bannerBackground }} - style={({ pressed }) => ({ - backgroundColor: isIOS && pressed - ? themes[theme].bannerBackground - : 'transparent' - })} - > + style={({ pressed }: any) => ({ + backgroundColor: isIOS && pressed ? themes[theme].bannerBackground : 'transparent' + })}> <View style={[styles.container, styles.button, style]}> <Avatar text={username} size={30} style={styles.avatar} /> <View style={styles.textContainer}> - <Text style={[styles.name, { color: themes[theme].titleText }]} numberOfLines={1}>{name}</Text> - <Text style={[styles.username, { color: themes[theme].auxiliaryText }]} numberOfLines={1}>@{username}</Text> + <Text style={[styles.name, { color: themes[theme].titleText }]} numberOfLines={1}> + {name} + </Text> + <Text style={[styles.username, { color: themes[theme].auxiliaryText }]} numberOfLines={1}> + @{username} + </Text> </View> {icon ? <CustomIcon name={icon} size={22} style={[styles.icon, { color: themes[theme].actionTintColor }]} /> : null} </View> </Pressable> ); -UserItem.propTypes = { - name: PropTypes.string.isRequired, - username: PropTypes.string.isRequired, - onPress: PropTypes.func.isRequired, - testID: PropTypes.string.isRequired, - onLongPress: PropTypes.func, - style: PropTypes.any, - icon: PropTypes.string, - theme: PropTypes.string -}; - export default UserItem; diff --git a/app/reducers/createChannel.js b/app/reducers/createChannel.js index 7d94513a4..60e13f007 100644 --- a/app/reducers/createChannel.js +++ b/app/reducers/createChannel.js @@ -7,7 +7,7 @@ const initialState = { error: {} }; -export default function(state = initialState, action) { +export default function (state = initialState, action) { switch (action.type) { case CREATE_CHANNEL.REQUEST: return { diff --git a/app/reducers/createDiscussion.js b/app/reducers/createDiscussion.js index 582164f71..04081d157 100644 --- a/app/reducers/createDiscussion.js +++ b/app/reducers/createDiscussion.js @@ -7,7 +7,7 @@ const initialState = { error: {} }; -export default function(state = initialState, action) { +export default function (state = initialState, action) { switch (action.type) { case CREATE_DISCUSSION.REQUEST: return { diff --git a/app/reducers/index.js b/app/reducers/index.js index c0f5a9953..7aad41aa0 100644 --- a/app/reducers/index.js +++ b/app/reducers/index.js @@ -1,4 +1,6 @@ import { combineReducers } from 'redux'; + +import inquiry from '../ee/omnichannel/reducers/inquiry'; import settings from './settings'; import login from './login'; import meteor from './connect'; @@ -20,8 +22,6 @@ import encryption from './encryption'; import permissions from './permissions'; import roles from './roles'; -import inquiry from '../ee/omnichannel/reducers/inquiry'; - export default combineReducers({ settings, login, diff --git a/app/reducers/room.js b/app/reducers/room.js index 980715051..086e43f5c 100644 --- a/app/reducers/room.js +++ b/app/reducers/room.js @@ -6,7 +6,7 @@ const initialState = { rooms: [] }; -export default function(state = initialState, action) { +export default function (state = initialState, action) { switch (action.type) { case ROOM.SUBSCRIBE: return { @@ -16,8 +16,7 @@ export default function(state = initialState, action) { case ROOM.UNSUBSCRIBE: return { ...state, - rooms: state.rooms - .filter(rid => rid !== action.rid) + rooms: state.rooms.filter(rid => rid !== action.rid) }; case ROOM.LEAVE: return { diff --git a/app/reducers/selectedUsers.js b/app/reducers/selectedUsers.js index 0d8fa4cf7..42d7982c1 100644 --- a/app/reducers/selectedUsers.js +++ b/app/reducers/selectedUsers.js @@ -5,7 +5,7 @@ const initialState = { loading: false }; -export default function(state = initialState, action) { +export default function (state = initialState, action) { switch (action.type) { case SELECTED_USERS.ADD_USER: return { diff --git a/app/reducers/server.js b/app/reducers/server.js index 2c5b98199..14c7bbfdf 100644 --- a/app/reducers/server.js +++ b/app/reducers/server.js @@ -7,12 +7,10 @@ const initialState = { server: '', version: null, loading: true, - adding: false, previousServer: null, changingServer: false }; - export default function server(state = initialState, action) { switch (action.type) { case SERVER.REQUEST: @@ -59,13 +57,11 @@ export default function server(state = initialState, action) { case SERVER.INIT_ADD: return { ...state, - adding: true, previousServer: action.previousServer }; case SERVER.FINISH_ADD: return { ...state, - adding: false, previousServer: null }; default: diff --git a/app/reducers/sortPreferences.js b/app/reducers/sortPreferences.js index eee86ea6d..1e7c0b2dd 100644 --- a/app/reducers/sortPreferences.js +++ b/app/reducers/sortPreferences.js @@ -7,7 +7,6 @@ const initialState = { showUnread: false }; - export default (state = initialState, action) => { switch (action.type) { case SORT_PREFERENCES.SET_ALL: diff --git a/app/sagas/createChannel.js b/app/sagas/createChannel.js index 6e244d904..c12d18a55 100644 --- a/app/sagas/createChannel.js +++ b/app/sagas/createChannel.js @@ -1,16 +1,14 @@ -import { - select, put, call, take, takeLatest -} from 'redux-saga/effects'; +import { call, put, select, 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 { createChannelFailure, createChannelSuccess } from '../actions/createChannel'; import { showErrorAlert } from '../utils/info'; import RocketChat from '../lib/rocketchat'; import Navigation from '../lib/Navigation'; import database from '../lib/database'; import I18n from '../i18n'; -import { logEvent, events } from '../utils/log'; +import { events, logEvent } from '../utils/log'; import { goRoom } from '../utils/goRoom'; const createChannel = function createChannel(data) { @@ -34,17 +32,12 @@ const handleRequest = function* handleRequest({ data }) { let sub; if (data.isTeam) { - const { - type, - readOnly, - broadcast, - encrypted - } = data; + const { type, readOnly, broadcast, encrypted } = data; logEvent(events.CT_CREATE, { - type: `${ type }`, - readOnly: `${ readOnly }`, - broadcast: `${ broadcast }`, - encrypted: `${ encrypted }` + type: `${type}`, + readOnly: `${readOnly}`, + broadcast: `${broadcast}`, + encrypted: `${encrypted}` }); const result = yield call(createTeam, data); sub = { @@ -62,12 +55,7 @@ const handleRequest = function* handleRequest({ data }) { }; } } else { - const { - type, - readOnly, - broadcast, - encrypted - } = data; + const { type, readOnly, broadcast, encrypted } = data; logEvent(events.CR_CREATE, { type: type ? 'private' : 'public', readOnly, @@ -84,8 +72,8 @@ const handleRequest = function* handleRequest({ data }) { try { const db = database.active; const subCollection = db.get('subscriptions'); - yield db.action(async() => { - await subCollection.create((s) => { + yield db.action(async () => { + await subCollection.create(s => { s._raw = sanitizedRaw({ id: sub.rid }, subCollection.schema); Object.assign(s, sub); }); @@ -120,9 +108,13 @@ const handleFailure = function handleFailure({ err, isTeam }) { setTimeout(() => { let msg = ''; - const actionError = I18n.t('There_was_an_error_while_action', { action: isTeam ? I18n.t('creating_team') : I18n.t('creating_channel') }); + const actionError = I18n.t('There_was_an_error_while_action', { + action: isTeam ? I18n.t('creating_team') : I18n.t('creating_channel') + }); if (err?.data?.errorType && err?.data?.details?.channel_name) { - msg = errorArray.includes(err.data.errorType) ? I18n.t(err.data.errorType, { room_name: err.data.details.channel_name }) : actionError; + msg = errorArray.includes(err.data.errorType) + ? I18n.t(err.data.errorType, { room_name: err.data.details.channel_name }) + : actionError; } else { msg = err?.reason || (errorArray.includes(err?.data?.error) ? I18n.t(err.data.error) : err.data.error || actionError); } diff --git a/app/sagas/createDiscussion.js b/app/sagas/createDiscussion.js index 746ab1583..6009fe8b2 100644 --- a/app/sagas/createDiscussion.js +++ b/app/sagas/createDiscussion.js @@ -1,13 +1,11 @@ -import { - select, put, call, take, takeLatest -} from 'redux-saga/effects'; +import { call, put, select, take, takeLatest } from 'redux-saga/effects'; import { sanitizedRaw } from '@nozbe/watermelondb/RawRecord'; import { CREATE_DISCUSSION, LOGIN } from '../actions/actionsTypes'; -import { createDiscussionSuccess, createDiscussionFailure } from '../actions/createDiscussion'; +import { createDiscussionFailure, createDiscussionSuccess } from '../actions/createDiscussion'; import RocketChat from '../lib/rocketchat'; import database from '../lib/database'; -import { logEvent, events } from '../utils/log'; +import { events, logEvent } from '../utils/log'; const create = function* create(data) { return yield RocketChat.createDiscussion(data); @@ -28,8 +26,8 @@ const handleRequest = function* handleRequest({ data }) { try { const db = database.active; const subCollection = db.get('subscriptions'); - yield db.action(async() => { - await subCollection.create((s) => { + yield db.action(async () => { + await subCollection.create(s => { s._raw = sanitizedRaw({ id: sub.rid }, subCollection.schema); Object.assign(s, sub); }); diff --git a/app/sagas/deepLinking.js b/app/sagas/deepLinking.js index a406f56a9..efad7afae 100644 --- a/app/sagas/deepLinking.js +++ b/app/sagas/deepLinking.js @@ -1,25 +1,24 @@ -import { - takeLatest, take, select, put, all, delay -} from 'redux-saga/effects'; +import { all, delay, put, select, take, takeLatest } from 'redux-saga/effects'; import UserPreferences from '../lib/userPreferences'; import Navigation from '../lib/Navigation'; import * as types from '../actions/actionsTypes'; import { selectServerRequest, serverInitAdd } from '../actions/server'; -import { inviteLinksSetToken, inviteLinksRequest } from '../actions/inviteLinks'; +import { inviteLinksRequest, inviteLinksSetToken } from '../actions/inviteLinks'; import database from '../lib/database'; import RocketChat from '../lib/rocketchat'; import EventEmitter from '../utils/events'; -import { - appStart, ROOT_INSIDE, ROOT_NEW_SERVER, appInit -} from '../actions/app'; +import { ROOT_INSIDE, ROOT_OUTSIDE, appInit, appStart } from '../actions/app'; import { localAuthenticate } from '../utils/localAuthentication'; import { goRoom } from '../utils/goRoom'; import { loginRequest } from '../actions/login'; import log from '../utils/log'; const roomTypes = { - channel: 'c', direct: 'd', group: 'p', channels: 'l' + channel: 'c', + direct: 'd', + group: 'p', + channels: 'l' }; const handleInviteLink = function* handleInviteLink({ params, requireLogin = false }) { @@ -132,9 +131,9 @@ const handleOpen = function* handleOpen({ params }) { // If there's host, continue if (!/^(http|https)/.test(host)) { if (/^localhost(:\d+)?/.test(host)) { - host = `http://${ host }`; + host = `http://${host}`; } else { - host = `https://${ host }`; + host = `https://${host}`; } } else { // Notification should always come from https @@ -147,7 +146,7 @@ const handleOpen = function* handleOpen({ params }) { const [server, user] = yield all([ UserPreferences.getStringAsync(RocketChat.CURRENT_SERVER), - UserPreferences.getStringAsync(`${ RocketChat.TOKEN_KEY }-${ host }`) + UserPreferences.getStringAsync(`${RocketChat.TOKEN_KEY}-${host}`) ]); // TODO: needs better test @@ -181,7 +180,7 @@ const handleOpen = function* handleOpen({ params }) { yield fallbackNavigation(); return; } - yield put(appStart({ root: ROOT_NEW_SERVER })); + yield put(appStart({ root: ROOT_OUTSIDE })); yield put(serverInitAdd(server)); yield delay(1000); EventEmitter.emit('NewServer', { server: host }); diff --git a/app/sagas/encryption.js b/app/sagas/encryption.js index 842cd7a6a..cdf938bcd 100644 --- a/app/sagas/encryption.js +++ b/app/sagas/encryption.js @@ -1,16 +1,11 @@ import EJSON from 'ejson'; -import { takeLatest, select, put } from 'redux-saga/effects'; +import { put, select, takeLatest } from 'redux-saga/effects'; import { ENCRYPTION } from '../actions/actionsTypes'; import { encryptionSet } from '../actions/encryption'; import { Encryption } from '../lib/encryption'; import Navigation from '../lib/Navigation'; -import { - E2E_PUBLIC_KEY, - E2E_PRIVATE_KEY, - E2E_BANNER_TYPE, - E2E_RANDOM_PASSWORD_KEY -} from '../lib/encryption/constants'; +import { E2E_BANNER_TYPE, E2E_PRIVATE_KEY, E2E_PUBLIC_KEY, E2E_RANDOM_PASSWORD_KEY } from '../lib/encryption/constants'; import database from '../lib/database'; import RocketChat from '../lib/rocketchat'; import UserPreferences from '../lib/userPreferences'; @@ -44,7 +39,7 @@ const handleEncryptionInit = function* handleEncryptionInit() { } // Fetch stored private e2e key for this server - const storedPrivateKey = yield UserPreferences.getStringAsync(`${ server }-${ E2E_PRIVATE_KEY }`); + const storedPrivateKey = yield UserPreferences.getStringAsync(`${server}-${E2E_PRIVATE_KEY}`); // Fetch server stored e2e keys const keys = yield RocketChat.e2eFetchMyKeys(); @@ -57,19 +52,18 @@ const handleEncryptionInit = function* handleEncryptionInit() { } // If the user has a private key stored, but never entered the password - const storedRandomPassword = yield UserPreferences.getStringAsync(`${ server }-${ E2E_RANDOM_PASSWORD_KEY }`); + const storedRandomPassword = yield UserPreferences.getStringAsync(`${server}-${E2E_RANDOM_PASSWORD_KEY}`); if (storedRandomPassword) { yield put(encryptionSet(true, E2E_BANNER_TYPE.SAVE_PASSWORD)); } // Fetch stored public e2e key for this server - let storedPublicKey = yield UserPreferences.getStringAsync(`${ server }-${ E2E_PUBLIC_KEY }`); + let storedPublicKey = yield UserPreferences.getStringAsync(`${server}-${E2E_PUBLIC_KEY}`); // Prevent parse undefined if (storedPublicKey) { storedPublicKey = EJSON.parse(storedPublicKey); } - if (storedPublicKey && storedPrivateKey && !storedRandomPassword) { // Persist these keys yield Encryption.persistKeys(server, storedPublicKey, storedPrivateKey); diff --git a/app/sagas/index.js b/app/sagas/index.js index e499d74ec..ab9508dfb 100644 --- a/app/sagas/index.js +++ b/app/sagas/index.js @@ -1,4 +1,6 @@ import { all } from 'redux-saga/effects'; + +import inquiry from '../ee/omnichannel/sagas/inquiry'; import login from './login'; import rooms from './rooms'; import room from './room'; @@ -12,8 +14,6 @@ import inviteLinks from './inviteLinks'; import createDiscussion from './createDiscussion'; import encryption from './encryption'; -import inquiry from '../ee/omnichannel/sagas/inquiry'; - const root = function* root() { yield all([ init(), diff --git a/app/sagas/init.js b/app/sagas/init.js index f8cce6804..af42c4faa 100644 --- a/app/sagas/init.js +++ b/app/sagas/init.js @@ -9,7 +9,7 @@ import RocketChat from '../lib/rocketchat'; import log from '../utils/log'; import database from '../lib/database'; import { localAuthenticate } from '../utils/localAuthentication'; -import { appStart, ROOT_OUTSIDE, appReady } from '../actions/app'; +import { ROOT_OUTSIDE, appReady, appStart } from '../actions/app'; export const initLocalSettings = function* initLocalSettings() { const sortPreferences = yield RocketChat.getSortPreferences(); @@ -19,7 +19,7 @@ export const initLocalSettings = function* initLocalSettings() { const restore = function* restore() { try { const server = yield UserPreferences.getStringAsync(RocketChat.CURRENT_SERVER); - let userId = yield UserPreferences.getStringAsync(`${ RocketChat.TOKEN_KEY }-${ server }`); + let userId = yield UserPreferences.getStringAsync(`${RocketChat.TOKEN_KEY}-${server}`); if (!server) { yield put(appStart({ root: ROOT_OUTSIDE })); @@ -32,7 +32,7 @@ const restore = function* restore() { if (servers.length > 0) { for (let i = 0; i < servers.length; i += 1) { const newServer = servers[i].id; - userId = yield UserPreferences.getStringAsync(`${ RocketChat.TOKEN_KEY }-${ newServer }`); + userId = yield UserPreferences.getStringAsync(`${RocketChat.TOKEN_KEY}-${newServer}`); if (userId) { return yield put(selectServerRequest(newServer)); } diff --git a/app/sagas/inviteLinks.js b/app/sagas/inviteLinks.js index d3ecfbba9..2fd536614 100644 --- a/app/sagas/inviteLinks.js +++ b/app/sagas/inviteLinks.js @@ -1,10 +1,8 @@ -import { - put, takeLatest, delay, select -} from 'redux-saga/effects'; +import { delay, put, select, takeLatest } from 'redux-saga/effects'; import { Alert } from 'react-native'; import { INVITE_LINKS } from '../actions/actionsTypes'; -import { inviteLinksSuccess, inviteLinksFailure, inviteLinksSetInvite } from '../actions/inviteLinks'; +import { inviteLinksFailure, inviteLinksSetInvite, inviteLinksSuccess } from '../actions/inviteLinks'; import RocketChat from '../lib/rocketchat'; import log from '../utils/log'; import Navigation from '../lib/Navigation'; @@ -50,7 +48,9 @@ const handleCreateInviteLink = function* handleCreateInviteLink({ rid }) { try { const inviteLinks = yield select(state => state.inviteLinks); const result = yield RocketChat.findOrCreateInvite({ - rid, days: inviteLinks.days, maxUses: inviteLinks.maxUses + rid, + days: inviteLinks.days, + maxUses: inviteLinks.maxUses }); if (!result.success) { Alert.alert(I18n.t('Oops'), I18n.t('There_was_an_error_while_action', { action: I18n.t('creating_invite') })); diff --git a/app/sagas/login.js b/app/sagas/login.js index 9381e0459..c3f987054 100644 --- a/app/sagas/login.js +++ b/app/sagas/login.js @@ -1,19 +1,14 @@ -import { - put, call, takeLatest, select, take, fork, cancel, race, delay -} from 'redux-saga/effects'; +import { call, cancel, delay, fork, put, race, select, take, takeLatest } from 'redux-saga/effects'; import { sanitizedRaw } from '@nozbe/watermelondb/RawRecord'; import { Q } from '@nozbe/watermelondb'; + import * as types from '../actions/actionsTypes'; -import { - appStart, ROOT_SET_USERNAME, ROOT_INSIDE, ROOT_LOADING, ROOT_OUTSIDE -} from '../actions/app'; -import { serverFinishAdd, selectServerRequest } from '../actions/server'; -import { - loginFailure, loginSuccess, setUser, logout -} from '../actions/login'; +import { ROOT_INSIDE, ROOT_LOADING, ROOT_OUTSIDE, ROOT_SET_USERNAME, appStart } from '../actions/app'; +import { selectServerRequest, serverFinishAdd } from '../actions/server'; +import { loginFailure, loginSuccess, logout, setUser } from '../actions/login'; import { roomsRequest } from '../actions/rooms'; import RocketChat from '../lib/rocketchat'; -import log, { logEvent, events } from '../utils/log'; +import log, { events, logEvent } from '../utils/log'; import I18n, { setLanguage } from '../i18n'; import database from '../lib/database'; import EventEmitter from '../utils/events'; @@ -23,7 +18,6 @@ import { localAuthenticate } from '../utils/localAuthentication'; import { setActiveUsers } from '../actions/activeUsers'; import { encryptionInit, encryptionStop } from '../actions/encryption'; import UserPreferences from '../lib/userPreferences'; - import { inquiryRequest, inquiryReset } from '../ee/omnichannel/actions/inquiry'; import { isOmnichannelStatusAvailable } from '../ee/omnichannel/lib'; import Navigation from '../lib/Navigation'; @@ -53,14 +47,14 @@ const handleLoginRequest = function* handleLoginRequest({ credentials, logoutOnE // Saves username on server history const serversDB = database.servers; const serversHistoryCollection = serversDB.get('servers_history'); - yield serversDB.action(async() => { + yield serversDB.action(async () => { try { const serversHistory = await serversHistoryCollection.query(Q.where('url', server)).fetch(); if (serversHistory?.length) { const serverHistoryRecord = serversHistory[0]; // this is updating on every login just to save `updated_at` // keeping this server as the most recent on autocomplete order - await serverHistoryRecord.update((s) => { + await serverHistoryRecord.update(s => { s.username = result.username; }); } @@ -71,7 +65,7 @@ const handleLoginRequest = function* handleLoginRequest({ credentials, logoutOnE 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))) { + if (logoutOnError && e.data && e.data.message && /you've been logged out by the server/i.test(e.data.message)) { yield put(logout(true)); } else { logEvent(events.LOGIN_DEFAULT_LOGIN_F); @@ -124,8 +118,6 @@ const fetchRooms = function* fetchRooms() { const handleLoginSuccess = function* handleLoginSuccess({ user }) { try { - const adding = yield select(state => state.server.adding); - RocketChat.getUserPresence(user.id); const server = yield select(getServer); @@ -156,44 +148,30 @@ const handleLoginSuccess = function* handleLoginSuccess({ user }) { showMessageInMainThread: user.showMessageInMainThread, avatarETag: user.avatarETag }; - yield serversDB.action(async() => { + yield serversDB.action(async () => { try { const userRecord = await usersCollection.find(user.id); - await userRecord.update((record) => { + await userRecord.update(record => { record._raw = sanitizedRaw({ id: user.id, ...record._raw }, usersCollection.schema); Object.assign(record, u); }); } catch (e) { - await usersCollection.create((record) => { + await usersCollection.create(record => { record._raw = sanitizedRaw({ id: user.id }, usersCollection.schema); Object.assign(record, u); }); } }); - yield UserPreferences.setStringAsync(`${ RocketChat.TOKEN_KEY }-${ server }`, user.id); - yield UserPreferences.setStringAsync(`${ RocketChat.TOKEN_KEY }-${ user.id }`, user.token); + yield UserPreferences.setStringAsync(`${RocketChat.TOKEN_KEY}-${server}`, user.id); + yield UserPreferences.setStringAsync(`${RocketChat.TOKEN_KEY}-${user.id}`, user.token); yield put(setUser(user)); EventEmitter.emit('connected'); - let currentRoot; - if (adding) { - yield put(serverFinishAdd()); - yield put(appStart({ root: ROOT_INSIDE })); - } else { - currentRoot = yield select(state => state.app.root); - if (currentRoot !== ROOT_INSIDE) { - yield put(appStart({ root: ROOT_INSIDE })); - } - } - - // after a successful login, check if it's been invited via invite link - currentRoot = yield select(state => state.app.root); - if (currentRoot === ROOT_INSIDE) { - const inviteLinkToken = yield select(state => state.inviteLinks.token); - if (inviteLinkToken) { - yield put(inviteLinksRequest(inviteLinkToken)); - } + yield put(appStart({ root: ROOT_INSIDE })); + const inviteLinkToken = yield select(state => state.inviteLinks.token); + if (inviteLinkToken) { + yield put(inviteLinksRequest(inviteLinkToken)); } } catch (e) { log(e); @@ -226,7 +204,7 @@ const handleLogout = function* handleLogout({ forcedByServer }) { if (servers.length > 0) { for (let i = 0; i < servers.length; i += 1) { const newServer = servers[i].id; - const token = yield UserPreferences.getStringAsync(`${ RocketChat.TOKEN_KEY }-${ newServer }`); + const token = yield UserPreferences.getStringAsync(`${RocketChat.TOKEN_KEY}-${newServer}`); if (token) { yield put(selectServerRequest(newServer)); return; diff --git a/app/sagas/messages.js b/app/sagas/messages.js index 2674be42e..d4d110faa 100644 --- a/app/sagas/messages.js +++ b/app/sagas/messages.js @@ -1,4 +1,4 @@ -import { takeLatest, select } from 'redux-saga/effects'; +import { select, takeLatest } from 'redux-saga/effects'; import { Q } from '@nozbe/watermelondb'; import Navigation from '../lib/Navigation'; diff --git a/app/sagas/room.js b/app/sagas/room.js index 70a0bc4da..e3437a4ae 100644 --- a/app/sagas/room.js +++ b/app/sagas/room.js @@ -1,15 +1,13 @@ import { Alert } from 'react-native'; import prompt from 'react-native-prompt-android'; -import { - takeLatest, take, select, delay, race, put -} from 'redux-saga/effects'; +import { delay, put, race, select, take, takeLatest } from 'redux-saga/effects'; import EventEmitter from '../utils/events'; import Navigation from '../lib/Navigation'; import * as types from '../actions/actionsTypes'; import { removedRoom } from '../actions/room'; import RocketChat from '../lib/rocketchat'; -import log, { logEvent, events } from '../utils/log'; +import log, { events, logEvent } from '../utils/log'; import I18n from '../i18n'; import { showErrorAlert } from '../utils/info'; import { LISTENER } from '../containers/Toast'; @@ -41,13 +39,16 @@ const handleRemovedRoom = function* handleRemovedRoom(roomType, actionType) { } if (actionType === 'leave') { - EventEmitter.emit(LISTENER, { message: roomType === 'team' ? I18n.t('Left_The_Team_Successfully') : I18n.t('Left_The_Room_Successfully') }); + EventEmitter.emit(LISTENER, { + message: roomType === 'team' ? I18n.t('Left_The_Team_Successfully') : I18n.t('Left_The_Room_Successfully') + }); } if (actionType === 'delete') { - EventEmitter.emit(LISTENER, { message: roomType === 'team' ? I18n.t('Deleted_The_Team_Successfully') : I18n.t('Deleted_The_Room_Successfully') }); + EventEmitter.emit(LISTENER, { + message: roomType === 'team' ? I18n.t('Deleted_The_Team_Successfully') : I18n.t('Deleted_The_Room_Successfully') + }); } - // types.ROOM.REMOVE is triggered by `subscriptions-changed` with `removed` arg const { timeout } = yield race({ deleteFinished: take(types.ROOM.REMOVED), @@ -100,7 +101,12 @@ const handleDeleteRoom = function* handleDeleteRoom({ room, roomType, selected } } } catch (e) { logEvent(events.RI_EDIT_DELETE_F); - Alert.alert(I18n.t('Oops'), I18n.t('There_was_an_error_while_action', { action: roomType === 'team' ? I18n.t('deleting_team') : I18n.t('deleting_room') })); + Alert.alert( + I18n.t('Oops'), + I18n.t('There_was_an_error_while_action', { + action: roomType === 'team' ? I18n.t('deleting_team') : I18n.t('deleting_room') + }) + ); } }; @@ -108,7 +114,7 @@ const handleCloseRoom = function* handleCloseRoom({ rid }) { const isMasterDetail = yield select(state => state.app.isMasterDetail); const requestComment = yield select(state => state.settings.Livechat_request_comment_when_closing_conversation); - const closeRoom = async(comment = '') => { + const closeRoom = async (comment = '') => { try { await RocketChat.closeLivechat(rid, comment); if (isMasterDetail) { @@ -130,7 +136,7 @@ const handleCloseRoom = function* handleCloseRoom({ rid }) { I18n.t('Closing_chat'), I18n.t('Please_add_a_comment'), [ - { text: I18n.t('Cancel'), onPress: () => { }, style: 'cancel' }, + { text: I18n.t('Cancel'), onPress: () => {}, style: 'cancel' }, { text: I18n.t('Submit'), onPress: comment => closeRoom(comment) diff --git a/app/sagas/rooms.js b/app/sagas/rooms.js index 4b0047a14..56d6c6c42 100644 --- a/app/sagas/rooms.js +++ b/app/sagas/rooms.js @@ -1,11 +1,9 @@ -import { - put, select, race, take, fork, cancel, delay -} from 'redux-saga/effects'; +import { cancel, delay, fork, put, race, select, take } from 'redux-saga/effects'; import { Q } from '@nozbe/watermelondb'; import { sanitizedRaw } from '@nozbe/watermelondb/RawRecord'; import * as types from '../actions/actionsTypes'; -import { roomsSuccess, roomsFailure, roomsRefresh } from '../actions/rooms'; +import { roomsFailure, roomsRefresh, roomsSuccess } from '../actions/rooms'; import database from '../lib/database'; import log from '../utils/log'; import mergeSubscriptionsRooms from '../lib/methods/helpers/mergeSubscriptionsRooms'; @@ -20,8 +18,8 @@ const updateRooms = function* updateRooms({ server, newRoomsUpdatedAt }) { try { const serverRecord = yield serversCollection.find(server); - return serversDB.action(async() => { - await serverRecord.update((record) => { + return serversDB.action(async () => { + await serverRecord.update(record => { record.roomsUpdatedAt = newRoomsUpdatedAt; }); }); @@ -51,7 +49,7 @@ const handleRoomsRequest = function* handleRoomsRequest({ params }) { // Force fetch all subscriptions to update columns related to Teams feature // TODO: remove it a couple of releases - const teamsMigrationKey = `${ server }_TEAMS_MIGRATION`; + const teamsMigrationKey = `${server}_TEAMS_MIGRATION`; const teamsMigration = yield UserPreferences.getBoolAsync(teamsMigrationKey); if (!teamsMigration) { roomsUpdatedAt = null; @@ -87,11 +85,13 @@ const handleRoomsRequest = function* handleRoomsRequest({ params }) { const messagesToCreate = lastMessages.filter(i1 => !existingMessages.find(i2 => i1._id === i2.id)); const allRecords = [ - ...subsToCreate.map(subscription => subCollection.prepareCreate((s) => { - s._raw = sanitizedRaw({ id: subscription.rid }, subCollection.schema); - return Object.assign(s, subscription); - })), - ...subsToUpdate.map((subscription) => { + ...subsToCreate.map(subscription => + subCollection.prepareCreate(s => { + s._raw = sanitizedRaw({ id: subscription.rid }, subCollection.schema); + return Object.assign(s, subscription); + }) + ), + ...subsToUpdate.map(subscription => { const newSub = subscriptions.find(s => s._id === subscription._id); return subscription.prepareUpdate(() => { if (newSub.announcement) { @@ -103,20 +103,26 @@ const handleRoomsRequest = function* handleRoomsRequest({ params }) { }); }), ...subsToDelete.map(subscription => subscription.prepareDestroyPermanently()), - ...messagesToCreate.map(message => messagesCollection.prepareCreate(protectedFunction((m) => { - m._raw = sanitizedRaw({ id: message._id }, messagesCollection.schema); - m.subscription.id = message.rid; - return Object.assign(m, message); - }))), - ...messagesToUpdate.map((message) => { + ...messagesToCreate.map(message => + messagesCollection.prepareCreate( + protectedFunction(m => { + m._raw = sanitizedRaw({ id: message._id }, messagesCollection.schema); + m.subscription.id = message.rid; + return Object.assign(m, message); + }) + ) + ), + ...messagesToUpdate.map(message => { const newMessage = lastMessages.find(m => m._id === message.id); - return message.prepareUpdate(protectedFunction(() => { - Object.assign(message, newMessage); - })); + return message.prepareUpdate( + protectedFunction(() => { + Object.assign(message, newMessage); + }) + ); }) ]; - yield db.action(async() => { + yield db.action(async () => { await db.batch(...allRecords); }); } diff --git a/app/sagas/selectServer.js b/app/sagas/selectServer.js index 2abf47061..5aacd2b11 100644 --- a/app/sagas/selectServer.js +++ b/app/sagas/selectServer.js @@ -7,9 +7,7 @@ import coerce from 'semver/functions/coerce'; import Navigation from '../lib/Navigation'; import { SERVER } from '../actions/actionsTypes'; -import { - serverFailure, selectServerRequest, selectServerSuccess, selectServerFailure -} from '../actions/server'; +import { selectServerFailure, selectServerRequest, selectServerSuccess, serverFailure } from '../actions/server'; import { clearSettings } from '../actions/settings'; import { setUser } from '../actions/login'; import RocketChat from '../lib/rocketchat'; @@ -17,11 +15,10 @@ import database from '../lib/database'; import log, { logServerVersion } from '../utils/log'; import I18n from '../i18n'; import { BASIC_AUTH_KEY, setBasicAuth } from '../utils/fetch'; -import { appStart, ROOT_INSIDE, ROOT_OUTSIDE } from '../actions/app'; +import { ROOT_INSIDE, ROOT_OUTSIDE, appStart } from '../actions/app'; import UserPreferences from '../lib/userPreferences'; import { encryptionStop } from '../actions/encryption'; import SSLPinning from '../utils/sslPinning'; - import { inquiryReset } from '../ee/omnichannel/actions/inquiry'; const getServerInfo = function* getServerInfo({ server, raiseError = true }) { @@ -47,14 +44,14 @@ const getServerInfo = function* getServerInfo({ server, raiseError = true }) { const serversDB = database.servers; const serversCollection = serversDB.get('servers'); - yield serversDB.action(async() => { + yield serversDB.action(async () => { try { const serverRecord = await serversCollection.find(server); - await serverRecord.update((record) => { + await serverRecord.update(record => { record.version = serverVersion; }); } catch (e) { - await serversCollection.create((record) => { + await serversCollection.create(record => { record._raw = sanitizedRaw({ id: server }, serversCollection.schema); record.version = serverVersion; }); @@ -70,14 +67,14 @@ const getServerInfo = function* getServerInfo({ server, raiseError = true }) { const handleSelectServer = function* handleSelectServer({ server, version, fetchVersion }) { try { // SSL Pinning - Read certificate alias and set it to be used by network requests - const certificate = yield UserPreferences.getStringAsync(`${ RocketChat.CERTIFICATE_KEY }-${ server }`); + const certificate = yield UserPreferences.getStringAsync(`${RocketChat.CERTIFICATE_KEY}-${server}`); yield SSLPinning.setCertificate(certificate, server); yield put(inquiryReset()); yield put(encryptionStop()); const serversDB = database.servers; yield UserPreferences.setStringAsync(RocketChat.CURRENT_SERVER, server); - const userId = yield UserPreferences.getStringAsync(`${ RocketChat.TOKEN_KEY }-${ server }`); + const userId = yield UserPreferences.getStringAsync(`${RocketChat.TOKEN_KEY}-${server}`); const userCollections = serversDB.get('users'); let user = null; if (userId) { @@ -97,14 +94,14 @@ const handleSelectServer = function* handleSelectServer({ server, version, fetch }; } catch { // search credentials on shared credentials (Experimental/Official) - const token = yield UserPreferences.getStringAsync(`${ RocketChat.TOKEN_KEY }-${ userId }`); + const token = yield UserPreferences.getStringAsync(`${RocketChat.TOKEN_KEY}-${userId}`); if (token) { user = { token }; } } } - const basicAuth = yield UserPreferences.getStringAsync(`${ BASIC_AUTH_KEY }-${ server }`); + const basicAuth = yield UserPreferences.getStringAsync(`${BASIC_AUTH_KEY}-${server}`); setBasicAuth(basicAuth); // Check for running requests and abort them before connecting to the server @@ -148,7 +145,7 @@ const handleSelectServer = function* handleSelectServer({ server, version, fetch const handleServerRequest = function* handleServerRequest({ server, username, fromServerHistory }) { try { // SSL Pinning - Read certificate alias and set it to be used by network requests - const certificate = yield UserPreferences.getStringAsync(`${ RocketChat.CERTIFICATE_KEY }-${ server }`); + const certificate = yield UserPreferences.getStringAsync(`${RocketChat.CERTIFICATE_KEY}-${server}`); yield SSLPinning.setCertificate(certificate, server); const serverInfo = yield getServerInfo({ server }); @@ -164,11 +161,11 @@ const handleServerRequest = function* handleServerRequest({ server, username, fr Navigation.navigate('LoginView', { username }); } - yield serversDB.action(async() => { + yield serversDB.action(async () => { try { const serversHistory = await serversHistoryCollection.query(Q.where('url', server)).fetch(); if (!serversHistory?.length) { - await serversHistoryCollection.create((s) => { + await serversHistoryCollection.create(s => { s.url = server; }); } diff --git a/app/sagas/state.js b/app/sagas/state.js index 5b86af2ef..b9afeb4dd 100644 --- a/app/sagas/state.js +++ b/app/sagas/state.js @@ -1,4 +1,4 @@ -import { takeLatest, select } from 'redux-saga/effects'; +import { select, takeLatest } from 'redux-saga/effects'; import RocketChat from '../lib/rocketchat'; import { setBadgeCount } from '../notifications/push'; diff --git a/app/selectors/login.js b/app/selectors/login.js index b5278a3e3..03e3a2ecb 100644 --- a/app/selectors/login.js +++ b/app/selectors/login.js @@ -1,7 +1,7 @@ import { createSelector } from 'reselect'; import isEmpty from 'lodash/isEmpty'; -const getUser = (state) => { +const getUser = state => { if (!isEmpty(state.share?.user)) { return state.share.user; } @@ -11,10 +11,7 @@ const getLoginServices = state => state.login.services || {}; const getShowFormLoginSetting = state => state.settings.Accounts_ShowFormLogin || false; const getIframeEnabledSetting = state => state.settings.Accounts_iframe_enabled || false; -export const getUserSelector = createSelector( - [getUser], - user => user -); +export const getUserSelector = createSelector([getUser], user => user); export const getShowLoginButton = createSelector( [getLoginServices, getShowFormLoginSetting, getIframeEnabledSetting], diff --git a/app/share.js b/app/share.tsx similarity index 65% rename from app/share.js rename to app/share.tsx index dbf0bfbff..ceb85477d 100644 --- a/app/share.js +++ b/app/share.tsx @@ -1,32 +1,22 @@ import React, { useContext } from 'react'; import { Dimensions } from 'react-native'; -import PropTypes from 'prop-types'; import { NavigationContainer } from '@react-navigation/native'; import { AppearanceProvider } from 'react-native-appearance'; import { createStackNavigator } from '@react-navigation/stack'; import { Provider } from 'react-redux'; -import { - defaultTheme, - newThemeState, - subscribeTheme, - unsubscribeTheme -} from './utils/theme'; +import { defaultTheme, newThemeState, subscribeTheme, unsubscribeTheme } from './utils/theme'; import UserPreferences from './lib/userPreferences'; import Navigation from './lib/ShareNavigation'; import store from './lib/createStore'; import { supportSystemTheme } from './utils/deviceInfo'; -import { - defaultHeader, themedHeader, getActiveRouteName, navigationTheme -} from './utils/navigation'; +import { defaultHeader, getActiveRouteName, navigationTheme, themedHeader } from './utils/navigation'; import RocketChat, { THEME_PREFERENCES_KEY } from './lib/rocketchat'; import { ThemeContext } from './theme'; import { localAuthenticate } from './utils/localAuthentication'; import ScreenLockedView from './views/ScreenLockedView'; - // Outside Stack import WithoutServersView from './views/WithoutServersView'; - // Inside Stack import ShareListView from './views/ShareListView'; import ShareView from './views/ShareView'; @@ -36,6 +26,26 @@ import AuthLoadingView from './views/AuthLoadingView'; import { DimensionsContext } from './dimensions'; import debounce from './utils/debounce'; +interface IDimensions { + width: number; + height: number; + scale: number; + fontScale: number; +} + +interface IState { + theme: string; + themePreferences: { + currentTheme: 'automatic' | 'light'; + darkLevel: string; + }; + root: any; + width: number; + height: number; + scale: number; + fontScale: number; +} + const Inside = createStackNavigator(); const InsideStack = () => { const { theme } = useContext(ThemeContext); @@ -44,26 +54,13 @@ const InsideStack = () => { ...defaultHeader, ...themedHeader(theme) }; - screenOptions.headerStyle = { - ...screenOptions.headerStyle, - height: 57 - }; + screenOptions.headerStyle = { ...screenOptions.headerStyle, height: 57 }; return ( <Inside.Navigator screenOptions={screenOptions}> - <Inside.Screen - name='ShareListView' - component={ShareListView} - /> - <Inside.Screen - name='ShareView' - component={ShareView} - /> - <Inside.Screen - name='SelectServerView' - component={SelectServerView} - options={SelectServerView.navigationOptions} - /> + <Inside.Screen name='ShareListView' component={ShareListView} /> + <Inside.Screen name='ShareView' component={ShareView} /> + <Inside.Screen name='SelectServerView' component={SelectServerView} options={SelectServerView.navigationOptions} /> </Inside.Navigator> ); }; @@ -77,6 +74,7 @@ const OutsideStack = () => { <Outside.Screen name='WithoutServersView' component={WithoutServersView} + /* @ts-ignore*/ options={WithoutServersView.navigationOptions} /> </Outside.Navigator> @@ -85,41 +83,20 @@ const OutsideStack = () => { // App const Stack = createStackNavigator(); -export const App = ({ root }) => ( +export const App = ({ root }: any) => ( <Stack.Navigator screenOptions={{ headerShown: false, animationEnabled: false }}> <> - {!root ? ( - <Stack.Screen - name='AuthLoading' - component={AuthLoadingView} - /> - ) : null} - {root === 'outside' ? ( - <Stack.Screen - name='OutsideStack' - component={OutsideStack} - /> - ) : null} - {root === 'inside' ? ( - <Stack.Screen - name='InsideStack' - component={InsideStack} - /> - ) : null} + {!root ? <Stack.Screen name='AuthLoading' component={AuthLoadingView} /> : null} + {root === 'outside' ? <Stack.Screen name='OutsideStack' component={OutsideStack} /> : null} + {root === 'inside' ? <Stack.Screen name='InsideStack' component={InsideStack} /> : null} </> </Stack.Navigator> ); -App.propTypes = { - root: PropTypes.string -}; - -class Root extends React.Component { - constructor(props) { +class Root extends React.Component<{}, IState> { + constructor(props: any) { super(props); - const { - width, height, scale, fontScale - } = Dimensions.get('screen'); + const { width, height, scale, fontScale } = Dimensions.get('screen'); this.state = { theme: defaultTheme(), themePreferences: { @@ -140,8 +117,8 @@ class Root extends React.Component { unsubscribeTheme(); } - init = async() => { - UserPreferences.getMapAsync(THEME_PREFERENCES_KEY).then(this.setTheme); + init = async () => { + UserPreferences.getMapAsync(THEME_PREFERENCES_KEY).then((theme: any) => this.setTheme(theme)); const currentServer = await UserPreferences.getStringAsync(RocketChat.CURRENT_SERVER); @@ -157,41 +134,36 @@ class Root extends React.Component { const currentRouteName = getActiveRouteName(state); Navigation.routeNameRef.current = currentRouteName; setCurrentScreen(currentRouteName); - } + }; setTheme = (newTheme = {}) => { // change theme state - this.setState(prevState => newThemeState(prevState, newTheme), () => { - const { themePreferences } = this.state; - // subscribe to Appearance changes - subscribeTheme(themePreferences, this.setTheme); - }); - } + this.setState( + prevState => newThemeState(prevState, newTheme), + () => { + const { themePreferences } = this.state; + // subscribe to Appearance changes + subscribeTheme(themePreferences, this.setTheme); + } + ); + }; // Dimensions update fires twice - onDimensionsChange = debounce(({ - window: { - width, height, scale, fontScale - } - }) => { - this.setDimensions({ - width, height, scale, fontScale - }); - this.setMasterDetail(width); - }) + onDimensionsChange = debounce(({ window: { width, height, scale, fontScale } }: { window: IDimensions }) => { + this.setDimensions({ width, height, scale, fontScale }); + }); - setDimensions = ({ - width, height, scale, fontScale - }) => { + setDimensions = ({ width, height, scale, fontScale }: IDimensions) => { this.setState({ - width, height, scale, fontScale + width, + height, + scale, + fontScale }); - } + }; render() { - const { - theme, root, width, height, scale, fontScale - } = this.state; + const { theme, root, width, height, scale, fontScale } = this.state; const navTheme = navigationTheme(theme); return ( <AppearanceProvider> @@ -204,20 +176,18 @@ class Root extends React.Component { scale, fontScale, setDimensions: this.setDimensions - }} - > + }}> <NavigationContainer theme={navTheme} ref={Navigation.navigationRef} - onStateChange={(state) => { + onStateChange={state => { const previousRouteName = Navigation.routeNameRef.current; const currentRouteName = getActiveRouteName(state); if (previousRouteName !== currentRouteName) { setCurrentScreen(currentRouteName); } Navigation.routeNameRef.current = currentRouteName; - }} - > + }}> <App root={root} /> </NavigationContainer> <ScreenLockedView /> diff --git a/app/stacks/InsideStack.js b/app/stacks/InsideStack.js index 83fc22240..95e6020db 100644 --- a/app/stacks/InsideStack.js +++ b/app/stacks/InsideStack.js @@ -4,9 +4,7 @@ import { createStackNavigator } from '@react-navigation/stack'; import { createDrawerNavigator } from '@react-navigation/drawer'; import { ThemeContext } from '../theme'; -import { - defaultHeader, themedHeader, ModalAnimation, StackAnimation -} from '../utils/navigation'; +import { ModalAnimation, StackAnimation, defaultHeader, themedHeader } from '../utils/navigation'; import Sidebar from '../views/SidebarView'; // Chats Stack @@ -32,8 +30,9 @@ import ThreadMessagesView from '../views/ThreadMessagesView'; import TeamChannelsView from '../views/TeamChannelsView'; import MarkdownTableView from '../views/MarkdownTableView'; import ReadReceiptsView from '../views/ReadReceiptView'; +import CannedResponsesListView from '../views/CannedResponsesListView'; +import CannedResponseDetail from '../views/CannedResponseDetail'; import { themes } from '../constants/colors'; - // Profile Stack import ProfileView from '../views/ProfileView'; import UserPreferencesView from '../views/UserPreferencesView'; @@ -69,7 +68,6 @@ import JitsiMeetView from '../views/JitsiMeetView'; import StatusView from '../views/StatusView'; import ShareView from '../views/ShareView'; import CreateDiscussionView from '../views/CreateDiscussionView'; - import QueueListView from '../ee/omnichannel/views/QueueListView'; import AddChannelTeamView from '../views/AddChannelTeamView'; import AddExistingChannelView from '../views/AddExistingChannelView'; @@ -81,72 +79,28 @@ const ChatsStackNavigator = () => { const { theme } = React.useContext(ThemeContext); return ( <ChatsStack.Navigator screenOptions={{ ...defaultHeader, ...themedHeader(theme), ...StackAnimation }}> - <ChatsStack.Screen - name='RoomsListView' - component={RoomsListView} - /> - <ChatsStack.Screen - name='RoomView' - component={RoomView} - /> - <ChatsStack.Screen - name='RoomActionsView' - component={RoomActionsView} - options={RoomActionsView.navigationOptions} - /> - <ChatsStack.Screen - name='SelectListView' - component={SelectListView} - options={SelectListView.navigationOptions} - /> - <ChatsStack.Screen - name='RoomInfoView' - component={RoomInfoView} - options={RoomInfoView.navigationOptions} - /> - <ChatsStack.Screen - name='RoomInfoEditView' - component={RoomInfoEditView} - options={RoomInfoEditView.navigationOptions} - /> - <ChatsStack.Screen - name='RoomMembersView' - component={RoomMembersView} - options={RoomMembersView.navigationOptions} - /> + <ChatsStack.Screen name='RoomsListView' component={RoomsListView} /> + <ChatsStack.Screen name='RoomView' component={RoomView} /> + <ChatsStack.Screen name='RoomActionsView' component={RoomActionsView} options={RoomActionsView.navigationOptions} /> + <ChatsStack.Screen name='SelectListView' component={SelectListView} options={SelectListView.navigationOptions} /> + <ChatsStack.Screen name='RoomInfoView' component={RoomInfoView} options={RoomInfoView.navigationOptions} /> + <ChatsStack.Screen name='RoomInfoEditView' component={RoomInfoEditView} options={RoomInfoEditView.navigationOptions} /> + <ChatsStack.Screen name='RoomMembersView' component={RoomMembersView} options={RoomMembersView.navigationOptions} /> <ChatsStack.Screen name='SearchMessagesView' component={SearchMessagesView} options={SearchMessagesView.navigationOptions} /> - <ChatsStack.Screen - name='SelectedUsersView' - component={SelectedUsersView} - /> - <ChatsStack.Screen - name='InviteUsersView' - component={InviteUsersView} - options={InviteUsersView.navigationOptions} - /> + <ChatsStack.Screen name='SelectedUsersView' component={SelectedUsersView} /> + <ChatsStack.Screen name='InviteUsersView' component={InviteUsersView} options={InviteUsersView.navigationOptions} /> <ChatsStack.Screen name='InviteUsersEditView' component={InviteUsersEditView} options={InviteUsersEditView.navigationOptions} /> - <ChatsStack.Screen - name='MessagesView' - component={MessagesView} - /> - <ChatsStack.Screen - name='AutoTranslateView' - component={AutoTranslateView} - options={AutoTranslateView.navigationOptions} - /> - <ChatsStack.Screen - name='DirectoryView' - component={DirectoryView} - options={DirectoryView.navigationOptions} - /> + <ChatsStack.Screen name='MessagesView' component={MessagesView} /> + <ChatsStack.Screen name='AutoTranslateView' component={AutoTranslateView} options={AutoTranslateView.navigationOptions} /> + <ChatsStack.Screen name='DirectoryView' component={DirectoryView} options={DirectoryView.navigationOptions} /> <ChatsStack.Screen name='NotificationPrefView' component={NotificationPrefView} @@ -162,31 +116,15 @@ const ChatsStackNavigator = () => { component={ForwardLivechatView} options={ForwardLivechatView.navigationOptions} /> - <ChatsStack.Screen - name='LivechatEditView' - component={LivechatEditView} - options={LivechatEditView.navigationOptions} - /> - <ChatsStack.Screen - name='PickerView' - component={PickerView} - options={PickerView.navigationOptions} - /> + <ChatsStack.Screen name='LivechatEditView' component={LivechatEditView} options={LivechatEditView.navigationOptions} /> + <ChatsStack.Screen name='PickerView' component={PickerView} options={PickerView.navigationOptions} /> <ChatsStack.Screen name='ThreadMessagesView' component={ThreadMessagesView} options={ThreadMessagesView.navigationOptions} /> - <ChatsStack.Screen - name='TeamChannelsView' - component={TeamChannelsView} - options={TeamChannelsView.navigationOptions} - /> - <ChatsStack.Screen - name='CreateChannelView' - component={CreateChannelView} - options={CreateChannelView.navigationOptions} - /> + <ChatsStack.Screen name='TeamChannelsView' component={TeamChannelsView} options={TeamChannelsView.navigationOptions} /> + <ChatsStack.Screen name='CreateChannelView' component={CreateChannelView} options={CreateChannelView.navigationOptions} /> <ChatsStack.Screen name='AddChannelTeamView' component={AddChannelTeamView} @@ -197,20 +135,18 @@ const ChatsStackNavigator = () => { component={AddExistingChannelView} options={AddExistingChannelView.navigationOptions} /> + <ChatsStack.Screen name='MarkdownTableView' component={MarkdownTableView} options={MarkdownTableView.navigationOptions} /> + <ChatsStack.Screen name='ReadReceiptsView' component={ReadReceiptsView} options={ReadReceiptsView.navigationOptions} /> + <ChatsStack.Screen name='QueueListView' component={QueueListView} options={QueueListView.navigationOptions} /> <ChatsStack.Screen - name='MarkdownTableView' - component={MarkdownTableView} - options={MarkdownTableView.navigationOptions} + name='CannedResponsesListView' + component={CannedResponsesListView} + options={CannedResponsesListView.navigationOptions} /> <ChatsStack.Screen - name='ReadReceiptsView' - component={ReadReceiptsView} - options={ReadReceiptsView.navigationOptions} - /> - <ChatsStack.Screen - name='QueueListView' - component={QueueListView} - options={QueueListView.navigationOptions} + name='CannedResponseDetail' + component={CannedResponseDetail} + options={CannedResponseDetail.navigationOptions} /> </ChatsStack.Navigator> ); @@ -222,11 +158,7 @@ const ProfileStackNavigator = () => { const { theme } = React.useContext(ThemeContext); return ( <ProfileStack.Navigator screenOptions={{ ...defaultHeader, ...themedHeader(theme), ...StackAnimation }}> - <ProfileStack.Screen - name='ProfileView' - component={ProfileView} - options={ProfileView.navigationOptions} - /> + <ProfileStack.Screen name='ProfileView' component={ProfileView} options={ProfileView.navigationOptions} /> <ProfileStack.Screen name='UserPreferencesView' component={UserPreferencesView} @@ -237,11 +169,7 @@ const ProfileStackNavigator = () => { component={UserNotificationPrefView} options={UserNotificationPrefView.navigationOptions} /> - <ProfileStack.Screen - name='PickerView' - component={PickerView} - options={PickerView.navigationOptions} - /> + <ProfileStack.Screen name='PickerView' component={PickerView} options={PickerView.navigationOptions} /> </ProfileStack.Navigator> ); }; @@ -253,11 +181,7 @@ const SettingsStackNavigator = () => { return ( <SettingsStack.Navigator screenOptions={{ ...defaultHeader, ...themedHeader(theme), ...StackAnimation }}> - <SettingsStack.Screen - name='SettingsView' - component={SettingsView} - options={SettingsView.navigationOptions} - /> + <SettingsStack.Screen name='SettingsView' component={SettingsView} options={SettingsView.navigationOptions} /> <SettingsStack.Screen name='SecurityPrivacyView' component={SecurityPrivacyView} @@ -268,16 +192,8 @@ const SettingsStackNavigator = () => { component={E2EEncryptionSecurityView} options={E2EEncryptionSecurityView.navigationOptions} /> - <SettingsStack.Screen - name='LanguageView' - component={LanguageView} - options={LanguageView.navigationOptions} - /> - <SettingsStack.Screen - name='ThemeView' - component={ThemeView} - options={ThemeView.navigationOptions} - /> + <SettingsStack.Screen name='LanguageView' component={LanguageView} options={LanguageView.navigationOptions} /> + <SettingsStack.Screen name='ThemeView' component={ThemeView} options={ThemeView.navigationOptions} /> <SettingsStack.Screen name='DefaultBrowserView' component={DefaultBrowserView} @@ -299,11 +215,7 @@ const AdminPanelStackNavigator = () => { return ( <AdminPanelStack.Navigator screenOptions={{ ...defaultHeader, ...themedHeader(theme), ...StackAnimation }}> - <AdminPanelStack.Screen - name='AdminPanelView' - component={AdminPanelView} - options={AdminPanelView.navigationOptions} - /> + <AdminPanelStack.Screen name='AdminPanelView' component={AdminPanelView} options={AdminPanelView.navigationOptions} /> </AdminPanelStack.Navigator> ); }; @@ -319,8 +231,7 @@ const DrawerNavigator = () => { drawerPosition={I18nManager.isRTL ? 'right' : 'left'} screenOptions={{ swipeEnabled: false }} drawerType='back' - overlayColor={`rgba(0,0,0,${ themes[theme].backdropOpacity })`} - > + overlayColor={`rgba(0,0,0,${themes[theme].backdropOpacity})`}> <Drawer.Screen name='ChatsStackNavigator' component={ChatsStackNavigator} /> <Drawer.Screen name='ProfileStackNavigator' component={ProfileStackNavigator} /> <Drawer.Screen name='SettingsStackNavigator' component={SettingsStackNavigator} /> @@ -336,24 +247,14 @@ const NewMessageStackNavigator = () => { return ( <NewMessageStack.Navigator screenOptions={{ ...defaultHeader, ...themedHeader(theme), ...StackAnimation }}> - <NewMessageStack.Screen - name='NewMessageView' - component={NewMessageView} - options={NewMessageView.navigationOptions} - /> - <NewMessageStack.Screen - name='SelectedUsersViewCreateChannel' - component={SelectedUsersView} - /> + <NewMessageStack.Screen name='NewMessageView' component={NewMessageView} options={NewMessageView.navigationOptions} /> + <NewMessageStack.Screen name='SelectedUsersViewCreateChannel' component={SelectedUsersView} /> <NewMessageStack.Screen name='CreateChannelView' component={CreateChannelView} options={CreateChannelView.navigationOptions} /> - <NewMessageStack.Screen - name='CreateDiscussionView' - component={CreateDiscussionView} - /> + <NewMessageStack.Screen name='CreateDiscussionView' component={CreateDiscussionView} /> </NewMessageStack.Navigator> ); }; @@ -402,16 +303,8 @@ const InsideStackNavigator = () => { return ( <InsideStack.Navigator mode='modal' screenOptions={{ ...defaultHeader, ...themedHeader(theme), ...ModalAnimation }}> - <InsideStack.Screen - name='DrawerNavigator' - component={DrawerNavigator} - options={{ headerShown: false }} - /> - <InsideStack.Screen - name='NewMessageStackNavigator' - component={NewMessageStackNavigator} - options={{ headerShown: false }} - /> + <InsideStack.Screen name='DrawerNavigator' component={DrawerNavigator} options={{ headerShown: false }} /> + <InsideStack.Screen name='NewMessageStackNavigator' component={NewMessageStackNavigator} options={{ headerShown: false }} /> <InsideStack.Screen name='E2ESaveYourPasswordStackNavigator' component={E2ESaveYourPasswordStackNavigator} @@ -422,28 +315,11 @@ const InsideStackNavigator = () => { component={E2EEnterYourPasswordStackNavigator} options={{ headerShown: false }} /> - <InsideStack.Screen - name='AttachmentView' - component={AttachmentView} - /> - <InsideStack.Screen - name='StatusView' - component={StatusView} - /> - <InsideStack.Screen - name='ShareView' - component={ShareView} - /> - <InsideStack.Screen - name='ModalBlockView' - component={ModalBlockView} - options={ModalBlockView.navigationOptions} - /> - <InsideStack.Screen - name='JitsiMeetView' - component={JitsiMeetView} - options={{ headerShown: false }} - /> + <InsideStack.Screen name='AttachmentView' component={AttachmentView} /> + <InsideStack.Screen name='StatusView' component={StatusView} /> + <InsideStack.Screen name='ShareView' component={ShareView} /> + <InsideStack.Screen name='ModalBlockView' component={ModalBlockView} options={ModalBlockView.navigationOptions} /> + <InsideStack.Screen name='JitsiMeetView' component={JitsiMeetView} options={{ headerShown: false }} /> </InsideStack.Navigator> ); }; diff --git a/app/stacks/MasterDetailStack/ModalContainer.js b/app/stacks/MasterDetailStack/ModalContainer.js index 79b572010..7be11f8c7 100644 --- a/app/stacks/MasterDetailStack/ModalContainer.js +++ b/app/stacks/MasterDetailStack/ModalContainer.js @@ -1,5 +1,5 @@ import React from 'react'; -import { TouchableWithoutFeedback, View, StyleSheet } from 'react-native'; +import { StyleSheet, TouchableWithoutFeedback, View } from 'react-native'; import PropTypes from 'prop-types'; import sharedStyles from '../../views/Styles'; @@ -17,13 +17,11 @@ const styles = StyleSheet.create({ }); export const ModalContainer = ({ navigation, children, theme }) => ( - <View style={[styles.root, { backgroundColor: `${ themes[theme].backdropColor }70` }]}> + <View style={[styles.root, { backgroundColor: `${themes[theme].backdropColor}70` }]}> <TouchableWithoutFeedback onPress={() => navigation.pop()}> <View style={styles.backdrop} /> </TouchableWithoutFeedback> - <View style={sharedStyles.modalFormSheet}> - {children} - </View> + <View style={sharedStyles.modalFormSheet}>{children}</View> </View> ); diff --git a/app/stacks/MasterDetailStack/index.js b/app/stacks/MasterDetailStack/index.js index ea7c038f2..e1ba84957 100644 --- a/app/stacks/MasterDetailStack/index.js +++ b/app/stacks/MasterDetailStack/index.js @@ -5,10 +5,7 @@ import { createStackNavigator } from '@react-navigation/stack'; import { createDrawerNavigator } from '@react-navigation/drawer'; import { ThemeContext } from '../../theme'; -import { - defaultHeader, themedHeader, StackAnimation, FadeFromCenterModal -} from '../../utils/navigation'; -import { ModalContainer } from './ModalContainer'; +import { FadeFromCenterModal, StackAnimation, defaultHeader, themedHeader } from '../../utils/navigation'; // Chats Stack import RoomView from '../../views/RoomView'; @@ -27,6 +24,8 @@ import DirectoryView from '../../views/DirectoryView'; import NotificationPrefView from '../../views/NotificationPreferencesView'; import VisitorNavigationView from '../../views/VisitorNavigationView'; import ForwardLivechatView from '../../views/ForwardLivechatView'; +import CannedResponsesListView from '../../views/CannedResponsesListView'; +import CannedResponseDetail from '../../views/CannedResponseDetail'; import LivechatEditView from '../../views/LivechatEditView'; import PickerView from '../../views/PickerView'; import ThreadMessagesView from '../../views/ThreadMessagesView'; @@ -56,14 +55,13 @@ import CreateDiscussionView from '../../views/CreateDiscussionView'; import E2ESaveYourPasswordView from '../../views/E2ESaveYourPasswordView'; import E2EHowItWorksView from '../../views/E2EHowItWorksView'; import E2EEnterYourPasswordView from '../../views/E2EEnterYourPasswordView'; - -import { setKeyCommands, deleteKeyCommands } from '../../commands'; +import { deleteKeyCommands, setKeyCommands } from '../../commands'; import ShareView from '../../views/ShareView'; - import QueueListView from '../../ee/omnichannel/views/QueueListView'; import AddChannelTeamView from '../../views/AddChannelTeamView'; import AddExistingChannelView from '../../views/AddExistingChannelView'; import SelectListView from '../../views/SelectListView'; +import { ModalContainer } from './ModalContainer'; // ChatsStackNavigator const ChatsStack = createStackNavigator(); @@ -84,11 +82,7 @@ const ChatsStackNavigator = React.memo(() => { return ( <ChatsStack.Navigator screenOptions={{ ...defaultHeader, ...themedHeader(theme), ...StackAnimation }}> - <ChatsStack.Screen - name='RoomView' - component={RoomView} - options={{ headerShown: false }} - /> + <ChatsStack.Screen name='RoomView' component={RoomView} options={{ headerShown: false }} /> </ChatsStack.Navigator> ); }); @@ -98,8 +92,7 @@ const Drawer = createDrawerNavigator(); const DrawerNavigator = React.memo(() => ( <Drawer.Navigator drawerContent={({ navigation, state }) => <RoomsListView navigation={navigation} state={state} />} - drawerType='permanent' - > + drawerType='permanent'> <Drawer.Screen name='ChatsStackNavigator' component={ChatsStackNavigator} /> </Drawer.Navigator> )); @@ -115,40 +108,17 @@ const ModalStackNavigator = React.memo(({ navigation }) => { component={RoomActionsView} options={props => RoomActionsView.navigationOptions({ ...props, isMasterDetail: true })} /> - <ModalStack.Screen - name='RoomInfoView' - component={RoomInfoView} - options={RoomInfoView.navigationOptions} - /> - <ModalStack.Screen - name='SelectListView' - component={SelectListView} - options={SelectListView.navigationOptions} - /> - <ModalStack.Screen - name='RoomInfoEditView' - component={RoomInfoEditView} - options={RoomInfoEditView.navigationOptions} - /> - <ModalStack.Screen - name='RoomMembersView' - component={RoomMembersView} - options={RoomMembersView.navigationOptions} - /> + <ModalStack.Screen name='RoomInfoView' component={RoomInfoView} options={RoomInfoView.navigationOptions} /> + <ModalStack.Screen name='SelectListView' component={SelectListView} options={SelectListView.navigationOptions} /> + <ModalStack.Screen name='RoomInfoEditView' component={RoomInfoEditView} options={RoomInfoEditView.navigationOptions} /> + <ModalStack.Screen name='RoomMembersView' component={RoomMembersView} options={RoomMembersView.navigationOptions} /> <ModalStack.Screen name='SearchMessagesView' component={SearchMessagesView} options={SearchMessagesView.navigationOptions} /> - <ModalStack.Screen - name='SelectedUsersView' - component={SelectedUsersView} - /> - <ModalStack.Screen - name='InviteUsersView' - component={InviteUsersView} - options={InviteUsersView.navigationOptions} - /> + <ModalStack.Screen name='SelectedUsersView' component={SelectedUsersView} /> + <ModalStack.Screen name='InviteUsersView' component={InviteUsersView} options={InviteUsersView.navigationOptions} /> <ModalStack.Screen name='AddChannelTeamView' component={AddChannelTeamView} @@ -164,15 +134,8 @@ const ModalStackNavigator = React.memo(({ navigation }) => { component={InviteUsersEditView} options={InviteUsersEditView.navigationOptions} /> - <ModalStack.Screen - name='MessagesView' - component={MessagesView} - /> - <ModalStack.Screen - name='AutoTranslateView' - component={AutoTranslateView} - options={AutoTranslateView.navigationOptions} - /> + <ModalStack.Screen name='MessagesView' component={MessagesView} /> + <ModalStack.Screen name='AutoTranslateView' component={AutoTranslateView} options={AutoTranslateView.navigationOptions} /> <ModalStack.Screen name='DirectoryView' component={DirectoryView} @@ -199,29 +162,20 @@ const ModalStackNavigator = React.memo(({ navigation }) => { options={ForwardLivechatView.navigationOptions} /> <ModalStack.Screen - name='LivechatEditView' - component={LivechatEditView} - options={LivechatEditView.navigationOptions} + name='CannedResponsesListView' + component={CannedResponsesListView} + options={CannedResponsesListView.navigationOptions} /> <ModalStack.Screen - name='PickerView' - component={PickerView} - options={PickerView.navigationOptions} - /> - <ModalStack.Screen - name='ThreadMessagesView' - component={ThreadMessagesView} - /> - <ModalStack.Screen - name='TeamChannelsView' - component={TeamChannelsView} - options={TeamChannelsView.navigationOptions} - /> - <ModalStack.Screen - name='MarkdownTableView' - component={MarkdownTableView} - options={MarkdownTableView.navigationOptions} + name='CannedResponseDetail' + component={CannedResponseDetail} + options={CannedResponseDetail.navigationOptions} /> + <ModalStack.Screen name='LivechatEditView' component={LivechatEditView} options={LivechatEditView.navigationOptions} /> + <ModalStack.Screen name='PickerView' component={PickerView} options={PickerView.navigationOptions} /> + <ModalStack.Screen name='ThreadMessagesView' component={ThreadMessagesView} /> + <ModalStack.Screen name='TeamChannelsView' component={TeamChannelsView} options={TeamChannelsView.navigationOptions} /> + <ModalStack.Screen name='MarkdownTableView' component={MarkdownTableView} options={MarkdownTableView.navigationOptions} /> <ModalStack.Screen name='ReadReceiptsView' component={ReadReceiptsView} @@ -232,16 +186,8 @@ const ModalStackNavigator = React.memo(({ navigation }) => { component={SettingsView} options={props => SettingsView.navigationOptions({ ...props, isMasterDetail: true })} /> - <ModalStack.Screen - name='LanguageView' - component={LanguageView} - options={LanguageView.navigationOptions} - /> - <ModalStack.Screen - name='ThemeView' - component={ThemeView} - options={ThemeView.navigationOptions} - /> + <ModalStack.Screen name='LanguageView' component={LanguageView} options={LanguageView.navigationOptions} /> + <ModalStack.Screen name='ThemeView' component={ThemeView} options={ThemeView.navigationOptions} /> <ModalStack.Screen name='DefaultBrowserView' component={DefaultBrowserView} @@ -252,10 +198,7 @@ const ModalStackNavigator = React.memo(({ navigation }) => { component={ScreenLockConfigView} options={ScreenLockConfigView.navigationOptions} /> - <ModalStack.Screen - name='StatusView' - component={StatusView} - /> + <ModalStack.Screen name='StatusView' component={StatusView} /> <ModalStack.Screen name='ProfileView' component={ProfileView} @@ -266,34 +209,16 @@ const ModalStackNavigator = React.memo(({ navigation }) => { component={AdminPanelView} options={props => AdminPanelView.navigationOptions({ ...props, isMasterDetail: true })} /> - <ModalStack.Screen - name='NewMessageView' - component={NewMessageView} - options={NewMessageView.navigationOptions} - /> - <ModalStack.Screen - name='SelectedUsersViewCreateChannel' - component={SelectedUsersView} - /> - <ModalStack.Screen - name='CreateChannelView' - component={CreateChannelView} - options={CreateChannelView.navigationOptions} - /> - <ModalStack.Screen - name='CreateDiscussionView' - component={CreateDiscussionView} - /> + <ModalStack.Screen name='NewMessageView' component={NewMessageView} options={NewMessageView.navigationOptions} /> + <ModalStack.Screen name='SelectedUsersViewCreateChannel' component={SelectedUsersView} /> + <ModalStack.Screen name='CreateChannelView' component={CreateChannelView} options={CreateChannelView.navigationOptions} /> + <ModalStack.Screen name='CreateDiscussionView' component={CreateDiscussionView} /> <ModalStack.Screen name='E2ESaveYourPasswordView' component={E2ESaveYourPasswordView} options={E2ESaveYourPasswordView.navigationOptions} /> - <ModalStack.Screen - name='E2EHowItWorksView' - component={E2EHowItWorksView} - options={E2EHowItWorksView.navigationOptions} - /> + <ModalStack.Screen name='E2EHowItWorksView' component={E2EHowItWorksView} options={E2EHowItWorksView.navigationOptions} /> <ModalStack.Screen name='E2EEnterYourPasswordView' component={E2EEnterYourPasswordView} @@ -334,34 +259,12 @@ const InsideStackNavigator = React.memo(() => { const { theme } = React.useContext(ThemeContext); return ( <InsideStack.Navigator mode='modal' screenOptions={{ ...defaultHeader, ...themedHeader(theme), ...FadeFromCenterModal }}> - <InsideStack.Screen - name='DrawerNavigator' - component={DrawerNavigator} - options={{ headerShown: false }} - /> - <InsideStack.Screen - name='ModalStackNavigator' - component={ModalStackNavigator} - options={{ headerShown: false }} - /> - <InsideStack.Screen - name='AttachmentView' - component={AttachmentView} - /> - <InsideStack.Screen - name='ModalBlockView' - component={ModalBlockView} - options={ModalBlockView.navigationOptions} - /> - <InsideStack.Screen - name='JitsiMeetView' - component={JitsiMeetView} - options={{ headerShown: false }} - /> - <InsideStack.Screen - name='ShareView' - component={ShareView} - /> + <InsideStack.Screen name='DrawerNavigator' component={DrawerNavigator} options={{ headerShown: false }} /> + <InsideStack.Screen name='ModalStackNavigator' component={ModalStackNavigator} options={{ headerShown: false }} /> + <InsideStack.Screen name='AttachmentView' component={AttachmentView} /> + <InsideStack.Screen name='ModalBlockView' component={ModalBlockView} options={ModalBlockView.navigationOptions} /> + <InsideStack.Screen name='JitsiMeetView' component={JitsiMeetView} options={{ headerShown: false }} /> + <InsideStack.Screen name='ShareView' component={ShareView} /> </InsideStack.Navigator> ); }); diff --git a/app/stacks/OutsideStack.js b/app/stacks/OutsideStack.js index f66e734fe..f23e65c3d 100644 --- a/app/stacks/OutsideStack.js +++ b/app/stacks/OutsideStack.js @@ -1,15 +1,11 @@ import React from 'react'; import { createStackNavigator } from '@react-navigation/stack'; import { connect } from 'react-redux'; -import PropTypes from 'prop-types'; import { ThemeContext } from '../theme'; -import { - defaultHeader, themedHeader, StackAnimation, ModalAnimation -} from '../utils/navigation'; +import { ModalAnimation, StackAnimation, defaultHeader, themedHeader } from '../utils/navigation'; // Outside Stack -import OnboardingView from '../views/OnboardingView'; import NewServerView from '../views/NewServerView'; import WorkspaceView from '../views/WorkspaceView'; import LoginView from '../views/LoginView'; @@ -17,52 +13,20 @@ import ForgotPasswordView from '../views/ForgotPasswordView'; import RegisterView from '../views/RegisterView'; import LegalView from '../views/LegalView'; import AuthenticationWebView from '../views/AuthenticationWebView'; -import { ROOT_OUTSIDE } from '../actions/app'; // Outside const Outside = createStackNavigator(); -const _OutsideStack = ({ root }) => { +const _OutsideStack = () => { const { theme } = React.useContext(ThemeContext); return ( <Outside.Navigator screenOptions={{ ...defaultHeader, ...themedHeader(theme), ...StackAnimation }}> - {root === ROOT_OUTSIDE ? ( - <Outside.Screen - name='OnboardingView' - component={OnboardingView} - options={OnboardingView.navigationOptions} - /> - ) : null} - <Outside.Screen - name='NewServerView' - component={NewServerView} - options={NewServerView.navigationOptions} - /> - <Outside.Screen - name='WorkspaceView' - component={WorkspaceView} - options={WorkspaceView.navigationOptions} - /> - <Outside.Screen - name='LoginView' - component={LoginView} - options={LoginView.navigationOptions} - /> - <Outside.Screen - name='ForgotPasswordView' - component={ForgotPasswordView} - options={ForgotPasswordView.navigationOptions} - /> - <Outside.Screen - name='RegisterView' - component={RegisterView} - options={RegisterView.navigationOptions} - /> - <Outside.Screen - name='LegalView' - component={LegalView} - options={LegalView.navigationOptions} - /> + <Outside.Screen name='NewServerView' component={NewServerView} options={NewServerView.navigationOptions} /> + <Outside.Screen name='WorkspaceView' component={WorkspaceView} options={WorkspaceView.navigationOptions} /> + <Outside.Screen name='LoginView' component={LoginView} options={LoginView.navigationOptions} /> + <Outside.Screen name='ForgotPasswordView' component={ForgotPasswordView} options={ForgotPasswordView.navigationOptions} /> + <Outside.Screen name='RegisterView' component={RegisterView} options={RegisterView.navigationOptions} /> + <Outside.Screen name='LegalView' component={LegalView} options={LegalView.navigationOptions} /> </Outside.Navigator> ); }; @@ -71,10 +35,6 @@ const mapStateToProps = state => ({ root: state.app.root }); -_OutsideStack.propTypes = { - root: PropTypes.string -}; - const OutsideStack = connect(mapStateToProps)(_OutsideStack); // OutsideStackModal @@ -84,11 +44,7 @@ const OutsideStackModal = () => { return ( <OutsideModal.Navigator mode='modal' screenOptions={{ ...defaultHeader, ...themedHeader(theme), ...ModalAnimation }}> - <OutsideModal.Screen - name='OutsideStack' - component={OutsideStack} - options={{ headerShown: false }} - /> + <OutsideModal.Screen name='OutsideStack' component={OutsideStack} options={{ headerShown: false }} /> <OutsideModal.Screen name='AuthenticationWebView' component={AuthenticationWebView} diff --git a/app/theme.js b/app/theme.js deleted file mode 100644 index c34f56de0..000000000 --- a/app/theme.js +++ /dev/null @@ -1,16 +0,0 @@ -import React from 'react'; -import hoistNonReactStatics from 'hoist-non-react-statics'; - -export const ThemeContext = React.createContext({ theme: 'light' }); - -export function withTheme(Component) { - const ThemedComponent = props => ( - <ThemeContext.Consumer> - {contexts => <Component {...props} {...contexts} />} - </ThemeContext.Consumer> - ); - hoistNonReactStatics(ThemedComponent, Component); - return ThemedComponent; -} - -export const useTheme = () => React.useContext(ThemeContext); diff --git a/app/theme.tsx b/app/theme.tsx new file mode 100644 index 000000000..450bc2801 --- /dev/null +++ b/app/theme.tsx @@ -0,0 +1,23 @@ +import React from 'react'; +import hoistNonReactStatics from 'hoist-non-react-statics'; + +interface IThemeContextProps { + theme: string; + themePreferences: { + currentTheme: 'automatic' | 'light'; + darkLevel: string; + }; + setTheme: (newTheme?: {}) => void; +} + +export const ThemeContext = React.createContext<Partial<IThemeContextProps>>({ theme: 'light' }); + +export function withTheme(Component: any) { + const ThemedComponent = (props: any) => ( + <ThemeContext.Consumer>{contexts => <Component {...props} {...contexts} />}</ThemeContext.Consumer> + ); + hoistNonReactStatics(ThemedComponent, Component); + return ThemedComponent; +} + +export const useTheme = () => React.useContext(ThemeContext); diff --git a/app/utils/avatar.js b/app/utils/avatar.js index 664c3ea45..4cc15cdad 100644 --- a/app/utils/avatar.js +++ b/app/utils/avatar.js @@ -1,26 +1,35 @@ import { compareServerVersion, methods } from '../lib/utils'; -const formatUrl = (url, size, query) => `${ url }?format=png&size=${ size }${ query }`; +const formatUrl = (url, size, query) => `${url}?format=png&size=${size}${query}`; export const avatarURL = ({ - type, text, size = 25, user = {}, avatar, server, avatarETag, rid, blockUnauthenticatedAccess, serverVersion + type, + text, + size = 25, + user = {}, + avatar, + server, + avatarETag, + rid, + blockUnauthenticatedAccess, + serverVersion }) => { let room; if (type === 'd') { room = text; - } else if (rid && !(compareServerVersion(serverVersion, '3.6.0', methods.lowerThan))) { - room = `room/${ rid }`; + } else if (rid && !compareServerVersion(serverVersion, '3.6.0', methods.lowerThan)) { + room = `room/${rid}`; } else { - room = `@${ text }`; + room = `@${text}`; } const { id, token } = user; let query = ''; if (id && token && blockUnauthenticatedAccess) { - query += `&rc_token=${ token }&rc_uid=${ id }`; + query += `&rc_token=${token}&rc_uid=${id}`; } if (avatarETag) { - query += `&etag=${ avatarETag }`; + query += `&etag=${avatarETag}`; } if (avatar) { @@ -28,8 +37,8 @@ export const avatarURL = ({ return avatar; } - return formatUrl(`${ server }${ avatar }`, size, query); + return formatUrl(`${server}${avatar}`, size, query); } - return formatUrl(`${ server }/avatar/${ room }`, size, query); + return formatUrl(`${server}/avatar/${room}`, size, query); }; diff --git a/app/utils/base64-js/base64-js.test.js b/app/utils/base64-js/base64-js.test.js index 83e3de762..a05ceb95b 100644 --- a/app/utils/base64-js/base64-js.test.js +++ b/app/utils/base64-js/base64-js.test.js @@ -2,11 +2,7 @@ /* eslint-disable no-bitwise */ // https://github.com/beatgammit/base64-js/tree/master/test -import { - byteLength, - toByteArray, - fromByteArray -} from './index'; +import { byteLength, fromByteArray, toByteArray } from './index'; const map = (arr, callback) => { const res = []; @@ -14,7 +10,7 @@ const map = (arr, callback) => { let mappedValue; for (let k = 0, len = arr.length; k < len; k += 1) { - if ((typeof arr === 'string' && !!arr.charAt(k))) { + if (typeof arr === 'string' && !!arr.charAt(k)) { kValue = arr.charAt(k); mappedValue = callback(kValue, k, arr); res[k] = mappedValue; @@ -37,7 +33,7 @@ expect.extend({ }; } for (i = 0; i < length; i += 1) { - if ((a[i] & 0xFF) !== (b[i] & 0xFF)) { + if ((a[i] & 0xff) !== (b[i] & 0xff)) { return { pass: false }; @@ -76,17 +72,7 @@ test('padding bytes found inside base64 string', () => { expect(byteLength(str)).toBe(1); }); -const checks = [ - 'a', - 'aa', - 'aaa', - 'hi', - 'hi!', - 'hi!!', - 'sup', - 'sup?', - 'sup?!' -]; +const checks = ['a', 'aa', 'aaa', 'hi', 'hi!', 'hi!!', 'sup', 'sup?', 'sup?!']; test('convert to base64 and back', () => { for (let i = 0; i < checks.length; i += 1) { diff --git a/app/utils/base64-js/index.js b/app/utils/base64-js/index.js index ef4bcd4ce..5616f71df 100644 --- a/app/utils/base64-js/index.js +++ b/app/utils/base64-js/index.js @@ -16,7 +16,7 @@ for (let i = 0, len = code.length; i < len; i += 1) { revLookup['-'.charCodeAt(0)] = 62; revLookup['_'.charCodeAt(0)] = 63; -const getLens = (b64) => { +const getLens = b64 => { const len = b64.length; // We're encoding some strings not multiple of 4, so, disable this check @@ -27,26 +27,26 @@ const getLens = (b64) => { // Trim off extra bytes after placeholder bytes are found // See: https://github.com/beatgammit/base64-js/issues/42 let validLen = b64.indexOf('='); - if (validLen === -1) { validLen = len; } + if (validLen === -1) { + validLen = len; + } - const placeHoldersLen = validLen === len - ? 0 - : 4 - (validLen % 4); + const placeHoldersLen = validLen === len ? 0 : 4 - (validLen % 4); return [validLen, placeHoldersLen]; }; // base64 is 4/3 + up to two characters of the original data -export const byteLength = (b64) => { +export const byteLength = b64 => { const lens = getLens(b64); const validLen = lens[0]; const placeHoldersLen = lens[1]; - return (((validLen + placeHoldersLen) * 3) / 4) - placeHoldersLen; + return ((validLen + placeHoldersLen) * 3) / 4 - placeHoldersLen; }; -const _byteLength = (b64, validLen, placeHoldersLen) => (((validLen + placeHoldersLen) * 3) / 4) - placeHoldersLen; +const _byteLength = (b64, validLen, placeHoldersLen) => ((validLen + placeHoldersLen) * 3) / 4 - placeHoldersLen; -export const toByteArray = (b64) => { +export const toByteArray = b64 => { let tmp; const lens = getLens(b64); const validLen = lens[0]; @@ -57,60 +57,55 @@ export const toByteArray = (b64) => { let curByte = 0; // if there are placeholders, only get up to the last complete 4 chars - const len = placeHoldersLen > 0 - ? validLen - 4 - : validLen; + const len = placeHoldersLen > 0 ? validLen - 4 : validLen; let i; for (i = 0; i < len; i += 4) { - tmp = (revLookup[b64.charCodeAt(i)] << 18) - | (revLookup[b64.charCodeAt(i + 1)] << 12) - | (revLookup[b64.charCodeAt(i + 2)] << 6) - | revLookup[b64.charCodeAt(i + 3)]; - arr[curByte] = (tmp >> 16) & 0xFF; + tmp = + (revLookup[b64.charCodeAt(i)] << 18) | + (revLookup[b64.charCodeAt(i + 1)] << 12) | + (revLookup[b64.charCodeAt(i + 2)] << 6) | + revLookup[b64.charCodeAt(i + 3)]; + arr[curByte] = (tmp >> 16) & 0xff; curByte += 1; - arr[curByte] = (tmp >> 8) & 0xFF; + arr[curByte] = (tmp >> 8) & 0xff; curByte += 1; - arr[curByte] = tmp & 0xFF; + arr[curByte] = tmp & 0xff; curByte += 1; } if (placeHoldersLen === 2) { - tmp = (revLookup[b64.charCodeAt(i)] << 2) - | (revLookup[b64.charCodeAt(i + 1)] >> 4); - arr[curByte] = tmp & 0xFF; + tmp = (revLookup[b64.charCodeAt(i)] << 2) | (revLookup[b64.charCodeAt(i + 1)] >> 4); + arr[curByte] = tmp & 0xff; curByte += 1; } if (placeHoldersLen === 1) { - tmp = (revLookup[b64.charCodeAt(i)] << 10) - | (revLookup[b64.charCodeAt(i + 1)] << 4) - | (revLookup[b64.charCodeAt(i + 2)] >> 2); - arr[curByte] = (tmp >> 8) & 0xFF; + tmp = + (revLookup[b64.charCodeAt(i)] << 10) | (revLookup[b64.charCodeAt(i + 1)] << 4) | (revLookup[b64.charCodeAt(i + 2)] >> 2); + arr[curByte] = (tmp >> 8) & 0xff; curByte += 1; - arr[curByte] = tmp & 0xFF; + arr[curByte] = tmp & 0xff; curByte += 1; } return arr; }; -const tripletToBase64 = num => lookup[(num >> 18) & 0x3F] - + lookup[(num >> 12) & 0x3F] - + lookup[(num >> 6) & 0x3F] - + lookup[num & 0x3F]; +const tripletToBase64 = num => + lookup[(num >> 18) & 0x3f] + lookup[(num >> 12) & 0x3f] + lookup[(num >> 6) & 0x3f] + lookup[num & 0x3f]; const encodeChunk = (uint8, start, end) => { let tmp; const output = []; for (let i = start; i < end; i += 3) { - tmp = ((uint8[i] << 16) & 0xFF0000) + ((uint8[i + 1] << 8) & 0xFF00) + (uint8[i + 2] & 0xFF); + tmp = ((uint8[i] << 16) & 0xff0000) + ((uint8[i + 1] << 8) & 0xff00) + (uint8[i + 2] & 0xff); output.push(tripletToBase64(tmp)); } return output.join(''); }; -export const fromByteArray = (uint8) => { +export const fromByteArray = uint8 => { let tmp; const len = uint8.length; const extraBytes = len % 3; // if we have 1 byte left, pad 2 bytes @@ -119,22 +114,16 @@ export const fromByteArray = (uint8) => { // go through the array every three bytes, we'll deal with trailing stuff later for (let i = 0, len2 = len - extraBytes; i < len2; i += maxChunkLength) { - parts.push(encodeChunk( - uint8, i, (i + maxChunkLength) > len2 ? len2 : (i + maxChunkLength) - )); + parts.push(encodeChunk(uint8, i, i + maxChunkLength > len2 ? len2 : i + maxChunkLength)); } // pad the end with zeros, but make sure to not forget the extra bytes if (extraBytes === 1) { tmp = uint8[len - 1]; - parts.push( - `${ lookup[tmp >> 2] + lookup[(tmp << 4) & 0x3F] }==` - ); + parts.push(`${lookup[tmp >> 2] + lookup[(tmp << 4) & 0x3f]}==`); } else if (extraBytes === 2) { tmp = (uint8[len - 2] << 8) + uint8[len - 1]; - parts.push( - `${ lookup[tmp >> 10] + lookup[(tmp >> 4) & 0x3F] + lookup[(tmp << 2) & 0x3F] }=` - ); + parts.push(`${lookup[tmp >> 10] + lookup[(tmp >> 4) & 0x3f] + lookup[(tmp << 2) & 0x3f]}=`); } return parts.join(''); diff --git a/app/utils/debounce.js b/app/utils/debounce.js index 27fdffc99..106c61d00 100644 --- a/app/utils/debounce.js +++ b/app/utils/debounce.js @@ -4,12 +4,16 @@ export default function debounce(func, wait, immediate) { const context = this; const later = function __debounce() { timeout = null; - if (!immediate) { func.apply(context, args); } + if (!immediate) { + func.apply(context, args); + } }; const callNow = immediate && !timeout; clearTimeout(timeout); timeout = setTimeout(later, wait); - if (callNow) { func.apply(context, args); } + if (callNow) { + func.apply(context, args); + } } _debounce.stop = () => clearTimeout(timeout); return _debounce; diff --git a/app/utils/events.js b/app/utils/events.js index 40a196242..8e67fc82e 100644 --- a/app/utils/events.js +++ b/app/utils/events.js @@ -27,7 +27,7 @@ class EventEmitter { emit(event, ...args) { if (typeof this.events[event] === 'object') { - this.events[event].forEach((listener) => { + this.events[event].forEach(listener => { try { listener.apply(this, args); } catch (e) { diff --git a/app/utils/fetch.js b/app/utils/fetch.js index a804e0a12..84f5669ae 100644 --- a/app/utils/fetch.js +++ b/app/utils/fetch.js @@ -1,18 +1,21 @@ import { Platform } from 'react-native'; import DeviceInfo from 'react-native-device-info'; import { settings as RocketChatSettings } from '@rocket.chat/sdk'; + import RocketChat from '../lib/rocketchat'; // 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() })` + 'User-Agent': `RC Mobile; ${ + Platform.OS + } ${DeviceInfo.getSystemVersion()}; v${DeviceInfo.getVersion()} (${DeviceInfo.getBuildNumber()})` }; let _basicAuth; -export const setBasicAuth = (basicAuth) => { +export const setBasicAuth = basicAuth => { _basicAuth = basicAuth; if (basicAuth) { - RocketChatSettings.customHeaders = { ...headers, Authorization: `Basic ${ _basicAuth }` }; + RocketChatSettings.customHeaders = { ...headers, Authorization: `Basic ${_basicAuth}` }; } else { RocketChatSettings.customHeaders = headers; } diff --git a/app/utils/fileUpload/index.android.js b/app/utils/fileUpload/index.android.js index 6cf452468..5c45c27bd 100644 --- a/app/utils/fileUpload/index.android.js +++ b/app/utils/fileUpload/index.android.js @@ -2,7 +2,7 @@ import RNFetchBlob from 'rn-fetch-blob'; class FileUpload { fetch = (method, url, headers, data) => { - const formData = data.map((item) => { + const formData = data.map(item => { if (item.uri) { return { name: item.name, @@ -15,7 +15,7 @@ class FileUpload { }); return RNFetchBlob.fetch(method, url, headers, formData); - } + }; } const fileUpload = new FileUpload(); diff --git a/app/utils/fileUpload/index.ios.js b/app/utils/fileUpload/index.ios.js index 9b8d8d08e..a97640553 100644 --- a/app/utils/fileUpload/index.ios.js +++ b/app/utils/fileUpload/index.ios.js @@ -4,23 +4,23 @@ class Upload { this.formData = new FormData(); } - then = (callback) => { + then = callback => { this.xhr.onload = () => callback({ respInfo: this.xhr }); this.xhr.send(this.formData); - } + }; - catch = (callback) => { + catch = callback => { this.xhr.onerror = callback; - } + }; - uploadProgress = (callback) => { + uploadProgress = callback => { this.xhr.upload.onprogress = ({ total, loaded }) => callback(loaded, total); - } + }; cancel = () => { this.xhr.abort(); return Promise.resolve(); - } + }; } class FileUpload { @@ -28,11 +28,11 @@ class FileUpload { const upload = new Upload(); upload.xhr.open(method, url); - Object.keys(headers).forEach((key) => { + Object.keys(headers).forEach(key => { upload.xhr.setRequestHeader(key, headers[key]); }); - data.forEach((item) => { + data.forEach(item => { if (item.uri) { upload.formData.append(item.name, { uri: item.uri, @@ -45,7 +45,7 @@ class FileUpload { }); return upload; - } + }; } const fileUpload = new FileUpload(); diff --git a/app/utils/goRoom.js b/app/utils/goRoom.js index e9811e651..1025a17d4 100644 --- a/app/utils/goRoom.js +++ b/app/utils/goRoom.js @@ -20,7 +20,7 @@ const navigate = ({ item, isMasterDetail, ...props }) => { }); }; -export const goRoom = async({ item = {}, isMasterDetail = false, ...props }) => { +export const goRoom = async ({ item = {}, isMasterDetail = false, ...props }) => { if (item.t === 'd' && item.search) { // if user is using the search we need first to join/create room try { diff --git a/app/utils/info.js b/app/utils/info.js index 685b2f948..5d72f200e 100644 --- a/app/utils/info.js +++ b/app/utils/info.js @@ -1,11 +1,11 @@ import { Alert } from 'react-native'; + import I18n from '../i18n'; -export const showErrorAlert = (message, title, onPress = () => {}) => Alert.alert(title, message, [{ text: 'OK', onPress }], { cancelable: true }); +export const showErrorAlert = (message, title, onPress = () => {}) => + Alert.alert(title, message, [{ text: 'OK', onPress }], { cancelable: true }); -export const showConfirmationAlert = ({ - title, message, confirmationText, dismissText = I18n.t('Cancel'), onPress, onCancel -}) => ( +export const showConfirmationAlert = ({ title, message, confirmationText, dismissText = I18n.t('Cancel'), onPress, onCancel }) => Alert.alert( title || I18n.t('Are_you_sure_question_mark'), message, @@ -22,5 +22,4 @@ export const showConfirmationAlert = ({ } ], { cancelable: false } - ) -); + ); diff --git a/app/utils/isReadOnly.js b/app/utils/isReadOnly.js index f6f1937f6..62ae4fffe 100644 --- a/app/utils/isReadOnly.js +++ b/app/utils/isReadOnly.js @@ -1,7 +1,7 @@ import RocketChat from '../lib/rocketchat'; import reduxStore from '../lib/createStore'; -const canPostReadOnly = async({ rid }) => { +const canPostReadOnly = async ({ rid }) => { // TODO: this is not reactive. If this permission changes, the component won't be updated const postReadOnlyPermission = reduxStore.getState().permissions['post-readonly']; const permission = await RocketChat.hasPermission([postReadOnlyPermission], rid); @@ -10,7 +10,7 @@ const canPostReadOnly = async({ rid }) => { const isMuted = (room, user) => room && room.muted && room.muted.find && !!room.muted.find(m => m === user.username); -export const isReadOnly = async(room, user) => { +export const isReadOnly = async (room, user) => { if (room.archived) { return true; } diff --git a/app/utils/isValidEmail.js b/app/utils/isValidEmail.js index 58cc9077b..a8bd490f9 100644 --- a/app/utils/isValidEmail.js +++ b/app/utils/isValidEmail.js @@ -1,5 +1,6 @@ export default function isValidEmail(email) { /* eslint-disable no-useless-escape */ - const reg = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; + const reg = + /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; return reg.test(email); } diff --git a/app/utils/layoutAnimation.js b/app/utils/layoutAnimation.js index b338c7a3d..d4a26627d 100644 --- a/app/utils/layoutAnimation.js +++ b/app/utils/layoutAnimation.js @@ -3,21 +3,25 @@ import { LayoutAnimation } from 'react-native'; import debounce from './debounce'; import { isIOS } from './deviceInfo'; -export const animateNextTransition = debounce(() => { - if (isIOS) { - 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); +export const animateNextTransition = debounce( + () => { + if (isIOS) { + 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 +); diff --git a/app/utils/localAuthentication.js b/app/utils/localAuthentication.js index 759a07a0c..29f256850 100644 --- a/app/utils/localAuthentication.js +++ b/app/utils/localAuthentication.js @@ -7,23 +7,27 @@ import { sha256 } from 'js-sha256'; import UserPreferences from '../lib/userPreferences'; import store from '../lib/createStore'; import database from '../lib/database'; -import { isIOS } from './deviceInfo'; -import EventEmitter from './events'; import { - LOCAL_AUTHENTICATE_EMITTER, LOCKED_OUT_TIMER_KEY, ATTEMPTS_KEY, PASSCODE_KEY, CHANGE_PASSCODE_EMITTER + ATTEMPTS_KEY, + CHANGE_PASSCODE_EMITTER, + LOCAL_AUTHENTICATE_EMITTER, + LOCKED_OUT_TIMER_KEY, + PASSCODE_KEY } from '../constants/localAuthentication'; import I18n from '../i18n'; import { setLocalAuthenticated } from '../actions/login'; +import EventEmitter from './events'; +import { isIOS } from './deviceInfo'; -export const saveLastLocalAuthenticationSession = async(server, serverRecord) => { +export const saveLastLocalAuthenticationSession = async (server, serverRecord) => { const serversDB = database.servers; const serversCollection = serversDB.get('servers'); - await serversDB.action(async() => { + await serversDB.action(async () => { try { if (!serverRecord) { serverRecord = await serversCollection.find(server); } - await serverRecord.update((record) => { + await serverRecord.update(record => { record.lastLocalAuthenticatedSession = new Date(); }); } catch (e) { @@ -34,43 +38,46 @@ export const saveLastLocalAuthenticationSession = async(server, serverRecord) => export const resetAttempts = () => AsyncStorage.multiRemove([LOCKED_OUT_TIMER_KEY, ATTEMPTS_KEY]); -const openModal = hasBiometry => new Promise((resolve) => { - EventEmitter.emit(LOCAL_AUTHENTICATE_EMITTER, { - submit: () => resolve(), - hasBiometry +const openModal = hasBiometry => + new Promise(resolve => { + EventEmitter.emit(LOCAL_AUTHENTICATE_EMITTER, { + submit: () => resolve(), + hasBiometry + }); }); -}); -const openChangePasscodeModal = ({ force }) => new Promise((resolve, reject) => { - EventEmitter.emit(CHANGE_PASSCODE_EMITTER, { - submit: passcode => resolve(passcode), - cancel: () => reject(), - force +const openChangePasscodeModal = ({ force }) => + new Promise((resolve, reject) => { + EventEmitter.emit(CHANGE_PASSCODE_EMITTER, { + submit: passcode => resolve(passcode), + cancel: () => reject(), + force + }); }); -}); -export const changePasscode = async({ force = false }) => { +export const changePasscode = async ({ force = false }) => { const passcode = await openChangePasscodeModal({ force }); await UserPreferences.setStringAsync(PASSCODE_KEY, sha256(passcode)); }; -export const biometryAuth = force => LocalAuthentication.authenticateAsync({ - disableDeviceFallback: true, - cancelLabel: force ? I18n.t('Dont_activate') : I18n.t('Local_authentication_biometry_fallback'), - promptMessage: I18n.t('Local_authentication_biometry_title') -}); +export const biometryAuth = force => + LocalAuthentication.authenticateAsync({ + disableDeviceFallback: true, + cancelLabel: force ? I18n.t('Dont_activate') : I18n.t('Local_authentication_biometry_fallback'), + promptMessage: I18n.t('Local_authentication_biometry_title') + }); /* * It'll help us to get the permission to use FaceID * and enable/disable the biometry when user put their first passcode -*/ -const checkBiometry = async(serverRecord) => { + */ +const checkBiometry = async serverRecord => { const serversDB = database.servers; const result = await biometryAuth(true); - await serversDB.action(async() => { + await serversDB.action(async () => { try { - await serverRecord.update((record) => { + await serverRecord.update(record => { record.biometry = !!result?.success; }); } catch { @@ -79,7 +86,7 @@ const checkBiometry = async(serverRecord) => { }); }; -export const checkHasPasscode = async({ force = true, serverRecord }) => { +export const checkHasPasscode = async ({ force = true, serverRecord }) => { const storedPasscode = await UserPreferences.getStringAsync(PASSCODE_KEY); if (!storedPasscode) { await changePasscode({ force }); @@ -89,7 +96,7 @@ export const checkHasPasscode = async({ force = true, serverRecord }) => { return Promise.resolve(); }; -export const localAuthenticate = async(server) => { +export const localAuthenticate = async server => { const serversDB = database.servers; const serversCollection = serversDB.get('servers'); @@ -103,7 +110,11 @@ export const localAuthenticate = async(server) => { // if screen lock is enabled if (serverRecord?.autoLock) { // Make sure splash screen has been hidden - await RNBootSplash.hide(); + try { + await RNBootSplash.hide(); + } catch { + // Do nothing + } // Check if the app has passcode const result = await checkHasPasscode({ serverRecord }); @@ -139,7 +150,7 @@ export const localAuthenticate = async(server) => { } }; -export const supportedBiometryLabel = async() => { +export const supportedBiometryLabel = async () => { try { const enrolled = await LocalAuthentication.isEnrolledAsync(); diff --git a/app/utils/log/events.js b/app/utils/log/events.js index 2ab3f539b..4a3ec38df 100644 --- a/app/utils/log/events.js +++ b/app/utils/log/events.js @@ -1,9 +1,4 @@ export default { - // ONBOARDING VIEW - ONBOARD_JOIN_A_WORKSPACE: 'onboard_join_a_workspace', - ONBOARD_CREATE_NEW_WORKSPACE: 'onboard_create_new_workspace', - ONBOARD_CREATE_NEW_WORKSPACE_F: 'onboard_create_new_workspace_f', - // NEW SERVER VIEW NS_CONNECT_TO_WORKSPACE: 'ns_connect_to_workspace', NS_JOIN_OPEN_WORKSPACE: 'ns_join_open_workspace', @@ -78,6 +73,7 @@ export default { RL_GROUP_CHANNELS_BY_TYPE: 'rl_group_channels_by_type', RL_GROUP_CHANNELS_BY_FAVORITE: 'rl_group_channels_by_favorite', RL_GROUP_CHANNELS_BY_UNREAD: 'rl_group_channels_by_unread', + RL_CREATE_NEW_WORKSPACE: 'rl_create_new_workspace', // QUEUE LIST VIEW QL_GO_ROOM: 'ql_go_room', @@ -85,6 +81,7 @@ export default { // DIRECTORY VIEW DIRECTORY_SEARCH_USERS: 'directory_search_users', DIRECTORY_SEARCH_CHANNELS: 'directory_search_channels', + DIRECTORY_SEARCH_TEAMS: 'directory_search_teams', // NEW MESSAGE VIEW NEW_MSG_CREATE_CHANNEL: 'new_msg_create_channel', diff --git a/app/utils/log/index.js b/app/utils/log/index.js index 266f4bd44..41074832f 100644 --- a/app/utils/log/index.js +++ b/app/utils/log/index.js @@ -1,4 +1,5 @@ import firebaseAnalytics from '@react-native-firebase/analytics'; + import { isFDroidBuild } from '../../constants/environment'; import events from './events'; @@ -11,7 +12,6 @@ let reportAnalyticsEvents = true; export const getReportCrashErrorsValue = () => reportCrashErrors; export const getReportAnalyticsEventsValue = () => reportAnalyticsEvents; - if (!isFDroidBuild) { bugsnag = require('@bugsnag/react-native').default; bugsnag.start({ @@ -19,7 +19,9 @@ if (!isFDroidBuild) { return reportAnalyticsEvents; }, onError(error) { - if (!reportAnalyticsEvents) { error.breadcrumbs = []; } + if (!reportAnalyticsEvents) { + error.breadcrumbs = []; + } return reportCrashErrors; } }); @@ -32,7 +34,7 @@ export { events }; let metadata = {}; -export const logServerVersion = (serverVersion) => { +export const logServerVersion = serverVersion => { metadata = { serverVersion }; @@ -49,26 +51,26 @@ export const logEvent = (eventName, payload) => { } }; -export const setCurrentScreen = (currentScreen) => { +export const setCurrentScreen = currentScreen => { if (!isFDroidBuild) { analytics().setCurrentScreen(currentScreen); bugsnag.leaveBreadcrumb(currentScreen, { type: 'navigation' }); } }; -export const toggleCrashErrorsReport = (value) => { +export const toggleCrashErrorsReport = value => { crashlytics().setCrashlyticsCollectionEnabled(value); - return reportCrashErrors = value; + return (reportCrashErrors = value); }; -export const toggleAnalyticsEventsReport = (value) => { +export const toggleAnalyticsEventsReport = value => { analytics().setAnalyticsCollectionEnabled(value); - return reportAnalyticsEvents = value; + return (reportAnalyticsEvents = value); }; -export default (e) => { +export default e => { if (e instanceof Error && bugsnag && e.message !== 'Aborted' && !__DEV__) { - bugsnag.notify(e, (event) => { + bugsnag.notify(e, event => { event.addMetadata('details', { ...metadata }); }); if (!isFDroidBuild) { diff --git a/app/utils/messageTypes.js b/app/utils/messageTypes.js index eacd2c514..d1f6fbf69 100644 --- a/app/utils/messageTypes.js +++ b/app/utils/messageTypes.js @@ -2,40 +2,52 @@ 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' } diff --git a/app/utils/navigation/animations.js b/app/utils/navigation/animations.js index 7b2d15543..9f99764c4 100644 --- a/app/utils/navigation/animations.js +++ b/app/utils/navigation/animations.js @@ -1,15 +1,12 @@ -import { Easing, Animated } from 'react-native'; -import { TransitionPresets, HeaderStyleInterpolators } from '@react-navigation/stack'; +import { Animated, Easing } from 'react-native'; +import { HeaderStyleInterpolators, TransitionPresets } from '@react-navigation/stack'; import { isAndroid } from '../deviceInfo'; import conditional from './conditional'; const { multiply } = Animated; -const forFadeFromCenter = ({ - current, - closing -}) => { +const forFadeFromCenter = ({ current, closing }) => { const opacity = conditional( closing, current.progress, @@ -51,11 +48,7 @@ export const FadeFromCenterModal = { cardStyleInterpolator: forFadeFromCenter }; -const forStackAndroid = ({ - current, - inverted, - layouts: { screen } -}) => { +const forStackAndroid = ({ current, inverted, layouts: { screen } }) => { const translateX = multiply( current.progress.interpolate({ inputRange: [0, 1], diff --git a/app/utils/navigation/index.js b/app/utils/navigation/index.ts similarity index 76% rename from app/utils/navigation/index.js rename to app/utils/navigation/index.ts index 6f4faf6c1..9314e88e8 100644 --- a/app/utils/navigation/index.js +++ b/app/utils/navigation/index.ts @@ -1,5 +1,5 @@ import { StyleSheet } from 'react-native'; -import { DefaultTheme, DarkTheme } from '@react-navigation/native'; +import { DarkTheme, DefaultTheme } from '@react-navigation/native'; import { themes } from '../../constants/colors'; @@ -11,18 +11,17 @@ export const defaultHeader = { cardStyle: { backgroundColor: 'transparent' } }; - export const cardStyle = { backgroundColor: 'rgba(0,0,0,0)' }; -export const borderBottom = theme => ({ +export const borderBottom: any = (theme: any) => ({ borderBottomWidth: StyleSheet.hairlineWidth, borderBottomColor: themes[theme].headerBorder, elevation: 0 }); -export const themedHeader = theme => ({ +export const themedHeader = (theme: any) => ({ headerStyle: { ...borderBottom(theme), backgroundColor: themes[theme].headerBackground @@ -31,7 +30,7 @@ export const themedHeader = theme => ({ headerTitleStyle: { color: themes[theme].headerTitleColor } }); -export const navigationTheme = (theme) => { +export const navigationTheme = (theme: any) => { const defaultNavTheme = theme === 'light' ? DefaultTheme : DarkTheme; return { @@ -45,7 +44,7 @@ export const navigationTheme = (theme) => { }; // Gets the current screen from navigation state -export const getActiveRoute = (state) => { +export const getActiveRoute: any = (state: any) => { const route = state?.routes[state?.index]; if (route?.state) { @@ -56,4 +55,4 @@ export const getActiveRoute = (state) => { return route; }; -export const getActiveRouteName = state => getActiveRoute(state)?.name; +export const getActiveRouteName = (state: any) => getActiveRoute(state)?.name; diff --git a/app/utils/openLink.js b/app/utils/openLink.js index 703625497..92df16a70 100644 --- a/app/utils/openLink.js +++ b/app/utils/openLink.js @@ -27,15 +27,15 @@ const appSchemeURL = (url, browser) => { schemeUrl = url.replace(protocol, scheme.chromeSecure); } } else if (browser === 'firefox') { - schemeUrl = `${ scheme.firefox }//open-url?url=${ url }`; + schemeUrl = `${scheme.firefox}//open-url?url=${url}`; } else if (browser === 'brave') { - schemeUrl = `${ scheme.brave }//open-url?url=${ url }`; + schemeUrl = `${scheme.brave}//open-url?url=${url}`; } return schemeUrl; }; -const openLink = async(url, theme = 'light') => { +const openLink = async (url, theme = 'light') => { try { const browser = await UserPreferences.getStringAsync(DEFAULT_BROWSER_KEY); diff --git a/app/utils/review.js b/app/utils/review.js index 98d673631..15f1cf968 100644 --- a/app/utils/review.js +++ b/app/utils/review.js @@ -1,12 +1,12 @@ import { Alert, Linking } from 'react-native'; import AsyncStorage from '@react-native-community/async-storage'; -import { isIOS } from './deviceInfo'; import I18n from '../i18n'; -import { showErrorAlert } from './info'; import { STORE_REVIEW_LINK } from '../constants/links'; import { isFDroidBuild } from '../constants/environment'; -import { logEvent, events } from './log'; +import { showErrorAlert } from './info'; +import { isIOS } from './deviceInfo'; +import { events, logEvent } from './log'; const store = isIOS ? 'App Store' : 'Play Store'; @@ -32,7 +32,7 @@ const onCancelPress = () => { } }; -export const onReviewPress = async() => { +export const onReviewPress = async () => { logEvent(events.SE_REVIEW_THIS_APP); await onCancelPress(); try { @@ -59,20 +59,19 @@ const onReviewButton = { text: I18n.t('Review_app_yes'), onPress: onReviewPress const onAskMeLaterButton = { text: I18n.t('Review_app_later'), onPress: onAskMeLaterPress }; const onCancelButton = { text: I18n.t('Review_app_no'), onPress: onCancelPress }; -const askReview = () => Alert.alert( - I18n.t('Review_app_title'), - I18n.t('Review_app_desc', { store }), - isIOS - ? [onReviewButton, onAskMeLaterButton, onCancelButton] - : [onAskMeLaterButton, onCancelButton, onReviewButton], - { - cancelable: true, - onDismiss: onAskMeLaterPress - } -); +const askReview = () => + Alert.alert( + I18n.t('Review_app_title'), + I18n.t('Review_app_desc', { store }), + isIOS ? [onReviewButton, onAskMeLaterButton, onCancelButton] : [onAskMeLaterButton, onCancelButton, onReviewButton], + { + cancelable: true, + onDismiss: onAskMeLaterPress + } + ); -const tryReview = async() => { - const data = await AsyncStorage.getItem(reviewKey) || '{}'; +const tryReview = async () => { + const data = (await AsyncStorage.getItem(reviewKey)) || '{}'; const reviewData = JSON.parse(data); const { lastReview = 0, doneReview = false } = reviewData; const lastReviewDate = new Date(lastReview); @@ -97,7 +96,7 @@ class ReviewApp { tryReview(); } } - } + }; } export const Review = new ReviewApp(); diff --git a/app/utils/room.js b/app/utils/room.js index fef926d5f..67df65a98 100644 --- a/app/utils/room.js +++ b/app/utils/room.js @@ -1,9 +1,9 @@ import moment from 'moment'; -import { themes } from '../constants/colors'; +import { themes } from '../constants/colors'; import I18n from '../i18n'; -export const isBlocked = (room) => { +export const isBlocked = room => { if (room) { const { t, blocked, blocker } = room; if (t === 'd' && (blocked || blocker)) { @@ -13,24 +13,28 @@ export const isBlocked = (room) => { return false; }; -export const capitalize = (s) => { - if (typeof s !== 'string') { return ''; } +export const capitalize = s => { + if (typeof s !== 'string') { + return ''; + } return s.charAt(0).toUpperCase() + s.slice(1); }; -export const formatDate = date => moment(date).calendar(null, { - lastDay: `[${ I18n.t('Yesterday') }]`, - sameDay: 'LT', - lastWeek: 'dddd', - sameElse: 'L' -}); +export const formatDate = date => + moment(date).calendar(null, { + lastDay: `[${I18n.t('Yesterday')}]`, + sameDay: 'LT', + lastWeek: 'dddd', + sameElse: 'L' + }); -export const formatDateThreads = date => moment(date).calendar(null, { - sameDay: 'LT', - lastDay: `[${ I18n.t('Yesterday') }] LT`, - lastWeek: 'dddd LT', - sameElse: 'LL' -}); +export const formatDateThreads = date => + moment(date).calendar(null, { + sameDay: 'LT', + lastDay: `[${I18n.t('Yesterday')}] LT`, + lastWeek: 'dddd LT', + sameElse: 'LL' + }); export const getBadgeColor = ({ subscription, messageId, theme }) => { if (subscription?.tunreadUser?.includes(messageId)) { diff --git a/app/utils/scaling.js b/app/utils/scaling.js index 1168cc614..7cf33f1fc 100644 --- a/app/utils/scaling.js +++ b/app/utils/scaling.js @@ -1,14 +1,11 @@ -import { Dimensions } from 'react-native'; - import { isTablet } from './deviceInfo'; -const { width, height } = Dimensions.get('window'); - const guidelineBaseWidth = isTablet ? 600 : 375; const guidelineBaseHeight = isTablet ? 800 : 667; -const scale = size => (width / guidelineBaseWidth) * size; -const verticalScale = size => (height / guidelineBaseHeight) * size; -const moderateScale = (size, factor = 0.5) => size + ((scale(size) - size) * factor); +// TODO: we need to refactor this +const scale = (size, width) => (width / guidelineBaseWidth) * size; +const verticalScale = (size, height) => (height / guidelineBaseHeight) * size; +const moderateScale = (size, factor = 0.5, width) => size + (scale(size, width) - size) * factor; export { scale, verticalScale, moderateScale }; diff --git a/app/utils/server.js b/app/utils/server.js index 0dcc10546..e7be96b30 100644 --- a/app/utils/server.js +++ b/app/utils/server.js @@ -3,11 +3,11 @@ url = 'https://open.rocket.chat/method' hostname = 'open.rocket.chat' */ -export const extractHostname = (url) => { +export const extractHostname = url => { let hostname; if (url.indexOf('//') > -1) { - [,, hostname] = url.split('/'); + [, , hostname] = url.split('/'); } else { [hostname] = url.split('/'); } diff --git a/app/utils/shortnameToUnicode/ascii.js b/app/utils/shortnameToUnicode/ascii.js index be3cae336..4d9d04cd3 100644 --- a/app/utils/shortnameToUnicode/ascii.js +++ b/app/utils/shortnameToUnicode/ascii.js @@ -3,8 +3,125 @@ /* eslint-disable object-curly-spacing */ /* eslint-disable comma-spacing */ /* eslint-disable key-spacing */ -const ascii = {'*\\0/*':'🙆','*\\O/*':'🙆','-___-':'😑',':\'-)':'😂','\':-)':'😅','\':-D':'😅','>:-)':'😆','\':-(':'😓','>:-(':'😠',':\'-(':'😢','O:-)':'😇','0:-3':'😇','0:-)':'😇','0;^)':'😇','O;-)':'😇','0;-)':'😇','O:-3':'😇','-__-':'😑',':-Þ':'😛','</3':'💔',':\')':'😂',':-D':'😃','\':)':'😅','\'=)':'😅','\':D':'😅','\'=D':'😅','>:)':'😆','>;)':'😆','>=)':'😆',';-)':'😉','*-)':'😉',';-]':'😉',';^)':'😉','\':(':'😓','\'=(':'😓',':-*':'😘',':^*':'😘','>:P':'😜','X-P':'😜','>:[':'😞',':-(':'😞',':-[':'😞','>:(':'😠',':\'(':'😢',';-(':'😢','>.<':'😣','#-)':'😵','%-)':'😵','X-)':'😵','\\0/':'🙆','\\O/':'🙆','0:3':'😇','0:)':'😇','O:)':'😇','O=)':'😇','O:3':'😇','B-)':'😎','8-)':'😎','B-D':'😎','8-D':'😎','-_-':'😑','>:\\':'😕','>:/':'😕',':-/':'😕',':-.':'😕',':-P':'😛',':Þ':'😛',':-b':'😛',':-O':'😮','O_O':'😮','>:O':'😮',':-X':'😶',':-#':'😶',':-)':'🙂','(y)':'👍','<3':'❤','=D':'😃',';)':'😉','*)':'😉',';]':'😉',';D':'😉',':*':'😘','=*':'😘',':(':'😞',':[':'😞','=(':'😞',':@':'😠',';(':'😢','D:':'😨',':$':'😳','=$':'😳','#)':'😵','%)':'😵','X)':'😵','B)':'😎','8)':'😎',':/':'😕',':\\':'😕','=/':'😕','=\\':'😕',':L':'😕','=L':'😕',':P':'😛','=P':'😛',':b':'😛',':O':'😮',':X':'😶',':#':'😶','=X':'😶','=#':'😶',':)':'🙂','=]':'🙂','=)':'🙂',':]':'🙂',':D':'😄'}; +const ascii = { + '*\\0/*': '🙆', + '*\\O/*': '🙆', + '-___-': '😑', + ":'-)": '😂', + "':-)": '😅', + "':-D": '😅', + '>:-)': '😆', + "':-(": '😓', + '>:-(': '😠', + ":'-(": '😢', + 'O:-)': '😇', + '0:-3': '😇', + '0:-)': '😇', + '0;^)': '😇', + 'O;-)': '😇', + '0;-)': '😇', + 'O:-3': '😇', + '-__-': '😑', + ':-Þ': '😛', + '</3': '💔', + ":')": '😂', + ':-D': '😃', + "':)": '😅', + "'=)": '😅', + "':D": '😅', + "'=D": '😅', + '>:)': '😆', + '>;)': '😆', + '>=)': '😆', + ';-)': '😉', + '*-)': '😉', + ';-]': '😉', + ';^)': '😉', + "':(": '😓', + "'=(": '😓', + ':-*': '😘', + ':^*': '😘', + '>:P': '😜', + 'X-P': '😜', + '>:[': '😞', + ':-(': '😞', + ':-[': '😞', + '>:(': '😠', + ":'(": '😢', + ';-(': '😢', + '>.<': '😣', + '#-)': '😵', + '%-)': '😵', + 'X-)': '😵', + '\\0/': '🙆', + '\\O/': '🙆', + '0:3': '😇', + '0:)': '😇', + 'O:)': '😇', + 'O=)': '😇', + 'O:3': '😇', + 'B-)': '😎', + '8-)': '😎', + 'B-D': '😎', + '8-D': '😎', + '-_-': '😑', + '>:\\': '😕', + '>:/': '😕', + ':-/': '😕', + ':-.': '😕', + ':-P': '😛', + ':Þ': '😛', + ':-b': '😛', + ':-O': '😮', + O_O: '😮', + '>:O': '😮', + ':-X': '😶', + ':-#': '😶', + ':-)': '🙂', + '(y)': '👍', + '<3': '❤', + '=D': '😃', + ';)': '😉', + '*)': '😉', + ';]': '😉', + ';D': '😉', + ':*': '😘', + '=*': '😘', + ':(': '😞', + ':[': '😞', + '=(': '😞', + ':@': '😠', + ';(': '😢', + 'D:': '😨', + ':$': '😳', + '=$': '😳', + '#)': '😵', + '%)': '😵', + 'X)': '😵', + 'B)': '😎', + '8)': '😎', + ':/': '😕', + ':\\': '😕', + '=/': '😕', + '=\\': '😕', + ':L': '😕', + '=L': '😕', + ':P': '😛', + '=P': '😛', + ':b': '😛', + ':O': '😮', + ':X': '😶', + ':#': '😶', + '=X': '😶', + '=#': '😶', + ':)': '🙂', + '=]': '🙂', + '=)': '🙂', + ':]': '🙂', + ':D': '😄' +}; -export const asciiRegexp = '(\\*\\\\0\\/\\*|\\*\\\\O\\/\\*|\\-___\\-|\\:\'\\-\\)|\'\\:\\-\\)|\'\\:\\-D|\\>\\:\\-\\)|>\\:\\-\\)|\'\\:\\-\\(|\\>\\:\\-\\(|>\\:\\-\\(|\\:\'\\-\\(|O\\:\\-\\)|0\\:\\-3|0\\:\\-\\)|0;\\^\\)|O;\\-\\)|0;\\-\\)|O\\:\\-3|\\-__\\-|\\:\\-Þ|\\:\\-Þ|\\<\\/3|<\\/3|\\:\'\\)|\\:\\-D|\'\\:\\)|\'\\=\\)|\'\\:D|\'\\=D|\\>\\:\\)|>\\:\\)|\\>;\\)|>;\\)|\\>\\=\\)|>\\=\\)|;\\-\\)|\\*\\-\\)|;\\-\\]|;\\^\\)|\'\\:\\(|\'\\=\\(|\\:\\-\\*|\\:\\^\\*|\\>\\:P|>\\:P|X\\-P|\\>\\:\\[|>\\:\\[|\\:\\-\\(|\\:\\-\\[|\\>\\:\\(|>\\:\\(|\\:\'\\(|;\\-\\(|\\>\\.\\<|>\\.<|#\\-\\)|%\\-\\)|X\\-\\)|\\\\0\\/|\\\\O\\/|0\\:3|0\\:\\)|O\\:\\)|O\\=\\)|O\\:3|B\\-\\)|8\\-\\)|B\\-D|8\\-D|\\-_\\-|\\>\\:\\\\|>\\:\\\\|\\>\\:\\/|>\\:\\/|\\:\\-\\/|\\:\\-\\.|\\:\\-P|\\:Þ|\\:Þ|\\:\\-b|\\:\\-O|O_O|\\>\\:O|>\\:O|\\:\\-X|\\:\\-#|\\:\\-\\)|\\(y\\)|\\<3|<3|\\=D|;\\)|\\*\\)|;\\]|;D|\\:\\*|\\=\\*|\\:\\(|\\:\\[|\\=\\(|\\:@|;\\(|D\\:|\\:\\$|\\=\\$|#\\)|%\\)|X\\)|B\\)|8\\)|\\:\\/|\\:\\\\|\\=\\/|\\=\\\\|\\:L|\\=L|\\:P|\\=P|\\:b|\\:O|\\:X|\\:#|\\=X|\\=#|\\:\\)|\\=\\]|\\=\\)|\\:\\]|\\:D)'; +export const asciiRegexp = + "(\\*\\\\0\\/\\*|\\*\\\\O\\/\\*|\\-___\\-|\\:'\\-\\)|'\\:\\-\\)|'\\:\\-D|\\>\\:\\-\\)|>\\:\\-\\)|'\\:\\-\\(|\\>\\:\\-\\(|>\\:\\-\\(|\\:'\\-\\(|O\\:\\-\\)|0\\:\\-3|0\\:\\-\\)|0;\\^\\)|O;\\-\\)|0;\\-\\)|O\\:\\-3|\\-__\\-|\\:\\-Þ|\\:\\-Þ|\\<\\/3|<\\/3|\\:'\\)|\\:\\-D|'\\:\\)|'\\=\\)|'\\:D|'\\=D|\\>\\:\\)|>\\:\\)|\\>;\\)|>;\\)|\\>\\=\\)|>\\=\\)|;\\-\\)|\\*\\-\\)|;\\-\\]|;\\^\\)|'\\:\\(|'\\=\\(|\\:\\-\\*|\\:\\^\\*|\\>\\:P|>\\:P|X\\-P|\\>\\:\\[|>\\:\\[|\\:\\-\\(|\\:\\-\\[|\\>\\:\\(|>\\:\\(|\\:'\\(|;\\-\\(|\\>\\.\\<|>\\.<|#\\-\\)|%\\-\\)|X\\-\\)|\\\\0\\/|\\\\O\\/|0\\:3|0\\:\\)|O\\:\\)|O\\=\\)|O\\:3|B\\-\\)|8\\-\\)|B\\-D|8\\-D|\\-_\\-|\\>\\:\\\\|>\\:\\\\|\\>\\:\\/|>\\:\\/|\\:\\-\\/|\\:\\-\\.|\\:\\-P|\\:Þ|\\:Þ|\\:\\-b|\\:\\-O|O_O|\\>\\:O|>\\:O|\\:\\-X|\\:\\-#|\\:\\-\\)|\\(y\\)|\\<3|<3|\\=D|;\\)|\\*\\)|;\\]|;D|\\:\\*|\\=\\*|\\:\\(|\\:\\[|\\=\\(|\\:@|;\\(|D\\:|\\:\\$|\\=\\$|#\\)|%\\)|X\\)|B\\)|8\\)|\\:\\/|\\:\\\\|\\=\\/|\\=\\\\|\\:L|\\=L|\\:P|\\=P|\\:b|\\:O|\\:X|\\:#|\\=X|\\=#|\\:\\)|\\=\\]|\\=\\)|\\:\\]|\\:D)"; export default ascii; diff --git a/app/utils/shortnameToUnicode/emojis.js b/app/utils/shortnameToUnicode/emojis.js index e6f294719..14cd61337 100644 --- a/app/utils/shortnameToUnicode/emojis.js +++ b/app/utils/shortnameToUnicode/emojis.js @@ -3,5 +3,4633 @@ /* eslint-disable object-curly-spacing */ /* eslint-disable comma-spacing */ /* eslint-disable key-spacing */ -const emojis = {':england:':'🏴󠁧󠁢󠁥󠁮󠁧󠁿',':scotland:':'🏴󠁧󠁢󠁳󠁣󠁴󠁿',':wales:':'🏴󠁧󠁢󠁷󠁬󠁳󠁿',':men_holding_hands_medium_light_skin_tone_light_skin_tone:':'👨🏼‍🤝‍👨🏻',':men_holding_hands_tone2_tone1:':'👨🏼‍🤝‍👨🏻',':men_holding_hands_medium_skin_tone_light_skin_tone:':'👨🏽‍🤝‍👨🏻',':men_holding_hands_tone3_tone1:':'👨🏽‍🤝‍👨🏻',':men_holding_hands_medium_skin_tone_medium_light_skin_tone:':'👨🏽‍🤝‍👨🏼',':men_holding_hands_tone3_tone2:':'👨🏽‍🤝‍👨🏼',':men_holding_hands_medium_dark_skin_tone_light_skin_tone:':'👨🏾‍🤝‍👨🏻',':men_holding_hands_tone4_tone1:':'👨🏾‍🤝‍👨🏻',':men_holding_hands_medium_dark_skin_tone_medium_light_skin_tone:':'👨🏾‍🤝‍👨🏼',':men_holding_hands_tone4_tone2:':'👨🏾‍🤝‍👨🏼',':men_holding_hands_medium_dark_skin_tone_medium_skin_tone:':'👨🏾‍🤝‍👨🏽',':men_holding_hands_tone4_tone3:':'👨🏾‍🤝‍👨🏽',':men_holding_hands_dark_skin_tone_light_skin_tone:':'👨🏿‍🤝‍👨🏻',':men_holding_hands_tone5_tone1:':'👨🏿‍🤝‍👨🏻',':men_holding_hands_dark_skin_tone_medium_light_skin_tone:':'👨🏿‍🤝‍👨🏼',':men_holding_hands_tone5_tone2:':'👨🏿‍🤝‍👨🏼',':men_holding_hands_dark_skin_tone_medium_skin_tone:':'👨🏿‍🤝‍👨🏽',':men_holding_hands_tone5_tone3:':'👨🏿‍🤝‍👨🏽',':men_holding_hands_dark_skin_tone_medium_dark_skin_tone:':'👨🏿‍🤝‍👨🏾',':men_holding_hands_tone5_tone4:':'👨🏿‍🤝‍👨🏾',':people_holding_hands_light_skin_tone:':'🧑🏻‍🤝‍🧑🏻',':people_holding_hands_tone1:':'🧑🏻‍🤝‍🧑🏻',':people_holding_hands_medium_light_skin_tone:':'🧑🏼‍🤝‍🧑🏼',':people_holding_hands_tone2:':'🧑🏼‍🤝‍🧑🏼',':people_holding_hands_medium_light_skin_tone_light_skin_tone:':'🧑🏼‍🤝‍🧑🏻',':people_holding_hands_tone2_tone1:':'🧑🏼‍🤝‍🧑🏻',':people_holding_hands_medium_skin_tone:':'🧑🏽‍🤝‍🧑🏽',':people_holding_hands_tone3:':'🧑🏽‍🤝‍🧑🏽',':people_holding_hands_medium_skin_tone_light_skin_tone:':'🧑🏽‍🤝‍🧑🏻',':people_holding_hands_tone3_tone1:':'🧑🏽‍🤝‍🧑🏻',':people_holding_hands_medium_skin_tone_medium_light_skin_tone:':'🧑🏽‍🤝‍🧑🏼',':people_holding_hands_tone3_tone2:':'🧑🏽‍🤝‍🧑🏼',':people_holding_hands_medium_dark_skin_tone:':'🧑🏾‍🤝‍🧑🏾',':people_holding_hands_tone4:':'🧑🏾‍🤝‍🧑🏾',':people_holding_hands_medium_dark_skin_tone_light_skin_tone:':'🧑🏾‍🤝‍🧑🏻',':people_holding_hands_tone4_tone1:':'🧑🏾‍🤝‍🧑🏻',':people_holding_hands_medium_dark_skin_tone_medium_light_skin_tone:':'🧑🏾‍🤝‍🧑🏼',':people_holding_hands_tone4_tone2:':'🧑🏾‍🤝‍🧑🏼',':people_holding_hands_medium_dark_skin_tone_medium_skin_tone:':'🧑🏾‍🤝‍🧑🏽',':people_holding_hands_tone4_tone3:':'🧑🏾‍🤝‍🧑🏽',':people_holding_hands_dark_skin_tone:':'🧑🏿‍🤝‍🧑🏿',':people_holding_hands_tone5:':'🧑🏿‍🤝‍🧑🏿',':people_holding_hands_dark_skin_tone_light_skin_tone:':'🧑🏿‍🤝‍🧑🏻',':people_holding_hands_tone5_tone1:':'🧑🏿‍🤝‍🧑🏻',':people_holding_hands_dark_skin_tone_medium_light_skin_tone:':'🧑🏿‍🤝‍🧑🏼',':people_holding_hands_tone5_tone2:':'🧑🏿‍🤝‍🧑🏼',':people_holding_hands_dark_skin_tone_medium_skin_tone:':'🧑🏿‍🤝‍🧑🏽',':people_holding_hands_tone5_tone3:':'🧑🏿‍🤝‍🧑🏽',':people_holding_hands_dark_skin_tone_medium_dark_skin_tone:':'🧑🏿‍🤝‍🧑🏾',':people_holding_hands_tone5_tone4:':'🧑🏿‍🤝‍🧑🏾',':woman_and_man_holding_hands_light_skin_tone_medium_light_skin_tone:':'👩🏻‍🤝‍👨🏼',':woman_and_man_holding_hands_tone1_tone2:':'👩🏻‍🤝‍👨🏼',':woman_and_man_holding_hands_light_skin_tone_medium_skin_tone:':'👩🏻‍🤝‍👨🏽',':woman_and_man_holding_hands_tone1_tone3:':'👩🏻‍🤝‍👨🏽',':woman_and_man_holding_hands_light_skin_tone_medium_dark_skin_tone:':'👩🏻‍🤝‍👨🏾',':woman_and_man_holding_hands_tone1_tone4:':'👩🏻‍🤝‍👨🏾',':woman_and_man_holding_hands_light_skin_tone_dark_skin_tone:':'👩🏻‍🤝‍👨🏿',':woman_and_man_holding_hands_tone1_tone5:':'👩🏻‍🤝‍👨🏿',':woman_and_man_holding_hands_medium_light_skin_tone_light_skin_tone:':'👩🏼‍🤝‍👨🏻',':woman_and_man_holding_hands_tone2_tone1:':'👩🏼‍🤝‍👨🏻',':woman_and_man_holding_hands_medium_light_skin_tone_medium_skin_tone:':'👩🏼‍🤝‍👨🏽',':woman_and_man_holding_hands_tone2_tone3:':'👩🏼‍🤝‍👨🏽',':woman_and_man_holding_hands_medium_light_skin_tone_medium_dark_skin_tone:':'👩🏼‍🤝‍👨🏾',':woman_and_man_holding_hands_tone2_tone4:':'👩🏼‍🤝‍👨🏾',':woman_and_man_holding_hands_medium_light_skin_tone_dark_skin_tone:':'👩🏼‍🤝‍👨🏿',':woman_and_man_holding_hands_tone2_tone5:':'👩🏼‍🤝‍👨🏿',':woman_and_man_holding_hands_medium_skin_tone_light_skin_tone:':'👩🏽‍🤝‍👨🏻',':woman_and_man_holding_hands_tone3_tone1:':'👩🏽‍🤝‍👨🏻',':woman_and_man_holding_hands_medium_skin_tone_medium_light_skin_tone:':'👩🏽‍🤝‍👨🏼',':woman_and_man_holding_hands_tone3_tone2:':'👩🏽‍🤝‍👨🏼',':woman_and_man_holding_hands_medium_skin_tone_medium_dark_skin_tone:':'👩🏽‍🤝‍👨🏾',':woman_and_man_holding_hands_tone3_tone4:':'👩🏽‍🤝‍👨🏾',':woman_and_man_holding_hands_medium_skin_tone_dark_skin_tone:':'👩🏽‍🤝‍👨🏿',':woman_and_man_holding_hands_tone3_tone5:':'👩🏽‍🤝‍👨🏿',':woman_and_man_holding_hands_medium_dark_skin_tone_light_skin_tone:':'👩🏾‍🤝‍👨🏻',':woman_and_man_holding_hands_tone4_tone1:':'👩🏾‍🤝‍👨🏻',':woman_and_man_holding_hands_medium_dark_skin_tone_medium_light_skin_tone:':'👩🏾‍🤝‍👨🏼',':woman_and_man_holding_hands_tone4_tone2:':'👩🏾‍🤝‍👨🏼',':woman_and_man_holding_hands_medium_dark_skin_tone_medium_skin_tone:':'👩🏾‍🤝‍👨🏽',':woman_and_man_holding_hands_tone4_tone3:':'👩🏾‍🤝‍👨🏽',':woman_and_man_holding_hands_medium_dark_skin_tone_dark_skin_tone:':'👩🏾‍🤝‍👨🏿',':woman_and_man_holding_hands_tone4_tone5:':'👩🏾‍🤝‍👨🏿',':woman_and_man_holding_hands_dark_skin_tone_light_skin_tone:':'👩🏿‍🤝‍👨🏻',':woman_and_man_holding_hands_tone5_tone1:':'👩🏿‍🤝‍👨🏻',':woman_and_man_holding_hands_dark_skin_tone_medium_light_skin_tone:':'👩🏿‍🤝‍👨🏼',':woman_and_man_holding_hands_tone5_tone2:':'👩🏿‍🤝‍👨🏼',':woman_and_man_holding_hands_dark_skin_tone_medium_skin_tone:':'👩🏿‍🤝‍👨🏽',':woman_and_man_holding_hands_tone5_tone3:':'👩🏿‍🤝‍👨🏽',':woman_and_man_holding_hands_dark_skin_tone_medium_dark_skin_tone:':'👩🏿‍🤝‍👨🏾',':woman_and_man_holding_hands_tone5_tone4:':'👩🏿‍🤝‍👨🏾',':women_holding_hands_medium_light_skin_tone_light_skin_tone:':'👩🏼‍🤝‍👩🏻',':women_holding_hands_tone2_tone1:':'👩🏼‍🤝‍👩🏻',':women_holding_hands_medium_skin_tone_light_skin_tone:':'👩🏽‍🤝‍👩🏻',':women_holding_hands_tone3_tone1:':'👩🏽‍🤝‍👩🏻',':women_holding_hands_medium_skin_tone_medium_light_skin_tone:':'👩🏽‍🤝‍👩🏼',':women_holding_hands_tone3_tone2:':'👩🏽‍🤝‍👩🏼',':women_holding_hands_medium_dark_skin_tone_light_skin_tone:':'👩🏾‍🤝‍👩🏻',':women_holding_hands_tone4_tone1:':'👩🏾‍🤝‍👩🏻',':women_holding_hands_medium_dark_skin_tone_medium_light_skin_tone:':'👩🏾‍🤝‍👩🏼',':women_holding_hands_tone4_tone2:':'👩🏾‍🤝‍👩🏼',':women_holding_hands_medium_dark_skin_tone_medium_skin_tone:':'👩🏾‍🤝‍👩🏽',':women_holding_hands_tone4_tone3:':'👩🏾‍🤝‍👩🏽',':women_holding_hands_dark_skin_tone_light_skin_tone:':'👩🏿‍🤝‍👩🏻',':women_holding_hands_tone5_tone1:':'👩🏿‍🤝‍👩🏻',':women_holding_hands_dark_skin_tone_medium_light_skin_tone:':'👩🏿‍🤝‍👩🏼',':women_holding_hands_tone5_tone2:':'👩🏿‍🤝‍👩🏼',':women_holding_hands_dark_skin_tone_medium_skin_tone:':'👩🏿‍🤝‍👩🏽',':women_holding_hands_tone5_tone3:':'👩🏿‍🤝‍👩🏽',':women_holding_hands_dark_skin_tone_medium_dark_skin_tone:':'👩🏿‍🤝‍👩🏾',':women_holding_hands_tone5_tone4:':'👩🏿‍🤝‍👩🏾',':family_mmbb:':'👨‍👨‍👦‍👦',':family_mmgb:':'👨‍👨‍👧‍👦',':family_mmgg:':'👨‍👨‍👧‍👧',':family_mwbb:':'👨‍👩‍👦‍👦',':family_mwgb:':'👨‍👩‍👧‍👦',':family_mwgg:':'👨‍👩‍👧‍👧',':family_wwbb:':'👩‍👩‍👦‍👦',':family_wwgb:':'👩‍👩‍👧‍👦',':family_wwgg:':'👩‍👩‍👧‍👧',':couplekiss_mm:':'👨‍❤️‍💋👨',':kiss_mm:':'👨‍❤️‍💋👨',':kiss_woman_man:':'👩‍❤️‍💋👨',':couplekiss_ww:':'👩‍❤️‍💋👩',':kiss_ww:':'👩‍❤️‍💋👩',':family_man_boy_boy:':'👨‍👦‍👦',':family_man_girl_boy:':'👨‍👧‍👦',':family_man_girl_girl:':'👨‍👧‍👧',':family_man_woman_boy:':'👨‍👩‍👦',':family_mmb:':'👨‍👨‍👦',':family_mmg:':'👨‍👨‍👧',':family_mwg:':'👨‍👩‍👧',':family_woman_boy_boy:':'👩‍👦‍👦',':family_woman_girl_boy:':'👩‍👧‍👦',':family_woman_girl_girl:':'👩‍👧‍👧',':family_wwb:':'👩‍👩‍👦',':family_wwg:':'👩‍👩‍👧',':man_artist_light_skin_tone:':'👨🏻‍🎨',':man_artist_tone1:':'👨🏻‍🎨',':man_artist_medium_light_skin_tone:':'👨🏼‍🎨',':man_artist_tone2:':'👨🏼‍🎨',':man_artist_medium_skin_tone:':'👨🏽‍🎨',':man_artist_tone3:':'👨🏽‍🎨',':man_artist_medium_dark_skin_tone:':'👨🏾‍🎨',':man_artist_tone4:':'👨🏾‍🎨',':man_artist_dark_skin_tone:':'👨🏿‍🎨',':man_artist_tone5:':'👨🏿‍🎨',':man_astronaut_light_skin_tone:':'👨🏻‍🚀',':man_astronaut_tone1:':'👨🏻‍🚀',':man_astronaut_medium_light_skin_tone:':'👨🏼‍🚀',':man_astronaut_tone2:':'👨🏼‍🚀',':man_astronaut_medium_skin_tone:':'👨🏽‍🚀',':man_astronaut_tone3:':'👨🏽‍🚀',':man_astronaut_medium_dark_skin_tone:':'👨🏾‍🚀',':man_astronaut_tone4:':'👨🏾‍🚀',':man_astronaut_dark_skin_tone:':'👨🏿‍🚀',':man_astronaut_tone5:':'👨🏿‍🚀',':man_bald_light_skin_tone:':'👨🏻‍🦲',':man_bald_tone1:':'👨🏻‍🦲',':man_bald_medium_light_skin_tone:':'👨🏼‍🦲',':man_bald_tone2:':'👨🏼‍🦲',':man_bald_medium_skin_tone:':'👨🏽‍🦲',':man_bald_tone3:':'👨🏽‍🦲',':man_bald_medium_dark_skin_tone:':'👨🏾‍🦲',':man_bald_tone4:':'👨🏾‍🦲',':man_bald_dark_skin_tone:':'👨🏿‍🦲',':man_bald_tone5:':'👨🏿‍🦲',':man_cook_light_skin_tone:':'👨🏻‍🍳',':man_cook_tone1:':'👨🏻‍🍳',':man_cook_medium_light_skin_tone:':'👨🏼‍🍳',':man_cook_tone2:':'👨🏼‍🍳',':man_cook_medium_skin_tone:':'👨🏽‍🍳',':man_cook_tone3:':'👨🏽‍🍳',':man_cook_medium_dark_skin_tone:':'👨🏾‍🍳',':man_cook_tone4:':'👨🏾‍🍳',':man_cook_dark_skin_tone:':'👨🏿‍🍳',':man_cook_tone5:':'👨🏿‍🍳',':man_curly_haired_light_skin_tone:':'👨🏻‍🦱',':man_curly_haired_tone1:':'👨🏻‍🦱',':man_curly_haired_medium_light_skin_tone:':'👨🏼‍🦱',':man_curly_haired_tone2:':'👨🏼‍🦱',':man_curly_haired_medium_skin_tone:':'👨🏽‍🦱',':man_curly_haired_tone3:':'👨🏽‍🦱',':man_curly_haired_medium_dark_skin_tone:':'👨🏾‍🦱',':man_curly_haired_tone4:':'👨🏾‍🦱',':man_curly_haired_dark_skin_tone:':'👨🏿‍🦱',':man_curly_haired_tone5:':'👨🏿‍🦱',':man_factory_worker_light_skin_tone:':'👨🏻‍🏭',':man_factory_worker_tone1:':'👨🏻‍🏭',':man_factory_worker_medium_light_skin_tone:':'👨🏼‍🏭',':man_factory_worker_tone2:':'👨🏼‍🏭',':man_factory_worker_medium_skin_tone:':'👨🏽‍🏭',':man_factory_worker_tone3:':'👨🏽‍🏭',':man_factory_worker_medium_dark_skin_tone:':'👨🏾‍🏭',':man_factory_worker_tone4:':'👨🏾‍🏭',':man_factory_worker_dark_skin_tone:':'👨🏿‍🏭',':man_factory_worker_tone5:':'👨🏿‍🏭',':man_farmer_light_skin_tone:':'👨🏻‍🌾',':man_farmer_tone1:':'👨🏻‍🌾',':man_farmer_medium_light_skin_tone:':'👨🏼‍🌾',':man_farmer_tone2:':'👨🏼‍🌾',':man_farmer_medium_skin_tone:':'👨🏽‍🌾',':man_farmer_tone3:':'👨🏽‍🌾',':man_farmer_medium_dark_skin_tone:':'👨🏾‍🌾',':man_farmer_tone4:':'👨🏾‍🌾',':man_farmer_dark_skin_tone:':'👨🏿‍🌾',':man_farmer_tone5:':'👨🏿‍🌾',':man_firefighter_light_skin_tone:':'👨🏻‍🚒',':man_firefighter_tone1:':'👨🏻‍🚒',':man_firefighter_medium_light_skin_tone:':'👨🏼‍🚒',':man_firefighter_tone2:':'👨🏼‍🚒',':man_firefighter_medium_skin_tone:':'👨🏽‍🚒',':man_firefighter_tone3:':'👨🏽‍🚒',':man_firefighter_medium_dark_skin_tone:':'👨🏾‍🚒',':man_firefighter_tone4:':'👨🏾‍🚒',':man_firefighter_dark_skin_tone:':'👨🏿‍🚒',':man_firefighter_tone5:':'👨🏿‍🚒',':man_in_manual_wheelchair_light_skin_tone:':'👨🏻‍🦽',':man_in_manual_wheelchair_tone1:':'👨🏻‍🦽',':man_in_manual_wheelchair_medium_light_skin_tone:':'👨🏼‍🦽',':man_in_manual_wheelchair_tone2:':'👨🏼‍🦽',':man_in_manual_wheelchair_medium_skin_tone:':'👨🏽‍🦽',':man_in_manual_wheelchair_tone3:':'👨🏽‍🦽',':man_in_manual_wheelchair_medium_dark_skin_tone:':'👨🏾‍🦽',':man_in_manual_wheelchair_tone4:':'👨🏾‍🦽',':man_in_manual_wheelchair_dark_skin_tone:':'👨🏿‍🦽',':man_in_manual_wheelchair_tone5:':'👨🏿‍🦽',':man_in_motorized_wheelchair_light_skin_tone:':'👨🏻‍🦼',':man_in_motorized_wheelchair_tone1:':'👨🏻‍🦼',':man_in_motorized_wheelchair_medium_light_skin_tone:':'👨🏼‍🦼',':man_in_motorized_wheelchair_tone2:':'👨🏼‍🦼',':man_in_motorized_wheelchair_medium_skin_tone:':'👨🏽‍🦼',':man_in_motorized_wheelchair_tone3:':'👨🏽‍🦼',':man_in_motorized_wheelchair_medium_dark_skin_tone:':'👨🏾‍🦼',':man_in_motorized_wheelchair_tone4:':'👨🏾‍🦼',':man_in_motorized_wheelchair_dark_skin_tone:':'👨🏿‍🦼',':man_in_motorized_wheelchair_tone5:':'👨🏿‍🦼',':man_mechanic_light_skin_tone:':'👨🏻‍🔧',':man_mechanic_tone1:':'👨🏻‍🔧',':man_mechanic_medium_light_skin_tone:':'👨🏼‍🔧',':man_mechanic_tone2:':'👨🏼‍🔧',':man_mechanic_medium_skin_tone:':'👨🏽‍🔧',':man_mechanic_tone3:':'👨🏽‍🔧',':man_mechanic_medium_dark_skin_tone:':'👨🏾‍🔧',':man_mechanic_tone4:':'👨🏾‍🔧',':man_mechanic_dark_skin_tone:':'👨🏿‍🔧',':man_mechanic_tone5:':'👨🏿‍🔧',':man_office_worker_light_skin_tone:':'👨🏻‍💼',':man_office_worker_tone1:':'👨🏻‍💼',':man_office_worker_medium_light_skin_tone:':'👨🏼‍💼',':man_office_worker_tone2:':'👨🏼‍💼',':man_office_worker_medium_skin_tone:':'👨🏽‍💼',':man_office_worker_tone3:':'👨🏽‍💼',':man_office_worker_medium_dark_skin_tone:':'👨🏾‍💼',':man_office_worker_tone4:':'👨🏾‍💼',':man_office_worker_dark_skin_tone:':'👨🏿‍💼',':man_office_worker_tone5:':'👨🏿‍💼',':man_red_haired_light_skin_tone:':'👨🏻‍🦰',':man_red_haired_tone1:':'👨🏻‍🦰',':man_red_haired_medium_light_skin_tone:':'👨🏼‍🦰',':man_red_haired_tone2:':'👨🏼‍🦰',':man_red_haired_medium_skin_tone:':'👨🏽‍🦰',':man_red_haired_tone3:':'👨🏽‍🦰',':man_red_haired_medium_dark_skin_tone:':'👨🏾‍🦰',':man_red_haired_tone4:':'👨🏾‍🦰',':man_red_haired_dark_skin_tone:':'👨🏿‍🦰',':man_red_haired_tone5:':'👨🏿‍🦰',':man_scientist_light_skin_tone:':'👨🏻‍🔬',':man_scientist_tone1:':'👨🏻‍🔬',':man_scientist_medium_light_skin_tone:':'👨🏼‍🔬',':man_scientist_tone2:':'👨🏼‍🔬',':man_scientist_medium_skin_tone:':'👨🏽‍🔬',':man_scientist_tone3:':'👨🏽‍🔬',':man_scientist_medium_dark_skin_tone:':'👨🏾‍🔬',':man_scientist_tone4:':'👨🏾‍🔬',':man_scientist_dark_skin_tone:':'👨🏿‍🔬',':man_scientist_tone5:':'👨🏿‍🔬',':man_singer_light_skin_tone:':'👨🏻‍🎤',':man_singer_tone1:':'👨🏻‍🎤',':man_singer_medium_light_skin_tone:':'👨🏼‍🎤',':man_singer_tone2:':'👨🏼‍🎤',':man_singer_medium_skin_tone:':'👨🏽‍🎤',':man_singer_tone3:':'👨🏽‍🎤',':man_singer_medium_dark_skin_tone:':'👨🏾‍🎤',':man_singer_tone4:':'👨🏾‍🎤',':man_singer_dark_skin_tone:':'👨🏿‍🎤',':man_singer_tone5:':'👨🏿‍🎤',':man_student_light_skin_tone:':'👨🏻‍🎓',':man_student_tone1:':'👨🏻‍🎓',':man_student_medium_light_skin_tone:':'👨🏼‍🎓',':man_student_tone2:':'👨🏼‍🎓',':man_student_medium_skin_tone:':'👨🏽‍🎓',':man_student_tone3:':'👨🏽‍🎓',':man_student_medium_dark_skin_tone:':'👨🏾‍🎓',':man_student_tone4:':'👨🏾‍🎓',':man_student_dark_skin_tone:':'👨🏿‍🎓',':man_student_tone5:':'👨🏿‍🎓',':man_teacher_light_skin_tone:':'👨🏻‍🏫',':man_teacher_tone1:':'👨🏻‍🏫',':man_teacher_medium_light_skin_tone:':'👨🏼‍🏫',':man_teacher_tone2:':'👨🏼‍🏫',':man_teacher_medium_skin_tone:':'👨🏽‍🏫',':man_teacher_tone3:':'👨🏽‍🏫',':man_teacher_medium_dark_skin_tone:':'👨🏾‍🏫',':man_teacher_tone4:':'👨🏾‍🏫',':man_teacher_dark_skin_tone:':'👨🏿‍🏫',':man_teacher_tone5:':'👨🏿‍🏫',':man_technologist_light_skin_tone:':'👨🏻‍💻',':man_technologist_tone1:':'👨🏻‍💻',':man_technologist_medium_light_skin_tone:':'👨🏼‍💻',':man_technologist_tone2:':'👨🏼‍💻',':man_technologist_medium_skin_tone:':'👨🏽‍💻',':man_technologist_tone3:':'👨🏽‍💻',':man_technologist_medium_dark_skin_tone:':'👨🏾‍💻',':man_technologist_tone4:':'👨🏾‍💻',':man_technologist_dark_skin_tone:':'👨🏿‍💻',':man_technologist_tone5:':'👨🏿‍💻',':man_white_haired_light_skin_tone:':'👨🏻‍🦳',':man_white_haired_tone1:':'👨🏻‍🦳',':man_white_haired_medium_light_skin_tone:':'👨🏼‍🦳',':man_white_haired_tone2:':'👨🏼‍🦳',':man_white_haired_medium_skin_tone:':'👨🏽‍🦳',':man_white_haired_tone3:':'👨🏽‍🦳',':man_white_haired_medium_dark_skin_tone:':'👨🏾‍🦳',':man_white_haired_tone4:':'👨🏾‍🦳',':man_white_haired_dark_skin_tone:':'👨🏿‍🦳',':man_white_haired_tone5:':'👨🏿‍🦳',':man_with_probing_cane_light_skin_tone:':'👨🏻‍🦯',':man_with_probing_cane_tone1:':'👨🏻‍🦯',':man_with_probing_cane_medium_light_skin_tone:':'👨🏼‍🦯',':man_with_probing_cane_tone2:':'👨🏼‍🦯',':man_with_probing_cane_medium_skin_tone:':'👨🏽‍🦯',':man_with_probing_cane_tone3:':'👨🏽‍🦯',':man_with_probing_cane_medium_dark_skin_tone:':'👨🏾‍🦯',':man_with_probing_cane_tone4:':'👨🏾‍🦯',':man_with_probing_cane_dark_skin_tone:':'👨🏿‍🦯',':man_with_probing_cane_tone5:':'👨🏿‍🦯',':people_holding_hands:':'🧑‍🤝‍🧑',':woman_artist_light_skin_tone:':'👩🏻‍🎨',':woman_artist_tone1:':'👩🏻‍🎨',':woman_artist_medium_light_skin_tone:':'👩🏼‍🎨',':woman_artist_tone2:':'👩🏼‍🎨',':woman_artist_medium_skin_tone:':'👩🏽‍🎨',':woman_artist_tone3:':'👩🏽‍🎨',':woman_artist_medium_dark_skin_tone:':'👩🏾‍🎨',':woman_artist_tone4:':'👩🏾‍🎨',':woman_artist_dark_skin_tone:':'👩🏿‍🎨',':woman_artist_tone5:':'👩🏿‍🎨',':woman_astronaut_light_skin_tone:':'👩🏻‍🚀',':woman_astronaut_tone1:':'👩🏻‍🚀',':woman_astronaut_medium_light_skin_tone:':'👩🏼‍🚀',':woman_astronaut_tone2:':'👩🏼‍🚀',':woman_astronaut_medium_skin_tone:':'👩🏽‍🚀',':woman_astronaut_tone3:':'👩🏽‍🚀',':woman_astronaut_medium_dark_skin_tone:':'👩🏾‍🚀',':woman_astronaut_tone4:':'👩🏾‍🚀',':woman_astronaut_dark_skin_tone:':'👩🏿‍🚀',':woman_astronaut_tone5:':'👩🏿‍🚀',':woman_bald_light_skin_tone:':'👩🏻‍🦲',':woman_bald_tone1:':'👩🏻‍🦲',':woman_bald_medium_light_skin_tone:':'👩🏼‍🦲',':woman_bald_tone2:':'👩🏼‍🦲',':woman_bald_medium_skin_tone:':'👩🏽‍🦲',':woman_bald_tone3:':'👩🏽‍🦲',':woman_bald_medium_dark_skin_tone:':'👩🏾‍🦲',':woman_bald_tone4:':'👩🏾‍🦲',':woman_bald_dark_skin_tone:':'👩🏿‍🦲',':woman_bald_tone5:':'👩🏿‍🦲',':woman_cook_light_skin_tone:':'👩🏻‍🍳',':woman_cook_tone1:':'👩🏻‍🍳',':woman_cook_medium_light_skin_tone:':'👩🏼‍🍳',':woman_cook_tone2:':'👩🏼‍🍳',':woman_cook_medium_skin_tone:':'👩🏽‍🍳',':woman_cook_tone3:':'👩🏽‍🍳',':woman_cook_medium_dark_skin_tone:':'👩🏾‍🍳',':woman_cook_tone4:':'👩🏾‍🍳',':woman_cook_dark_skin_tone:':'👩🏿‍🍳',':woman_cook_tone5:':'👩🏿‍🍳',':woman_curly_haired_light_skin_tone:':'👩🏻‍🦱',':woman_curly_haired_tone1:':'👩🏻‍🦱',':woman_curly_haired_medium_light_skin_tone:':'👩🏼‍🦱',':woman_curly_haired_tone2:':'👩🏼‍🦱',':woman_curly_haired_medium_skin_tone:':'👩🏽‍🦱',':woman_curly_haired_tone3:':'👩🏽‍🦱',':woman_curly_haired_medium_dark_skin_tone:':'👩🏾‍🦱',':woman_curly_haired_tone4:':'👩🏾‍🦱',':woman_curly_haired_dark_skin_tone:':'👩🏿‍🦱',':woman_curly_haired_tone5:':'👩🏿‍🦱',':woman_factory_worker_light_skin_tone:':'👩🏻‍🏭',':woman_factory_worker_tone1:':'👩🏻‍🏭',':woman_factory_worker_medium_light_skin_tone:':'👩🏼‍🏭',':woman_factory_worker_tone2:':'👩🏼‍🏭',':woman_factory_worker_medium_skin_tone:':'👩🏽‍🏭',':woman_factory_worker_tone3:':'👩🏽‍🏭',':woman_factory_worker_medium_dark_skin_tone:':'👩🏾‍🏭',':woman_factory_worker_tone4:':'👩🏾‍🏭',':woman_factory_worker_dark_skin_tone:':'👩🏿‍🏭',':woman_factory_worker_tone5:':'👩🏿‍🏭',':woman_farmer_light_skin_tone:':'👩🏻‍🌾',':woman_farmer_tone1:':'👩🏻‍🌾',':woman_farmer_medium_light_skin_tone:':'👩🏼‍🌾',':woman_farmer_tone2:':'👩🏼‍🌾',':woman_farmer_medium_skin_tone:':'👩🏽‍🌾',':woman_farmer_tone3:':'👩🏽‍🌾',':woman_farmer_medium_dark_skin_tone:':'👩🏾‍🌾',':woman_farmer_tone4:':'👩🏾‍🌾',':woman_farmer_dark_skin_tone:':'👩🏿‍🌾',':woman_farmer_tone5:':'👩🏿‍🌾',':woman_firefighter_light_skin_tone:':'👩🏻‍🚒',':woman_firefighter_tone1:':'👩🏻‍🚒',':woman_firefighter_medium_light_skin_tone:':'👩🏼‍🚒',':woman_firefighter_tone2:':'👩🏼‍🚒',':woman_firefighter_medium_skin_tone:':'👩🏽‍🚒',':woman_firefighter_tone3:':'👩🏽‍🚒',':woman_firefighter_medium_dark_skin_tone:':'👩🏾‍🚒',':woman_firefighter_tone4:':'👩🏾‍🚒',':woman_firefighter_dark_skin_tone:':'👩🏿‍🚒',':woman_firefighter_tone5:':'👩🏿‍🚒',':woman_in_manual_wheelchair_light_skin_tone:':'👩🏻‍🦽',':woman_in_manual_wheelchair_tone1:':'👩🏻‍🦽',':woman_in_manual_wheelchair_medium_light_skin_tone:':'👩🏼‍🦽',':woman_in_manual_wheelchair_tone2:':'👩🏼‍🦽',':woman_in_manual_wheelchair_medium_skin_tone:':'👩🏽‍🦽',':woman_in_manual_wheelchair_tone3:':'👩🏽‍🦽',':woman_in_manual_wheelchair_medium_dark_skin_tone:':'👩🏾‍🦽',':woman_in_manual_wheelchair_tone4:':'👩🏾‍🦽',':woman_in_manual_wheelchair_dark_skin_tone:':'👩🏿‍🦽',':woman_in_manual_wheelchair_tone5:':'👩🏿‍🦽',':woman_in_motorized_wheelchair_light_skin_tone:':'👩🏻‍🦼',':woman_in_motorized_wheelchair_tone1:':'👩🏻‍🦼',':woman_in_motorized_wheelchair_medium_light_skin_tone:':'👩🏼‍🦼',':woman_in_motorized_wheelchair_tone2:':'👩🏼‍🦼',':woman_in_motorized_wheelchair_medium_skin_tone:':'👩🏽‍🦼',':woman_in_motorized_wheelchair_tone3:':'👩🏽‍🦼',':woman_in_motorized_wheelchair_medium_dark_skin_tone:':'👩🏾‍🦼',':woman_in_motorized_wheelchair_tone4:':'👩🏾‍🦼',':woman_in_motorized_wheelchair_dark_skin_tone:':'👩🏿‍🦼',':woman_in_motorized_wheelchair_tone5:':'👩🏿‍🦼',':woman_mechanic_light_skin_tone:':'👩🏻‍🔧',':woman_mechanic_tone1:':'👩🏻‍🔧',':woman_mechanic_medium_light_skin_tone:':'👩🏼‍🔧',':woman_mechanic_tone2:':'👩🏼‍🔧',':woman_mechanic_medium_skin_tone:':'👩🏽‍🔧',':woman_mechanic_tone3:':'👩🏽‍🔧',':woman_mechanic_medium_dark_skin_tone:':'👩🏾‍🔧',':woman_mechanic_tone4:':'👩🏾‍🔧',':woman_mechanic_dark_skin_tone:':'👩🏿‍🔧',':woman_mechanic_tone5:':'👩🏿‍🔧',':woman_office_worker_light_skin_tone:':'👩🏻‍💼',':woman_office_worker_tone1:':'👩🏻‍💼',':woman_office_worker_medium_light_skin_tone:':'👩🏼‍💼',':woman_office_worker_tone2:':'👩🏼‍💼',':woman_office_worker_medium_skin_tone:':'👩🏽‍💼',':woman_office_worker_tone3:':'👩🏽‍💼',':woman_office_worker_medium_dark_skin_tone:':'👩🏾‍💼',':woman_office_worker_tone4:':'👩🏾‍💼',':woman_office_worker_dark_skin_tone:':'👩🏿‍💼',':woman_office_worker_tone5:':'👩🏿‍💼',':woman_red_haired_light_skin_tone:':'👩🏻‍🦰',':woman_red_haired_tone1:':'👩🏻‍🦰',':woman_red_haired_medium_light_skin_tone:':'👩🏼‍🦰',':woman_red_haired_tone2:':'👩🏼‍🦰',':woman_red_haired_medium_skin_tone:':'👩🏽‍🦰',':woman_red_haired_tone3:':'👩🏽‍🦰',':woman_red_haired_medium_dark_skin_tone:':'👩🏾‍🦰',':woman_red_haired_tone4:':'👩🏾‍🦰',':woman_red_haired_dark_skin_tone:':'👩🏿‍🦰',':woman_red_haired_tone5:':'👩🏿‍🦰',':woman_scientist_light_skin_tone:':'👩🏻‍🔬',':woman_scientist_tone1:':'👩🏻‍🔬',':woman_scientist_medium_light_skin_tone:':'👩🏼‍🔬',':woman_scientist_tone2:':'👩🏼‍🔬',':woman_scientist_medium_skin_tone:':'👩🏽‍🔬',':woman_scientist_tone3:':'👩🏽‍🔬',':woman_scientist_medium_dark_skin_tone:':'👩🏾‍🔬',':woman_scientist_tone4:':'👩🏾‍🔬',':woman_scientist_dark_skin_tone:':'👩🏿‍🔬',':woman_scientist_tone5:':'👩🏿‍🔬',':woman_singer_light_skin_tone:':'👩🏻‍🎤',':woman_singer_tone1:':'👩🏻‍🎤',':woman_singer_medium_light_skin_tone:':'👩🏼‍🎤',':woman_singer_tone2:':'👩🏼‍🎤',':woman_singer_medium_skin_tone:':'👩🏽‍🎤',':woman_singer_tone3:':'👩🏽‍🎤',':woman_singer_medium_dark_skin_tone:':'👩🏾‍🎤',':woman_singer_tone4:':'👩🏾‍🎤',':woman_singer_dark_skin_tone:':'👩🏿‍🎤',':woman_singer_tone5:':'👩🏿‍🎤',':woman_student_light_skin_tone:':'👩🏻‍🎓',':woman_student_tone1:':'👩🏻‍🎓',':woman_student_medium_light_skin_tone:':'👩🏼‍🎓',':woman_student_tone2:':'👩🏼‍🎓',':woman_student_medium_skin_tone:':'👩🏽‍🎓',':woman_student_tone3:':'👩🏽‍🎓',':woman_student_medium_dark_skin_tone:':'👩🏾‍🎓',':woman_student_tone4:':'👩🏾‍🎓',':woman_student_dark_skin_tone:':'👩🏿‍🎓',':woman_student_tone5:':'👩🏿‍🎓',':woman_teacher_light_skin_tone:':'👩🏻‍🏫',':woman_teacher_tone1:':'👩🏻‍🏫',':woman_teacher_medium_light_skin_tone:':'👩🏼‍🏫',':woman_teacher_tone2:':'👩🏼‍🏫',':woman_teacher_medium_skin_tone:':'👩🏽‍🏫',':woman_teacher_tone3:':'👩🏽‍🏫',':woman_teacher_medium_dark_skin_tone:':'👩🏾‍🏫',':woman_teacher_tone4:':'👩🏾‍🏫',':woman_teacher_dark_skin_tone:':'👩🏿‍🏫',':woman_teacher_tone5:':'👩🏿‍🏫',':woman_technologist_light_skin_tone:':'👩🏻‍💻',':woman_technologist_tone1:':'👩🏻‍💻',':woman_technologist_medium_light_skin_tone:':'👩🏼‍💻',':woman_technologist_tone2:':'👩🏼‍💻',':woman_technologist_medium_skin_tone:':'👩🏽‍💻',':woman_technologist_tone3:':'👩🏽‍💻',':woman_technologist_medium_dark_skin_tone:':'👩🏾‍💻',':woman_technologist_tone4:':'👩🏾‍💻',':woman_technologist_dark_skin_tone:':'👩🏿‍💻',':woman_technologist_tone5:':'👩🏿‍💻',':woman_white_haired_light_skin_tone:':'👩🏻‍🦳',':woman_white_haired_tone1:':'👩🏻‍🦳',':woman_white_haired_medium_light_skin_tone:':'👩🏼‍🦳',':woman_white_haired_tone2:':'👩🏼‍🦳',':woman_white_haired_medium_skin_tone:':'👩🏽‍🦳',':woman_white_haired_tone3:':'👩🏽‍🦳',':woman_white_haired_medium_dark_skin_tone:':'👩🏾‍🦳',':woman_white_haired_tone4:':'👩🏾‍🦳',':woman_white_haired_dark_skin_tone:':'👩🏿‍🦳',':woman_white_haired_tone5:':'👩🏿‍🦳',':woman_with_probing_cane_light_skin_tone:':'👩🏻‍🦯',':woman_with_probing_cane_tone1:':'👩🏻‍🦯',':woman_with_probing_cane_medium_light_skin_tone:':'👩🏼‍🦯',':woman_with_probing_cane_tone2:':'👩🏼‍🦯',':woman_with_probing_cane_medium_skin_tone:':'👩🏽‍🦯',':woman_with_probing_cane_tone3:':'👩🏽‍🦯',':woman_with_probing_cane_medium_dark_skin_tone:':'👩🏾‍🦯',':woman_with_probing_cane_tone4:':'👩🏾‍🦯',':woman_with_probing_cane_dark_skin_tone:':'👩🏿‍🦯',':woman_with_probing_cane_tone5:':'👩🏿‍🦯',':blond-haired_man_light_skin_tone:':'👱🏻‍♂️',':blond-haired_man_tone1:':'👱🏻‍♂️',':blond-haired_man_medium_light_skin_tone:':'👱🏼‍♂️',':blond-haired_man_tone2:':'👱🏼‍♂️',':blond-haired_man_medium_skin_tone:':'👱🏽‍♂️',':blond-haired_man_tone3:':'👱🏽‍♂️',':blond-haired_man_medium_dark_skin_tone:':'👱🏾‍♂️',':blond-haired_man_tone4:':'👱🏾‍♂️',':blond-haired_man_dark_skin_tone:':'👱🏿‍♂️',':blond-haired_man_tone5:':'👱🏿‍♂️',':blond-haired_woman_light_skin_tone:':'👱🏻‍♀️',':blond-haired_woman_tone1:':'👱🏻‍♀️',':blond-haired_woman_medium_light_skin_tone:':'👱🏼‍♀️',':blond-haired_woman_tone2:':'👱🏼‍♀️',':blond-haired_woman_medium_skin_tone:':'👱🏽‍♀️',':blond-haired_woman_tone3:':'👱🏽‍♀️',':blond-haired_woman_medium_dark_skin_tone:':'👱🏾‍♀️',':blond-haired_woman_tone4:':'👱🏾‍♀️',':blond-haired_woman_dark_skin_tone:':'👱🏿‍♀️',':blond-haired_woman_tone5:':'👱🏿‍♀️',':couple_with_heart_mm:':'👨‍❤️‍👨',':couple_mm:':'👨‍❤️‍👨',':couple_with_heart_woman_man:':'👩‍❤️‍👨',':couple_with_heart_ww:':'👩‍❤️‍👩',':couple_ww:':'👩‍❤️‍👩',':deaf_man_light_skin_tone:':'🧏🏻‍♂️',':deaf_man_tone1:':'🧏🏻‍♂️',':deaf_man_medium_light_skin_tone:':'🧏🏼‍♂️',':deaf_man_tone2:':'🧏🏼‍♂️',':deaf_man_medium_skin_tone:':'🧏🏽‍♂️',':deaf_man_tone3:':'🧏🏽‍♂️',':deaf_man_medium_dark_skin_tone:':'🧏🏾‍♂️',':deaf_man_tone4:':'🧏🏾‍♂️',':deaf_man_dark_skin_tone:':'🧏🏿‍♂️',':deaf_man_tone5:':'🧏🏿‍♂️',':deaf_woman_light_skin_tone:':'🧏🏻‍♀️',':deaf_woman_tone1:':'🧏🏻‍♀️',':deaf_woman_medium_light_skin_tone:':'🧏🏼‍♀️',':deaf_woman_tone2:':'🧏🏼‍♀️',':deaf_woman_medium_skin_tone:':'🧏🏽‍♀️',':deaf_woman_tone3:':'🧏🏽‍♀️',':deaf_woman_medium_dark_skin_tone:':'🧏🏾‍♀️',':deaf_woman_tone4:':'🧏🏾‍♀️',':deaf_woman_dark_skin_tone:':'🧏🏿‍♀️',':deaf_woman_tone5:':'🧏🏿‍♀️',':man_biking_light_skin_tone:':'🚴🏻‍♂️',':man_biking_tone1:':'🚴🏻‍♂️',':man_biking_medium_light_skin_tone:':'🚴🏼‍♂️',':man_biking_tone2:':'🚴🏼‍♂️',':man_biking_medium_skin_tone:':'🚴🏽‍♂️',':man_biking_tone3:':'🚴🏽‍♂️',':man_biking_medium_dark_skin_tone:':'🚴🏾‍♂️',':man_biking_tone4:':'🚴🏾‍♂️',':man_biking_dark_skin_tone:':'🚴🏿‍♂️',':man_biking_tone5:':'🚴🏿‍♂️',':man_bowing_light_skin_tone:':'🙇🏻‍♂️',':man_bowing_tone1:':'🙇🏻‍♂️',':man_bowing_medium_light_skin_tone:':'🙇🏼‍♂️',':man_bowing_tone2:':'🙇🏼‍♂️',':man_bowing_medium_skin_tone:':'🙇🏽‍♂️',':man_bowing_tone3:':'🙇🏽‍♂️',':man_bowing_medium_dark_skin_tone:':'🙇🏾‍♂️',':man_bowing_tone4:':'🙇🏾‍♂️',':man_bowing_dark_skin_tone:':'🙇🏿‍♂️',':man_bowing_tone5:':'🙇🏿‍♂️',':man_cartwheeling_light_skin_tone:':'🤸🏻‍♂️',':man_cartwheeling_tone1:':'🤸🏻‍♂️',':man_cartwheeling_medium_light_skin_tone:':'🤸🏼‍♂️',':man_cartwheeling_tone2:':'🤸🏼‍♂️',':man_cartwheeling_medium_skin_tone:':'🤸🏽‍♂️',':man_cartwheeling_tone3:':'🤸🏽‍♂️',':man_cartwheeling_medium_dark_skin_tone:':'🤸🏾‍♂️',':man_cartwheeling_tone4:':'🤸🏾‍♂️',':man_cartwheeling_dark_skin_tone:':'🤸🏿‍♂️',':man_cartwheeling_tone5:':'🤸🏿‍♂️',':man_climbing_light_skin_tone:':'🧗🏻‍♂️',':man_climbing_tone1:':'🧗🏻‍♂️',':man_climbing_medium_light_skin_tone:':'🧗🏼‍♂️',':man_climbing_tone2:':'🧗🏼‍♂️',':man_climbing_medium_skin_tone:':'🧗🏽‍♂️',':man_climbing_tone3:':'🧗🏽‍♂️',':man_climbing_medium_dark_skin_tone:':'🧗🏾‍♂️',':man_climbing_tone4:':'🧗🏾‍♂️',':man_climbing_dark_skin_tone:':'🧗🏿‍♂️',':man_climbing_tone5:':'🧗🏿‍♂️',':man_construction_worker_light_skin_tone:':'👷🏻‍♂️',':man_construction_worker_tone1:':'👷🏻‍♂️',':man_construction_worker_medium_light_skin_tone:':'👷🏼‍♂️',':man_construction_worker_tone2:':'👷🏼‍♂️',':man_construction_worker_medium_skin_tone:':'👷🏽‍♂️',':man_construction_worker_tone3:':'👷🏽‍♂️',':man_construction_worker_medium_dark_skin_tone:':'👷🏾‍♂️',':man_construction_worker_tone4:':'👷🏾‍♂️',':man_construction_worker_dark_skin_tone:':'👷🏿‍♂️',':man_construction_worker_tone5:':'👷🏿‍♂️',':man_detective_light_skin_tone:':'🕵️🏻‍♂️',':man_detective_tone1:':'🕵️🏻‍♂️',':man_detective_medium_light_skin_tone:':'🕵️🏼‍♂️',':man_detective_tone2:':'🕵️🏼‍♂️',':man_detective_medium_skin_tone:':'🕵️🏽‍♂️',':man_detective_tone3:':'🕵️🏽‍♂️',':man_detective_medium_dark_skin_tone:':'🕵️🏾‍♂️',':man_detective_tone4:':'🕵️🏾‍♂️',':man_detective_dark_skin_tone:':'🕵️🏿‍♂️',':man_detective_tone5:':'🕵️🏿‍♂️',':man_elf_light_skin_tone:':'🧝🏻‍♂️',':man_elf_tone1:':'🧝🏻‍♂️',':man_elf_medium_light_skin_tone:':'🧝🏼‍♂️',':man_elf_tone2:':'🧝🏼‍♂️',':man_elf_medium_skin_tone:':'🧝🏽‍♂️',':man_elf_tone3:':'🧝🏽‍♂️',':man_elf_medium_dark_skin_tone:':'🧝🏾‍♂️',':man_elf_tone4:':'🧝🏾‍♂️',':man_elf_dark_skin_tone:':'🧝🏿‍♂️',':man_elf_tone5:':'🧝🏿‍♂️',':man_facepalming_light_skin_tone:':'🤦🏻‍♂️',':man_facepalming_tone1:':'🤦🏻‍♂️',':man_facepalming_medium_light_skin_tone:':'🤦🏼‍♂️',':man_facepalming_tone2:':'🤦🏼‍♂️',':man_facepalming_medium_skin_tone:':'🤦🏽‍♂️',':man_facepalming_tone3:':'🤦🏽‍♂️',':man_facepalming_medium_dark_skin_tone:':'🤦🏾‍♂️',':man_facepalming_tone4:':'🤦🏾‍♂️',':man_facepalming_dark_skin_tone:':'🤦🏿‍♂️',':man_facepalming_tone5:':'🤦🏿‍♂️',':man_fairy_light_skin_tone:':'🧚🏻‍♂️',':man_fairy_tone1:':'🧚🏻‍♂️',':man_fairy_medium_light_skin_tone:':'🧚🏼‍♂️',':man_fairy_tone2:':'🧚🏼‍♂️',':man_fairy_medium_skin_tone:':'🧚🏽‍♂️',':man_fairy_tone3:':'🧚🏽‍♂️',':man_fairy_medium_dark_skin_tone:':'🧚🏾‍♂️',':man_fairy_tone4:':'🧚🏾‍♂️',':man_fairy_dark_skin_tone:':'🧚🏿‍♂️',':man_fairy_tone5:':'🧚🏿‍♂️',':man_frowning_light_skin_tone:':'🙍🏻‍♂️',':man_frowning_tone1:':'🙍🏻‍♂️',':man_frowning_medium_light_skin_tone:':'🙍🏼‍♂️',':man_frowning_tone2:':'🙍🏼‍♂️',':man_frowning_medium_skin_tone:':'🙍🏽‍♂️',':man_frowning_tone3:':'🙍🏽‍♂️',':man_frowning_medium_dark_skin_tone:':'🙍🏾‍♂️',':man_frowning_tone4:':'🙍🏾‍♂️',':man_frowning_dark_skin_tone:':'🙍🏿‍♂️',':man_frowning_tone5:':'🙍🏿‍♂️',':man_gesturing_no_light_skin_tone:':'🙅🏻‍♂️',':man_gesturing_no_tone1:':'🙅🏻‍♂️',':man_gesturing_no_medium_light_skin_tone:':'🙅🏼‍♂️',':man_gesturing_no_tone2:':'🙅🏼‍♂️',':man_gesturing_no_medium_skin_tone:':'🙅🏽‍♂️',':man_gesturing_no_tone3:':'🙅🏽‍♂️',':man_gesturing_no_medium_dark_skin_tone:':'🙅🏾‍♂️',':man_gesturing_no_tone4:':'🙅🏾‍♂️',':man_gesturing_no_dark_skin_tone:':'🙅🏿‍♂️',':man_gesturing_no_tone5:':'🙅🏿‍♂️',':man_gesturing_ok_light_skin_tone:':'🙆🏻‍♂️',':man_gesturing_ok_tone1:':'🙆🏻‍♂️',':man_gesturing_ok_medium_light_skin_tone:':'🙆🏼‍♂️',':man_gesturing_ok_tone2:':'🙆🏼‍♂️',':man_gesturing_ok_medium_skin_tone:':'🙆🏽‍♂️',':man_gesturing_ok_tone3:':'🙆🏽‍♂️',':man_gesturing_ok_medium_dark_skin_tone:':'🙆🏾‍♂️',':man_gesturing_ok_tone4:':'🙆🏾‍♂️',':man_gesturing_ok_dark_skin_tone:':'🙆🏿‍♂️',':man_gesturing_ok_tone5:':'🙆🏿‍♂️',':man_getting_face_massage_light_skin_tone:':'💆🏻‍♂️',':man_getting_face_massage_tone1:':'💆🏻‍♂️',':man_getting_face_massage_medium_light_skin_tone:':'💆🏼‍♂️',':man_getting_face_massage_tone2:':'💆🏼‍♂️',':man_getting_face_massage_medium_skin_tone:':'💆🏽‍♂️',':man_getting_face_massage_tone3:':'💆🏽‍♂️',':man_getting_face_massage_medium_dark_skin_tone:':'💆🏾‍♂️',':man_getting_face_massage_tone4:':'💆🏾‍♂️',':man_getting_face_massage_dark_skin_tone:':'💆🏿‍♂️',':man_getting_face_massage_tone5:':'💆🏿‍♂️',':man_getting_haircut_light_skin_tone:':'💇🏻‍♂️',':man_getting_haircut_tone1:':'💇🏻‍♂️',':man_getting_haircut_medium_light_skin_tone:':'💇🏼‍♂️',':man_getting_haircut_tone2:':'💇🏼‍♂️',':man_getting_haircut_medium_skin_tone:':'💇🏽‍♂️',':man_getting_haircut_tone3:':'💇🏽‍♂️',':man_getting_haircut_medium_dark_skin_tone:':'💇🏾‍♂️',':man_getting_haircut_tone4:':'💇🏾‍♂️',':man_getting_haircut_dark_skin_tone:':'💇🏿‍♂️',':man_getting_haircut_tone5:':'💇🏿‍♂️',':man_golfing_light_skin_tone:':'🏌️🏻‍♂️',':man_golfing_tone1:':'🏌️🏻‍♂️',':man_golfing_medium_light_skin_tone:':'🏌️🏼‍♂️',':man_golfing_tone2:':'🏌️🏼‍♂️',':man_golfing_medium_skin_tone:':'🏌️🏽‍♂️',':man_golfing_tone3:':'🏌️🏽‍♂️',':man_golfing_medium_dark_skin_tone:':'🏌️🏾‍♂️',':man_golfing_tone4:':'🏌️🏾‍♂️',':man_golfing_dark_skin_tone:':'🏌️🏿‍♂️',':man_golfing_tone5:':'🏌️🏿‍♂️',':man_guard_light_skin_tone:':'💂🏻‍♂️',':man_guard_tone1:':'💂🏻‍♂️',':man_guard_medium_light_skin_tone:':'💂🏼‍♂️',':man_guard_tone2:':'💂🏼‍♂️',':man_guard_medium_skin_tone:':'💂🏽‍♂️',':man_guard_tone3:':'💂🏽‍♂️',':man_guard_medium_dark_skin_tone:':'💂🏾‍♂️',':man_guard_tone4:':'💂🏾‍♂️',':man_guard_dark_skin_tone:':'💂🏿‍♂️',':man_guard_tone5:':'💂🏿‍♂️',':man_health_worker_light_skin_tone:':'👨🏻‍⚕️',':man_health_worker_tone1:':'👨🏻‍⚕️',':man_health_worker_medium_light_skin_tone:':'👨🏼‍⚕️',':man_health_worker_tone2:':'👨🏼‍⚕️',':man_health_worker_medium_skin_tone:':'👨🏽‍⚕️',':man_health_worker_tone3:':'👨🏽‍⚕️',':man_health_worker_medium_dark_skin_tone:':'👨🏾‍⚕️',':man_health_worker_tone4:':'👨🏾‍⚕️',':man_health_worker_dark_skin_tone:':'👨🏿‍⚕️',':man_health_worker_tone5:':'👨🏿‍⚕️',':man_in_lotus_position_light_skin_tone:':'🧘🏻‍♂️',':man_in_lotus_position_tone1:':'🧘🏻‍♂️',':man_in_lotus_position_medium_light_skin_tone:':'🧘🏼‍♂️',':man_in_lotus_position_tone2:':'🧘🏼‍♂️',':man_in_lotus_position_medium_skin_tone:':'🧘🏽‍♂️',':man_in_lotus_position_tone3:':'🧘🏽‍♂️',':man_in_lotus_position_medium_dark_skin_tone:':'🧘🏾‍♂️',':man_in_lotus_position_tone4:':'🧘🏾‍♂️',':man_in_lotus_position_dark_skin_tone:':'🧘🏿‍♂️',':man_in_lotus_position_tone5:':'🧘🏿‍♂️',':man_in_steamy_room_light_skin_tone:':'🧖🏻‍♂️',':man_in_steamy_room_tone1:':'🧖🏻‍♂️',':man_in_steamy_room_medium_light_skin_tone:':'🧖🏼‍♂️',':man_in_steamy_room_tone2:':'🧖🏼‍♂️',':man_in_steamy_room_medium_skin_tone:':'🧖🏽‍♂️',':man_in_steamy_room_tone3:':'🧖🏽‍♂️',':man_in_steamy_room_medium_dark_skin_tone:':'🧖🏾‍♂️',':man_in_steamy_room_tone4:':'🧖🏾‍♂️',':man_in_steamy_room_dark_skin_tone:':'🧖🏿‍♂️',':man_in_steamy_room_tone5:':'🧖🏿‍♂️',':man_judge_light_skin_tone:':'👨🏻‍⚖️',':man_judge_tone1:':'👨🏻‍⚖️',':man_judge_medium_light_skin_tone:':'👨🏼‍⚖️',':man_judge_tone2:':'👨🏼‍⚖️',':man_judge_medium_skin_tone:':'👨🏽‍⚖️',':man_judge_tone3:':'👨🏽‍⚖️',':man_judge_medium_dark_skin_tone:':'👨🏾‍⚖️',':man_judge_tone4:':'👨🏾‍⚖️',':man_judge_dark_skin_tone:':'👨🏿‍⚖️',':man_judge_tone5:':'👨🏿‍⚖️',':man_juggling_light_skin_tone:':'🤹🏻‍♂️',':man_juggling_tone1:':'🤹🏻‍♂️',':man_juggling_medium_light_skin_tone:':'🤹🏼‍♂️',':man_juggling_tone2:':'🤹🏼‍♂️',':man_juggling_medium_skin_tone:':'🤹🏽‍♂️',':man_juggling_tone3:':'🤹🏽‍♂️',':man_juggling_medium_dark_skin_tone:':'🤹🏾‍♂️',':man_juggling_tone4:':'🤹🏾‍♂️',':man_juggling_dark_skin_tone:':'🤹🏿‍♂️',':man_juggling_tone5:':'🤹🏿‍♂️',':man_kneeling_light_skin_tone:':'🧎🏻‍♂️',':man_kneeling_tone1:':'🧎🏻‍♂️',':man_kneeling_medium_light_skin_tone:':'🧎🏼‍♂️',':man_kneeling_tone2:':'🧎🏼‍♂️',':man_kneeling_medium_skin_tone:':'🧎🏽‍♂️',':man_kneeling_tone3:':'🧎🏽‍♂️',':man_kneeling_medium_dark_skin_tone:':'🧎🏾‍♂️',':man_kneeling_tone4:':'🧎🏾‍♂️',':man_kneeling_dark_skin_tone:':'🧎🏿‍♂️',':man_kneeling_tone5:':'🧎🏿‍♂️',':man_lifting_weights_light_skin_tone:':'🏋️🏻‍♂️',':man_lifting_weights_tone1:':'🏋️🏻‍♂️',':man_lifting_weights_medium_light_skin_tone:':'🏋️🏼‍♂️',':man_lifting_weights_tone2:':'🏋️🏼‍♂️',':man_lifting_weights_medium_skin_tone:':'🏋️🏽‍♂️',':man_lifting_weights_tone3:':'🏋️🏽‍♂️',':man_lifting_weights_medium_dark_skin_tone:':'🏋️🏾‍♂️',':man_lifting_weights_tone4:':'🏋️🏾‍♂️',':man_lifting_weights_dark_skin_tone:':'🏋️🏿‍♂️',':man_lifting_weights_tone5:':'🏋️🏿‍♂️',':man_mage_light_skin_tone:':'🧙🏻‍♂️',':man_mage_tone1:':'🧙🏻‍♂️',':man_mage_medium_light_skin_tone:':'🧙🏼‍♂️',':man_mage_tone2:':'🧙🏼‍♂️',':man_mage_medium_skin_tone:':'🧙🏽‍♂️',':man_mage_tone3:':'🧙🏽‍♂️',':man_mage_medium_dark_skin_tone:':'🧙🏾‍♂️',':man_mage_tone4:':'🧙🏾‍♂️',':man_mage_dark_skin_tone:':'🧙🏿‍♂️',':man_mage_tone5:':'🧙🏿‍♂️',':man_mountain_biking_light_skin_tone:':'🚵🏻‍♂️',':man_mountain_biking_tone1:':'🚵🏻‍♂️',':man_mountain_biking_medium_light_skin_tone:':'🚵🏼‍♂️',':man_mountain_biking_tone2:':'🚵🏼‍♂️',':man_mountain_biking_medium_skin_tone:':'🚵🏽‍♂️',':man_mountain_biking_tone3:':'🚵🏽‍♂️',':man_mountain_biking_medium_dark_skin_tone:':'🚵🏾‍♂️',':man_mountain_biking_tone4:':'🚵🏾‍♂️',':man_mountain_biking_dark_skin_tone:':'🚵🏿‍♂️',':man_mountain_biking_tone5:':'🚵🏿‍♂️',':man_pilot_light_skin_tone:':'👨🏻‍✈️',':man_pilot_tone1:':'👨🏻‍✈️',':man_pilot_medium_light_skin_tone:':'👨🏼‍✈️',':man_pilot_tone2:':'👨🏼‍✈️',':man_pilot_medium_skin_tone:':'👨🏽‍✈️',':man_pilot_tone3:':'👨🏽‍✈️',':man_pilot_medium_dark_skin_tone:':'👨🏾‍✈️',':man_pilot_tone4:':'👨🏾‍✈️',':man_pilot_dark_skin_tone:':'👨🏿‍✈️',':man_pilot_tone5:':'👨🏿‍✈️',':man_playing_handball_light_skin_tone:':'🤾🏻‍♂️',':man_playing_handball_tone1:':'🤾🏻‍♂️',':man_playing_handball_medium_light_skin_tone:':'🤾🏼‍♂️',':man_playing_handball_tone2:':'🤾🏼‍♂️',':man_playing_handball_medium_skin_tone:':'🤾🏽‍♂️',':man_playing_handball_tone3:':'🤾🏽‍♂️',':man_playing_handball_medium_dark_skin_tone:':'🤾🏾‍♂️',':man_playing_handball_tone4:':'🤾🏾‍♂️',':man_playing_handball_dark_skin_tone:':'🤾🏿‍♂️',':man_playing_handball_tone5:':'🤾🏿‍♂️',':man_playing_water_polo_light_skin_tone:':'🤽🏻‍♂️',':man_playing_water_polo_tone1:':'🤽🏻‍♂️',':man_playing_water_polo_medium_light_skin_tone:':'🤽🏼‍♂️',':man_playing_water_polo_tone2:':'🤽🏼‍♂️',':man_playing_water_polo_medium_skin_tone:':'🤽🏽‍♂️',':man_playing_water_polo_tone3:':'🤽🏽‍♂️',':man_playing_water_polo_medium_dark_skin_tone:':'🤽🏾‍♂️',':man_playing_water_polo_tone4:':'🤽🏾‍♂️',':man_playing_water_polo_dark_skin_tone:':'🤽🏿‍♂️',':man_playing_water_polo_tone5:':'🤽🏿‍♂️',':man_police_officer_light_skin_tone:':'👮🏻‍♂️',':man_police_officer_tone1:':'👮🏻‍♂️',':man_police_officer_medium_light_skin_tone:':'👮🏼‍♂️',':man_police_officer_tone2:':'👮🏼‍♂️',':man_police_officer_medium_skin_tone:':'👮🏽‍♂️',':man_police_officer_tone3:':'👮🏽‍♂️',':man_police_officer_medium_dark_skin_tone:':'👮🏾‍♂️',':man_police_officer_tone4:':'👮🏾‍♂️',':man_police_officer_dark_skin_tone:':'👮🏿‍♂️',':man_police_officer_tone5:':'👮🏿‍♂️',':man_pouting_light_skin_tone:':'🙎🏻‍♂️',':man_pouting_tone1:':'🙎🏻‍♂️',':man_pouting_medium_light_skin_tone:':'🙎🏼‍♂️',':man_pouting_tone2:':'🙎🏼‍♂️',':man_pouting_medium_skin_tone:':'🙎🏽‍♂️',':man_pouting_tone3:':'🙎🏽‍♂️',':man_pouting_medium_dark_skin_tone:':'🙎🏾‍♂️',':man_pouting_tone4:':'🙎🏾‍♂️',':man_pouting_dark_skin_tone:':'🙎🏿‍♂️',':man_pouting_tone5:':'🙎🏿‍♂️',':man_raising_hand_light_skin_tone:':'🙋🏻‍♂️',':man_raising_hand_tone1:':'🙋🏻‍♂️',':man_raising_hand_medium_light_skin_tone:':'🙋🏼‍♂️',':man_raising_hand_tone2:':'🙋🏼‍♂️',':man_raising_hand_medium_skin_tone:':'🙋🏽‍♂️',':man_raising_hand_tone3:':'🙋🏽‍♂️',':man_raising_hand_medium_dark_skin_tone:':'🙋🏾‍♂️',':man_raising_hand_tone4:':'🙋🏾‍♂️',':man_raising_hand_dark_skin_tone:':'🙋🏿‍♂️',':man_raising_hand_tone5:':'🙋🏿‍♂️',':man_rowing_boat_light_skin_tone:':'🚣🏻‍♂️',':man_rowing_boat_tone1:':'🚣🏻‍♂️',':man_rowing_boat_medium_light_skin_tone:':'🚣🏼‍♂️',':man_rowing_boat_tone2:':'🚣🏼‍♂️',':man_rowing_boat_medium_skin_tone:':'🚣🏽‍♂️',':man_rowing_boat_tone3:':'🚣🏽‍♂️',':man_rowing_boat_medium_dark_skin_tone:':'🚣🏾‍♂️',':man_rowing_boat_tone4:':'🚣🏾‍♂️',':man_rowing_boat_dark_skin_tone:':'🚣🏿‍♂️',':man_rowing_boat_tone5:':'🚣🏿‍♂️',':man_running_light_skin_tone:':'🏃🏻‍♂️',':man_running_tone1:':'🏃🏻‍♂️',':man_running_medium_light_skin_tone:':'🏃🏼‍♂️',':man_running_tone2:':'🏃🏼‍♂️',':man_running_medium_skin_tone:':'🏃🏽‍♂️',':man_running_tone3:':'🏃🏽‍♂️',':man_running_medium_dark_skin_tone:':'🏃🏾‍♂️',':man_running_tone4:':'🏃🏾‍♂️',':man_running_dark_skin_tone:':'🏃🏿‍♂️',':man_running_tone5:':'🏃🏿‍♂️',':man_shrugging_light_skin_tone:':'🤷🏻‍♂️',':man_shrugging_tone1:':'🤷🏻‍♂️',':man_shrugging_medium_light_skin_tone:':'🤷🏼‍♂️',':man_shrugging_tone2:':'🤷🏼‍♂️',':man_shrugging_medium_skin_tone:':'🤷🏽‍♂️',':man_shrugging_tone3:':'🤷🏽‍♂️',':man_shrugging_medium_dark_skin_tone:':'🤷🏾‍♂️',':man_shrugging_tone4:':'🤷🏾‍♂️',':man_shrugging_dark_skin_tone:':'🤷🏿‍♂️',':man_shrugging_tone5:':'🤷🏿‍♂️',':man_standing_light_skin_tone:':'🧍🏻‍♂️',':man_standing_tone1:':'🧍🏻‍♂️',':man_standing_medium_light_skin_tone:':'🧍🏼‍♂️',':man_standing_tone2:':'🧍🏼‍♂️',':man_standing_medium_skin_tone:':'🧍🏽‍♂️',':man_standing_tone3:':'🧍🏽‍♂️',':man_standing_medium_dark_skin_tone:':'🧍🏾‍♂️',':man_standing_tone4:':'🧍🏾‍♂️',':man_standing_dark_skin_tone:':'🧍🏿‍♂️',':man_standing_tone5:':'🧍🏿‍♂️',':man_superhero_light_skin_tone:':'🦸🏻‍♂️',':man_superhero_tone1:':'🦸🏻‍♂️',':man_superhero_medium_light_skin_tone:':'🦸🏼‍♂️',':man_superhero_tone2:':'🦸🏼‍♂️',':man_superhero_medium_skin_tone:':'🦸🏽‍♂️',':man_superhero_tone3:':'🦸🏽‍♂️',':man_superhero_medium_dark_skin_tone:':'🦸🏾‍♂️',':man_superhero_tone4:':'🦸🏾‍♂️',':man_superhero_dark_skin_tone:':'🦸🏿‍♂️',':man_superhero_tone5:':'🦸🏿‍♂️',':man_supervillain_light_skin_tone:':'🦹🏻‍♂️',':man_supervillain_tone1:':'🦹🏻‍♂️',':man_supervillain_medium_light_skin_tone:':'🦹🏼‍♂️',':man_supervillain_tone2:':'🦹🏼‍♂️',':man_supervillain_medium_skin_tone:':'🦹🏽‍♂️',':man_supervillain_tone3:':'🦹🏽‍♂️',':man_supervillain_medium_dark_skin_tone:':'🦹🏾‍♂️',':man_supervillain_tone4:':'🦹🏾‍♂️',':man_supervillain_dark_skin_tone:':'🦹🏿‍♂️',':man_supervillain_tone5:':'🦹🏿‍♂️',':man_surfing_light_skin_tone:':'🏄🏻‍♂️',':man_surfing_tone1:':'🏄🏻‍♂️',':man_surfing_medium_light_skin_tone:':'🏄🏼‍♂️',':man_surfing_tone2:':'🏄🏼‍♂️',':man_surfing_medium_skin_tone:':'🏄🏽‍♂️',':man_surfing_tone3:':'🏄🏽‍♂️',':man_surfing_medium_dark_skin_tone:':'🏄🏾‍♂️',':man_surfing_tone4:':'🏄🏾‍♂️',':man_surfing_dark_skin_tone:':'🏄🏿‍♂️',':man_surfing_tone5:':'🏄🏿‍♂️',':man_swimming_light_skin_tone:':'🏊🏻‍♂️',':man_swimming_tone1:':'🏊🏻‍♂️',':man_swimming_medium_light_skin_tone:':'🏊🏼‍♂️',':man_swimming_tone2:':'🏊🏼‍♂️',':man_swimming_medium_skin_tone:':'🏊🏽‍♂️',':man_swimming_tone3:':'🏊🏽‍♂️',':man_swimming_medium_dark_skin_tone:':'🏊🏾‍♂️',':man_swimming_tone4:':'🏊🏾‍♂️',':man_swimming_dark_skin_tone:':'🏊🏿‍♂️',':man_swimming_tone5:':'🏊🏿‍♂️',':man_tipping_hand_light_skin_tone:':'💁🏻‍♂️',':man_tipping_hand_tone1:':'💁🏻‍♂️',':man_tipping_hand_medium_light_skin_tone:':'💁🏼‍♂️',':man_tipping_hand_tone2:':'💁🏼‍♂️',':man_tipping_hand_medium_skin_tone:':'💁🏽‍♂️',':man_tipping_hand_tone3:':'💁🏽‍♂️',':man_tipping_hand_medium_dark_skin_tone:':'💁🏾‍♂️',':man_tipping_hand_tone4:':'💁🏾‍♂️',':man_tipping_hand_dark_skin_tone:':'💁🏿‍♂️',':man_tipping_hand_tone5:':'💁🏿‍♂️',':man_vampire_light_skin_tone:':'🧛🏻‍♂️',':man_vampire_tone1:':'🧛🏻‍♂️',':man_vampire_medium_light_skin_tone:':'🧛🏼‍♂️',':man_vampire_tone2:':'🧛🏼‍♂️',':man_vampire_medium_skin_tone:':'🧛🏽‍♂️',':man_vampire_tone3:':'🧛🏽‍♂️',':man_vampire_medium_dark_skin_tone:':'🧛🏾‍♂️',':man_vampire_tone4:':'🧛🏾‍♂️',':man_vampire_dark_skin_tone:':'🧛🏿‍♂️',':man_vampire_tone5:':'🧛🏿‍♂️',':man_walking_light_skin_tone:':'🚶🏻‍♂️',':man_walking_tone1:':'🚶🏻‍♂️',':man_walking_medium_light_skin_tone:':'🚶🏼‍♂️',':man_walking_tone2:':'🚶🏼‍♂️',':man_walking_medium_skin_tone:':'🚶🏽‍♂️',':man_walking_tone3:':'🚶🏽‍♂️',':man_walking_medium_dark_skin_tone:':'🚶🏾‍♂️',':man_walking_tone4:':'🚶🏾‍♂️',':man_walking_dark_skin_tone:':'🚶🏿‍♂️',':man_walking_tone5:':'🚶🏿‍♂️',':man_wearing_turban_light_skin_tone:':'👳🏻‍♂️',':man_wearing_turban_tone1:':'👳🏻‍♂️',':man_wearing_turban_medium_light_skin_tone:':'👳🏼‍♂️',':man_wearing_turban_tone2:':'👳🏼‍♂️',':man_wearing_turban_medium_skin_tone:':'👳🏽‍♂️',':man_wearing_turban_tone3:':'👳🏽‍♂️',':man_wearing_turban_medium_dark_skin_tone:':'👳🏾‍♂️',':man_wearing_turban_tone4:':'👳🏾‍♂️',':man_wearing_turban_dark_skin_tone:':'👳🏿‍♂️',':man_wearing_turban_tone5:':'👳🏿‍♂️',':mermaid_light_skin_tone:':'🧜🏻‍♀️',':mermaid_tone1:':'🧜🏻‍♀️',':mermaid_medium_light_skin_tone:':'🧜🏼‍♀️',':mermaid_tone2:':'🧜🏼‍♀️',':mermaid_medium_skin_tone:':'🧜🏽‍♀️',':mermaid_tone3:':'🧜🏽‍♀️',':mermaid_medium_dark_skin_tone:':'🧜🏾‍♀️',':mermaid_tone4:':'🧜🏾‍♀️',':mermaid_dark_skin_tone:':'🧜🏿‍♀️',':mermaid_tone5:':'🧜🏿‍♀️',':merman_light_skin_tone:':'🧜🏻‍♂️',':merman_tone1:':'🧜🏻‍♂️',':merman_medium_light_skin_tone:':'🧜🏼‍♂️',':merman_tone2:':'🧜🏼‍♂️',':merman_medium_skin_tone:':'🧜🏽‍♂️',':merman_tone3:':'🧜🏽‍♂️',':merman_medium_dark_skin_tone:':'🧜🏾‍♂️',':merman_tone4:':'🧜🏾‍♂️',':merman_dark_skin_tone:':'🧜🏿‍♂️',':merman_tone5:':'🧜🏿‍♂️',':woman_biking_light_skin_tone:':'🚴🏻‍♀️',':woman_biking_tone1:':'🚴🏻‍♀️',':woman_biking_medium_light_skin_tone:':'🚴🏼‍♀️',':woman_biking_tone2:':'🚴🏼‍♀️',':woman_biking_medium_skin_tone:':'🚴🏽‍♀️',':woman_biking_tone3:':'🚴🏽‍♀️',':woman_biking_medium_dark_skin_tone:':'🚴🏾‍♀️',':woman_biking_tone4:':'🚴🏾‍♀️',':woman_biking_dark_skin_tone:':'🚴🏿‍♀️',':woman_biking_tone5:':'🚴🏿‍♀️',':woman_bowing_light_skin_tone:':'🙇🏻‍♀️',':woman_bowing_tone1:':'🙇🏻‍♀️',':woman_bowing_medium_light_skin_tone:':'🙇🏼‍♀️',':woman_bowing_tone2:':'🙇🏼‍♀️',':woman_bowing_medium_skin_tone:':'🙇🏽‍♀️',':woman_bowing_tone3:':'🙇🏽‍♀️',':woman_bowing_medium_dark_skin_tone:':'🙇🏾‍♀️',':woman_bowing_tone4:':'🙇🏾‍♀️',':woman_bowing_dark_skin_tone:':'🙇🏿‍♀️',':woman_bowing_tone5:':'🙇🏿‍♀️',':woman_cartwheeling_light_skin_tone:':'🤸🏻‍♀️',':woman_cartwheeling_tone1:':'🤸🏻‍♀️',':woman_cartwheeling_medium_light_skin_tone:':'🤸🏼‍♀️',':woman_cartwheeling_tone2:':'🤸🏼‍♀️',':woman_cartwheeling_medium_skin_tone:':'🤸🏽‍♀️',':woman_cartwheeling_tone3:':'🤸🏽‍♀️',':woman_cartwheeling_medium_dark_skin_tone:':'🤸🏾‍♀️',':woman_cartwheeling_tone4:':'🤸🏾‍♀️',':woman_cartwheeling_dark_skin_tone:':'🤸🏿‍♀️',':woman_cartwheeling_tone5:':'🤸🏿‍♀️',':woman_climbing_light_skin_tone:':'🧗🏻‍♀️',':woman_climbing_tone1:':'🧗🏻‍♀️',':woman_climbing_medium_light_skin_tone:':'🧗🏼‍♀️',':woman_climbing_tone2:':'🧗🏼‍♀️',':woman_climbing_medium_skin_tone:':'🧗🏽‍♀️',':woman_climbing_tone3:':'🧗🏽‍♀️',':woman_climbing_medium_dark_skin_tone:':'🧗🏾‍♀️',':woman_climbing_tone4:':'🧗🏾‍♀️',':woman_climbing_dark_skin_tone:':'🧗🏿‍♀️',':woman_climbing_tone5:':'🧗🏿‍♀️',':woman_construction_worker_light_skin_tone:':'👷🏻‍♀️',':woman_construction_worker_tone1:':'👷🏻‍♀️',':woman_construction_worker_medium_light_skin_tone:':'👷🏼‍♀️',':woman_construction_worker_tone2:':'👷🏼‍♀️',':woman_construction_worker_medium_skin_tone:':'👷🏽‍♀️',':woman_construction_worker_tone3:':'👷🏽‍♀️',':woman_construction_worker_medium_dark_skin_tone:':'👷🏾‍♀️',':woman_construction_worker_tone4:':'👷🏾‍♀️',':woman_construction_worker_dark_skin_tone:':'👷🏿‍♀️',':woman_construction_worker_tone5:':'👷🏿‍♀️',':woman_detective_light_skin_tone:':'🕵️🏻‍♀️',':woman_detective_tone1:':'🕵️🏻‍♀️',':woman_detective_medium_light_skin_tone:':'🕵️🏼‍♀️',':woman_detective_tone2:':'🕵️🏼‍♀️',':woman_detective_medium_skin_tone:':'🕵️🏽‍♀️',':woman_detective_tone3:':'🕵️🏽‍♀️',':woman_detective_medium_dark_skin_tone:':'🕵️🏾‍♀️',':woman_detective_tone4:':'🕵️🏾‍♀️',':woman_detective_dark_skin_tone:':'🕵️🏿‍♀️',':woman_detective_tone5:':'🕵️🏿‍♀️',':woman_elf_light_skin_tone:':'🧝🏻‍♀️',':woman_elf_tone1:':'🧝🏻‍♀️',':woman_elf_medium_light_skin_tone:':'🧝🏼‍♀️',':woman_elf_tone2:':'🧝🏼‍♀️',':woman_elf_medium_skin_tone:':'🧝🏽‍♀️',':woman_elf_tone3:':'🧝🏽‍♀️',':woman_elf_medium_dark_skin_tone:':'🧝🏾‍♀️',':woman_elf_tone4:':'🧝🏾‍♀️',':woman_elf_dark_skin_tone:':'🧝🏿‍♀️',':woman_elf_tone5:':'🧝🏿‍♀️',':woman_facepalming_light_skin_tone:':'🤦🏻‍♀️',':woman_facepalming_tone1:':'🤦🏻‍♀️',':woman_facepalming_medium_light_skin_tone:':'🤦🏼‍♀️',':woman_facepalming_tone2:':'🤦🏼‍♀️',':woman_facepalming_medium_skin_tone:':'🤦🏽‍♀️',':woman_facepalming_tone3:':'🤦🏽‍♀️',':woman_facepalming_medium_dark_skin_tone:':'🤦🏾‍♀️',':woman_facepalming_tone4:':'🤦🏾‍♀️',':woman_facepalming_dark_skin_tone:':'🤦🏿‍♀️',':woman_facepalming_tone5:':'🤦🏿‍♀️',':woman_fairy_light_skin_tone:':'🧚🏻‍♀️',':woman_fairy_tone1:':'🧚🏻‍♀️',':woman_fairy_medium_light_skin_tone:':'🧚🏼‍♀️',':woman_fairy_tone2:':'🧚🏼‍♀️',':woman_fairy_medium_skin_tone:':'🧚🏽‍♀️',':woman_fairy_tone3:':'🧚🏽‍♀️',':woman_fairy_medium_dark_skin_tone:':'🧚🏾‍♀️',':woman_fairy_tone4:':'🧚🏾‍♀️',':woman_fairy_dark_skin_tone:':'🧚🏿‍♀️',':woman_fairy_tone5:':'🧚🏿‍♀️',':woman_frowning_light_skin_tone:':'🙍🏻‍♀️',':woman_frowning_tone1:':'🙍🏻‍♀️',':woman_frowning_medium_light_skin_tone:':'🙍🏼‍♀️',':woman_frowning_tone2:':'🙍🏼‍♀️',':woman_frowning_medium_skin_tone:':'🙍🏽‍♀️',':woman_frowning_tone3:':'🙍🏽‍♀️',':woman_frowning_medium_dark_skin_tone:':'🙍🏾‍♀️',':woman_frowning_tone4:':'🙍🏾‍♀️',':woman_frowning_dark_skin_tone:':'🙍🏿‍♀️',':woman_frowning_tone5:':'🙍🏿‍♀️',':woman_gesturing_no_light_skin_tone:':'🙅🏻‍♀️',':woman_gesturing_no_tone1:':'🙅🏻‍♀️',':woman_gesturing_no_medium_light_skin_tone:':'🙅🏼‍♀️',':woman_gesturing_no_tone2:':'🙅🏼‍♀️',':woman_gesturing_no_medium_skin_tone:':'🙅🏽‍♀️',':woman_gesturing_no_tone3:':'🙅🏽‍♀️',':woman_gesturing_no_medium_dark_skin_tone:':'🙅🏾‍♀️',':woman_gesturing_no_tone4:':'🙅🏾‍♀️',':woman_gesturing_no_dark_skin_tone:':'🙅🏿‍♀️',':woman_gesturing_no_tone5:':'🙅🏿‍♀️',':woman_gesturing_ok_light_skin_tone:':'🙆🏻‍♀️',':woman_gesturing_ok_tone1:':'🙆🏻‍♀️',':woman_gesturing_ok_medium_light_skin_tone:':'🙆🏼‍♀️',':woman_gesturing_ok_tone2:':'🙆🏼‍♀️',':woman_gesturing_ok_medium_skin_tone:':'🙆🏽‍♀️',':woman_gesturing_ok_tone3:':'🙆🏽‍♀️',':woman_gesturing_ok_medium_dark_skin_tone:':'🙆🏾‍♀️',':woman_gesturing_ok_tone4:':'🙆🏾‍♀️',':woman_gesturing_ok_dark_skin_tone:':'🙆🏿‍♀️',':woman_gesturing_ok_tone5:':'🙆🏿‍♀️',':woman_getting_face_massage_light_skin_tone:':'💆🏻‍♀️',':woman_getting_face_massage_tone1:':'💆🏻‍♀️',':woman_getting_face_massage_medium_light_skin_tone:':'💆🏼‍♀️',':woman_getting_face_massage_tone2:':'💆🏼‍♀️',':woman_getting_face_massage_medium_skin_tone:':'💆🏽‍♀️',':woman_getting_face_massage_tone3:':'💆🏽‍♀️',':woman_getting_face_massage_medium_dark_skin_tone:':'💆🏾‍♀️',':woman_getting_face_massage_tone4:':'💆🏾‍♀️',':woman_getting_face_massage_dark_skin_tone:':'💆🏿‍♀️',':woman_getting_face_massage_tone5:':'💆🏿‍♀️',':woman_getting_haircut_light_skin_tone:':'💇🏻‍♀️',':woman_getting_haircut_tone1:':'💇🏻‍♀️',':woman_getting_haircut_medium_light_skin_tone:':'💇🏼‍♀️',':woman_getting_haircut_tone2:':'💇🏼‍♀️',':woman_getting_haircut_medium_skin_tone:':'💇🏽‍♀️',':woman_getting_haircut_tone3:':'💇🏽‍♀️',':woman_getting_haircut_medium_dark_skin_tone:':'💇🏾‍♀️',':woman_getting_haircut_tone4:':'💇🏾‍♀️',':woman_getting_haircut_dark_skin_tone:':'💇🏿‍♀️',':woman_getting_haircut_tone5:':'💇🏿‍♀️',':woman_golfing_light_skin_tone:':'🏌️🏻‍♀️',':woman_golfing_tone1:':'🏌️🏻‍♀️',':woman_golfing_medium_light_skin_tone:':'🏌️🏼‍♀️',':woman_golfing_tone2:':'🏌️🏼‍♀️',':woman_golfing_medium_skin_tone:':'🏌️🏽‍♀️',':woman_golfing_tone3:':'🏌️🏽‍♀️',':woman_golfing_medium_dark_skin_tone:':'🏌️🏾‍♀️',':woman_golfing_tone4:':'🏌️🏾‍♀️',':woman_golfing_dark_skin_tone:':'🏌️🏿‍♀️',':woman_golfing_tone5:':'🏌️🏿‍♀️',':woman_guard_light_skin_tone:':'💂🏻‍♀️',':woman_guard_tone1:':'💂🏻‍♀️',':woman_guard_medium_light_skin_tone:':'💂🏼‍♀️',':woman_guard_tone2:':'💂🏼‍♀️',':woman_guard_medium_skin_tone:':'💂🏽‍♀️',':woman_guard_tone3:':'💂🏽‍♀️',':woman_guard_medium_dark_skin_tone:':'💂🏾‍♀️',':woman_guard_tone4:':'💂🏾‍♀️',':woman_guard_dark_skin_tone:':'💂🏿‍♀️',':woman_guard_tone5:':'💂🏿‍♀️',':woman_health_worker_light_skin_tone:':'👩🏻‍⚕️',':woman_health_worker_tone1:':'👩🏻‍⚕️',':woman_health_worker_medium_light_skin_tone:':'👩🏼‍⚕️',':woman_health_worker_tone2:':'👩🏼‍⚕️',':woman_health_worker_medium_skin_tone:':'👩🏽‍⚕️',':woman_health_worker_tone3:':'👩🏽‍⚕️',':woman_health_worker_medium_dark_skin_tone:':'👩🏾‍⚕️',':woman_health_worker_tone4:':'👩🏾‍⚕️',':woman_health_worker_dark_skin_tone:':'👩🏿‍⚕️',':woman_health_worker_tone5:':'👩🏿‍⚕️',':woman_in_lotus_position_light_skin_tone:':'🧘🏻‍♀️',':woman_in_lotus_position_tone1:':'🧘🏻‍♀️',':woman_in_lotus_position_medium_light_skin_tone:':'🧘🏼‍♀️',':woman_in_lotus_position_tone2:':'🧘🏼‍♀️',':woman_in_lotus_position_medium_skin_tone:':'🧘🏽‍♀️',':woman_in_lotus_position_tone3:':'🧘🏽‍♀️',':woman_in_lotus_position_medium_dark_skin_tone:':'🧘🏾‍♀️',':woman_in_lotus_position_tone4:':'🧘🏾‍♀️',':woman_in_lotus_position_dark_skin_tone:':'🧘🏿‍♀️',':woman_in_lotus_position_tone5:':'🧘🏿‍♀️',':woman_in_steamy_room_light_skin_tone:':'🧖🏻‍♀️',':woman_in_steamy_room_tone1:':'🧖🏻‍♀️',':woman_in_steamy_room_medium_light_skin_tone:':'🧖🏼‍♀️',':woman_in_steamy_room_tone2:':'🧖🏼‍♀️',':woman_in_steamy_room_medium_skin_tone:':'🧖🏽‍♀️',':woman_in_steamy_room_tone3:':'🧖🏽‍♀️',':woman_in_steamy_room_medium_dark_skin_tone:':'🧖🏾‍♀️',':woman_in_steamy_room_tone4:':'🧖🏾‍♀️',':woman_in_steamy_room_dark_skin_tone:':'🧖🏿‍♀️',':woman_in_steamy_room_tone5:':'🧖🏿‍♀️',':woman_judge_light_skin_tone:':'👩🏻‍⚖️',':woman_judge_tone1:':'👩🏻‍⚖️',':woman_judge_medium_light_skin_tone:':'👩🏼‍⚖️',':woman_judge_tone2:':'👩🏼‍⚖️',':woman_judge_medium_skin_tone:':'👩🏽‍⚖️',':woman_judge_tone3:':'👩🏽‍⚖️',':woman_judge_medium_dark_skin_tone:':'👩🏾‍⚖️',':woman_judge_tone4:':'👩🏾‍⚖️',':woman_judge_dark_skin_tone:':'👩🏿‍⚖️',':woman_judge_tone5:':'👩🏿‍⚖️',':woman_juggling_light_skin_tone:':'🤹🏻‍♀️',':woman_juggling_tone1:':'🤹🏻‍♀️',':woman_juggling_medium_light_skin_tone:':'🤹🏼‍♀️',':woman_juggling_tone2:':'🤹🏼‍♀️',':woman_juggling_medium_skin_tone:':'🤹🏽‍♀️',':woman_juggling_tone3:':'🤹🏽‍♀️',':woman_juggling_medium_dark_skin_tone:':'🤹🏾‍♀️',':woman_juggling_tone4:':'🤹🏾‍♀️',':woman_juggling_dark_skin_tone:':'🤹🏿‍♀️',':woman_juggling_tone5:':'🤹🏿‍♀️',':woman_kneeling_light_skin_tone:':'🧎🏻‍♀️',':woman_kneeling_tone1:':'🧎🏻‍♀️',':woman_kneeling_medium_light_skin_tone:':'🧎🏼‍♀️',':woman_kneeling_tone2:':'🧎🏼‍♀️',':woman_kneeling_medium_skin_tone:':'🧎🏽‍♀️',':woman_kneeling_tone3:':'🧎🏽‍♀️',':woman_kneeling_medium_dark_skin_tone:':'🧎🏾‍♀️',':woman_kneeling_tone4:':'🧎🏾‍♀️',':woman_kneeling_dark_skin_tone:':'🧎🏿‍♀️',':woman_kneeling_tone5:':'🧎🏿‍♀️',':woman_lifting_weights_light_skin_tone:':'🏋️🏻‍♀️',':woman_lifting_weights_tone1:':'🏋️🏻‍♀️',':woman_lifting_weights_medium_light_skin_tone:':'🏋️🏼‍♀️',':woman_lifting_weights_tone2:':'🏋️🏼‍♀️',':woman_lifting_weights_medium_skin_tone:':'🏋️🏽‍♀️',':woman_lifting_weights_tone3:':'🏋️🏽‍♀️',':woman_lifting_weights_medium_dark_skin_tone:':'🏋️🏾‍♀️',':woman_lifting_weights_tone4:':'🏋️🏾‍♀️',':woman_lifting_weights_dark_skin_tone:':'🏋️🏿‍♀️',':woman_lifting_weights_tone5:':'🏋️🏿‍♀️',':woman_mage_light_skin_tone:':'🧙🏻‍♀️',':woman_mage_tone1:':'🧙🏻‍♀️',':woman_mage_medium_light_skin_tone:':'🧙🏼‍♀️',':woman_mage_tone2:':'🧙🏼‍♀️',':woman_mage_medium_skin_tone:':'🧙🏽‍♀️',':woman_mage_tone3:':'🧙🏽‍♀️',':woman_mage_medium_dark_skin_tone:':'🧙🏾‍♀️',':woman_mage_tone4:':'🧙🏾‍♀️',':woman_mage_dark_skin_tone:':'🧙🏿‍♀️',':woman_mage_tone5:':'🧙🏿‍♀️',':woman_mountain_biking_light_skin_tone:':'🚵🏻‍♀️',':woman_mountain_biking_tone1:':'🚵🏻‍♀️',':woman_mountain_biking_medium_light_skin_tone:':'🚵🏼‍♀️',':woman_mountain_biking_tone2:':'🚵🏼‍♀️',':woman_mountain_biking_medium_skin_tone:':'🚵🏽‍♀️',':woman_mountain_biking_tone3:':'🚵🏽‍♀️',':woman_mountain_biking_medium_dark_skin_tone:':'🚵🏾‍♀️',':woman_mountain_biking_tone4:':'🚵🏾‍♀️',':woman_mountain_biking_dark_skin_tone:':'🚵🏿‍♀️',':woman_mountain_biking_tone5:':'🚵🏿‍♀️',':woman_pilot_light_skin_tone:':'👩🏻‍✈️',':woman_pilot_tone1:':'👩🏻‍✈️',':woman_pilot_medium_light_skin_tone:':'👩🏼‍✈️',':woman_pilot_tone2:':'👩🏼‍✈️',':woman_pilot_medium_skin_tone:':'👩🏽‍✈️',':woman_pilot_tone3:':'👩🏽‍✈️',':woman_pilot_medium_dark_skin_tone:':'👩🏾‍✈️',':woman_pilot_tone4:':'👩🏾‍✈️',':woman_pilot_dark_skin_tone:':'👩🏿‍✈️',':woman_pilot_tone5:':'👩🏿‍✈️',':woman_playing_handball_light_skin_tone:':'🤾🏻‍♀️',':woman_playing_handball_tone1:':'🤾🏻‍♀️',':woman_playing_handball_medium_light_skin_tone:':'🤾🏼‍♀️',':woman_playing_handball_tone2:':'🤾🏼‍♀️',':woman_playing_handball_medium_skin_tone:':'🤾🏽‍♀️',':woman_playing_handball_tone3:':'🤾🏽‍♀️',':woman_playing_handball_medium_dark_skin_tone:':'🤾🏾‍♀️',':woman_playing_handball_tone4:':'🤾🏾‍♀️',':woman_playing_handball_dark_skin_tone:':'🤾🏿‍♀️',':woman_playing_handball_tone5:':'🤾🏿‍♀️',':woman_playing_water_polo_light_skin_tone:':'🤽🏻‍♀️',':woman_playing_water_polo_tone1:':'🤽🏻‍♀️',':woman_playing_water_polo_medium_light_skin_tone:':'🤽🏼‍♀️',':woman_playing_water_polo_tone2:':'🤽🏼‍♀️',':woman_playing_water_polo_medium_skin_tone:':'🤽🏽‍♀️',':woman_playing_water_polo_tone3:':'🤽🏽‍♀️',':woman_playing_water_polo_medium_dark_skin_tone:':'🤽🏾‍♀️',':woman_playing_water_polo_tone4:':'🤽🏾‍♀️',':woman_playing_water_polo_dark_skin_tone:':'🤽🏿‍♀️',':woman_playing_water_polo_tone5:':'🤽🏿‍♀️',':woman_police_officer_light_skin_tone:':'👮🏻‍♀️',':woman_police_officer_tone1:':'👮🏻‍♀️',':woman_police_officer_medium_light_skin_tone:':'👮🏼‍♀️',':woman_police_officer_tone2:':'👮🏼‍♀️',':woman_police_officer_medium_skin_tone:':'👮🏽‍♀️',':woman_police_officer_tone3:':'👮🏽‍♀️',':woman_police_officer_medium_dark_skin_tone:':'👮🏾‍♀️',':woman_police_officer_tone4:':'👮🏾‍♀️',':woman_police_officer_dark_skin_tone:':'👮🏿‍♀️',':woman_police_officer_tone5:':'👮🏿‍♀️',':woman_pouting_light_skin_tone:':'🙎🏻‍♀️',':woman_pouting_tone1:':'🙎🏻‍♀️',':woman_pouting_medium_light_skin_tone:':'🙎🏼‍♀️',':woman_pouting_tone2:':'🙎🏼‍♀️',':woman_pouting_medium_skin_tone:':'🙎🏽‍♀️',':woman_pouting_tone3:':'🙎🏽‍♀️',':woman_pouting_medium_dark_skin_tone:':'🙎🏾‍♀️',':woman_pouting_tone4:':'🙎🏾‍♀️',':woman_pouting_dark_skin_tone:':'🙎🏿‍♀️',':woman_pouting_tone5:':'🙎🏿‍♀️',':woman_raising_hand_light_skin_tone:':'🙋🏻‍♀️',':woman_raising_hand_tone1:':'🙋🏻‍♀️',':woman_raising_hand_medium_light_skin_tone:':'🙋🏼‍♀️',':woman_raising_hand_tone2:':'🙋🏼‍♀️',':woman_raising_hand_medium_skin_tone:':'🙋🏽‍♀️',':woman_raising_hand_tone3:':'🙋🏽‍♀️',':woman_raising_hand_medium_dark_skin_tone:':'🙋🏾‍♀️',':woman_raising_hand_tone4:':'🙋🏾‍♀️',':woman_raising_hand_dark_skin_tone:':'🙋🏿‍♀️',':woman_raising_hand_tone5:':'🙋🏿‍♀️',':woman_rowing_boat_light_skin_tone:':'🚣🏻‍♀️',':woman_rowing_boat_tone1:':'🚣🏻‍♀️',':woman_rowing_boat_medium_light_skin_tone:':'🚣🏼‍♀️',':woman_rowing_boat_tone2:':'🚣🏼‍♀️',':woman_rowing_boat_medium_skin_tone:':'🚣🏽‍♀️',':woman_rowing_boat_tone3:':'🚣🏽‍♀️',':woman_rowing_boat_medium_dark_skin_tone:':'🚣🏾‍♀️',':woman_rowing_boat_tone4:':'🚣🏾‍♀️',':woman_rowing_boat_dark_skin_tone:':'🚣🏿‍♀️',':woman_rowing_boat_tone5:':'🚣🏿‍♀️',':woman_running_light_skin_tone:':'🏃🏻‍♀️',':woman_running_tone1:':'🏃🏻‍♀️',':woman_running_medium_light_skin_tone:':'🏃🏼‍♀️',':woman_running_tone2:':'🏃🏼‍♀️',':woman_running_medium_skin_tone:':'🏃🏽‍♀️',':woman_running_tone3:':'🏃🏽‍♀️',':woman_running_medium_dark_skin_tone:':'🏃🏾‍♀️',':woman_running_tone4:':'🏃🏾‍♀️',':woman_running_dark_skin_tone:':'🏃🏿‍♀️',':woman_running_tone5:':'🏃🏿‍♀️',':woman_shrugging_light_skin_tone:':'🤷🏻‍♀️',':woman_shrugging_tone1:':'🤷🏻‍♀️',':woman_shrugging_medium_light_skin_tone:':'🤷🏼‍♀️',':woman_shrugging_tone2:':'🤷🏼‍♀️',':woman_shrugging_medium_skin_tone:':'🤷🏽‍♀️',':woman_shrugging_tone3:':'🤷🏽‍♀️',':woman_shrugging_medium_dark_skin_tone:':'🤷🏾‍♀️',':woman_shrugging_tone4:':'🤷🏾‍♀️',':woman_shrugging_dark_skin_tone:':'🤷🏿‍♀️',':woman_shrugging_tone5:':'🤷🏿‍♀️',':woman_standing_light_skin_tone:':'🧍🏻‍♀️',':woman_standing_tone1:':'🧍🏻‍♀️',':woman_standing_medium_light_skin_tone:':'🧍🏼‍♀️',':woman_standing_tone2:':'🧍🏼‍♀️',':woman_standing_medium_skin_tone:':'🧍🏽‍♀️',':woman_standing_tone3:':'🧍🏽‍♀️',':woman_standing_medium_dark_skin_tone:':'🧍🏾‍♀️',':woman_standing_tone4:':'🧍🏾‍♀️',':woman_standing_dark_skin_tone:':'🧍🏿‍♀️',':woman_standing_tone5:':'🧍🏿‍♀️',':woman_superhero_light_skin_tone:':'🦸🏻‍♀️',':woman_superhero_tone1:':'🦸🏻‍♀️',':woman_superhero_medium_light_skin_tone:':'🦸🏼‍♀️',':woman_superhero_tone2:':'🦸🏼‍♀️',':woman_superhero_medium_skin_tone:':'🦸🏽‍♀️',':woman_superhero_tone3:':'🦸🏽‍♀️',':woman_superhero_medium_dark_skin_tone:':'🦸🏾‍♀️',':woman_superhero_tone4:':'🦸🏾‍♀️',':woman_superhero_dark_skin_tone:':'🦸🏿‍♀️',':woman_superhero_tone5:':'🦸🏿‍♀️',':woman_supervillain_light_skin_tone:':'🦹🏻‍♀️',':woman_supervillain_tone1:':'🦹🏻‍♀️',':woman_supervillain_medium_light_skin_tone:':'🦹🏼‍♀️',':woman_supervillain_tone2:':'🦹🏼‍♀️',':woman_supervillain_medium_skin_tone:':'🦹🏽‍♀️',':woman_supervillain_tone3:':'🦹🏽‍♀️',':woman_supervillain_medium_dark_skin_tone:':'🦹🏾‍♀️',':woman_supervillain_tone4:':'🦹🏾‍♀️',':woman_supervillain_dark_skin_tone:':'🦹🏿‍♀️',':woman_supervillain_tone5:':'🦹🏿‍♀️',':woman_surfing_light_skin_tone:':'🏄🏻‍♀️',':woman_surfing_tone1:':'🏄🏻‍♀️',':woman_surfing_medium_light_skin_tone:':'🏄🏼‍♀️',':woman_surfing_tone2:':'🏄🏼‍♀️',':woman_surfing_medium_skin_tone:':'🏄🏽‍♀️',':woman_surfing_tone3:':'🏄🏽‍♀️',':woman_surfing_medium_dark_skin_tone:':'🏄🏾‍♀️',':woman_surfing_tone4:':'🏄🏾‍♀️',':woman_surfing_dark_skin_tone:':'🏄🏿‍♀️',':woman_surfing_tone5:':'🏄🏿‍♀️',':woman_swimming_light_skin_tone:':'🏊🏻‍♀️',':woman_swimming_tone1:':'🏊🏻‍♀️',':woman_swimming_medium_light_skin_tone:':'🏊🏼‍♀️',':woman_swimming_tone2:':'🏊🏼‍♀️',':woman_swimming_medium_skin_tone:':'🏊🏽‍♀️',':woman_swimming_tone3:':'🏊🏽‍♀️',':woman_swimming_medium_dark_skin_tone:':'🏊🏾‍♀️',':woman_swimming_tone4:':'🏊🏾‍♀️',':woman_swimming_dark_skin_tone:':'🏊🏿‍♀️',':woman_swimming_tone5:':'🏊🏿‍♀️',':woman_tipping_hand_light_skin_tone:':'💁🏻‍♀️',':woman_tipping_hand_tone1:':'💁🏻‍♀️',':woman_tipping_hand_medium_light_skin_tone:':'💁🏼‍♀️',':woman_tipping_hand_tone2:':'💁🏼‍♀️',':woman_tipping_hand_medium_skin_tone:':'💁🏽‍♀️',':woman_tipping_hand_tone3:':'💁🏽‍♀️',':woman_tipping_hand_medium_dark_skin_tone:':'💁🏾‍♀️',':woman_tipping_hand_tone4:':'💁🏾‍♀️',':woman_tipping_hand_dark_skin_tone:':'💁🏿‍♀️',':woman_tipping_hand_tone5:':'💁🏿‍♀️',':woman_vampire_light_skin_tone:':'🧛🏻‍♀️',':woman_vampire_tone1:':'🧛🏻‍♀️',':woman_vampire_medium_light_skin_tone:':'🧛🏼‍♀️',':woman_vampire_tone2:':'🧛🏼‍♀️',':woman_vampire_medium_skin_tone:':'🧛🏽‍♀️',':woman_vampire_tone3:':'🧛🏽‍♀️',':woman_vampire_medium_dark_skin_tone:':'🧛🏾‍♀️',':woman_vampire_tone4:':'🧛🏾‍♀️',':woman_vampire_dark_skin_tone:':'🧛🏿‍♀️',':woman_vampire_tone5:':'🧛🏿‍♀️',':woman_walking_light_skin_tone:':'🚶🏻‍♀️',':woman_walking_tone1:':'🚶🏻‍♀️',':woman_walking_medium_light_skin_tone:':'🚶🏼‍♀️',':woman_walking_tone2:':'🚶🏼‍♀️',':woman_walking_medium_skin_tone:':'🚶🏽‍♀️',':woman_walking_tone3:':'🚶🏽‍♀️',':woman_walking_medium_dark_skin_tone:':'🚶🏾‍♀️',':woman_walking_tone4:':'🚶🏾‍♀️',':woman_walking_dark_skin_tone:':'🚶🏿‍♀️',':woman_walking_tone5:':'🚶🏿‍♀️',':woman_wearing_turban_light_skin_tone:':'👳🏻‍♀️',':woman_wearing_turban_tone1:':'👳🏻‍♀️',':woman_wearing_turban_medium_light_skin_tone:':'👳🏼‍♀️',':woman_wearing_turban_tone2:':'👳🏼‍♀️',':woman_wearing_turban_medium_skin_tone:':'👳🏽‍♀️',':woman_wearing_turban_tone3:':'👳🏽‍♀️',':woman_wearing_turban_medium_dark_skin_tone:':'👳🏾‍♀️',':woman_wearing_turban_tone4:':'👳🏾‍♀️',':woman_wearing_turban_dark_skin_tone:':'👳🏿‍♀️',':woman_wearing_turban_tone5:':'👳🏿‍♀️',':man_bouncing_ball_light_skin_tone:':'⛹️🏻‍♂️',':man_bouncing_ball_tone1:':'⛹️🏻‍♂️',':man_bouncing_ball_medium_light_skin_tone:':'⛹️🏼‍♂️',':man_bouncing_ball_tone2:':'⛹️🏼‍♂️',':man_bouncing_ball_medium_skin_tone:':'⛹️🏽‍♂️',':man_bouncing_ball_tone3:':'⛹️🏽‍♂️',':man_bouncing_ball_medium_dark_skin_tone:':'⛹️🏾‍♂️',':man_bouncing_ball_tone4:':'⛹️🏾‍♂️',':man_bouncing_ball_dark_skin_tone:':'⛹️🏿‍♂️',':man_bouncing_ball_tone5:':'⛹️🏿‍♂️',':woman_bouncing_ball_light_skin_tone:':'⛹️🏻‍♀️',':woman_bouncing_ball_tone1:':'⛹️🏻‍♀️',':woman_bouncing_ball_medium_light_skin_tone:':'⛹️🏼‍♀️',':woman_bouncing_ball_tone2:':'⛹️🏼‍♀️',':woman_bouncing_ball_medium_skin_tone:':'⛹️🏽‍♀️',':woman_bouncing_ball_tone3:':'⛹️🏽‍♀️',':woman_bouncing_ball_medium_dark_skin_tone:':'⛹️🏾‍♀️',':woman_bouncing_ball_tone4:':'⛹️🏾‍♀️',':woman_bouncing_ball_dark_skin_tone:':'⛹️🏿‍♀️',':woman_bouncing_ball_tone5:':'⛹️🏿‍♀️',':adult_light_skin_tone:':'🧑🏻',':adult_tone1:':'🧑🏻',':adult_medium_light_skin_tone:':'🧑🏼',':adult_tone2:':'🧑🏼',':adult_medium_skin_tone:':'🧑🏽',':adult_tone3:':'🧑🏽',':adult_medium_dark_skin_tone:':'🧑🏾',':adult_tone4:':'🧑🏾',':adult_dark_skin_tone:':'🧑🏿',':adult_tone5:':'🧑🏿',':angel_tone1:':'👼🏻',':angel_tone2:':'👼🏼',':angel_tone3:':'👼🏽',':angel_tone4:':'👼🏾',':angel_tone5:':'👼🏿',':baby_tone1:':'👶🏻',':baby_tone2:':'👶🏼',':baby_tone3:':'👶🏽',':baby_tone4:':'👶🏾',':baby_tone5:':'👶🏿',':bath_tone1:':'🛀🏻',':bath_tone2:':'🛀🏼',':bath_tone3:':'🛀🏽',':bath_tone4:':'🛀🏾',':bath_tone5:':'🛀🏿',':bearded_person_light_skin_tone:':'🧔🏻',':bearded_person_tone1:':'🧔🏻',':bearded_person_medium_light_skin_tone:':'🧔🏼',':bearded_person_tone2:':'🧔🏼',':bearded_person_medium_skin_tone:':'🧔🏽',':bearded_person_tone3:':'🧔🏽',':bearded_person_medium_dark_skin_tone:':'🧔🏾',':bearded_person_tone4:':'🧔🏾',':bearded_person_dark_skin_tone:':'🧔🏿',':bearded_person_tone5:':'🧔🏿',':person_with_blond_hair_tone1:':'👱🏻',':blond_haired_person_tone1:':'👱🏻',':person_with_blond_hair_tone2:':'👱🏼',':blond_haired_person_tone2:':'👱🏼',':person_with_blond_hair_tone3:':'👱🏽',':blond_haired_person_tone3:':'👱🏽',':person_with_blond_hair_tone4:':'👱🏾',':blond_haired_person_tone4:':'👱🏾',':person_with_blond_hair_tone5:':'👱🏿',':blond_haired_person_tone5:':'👱🏿',':boy_tone1:':'👦🏻',':boy_tone2:':'👦🏼',':boy_tone3:':'👦🏽',':boy_tone4:':'👦🏾',':boy_tone5:':'👦🏿',':breast_feeding_light_skin_tone:':'🤱🏻',':breast_feeding_tone1:':'🤱🏻',':breast_feeding_medium_light_skin_tone:':'🤱🏼',':breast_feeding_tone2:':'🤱🏼',':breast_feeding_medium_skin_tone:':'🤱🏽',':breast_feeding_tone3:':'🤱🏽',':breast_feeding_medium_dark_skin_tone:':'🤱🏾',':breast_feeding_tone4:':'🤱🏾',':breast_feeding_dark_skin_tone:':'🤱🏿',':breast_feeding_tone5:':'🤱🏿',':bride_with_veil_tone1:':'👰🏻',':bride_with_veil_tone2:':'👰🏼',':bride_with_veil_tone3:':'👰🏽',':bride_with_veil_tone4:':'👰🏾',':bride_with_veil_tone5:':'👰🏿',':call_me_hand_tone1:':'🤙🏻',':call_me_tone1:':'🤙🏻',':call_me_hand_tone2:':'🤙🏼',':call_me_tone2:':'🤙🏼',':call_me_hand_tone3:':'🤙🏽',':call_me_tone3:':'🤙🏽',':call_me_hand_tone4:':'🤙🏾',':call_me_tone4:':'🤙🏾',':call_me_hand_tone5:':'🤙🏿',':call_me_tone5:':'🤙🏿',':child_light_skin_tone:':'🧒🏻',':child_tone1:':'🧒🏻',':child_medium_light_skin_tone:':'🧒🏼',':child_tone2:':'🧒🏼',':child_medium_skin_tone:':'🧒🏽',':child_tone3:':'🧒🏽',':child_medium_dark_skin_tone:':'🧒🏾',':child_tone4:':'🧒🏾',':child_dark_skin_tone:':'🧒🏿',':child_tone5:':'🧒🏿',':clap_tone1:':'👏🏻',':clap_tone2:':'👏🏼',':clap_tone3:':'👏🏽',':clap_tone4:':'👏🏾',':clap_tone5:':'👏🏿',':construction_worker_tone1:':'👷🏻',':construction_worker_tone2:':'👷🏼',':construction_worker_tone3:':'👷🏽',':construction_worker_tone4:':'👷🏾',':construction_worker_tone5:':'👷🏿',':dancer_tone1:':'💃🏻',':dancer_tone2:':'💃🏼',':dancer_tone3:':'💃🏽',':dancer_tone4:':'💃🏾',':dancer_tone5:':'💃🏿',':deaf_person_light_skin_tone:':'🧏🏻',':deaf_person_tone1:':'🧏🏻',':deaf_person_medium_light_skin_tone:':'🧏🏼',':deaf_person_tone2:':'🧏🏼',':deaf_person_medium_skin_tone:':'🧏🏽',':deaf_person_tone3:':'🧏🏽',':deaf_person_medium_dark_skin_tone:':'🧏🏾',':deaf_person_tone4:':'🧏🏾',':deaf_person_dark_skin_tone:':'🧏🏿',':deaf_person_tone5:':'🧏🏿',':spy_tone1:':'🕵️🏻',':sleuth_or_spy_tone1:':'🕵️🏻',':detective_tone1:':'🕵️🏻',':spy_tone2:':'🕵️🏼',':sleuth_or_spy_tone2:':'🕵️🏼',':detective_tone2:':'🕵️🏼',':spy_tone3:':'🕵️🏽',':sleuth_or_spy_tone3:':'🕵️🏽',':detective_tone3:':'🕵️🏽',':spy_tone4:':'🕵️🏾',':sleuth_or_spy_tone4:':'🕵️🏾',':detective_tone4:':'🕵️🏾',':spy_tone5:':'🕵️🏿',':sleuth_or_spy_tone5:':'🕵️🏿',':detective_tone5:':'🕵️🏿',':ear_tone1:':'👂🏻',':ear_tone2:':'👂🏼',':ear_tone3:':'👂🏽',':ear_tone4:':'👂🏾',':ear_tone5:':'👂🏿',':ear_with_hearing_aid_light_skin_tone:':'🦻🏻',':ear_with_hearing_aid_tone1:':'🦻🏻',':ear_with_hearing_aid_medium_light_skin_tone:':'🦻🏼',':ear_with_hearing_aid_tone2:':'🦻🏼',':ear_with_hearing_aid_medium_skin_tone:':'🦻🏽',':ear_with_hearing_aid_tone3:':'🦻🏽',':ear_with_hearing_aid_medium_dark_skin_tone:':'🦻🏾',':ear_with_hearing_aid_tone4:':'🦻🏾',':ear_with_hearing_aid_dark_skin_tone:':'🦻🏿',':ear_with_hearing_aid_tone5:':'🦻🏿',':elf_light_skin_tone:':'🧝🏻',':elf_tone1:':'🧝🏻',':elf_medium_light_skin_tone:':'🧝🏼',':elf_tone2:':'🧝🏼',':elf_medium_skin_tone:':'🧝🏽',':elf_tone3:':'🧝🏽',':elf_medium_dark_skin_tone:':'🧝🏾',':elf_tone4:':'🧝🏾',':elf_dark_skin_tone:':'🧝🏿',':elf_tone5:':'🧝🏿',':eye_in_speech_bubble:':'👁️‍🗨️',':fairy_light_skin_tone:':'🧚🏻',':fairy_tone1:':'🧚🏻',':fairy_medium_light_skin_tone:':'🧚🏼',':fairy_tone2:':'🧚🏼',':fairy_medium_skin_tone:':'🧚🏽',':fairy_tone3:':'🧚🏽',':fairy_medium_dark_skin_tone:':'🧚🏾',':fairy_tone4:':'🧚🏾',':fairy_dark_skin_tone:':'🧚🏿',':fairy_tone5:':'🧚🏿',':family_man_boy:':'👨‍👦',':family_man_girl:':'👨‍👧',':family_woman_boy:':'👩‍👦',':family_woman_girl:':'👩‍👧',':hand_with_index_and_middle_fingers_crossed_tone1:':'🤞🏻',':fingers_crossed_tone1:':'🤞🏻',':hand_with_index_and_middle_fingers_crossed_tone2:':'🤞🏼',':fingers_crossed_tone2:':'🤞🏼',':hand_with_index_and_middle_fingers_crossed_tone3:':'🤞🏽',':fingers_crossed_tone3:':'🤞🏽',':hand_with_index_and_middle_fingers_crossed_tone4:':'🤞🏾',':fingers_crossed_tone4:':'🤞🏾',':hand_with_index_and_middle_fingers_crossed_tone5:':'🤞🏿',':fingers_crossed_tone5:':'🤞🏿',':ac:':'🇦🇨',':flag_ac:':'🇦🇨',':ad:':'🇦🇩',':flag_ad:':'🇦🇩',':ae:':'🇦🇪',':flag_ae:':'🇦🇪',':af:':'🇦🇫',':flag_af:':'🇦🇫',':ag:':'🇦🇬',':flag_ag:':'🇦🇬',':ai:':'🇦🇮',':flag_ai:':'🇦🇮',':al:':'🇦🇱',':flag_al:':'🇦🇱',':am:':'🇦🇲',':flag_am:':'🇦🇲',':ao:':'🇦🇴',':flag_ao:':'🇦🇴',':aq:':'🇦🇶',':flag_aq:':'🇦🇶',':ar:':'🇦🇷',':flag_ar:':'🇦🇷',':as:':'🇦🇸',':flag_as:':'🇦🇸',':at:':'🇦🇹',':flag_at:':'🇦🇹',':au:':'🇦🇺',':flag_au:':'🇦🇺',':aw:':'🇦🇼',':flag_aw:':'🇦🇼',':ax:':'🇦🇽',':flag_ax:':'🇦🇽',':az:':'🇦🇿',':flag_az:':'🇦🇿',':ba:':'🇧🇦',':flag_ba:':'🇧🇦',':bb:':'🇧🇧',':flag_bb:':'🇧🇧',':bd:':'🇧🇩',':flag_bd:':'🇧🇩',':be:':'🇧🇪',':flag_be:':'🇧🇪',':bf:':'🇧🇫',':flag_bf:':'🇧🇫',':bg:':'🇧🇬',':flag_bg:':'🇧🇬',':bh:':'🇧🇭',':flag_bh:':'🇧🇭',':bi:':'🇧🇮',':flag_bi:':'🇧🇮',':bj:':'🇧🇯',':flag_bj:':'🇧🇯',':bm:':'🇧🇲',':flag_bm:':'🇧🇲',':bn:':'🇧🇳',':flag_bn:':'🇧🇳',':bo:':'🇧🇴',':flag_bo:':'🇧🇴',':br:':'🇧🇷',':flag_br:':'🇧🇷',':bs:':'🇧🇸',':flag_bs:':'🇧🇸',':bt:':'🇧🇹',':flag_bt:':'🇧🇹',':bv:':'🇧🇻',':flag_bv:':'🇧🇻',':bw:':'🇧🇼',':flag_bw:':'🇧🇼',':by:':'🇧🇾',':flag_by:':'🇧🇾',':bz:':'🇧🇿',':flag_bz:':'🇧🇿',':ca:':'🇨🇦',':flag_ca:':'🇨🇦',':cc:':'🇨🇨',':flag_cc:':'🇨🇨',':congo:':'🇨🇩',':flag_cd:':'🇨🇩',':cf:':'🇨🇫',':flag_cf:':'🇨🇫',':cg:':'🇨🇬',':flag_cg:':'🇨🇬',':ch:':'🇨🇭',':flag_ch:':'🇨🇭',':ci:':'🇨🇮',':flag_ci:':'🇨🇮',':ck:':'🇨🇰',':flag_ck:':'🇨🇰',':chile:':'🇨🇱',':flag_cl:':'🇨🇱',':cm:':'🇨🇲',':flag_cm:':'🇨🇲',':cn:':'🇨🇳',':flag_cn:':'🇨🇳',':co:':'🇨🇴',':flag_co:':'🇨🇴',':cp:':'🇨🇵',':flag_cp:':'🇨🇵',':cr:':'🇨🇷',':flag_cr:':'🇨🇷',':cu:':'🇨🇺',':flag_cu:':'🇨🇺',':cv:':'🇨🇻',':flag_cv:':'🇨🇻',':cw:':'🇨🇼',':flag_cw:':'🇨🇼',':cx:':'🇨🇽',':flag_cx:':'🇨🇽',':cy:':'🇨🇾',':flag_cy:':'🇨🇾',':cz:':'🇨🇿',':flag_cz:':'🇨🇿',':de:':'🇩🇪',':flag_de:':'🇩🇪',':dj:':'🇩🇯',':flag_dj:':'🇩🇯',':dk:':'🇩🇰',':flag_dk:':'🇩🇰',':dm:':'🇩🇲',':flag_dm:':'🇩🇲',':do:':'🇩🇴',':flag_do:':'🇩🇴',':dz:':'🇩🇿',':flag_dz:':'🇩🇿',':ec:':'🇪🇨',':flag_ec:':'🇪🇨',':ee:':'🇪🇪',':flag_ee:':'🇪🇪',':eg:':'🇪🇬',':flag_eg:':'🇪🇬',':er:':'🇪🇷',':flag_er:':'🇪🇷',':es:':'🇪🇸',':flag_es:':'🇪🇸',':et:':'🇪🇹',':flag_et:':'🇪🇹',':eu:':'🇪🇺',':flag_eu:':'🇪🇺',':fi:':'🇫🇮',':flag_fi:':'🇫🇮',':fj:':'🇫🇯',':flag_fj:':'🇫🇯',':fm:':'🇫🇲',':flag_fm:':'🇫🇲',':fo:':'🇫🇴',':flag_fo:':'🇫🇴',':fr:':'🇫🇷',':flag_fr:':'🇫🇷',':ga:':'🇬🇦',':flag_ga:':'🇬🇦',':gb:':'🇬🇧',':flag_gb:':'🇬🇧',':gd:':'🇬🇩',':flag_gd:':'🇬🇩',':ge:':'🇬🇪',':flag_ge:':'🇬🇪',':gg:':'🇬🇬',':flag_gg:':'🇬🇬',':gh:':'🇬🇭',':flag_gh:':'🇬🇭',':gi:':'🇬🇮',':flag_gi:':'🇬🇮',':gl:':'🇬🇱',':flag_gl:':'🇬🇱',':gm:':'🇬🇲',':flag_gm:':'🇬🇲',':gn:':'🇬🇳',':flag_gn:':'🇬🇳',':gq:':'🇬🇶',':flag_gq:':'🇬🇶',':gr:':'🇬🇷',':flag_gr:':'🇬🇷',':gt:':'🇬🇹',':flag_gt:':'🇬🇹',':gu:':'🇬🇺',':flag_gu:':'🇬🇺',':gw:':'🇬🇼',':flag_gw:':'🇬🇼',':gy:':'🇬🇾',':flag_gy:':'🇬🇾',':hk:':'🇭🇰',':flag_hk:':'🇭🇰',':hm:':'🇭🇲',':flag_hm:':'🇭🇲',':hn:':'🇭🇳',':flag_hn:':'🇭🇳',':hr:':'🇭🇷',':flag_hr:':'🇭🇷',':ht:':'🇭🇹',':flag_ht:':'🇭🇹',':hu:':'🇭🇺',':flag_hu:':'🇭🇺',':ic:':'🇮🇨',':flag_ic:':'🇮🇨',':indonesia:':'🇮🇩',':flag_id:':'🇮🇩',':ie:':'🇮🇪',':flag_ie:':'🇮🇪',':il:':'🇮🇱',':flag_il:':'🇮🇱',':im:':'🇮🇲',':flag_im:':'🇮🇲',':in:':'🇮🇳',':flag_in:':'🇮🇳',':io:':'🇮🇴',':flag_io:':'🇮🇴',':iq:':'🇮🇶',':flag_iq:':'🇮🇶',':ir:':'🇮🇷',':flag_ir:':'🇮🇷',':is:':'🇮🇸',':flag_is:':'🇮🇸',':it:':'🇮🇹',':flag_it:':'🇮🇹',':je:':'🇯🇪',':flag_je:':'🇯🇪',':jm:':'🇯🇲',':flag_jm:':'🇯🇲',':jo:':'🇯🇴',':flag_jo:':'🇯🇴',':jp:':'🇯🇵',':flag_jp:':'🇯🇵',':ke:':'🇰🇪',':flag_ke:':'🇰🇪',':kg:':'🇰🇬',':flag_kg:':'🇰🇬',':kh:':'🇰🇭',':flag_kh:':'🇰🇭',':ki:':'🇰🇮',':flag_ki:':'🇰🇮',':km:':'🇰🇲',':flag_km:':'🇰🇲',':kn:':'🇰🇳',':flag_kn:':'🇰🇳',':kp:':'🇰🇵',':flag_kp:':'🇰🇵',':kr:':'🇰🇷',':flag_kr:':'🇰🇷',':kw:':'🇰🇼',':flag_kw:':'🇰🇼',':ky:':'🇰🇾',':flag_ky:':'🇰🇾',':kz:':'🇰🇿',':flag_kz:':'🇰🇿',':la:':'🇱🇦',':flag_la:':'🇱🇦',':lb:':'🇱🇧',':flag_lb:':'🇱🇧',':lc:':'🇱🇨',':flag_lc:':'🇱🇨',':li:':'🇱🇮',':flag_li:':'🇱🇮',':lk:':'🇱🇰',':flag_lk:':'🇱🇰',':lr:':'🇱🇷',':flag_lr:':'🇱🇷',':ls:':'🇱🇸',':flag_ls:':'🇱🇸',':lt:':'🇱🇹',':flag_lt:':'🇱🇹',':lu:':'🇱🇺',':flag_lu:':'🇱🇺',':lv:':'🇱🇻',':flag_lv:':'🇱🇻',':ly:':'🇱🇾',':flag_ly:':'🇱🇾',':ma:':'🇲🇦',':flag_ma:':'🇲🇦',':mc:':'🇲🇨',':flag_mc:':'🇲🇨',':md:':'🇲🇩',':flag_md:':'🇲🇩',':me:':'🇲🇪',':flag_me:':'🇲🇪',':mg:':'🇲🇬',':flag_mg:':'🇲🇬',':mh:':'🇲🇭',':flag_mh:':'🇲🇭',':mk:':'🇲🇰',':flag_mk:':'🇲🇰',':ml:':'🇲🇱',':flag_ml:':'🇲🇱',':mm:':'🇲🇲',':flag_mm:':'🇲🇲',':mn:':'🇲🇳',':flag_mn:':'🇲🇳',':mo:':'🇲🇴',':flag_mo:':'🇲🇴',':mp:':'🇲🇵',':flag_mp:':'🇲🇵',':mr:':'🇲🇷',':flag_mr:':'🇲🇷',':ms:':'🇲🇸',':flag_ms:':'🇲🇸',':mt:':'🇲🇹',':flag_mt:':'🇲🇹',':mu:':'🇲🇺',':flag_mu:':'🇲🇺',':mv:':'🇲🇻',':flag_mv:':'🇲🇻',':mw:':'🇲🇼',':flag_mw:':'🇲🇼',':mx:':'🇲🇽',':flag_mx:':'🇲🇽',':my:':'🇲🇾',':flag_my:':'🇲🇾',':mz:':'🇲🇿',':flag_mz:':'🇲🇿',':na:':'🇳🇦',':flag_na:':'🇳🇦',':ne:':'🇳🇪',':flag_ne:':'🇳🇪',':nf:':'🇳🇫',':flag_nf:':'🇳🇫',':nigeria:':'🇳🇬',':flag_ng:':'🇳🇬',':ni:':'🇳🇮',':flag_ni:':'🇳🇮',':nl:':'🇳🇱',':flag_nl:':'🇳🇱',':no:':'🇳🇴',':flag_no:':'🇳🇴',':np:':'🇳🇵',':flag_np:':'🇳🇵',':nr:':'🇳🇷',':flag_nr:':'🇳🇷',':nu:':'🇳🇺',':flag_nu:':'🇳🇺',':nz:':'🇳🇿',':flag_nz:':'🇳🇿',':om:':'🇴🇲',':flag_om:':'🇴🇲',':pa:':'🇵🇦',':flag_pa:':'🇵🇦',':pe:':'🇵🇪',':flag_pe:':'🇵🇪',':pf:':'🇵🇫',':flag_pf:':'🇵🇫',':pg:':'🇵🇬',':flag_pg:':'🇵🇬',':ph:':'🇵🇭',':flag_ph:':'🇵🇭',':pk:':'🇵🇰',':flag_pk:':'🇵🇰',':pl:':'🇵🇱',':flag_pl:':'🇵🇱',':pn:':'🇵🇳',':flag_pn:':'🇵🇳',':pr:':'🇵🇷',':flag_pr:':'🇵🇷',':ps:':'🇵🇸',':flag_ps:':'🇵🇸',':pt:':'🇵🇹',':flag_pt:':'🇵🇹',':pw:':'🇵🇼',':flag_pw:':'🇵🇼',':py:':'🇵🇾',':flag_py:':'🇵🇾',':qa:':'🇶🇦',':flag_qa:':'🇶🇦',':ro:':'🇷🇴',':flag_ro:':'🇷🇴',':rs:':'🇷🇸',':flag_rs:':'🇷🇸',':ru:':'🇷🇺',':flag_ru:':'🇷🇺',':rw:':'🇷🇼',':flag_rw:':'🇷🇼',':saudiarabia:':'🇸🇦',':saudi:':'🇸🇦',':flag_sa:':'🇸🇦',':sb:':'🇸🇧',':flag_sb:':'🇸🇧',':sc:':'🇸🇨',':flag_sc:':'🇸🇨',':sd:':'🇸🇩',':flag_sd:':'🇸🇩',':se:':'🇸🇪',':flag_se:':'🇸🇪',':sg:':'🇸🇬',':flag_sg:':'🇸🇬',':sh:':'🇸🇭',':flag_sh:':'🇸🇭',':si:':'🇸🇮',':flag_si:':'🇸🇮',':sj:':'🇸🇯',':flag_sj:':'🇸🇯',':sk:':'🇸🇰',':flag_sk:':'🇸🇰',':sl:':'🇸🇱',':flag_sl:':'🇸🇱',':sm:':'🇸🇲',':flag_sm:':'🇸🇲',':sn:':'🇸🇳',':flag_sn:':'🇸🇳',':so:':'🇸🇴',':flag_so:':'🇸🇴',':sr:':'🇸🇷',':flag_sr:':'🇸🇷',':ss:':'🇸🇸',':flag_ss:':'🇸🇸',':st:':'🇸🇹',':flag_st:':'🇸🇹',':sv:':'🇸🇻',':flag_sv:':'🇸🇻',':sx:':'🇸🇽',':flag_sx:':'🇸🇽',':sy:':'🇸🇾',':flag_sy:':'🇸🇾',':sz:':'🇸🇿',':flag_sz:':'🇸🇿',':ta:':'🇹🇦',':flag_ta:':'🇹🇦',':tc:':'🇹🇨',':flag_tc:':'🇹🇨',':td:':'🇹🇩',':flag_td:':'🇹🇩',':tg:':'🇹🇬',':flag_tg:':'🇹🇬',':th:':'🇹🇭',':flag_th:':'🇹🇭',':tj:':'🇹🇯',':flag_tj:':'🇹🇯',':tk:':'🇹🇰',':flag_tk:':'🇹🇰',':tl:':'🇹🇱',':flag_tl:':'🇹🇱',':turkmenistan:':'🇹🇲',':flag_tm:':'🇹🇲',':tn:':'🇹🇳',':flag_tn:':'🇹🇳',':to:':'🇹🇴',':flag_to:':'🇹🇴',':tr:':'🇹🇷',':flag_tr:':'🇹🇷',':tt:':'🇹🇹',':flag_tt:':'🇹🇹',':tuvalu:':'🇹🇻',':flag_tv:':'🇹🇻',':tw:':'🇹🇼',':flag_tw:':'🇹🇼',':tz:':'🇹🇿',':flag_tz:':'🇹🇿',':ua:':'🇺🇦',':flag_ua:':'🇺🇦',':ug:':'🇺🇬',':flag_ug:':'🇺🇬',':um:':'🇺🇲',':flag_um:':'🇺🇲',':us:':'🇺🇸',':flag_us:':'🇺🇸',':uy:':'🇺🇾',':flag_uy:':'🇺🇾',':uz:':'🇺🇿',':flag_uz:':'🇺🇿',':va:':'🇻🇦',':flag_va:':'🇻🇦',':vc:':'🇻🇨',':flag_vc:':'🇻🇨',':ve:':'🇻🇪',':flag_ve:':'🇻🇪',':vg:':'🇻🇬',':flag_vg:':'🇻🇬',':vi:':'🇻🇮',':flag_vi:':'🇻🇮',':vn:':'🇻🇳',':flag_vn:':'🇻🇳',':vu:':'🇻🇺',':flag_vu:':'🇻🇺',':ws:':'🇼🇸',':flag_ws:':'🇼🇸',':ye:':'🇾🇪',':flag_ye:':'🇾🇪',':za:':'🇿🇦',':flag_za:':'🇿🇦',':zm:':'🇿🇲',':flag_zm:':'🇿🇲',':zw:':'🇿🇼',':flag_zw:':'🇿🇼',':foot_light_skin_tone:':'🦶🏻',':foot_tone1:':'🦶🏻',':foot_medium_light_skin_tone:':'🦶🏼',':foot_tone2:':'🦶🏼',':foot_medium_skin_tone:':'🦶🏽',':foot_tone3:':'🦶🏽',':foot_medium_dark_skin_tone:':'🦶🏾',':foot_tone4:':'🦶🏾',':foot_dark_skin_tone:':'🦶🏿',':foot_tone5:':'🦶🏿',':girl_tone1:':'👧🏻',':girl_tone2:':'👧🏼',':girl_tone3:':'👧🏽',':girl_tone4:':'👧🏾',':girl_tone5:':'👧🏿',':guardsman_tone1:':'💂🏻',':guard_tone1:':'💂🏻',':guardsman_tone2:':'💂🏼',':guard_tone2:':'💂🏼',':guardsman_tone3:':'💂🏽',':guard_tone3:':'💂🏽',':guardsman_tone4:':'💂🏾',':guard_tone4:':'💂🏾',':guardsman_tone5:':'💂🏿',':guard_tone5:':'💂🏿',':raised_hand_with_fingers_splayed_tone1:':'🖐️🏻',':hand_splayed_tone1:':'🖐️🏻',':raised_hand_with_fingers_splayed_tone2:':'🖐️🏼',':hand_splayed_tone2:':'🖐️🏼',':raised_hand_with_fingers_splayed_tone3:':'🖐️🏽',':hand_splayed_tone3:':'🖐️🏽',':raised_hand_with_fingers_splayed_tone4:':'🖐️🏾',':hand_splayed_tone4:':'🖐️🏾',':raised_hand_with_fingers_splayed_tone5:':'🖐️🏿',':hand_splayed_tone5:':'🖐️🏿',':horse_racing_tone1:':'🏇🏻',':horse_racing_tone2:':'🏇🏼',':horse_racing_tone3:':'🏇🏽',':horse_racing_tone4:':'🏇🏾',':horse_racing_tone5:':'🏇🏿',':left_fist_tone1:':'🤛🏻',':left_facing_fist_tone1:':'🤛🏻',':left_fist_tone2:':'🤛🏼',':left_facing_fist_tone2:':'🤛🏼',':left_fist_tone3:':'🤛🏽',':left_facing_fist_tone3:':'🤛🏽',':left_fist_tone4:':'🤛🏾',':left_facing_fist_tone4:':'🤛🏾',':left_fist_tone5:':'🤛🏿',':left_facing_fist_tone5:':'🤛🏿',':leg_light_skin_tone:':'🦵🏻',':leg_tone1:':'🦵🏻',':leg_medium_light_skin_tone:':'🦵🏼',':leg_tone2:':'🦵🏼',':leg_medium_skin_tone:':'🦵🏽',':leg_tone3:':'🦵🏽',':leg_medium_dark_skin_tone:':'🦵🏾',':leg_tone4:':'🦵🏾',':leg_dark_skin_tone:':'🦵🏿',':leg_tone5:':'🦵🏿',':man_in_business_suit_levitating_tone1:':'🕴️🏻',':man_in_business_suit_levitating_light_skin_tone:':'🕴️🏻',':levitate_tone1:':'🕴️🏻',':man_in_business_suit_levitating_tone2:':'🕴️🏼',':man_in_business_suit_levitating_medium_light_skin_tone:':'🕴️🏼',':levitate_tone2:':'🕴️🏼',':man_in_business_suit_levitating_tone3:':'🕴️🏽',':man_in_business_suit_levitating_medium_skin_tone:':'🕴️🏽',':levitate_tone3:':'🕴️🏽',':man_in_business_suit_levitating_tone4:':'🕴️🏾',':man_in_business_suit_levitating_medium_dark_skin_tone:':'🕴️🏾',':levitate_tone4:':'🕴️🏾',':man_in_business_suit_levitating_tone5:':'🕴️🏿',':man_in_business_suit_levitating_dark_skin_tone:':'🕴️🏿',':levitate_tone5:':'🕴️🏿',':love_you_gesture_light_skin_tone:':'🤟🏻',':love_you_gesture_tone1:':'🤟🏻',':love_you_gesture_medium_light_skin_tone:':'🤟🏼',':love_you_gesture_tone2:':'🤟🏼',':love_you_gesture_medium_skin_tone:':'🤟🏽',':love_you_gesture_tone3:':'🤟🏽',':love_you_gesture_medium_dark_skin_tone:':'🤟🏾',':love_you_gesture_tone4:':'🤟🏾',':love_you_gesture_dark_skin_tone:':'🤟🏿',':love_you_gesture_tone5:':'🤟🏿',':mage_light_skin_tone:':'🧙🏻',':mage_tone1:':'🧙🏻',':mage_medium_light_skin_tone:':'🧙🏼',':mage_tone2:':'🧙🏼',':mage_medium_skin_tone:':'🧙🏽',':mage_tone3:':'🧙🏽',':mage_medium_dark_skin_tone:':'🧙🏾',':mage_tone4:':'🧙🏾',':mage_dark_skin_tone:':'🧙🏿',':mage_tone5:':'🧙🏿',':man_artist:':'👨‍🎨',':man_astronaut:':'👨‍🚀',':man_bald:':'👨‍🦲',':man_cook:':'👨‍🍳',':man_curly_haired:':'👨‍🦱',':male_dancer_tone1:':'🕺🏻',':man_dancing_tone1:':'🕺🏻',':male_dancer_tone2:':'🕺🏼',':man_dancing_tone2:':'🕺🏼',':male_dancer_tone3:':'🕺🏽',':man_dancing_tone3:':'🕺🏽',':male_dancer_tone4:':'🕺🏾',':man_dancing_tone4:':'🕺🏾',':male_dancer_tone5:':'🕺🏿',':man_dancing_tone5:':'🕺🏿',':man_factory_worker:':'👨‍🏭',':man_farmer:':'👨‍🌾',':man_firefighter:':'👨‍🚒',':man_in_manual_wheelchair:':'👨‍🦽',':man_in_motorized_wheelchair:':'👨‍🦼',':tuxedo_tone1:':'🤵🏻',':man_in_tuxedo_tone1:':'🤵🏻',':tuxedo_tone2:':'🤵🏼',':man_in_tuxedo_tone2:':'🤵🏼',':tuxedo_tone3:':'🤵🏽',':man_in_tuxedo_tone3:':'🤵🏽',':tuxedo_tone4:':'🤵🏾',':man_in_tuxedo_tone4:':'🤵🏾',':tuxedo_tone5:':'🤵🏿',':man_in_tuxedo_tone5:':'🤵🏿',':man_mechanic:':'👨‍🔧',':man_office_worker:':'👨‍💼',':man_red_haired:':'👨‍🦰',':man_scientist:':'👨‍🔬',':man_singer:':'👨‍🎤',':man_student:':'👨‍🎓',':man_teacher:':'👨‍🏫',':man_technologist:':'👨‍💻',':man_tone1:':'👨🏻',':man_tone2:':'👨🏼',':man_tone3:':'👨🏽',':man_tone4:':'👨🏾',':man_tone5:':'👨🏿',':man_white_haired:':'👨‍🦳',':man_with_gua_pi_mao_tone1:':'👲🏻',':man_with_chinese_cap_tone1:':'👲🏻',':man_with_gua_pi_mao_tone2:':'👲🏼',':man_with_chinese_cap_tone2:':'👲🏼',':man_with_gua_pi_mao_tone3:':'👲🏽',':man_with_chinese_cap_tone3:':'👲🏽',':man_with_gua_pi_mao_tone4:':'👲🏾',':man_with_chinese_cap_tone4:':'👲🏾',':man_with_gua_pi_mao_tone5:':'👲🏿',':man_with_chinese_cap_tone5:':'👲🏿',':man_with_probing_cane:':'👨‍🦯',':men_holding_hands_light_skin_tone:':'👬🏻',':men_holding_hands_tone1:':'👬🏻',':men_holding_hands_medium_light_skin_tone:':'👬🏼',':men_holding_hands_tone2:':'👬🏼',':men_holding_hands_medium_skin_tone:':'👬🏽',':men_holding_hands_tone3:':'👬🏽',':men_holding_hands_medium_dark_skin_tone:':'👬🏾',':men_holding_hands_tone4:':'👬🏾',':men_holding_hands_dark_skin_tone:':'👬🏿',':men_holding_hands_tone5:':'👬🏿',':merperson_light_skin_tone:':'🧜🏻',':merperson_tone1:':'🧜🏻',':merperson_medium_light_skin_tone:':'🧜🏼',':merperson_tone2:':'🧜🏼',':merperson_medium_skin_tone:':'🧜🏽',':merperson_tone3:':'🧜🏽',':merperson_medium_dark_skin_tone:':'🧜🏾',':merperson_tone4:':'🧜🏾',':merperson_dark_skin_tone:':'🧜🏿',':merperson_tone5:':'🧜🏿',':sign_of_the_horns_tone1:':'🤘🏻',':metal_tone1:':'🤘🏻',':sign_of_the_horns_tone2:':'🤘🏼',':metal_tone2:':'🤘🏼',':sign_of_the_horns_tone3:':'🤘🏽',':metal_tone3:':'🤘🏽',':sign_of_the_horns_tone4:':'🤘🏾',':metal_tone4:':'🤘🏾',':sign_of_the_horns_tone5:':'🤘🏿',':metal_tone5:':'🤘🏿',':reversed_hand_with_middle_finger_extended_tone1:':'🖕🏻',':middle_finger_tone1:':'🖕🏻',':reversed_hand_with_middle_finger_extended_tone2:':'🖕🏼',':middle_finger_tone2:':'🖕🏼',':reversed_hand_with_middle_finger_extended_tone3:':'🖕🏽',':middle_finger_tone3:':'🖕🏽',':reversed_hand_with_middle_finger_extended_tone4:':'🖕🏾',':middle_finger_tone4:':'🖕🏾',':reversed_hand_with_middle_finger_extended_tone5:':'🖕🏿',':middle_finger_tone5:':'🖕🏿',':mother_christmas_tone1:':'🤶🏻',':mrs_claus_tone1:':'🤶🏻',':mother_christmas_tone2:':'🤶🏼',':mrs_claus_tone2:':'🤶🏼',':mother_christmas_tone3:':'🤶🏽',':mrs_claus_tone3:':'🤶🏽',':mother_christmas_tone4:':'🤶🏾',':mrs_claus_tone4:':'🤶🏾',':mother_christmas_tone5:':'🤶🏿',':mrs_claus_tone5:':'🤶🏿',':muscle_tone1:':'💪🏻',':muscle_tone2:':'💪🏼',':muscle_tone3:':'💪🏽',':muscle_tone4:':'💪🏾',':muscle_tone5:':'💪🏿',':nail_care_tone1:':'💅🏻',':nail_care_tone2:':'💅🏼',':nail_care_tone3:':'💅🏽',':nail_care_tone4:':'💅🏾',':nail_care_tone5:':'💅🏿',':nose_tone1:':'👃🏻',':nose_tone2:':'👃🏼',':nose_tone3:':'👃🏽',':nose_tone4:':'👃🏾',':nose_tone5:':'👃🏿',':ok_hand_tone1:':'👌🏻',':ok_hand_tone2:':'👌🏼',':ok_hand_tone3:':'👌🏽',':ok_hand_tone4:':'👌🏾',':ok_hand_tone5:':'👌🏿',':older_adult_light_skin_tone:':'🧓🏻',':older_adult_tone1:':'🧓🏻',':older_adult_medium_light_skin_tone:':'🧓🏼',':older_adult_tone2:':'🧓🏼',':older_adult_medium_skin_tone:':'🧓🏽',':older_adult_tone3:':'🧓🏽',':older_adult_medium_dark_skin_tone:':'🧓🏾',':older_adult_tone4:':'🧓🏾',':older_adult_dark_skin_tone:':'🧓🏿',':older_adult_tone5:':'🧓🏿',':older_man_tone1:':'👴🏻',':older_man_tone2:':'👴🏼',':older_man_tone3:':'👴🏽',':older_man_tone4:':'👴🏾',':older_man_tone5:':'👴🏿',':grandma_tone1:':'👵🏻',':older_woman_tone1:':'👵🏻',':grandma_tone2:':'👵🏼',':older_woman_tone2:':'👵🏼',':grandma_tone3:':'👵🏽',':older_woman_tone3:':'👵🏽',':grandma_tone4:':'👵🏾',':older_woman_tone4:':'👵🏾',':grandma_tone5:':'👵🏿',':older_woman_tone5:':'👵🏿',':open_hands_tone1:':'👐🏻',':open_hands_tone2:':'👐🏼',':open_hands_tone3:':'👐🏽',':open_hands_tone4:':'👐🏾',':open_hands_tone5:':'👐🏿',':palms_up_together_light_skin_tone:':'🤲🏻',':palms_up_together_tone1:':'🤲🏻',':palms_up_together_medium_light_skin_tone:':'🤲🏼',':palms_up_together_tone2:':'🤲🏼',':palms_up_together_medium_skin_tone:':'🤲🏽',':palms_up_together_tone3:':'🤲🏽',':palms_up_together_medium_dark_skin_tone:':'🤲🏾',':palms_up_together_tone4:':'🤲🏾',':palms_up_together_dark_skin_tone:':'🤲🏿',':palms_up_together_tone5:':'🤲🏿',':bicyclist_tone1:':'🚴🏻',':person_biking_tone1:':'🚴🏻',':bicyclist_tone2:':'🚴🏼',':person_biking_tone2:':'🚴🏼',':bicyclist_tone3:':'🚴🏽',':person_biking_tone3:':'🚴🏽',':bicyclist_tone4:':'🚴🏾',':person_biking_tone4:':'🚴🏾',':bicyclist_tone5:':'🚴🏿',':person_biking_tone5:':'🚴🏿',':bow_tone1:':'🙇🏻',':person_bowing_tone1:':'🙇🏻',':bow_tone2:':'🙇🏼',':person_bowing_tone2:':'🙇🏼',':bow_tone3:':'🙇🏽',':person_bowing_tone3:':'🙇🏽',':bow_tone4:':'🙇🏾',':person_bowing_tone4:':'🙇🏾',':bow_tone5:':'🙇🏿',':person_bowing_tone5:':'🙇🏿',':person_climbing_light_skin_tone:':'🧗🏻',':person_climbing_tone1:':'🧗🏻',':person_climbing_medium_light_skin_tone:':'🧗🏼',':person_climbing_tone2:':'🧗🏼',':person_climbing_medium_skin_tone:':'🧗🏽',':person_climbing_tone3:':'🧗🏽',':person_climbing_medium_dark_skin_tone:':'🧗🏾',':person_climbing_tone4:':'🧗🏾',':person_climbing_dark_skin_tone:':'🧗🏿',':person_climbing_tone5:':'🧗🏿',':cartwheel_tone1:':'🤸🏻',':person_doing_cartwheel_tone1:':'🤸🏻',':cartwheel_tone2:':'🤸🏼',':person_doing_cartwheel_tone2:':'🤸🏼',':cartwheel_tone3:':'🤸🏽',':person_doing_cartwheel_tone3:':'🤸🏽',':cartwheel_tone4:':'🤸🏾',':person_doing_cartwheel_tone4:':'🤸🏾',':cartwheel_tone5:':'🤸🏿',':person_doing_cartwheel_tone5:':'🤸🏿',':face_palm_tone1:':'🤦🏻',':facepalm_tone1:':'🤦🏻',':person_facepalming_tone1:':'🤦🏻',':face_palm_tone2:':'🤦🏼',':facepalm_tone2:':'🤦🏼',':person_facepalming_tone2:':'🤦🏼',':face_palm_tone3:':'🤦🏽',':facepalm_tone3:':'🤦🏽',':person_facepalming_tone3:':'🤦🏽',':face_palm_tone4:':'🤦🏾',':facepalm_tone4:':'🤦🏾',':person_facepalming_tone4:':'🤦🏾',':face_palm_tone5:':'🤦🏿',':facepalm_tone5:':'🤦🏿',':person_facepalming_tone5:':'🤦🏿',':person_frowning_tone1:':'🙍🏻',':person_frowning_tone2:':'🙍🏼',':person_frowning_tone3:':'🙍🏽',':person_frowning_tone4:':'🙍🏾',':person_frowning_tone5:':'🙍🏿',':no_good_tone1:':'🙅🏻',':person_gesturing_no_tone1:':'🙅🏻',':no_good_tone2:':'🙅🏼',':person_gesturing_no_tone2:':'🙅🏼',':no_good_tone3:':'🙅🏽',':person_gesturing_no_tone3:':'🙅🏽',':no_good_tone4:':'🙅🏾',':person_gesturing_no_tone4:':'🙅🏾',':no_good_tone5:':'🙅🏿',':person_gesturing_no_tone5:':'🙅🏿',':ok_woman_tone1:':'🙆🏻',':person_gesturing_ok_tone1:':'🙆🏻',':ok_woman_tone2:':'🙆🏼',':person_gesturing_ok_tone2:':'🙆🏼',':ok_woman_tone3:':'🙆🏽',':person_gesturing_ok_tone3:':'🙆🏽',':ok_woman_tone4:':'🙆🏾',':person_gesturing_ok_tone4:':'🙆🏾',':ok_woman_tone5:':'🙆🏿',':person_gesturing_ok_tone5:':'🙆🏿',':haircut_tone1:':'💇🏻',':person_getting_haircut_tone1:':'💇🏻',':haircut_tone2:':'💇🏼',':person_getting_haircut_tone2:':'💇🏼',':haircut_tone3:':'💇🏽',':person_getting_haircut_tone3:':'💇🏽',':haircut_tone4:':'💇🏾',':person_getting_haircut_tone4:':'💇🏾',':haircut_tone5:':'💇🏿',':person_getting_haircut_tone5:':'💇🏿',':massage_tone1:':'💆🏻',':person_getting_massage_tone1:':'💆🏻',':massage_tone2:':'💆🏼',':person_getting_massage_tone2:':'💆🏼',':massage_tone3:':'💆🏽',':person_getting_massage_tone3:':'💆🏽',':massage_tone4:':'💆🏾',':person_getting_massage_tone4:':'💆🏾',':massage_tone5:':'💆🏿',':person_getting_massage_tone5:':'💆🏿',':person_golfing_light_skin_tone:':'🏌️🏻',':person_golfing_tone1:':'🏌️🏻',':person_golfing_medium_light_skin_tone:':'🏌️🏼',':person_golfing_tone2:':'🏌️🏼',':person_golfing_medium_skin_tone:':'🏌️🏽',':person_golfing_tone3:':'🏌️🏽',':person_golfing_medium_dark_skin_tone:':'🏌️🏾',':person_golfing_tone4:':'🏌️🏾',':person_golfing_dark_skin_tone:':'🏌️🏿',':person_golfing_tone5:':'🏌️🏿',':person_in_bed_light_skin_tone:':'🛌🏻',':person_in_bed_tone1:':'🛌🏻',':person_in_bed_medium_light_skin_tone:':'🛌🏼',':person_in_bed_tone2:':'🛌🏼',':person_in_bed_medium_skin_tone:':'🛌🏽',':person_in_bed_tone3:':'🛌🏽',':person_in_bed_medium_dark_skin_tone:':'🛌🏾',':person_in_bed_tone4:':'🛌🏾',':person_in_bed_dark_skin_tone:':'🛌🏿',':person_in_bed_tone5:':'🛌🏿',':person_in_lotus_position_light_skin_tone:':'🧘🏻',':person_in_lotus_position_tone1:':'🧘🏻',':person_in_lotus_position_medium_light_skin_tone:':'🧘🏼',':person_in_lotus_position_tone2:':'🧘🏼',':person_in_lotus_position_medium_skin_tone:':'🧘🏽',':person_in_lotus_position_tone3:':'🧘🏽',':person_in_lotus_position_medium_dark_skin_tone:':'🧘🏾',':person_in_lotus_position_tone4:':'🧘🏾',':person_in_lotus_position_dark_skin_tone:':'🧘🏿',':person_in_lotus_position_tone5:':'🧘🏿',':person_in_steamy_room_light_skin_tone:':'🧖🏻',':person_in_steamy_room_tone1:':'🧖🏻',':person_in_steamy_room_medium_light_skin_tone:':'🧖🏼',':person_in_steamy_room_tone2:':'🧖🏼',':person_in_steamy_room_medium_skin_tone:':'🧖🏽',':person_in_steamy_room_tone3:':'🧖🏽',':person_in_steamy_room_medium_dark_skin_tone:':'🧖🏾',':person_in_steamy_room_tone4:':'🧖🏾',':person_in_steamy_room_dark_skin_tone:':'🧖🏿',':person_in_steamy_room_tone5:':'🧖🏿',':juggling_tone1:':'🤹🏻',':juggler_tone1:':'🤹🏻',':person_juggling_tone1:':'🤹🏻',':juggling_tone2:':'🤹🏼',':juggler_tone2:':'🤹🏼',':person_juggling_tone2:':'🤹🏼',':juggling_tone3:':'🤹🏽',':juggler_tone3:':'🤹🏽',':person_juggling_tone3:':'🤹🏽',':juggling_tone4:':'🤹🏾',':juggler_tone4:':'🤹🏾',':person_juggling_tone4:':'🤹🏾',':juggling_tone5:':'🤹🏿',':juggler_tone5:':'🤹🏿',':person_juggling_tone5:':'🤹🏿',':person_kneeling_light_skin_tone:':'🧎🏻',':person_kneeling_tone1:':'🧎🏻',':person_kneeling_medium_light_skin_tone:':'🧎🏼',':person_kneeling_tone2:':'🧎🏼',':person_kneeling_medium_skin_tone:':'🧎🏽',':person_kneeling_tone3:':'🧎🏽',':person_kneeling_medium_dark_skin_tone:':'🧎🏾',':person_kneeling_tone4:':'🧎🏾',':person_kneeling_dark_skin_tone:':'🧎🏿',':person_kneeling_tone5:':'🧎🏿',':lifter_tone1:':'🏋️🏻',':weight_lifter_tone1:':'🏋️🏻',':person_lifting_weights_tone1:':'🏋️🏻',':lifter_tone2:':'🏋️🏼',':weight_lifter_tone2:':'🏋️🏼',':person_lifting_weights_tone2:':'🏋️🏼',':lifter_tone3:':'🏋️🏽',':weight_lifter_tone3:':'🏋️🏽',':person_lifting_weights_tone3:':'🏋️🏽',':lifter_tone4:':'🏋️🏾',':weight_lifter_tone4:':'🏋️🏾',':person_lifting_weights_tone4:':'🏋️🏾',':lifter_tone5:':'🏋️🏿',':weight_lifter_tone5:':'🏋️🏿',':person_lifting_weights_tone5:':'🏋️🏿',':mountain_bicyclist_tone1:':'🚵🏻',':person_mountain_biking_tone1:':'🚵🏻',':mountain_bicyclist_tone2:':'🚵🏼',':person_mountain_biking_tone2:':'🚵🏼',':mountain_bicyclist_tone3:':'🚵🏽',':person_mountain_biking_tone3:':'🚵🏽',':mountain_bicyclist_tone4:':'🚵🏾',':person_mountain_biking_tone4:':'🚵🏾',':mountain_bicyclist_tone5:':'🚵🏿',':person_mountain_biking_tone5:':'🚵🏿',':handball_tone1:':'🤾🏻',':person_playing_handball_tone1:':'🤾🏻',':handball_tone2:':'🤾🏼',':person_playing_handball_tone2:':'🤾🏼',':handball_tone3:':'🤾🏽',':person_playing_handball_tone3:':'🤾🏽',':handball_tone4:':'🤾🏾',':person_playing_handball_tone4:':'🤾🏾',':handball_tone5:':'🤾🏿',':person_playing_handball_tone5:':'🤾🏿',':water_polo_tone1:':'🤽🏻',':person_playing_water_polo_tone1:':'🤽🏻',':water_polo_tone2:':'🤽🏼',':person_playing_water_polo_tone2:':'🤽🏼',':water_polo_tone3:':'🤽🏽',':person_playing_water_polo_tone3:':'🤽🏽',':water_polo_tone4:':'🤽🏾',':person_playing_water_polo_tone4:':'🤽🏾',':water_polo_tone5:':'🤽🏿',':person_playing_water_polo_tone5:':'🤽🏿',':person_with_pouting_face_tone1:':'🙎🏻',':person_pouting_tone1:':'🙎🏻',':person_with_pouting_face_tone2:':'🙎🏼',':person_pouting_tone2:':'🙎🏼',':person_with_pouting_face_tone3:':'🙎🏽',':person_pouting_tone3:':'🙎🏽',':person_with_pouting_face_tone4:':'🙎🏾',':person_pouting_tone4:':'🙎🏾',':person_with_pouting_face_tone5:':'🙎🏿',':person_pouting_tone5:':'🙎🏿',':raising_hand_tone1:':'🙋🏻',':person_raising_hand_tone1:':'🙋🏻',':raising_hand_tone2:':'🙋🏼',':person_raising_hand_tone2:':'🙋🏼',':raising_hand_tone3:':'🙋🏽',':person_raising_hand_tone3:':'🙋🏽',':raising_hand_tone4:':'🙋🏾',':person_raising_hand_tone4:':'🙋🏾',':raising_hand_tone5:':'🙋🏿',':person_raising_hand_tone5:':'🙋🏿',':rowboat_tone1:':'🚣🏻',':person_rowing_boat_tone1:':'🚣🏻',':rowboat_tone2:':'🚣🏼',':person_rowing_boat_tone2:':'🚣🏼',':rowboat_tone3:':'🚣🏽',':person_rowing_boat_tone3:':'🚣🏽',':rowboat_tone4:':'🚣🏾',':person_rowing_boat_tone4:':'🚣🏾',':rowboat_tone5:':'🚣🏿',':person_rowing_boat_tone5:':'🚣🏿',':runner_tone1:':'🏃🏻',':person_running_tone1:':'🏃🏻',':runner_tone2:':'🏃🏼',':person_running_tone2:':'🏃🏼',':runner_tone3:':'🏃🏽',':person_running_tone3:':'🏃🏽',':runner_tone4:':'🏃🏾',':person_running_tone4:':'🏃🏾',':runner_tone5:':'🏃🏿',':person_running_tone5:':'🏃🏿',':shrug_tone1:':'🤷🏻',':person_shrugging_tone1:':'🤷🏻',':shrug_tone2:':'🤷🏼',':person_shrugging_tone2:':'🤷🏼',':shrug_tone3:':'🤷🏽',':person_shrugging_tone3:':'🤷🏽',':shrug_tone4:':'🤷🏾',':person_shrugging_tone4:':'🤷🏾',':shrug_tone5:':'🤷🏿',':person_shrugging_tone5:':'🤷🏿',':person_standing_light_skin_tone:':'🧍🏻',':person_standing_tone1:':'🧍🏻',':person_standing_medium_light_skin_tone:':'🧍🏼',':person_standing_tone2:':'🧍🏼',':person_standing_medium_skin_tone:':'🧍🏽',':person_standing_tone3:':'🧍🏽',':person_standing_medium_dark_skin_tone:':'🧍🏾',':person_standing_tone4:':'🧍🏾',':person_standing_dark_skin_tone:':'🧍🏿',':person_standing_tone5:':'🧍🏿',':surfer_tone1:':'🏄🏻',':person_surfing_tone1:':'🏄🏻',':surfer_tone2:':'🏄🏼',':person_surfing_tone2:':'🏄🏼',':surfer_tone3:':'🏄🏽',':person_surfing_tone3:':'🏄🏽',':surfer_tone4:':'🏄🏾',':person_surfing_tone4:':'🏄🏾',':surfer_tone5:':'🏄🏿',':person_surfing_tone5:':'🏄🏿',':swimmer_tone1:':'🏊🏻',':person_swimming_tone1:':'🏊🏻',':swimmer_tone2:':'🏊🏼',':person_swimming_tone2:':'🏊🏼',':swimmer_tone3:':'🏊🏽',':person_swimming_tone3:':'🏊🏽',':swimmer_tone4:':'🏊🏾',':person_swimming_tone4:':'🏊🏾',':swimmer_tone5:':'🏊🏿',':person_swimming_tone5:':'🏊🏿',':information_desk_person_tone1:':'💁🏻',':person_tipping_hand_tone1:':'💁🏻',':information_desk_person_tone2:':'💁🏼',':person_tipping_hand_tone2:':'💁🏼',':information_desk_person_tone3:':'💁🏽',':person_tipping_hand_tone3:':'💁🏽',':information_desk_person_tone4:':'💁🏾',':person_tipping_hand_tone4:':'💁🏾',':information_desk_person_tone5:':'💁🏿',':person_tipping_hand_tone5:':'💁🏿',':walking_tone1:':'🚶🏻',':person_walking_tone1:':'🚶🏻',':walking_tone2:':'🚶🏼',':person_walking_tone2:':'🚶🏼',':walking_tone3:':'🚶🏽',':person_walking_tone3:':'🚶🏽',':walking_tone4:':'🚶🏾',':person_walking_tone4:':'🚶🏾',':walking_tone5:':'🚶🏿',':person_walking_tone5:':'🚶🏿',':man_with_turban_tone1:':'👳🏻',':person_wearing_turban_tone1:':'👳🏻',':man_with_turban_tone2:':'👳🏼',':person_wearing_turban_tone2:':'👳🏼',':man_with_turban_tone3:':'👳🏽',':person_wearing_turban_tone3:':'👳🏽',':man_with_turban_tone4:':'👳🏾',':person_wearing_turban_tone4:':'👳🏾',':man_with_turban_tone5:':'👳🏿',':person_wearing_turban_tone5:':'👳🏿',':pinching_hand_light_skin_tone:':'🤏🏻',':pinching_hand_tone1:':'🤏🏻',':pinching_hand_medium_light_skin_tone:':'🤏🏼',':pinching_hand_tone2:':'🤏🏼',':pinching_hand_medium_skin_tone:':'🤏🏽',':pinching_hand_tone3:':'🤏🏽',':pinching_hand_medium_dark_skin_tone:':'🤏🏾',':pinching_hand_tone4:':'🤏🏾',':pinching_hand_dark_skin_tone:':'🤏🏿',':pinching_hand_tone5:':'🤏🏿',':point_down_tone1:':'👇🏻',':point_down_tone2:':'👇🏼',':point_down_tone3:':'👇🏽',':point_down_tone4:':'👇🏾',':point_down_tone5:':'👇🏿',':point_left_tone1:':'👈🏻',':point_left_tone2:':'👈🏼',':point_left_tone3:':'👈🏽',':point_left_tone4:':'👈🏾',':point_left_tone5:':'👈🏿',':point_right_tone1:':'👉🏻',':point_right_tone2:':'👉🏼',':point_right_tone3:':'👉🏽',':point_right_tone4:':'👉🏾',':point_right_tone5:':'👉🏿',':point_up_2_tone1:':'👆🏻',':point_up_2_tone2:':'👆🏼',':point_up_2_tone3:':'👆🏽',':point_up_2_tone4:':'👆🏾',':point_up_2_tone5:':'👆🏿',':cop_tone1:':'👮🏻',':police_officer_tone1:':'👮🏻',':cop_tone2:':'👮🏼',':police_officer_tone2:':'👮🏼',':cop_tone3:':'👮🏽',':police_officer_tone3:':'👮🏽',':cop_tone4:':'👮🏾',':police_officer_tone4:':'👮🏾',':cop_tone5:':'👮🏿',':police_officer_tone5:':'👮🏿',':pray_tone1:':'🙏🏻',':pray_tone2:':'🙏🏼',':pray_tone3:':'🙏🏽',':pray_tone4:':'🙏🏾',':pray_tone5:':'🙏🏿',':expecting_woman_tone1:':'🤰🏻',':pregnant_woman_tone1:':'🤰🏻',':expecting_woman_tone2:':'🤰🏼',':pregnant_woman_tone2:':'🤰🏼',':expecting_woman_tone3:':'🤰🏽',':pregnant_woman_tone3:':'🤰🏽',':expecting_woman_tone4:':'🤰🏾',':pregnant_woman_tone4:':'🤰🏾',':expecting_woman_tone5:':'🤰🏿',':pregnant_woman_tone5:':'🤰🏿',':prince_tone1:':'🤴🏻',':prince_tone2:':'🤴🏼',':prince_tone3:':'🤴🏽',':prince_tone4:':'🤴🏾',':prince_tone5:':'🤴🏿',':princess_tone1:':'👸🏻',':princess_tone2:':'👸🏼',':princess_tone3:':'👸🏽',':princess_tone4:':'👸🏾',':princess_tone5:':'👸🏿',':punch_tone1:':'👊🏻',':punch_tone2:':'👊🏼',':punch_tone3:':'👊🏽',':punch_tone4:':'👊🏾',':punch_tone5:':'👊🏿',':gay_pride_flag:':'🏳️‍🌈',':rainbow_flag:':'🏳️‍🌈',':back_of_hand_tone1:':'🤚🏻',':raised_back_of_hand_tone1:':'🤚🏻',':back_of_hand_tone2:':'🤚🏼',':raised_back_of_hand_tone2:':'🤚🏼',':back_of_hand_tone3:':'🤚🏽',':raised_back_of_hand_tone3:':'🤚🏽',':back_of_hand_tone4:':'🤚🏾',':raised_back_of_hand_tone4:':'🤚🏾',':back_of_hand_tone5:':'🤚🏿',':raised_back_of_hand_tone5:':'🤚🏿',':raised_hands_tone1:':'🙌🏻',':raised_hands_tone2:':'🙌🏼',':raised_hands_tone3:':'🙌🏽',':raised_hands_tone4:':'🙌🏾',':raised_hands_tone5:':'🙌🏿',':right_fist_tone1:':'🤜🏻',':right_facing_fist_tone1:':'🤜🏻',':right_fist_tone2:':'🤜🏼',':right_facing_fist_tone2:':'🤜🏼',':right_fist_tone3:':'🤜🏽',':right_facing_fist_tone3:':'🤜🏽',':right_fist_tone4:':'🤜🏾',':right_facing_fist_tone4:':'🤜🏾',':right_fist_tone5:':'🤜🏿',':right_facing_fist_tone5:':'🤜🏿',':santa_tone1:':'🎅🏻',':santa_tone2:':'🎅🏼',':santa_tone3:':'🎅🏽',':santa_tone4:':'🎅🏾',':santa_tone5:':'🎅🏿',':selfie_tone1:':'🤳🏻',':selfie_tone2:':'🤳🏼',':selfie_tone3:':'🤳🏽',':selfie_tone4:':'🤳🏾',':selfie_tone5:':'🤳🏿',':service_dog:':'🐕‍🦺',':snowboarder_light_skin_tone:':'🏂🏻',':snowboarder_tone1:':'🏂🏻',':snowboarder_medium_light_skin_tone:':'🏂🏼',':snowboarder_tone2:':'🏂🏼',':snowboarder_medium_skin_tone:':'🏂🏽',':snowboarder_tone3:':'🏂🏽',':snowboarder_medium_dark_skin_tone:':'🏂🏾',':snowboarder_tone4:':'🏂🏾',':snowboarder_dark_skin_tone:':'🏂🏿',':snowboarder_tone5:':'🏂🏿',':superhero_light_skin_tone:':'🦸🏻',':superhero_tone1:':'🦸🏻',':superhero_medium_light_skin_tone:':'🦸🏼',':superhero_tone2:':'🦸🏼',':superhero_medium_skin_tone:':'🦸🏽',':superhero_tone3:':'🦸🏽',':superhero_medium_dark_skin_tone:':'🦸🏾',':superhero_tone4:':'🦸🏾',':superhero_dark_skin_tone:':'🦸🏿',':superhero_tone5:':'🦸🏿',':supervillain_light_skin_tone:':'🦹🏻',':supervillain_tone1:':'🦹🏻',':supervillain_medium_light_skin_tone:':'🦹🏼',':supervillain_tone2:':'🦹🏼',':supervillain_medium_skin_tone:':'🦹🏽',':supervillain_tone3:':'🦹🏽',':supervillain_medium_dark_skin_tone:':'🦹🏾',':supervillain_tone4:':'🦹🏾',':supervillain_dark_skin_tone:':'🦹🏿',':supervillain_tone5:':'🦹🏿',':-1_tone1:':'👎🏻',':thumbdown_tone1:':'👎🏻',':thumbsdown_tone1:':'👎🏻',':-1_tone2:':'👎🏼',':thumbdown_tone2:':'👎🏼',':thumbsdown_tone2:':'👎🏼',':-1_tone3:':'👎🏽',':thumbdown_tone3:':'👎🏽',':thumbsdown_tone3:':'👎🏽',':-1_tone4:':'👎🏾',':thumbdown_tone4:':'👎🏾',':thumbsdown_tone4:':'👎🏾',':-1_tone5:':'👎🏿',':thumbdown_tone5:':'👎🏿',':thumbsdown_tone5:':'👎🏿',':+1_tone1:':'👍🏻',':thumbup_tone1:':'👍🏻',':thumbsup_tone1:':'👍🏻',':+1_tone2:':'👍🏼',':thumbup_tone2:':'👍🏼',':thumbsup_tone2:':'👍🏼',':+1_tone3:':'👍🏽',':thumbup_tone3:':'👍🏽',':thumbsup_tone3:':'👍🏽',':+1_tone4:':'👍🏾',':thumbup_tone4:':'👍🏾',':thumbsup_tone4:':'👍🏾',':+1_tone5:':'👍🏿',':thumbup_tone5:':'👍🏿',':thumbsup_tone5:':'👍🏿',':united_nations:':'🇺🇳',':vampire_light_skin_tone:':'🧛🏻',':vampire_tone1:':'🧛🏻',':vampire_medium_light_skin_tone:':'🧛🏼',':vampire_tone2:':'🧛🏼',':vampire_medium_skin_tone:':'🧛🏽',':vampire_tone3:':'🧛🏽',':vampire_medium_dark_skin_tone:':'🧛🏾',':vampire_tone4:':'🧛🏾',':vampire_dark_skin_tone:':'🧛🏿',':vampire_tone5:':'🧛🏿',':raised_hand_with_part_between_middle_and_ring_fingers_tone1:':'🖖🏻',':vulcan_tone1:':'🖖🏻',':raised_hand_with_part_between_middle_and_ring_fingers_tone2:':'🖖🏼',':vulcan_tone2:':'🖖🏼',':raised_hand_with_part_between_middle_and_ring_fingers_tone3:':'🖖🏽',':vulcan_tone3:':'🖖🏽',':raised_hand_with_part_between_middle_and_ring_fingers_tone4:':'🖖🏾',':vulcan_tone4:':'🖖🏾',':raised_hand_with_part_between_middle_and_ring_fingers_tone5:':'🖖🏿',':vulcan_tone5:':'🖖🏿',':wave_tone1:':'👋🏻',':wave_tone2:':'👋🏼',':wave_tone3:':'👋🏽',':wave_tone4:':'👋🏾',':wave_tone5:':'👋🏿',':woman_and_man_holding_hands_light_skin_tone:':'👫🏻',':woman_and_man_holding_hands_tone1:':'👫🏻',':woman_and_man_holding_hands_medium_light_skin_tone:':'👫🏼',':woman_and_man_holding_hands_tone2:':'👫🏼',':woman_and_man_holding_hands_medium_skin_tone:':'👫🏽',':woman_and_man_holding_hands_tone3:':'👫🏽',':woman_and_man_holding_hands_medium_dark_skin_tone:':'👫🏾',':woman_and_man_holding_hands_tone4:':'👫🏾',':woman_and_man_holding_hands_dark_skin_tone:':'👫🏿',':woman_and_man_holding_hands_tone5:':'👫🏿',':woman_artist:':'👩‍🎨',':woman_astronaut:':'👩‍🚀',':woman_bald:':'👩‍🦲',':woman_cook:':'👩‍🍳',':woman_curly_haired:':'👩‍🦱',':woman_factory_worker:':'👩‍🏭',':woman_farmer:':'👩‍🌾',':woman_firefighter:':'👩‍🚒',':woman_in_manual_wheelchair:':'👩‍🦽',':woman_in_motorized_wheelchair:':'👩‍🦼',':woman_mechanic:':'👩‍🔧',':woman_office_worker:':'👩‍💼',':woman_red_haired:':'👩‍🦰',':woman_scientist:':'👩‍🔬',':woman_singer:':'👩‍🎤',':woman_student:':'👩‍🎓',':woman_teacher:':'👩‍🏫',':woman_technologist:':'👩‍💻',':woman_tone1:':'👩🏻',':woman_tone2:':'👩🏼',':woman_tone3:':'👩🏽',':woman_tone4:':'👩🏾',':woman_tone5:':'👩🏿',':woman_white_haired:':'👩‍🦳',':woman_with_headscarf_light_skin_tone:':'🧕🏻',':woman_with_headscarf_tone1:':'🧕🏻',':woman_with_headscarf_medium_light_skin_tone:':'🧕🏼',':woman_with_headscarf_tone2:':'🧕🏼',':woman_with_headscarf_medium_skin_tone:':'🧕🏽',':woman_with_headscarf_tone3:':'🧕🏽',':woman_with_headscarf_medium_dark_skin_tone:':'🧕🏾',':woman_with_headscarf_tone4:':'🧕🏾',':woman_with_headscarf_dark_skin_tone:':'🧕🏿',':woman_with_headscarf_tone5:':'🧕🏿',':woman_with_probing_cane:':'👩‍🦯',':women_holding_hands_light_skin_tone:':'👭🏻',':women_holding_hands_tone1:':'👭🏻',':women_holding_hands_medium_light_skin_tone:':'👭🏼',':women_holding_hands_tone2:':'👭🏼',':women_holding_hands_medium_skin_tone:':'👭🏽',':women_holding_hands_tone3:':'👭🏽',':women_holding_hands_medium_dark_skin_tone:':'👭🏾',':women_holding_hands_tone4:':'👭🏾',':women_holding_hands_dark_skin_tone:':'👭🏿',':women_holding_hands_tone5:':'👭🏿',':blond-haired_man:':'👱‍♂️',':blond-haired_woman:':'👱‍♀️',':deaf_man:':'🧏‍♂️',':deaf_woman:':'🧏‍♀️',':fist_tone1:':'✊🏻',':fist_tone2:':'✊🏼',':fist_tone3:':'✊🏽',':fist_tone4:':'✊🏾',':fist_tone5:':'✊🏿',':man_biking:':'🚴‍♂️',':man_bowing:':'🙇‍♂️',':man_cartwheeling:':'🤸‍♂️',':man_climbing:':'🧗‍♂️',':man_construction_worker:':'👷‍♂️',':man_detective:':'🕵️‍♂️',':man_elf:':'🧝‍♂️',':man_facepalming:':'🤦‍♂️',':man_fairy:':'🧚‍♂️',':man_frowning:':'🙍‍♂️',':man_genie:':'🧞‍♂️',':man_gesturing_no:':'🙅‍♂️',':man_gesturing_ok:':'🙆‍♂️',':man_getting_face_massage:':'💆‍♂️',':man_getting_haircut:':'💇‍♂️',':man_golfing:':'🏌️‍♂️',':man_guard:':'💂‍♂️',':man_health_worker:':'👨‍⚕️',':man_in_lotus_position:':'🧘‍♂️',':man_in_steamy_room:':'🧖‍♂️',':man_judge:':'👨‍⚖️',':man_juggling:':'🤹‍♂️',':man_kneeling:':'🧎‍♂️',':man_lifting_weights:':'🏋️‍♂️',':man_mage:':'🧙‍♂️',':man_mountain_biking:':'🚵‍♂️',':man_pilot:':'👨‍✈️',':man_playing_handball:':'🤾‍♂️',':man_playing_water_polo:':'🤽‍♂️',':man_police_officer:':'👮‍♂️',':man_pouting:':'🙎‍♂️',':man_raising_hand:':'🙋‍♂️',':man_rowing_boat:':'🚣‍♂️',':man_running:':'🏃‍♂️',':man_shrugging:':'🤷‍♂️',':man_standing:':'🧍‍♂️',':man_superhero:':'🦸‍♂️',':man_supervillain:':'🦹‍♂️',':man_surfing:':'🏄‍♂️',':man_swimming:':'🏊‍♂️',':man_tipping_hand:':'💁‍♂️',':man_vampire:':'🧛‍♂️',':man_walking:':'🚶‍♂️',':man_wearing_turban:':'👳‍♂️',':man_zombie:':'🧟‍♂️',':men_with_bunny_ears_partying:':'👯‍♂️',':men_wrestling:':'🤼♂️',':mermaid:':'🧜‍♀️',':merman:':'🧜‍♂️',':basketball_player_tone1:':'⛹️🏻',':person_with_ball_tone1:':'⛹️🏻',':person_bouncing_ball_tone1:':'⛹️🏻',':basketball_player_tone2:':'⛹️🏼',':person_with_ball_tone2:':'⛹️🏼',':person_bouncing_ball_tone2:':'⛹️🏼',':basketball_player_tone3:':'⛹️🏽',':person_with_ball_tone3:':'⛹️🏽',':person_bouncing_ball_tone3:':'⛹️🏽',':basketball_player_tone4:':'⛹️🏾',':person_with_ball_tone4:':'⛹️🏾',':person_bouncing_ball_tone4:':'⛹️🏾',':basketball_player_tone5:':'⛹️🏿',':person_with_ball_tone5:':'⛹️🏿',':person_bouncing_ball_tone5:':'⛹️🏿',':pirate_flag:':'🏴‍☠️',':point_up_tone1:':'☝️🏻',':point_up_tone2:':'☝️🏼',':point_up_tone3:':'☝️🏽',':point_up_tone4:':'☝️🏾',':point_up_tone5:':'☝️🏿',':raised_hand_tone1:':'✋🏻',':raised_hand_tone2:':'✋🏼',':raised_hand_tone3:':'✋🏽',':raised_hand_tone4:':'✋🏾',':raised_hand_tone5:':'✋🏿',':v_tone1:':'✌️🏻',':v_tone2:':'✌️🏼',':v_tone3:':'✌️🏽',':v_tone4:':'✌️🏾',':v_tone5:':'✌️🏿',':woman_biking:':'🚴‍♀️',':woman_bowing:':'🙇‍♀️',':woman_cartwheeling:':'🤸‍♀️',':woman_climbing:':'🧗‍♀️',':woman_construction_worker:':'👷‍♀️',':woman_detective:':'🕵️‍♀️',':woman_elf:':'🧝‍♀️',':woman_facepalming:':'🤦‍♀️',':woman_fairy:':'🧚‍♀️',':woman_frowning:':'🙍‍♀️',':woman_genie:':'🧞‍♀️',':woman_gesturing_no:':'🙅‍♀️',':woman_gesturing_ok:':'🙆‍♀️',':woman_getting_face_massage:':'💆‍♀️',':woman_getting_haircut:':'💇‍♀️',':woman_golfing:':'🏌️‍♀️',':woman_guard:':'💂‍♀️',':woman_health_worker:':'👩‍⚕️',':woman_in_lotus_position:':'🧘‍♀️',':woman_in_steamy_room:':'🧖‍♀️',':woman_judge:':'👩‍⚖️',':woman_juggling:':'🤹‍♀️',':woman_kneeling:':'🧎‍♀️',':woman_lifting_weights:':'🏋️‍♀️',':woman_mage:':'🧙‍♀️',':woman_mountain_biking:':'🚵‍♀️',':woman_pilot:':'👩‍✈️',':woman_playing_handball:':'🤾‍♀️',':woman_playing_water_polo:':'🤽‍♀️',':woman_police_officer:':'👮‍♀️',':woman_pouting:':'🙎‍♀️',':woman_raising_hand:':'🙋‍♀️',':woman_rowing_boat:':'🚣‍♀️',':woman_running:':'🏃‍♀️',':woman_shrugging:':'🤷‍♀️',':woman_standing:':'🧍‍♀️',':woman_superhero:':'🦸‍♀️',':woman_supervillain:':'🦹‍♀️',':woman_surfing:':'🏄‍♀️',':woman_swimming:':'🏊‍♀️',':woman_tipping_hand:':'💁‍♀️',':woman_vampire:':'🧛‍♀️',':woman_walking:':'🚶‍♀️',':woman_wearing_turban:':'👳‍♀️',':woman_zombie:':'🧟‍♀️',':women_with_bunny_ears_partying:':'👯‍♀️',':women_wrestling:':'🤼♀️',':writing_hand_tone1:':'✍️🏻',':writing_hand_tone2:':'✍️🏼',':writing_hand_tone3:':'✍️🏽',':writing_hand_tone4:':'✍️🏾',':writing_hand_tone5:':'✍️🏿',':keycap_asterisk:':'*️⃣',':asterisk:':'*️⃣',':eight:':'8️⃣',':five:':'5️⃣',':four:':'4️⃣',':hash:':'#️⃣',':man_bouncing_ball:':'⛹️‍♂️',':nine:':'9️⃣',':one:':'1️⃣',':seven:':'7️⃣',':six:':'6️⃣',':three:':'3️⃣',':two:':'2️⃣',':woman_bouncing_ball:':'⛹️‍♀️',':zero:':'0️⃣',':100:':'💯',':1234:':'🔢',':8ball:':'🎱',':a:':'🅰️',':ab:':'🆎',':abacus:':'🧮',':abc:':'🔤',':abcd:':'🔡',':accept:':'🉑',':adhesive_bandage:':'🩹',':adult:':'🧑',':aerial_tramway:':'🚡',':airplane_arriving:':'🛬',':airplane_departure:':'🛫',':small_airplane:':'🛩️',':airplane_small:':'🛩️',':alien:':'👽',':ambulance:':'🚑',':amphora:':'🏺',':angel:':'👼',':anger:':'💢',':right_anger_bubble:':'🗯️',':anger_right:':'🗯️',':angry:':'😠',':anguished:':'😧',':ant:':'🐜',':apple:':'🍎',':arrow_down_small:':'🔽',':arrow_up_small:':'🔼',':arrows_clockwise:':'🔃',':arrows_counterclockwise:':'🔄',':art:':'🎨',':articulated_lorry:':'🚛',':astonished:':'😲',':athletic_shoe:':'👟',':atm:':'🏧',':auto_rickshaw:':'🛺',':avocado:':'🥑',':axe:':'🪓',':b:':'🅱️',':baby:':'👶',':baby_bottle:':'🍼',':baby_chick:':'🐤',':baby_symbol:':'🚼',':back:':'🔙',':bacon:':'🥓',':badger:':'🦡',':badminton:':'🏸',':bagel:':'🥯',':baggage_claim:':'🛄',':bald:':'🦲',':ballet_shoes:':'🩰',':balloon:':'🎈',':ballot_box_with_ballot:':'🗳️',':ballot_box:':'🗳️',':bamboo:':'🎍',':banana:':'🍌',':banjo:':'🪕',':bank:':'🏦',':bar_chart:':'📊',':barber:':'💈',':basket:':'🧺',':basketball:':'🏀',':bat:':'🦇',':bath:':'🛀',':bathtub:':'🛁',':battery:':'🔋',':beach_with_umbrella:':'🏖️',':beach:':'🏖️',':bear:':'🐻',':bearded_person:':'🧔',':bed:':'🛏️',':bee:':'🐝',':beer:':'🍺',':beers:':'🍻',':beetle:':'🐞',':beginner:':'🔰',':bell:':'🔔',':bellhop_bell:':'🛎️',':bellhop:':'🛎️',':bento:':'🍱',':beverage_box:':'🧃',':bike:':'🚲',':bikini:':'👙',':billed_cap:':'🧢',':bird:':'🐦',':birthday:':'🎂',':black_heart:':'🖤',':black_joker:':'🃏',':black_square_button:':'🔲',':person_with_blond_hair:':'👱',':blond_haired_person:':'👱',':blossom:':'🌼',':blowfish:':'🐡',':blue_book:':'📘',':blue_car:':'🚙',':blue_circle:':'🔵',':blue_heart:':'💙',':blue_square:':'🟦',':blush:':'😊',':boar:':'🐗',':bomb:':'💣',':bone:':'🦴',':book:':'📖',':bookmark:':'🔖',':bookmark_tabs:':'📑',':books:':'📚',':boom:':'💥',':boot:':'👢',':bouquet:':'💐',':archery:':'🏹',':bow_and_arrow:':'🏹',':bowl_with_spoon:':'🥣',':bowling:':'🎳',':boxing_gloves:':'🥊',':boxing_glove:':'🥊',':boy:':'👦',':brain:':'🧠',':bread:':'🍞',':breast_feeding:':'🤱',':bricks:':'🧱',':bride_with_veil:':'👰',':bridge_at_night:':'🌉',':briefcase:':'💼',':briefs:':'🩲',':broccoli:':'🥦',':broken_heart:':'💔',':broom:':'🧹',':brown_circle:':'🟤',':brown_heart:':'🤎',':brown_square:':'🟫',':bug:':'🐛',':bulb:':'💡',':bullettrain_front:':'🚅',':bullettrain_side:':'🚄',':burrito:':'🌯',':bus:':'🚌',':busstop:':'🚏',':bust_in_silhouette:':'👤',':busts_in_silhouette:':'👥',':butter:':'🧈',':butterfly:':'🦋',':cactus:':'🌵',':cake:':'🍰',':calendar:':'📆',':spiral_calendar_pad:':'🗓️',':calendar_spiral:':'🗓️',':call_me_hand:':'🤙',':call_me:':'🤙',':calling:':'📲',':camel:':'🐫',':camera:':'📷',':camera_with_flash:':'📸',':camping:':'🏕️',':candle:':'🕯️',':candy:':'🍬',':canned_food:':'🥫',':kayak:':'🛶',':canoe:':'🛶',':capital_abcd:':'🔠',':card_file_box:':'🗃️',':card_box:':'🗃️',':card_index:':'📇',':carousel_horse:':'🎠',':carrot:':'🥕',':cat2:':'🐈',':cat:':'🐱',':cd:':'💿',':chair:':'🪑',':bottle_with_popping_cork:':'🍾',':champagne:':'🍾',':clinking_glass:':'🥂',':champagne_glass:':'🥂',':chart:':'💹',':chart_with_downwards_trend:':'📉',':chart_with_upwards_trend:':'📈',':checkered_flag:':'🏁',':cheese_wedge:':'🧀',':cheese:':'🧀',':cherries:':'🍒',':cherry_blossom:':'🌸',':chestnut:':'🌰',':chicken:':'🐔',':child:':'🧒',':children_crossing:':'🚸',':chipmunk:':'🐿️',':chocolate_bar:':'🍫',':chopsticks:':'🥢',':christmas_tree:':'🎄',':cinema:':'🎦',':circus_tent:':'🎪',':city_dusk:':'🌆',':city_sunrise:':'🌇',':city_sunset:':'🌇',':cityscape:':'🏙️',':cl:':'🆑',':clap:':'👏',':clapper:':'🎬',':classical_building:':'🏛️',':clipboard:':'📋',':clock1030:':'🕥',':clock10:':'🕙',':clock1130:':'🕦',':clock11:':'🕚',':clock1230:':'🕧',':clock12:':'🕛',':clock130:':'🕜',':clock1:':'🕐',':clock230:':'🕝',':clock2:':'🕑',':clock330:':'🕞',':clock3:':'🕒',':clock430:':'🕟',':clock4:':'🕓',':clock530:':'🕠',':clock5:':'🕔',':clock630:':'🕡',':clock6:':'🕕',':clock730:':'🕢',':clock7:':'🕖',':clock830:':'🕣',':clock8:':'🕗',':clock930:':'🕤',':clock9:':'🕘',':mantlepiece_clock:':'🕰️',':clock:':'🕰️',':closed_book:':'📕',':closed_lock_with_key:':'🔐',':closed_umbrella:':'🌂',':cloud_with_lightning:':'🌩️',':cloud_lightning:':'🌩️',':cloud_with_rain:':'🌧️',':cloud_rain:':'🌧️',':cloud_with_snow:':'🌨️',':cloud_snow:':'🌨️',':cloud_with_tornado:':'🌪️',':cloud_tornado:':'🌪️',':clown_face:':'🤡',':clown:':'🤡',':coat:':'🧥',':cocktail:':'🍸',':coconut:':'🥥',':cold_face:':'🥶',':cold_sweat:':'😰',':compass:':'🧭',':compression:':'🗜️',':computer:':'💻',':confetti_ball:':'🎊',':confounded:':'😖',':confused:':'😕',':construction:':'🚧',':building_construction:':'🏗️',':construction_site:':'🏗️',':construction_worker:':'👷',':control_knobs:':'🎛️',':convenience_store:':'🏪',':cookie:':'🍪',':cooking:':'🍳',':cool:':'🆒',':corn:':'🌽',':couch_and_lamp:':'🛋️',':couch:':'🛋️',':couple:':'👫',':couple_with_heart:':'💑',':couplekiss:':'💏',':cow2:':'🐄',':cow:':'🐮',':face_with_cowboy_hat:':'🤠',':cowboy:':'🤠',':crab:':'🦀',':lower_left_crayon:':'🖍️',':crayon:':'🖍️',':credit_card:':'💳',':crescent_moon:':'🌙',':cricket:':'🦗',':cricket_bat_ball:':'🏏',':cricket_game:':'🏏',':crocodile:':'🐊',':croissant:':'🥐',':crossed_flags:':'🎌',':crown:':'👑',':passenger_ship:':'🛳️',':cruise_ship:':'🛳️',':cry:':'😢',':crying_cat_face:':'😿',':crystal_ball:':'🔮',':cucumber:':'🥒',':cup_with_straw:':'🥤',':cupcake:':'🧁',':cupid:':'💘',':curling_stone:':'🥌',':curly_haired:':'🦱',':currency_exchange:':'💱',':curry:':'🍛',':pudding:':'🍮',':flan:':'🍮',':custard:':'🍮',':customs:':'🛃',':cut_of_meat:':'🥩',':cyclone:':'🌀',':dagger_knife:':'🗡️',':dagger:':'🗡️',':dancer:':'💃',':dango:':'🍡',':dark_sunglasses:':'🕶️',':dart:':'🎯',':dash:':'💨',':date:':'📅',':deaf_person:':'🧏',':deciduous_tree:':'🌳',':deer:':'🦌',':department_store:':'🏬',':desert:':'🏜️',':desktop_computer:':'🖥️',':desktop:':'🖥️',':spy:':'🕵️',':sleuth_or_spy:':'🕵️',':detective:':'🕵️',':diamond_shape_with_a_dot_inside:':'💠',':disappointed:':'😞',':disappointed_relieved:':'😥',':card_index_dividers:':'🗂️',':dividers:':'🗂️',':diving_mask:':'🤿',':diya_lamp:':'🪔',':dizzy:':'💫',':dizzy_face:':'😵',':dna:':'🧬',':do_not_litter:':'🚯',':dog2:':'🐕',':dog:':'🐶',':dollar:':'💵',':dolls:':'🎎',':dolphin:':'🐬',':door:':'🚪',':doughnut:':'🍩',':dove_of_peace:':'🕊️',':dove:':'🕊️',':dragon:':'🐉',':dragon_face:':'🐲',':dress:':'👗',':dromedary_camel:':'🐪',':drool:':'🤤',':drooling_face:':'🤤',':drop_of_blood:':'🩸',':droplet:':'💧',':drum_with_drumsticks:':'🥁',':drum:':'🥁',':duck:':'🦆',':dumpling:':'🥟',':dvd:':'📀',':email:':'📧',':e-mail:':'📧',':eagle:':'🦅',':ear:':'👂',':ear_of_rice:':'🌾',':ear_with_hearing_aid:':'🦻',':earth_africa:':'🌍',':earth_americas:':'🌎',':earth_asia:':'🌏',':egg:':'🥚',':eggplant:':'🍆',':electric_plug:':'🔌',':elephant:':'🐘',':elf:':'🧝',':end:':'🔚',':envelope_with_arrow:':'📩',':euro:':'💶',':european_castle:':'🏰',':european_post_office:':'🏤',':evergreen_tree:':'🌲',':exploding_head:':'🤯',':expressionless:':'😑',':eye:':'👁️',':eyeglasses:':'👓',':eyes:':'👀',':face_vomiting:':'🤮',':face_with_hand_over_mouth:':'🤭',':face_with_monocle:':'🧐',':face_with_raised_eyebrow:':'🤨',':face_with_symbols_over_mouth:':'🤬',':factory:':'🏭',':fairy:':'🧚',':falafel:':'🧆',':fallen_leaf:':'🍂',':family:':'👪',':fax:':'📠',':fearful:':'😨',':paw_prints:':'🐾',':feet:':'🐾',':ferris_wheel:':'🎡',':field_hockey:':'🏑',':file_cabinet:':'🗄️',':file_folder:':'📁',':film_frames:':'🎞️',':hand_with_index_and_middle_finger_crossed:':'🤞',':fingers_crossed:':'🤞',':flame:':'🔥',':fire:':'🔥',':fire_engine:':'🚒',':fire_extinguisher:':'🧯',':firecracker:':'🧨',':fireworks:':'🎆',':first_place_medal:':'🥇',':first_place:':'🥇',':first_quarter_moon:':'🌓',':first_quarter_moon_with_face:':'🌛',':fish:':'🐟',':fish_cake:':'🍥',':fishing_pole_and_fish:':'🎣',':waving_black_flag:':'🏴',':flag_black:':'🏴',':waving_white_flag:':'🏳️',':flag_white:':'🏳️',':flags:':'🎏',':flamingo:':'🦩',':flashlight:':'🔦',':floppy_disk:':'💾',':flower_playing_cards:':'🎴',':flushed:':'😳',':flying_disc:':'🥏',':flying_saucer:':'🛸',':fog:':'🌫️',':foggy:':'🌁',':foot:':'🦶',':football:':'🏈',':footprints:':'👣',':fork_and_knife:':'🍴',':fork_and_knife_with_plate:':'🍽️',':fork_knife_plate:':'🍽️',':fortune_cookie:':'🥠',':four_leaf_clover:':'🍀',':fox_face:':'🦊',':fox:':'🦊',':frame_with_picture:':'🖼️',':frame_photo:':'🖼️',':free:':'🆓',':baguette_bread:':'🥖',':french_bread:':'🥖',':fried_shrimp:':'🍤',':fries:':'🍟',':frog:':'🐸',':frowning:':'😦',':full_moon:':'🌕',':full_moon_with_face:':'🌝',':game_die:':'🎲',':garlic:':'🧄',':gem:':'💎',':genie:':'🧞',':ghost:':'👻',':gift:':'🎁',':gift_heart:':'💝',':giraffe:':'🦒',':girl:':'👧',':globe_with_meridians:':'🌐',':gloves:':'🧤',':goal_net:':'🥅',':goal:':'🥅',':goat:':'🐐',':goggles:':'🥽',':gorilla:':'🦍',':grapes:':'🍇',':green_apple:':'🍏',':green_book:':'📗',':green_circle:':'🟢',':green_heart:':'💚',':green_square:':'🟩',':grimacing:':'😬',':grin:':'😁',':grinning:':'😀',':guardsman:':'💂',':guard:':'💂',':guide_dog:':'🦮',':guitar:':'🎸',':gun:':'🔫',':hamburger:':'🍔',':hammer:':'🔨',':hamster:':'🐹',':raised_hand_with_fingers_splayed:':'🖐️',':hand_splayed:':'🖐️',':handbag:':'👜',':shaking_hands:':'🤝',':handshake:':'🤝',':hatched_chick:':'🐥',':hatching_chick:':'🐣',':face_with_head_bandage:':'🤕',':head_bandage:':'🤕',':headphones:':'🎧',':hear_no_evil:':'🙉',':heart_decoration:':'💟',':heart_eyes:':'😍',':heart_eyes_cat:':'😻',':heartbeat:':'💓',':heartpulse:':'💗',':heavy_dollar_sign:':'💲',':hedgehog:':'🦔',':helicopter:':'🚁',':herb:':'🌿',':hibiscus:':'🌺',':high_brightness:':'🔆',':high_heel:':'👠',':hiking_boot:':'🥾',':hindu_temple:':'🛕',':hippopotamus:':'🦛',':hockey:':'🏒',':hole:':'🕳️',':house_buildings:':'🏘️',':homes:':'🏘️',':honey_pot:':'🍯',':horse:':'🐴',':horse_racing:':'🏇',':hospital:':'🏥',':hot_face:':'🥵',':hot_pepper:':'🌶️',':hot_dog:':'🌭',':hotdog:':'🌭',':hotel:':'🏨',':house:':'🏠',':derelict_house_building:':'🏚️',':house_abandoned:':'🏚️',':house_with_garden:':'🏡',':hugging_face:':'🤗',':hugging:':'🤗',':hushed:':'😯',':ice_cream:':'🍨',':ice_cube:':'🧊',':icecream:':'🍦',':id:':'🆔',':ideograph_advantage:':'🉐',':imp:':'👿',':inbox_tray:':'📥',':incoming_envelope:':'📨',':innocent:':'😇',':iphone:':'📱',':desert_island:':'🏝️',':island:':'🏝️',':izakaya_lantern:':'🏮',':jack_o_lantern:':'🎃',':japan:':'🗾',':japanese_castle:':'🏯',':japanese_goblin:':'👺',':japanese_ogre:':'👹',':jeans:':'👖',':jigsaw:':'🧩',':joy:':'😂',':joy_cat:':'😹',':joystick:':'🕹️',':kaaba:':'🕋',':kangaroo:':'🦘',':old_key:':'🗝️',':key2:':'🗝️',':key:':'🔑',':keycap_ten:':'🔟',':kimono:':'👘',':kiss:':'💋',':kissing:':'😗',':kissing_cat:':'😽',':kissing_closed_eyes:':'😚',':kissing_heart:':'😘',':kissing_smiling_eyes:':'😙',':kite:':'🪁',':kiwifruit:':'🥝',':kiwi:':'🥝',':knife:':'🔪',':koala:':'🐨',':koko:':'🈁',':lab_coat:':'🥼',':label:':'🏷️',':lacrosse:':'🥍',':large_blue_diamond:':'🔷',':large_orange_diamond:':'🔶',':last_quarter_moon:':'🌗',':last_quarter_moon_with_face:':'🌜',':satisfied:':'😆',':laughing:':'😆',':leafy_green:':'🥬',':leaves:':'🍃',':ledger:':'📒',':left_fist:':'🤛',':left_facing_fist:':'🤛',':left_luggage:':'🛅',':leg:':'🦵',':lemon:':'🍋',':leopard:':'🐆',':level_slider:':'🎚️',':man_in_business_suit_levitating:':'🕴️',':levitate:':'🕴️',':light_rail:':'🚈',':link:':'🔗',':lion:':'🦁',':lion_face:':'🦁',':lips:':'👄',':lipstick:':'💄',':lizard:':'🦎',':llama:':'🦙',':lobster:':'🦞',':lock:':'🔒',':lock_with_ink_pen:':'🔏',':lollipop:':'🍭',':loud_sound:':'🔊',':loudspeaker:':'📢',':love_hotel:':'🏩',':love_letter:':'💌',':love_you_gesture:':'🤟',':low_brightness:':'🔅',':luggage:':'🧳',':liar:':'🤥',':lying_face:':'🤥',':mag:':'🔍',':mag_right:':'🔎',':mage:':'🧙',':magnet:':'🧲',':mahjong:':'🀄',':mailbox:':'📫',':mailbox_closed:':'📪',':mailbox_with_mail:':'📬',':mailbox_with_no_mail:':'📭',':man:':'👨',':male_dancer:':'🕺',':man_dancing:':'🕺',':man_in_tuxedo:':'🤵',':man_with_gua_pi_mao:':'👲',':man_with_chinese_cap:':'👲',':mango:':'🥭',':mans_shoe:':'👞',':manual_wheelchair:':'🦽',':world_map:':'🗺️',':map:':'🗺️',':maple_leaf:':'🍁',':karate_uniform:':'🥋',':martial_arts_uniform:':'🥋',':mask:':'😷',':mate:':'🧉',':meat_on_bone:':'🍖',':mechanical_arm:':'🦾',':mechanical_leg:':'🦿',':sports_medal:':'🏅',':medal:':'🏅',':mega:':'📣',':melon:':'🍈',':menorah:':'🕎',':mens:':'🚹',':merperson:':'🧜',':sign_of_the_horns:':'🤘',':metal:':'🤘',':metro:':'🚇',':microbe:':'🦠',':studio_microphone:':'🎙️',':microphone2:':'🎙️',':microphone:':'🎤',':microscope:':'🔬',':reversed_hand_with_middle_finger_extended:':'🖕',':middle_finger:':'🖕',':military_medal:':'🎖️',':glass_of_milk:':'🥛',':milk:':'🥛',':milky_way:':'🌌',':minibus:':'🚐',':minidisc:':'💽',':mobile_phone_off:':'📴',':money_mouth_face:':'🤑',':money_mouth:':'🤑',':money_with_wings:':'💸',':moneybag:':'💰',':monkey:':'🐒',':monkey_face:':'🐵',':monorail:':'🚝',':moon_cake:':'🥮',':mortar_board:':'🎓',':mosque:':'🕌',':mosquito:':'🦟',':motorbike:':'🛵',':motor_scooter:':'🛵',':motorboat:':'🛥️',':racing_motorcycle:':'🏍️',':motorcycle:':'🏍️',':motorized_wheelchair:':'🦼',':motorway:':'🛣️',':mount_fuji:':'🗻',':mountain_cableway:':'🚠',':mountain_railway:':'🚞',':snow_capped_mountain:':'🏔️',':mountain_snow:':'🏔️',':mouse2:':'🐁',':mouse:':'🐭',':three_button_mouse:':'🖱️',':mouse_three_button:':'🖱️',':movie_camera:':'🎥',':moyai:':'🗿',':mother_christmas:':'🤶',':mrs_claus:':'🤶',':muscle:':'💪',':mushroom:':'🍄',':musical_keyboard:':'🎹',':musical_note:':'🎵',':musical_score:':'🎼',':mute:':'🔇',':nail_care:':'💅',':name_badge:':'📛',':sick:':'🤢',':nauseated_face:':'🤢',':nazar_amulet:':'🧿',':necktie:':'👔',':nerd_face:':'🤓',':nerd:':'🤓',':neutral_face:':'😐',':new:':'🆕',':new_moon:':'🌑',':new_moon_with_face:':'🌚',':rolled_up_newspaper:':'🗞️',':newspaper2:':'🗞️',':newspaper:':'📰',':ng:':'🆖',':night_with_stars:':'🌃',':no_bell:':'🔕',':no_bicycles:':'🚳',':no_entry_sign:':'🚫',':no_mobile_phones:':'📵',':no_mouth:':'😶',':no_pedestrians:':'🚷',':no_smoking:':'🚭',':non-potable_water:':'🚱',':nose:':'👃',':notebook:':'📓',':notebook_with_decorative_cover:':'📔',':spiral_note_pad:':'🗒️',':notepad_spiral:':'🗒️',':notes:':'🎶',':nut_and_bolt:':'🔩',':o2:':'🅾️',':ocean:':'🌊',':stop_sign:':'🛑',':octagonal_sign:':'🛑',':octopus:':'🐙',':oden:':'🍢',':office:':'🏢',':oil_drum:':'🛢️',':oil:':'🛢️',':ok:':'🆗',':ok_hand:':'👌',':older_adult:':'🧓',':older_man:':'👴',':grandma:':'👵',':older_woman:':'👵',':om_symbol:':'🕉️',':on:':'🔛',':oncoming_automobile:':'🚘',':oncoming_bus:':'🚍',':oncoming_police_car:':'🚔',':oncoming_taxi:':'🚖',':one_piece_swimsuit:':'🩱',':onion:':'🧅',':open_file_folder:':'📂',':open_hands:':'👐',':open_mouth:':'😮',':orange_book:':'📙',':orange_circle:':'🟠',':orange_heart:':'🧡',':orange_square:':'🟧',':orangutan:':'🦧',':otter:':'🦦',':outbox_tray:':'📤',':owl:':'🦉',':ox:':'🐂',':oyster:':'🦪',':package:':'📦',':page_facing_up:':'📄',':page_with_curl:':'📃',':pager:':'📟',':lower_left_paintbrush:':'🖌️',':paintbrush:':'🖌️',':palm_tree:':'🌴',':palms_up_together:':'🤲',':pancakes:':'🥞',':panda_face:':'🐼',':paperclip:':'📎',':linked_paperclips:':'🖇️',':paperclips:':'🖇️',':parachute:':'🪂',':national_park:':'🏞️',':park:':'🏞️',':parking:':'🅿️',':parrot:':'🦜',':partying_face:':'🥳',':passport_control:':'🛂',':peach:':'🍑',':peacock:':'🦚',':shelled_peanut:':'🥜',':peanuts:':'🥜',':pear:':'🍐',':lower_left_ballpoint_pen:':'🖊️',':pen_ballpoint:':'🖊️',':lower_left_fountain_pen:':'🖋️',':pen_fountain:':'🖋️',':memo:':'📝',':pencil:':'📝',':penguin:':'🐧',':pensive:':'😔',':dancers:':'👯',':people_with_bunny_ears_partying:':'👯',':wrestlers:':'🤼',':wrestling:':'🤼',':people_wrestling:':'🤼',':performing_arts:':'🎭',':persevere:':'😣',':bicyclist:':'🚴',':person_biking:':'🚴',':bow:':'🙇',':person_bowing:':'🙇',':person_climbing:':'🧗',':cartwheel:':'🤸',':person_doing_cartwheel:':'🤸',':face_palm:':'🤦',':facepalm:':'🤦',':person_facepalming:':'🤦',':fencer:':'🤺',':fencing:':'🤺',':person_fencing:':'🤺',':person_frowning:':'🙍',':no_good:':'🙅',':person_gesturing_no:':'🙅',':ok_woman:':'🙆',':person_gesturing_ok:':'🙆',':haircut:':'💇',':person_getting_haircut:':'💇',':massage:':'💆',':person_getting_massage:':'💆',':golfer:':'🏌️',':person_golfing:':'🏌️',':person_in_lotus_position:':'🧘',':person_in_steamy_room:':'🧖',':juggling:':'🤹',':juggler:':'🤹',':person_juggling:':'🤹',':person_kneeling:':'🧎',':lifter:':'🏋️',':weight_lifter:':'🏋️',':person_lifting_weights:':'🏋️',':mountain_bicyclist:':'🚵',':person_mountain_biking:':'🚵',':handball:':'🤾',':person_playing_handball:':'🤾',':water_polo:':'🤽',':person_playing_water_polo:':'🤽',':person_with_pouting_face:':'🙎',':person_pouting:':'🙎',':raising_hand:':'🙋',':person_raising_hand:':'🙋',':rowboat:':'🚣',':person_rowing_boat:':'🚣',':runner:':'🏃',':person_running:':'🏃',':shrug:':'🤷',':person_shrugging:':'🤷',':person_standing:':'🧍',':surfer:':'🏄',':person_surfing:':'🏄',':swimmer:':'🏊',':person_swimming:':'🏊',':information_desk_person:':'💁',':person_tipping_hand:':'💁',':walking:':'🚶',':person_walking:':'🚶',':man_with_turban:':'👳',':person_wearing_turban:':'👳',':petri_dish:':'🧫',':pie:':'🥧',':pig2:':'🐖',':pig:':'🐷',':pig_nose:':'🐽',':pill:':'💊',':pinching_hand:':'🤏',':pineapple:':'🍍',':table_tennis:':'🏓',':ping_pong:':'🏓',':pizza:':'🍕',':worship_symbol:':'🛐',':place_of_worship:':'🛐',':pleading_face:':'🥺',':point_down:':'👇',':point_left:':'👈',':point_right:':'👉',':point_up_2:':'👆',':police_car:':'🚓',':cop:':'👮',':police_officer:':'👮',':poodle:':'🐩',':shit:':'💩',':hankey:':'💩',':poo:':'💩',':poop:':'💩',':popcorn:':'🍿',':post_office:':'🏣',':postal_horn:':'📯',':postbox:':'📮',':potable_water:':'🚰',':potato:':'🥔',':pouch:':'👝',':poultry_leg:':'🍗',':pound:':'💷',':pouting_cat:':'😾',':pray:':'🙏',':prayer_beads:':'📿',':expecting_woman:':'🤰',':pregnant_woman:':'🤰',':pretzel:':'🥨',':prince:':'🤴',':princess:':'👸',':printer:':'🖨️',':probing_cane:':'🦯',':film_projector:':'📽️',':projector:':'📽️',':punch:':'👊',':purple_circle:':'🟣',':purple_heart:':'💜',':purple_square:':'🟪',':purse:':'👛',':pushpin:':'📌',':put_litter_in_its_place:':'🚮',':rabbit2:':'🐇',':rabbit:':'🐰',':raccoon:':'🦝',':racing_car:':'🏎️',':race_car:':'🏎️',':racehorse:':'🐎',':radio:':'📻',':radio_button:':'🔘',':rage:':'😡',':railway_car:':'🚃',':railroad_track:':'🛤️',':railway_track:':'🛤️',':rainbow:':'🌈',':back_of_hand:':'🤚',':raised_back_of_hand:':'🤚',':raised_hands:':'🙌',':ram:':'🐏',':ramen:':'🍜',':rat:':'🐀',':razor:':'🪒',':receipt:':'🧾',':red_car:':'🚗',':red_circle:':'🔴',':red_envelope:':'🧧',':red_haired:':'🦰',':red_square:':'🟥',':regional_indicator_a:':'🇦',':regional_indicator_b:':'🇧',':regional_indicator_c:':'🇨',':regional_indicator_d:':'🇩',':regional_indicator_e:':'🇪',':regional_indicator_f:':'🇫',':regional_indicator_g:':'🇬',':regional_indicator_h:':'🇭',':regional_indicator_i:':'🇮',':regional_indicator_j:':'🇯',':regional_indicator_k:':'🇰',':regional_indicator_l:':'🇱',':regional_indicator_m:':'🇲',':regional_indicator_n:':'🇳',':regional_indicator_o:':'🇴',':regional_indicator_p:':'🇵',':regional_indicator_q:':'🇶',':regional_indicator_r:':'🇷',':regional_indicator_s:':'🇸',':regional_indicator_t:':'🇹',':regional_indicator_u:':'🇺',':regional_indicator_v:':'🇻',':regional_indicator_w:':'🇼',':regional_indicator_x:':'🇽',':regional_indicator_y:':'🇾',':regional_indicator_z:':'🇿',':relieved:':'😌',':reminder_ribbon:':'🎗️',':repeat:':'🔁',':repeat_one:':'🔂',':restroom:':'🚻',':revolving_hearts:':'💞',':rhinoceros:':'🦏',':rhino:':'🦏',':ribbon:':'🎀',':rice:':'🍚',':rice_ball:':'🍙',':rice_cracker:':'🍘',':rice_scene:':'🎑',':right_fist:':'🤜',':right_facing_fist:':'🤜',':ring:':'💍',':ringed_planet:':'🪐',':robot_face:':'🤖',':robot:':'🤖',':rocket:':'🚀',':rolling_on_the_floor_laughing:':'🤣',':rofl:':'🤣',':roll_of_paper:':'🧻',':roller_coaster:':'🎢',':face_with_rolling_eyes:':'🙄',':rolling_eyes:':'🙄',':rooster:':'🐓',':rose:':'🌹',':rosette:':'🏵️',':rotating_light:':'🚨',':round_pushpin:':'📍',':rugby_football:':'🏉',':running_shirt_with_sash:':'🎽',':sa:':'🈂️',':safety_pin:':'🧷',':safety_vest:':'🦺',':sake:':'🍶',':green_salad:':'🥗',':salad:':'🥗',':salt:':'🧂',':sandal:':'👡',':sandwich:':'🥪',':santa:':'🎅',':sari:':'🥻',':satellite:':'📡',':satellite_orbital:':'🛰️',':sauropod:':'🦕',':saxophone:':'🎷',':scarf:':'🧣',':school:':'🏫',':school_satchel:':'🎒',':scooter:':'🛴',':scorpion:':'🦂',':scream:':'😱',':scream_cat:':'🙀',':scroll:':'📜',':seat:':'💺',':second_place_medal:':'🥈',':second_place:':'🥈',':see_no_evil:':'🙈',':seedling:':'🌱',':selfie:':'🤳',':paella:':'🥘',':shallow_pan_of_food:':'🥘',':shark:':'🦈',':shaved_ice:':'🍧',':sheep:':'🐑',':shell:':'🐚',':shield:':'🛡️',':ship:':'🚢',':shirt:':'👕',':shopping_bags:':'🛍️',':shopping_trolley:':'🛒',':shopping_cart:':'🛒',':shorts:':'🩳',':shower:':'🚿',':shrimp:':'🦐',':shushing_face:':'🤫',':signal_strength:':'📶',':six_pointed_star:':'🔯',':skateboard:':'🛹',':ski:':'🎿',':skeleton:':'💀',':skull:':'💀',':skunk:':'🦨',':sled:':'🛷',':sleeping:':'😴',':sleeping_accommodation:':'🛌',':sleepy:':'😪',':slightly_frowning_face:':'🙁',':slight_frown:':'🙁',':slightly_smiling_face:':'🙂',':slight_smile:':'🙂',':slot_machine:':'🎰',':sloth:':'🦥',':small_blue_diamond:':'🔹',':small_orange_diamond:':'🔸',':small_red_triangle:':'🔺',':small_red_triangle_down:':'🔻',':smile:':'😄',':smile_cat:':'😸',':smiley:':'😃',':smiley_cat:':'😺',':smiling_face_with_3_hearts:':'🥰',':smiling_imp:':'😈',':smirk:':'😏',':smirk_cat:':'😼',':smoking:':'🚬',':snail:':'🐌',':snake:':'🐍',':sneeze:':'🤧',':sneezing_face:':'🤧',':snowboarder:':'🏂',':soap:':'🧼',':sob:':'😭',':socks:':'🧦',':softball:':'🥎',':soon:':'🔜',':sos:':'🆘',':sound:':'🔉',':space_invader:':'👾',':spaghetti:':'🍝',':sparkler:':'🎇',':sparkling_heart:':'💖',':speak_no_evil:':'🙊',':speaker:':'🔈',':speaking_head_in_silhouette:':'🗣️',':speaking_head:':'🗣️',':speech_balloon:':'💬',':left_speech_bubble:':'🗨️',':speech_left:':'🗨️',':speedboat:':'🚤',':spider:':'🕷️',':spider_web:':'🕸️',':sponge:':'🧽',':spoon:':'🥄',':squeeze_bottle:':'🧴',':squid:':'🦑',':stadium:':'🏟️',':star2:':'🌟',':star_struck:':'🤩',':stars:':'🌠',':station:':'🚉',':statue_of_liberty:':'🗽',':steam_locomotive:':'🚂',':stethoscope:':'🩺',':stew:':'🍲',':straight_ruler:':'📏',':strawberry:':'🍓',':stuck_out_tongue:':'😛',':stuck_out_tongue_closed_eyes:':'😝',':stuck_out_tongue_winking_eye:':'😜',':stuffed_pita:':'🥙',':stuffed_flatbread:':'🥙',':sun_with_face:':'🌞',':sunflower:':'🌻',':sunglasses:':'😎',':sunrise:':'🌅',':sunrise_over_mountains:':'🌄',':superhero:':'🦸',':supervillain:':'🦹',':sushi:':'🍣',':suspension_railway:':'🚟',':swan:':'🦢',':sweat:':'😓',':sweat_drops:':'💦',':sweat_smile:':'😅',':sweet_potato:':'🍠',':symbols:':'🔣',':synagogue:':'🕍',':syringe:':'💉',':t_rex:':'🦖',':taco:':'🌮',':tada:':'🎉',':takeout_box:':'🥡',':tanabata_tree:':'🎋',':tangerine:':'🍊',':taxi:':'🚕',':tea:':'🍵',':teddy_bear:':'🧸',':telephone_receiver:':'📞',':telescope:':'🔭',':tennis:':'🎾',':test_tube:':'🧪',':thermometer:':'🌡️',':face_with_thermometer:':'🤒',':thermometer_face:':'🤒',':thinking_face:':'🤔',':thinking:':'🤔',':third_place_medal:':'🥉',':third_place:':'🥉',':thought_balloon:':'💭',':thread:':'🧵',':-1:':'👎',':thumbdown:':'👎',':thumbsdown:':'👎',':+1:':'👍',':thumbup:':'👍',':thumbsup:':'👍',':ticket:':'🎫',':admission_tickets:':'🎟️',':tickets:':'🎟️',':tiger2:':'🐅',':tiger:':'🐯',':tired_face:':'😫',':toilet:':'🚽',':tokyo_tower:':'🗼',':tomato:':'🍅',':tone1:':'🏻',':tone2:':'🏼',':tone3:':'🏽',':tone4:':'🏾',':tone5:':'🏿',':tongue:':'👅',':toolbox:':'🧰',':hammer_and_wrench:':'🛠️',':tools:':'🛠️',':tooth:':'🦷',':top:':'🔝',':tophat:':'🎩',':trackball:':'🖲️',':tractor:':'🚜',':traffic_light:':'🚥',':train2:':'🚆',':train:':'🚋',':tram:':'🚊',':triangular_flag_on_post:':'🚩',':triangular_ruler:':'📐',':trident:':'🔱',':triumph:':'😤',':trolleybus:':'🚎',':trophy:':'🏆',':tropical_drink:':'🍹',':tropical_fish:':'🐠',':truck:':'🚚',':trumpet:':'🎺',':tulip:':'🌷',':whisky:':'🥃',':tumbler_glass:':'🥃',':turkey:':'🦃',':turtle:':'🐢',':tv:':'📺',':twisted_rightwards_arrows:':'🔀',':two_hearts:':'💕',':two_men_holding_hands:':'👬',':two_women_holding_hands:':'👭',':u5272:':'🈹',':u5408:':'🈴',':u55b6:':'🈺',':u6307:':'🈯',':u6708:':'🈷️',':u6709:':'🈶',':u6e80:':'🈵',':u7121:':'🈚',':u7533:':'🈸',':u7981:':'🈲',':u7a7a:':'🈳',':unamused:':'😒',':underage:':'🔞',':unicorn_face:':'🦄',':unicorn:':'🦄',':unlock:':'🔓',':up:':'🆙',':upside_down_face:':'🙃',':upside_down:':'🙃',':vampire:':'🧛',':vertical_traffic_light:':'🚦',':vhs:':'📼',':vibration_mode:':'📳',':video_camera:':'📹',':video_game:':'🎮',':violin:':'🎻',':volcano:':'🌋',':volleyball:':'🏐',':vs:':'🆚',':raised_hand_with_part_between_middle_and_ring_fingers:':'🖖',':vulcan:':'🖖',':waffle:':'🧇',':waning_crescent_moon:':'🌘',':waning_gibbous_moon:':'🌖',':wastebasket:':'🗑️',':water_buffalo:':'🐃',':watermelon:':'🍉',':wave:':'👋',':waxing_crescent_moon:':'🌒',':waxing_gibbous_moon:':'🌔',':wc:':'🚾',':weary:':'😩',':wedding:':'💒',':whale2:':'🐋',':whale:':'🐳',':white_flower:':'💮',':white_haired:':'🦳',':white_heart:':'🤍',':white_square_button:':'🔳',':white_sun_behind_cloud:':'🌥️',':white_sun_cloud:':'🌥️',':white_sun_behind_cloud_with_rain:':'🌦️',':white_sun_rain_cloud:':'🌦️',':white_sun_with_small_cloud:':'🌤️',':white_sun_small_cloud:':'🌤️',':wilted_flower:':'🥀',':wilted_rose:':'🥀',':wind_blowing_face:':'🌬️',':wind_chime:':'🎐',':wine_glass:':'🍷',':wink:':'😉',':wolf:':'🐺',':woman:':'👩',':woman_with_headscarf:':'🧕',':womans_clothes:':'👚',':womans_flat_shoe:':'🥿',':womans_hat:':'👒',':womens:':'🚺',':woozy_face:':'🥴',':worried:':'😟',':wrench:':'🔧',':yarn:':'🧶',':yawning_face:':'🥱',':yellow_circle:':'🟡',':yellow_heart:':'💛',':yellow_square:':'🟨',':yen:':'💴',':yo_yo:':'🪀',':yum:':'😋',':zany_face:':'🤪',':zebra:':'🦓',':zipper_mouth_face:':'🤐',':zipper_mouth:':'🤐',':zombie:':'🧟',':zzz:':'💤',':airplane:':'✈️',':alarm_clock:':'⏰',':alembic:':'⚗️',':anchor:':'⚓',':aquarius:':'♒',':aries:':'♈',':arrow_backward:':'◀️',':arrow_double_down:':'⏬',':arrow_double_up:':'⏫',':arrow_down:':'⬇️',':arrow_forward:':'▶️',':arrow_heading_down:':'⤵️',':arrow_heading_up:':'⤴️',':arrow_left:':'⬅️',':arrow_lower_left:':'↙️',':arrow_lower_right:':'↘️',':arrow_right:':'➡️',':arrow_right_hook:':'↪️',':arrow_up:':'⬆️',':arrow_up_down:':'↕️',':arrow_upper_left:':'↖️',':arrow_upper_right:':'↗️',':asterisk_symbol:':'*️',':atom_symbol:':'⚛️',':atom:':'⚛️',':ballot_box_with_check:':'☑️',':bangbang:':'‼️',':baseball:':'⚾',':umbrella_on_ground:':'⛱️',':beach_umbrella:':'⛱️',':biohazard_sign:':'☣️',':biohazard:':'☣️',':black_circle:':'⚫',':black_large_square:':'⬛',':black_medium_small_square:':'◾',':black_medium_square:':'◼️',':black_nib:':'✒️',':black_small_square:':'▪️',':cancer:':'♋',':capricorn:':'♑',':chains:':'⛓️',':chess_pawn:':'♟️',':church:':'⛪',':cloud:':'☁️',':clubs:':'♣️',':coffee:':'☕',':coffin:':'⚰️',':comet:':'☄️',':congratulations:':'㊗️',':copyright:':'©️',':latin_cross:':'✝️',':cross:':'✝️',':crossed_swords:':'⚔️',':curly_loop:':'➰',':diamonds:':'♦️',':digit_eight:':'8️',':digit_five:':'5️',':digit_four:':'4️',':digit_nine:':'9️',':digit_one:':'1️',':digit_seven:':'7️',':digit_six:':'6️',':digit_three:':'3️',':digit_two:':'2️',':digit_zero:':'0️',':eight_pointed_black_star:':'✴️',':eight_spoked_asterisk:':'✳️',':eject_symbol:':'⏏️',':eject:':'⏏️',':envelope:':'✉️',':exclamation:':'❗',':fast_forward:':'⏩',':female_sign:':'♀️',':ferry:':'⛴️',':fist:':'✊',':fleur-de-lis:':'⚜️',':fountain:':'⛲',':white_frowning_face:':'☹️',':frowning2:':'☹️',':fuelpump:':'⛽',':gear:':'⚙️',':gemini:':'♊',':golf:':'⛳',':grey_exclamation:':'❕',':grey_question:':'❔',':hammer_and_pick:':'⚒️',':hammer_pick:':'⚒️',':heart:':'❤️',':heavy_heart_exclamation_mark_ornament:':'❣️',':heart_exclamation:':'❣️',':hearts:':'♥️',':heavy_check_mark:':'✔️',':heavy_division_sign:':'➗',':heavy_minus_sign:':'➖',':heavy_multiplication_x:':'✖️',':heavy_plus_sign:':'➕',':helmet_with_white_cross:':'⛑️',':helmet_with_cross:':'⛑️',':hotsprings:':'♨️',':hourglass:':'⌛',':hourglass_flowing_sand:':'⏳',':ice_skate:':'⛸️',':infinity:':'♾️',':information_source:':'ℹ️',':interrobang:':'⁉️',':keyboard:':'⌨️',':left_right_arrow:':'↔️',':leftwards_arrow_with_hook:':'↩️',':leo:':'♌',':libra:':'♎',':loop:':'➿',':m:':'Ⓜ️',':male_sign:':'♂️',':medical_symbol:':'⚕️',':mountain:':'⛰️',':negative_squared_cross_mark:':'❎',':no_entry:':'⛔',':o:':'⭕',':ophiuchus:':'⛎',':orthodox_cross:':'☦️',':part_alternation_mark:':'〽️',':partly_sunny:':'⛅',':double_vertical_bar:':'⏸️',':pause_button:':'⏸️',':peace_symbol:':'☮️',':peace:':'☮️',':pencil2:':'✏️',':basketball_player:':'⛹️',':person_with_ball:':'⛹️',':person_bouncing_ball:':'⛹️',':pick:':'⛏️',':pisces:':'♓',':play_pause:':'⏯️',':point_up:':'☝️',':pound_symbol:':'#️',':question:':'❓',':radioactive_sign:':'☢️',':radioactive:':'☢️',':raised_hand:':'✋',':record_button:':'⏺️',':recycle:':'♻️',':registered:':'®️',':relaxed:':'☺️',':rewind:':'⏪',':sagittarius:':'♐',':sailboat:':'⛵',':scales:':'⚖️',':scissors:':'✂️',':scorpius:':'♏',':secret:':'㊙️',':shamrock:':'☘️',':shinto_shrine:':'⛩️',':skier:':'⛷️',':skull_and_crossbones:':'☠️',':skull_crossbones:':'☠️',':snowflake:':'❄️',':snowman2:':'☃️',':snowman:':'⛄',':soccer:':'⚽',':spades:':'♠️',':sparkle:':'❇️',':sparkles:':'✨',':star:':'⭐',':star_and_crescent:':'☪️',':star_of_david:':'✡️',':stop_button:':'⏹️',':stopwatch:':'⏱️',':sunny:':'☀️',':taurus:':'♉',':telephone:':'☎️',':tent:':'⛺',':thunder_cloud_and_rain:':'⛈️',':thunder_cloud_rain:':'⛈️',':timer_clock:':'⏲️',':timer:':'⏲️',':tm:':'™️',':next_track:':'⏭️',':track_next:':'⏭️',':previous_track:':'⏮️',':track_previous:':'⏮️',':umbrella2:':'☂️',':umbrella:':'☔',':funeral_urn:':'⚱️',':urn:':'⚱️',':v:':'✌️',':virgo:':'♍',':warning:':'⚠️',':watch:':'⌚',':wavy_dash:':'〰️',':wheel_of_dharma:':'☸️',':wheelchair:':'♿',':white_check_mark:':'✅',':white_circle:':'⚪',':white_large_square:':'⬜',':white_medium_small_square:':'◽',':white_medium_square:':'◻️',':white_small_square:':'▫️',':writing_hand:':'✍️',':x:':'❌',':yin_yang:':'☯️',':zap:':'⚡'}; +const emojis = { + ':england:': '🏴󠁧󠁢󠁥󠁮󠁧󠁿', + ':scotland:': '🏴󠁧󠁢󠁳󠁣󠁴󠁿', + ':wales:': '🏴󠁧󠁢󠁷󠁬󠁳󠁿', + ':men_holding_hands_medium_light_skin_tone_light_skin_tone:': '👨🏼‍🤝‍👨🏻', + ':men_holding_hands_tone2_tone1:': '👨🏼‍🤝‍👨🏻', + ':men_holding_hands_medium_skin_tone_light_skin_tone:': '👨🏽‍🤝‍👨🏻', + ':men_holding_hands_tone3_tone1:': '👨🏽‍🤝‍👨🏻', + ':men_holding_hands_medium_skin_tone_medium_light_skin_tone:': '👨🏽‍🤝‍👨🏼', + ':men_holding_hands_tone3_tone2:': '👨🏽‍🤝‍👨🏼', + ':men_holding_hands_medium_dark_skin_tone_light_skin_tone:': '👨🏾‍🤝‍👨🏻', + ':men_holding_hands_tone4_tone1:': '👨🏾‍🤝‍👨🏻', + ':men_holding_hands_medium_dark_skin_tone_medium_light_skin_tone:': '👨🏾‍🤝‍👨🏼', + ':men_holding_hands_tone4_tone2:': '👨🏾‍🤝‍👨🏼', + ':men_holding_hands_medium_dark_skin_tone_medium_skin_tone:': '👨🏾‍🤝‍👨🏽', + ':men_holding_hands_tone4_tone3:': '👨🏾‍🤝‍👨🏽', + ':men_holding_hands_dark_skin_tone_light_skin_tone:': '👨🏿‍🤝‍👨🏻', + ':men_holding_hands_tone5_tone1:': '👨🏿‍🤝‍👨🏻', + ':men_holding_hands_dark_skin_tone_medium_light_skin_tone:': '👨🏿‍🤝‍👨🏼', + ':men_holding_hands_tone5_tone2:': '👨🏿‍🤝‍👨🏼', + ':men_holding_hands_dark_skin_tone_medium_skin_tone:': '👨🏿‍🤝‍👨🏽', + ':men_holding_hands_tone5_tone3:': '👨🏿‍🤝‍👨🏽', + ':men_holding_hands_dark_skin_tone_medium_dark_skin_tone:': '👨🏿‍🤝‍👨🏾', + ':men_holding_hands_tone5_tone4:': '👨🏿‍🤝‍👨🏾', + ':people_holding_hands_light_skin_tone:': '🧑🏻‍🤝‍🧑🏻', + ':people_holding_hands_tone1:': '🧑🏻‍🤝‍🧑🏻', + ':people_holding_hands_medium_light_skin_tone:': '🧑🏼‍🤝‍🧑🏼', + ':people_holding_hands_tone2:': '🧑🏼‍🤝‍🧑🏼', + ':people_holding_hands_medium_light_skin_tone_light_skin_tone:': '🧑🏼‍🤝‍🧑🏻', + ':people_holding_hands_tone2_tone1:': '🧑🏼‍🤝‍🧑🏻', + ':people_holding_hands_medium_skin_tone:': '🧑🏽‍🤝‍🧑🏽', + ':people_holding_hands_tone3:': '🧑🏽‍🤝‍🧑🏽', + ':people_holding_hands_medium_skin_tone_light_skin_tone:': '🧑🏽‍🤝‍🧑🏻', + ':people_holding_hands_tone3_tone1:': '🧑🏽‍🤝‍🧑🏻', + ':people_holding_hands_medium_skin_tone_medium_light_skin_tone:': '🧑🏽‍🤝‍🧑🏼', + ':people_holding_hands_tone3_tone2:': '🧑🏽‍🤝‍🧑🏼', + ':people_holding_hands_medium_dark_skin_tone:': '🧑🏾‍🤝‍🧑🏾', + ':people_holding_hands_tone4:': '🧑🏾‍🤝‍🧑🏾', + ':people_holding_hands_medium_dark_skin_tone_light_skin_tone:': '🧑🏾‍🤝‍🧑🏻', + ':people_holding_hands_tone4_tone1:': '🧑🏾‍🤝‍🧑🏻', + ':people_holding_hands_medium_dark_skin_tone_medium_light_skin_tone:': '🧑🏾‍🤝‍🧑🏼', + ':people_holding_hands_tone4_tone2:': '🧑🏾‍🤝‍🧑🏼', + ':people_holding_hands_medium_dark_skin_tone_medium_skin_tone:': '🧑🏾‍🤝‍🧑🏽', + ':people_holding_hands_tone4_tone3:': '🧑🏾‍🤝‍🧑🏽', + ':people_holding_hands_dark_skin_tone:': '🧑🏿‍🤝‍🧑🏿', + ':people_holding_hands_tone5:': '🧑🏿‍🤝‍🧑🏿', + ':people_holding_hands_dark_skin_tone_light_skin_tone:': '🧑🏿‍🤝‍🧑🏻', + ':people_holding_hands_tone5_tone1:': '🧑🏿‍🤝‍🧑🏻', + ':people_holding_hands_dark_skin_tone_medium_light_skin_tone:': '🧑🏿‍🤝‍🧑🏼', + ':people_holding_hands_tone5_tone2:': '🧑🏿‍🤝‍🧑🏼', + ':people_holding_hands_dark_skin_tone_medium_skin_tone:': '🧑🏿‍🤝‍🧑🏽', + ':people_holding_hands_tone5_tone3:': '🧑🏿‍🤝‍🧑🏽', + ':people_holding_hands_dark_skin_tone_medium_dark_skin_tone:': '🧑🏿‍🤝‍🧑🏾', + ':people_holding_hands_tone5_tone4:': '🧑🏿‍🤝‍🧑🏾', + ':woman_and_man_holding_hands_light_skin_tone_medium_light_skin_tone:': '👩🏻‍🤝‍👨🏼', + ':woman_and_man_holding_hands_tone1_tone2:': '👩🏻‍🤝‍👨🏼', + ':woman_and_man_holding_hands_light_skin_tone_medium_skin_tone:': '👩🏻‍🤝‍👨🏽', + ':woman_and_man_holding_hands_tone1_tone3:': '👩🏻‍🤝‍👨🏽', + ':woman_and_man_holding_hands_light_skin_tone_medium_dark_skin_tone:': '👩🏻‍🤝‍👨🏾', + ':woman_and_man_holding_hands_tone1_tone4:': '👩🏻‍🤝‍👨🏾', + ':woman_and_man_holding_hands_light_skin_tone_dark_skin_tone:': '👩🏻‍🤝‍👨🏿', + ':woman_and_man_holding_hands_tone1_tone5:': '👩🏻‍🤝‍👨🏿', + ':woman_and_man_holding_hands_medium_light_skin_tone_light_skin_tone:': '👩🏼‍🤝‍👨🏻', + ':woman_and_man_holding_hands_tone2_tone1:': '👩🏼‍🤝‍👨🏻', + ':woman_and_man_holding_hands_medium_light_skin_tone_medium_skin_tone:': '👩🏼‍🤝‍👨🏽', + ':woman_and_man_holding_hands_tone2_tone3:': '👩🏼‍🤝‍👨🏽', + ':woman_and_man_holding_hands_medium_light_skin_tone_medium_dark_skin_tone:': '👩🏼‍🤝‍👨🏾', + ':woman_and_man_holding_hands_tone2_tone4:': '👩🏼‍🤝‍👨🏾', + ':woman_and_man_holding_hands_medium_light_skin_tone_dark_skin_tone:': '👩🏼‍🤝‍👨🏿', + ':woman_and_man_holding_hands_tone2_tone5:': '👩🏼‍🤝‍👨🏿', + ':woman_and_man_holding_hands_medium_skin_tone_light_skin_tone:': '👩🏽‍🤝‍👨🏻', + ':woman_and_man_holding_hands_tone3_tone1:': '👩🏽‍🤝‍👨🏻', + ':woman_and_man_holding_hands_medium_skin_tone_medium_light_skin_tone:': '👩🏽‍🤝‍👨🏼', + ':woman_and_man_holding_hands_tone3_tone2:': '👩🏽‍🤝‍👨🏼', + ':woman_and_man_holding_hands_medium_skin_tone_medium_dark_skin_tone:': '👩🏽‍🤝‍👨🏾', + ':woman_and_man_holding_hands_tone3_tone4:': '👩🏽‍🤝‍👨🏾', + ':woman_and_man_holding_hands_medium_skin_tone_dark_skin_tone:': '👩🏽‍🤝‍👨🏿', + ':woman_and_man_holding_hands_tone3_tone5:': '👩🏽‍🤝‍👨🏿', + ':woman_and_man_holding_hands_medium_dark_skin_tone_light_skin_tone:': '👩🏾‍🤝‍👨🏻', + ':woman_and_man_holding_hands_tone4_tone1:': '👩🏾‍🤝‍👨🏻', + ':woman_and_man_holding_hands_medium_dark_skin_tone_medium_light_skin_tone:': '👩🏾‍🤝‍👨🏼', + ':woman_and_man_holding_hands_tone4_tone2:': '👩🏾‍🤝‍👨🏼', + ':woman_and_man_holding_hands_medium_dark_skin_tone_medium_skin_tone:': '👩🏾‍🤝‍👨🏽', + ':woman_and_man_holding_hands_tone4_tone3:': '👩🏾‍🤝‍👨🏽', + ':woman_and_man_holding_hands_medium_dark_skin_tone_dark_skin_tone:': '👩🏾‍🤝‍👨🏿', + ':woman_and_man_holding_hands_tone4_tone5:': '👩🏾‍🤝‍👨🏿', + ':woman_and_man_holding_hands_dark_skin_tone_light_skin_tone:': '👩🏿‍🤝‍👨🏻', + ':woman_and_man_holding_hands_tone5_tone1:': '👩🏿‍🤝‍👨🏻', + ':woman_and_man_holding_hands_dark_skin_tone_medium_light_skin_tone:': '👩🏿‍🤝‍👨🏼', + ':woman_and_man_holding_hands_tone5_tone2:': '👩🏿‍🤝‍👨🏼', + ':woman_and_man_holding_hands_dark_skin_tone_medium_skin_tone:': '👩🏿‍🤝‍👨🏽', + ':woman_and_man_holding_hands_tone5_tone3:': '👩🏿‍🤝‍👨🏽', + ':woman_and_man_holding_hands_dark_skin_tone_medium_dark_skin_tone:': '👩🏿‍🤝‍👨🏾', + ':woman_and_man_holding_hands_tone5_tone4:': '👩🏿‍🤝‍👨🏾', + ':women_holding_hands_medium_light_skin_tone_light_skin_tone:': '👩🏼‍🤝‍👩🏻', + ':women_holding_hands_tone2_tone1:': '👩🏼‍🤝‍👩🏻', + ':women_holding_hands_medium_skin_tone_light_skin_tone:': '👩🏽‍🤝‍👩🏻', + ':women_holding_hands_tone3_tone1:': '👩🏽‍🤝‍👩🏻', + ':women_holding_hands_medium_skin_tone_medium_light_skin_tone:': '👩🏽‍🤝‍👩🏼', + ':women_holding_hands_tone3_tone2:': '👩🏽‍🤝‍👩🏼', + ':women_holding_hands_medium_dark_skin_tone_light_skin_tone:': '👩🏾‍🤝‍👩🏻', + ':women_holding_hands_tone4_tone1:': '👩🏾‍🤝‍👩🏻', + ':women_holding_hands_medium_dark_skin_tone_medium_light_skin_tone:': '👩🏾‍🤝‍👩🏼', + ':women_holding_hands_tone4_tone2:': '👩🏾‍🤝‍👩🏼', + ':women_holding_hands_medium_dark_skin_tone_medium_skin_tone:': '👩🏾‍🤝‍👩🏽', + ':women_holding_hands_tone4_tone3:': '👩🏾‍🤝‍👩🏽', + ':women_holding_hands_dark_skin_tone_light_skin_tone:': '👩🏿‍🤝‍👩🏻', + ':women_holding_hands_tone5_tone1:': '👩🏿‍🤝‍👩🏻', + ':women_holding_hands_dark_skin_tone_medium_light_skin_tone:': '👩🏿‍🤝‍👩🏼', + ':women_holding_hands_tone5_tone2:': '👩🏿‍🤝‍👩🏼', + ':women_holding_hands_dark_skin_tone_medium_skin_tone:': '👩🏿‍🤝‍👩🏽', + ':women_holding_hands_tone5_tone3:': '👩🏿‍🤝‍👩🏽', + ':women_holding_hands_dark_skin_tone_medium_dark_skin_tone:': '👩🏿‍🤝‍👩🏾', + ':women_holding_hands_tone5_tone4:': '👩🏿‍🤝‍👩🏾', + ':family_mmbb:': '👨‍👨‍👦‍👦', + ':family_mmgb:': '👨‍👨‍👧‍👦', + ':family_mmgg:': '👨‍👨‍👧‍👧', + ':family_mwbb:': '👨‍👩‍👦‍👦', + ':family_mwgb:': '👨‍👩‍👧‍👦', + ':family_mwgg:': '👨‍👩‍👧‍👧', + ':family_wwbb:': '👩‍👩‍👦‍👦', + ':family_wwgb:': '👩‍👩‍👧‍👦', + ':family_wwgg:': '👩‍👩‍👧‍👧', + ':couplekiss_mm:': '👨‍❤️‍💋👨', + ':kiss_mm:': '👨‍❤️‍💋👨', + ':kiss_woman_man:': '👩‍❤️‍💋👨', + ':couplekiss_ww:': '👩‍❤️‍💋👩', + ':kiss_ww:': '👩‍❤️‍💋👩', + ':family_man_boy_boy:': '👨‍👦‍👦', + ':family_man_girl_boy:': '👨‍👧‍👦', + ':family_man_girl_girl:': '👨‍👧‍👧', + ':family_man_woman_boy:': '👨‍👩‍👦', + ':family_mmb:': '👨‍👨‍👦', + ':family_mmg:': '👨‍👨‍👧', + ':family_mwg:': '👨‍👩‍👧', + ':family_woman_boy_boy:': '👩‍👦‍👦', + ':family_woman_girl_boy:': '👩‍👧‍👦', + ':family_woman_girl_girl:': '👩‍👧‍👧', + ':family_wwb:': '👩‍👩‍👦', + ':family_wwg:': '👩‍👩‍👧', + ':man_artist_light_skin_tone:': '👨🏻‍🎨', + ':man_artist_tone1:': '👨🏻‍🎨', + ':man_artist_medium_light_skin_tone:': '👨🏼‍🎨', + ':man_artist_tone2:': '👨🏼‍🎨', + ':man_artist_medium_skin_tone:': '👨🏽‍🎨', + ':man_artist_tone3:': '👨🏽‍🎨', + ':man_artist_medium_dark_skin_tone:': '👨🏾‍🎨', + ':man_artist_tone4:': '👨🏾‍🎨', + ':man_artist_dark_skin_tone:': '👨🏿‍🎨', + ':man_artist_tone5:': '👨🏿‍🎨', + ':man_astronaut_light_skin_tone:': '👨🏻‍🚀', + ':man_astronaut_tone1:': '👨🏻‍🚀', + ':man_astronaut_medium_light_skin_tone:': '👨🏼‍🚀', + ':man_astronaut_tone2:': '👨🏼‍🚀', + ':man_astronaut_medium_skin_tone:': '👨🏽‍🚀', + ':man_astronaut_tone3:': '👨🏽‍🚀', + ':man_astronaut_medium_dark_skin_tone:': '👨🏾‍🚀', + ':man_astronaut_tone4:': '👨🏾‍🚀', + ':man_astronaut_dark_skin_tone:': '👨🏿‍🚀', + ':man_astronaut_tone5:': '👨🏿‍🚀', + ':man_bald_light_skin_tone:': '👨🏻‍🦲', + ':man_bald_tone1:': '👨🏻‍🦲', + ':man_bald_medium_light_skin_tone:': '👨🏼‍🦲', + ':man_bald_tone2:': '👨🏼‍🦲', + ':man_bald_medium_skin_tone:': '👨🏽‍🦲', + ':man_bald_tone3:': '👨🏽‍🦲', + ':man_bald_medium_dark_skin_tone:': '👨🏾‍🦲', + ':man_bald_tone4:': '👨🏾‍🦲', + ':man_bald_dark_skin_tone:': '👨🏿‍🦲', + ':man_bald_tone5:': '👨🏿‍🦲', + ':man_cook_light_skin_tone:': '👨🏻‍🍳', + ':man_cook_tone1:': '👨🏻‍🍳', + ':man_cook_medium_light_skin_tone:': '👨🏼‍🍳', + ':man_cook_tone2:': '👨🏼‍🍳', + ':man_cook_medium_skin_tone:': '👨🏽‍🍳', + ':man_cook_tone3:': '👨🏽‍🍳', + ':man_cook_medium_dark_skin_tone:': '👨🏾‍🍳', + ':man_cook_tone4:': '👨🏾‍🍳', + ':man_cook_dark_skin_tone:': '👨🏿‍🍳', + ':man_cook_tone5:': '👨🏿‍🍳', + ':man_curly_haired_light_skin_tone:': '👨🏻‍🦱', + ':man_curly_haired_tone1:': '👨🏻‍🦱', + ':man_curly_haired_medium_light_skin_tone:': '👨🏼‍🦱', + ':man_curly_haired_tone2:': '👨🏼‍🦱', + ':man_curly_haired_medium_skin_tone:': '👨🏽‍🦱', + ':man_curly_haired_tone3:': '👨🏽‍🦱', + ':man_curly_haired_medium_dark_skin_tone:': '👨🏾‍🦱', + ':man_curly_haired_tone4:': '👨🏾‍🦱', + ':man_curly_haired_dark_skin_tone:': '👨🏿‍🦱', + ':man_curly_haired_tone5:': '👨🏿‍🦱', + ':man_factory_worker_light_skin_tone:': '👨🏻‍🏭', + ':man_factory_worker_tone1:': '👨🏻‍🏭', + ':man_factory_worker_medium_light_skin_tone:': '👨🏼‍🏭', + ':man_factory_worker_tone2:': '👨🏼‍🏭', + ':man_factory_worker_medium_skin_tone:': '👨🏽‍🏭', + ':man_factory_worker_tone3:': '👨🏽‍🏭', + ':man_factory_worker_medium_dark_skin_tone:': '👨🏾‍🏭', + ':man_factory_worker_tone4:': '👨🏾‍🏭', + ':man_factory_worker_dark_skin_tone:': '👨🏿‍🏭', + ':man_factory_worker_tone5:': '👨🏿‍🏭', + ':man_farmer_light_skin_tone:': '👨🏻‍🌾', + ':man_farmer_tone1:': '👨🏻‍🌾', + ':man_farmer_medium_light_skin_tone:': '👨🏼‍🌾', + ':man_farmer_tone2:': '👨🏼‍🌾', + ':man_farmer_medium_skin_tone:': '👨🏽‍🌾', + ':man_farmer_tone3:': '👨🏽‍🌾', + ':man_farmer_medium_dark_skin_tone:': '👨🏾‍🌾', + ':man_farmer_tone4:': '👨🏾‍🌾', + ':man_farmer_dark_skin_tone:': '👨🏿‍🌾', + ':man_farmer_tone5:': '👨🏿‍🌾', + ':man_firefighter_light_skin_tone:': '👨🏻‍🚒', + ':man_firefighter_tone1:': '👨🏻‍🚒', + ':man_firefighter_medium_light_skin_tone:': '👨🏼‍🚒', + ':man_firefighter_tone2:': '👨🏼‍🚒', + ':man_firefighter_medium_skin_tone:': '👨🏽‍🚒', + ':man_firefighter_tone3:': '👨🏽‍🚒', + ':man_firefighter_medium_dark_skin_tone:': '👨🏾‍🚒', + ':man_firefighter_tone4:': '👨🏾‍🚒', + ':man_firefighter_dark_skin_tone:': '👨🏿‍🚒', + ':man_firefighter_tone5:': '👨🏿‍🚒', + ':man_in_manual_wheelchair_light_skin_tone:': '👨🏻‍🦽', + ':man_in_manual_wheelchair_tone1:': '👨🏻‍🦽', + ':man_in_manual_wheelchair_medium_light_skin_tone:': '👨🏼‍🦽', + ':man_in_manual_wheelchair_tone2:': '👨🏼‍🦽', + ':man_in_manual_wheelchair_medium_skin_tone:': '👨🏽‍🦽', + ':man_in_manual_wheelchair_tone3:': '👨🏽‍🦽', + ':man_in_manual_wheelchair_medium_dark_skin_tone:': '👨🏾‍🦽', + ':man_in_manual_wheelchair_tone4:': '👨🏾‍🦽', + ':man_in_manual_wheelchair_dark_skin_tone:': '👨🏿‍🦽', + ':man_in_manual_wheelchair_tone5:': '👨🏿‍🦽', + ':man_in_motorized_wheelchair_light_skin_tone:': '👨🏻‍🦼', + ':man_in_motorized_wheelchair_tone1:': '👨🏻‍🦼', + ':man_in_motorized_wheelchair_medium_light_skin_tone:': '👨🏼‍🦼', + ':man_in_motorized_wheelchair_tone2:': '👨🏼‍🦼', + ':man_in_motorized_wheelchair_medium_skin_tone:': '👨🏽‍🦼', + ':man_in_motorized_wheelchair_tone3:': '👨🏽‍🦼', + ':man_in_motorized_wheelchair_medium_dark_skin_tone:': '👨🏾‍🦼', + ':man_in_motorized_wheelchair_tone4:': '👨🏾‍🦼', + ':man_in_motorized_wheelchair_dark_skin_tone:': '👨🏿‍🦼', + ':man_in_motorized_wheelchair_tone5:': '👨🏿‍🦼', + ':man_mechanic_light_skin_tone:': '👨🏻‍🔧', + ':man_mechanic_tone1:': '👨🏻‍🔧', + ':man_mechanic_medium_light_skin_tone:': '👨🏼‍🔧', + ':man_mechanic_tone2:': '👨🏼‍🔧', + ':man_mechanic_medium_skin_tone:': '👨🏽‍🔧', + ':man_mechanic_tone3:': '👨🏽‍🔧', + ':man_mechanic_medium_dark_skin_tone:': '👨🏾‍🔧', + ':man_mechanic_tone4:': '👨🏾‍🔧', + ':man_mechanic_dark_skin_tone:': '👨🏿‍🔧', + ':man_mechanic_tone5:': '👨🏿‍🔧', + ':man_office_worker_light_skin_tone:': '👨🏻‍💼', + ':man_office_worker_tone1:': '👨🏻‍💼', + ':man_office_worker_medium_light_skin_tone:': '👨🏼‍💼', + ':man_office_worker_tone2:': '👨🏼‍💼', + ':man_office_worker_medium_skin_tone:': '👨🏽‍💼', + ':man_office_worker_tone3:': '👨🏽‍💼', + ':man_office_worker_medium_dark_skin_tone:': '👨🏾‍💼', + ':man_office_worker_tone4:': '👨🏾‍💼', + ':man_office_worker_dark_skin_tone:': '👨🏿‍💼', + ':man_office_worker_tone5:': '👨🏿‍💼', + ':man_red_haired_light_skin_tone:': '👨🏻‍🦰', + ':man_red_haired_tone1:': '👨🏻‍🦰', + ':man_red_haired_medium_light_skin_tone:': '👨🏼‍🦰', + ':man_red_haired_tone2:': '👨🏼‍🦰', + ':man_red_haired_medium_skin_tone:': '👨🏽‍🦰', + ':man_red_haired_tone3:': '👨🏽‍🦰', + ':man_red_haired_medium_dark_skin_tone:': '👨🏾‍🦰', + ':man_red_haired_tone4:': '👨🏾‍🦰', + ':man_red_haired_dark_skin_tone:': '👨🏿‍🦰', + ':man_red_haired_tone5:': '👨🏿‍🦰', + ':man_scientist_light_skin_tone:': '👨🏻‍🔬', + ':man_scientist_tone1:': '👨🏻‍🔬', + ':man_scientist_medium_light_skin_tone:': '👨🏼‍🔬', + ':man_scientist_tone2:': '👨🏼‍🔬', + ':man_scientist_medium_skin_tone:': '👨🏽‍🔬', + ':man_scientist_tone3:': '👨🏽‍🔬', + ':man_scientist_medium_dark_skin_tone:': '👨🏾‍🔬', + ':man_scientist_tone4:': '👨🏾‍🔬', + ':man_scientist_dark_skin_tone:': '👨🏿‍🔬', + ':man_scientist_tone5:': '👨🏿‍🔬', + ':man_singer_light_skin_tone:': '👨🏻‍🎤', + ':man_singer_tone1:': '👨🏻‍🎤', + ':man_singer_medium_light_skin_tone:': '👨🏼‍🎤', + ':man_singer_tone2:': '👨🏼‍🎤', + ':man_singer_medium_skin_tone:': '👨🏽‍🎤', + ':man_singer_tone3:': '👨🏽‍🎤', + ':man_singer_medium_dark_skin_tone:': '👨🏾‍🎤', + ':man_singer_tone4:': '👨🏾‍🎤', + ':man_singer_dark_skin_tone:': '👨🏿‍🎤', + ':man_singer_tone5:': '👨🏿‍🎤', + ':man_student_light_skin_tone:': '👨🏻‍🎓', + ':man_student_tone1:': '👨🏻‍🎓', + ':man_student_medium_light_skin_tone:': '👨🏼‍🎓', + ':man_student_tone2:': '👨🏼‍🎓', + ':man_student_medium_skin_tone:': '👨🏽‍🎓', + ':man_student_tone3:': '👨🏽‍🎓', + ':man_student_medium_dark_skin_tone:': '👨🏾‍🎓', + ':man_student_tone4:': '👨🏾‍🎓', + ':man_student_dark_skin_tone:': '👨🏿‍🎓', + ':man_student_tone5:': '👨🏿‍🎓', + ':man_teacher_light_skin_tone:': '👨🏻‍🏫', + ':man_teacher_tone1:': '👨🏻‍🏫', + ':man_teacher_medium_light_skin_tone:': '👨🏼‍🏫', + ':man_teacher_tone2:': '👨🏼‍🏫', + ':man_teacher_medium_skin_tone:': '👨🏽‍🏫', + ':man_teacher_tone3:': '👨🏽‍🏫', + ':man_teacher_medium_dark_skin_tone:': '👨🏾‍🏫', + ':man_teacher_tone4:': '👨🏾‍🏫', + ':man_teacher_dark_skin_tone:': '👨🏿‍🏫', + ':man_teacher_tone5:': '👨🏿‍🏫', + ':man_technologist_light_skin_tone:': '👨🏻‍💻', + ':man_technologist_tone1:': '👨🏻‍💻', + ':man_technologist_medium_light_skin_tone:': '👨🏼‍💻', + ':man_technologist_tone2:': '👨🏼‍💻', + ':man_technologist_medium_skin_tone:': '👨🏽‍💻', + ':man_technologist_tone3:': '👨🏽‍💻', + ':man_technologist_medium_dark_skin_tone:': '👨🏾‍💻', + ':man_technologist_tone4:': '👨🏾‍💻', + ':man_technologist_dark_skin_tone:': '👨🏿‍💻', + ':man_technologist_tone5:': '👨🏿‍💻', + ':man_white_haired_light_skin_tone:': '👨🏻‍🦳', + ':man_white_haired_tone1:': '👨🏻‍🦳', + ':man_white_haired_medium_light_skin_tone:': '👨🏼‍🦳', + ':man_white_haired_tone2:': '👨🏼‍🦳', + ':man_white_haired_medium_skin_tone:': '👨🏽‍🦳', + ':man_white_haired_tone3:': '👨🏽‍🦳', + ':man_white_haired_medium_dark_skin_tone:': '👨🏾‍🦳', + ':man_white_haired_tone4:': '👨🏾‍🦳', + ':man_white_haired_dark_skin_tone:': '👨🏿‍🦳', + ':man_white_haired_tone5:': '👨🏿‍🦳', + ':man_with_probing_cane_light_skin_tone:': '👨🏻‍🦯', + ':man_with_probing_cane_tone1:': '👨🏻‍🦯', + ':man_with_probing_cane_medium_light_skin_tone:': '👨🏼‍🦯', + ':man_with_probing_cane_tone2:': '👨🏼‍🦯', + ':man_with_probing_cane_medium_skin_tone:': '👨🏽‍🦯', + ':man_with_probing_cane_tone3:': '👨🏽‍🦯', + ':man_with_probing_cane_medium_dark_skin_tone:': '👨🏾‍🦯', + ':man_with_probing_cane_tone4:': '👨🏾‍🦯', + ':man_with_probing_cane_dark_skin_tone:': '👨🏿‍🦯', + ':man_with_probing_cane_tone5:': '👨🏿‍🦯', + ':people_holding_hands:': '🧑‍🤝‍🧑', + ':woman_artist_light_skin_tone:': '👩🏻‍🎨', + ':woman_artist_tone1:': '👩🏻‍🎨', + ':woman_artist_medium_light_skin_tone:': '👩🏼‍🎨', + ':woman_artist_tone2:': '👩🏼‍🎨', + ':woman_artist_medium_skin_tone:': '👩🏽‍🎨', + ':woman_artist_tone3:': '👩🏽‍🎨', + ':woman_artist_medium_dark_skin_tone:': '👩🏾‍🎨', + ':woman_artist_tone4:': '👩🏾‍🎨', + ':woman_artist_dark_skin_tone:': '👩🏿‍🎨', + ':woman_artist_tone5:': '👩🏿‍🎨', + ':woman_astronaut_light_skin_tone:': '👩🏻‍🚀', + ':woman_astronaut_tone1:': '👩🏻‍🚀', + ':woman_astronaut_medium_light_skin_tone:': '👩🏼‍🚀', + ':woman_astronaut_tone2:': '👩🏼‍🚀', + ':woman_astronaut_medium_skin_tone:': '👩🏽‍🚀', + ':woman_astronaut_tone3:': '👩🏽‍🚀', + ':woman_astronaut_medium_dark_skin_tone:': '👩🏾‍🚀', + ':woman_astronaut_tone4:': '👩🏾‍🚀', + ':woman_astronaut_dark_skin_tone:': '👩🏿‍🚀', + ':woman_astronaut_tone5:': '👩🏿‍🚀', + ':woman_bald_light_skin_tone:': '👩🏻‍🦲', + ':woman_bald_tone1:': '👩🏻‍🦲', + ':woman_bald_medium_light_skin_tone:': '👩🏼‍🦲', + ':woman_bald_tone2:': '👩🏼‍🦲', + ':woman_bald_medium_skin_tone:': '👩🏽‍🦲', + ':woman_bald_tone3:': '👩🏽‍🦲', + ':woman_bald_medium_dark_skin_tone:': '👩🏾‍🦲', + ':woman_bald_tone4:': '👩🏾‍🦲', + ':woman_bald_dark_skin_tone:': '👩🏿‍🦲', + ':woman_bald_tone5:': '👩🏿‍🦲', + ':woman_cook_light_skin_tone:': '👩🏻‍🍳', + ':woman_cook_tone1:': '👩🏻‍🍳', + ':woman_cook_medium_light_skin_tone:': '👩🏼‍🍳', + ':woman_cook_tone2:': '👩🏼‍🍳', + ':woman_cook_medium_skin_tone:': '👩🏽‍🍳', + ':woman_cook_tone3:': '👩🏽‍🍳', + ':woman_cook_medium_dark_skin_tone:': '👩🏾‍🍳', + ':woman_cook_tone4:': '👩🏾‍🍳', + ':woman_cook_dark_skin_tone:': '👩🏿‍🍳', + ':woman_cook_tone5:': '👩🏿‍🍳', + ':woman_curly_haired_light_skin_tone:': '👩🏻‍🦱', + ':woman_curly_haired_tone1:': '👩🏻‍🦱', + ':woman_curly_haired_medium_light_skin_tone:': '👩🏼‍🦱', + ':woman_curly_haired_tone2:': '👩🏼‍🦱', + ':woman_curly_haired_medium_skin_tone:': '👩🏽‍🦱', + ':woman_curly_haired_tone3:': '👩🏽‍🦱', + ':woman_curly_haired_medium_dark_skin_tone:': '👩🏾‍🦱', + ':woman_curly_haired_tone4:': '👩🏾‍🦱', + ':woman_curly_haired_dark_skin_tone:': '👩🏿‍🦱', + ':woman_curly_haired_tone5:': '👩🏿‍🦱', + ':woman_factory_worker_light_skin_tone:': '👩🏻‍🏭', + ':woman_factory_worker_tone1:': '👩🏻‍🏭', + ':woman_factory_worker_medium_light_skin_tone:': '👩🏼‍🏭', + ':woman_factory_worker_tone2:': '👩🏼‍🏭', + ':woman_factory_worker_medium_skin_tone:': '👩🏽‍🏭', + ':woman_factory_worker_tone3:': '👩🏽‍🏭', + ':woman_factory_worker_medium_dark_skin_tone:': '👩🏾‍🏭', + ':woman_factory_worker_tone4:': '👩🏾‍🏭', + ':woman_factory_worker_dark_skin_tone:': '👩🏿‍🏭', + ':woman_factory_worker_tone5:': '👩🏿‍🏭', + ':woman_farmer_light_skin_tone:': '👩🏻‍🌾', + ':woman_farmer_tone1:': '👩🏻‍🌾', + ':woman_farmer_medium_light_skin_tone:': '👩🏼‍🌾', + ':woman_farmer_tone2:': '👩🏼‍🌾', + ':woman_farmer_medium_skin_tone:': '👩🏽‍🌾', + ':woman_farmer_tone3:': '👩🏽‍🌾', + ':woman_farmer_medium_dark_skin_tone:': '👩🏾‍🌾', + ':woman_farmer_tone4:': '👩🏾‍🌾', + ':woman_farmer_dark_skin_tone:': '👩🏿‍🌾', + ':woman_farmer_tone5:': '👩🏿‍🌾', + ':woman_firefighter_light_skin_tone:': '👩🏻‍🚒', + ':woman_firefighter_tone1:': '👩🏻‍🚒', + ':woman_firefighter_medium_light_skin_tone:': '👩🏼‍🚒', + ':woman_firefighter_tone2:': '👩🏼‍🚒', + ':woman_firefighter_medium_skin_tone:': '👩🏽‍🚒', + ':woman_firefighter_tone3:': '👩🏽‍🚒', + ':woman_firefighter_medium_dark_skin_tone:': '👩🏾‍🚒', + ':woman_firefighter_tone4:': '👩🏾‍🚒', + ':woman_firefighter_dark_skin_tone:': '👩🏿‍🚒', + ':woman_firefighter_tone5:': '👩🏿‍🚒', + ':woman_in_manual_wheelchair_light_skin_tone:': '👩🏻‍🦽', + ':woman_in_manual_wheelchair_tone1:': '👩🏻‍🦽', + ':woman_in_manual_wheelchair_medium_light_skin_tone:': '👩🏼‍🦽', + ':woman_in_manual_wheelchair_tone2:': '👩🏼‍🦽', + ':woman_in_manual_wheelchair_medium_skin_tone:': '👩🏽‍🦽', + ':woman_in_manual_wheelchair_tone3:': '👩🏽‍🦽', + ':woman_in_manual_wheelchair_medium_dark_skin_tone:': '👩🏾‍🦽', + ':woman_in_manual_wheelchair_tone4:': '👩🏾‍🦽', + ':woman_in_manual_wheelchair_dark_skin_tone:': '👩🏿‍🦽', + ':woman_in_manual_wheelchair_tone5:': '👩🏿‍🦽', + ':woman_in_motorized_wheelchair_light_skin_tone:': '👩🏻‍🦼', + ':woman_in_motorized_wheelchair_tone1:': '👩🏻‍🦼', + ':woman_in_motorized_wheelchair_medium_light_skin_tone:': '👩🏼‍🦼', + ':woman_in_motorized_wheelchair_tone2:': '👩🏼‍🦼', + ':woman_in_motorized_wheelchair_medium_skin_tone:': '👩🏽‍🦼', + ':woman_in_motorized_wheelchair_tone3:': '👩🏽‍🦼', + ':woman_in_motorized_wheelchair_medium_dark_skin_tone:': '👩🏾‍🦼', + ':woman_in_motorized_wheelchair_tone4:': '👩🏾‍🦼', + ':woman_in_motorized_wheelchair_dark_skin_tone:': '👩🏿‍🦼', + ':woman_in_motorized_wheelchair_tone5:': '👩🏿‍🦼', + ':woman_mechanic_light_skin_tone:': '👩🏻‍🔧', + ':woman_mechanic_tone1:': '👩🏻‍🔧', + ':woman_mechanic_medium_light_skin_tone:': '👩🏼‍🔧', + ':woman_mechanic_tone2:': '👩🏼‍🔧', + ':woman_mechanic_medium_skin_tone:': '👩🏽‍🔧', + ':woman_mechanic_tone3:': '👩🏽‍🔧', + ':woman_mechanic_medium_dark_skin_tone:': '👩🏾‍🔧', + ':woman_mechanic_tone4:': '👩🏾‍🔧', + ':woman_mechanic_dark_skin_tone:': '👩🏿‍🔧', + ':woman_mechanic_tone5:': '👩🏿‍🔧', + ':woman_office_worker_light_skin_tone:': '👩🏻‍💼', + ':woman_office_worker_tone1:': '👩🏻‍💼', + ':woman_office_worker_medium_light_skin_tone:': '👩🏼‍💼', + ':woman_office_worker_tone2:': '👩🏼‍💼', + ':woman_office_worker_medium_skin_tone:': '👩🏽‍💼', + ':woman_office_worker_tone3:': '👩🏽‍💼', + ':woman_office_worker_medium_dark_skin_tone:': '👩🏾‍💼', + ':woman_office_worker_tone4:': '👩🏾‍💼', + ':woman_office_worker_dark_skin_tone:': '👩🏿‍💼', + ':woman_office_worker_tone5:': '👩🏿‍💼', + ':woman_red_haired_light_skin_tone:': '👩🏻‍🦰', + ':woman_red_haired_tone1:': '👩🏻‍🦰', + ':woman_red_haired_medium_light_skin_tone:': '👩🏼‍🦰', + ':woman_red_haired_tone2:': '👩🏼‍🦰', + ':woman_red_haired_medium_skin_tone:': '👩🏽‍🦰', + ':woman_red_haired_tone3:': '👩🏽‍🦰', + ':woman_red_haired_medium_dark_skin_tone:': '👩🏾‍🦰', + ':woman_red_haired_tone4:': '👩🏾‍🦰', + ':woman_red_haired_dark_skin_tone:': '👩🏿‍🦰', + ':woman_red_haired_tone5:': '👩🏿‍🦰', + ':woman_scientist_light_skin_tone:': '👩🏻‍🔬', + ':woman_scientist_tone1:': '👩🏻‍🔬', + ':woman_scientist_medium_light_skin_tone:': '👩🏼‍🔬', + ':woman_scientist_tone2:': '👩🏼‍🔬', + ':woman_scientist_medium_skin_tone:': '👩🏽‍🔬', + ':woman_scientist_tone3:': '👩🏽‍🔬', + ':woman_scientist_medium_dark_skin_tone:': '👩🏾‍🔬', + ':woman_scientist_tone4:': '👩🏾‍🔬', + ':woman_scientist_dark_skin_tone:': '👩🏿‍🔬', + ':woman_scientist_tone5:': '👩🏿‍🔬', + ':woman_singer_light_skin_tone:': '👩🏻‍🎤', + ':woman_singer_tone1:': '👩🏻‍🎤', + ':woman_singer_medium_light_skin_tone:': '👩🏼‍🎤', + ':woman_singer_tone2:': '👩🏼‍🎤', + ':woman_singer_medium_skin_tone:': '👩🏽‍🎤', + ':woman_singer_tone3:': '👩🏽‍🎤', + ':woman_singer_medium_dark_skin_tone:': '👩🏾‍🎤', + ':woman_singer_tone4:': '👩🏾‍🎤', + ':woman_singer_dark_skin_tone:': '👩🏿‍🎤', + ':woman_singer_tone5:': '👩🏿‍🎤', + ':woman_student_light_skin_tone:': '👩🏻‍🎓', + ':woman_student_tone1:': '👩🏻‍🎓', + ':woman_student_medium_light_skin_tone:': '👩🏼‍🎓', + ':woman_student_tone2:': '👩🏼‍🎓', + ':woman_student_medium_skin_tone:': '👩🏽‍🎓', + ':woman_student_tone3:': '👩🏽‍🎓', + ':woman_student_medium_dark_skin_tone:': '👩🏾‍🎓', + ':woman_student_tone4:': '👩🏾‍🎓', + ':woman_student_dark_skin_tone:': '👩🏿‍🎓', + ':woman_student_tone5:': '👩🏿‍🎓', + ':woman_teacher_light_skin_tone:': '👩🏻‍🏫', + ':woman_teacher_tone1:': '👩🏻‍🏫', + ':woman_teacher_medium_light_skin_tone:': '👩🏼‍🏫', + ':woman_teacher_tone2:': '👩🏼‍🏫', + ':woman_teacher_medium_skin_tone:': '👩🏽‍🏫', + ':woman_teacher_tone3:': '👩🏽‍🏫', + ':woman_teacher_medium_dark_skin_tone:': '👩🏾‍🏫', + ':woman_teacher_tone4:': '👩🏾‍🏫', + ':woman_teacher_dark_skin_tone:': '👩🏿‍🏫', + ':woman_teacher_tone5:': '👩🏿‍🏫', + ':woman_technologist_light_skin_tone:': '👩🏻‍💻', + ':woman_technologist_tone1:': '👩🏻‍💻', + ':woman_technologist_medium_light_skin_tone:': '👩🏼‍💻', + ':woman_technologist_tone2:': '👩🏼‍💻', + ':woman_technologist_medium_skin_tone:': '👩🏽‍💻', + ':woman_technologist_tone3:': '👩🏽‍💻', + ':woman_technologist_medium_dark_skin_tone:': '👩🏾‍💻', + ':woman_technologist_tone4:': '👩🏾‍💻', + ':woman_technologist_dark_skin_tone:': '👩🏿‍💻', + ':woman_technologist_tone5:': '👩🏿‍💻', + ':woman_white_haired_light_skin_tone:': '👩🏻‍🦳', + ':woman_white_haired_tone1:': '👩🏻‍🦳', + ':woman_white_haired_medium_light_skin_tone:': '👩🏼‍🦳', + ':woman_white_haired_tone2:': '👩🏼‍🦳', + ':woman_white_haired_medium_skin_tone:': '👩🏽‍🦳', + ':woman_white_haired_tone3:': '👩🏽‍🦳', + ':woman_white_haired_medium_dark_skin_tone:': '👩🏾‍🦳', + ':woman_white_haired_tone4:': '👩🏾‍🦳', + ':woman_white_haired_dark_skin_tone:': '👩🏿‍🦳', + ':woman_white_haired_tone5:': '👩🏿‍🦳', + ':woman_with_probing_cane_light_skin_tone:': '👩🏻‍🦯', + ':woman_with_probing_cane_tone1:': '👩🏻‍🦯', + ':woman_with_probing_cane_medium_light_skin_tone:': '👩🏼‍🦯', + ':woman_with_probing_cane_tone2:': '👩🏼‍🦯', + ':woman_with_probing_cane_medium_skin_tone:': '👩🏽‍🦯', + ':woman_with_probing_cane_tone3:': '👩🏽‍🦯', + ':woman_with_probing_cane_medium_dark_skin_tone:': '👩🏾‍🦯', + ':woman_with_probing_cane_tone4:': '👩🏾‍🦯', + ':woman_with_probing_cane_dark_skin_tone:': '👩🏿‍🦯', + ':woman_with_probing_cane_tone5:': '👩🏿‍🦯', + ':blond-haired_man_light_skin_tone:': '👱🏻‍♂️', + ':blond-haired_man_tone1:': '👱🏻‍♂️', + ':blond-haired_man_medium_light_skin_tone:': '👱🏼‍♂️', + ':blond-haired_man_tone2:': '👱🏼‍♂️', + ':blond-haired_man_medium_skin_tone:': '👱🏽‍♂️', + ':blond-haired_man_tone3:': '👱🏽‍♂️', + ':blond-haired_man_medium_dark_skin_tone:': '👱🏾‍♂️', + ':blond-haired_man_tone4:': '👱🏾‍♂️', + ':blond-haired_man_dark_skin_tone:': '👱🏿‍♂️', + ':blond-haired_man_tone5:': '👱🏿‍♂️', + ':blond-haired_woman_light_skin_tone:': '👱🏻‍♀️', + ':blond-haired_woman_tone1:': '👱🏻‍♀️', + ':blond-haired_woman_medium_light_skin_tone:': '👱🏼‍♀️', + ':blond-haired_woman_tone2:': '👱🏼‍♀️', + ':blond-haired_woman_medium_skin_tone:': '👱🏽‍♀️', + ':blond-haired_woman_tone3:': '👱🏽‍♀️', + ':blond-haired_woman_medium_dark_skin_tone:': '👱🏾‍♀️', + ':blond-haired_woman_tone4:': '👱🏾‍♀️', + ':blond-haired_woman_dark_skin_tone:': '👱🏿‍♀️', + ':blond-haired_woman_tone5:': '👱🏿‍♀️', + ':couple_with_heart_mm:': '👨‍❤️‍👨', + ':couple_mm:': '👨‍❤️‍👨', + ':couple_with_heart_woman_man:': '👩‍❤️‍👨', + ':couple_with_heart_ww:': '👩‍❤️‍👩', + ':couple_ww:': '👩‍❤️‍👩', + ':deaf_man_light_skin_tone:': '🧏🏻‍♂️', + ':deaf_man_tone1:': '🧏🏻‍♂️', + ':deaf_man_medium_light_skin_tone:': '🧏🏼‍♂️', + ':deaf_man_tone2:': '🧏🏼‍♂️', + ':deaf_man_medium_skin_tone:': '🧏🏽‍♂️', + ':deaf_man_tone3:': '🧏🏽‍♂️', + ':deaf_man_medium_dark_skin_tone:': '🧏🏾‍♂️', + ':deaf_man_tone4:': '🧏🏾‍♂️', + ':deaf_man_dark_skin_tone:': '🧏🏿‍♂️', + ':deaf_man_tone5:': '🧏🏿‍♂️', + ':deaf_woman_light_skin_tone:': '🧏🏻‍♀️', + ':deaf_woman_tone1:': '🧏🏻‍♀️', + ':deaf_woman_medium_light_skin_tone:': '🧏🏼‍♀️', + ':deaf_woman_tone2:': '🧏🏼‍♀️', + ':deaf_woman_medium_skin_tone:': '🧏🏽‍♀️', + ':deaf_woman_tone3:': '🧏🏽‍♀️', + ':deaf_woman_medium_dark_skin_tone:': '🧏🏾‍♀️', + ':deaf_woman_tone4:': '🧏🏾‍♀️', + ':deaf_woman_dark_skin_tone:': '🧏🏿‍♀️', + ':deaf_woman_tone5:': '🧏🏿‍♀️', + ':man_biking_light_skin_tone:': '🚴🏻‍♂️', + ':man_biking_tone1:': '🚴🏻‍♂️', + ':man_biking_medium_light_skin_tone:': '🚴🏼‍♂️', + ':man_biking_tone2:': '🚴🏼‍♂️', + ':man_biking_medium_skin_tone:': '🚴🏽‍♂️', + ':man_biking_tone3:': '🚴🏽‍♂️', + ':man_biking_medium_dark_skin_tone:': '🚴🏾‍♂️', + ':man_biking_tone4:': '🚴🏾‍♂️', + ':man_biking_dark_skin_tone:': '🚴🏿‍♂️', + ':man_biking_tone5:': '🚴🏿‍♂️', + ':man_bowing_light_skin_tone:': '🙇🏻‍♂️', + ':man_bowing_tone1:': '🙇🏻‍♂️', + ':man_bowing_medium_light_skin_tone:': '🙇🏼‍♂️', + ':man_bowing_tone2:': '🙇🏼‍♂️', + ':man_bowing_medium_skin_tone:': '🙇🏽‍♂️', + ':man_bowing_tone3:': '🙇🏽‍♂️', + ':man_bowing_medium_dark_skin_tone:': '🙇🏾‍♂️', + ':man_bowing_tone4:': '🙇🏾‍♂️', + ':man_bowing_dark_skin_tone:': '🙇🏿‍♂️', + ':man_bowing_tone5:': '🙇🏿‍♂️', + ':man_cartwheeling_light_skin_tone:': '🤸🏻‍♂️', + ':man_cartwheeling_tone1:': '🤸🏻‍♂️', + ':man_cartwheeling_medium_light_skin_tone:': '🤸🏼‍♂️', + ':man_cartwheeling_tone2:': '🤸🏼‍♂️', + ':man_cartwheeling_medium_skin_tone:': '🤸🏽‍♂️', + ':man_cartwheeling_tone3:': '🤸🏽‍♂️', + ':man_cartwheeling_medium_dark_skin_tone:': '🤸🏾‍♂️', + ':man_cartwheeling_tone4:': '🤸🏾‍♂️', + ':man_cartwheeling_dark_skin_tone:': '🤸🏿‍♂️', + ':man_cartwheeling_tone5:': '🤸🏿‍♂️', + ':man_climbing_light_skin_tone:': '🧗🏻‍♂️', + ':man_climbing_tone1:': '🧗🏻‍♂️', + ':man_climbing_medium_light_skin_tone:': '🧗🏼‍♂️', + ':man_climbing_tone2:': '🧗🏼‍♂️', + ':man_climbing_medium_skin_tone:': '🧗🏽‍♂️', + ':man_climbing_tone3:': '🧗🏽‍♂️', + ':man_climbing_medium_dark_skin_tone:': '🧗🏾‍♂️', + ':man_climbing_tone4:': '🧗🏾‍♂️', + ':man_climbing_dark_skin_tone:': '🧗🏿‍♂️', + ':man_climbing_tone5:': '🧗🏿‍♂️', + ':man_construction_worker_light_skin_tone:': '👷🏻‍♂️', + ':man_construction_worker_tone1:': '👷🏻‍♂️', + ':man_construction_worker_medium_light_skin_tone:': '👷🏼‍♂️', + ':man_construction_worker_tone2:': '👷🏼‍♂️', + ':man_construction_worker_medium_skin_tone:': '👷🏽‍♂️', + ':man_construction_worker_tone3:': '👷🏽‍♂️', + ':man_construction_worker_medium_dark_skin_tone:': '👷🏾‍♂️', + ':man_construction_worker_tone4:': '👷🏾‍♂️', + ':man_construction_worker_dark_skin_tone:': '👷🏿‍♂️', + ':man_construction_worker_tone5:': '👷🏿‍♂️', + ':man_detective_light_skin_tone:': '🕵️🏻‍♂️', + ':man_detective_tone1:': '🕵️🏻‍♂️', + ':man_detective_medium_light_skin_tone:': '🕵️🏼‍♂️', + ':man_detective_tone2:': '🕵️🏼‍♂️', + ':man_detective_medium_skin_tone:': '🕵️🏽‍♂️', + ':man_detective_tone3:': '🕵️🏽‍♂️', + ':man_detective_medium_dark_skin_tone:': '🕵️🏾‍♂️', + ':man_detective_tone4:': '🕵️🏾‍♂️', + ':man_detective_dark_skin_tone:': '🕵️🏿‍♂️', + ':man_detective_tone5:': '🕵️🏿‍♂️', + ':man_elf_light_skin_tone:': '🧝🏻‍♂️', + ':man_elf_tone1:': '🧝🏻‍♂️', + ':man_elf_medium_light_skin_tone:': '🧝🏼‍♂️', + ':man_elf_tone2:': '🧝🏼‍♂️', + ':man_elf_medium_skin_tone:': '🧝🏽‍♂️', + ':man_elf_tone3:': '🧝🏽‍♂️', + ':man_elf_medium_dark_skin_tone:': '🧝🏾‍♂️', + ':man_elf_tone4:': '🧝🏾‍♂️', + ':man_elf_dark_skin_tone:': '🧝🏿‍♂️', + ':man_elf_tone5:': '🧝🏿‍♂️', + ':man_facepalming_light_skin_tone:': '🤦🏻‍♂️', + ':man_facepalming_tone1:': '🤦🏻‍♂️', + ':man_facepalming_medium_light_skin_tone:': '🤦🏼‍♂️', + ':man_facepalming_tone2:': '🤦🏼‍♂️', + ':man_facepalming_medium_skin_tone:': '🤦🏽‍♂️', + ':man_facepalming_tone3:': '🤦🏽‍♂️', + ':man_facepalming_medium_dark_skin_tone:': '🤦🏾‍♂️', + ':man_facepalming_tone4:': '🤦🏾‍♂️', + ':man_facepalming_dark_skin_tone:': '🤦🏿‍♂️', + ':man_facepalming_tone5:': '🤦🏿‍♂️', + ':man_fairy_light_skin_tone:': '🧚🏻‍♂️', + ':man_fairy_tone1:': '🧚🏻‍♂️', + ':man_fairy_medium_light_skin_tone:': '🧚🏼‍♂️', + ':man_fairy_tone2:': '🧚🏼‍♂️', + ':man_fairy_medium_skin_tone:': '🧚🏽‍♂️', + ':man_fairy_tone3:': '🧚🏽‍♂️', + ':man_fairy_medium_dark_skin_tone:': '🧚🏾‍♂️', + ':man_fairy_tone4:': '🧚🏾‍♂️', + ':man_fairy_dark_skin_tone:': '🧚🏿‍♂️', + ':man_fairy_tone5:': '🧚🏿‍♂️', + ':man_frowning_light_skin_tone:': '🙍🏻‍♂️', + ':man_frowning_tone1:': '🙍🏻‍♂️', + ':man_frowning_medium_light_skin_tone:': '🙍🏼‍♂️', + ':man_frowning_tone2:': '🙍🏼‍♂️', + ':man_frowning_medium_skin_tone:': '🙍🏽‍♂️', + ':man_frowning_tone3:': '🙍🏽‍♂️', + ':man_frowning_medium_dark_skin_tone:': '🙍🏾‍♂️', + ':man_frowning_tone4:': '🙍🏾‍♂️', + ':man_frowning_dark_skin_tone:': '🙍🏿‍♂️', + ':man_frowning_tone5:': '🙍🏿‍♂️', + ':man_gesturing_no_light_skin_tone:': '🙅🏻‍♂️', + ':man_gesturing_no_tone1:': '🙅🏻‍♂️', + ':man_gesturing_no_medium_light_skin_tone:': '🙅🏼‍♂️', + ':man_gesturing_no_tone2:': '🙅🏼‍♂️', + ':man_gesturing_no_medium_skin_tone:': '🙅🏽‍♂️', + ':man_gesturing_no_tone3:': '🙅🏽‍♂️', + ':man_gesturing_no_medium_dark_skin_tone:': '🙅🏾‍♂️', + ':man_gesturing_no_tone4:': '🙅🏾‍♂️', + ':man_gesturing_no_dark_skin_tone:': '🙅🏿‍♂️', + ':man_gesturing_no_tone5:': '🙅🏿‍♂️', + ':man_gesturing_ok_light_skin_tone:': '🙆🏻‍♂️', + ':man_gesturing_ok_tone1:': '🙆🏻‍♂️', + ':man_gesturing_ok_medium_light_skin_tone:': '🙆🏼‍♂️', + ':man_gesturing_ok_tone2:': '🙆🏼‍♂️', + ':man_gesturing_ok_medium_skin_tone:': '🙆🏽‍♂️', + ':man_gesturing_ok_tone3:': '🙆🏽‍♂️', + ':man_gesturing_ok_medium_dark_skin_tone:': '🙆🏾‍♂️', + ':man_gesturing_ok_tone4:': '🙆🏾‍♂️', + ':man_gesturing_ok_dark_skin_tone:': '🙆🏿‍♂️', + ':man_gesturing_ok_tone5:': '🙆🏿‍♂️', + ':man_getting_face_massage_light_skin_tone:': '💆🏻‍♂️', + ':man_getting_face_massage_tone1:': '💆🏻‍♂️', + ':man_getting_face_massage_medium_light_skin_tone:': '💆🏼‍♂️', + ':man_getting_face_massage_tone2:': '💆🏼‍♂️', + ':man_getting_face_massage_medium_skin_tone:': '💆🏽‍♂️', + ':man_getting_face_massage_tone3:': '💆🏽‍♂️', + ':man_getting_face_massage_medium_dark_skin_tone:': '💆🏾‍♂️', + ':man_getting_face_massage_tone4:': '💆🏾‍♂️', + ':man_getting_face_massage_dark_skin_tone:': '💆🏿‍♂️', + ':man_getting_face_massage_tone5:': '💆🏿‍♂️', + ':man_getting_haircut_light_skin_tone:': '💇🏻‍♂️', + ':man_getting_haircut_tone1:': '💇🏻‍♂️', + ':man_getting_haircut_medium_light_skin_tone:': '💇🏼‍♂️', + ':man_getting_haircut_tone2:': '💇🏼‍♂️', + ':man_getting_haircut_medium_skin_tone:': '💇🏽‍♂️', + ':man_getting_haircut_tone3:': '💇🏽‍♂️', + ':man_getting_haircut_medium_dark_skin_tone:': '💇🏾‍♂️', + ':man_getting_haircut_tone4:': '💇🏾‍♂️', + ':man_getting_haircut_dark_skin_tone:': '💇🏿‍♂️', + ':man_getting_haircut_tone5:': '💇🏿‍♂️', + ':man_golfing_light_skin_tone:': '🏌️🏻‍♂️', + ':man_golfing_tone1:': '🏌️🏻‍♂️', + ':man_golfing_medium_light_skin_tone:': '🏌️🏼‍♂️', + ':man_golfing_tone2:': '🏌️🏼‍♂️', + ':man_golfing_medium_skin_tone:': '🏌️🏽‍♂️', + ':man_golfing_tone3:': '🏌️🏽‍♂️', + ':man_golfing_medium_dark_skin_tone:': '🏌️🏾‍♂️', + ':man_golfing_tone4:': '🏌️🏾‍♂️', + ':man_golfing_dark_skin_tone:': '🏌️🏿‍♂️', + ':man_golfing_tone5:': '🏌️🏿‍♂️', + ':man_guard_light_skin_tone:': '💂🏻‍♂️', + ':man_guard_tone1:': '💂🏻‍♂️', + ':man_guard_medium_light_skin_tone:': '💂🏼‍♂️', + ':man_guard_tone2:': '💂🏼‍♂️', + ':man_guard_medium_skin_tone:': '💂🏽‍♂️', + ':man_guard_tone3:': '💂🏽‍♂️', + ':man_guard_medium_dark_skin_tone:': '💂🏾‍♂️', + ':man_guard_tone4:': '💂🏾‍♂️', + ':man_guard_dark_skin_tone:': '💂🏿‍♂️', + ':man_guard_tone5:': '💂🏿‍♂️', + ':man_health_worker_light_skin_tone:': '👨🏻‍⚕️', + ':man_health_worker_tone1:': '👨🏻‍⚕️', + ':man_health_worker_medium_light_skin_tone:': '👨🏼‍⚕️', + ':man_health_worker_tone2:': '👨🏼‍⚕️', + ':man_health_worker_medium_skin_tone:': '👨🏽‍⚕️', + ':man_health_worker_tone3:': '👨🏽‍⚕️', + ':man_health_worker_medium_dark_skin_tone:': '👨🏾‍⚕️', + ':man_health_worker_tone4:': '👨🏾‍⚕️', + ':man_health_worker_dark_skin_tone:': '👨🏿‍⚕️', + ':man_health_worker_tone5:': '👨🏿‍⚕️', + ':man_in_lotus_position_light_skin_tone:': '🧘🏻‍♂️', + ':man_in_lotus_position_tone1:': '🧘🏻‍♂️', + ':man_in_lotus_position_medium_light_skin_tone:': '🧘🏼‍♂️', + ':man_in_lotus_position_tone2:': '🧘🏼‍♂️', + ':man_in_lotus_position_medium_skin_tone:': '🧘🏽‍♂️', + ':man_in_lotus_position_tone3:': '🧘🏽‍♂️', + ':man_in_lotus_position_medium_dark_skin_tone:': '🧘🏾‍♂️', + ':man_in_lotus_position_tone4:': '🧘🏾‍♂️', + ':man_in_lotus_position_dark_skin_tone:': '🧘🏿‍♂️', + ':man_in_lotus_position_tone5:': '🧘🏿‍♂️', + ':man_in_steamy_room_light_skin_tone:': '🧖🏻‍♂️', + ':man_in_steamy_room_tone1:': '🧖🏻‍♂️', + ':man_in_steamy_room_medium_light_skin_tone:': '🧖🏼‍♂️', + ':man_in_steamy_room_tone2:': '🧖🏼‍♂️', + ':man_in_steamy_room_medium_skin_tone:': '🧖🏽‍♂️', + ':man_in_steamy_room_tone3:': '🧖🏽‍♂️', + ':man_in_steamy_room_medium_dark_skin_tone:': '🧖🏾‍♂️', + ':man_in_steamy_room_tone4:': '🧖🏾‍♂️', + ':man_in_steamy_room_dark_skin_tone:': '🧖🏿‍♂️', + ':man_in_steamy_room_tone5:': '🧖🏿‍♂️', + ':man_judge_light_skin_tone:': '👨🏻‍⚖️', + ':man_judge_tone1:': '👨🏻‍⚖️', + ':man_judge_medium_light_skin_tone:': '👨🏼‍⚖️', + ':man_judge_tone2:': '👨🏼‍⚖️', + ':man_judge_medium_skin_tone:': '👨🏽‍⚖️', + ':man_judge_tone3:': '👨🏽‍⚖️', + ':man_judge_medium_dark_skin_tone:': '👨🏾‍⚖️', + ':man_judge_tone4:': '👨🏾‍⚖️', + ':man_judge_dark_skin_tone:': '👨🏿‍⚖️', + ':man_judge_tone5:': '👨🏿‍⚖️', + ':man_juggling_light_skin_tone:': '🤹🏻‍♂️', + ':man_juggling_tone1:': '🤹🏻‍♂️', + ':man_juggling_medium_light_skin_tone:': '🤹🏼‍♂️', + ':man_juggling_tone2:': '🤹🏼‍♂️', + ':man_juggling_medium_skin_tone:': '🤹🏽‍♂️', + ':man_juggling_tone3:': '🤹🏽‍♂️', + ':man_juggling_medium_dark_skin_tone:': '🤹🏾‍♂️', + ':man_juggling_tone4:': '🤹🏾‍♂️', + ':man_juggling_dark_skin_tone:': '🤹🏿‍♂️', + ':man_juggling_tone5:': '🤹🏿‍♂️', + ':man_kneeling_light_skin_tone:': '🧎🏻‍♂️', + ':man_kneeling_tone1:': '🧎🏻‍♂️', + ':man_kneeling_medium_light_skin_tone:': '🧎🏼‍♂️', + ':man_kneeling_tone2:': '🧎🏼‍♂️', + ':man_kneeling_medium_skin_tone:': '🧎🏽‍♂️', + ':man_kneeling_tone3:': '🧎🏽‍♂️', + ':man_kneeling_medium_dark_skin_tone:': '🧎🏾‍♂️', + ':man_kneeling_tone4:': '🧎🏾‍♂️', + ':man_kneeling_dark_skin_tone:': '🧎🏿‍♂️', + ':man_kneeling_tone5:': '🧎🏿‍♂️', + ':man_lifting_weights_light_skin_tone:': '🏋️🏻‍♂️', + ':man_lifting_weights_tone1:': '🏋️🏻‍♂️', + ':man_lifting_weights_medium_light_skin_tone:': '🏋️🏼‍♂️', + ':man_lifting_weights_tone2:': '🏋️🏼‍♂️', + ':man_lifting_weights_medium_skin_tone:': '🏋️🏽‍♂️', + ':man_lifting_weights_tone3:': '🏋️🏽‍♂️', + ':man_lifting_weights_medium_dark_skin_tone:': '🏋️🏾‍♂️', + ':man_lifting_weights_tone4:': '🏋️🏾‍♂️', + ':man_lifting_weights_dark_skin_tone:': '🏋️🏿‍♂️', + ':man_lifting_weights_tone5:': '🏋️🏿‍♂️', + ':man_mage_light_skin_tone:': '🧙🏻‍♂️', + ':man_mage_tone1:': '🧙🏻‍♂️', + ':man_mage_medium_light_skin_tone:': '🧙🏼‍♂️', + ':man_mage_tone2:': '🧙🏼‍♂️', + ':man_mage_medium_skin_tone:': '🧙🏽‍♂️', + ':man_mage_tone3:': '🧙🏽‍♂️', + ':man_mage_medium_dark_skin_tone:': '🧙🏾‍♂️', + ':man_mage_tone4:': '🧙🏾‍♂️', + ':man_mage_dark_skin_tone:': '🧙🏿‍♂️', + ':man_mage_tone5:': '🧙🏿‍♂️', + ':man_mountain_biking_light_skin_tone:': '🚵🏻‍♂️', + ':man_mountain_biking_tone1:': '🚵🏻‍♂️', + ':man_mountain_biking_medium_light_skin_tone:': '🚵🏼‍♂️', + ':man_mountain_biking_tone2:': '🚵🏼‍♂️', + ':man_mountain_biking_medium_skin_tone:': '🚵🏽‍♂️', + ':man_mountain_biking_tone3:': '🚵🏽‍♂️', + ':man_mountain_biking_medium_dark_skin_tone:': '🚵🏾‍♂️', + ':man_mountain_biking_tone4:': '🚵🏾‍♂️', + ':man_mountain_biking_dark_skin_tone:': '🚵🏿‍♂️', + ':man_mountain_biking_tone5:': '🚵🏿‍♂️', + ':man_pilot_light_skin_tone:': '👨🏻‍✈️', + ':man_pilot_tone1:': '👨🏻‍✈️', + ':man_pilot_medium_light_skin_tone:': '👨🏼‍✈️', + ':man_pilot_tone2:': '👨🏼‍✈️', + ':man_pilot_medium_skin_tone:': '👨🏽‍✈️', + ':man_pilot_tone3:': '👨🏽‍✈️', + ':man_pilot_medium_dark_skin_tone:': '👨🏾‍✈️', + ':man_pilot_tone4:': '👨🏾‍✈️', + ':man_pilot_dark_skin_tone:': '👨🏿‍✈️', + ':man_pilot_tone5:': '👨🏿‍✈️', + ':man_playing_handball_light_skin_tone:': '🤾🏻‍♂️', + ':man_playing_handball_tone1:': '🤾🏻‍♂️', + ':man_playing_handball_medium_light_skin_tone:': '🤾🏼‍♂️', + ':man_playing_handball_tone2:': '🤾🏼‍♂️', + ':man_playing_handball_medium_skin_tone:': '🤾🏽‍♂️', + ':man_playing_handball_tone3:': '🤾🏽‍♂️', + ':man_playing_handball_medium_dark_skin_tone:': '🤾🏾‍♂️', + ':man_playing_handball_tone4:': '🤾🏾‍♂️', + ':man_playing_handball_dark_skin_tone:': '🤾🏿‍♂️', + ':man_playing_handball_tone5:': '🤾🏿‍♂️', + ':man_playing_water_polo_light_skin_tone:': '🤽🏻‍♂️', + ':man_playing_water_polo_tone1:': '🤽🏻‍♂️', + ':man_playing_water_polo_medium_light_skin_tone:': '🤽🏼‍♂️', + ':man_playing_water_polo_tone2:': '🤽🏼‍♂️', + ':man_playing_water_polo_medium_skin_tone:': '🤽🏽‍♂️', + ':man_playing_water_polo_tone3:': '🤽🏽‍♂️', + ':man_playing_water_polo_medium_dark_skin_tone:': '🤽🏾‍♂️', + ':man_playing_water_polo_tone4:': '🤽🏾‍♂️', + ':man_playing_water_polo_dark_skin_tone:': '🤽🏿‍♂️', + ':man_playing_water_polo_tone5:': '🤽🏿‍♂️', + ':man_police_officer_light_skin_tone:': '👮🏻‍♂️', + ':man_police_officer_tone1:': '👮🏻‍♂️', + ':man_police_officer_medium_light_skin_tone:': '👮🏼‍♂️', + ':man_police_officer_tone2:': '👮🏼‍♂️', + ':man_police_officer_medium_skin_tone:': '👮🏽‍♂️', + ':man_police_officer_tone3:': '👮🏽‍♂️', + ':man_police_officer_medium_dark_skin_tone:': '👮🏾‍♂️', + ':man_police_officer_tone4:': '👮🏾‍♂️', + ':man_police_officer_dark_skin_tone:': '👮🏿‍♂️', + ':man_police_officer_tone5:': '👮🏿‍♂️', + ':man_pouting_light_skin_tone:': '🙎🏻‍♂️', + ':man_pouting_tone1:': '🙎🏻‍♂️', + ':man_pouting_medium_light_skin_tone:': '🙎🏼‍♂️', + ':man_pouting_tone2:': '🙎🏼‍♂️', + ':man_pouting_medium_skin_tone:': '🙎🏽‍♂️', + ':man_pouting_tone3:': '🙎🏽‍♂️', + ':man_pouting_medium_dark_skin_tone:': '🙎🏾‍♂️', + ':man_pouting_tone4:': '🙎🏾‍♂️', + ':man_pouting_dark_skin_tone:': '🙎🏿‍♂️', + ':man_pouting_tone5:': '🙎🏿‍♂️', + ':man_raising_hand_light_skin_tone:': '🙋🏻‍♂️', + ':man_raising_hand_tone1:': '🙋🏻‍♂️', + ':man_raising_hand_medium_light_skin_tone:': '🙋🏼‍♂️', + ':man_raising_hand_tone2:': '🙋🏼‍♂️', + ':man_raising_hand_medium_skin_tone:': '🙋🏽‍♂️', + ':man_raising_hand_tone3:': '🙋🏽‍♂️', + ':man_raising_hand_medium_dark_skin_tone:': '🙋🏾‍♂️', + ':man_raising_hand_tone4:': '🙋🏾‍♂️', + ':man_raising_hand_dark_skin_tone:': '🙋🏿‍♂️', + ':man_raising_hand_tone5:': '🙋🏿‍♂️', + ':man_rowing_boat_light_skin_tone:': '🚣🏻‍♂️', + ':man_rowing_boat_tone1:': '🚣🏻‍♂️', + ':man_rowing_boat_medium_light_skin_tone:': '🚣🏼‍♂️', + ':man_rowing_boat_tone2:': '🚣🏼‍♂️', + ':man_rowing_boat_medium_skin_tone:': '🚣🏽‍♂️', + ':man_rowing_boat_tone3:': '🚣🏽‍♂️', + ':man_rowing_boat_medium_dark_skin_tone:': '🚣🏾‍♂️', + ':man_rowing_boat_tone4:': '🚣🏾‍♂️', + ':man_rowing_boat_dark_skin_tone:': '🚣🏿‍♂️', + ':man_rowing_boat_tone5:': '🚣🏿‍♂️', + ':man_running_light_skin_tone:': '🏃🏻‍♂️', + ':man_running_tone1:': '🏃🏻‍♂️', + ':man_running_medium_light_skin_tone:': '🏃🏼‍♂️', + ':man_running_tone2:': '🏃🏼‍♂️', + ':man_running_medium_skin_tone:': '🏃🏽‍♂️', + ':man_running_tone3:': '🏃🏽‍♂️', + ':man_running_medium_dark_skin_tone:': '🏃🏾‍♂️', + ':man_running_tone4:': '🏃🏾‍♂️', + ':man_running_dark_skin_tone:': '🏃🏿‍♂️', + ':man_running_tone5:': '🏃🏿‍♂️', + ':man_shrugging_light_skin_tone:': '🤷🏻‍♂️', + ':man_shrugging_tone1:': '🤷🏻‍♂️', + ':man_shrugging_medium_light_skin_tone:': '🤷🏼‍♂️', + ':man_shrugging_tone2:': '🤷🏼‍♂️', + ':man_shrugging_medium_skin_tone:': '🤷🏽‍♂️', + ':man_shrugging_tone3:': '🤷🏽‍♂️', + ':man_shrugging_medium_dark_skin_tone:': '🤷🏾‍♂️', + ':man_shrugging_tone4:': '🤷🏾‍♂️', + ':man_shrugging_dark_skin_tone:': '🤷🏿‍♂️', + ':man_shrugging_tone5:': '🤷🏿‍♂️', + ':man_standing_light_skin_tone:': '🧍🏻‍♂️', + ':man_standing_tone1:': '🧍🏻‍♂️', + ':man_standing_medium_light_skin_tone:': '🧍🏼‍♂️', + ':man_standing_tone2:': '🧍🏼‍♂️', + ':man_standing_medium_skin_tone:': '🧍🏽‍♂️', + ':man_standing_tone3:': '🧍🏽‍♂️', + ':man_standing_medium_dark_skin_tone:': '🧍🏾‍♂️', + ':man_standing_tone4:': '🧍🏾‍♂️', + ':man_standing_dark_skin_tone:': '🧍🏿‍♂️', + ':man_standing_tone5:': '🧍🏿‍♂️', + ':man_superhero_light_skin_tone:': '🦸🏻‍♂️', + ':man_superhero_tone1:': '🦸🏻‍♂️', + ':man_superhero_medium_light_skin_tone:': '🦸🏼‍♂️', + ':man_superhero_tone2:': '🦸🏼‍♂️', + ':man_superhero_medium_skin_tone:': '🦸🏽‍♂️', + ':man_superhero_tone3:': '🦸🏽‍♂️', + ':man_superhero_medium_dark_skin_tone:': '🦸🏾‍♂️', + ':man_superhero_tone4:': '🦸🏾‍♂️', + ':man_superhero_dark_skin_tone:': '🦸🏿‍♂️', + ':man_superhero_tone5:': '🦸🏿‍♂️', + ':man_supervillain_light_skin_tone:': '🦹🏻‍♂️', + ':man_supervillain_tone1:': '🦹🏻‍♂️', + ':man_supervillain_medium_light_skin_tone:': '🦹🏼‍♂️', + ':man_supervillain_tone2:': '🦹🏼‍♂️', + ':man_supervillain_medium_skin_tone:': '🦹🏽‍♂️', + ':man_supervillain_tone3:': '🦹🏽‍♂️', + ':man_supervillain_medium_dark_skin_tone:': '🦹🏾‍♂️', + ':man_supervillain_tone4:': '🦹🏾‍♂️', + ':man_supervillain_dark_skin_tone:': '🦹🏿‍♂️', + ':man_supervillain_tone5:': '🦹🏿‍♂️', + ':man_surfing_light_skin_tone:': '🏄🏻‍♂️', + ':man_surfing_tone1:': '🏄🏻‍♂️', + ':man_surfing_medium_light_skin_tone:': '🏄🏼‍♂️', + ':man_surfing_tone2:': '🏄🏼‍♂️', + ':man_surfing_medium_skin_tone:': '🏄🏽‍♂️', + ':man_surfing_tone3:': '🏄🏽‍♂️', + ':man_surfing_medium_dark_skin_tone:': '🏄🏾‍♂️', + ':man_surfing_tone4:': '🏄🏾‍♂️', + ':man_surfing_dark_skin_tone:': '🏄🏿‍♂️', + ':man_surfing_tone5:': '🏄🏿‍♂️', + ':man_swimming_light_skin_tone:': '🏊🏻‍♂️', + ':man_swimming_tone1:': '🏊🏻‍♂️', + ':man_swimming_medium_light_skin_tone:': '🏊🏼‍♂️', + ':man_swimming_tone2:': '🏊🏼‍♂️', + ':man_swimming_medium_skin_tone:': '🏊🏽‍♂️', + ':man_swimming_tone3:': '🏊🏽‍♂️', + ':man_swimming_medium_dark_skin_tone:': '🏊🏾‍♂️', + ':man_swimming_tone4:': '🏊🏾‍♂️', + ':man_swimming_dark_skin_tone:': '🏊🏿‍♂️', + ':man_swimming_tone5:': '🏊🏿‍♂️', + ':man_tipping_hand_light_skin_tone:': '💁🏻‍♂️', + ':man_tipping_hand_tone1:': '💁🏻‍♂️', + ':man_tipping_hand_medium_light_skin_tone:': '💁🏼‍♂️', + ':man_tipping_hand_tone2:': '💁🏼‍♂️', + ':man_tipping_hand_medium_skin_tone:': '💁🏽‍♂️', + ':man_tipping_hand_tone3:': '💁🏽‍♂️', + ':man_tipping_hand_medium_dark_skin_tone:': '💁🏾‍♂️', + ':man_tipping_hand_tone4:': '💁🏾‍♂️', + ':man_tipping_hand_dark_skin_tone:': '💁🏿‍♂️', + ':man_tipping_hand_tone5:': '💁🏿‍♂️', + ':man_vampire_light_skin_tone:': '🧛🏻‍♂️', + ':man_vampire_tone1:': '🧛🏻‍♂️', + ':man_vampire_medium_light_skin_tone:': '🧛🏼‍♂️', + ':man_vampire_tone2:': '🧛🏼‍♂️', + ':man_vampire_medium_skin_tone:': '🧛🏽‍♂️', + ':man_vampire_tone3:': '🧛🏽‍♂️', + ':man_vampire_medium_dark_skin_tone:': '🧛🏾‍♂️', + ':man_vampire_tone4:': '🧛🏾‍♂️', + ':man_vampire_dark_skin_tone:': '🧛🏿‍♂️', + ':man_vampire_tone5:': '🧛🏿‍♂️', + ':man_walking_light_skin_tone:': '🚶🏻‍♂️', + ':man_walking_tone1:': '🚶🏻‍♂️', + ':man_walking_medium_light_skin_tone:': '🚶🏼‍♂️', + ':man_walking_tone2:': '🚶🏼‍♂️', + ':man_walking_medium_skin_tone:': '🚶🏽‍♂️', + ':man_walking_tone3:': '🚶🏽‍♂️', + ':man_walking_medium_dark_skin_tone:': '🚶🏾‍♂️', + ':man_walking_tone4:': '🚶🏾‍♂️', + ':man_walking_dark_skin_tone:': '🚶🏿‍♂️', + ':man_walking_tone5:': '🚶🏿‍♂️', + ':man_wearing_turban_light_skin_tone:': '👳🏻‍♂️', + ':man_wearing_turban_tone1:': '👳🏻‍♂️', + ':man_wearing_turban_medium_light_skin_tone:': '👳🏼‍♂️', + ':man_wearing_turban_tone2:': '👳🏼‍♂️', + ':man_wearing_turban_medium_skin_tone:': '👳🏽‍♂️', + ':man_wearing_turban_tone3:': '👳🏽‍♂️', + ':man_wearing_turban_medium_dark_skin_tone:': '👳🏾‍♂️', + ':man_wearing_turban_tone4:': '👳🏾‍♂️', + ':man_wearing_turban_dark_skin_tone:': '👳🏿‍♂️', + ':man_wearing_turban_tone5:': '👳🏿‍♂️', + ':mermaid_light_skin_tone:': '🧜🏻‍♀️', + ':mermaid_tone1:': '🧜🏻‍♀️', + ':mermaid_medium_light_skin_tone:': '🧜🏼‍♀️', + ':mermaid_tone2:': '🧜🏼‍♀️', + ':mermaid_medium_skin_tone:': '🧜🏽‍♀️', + ':mermaid_tone3:': '🧜🏽‍♀️', + ':mermaid_medium_dark_skin_tone:': '🧜🏾‍♀️', + ':mermaid_tone4:': '🧜🏾‍♀️', + ':mermaid_dark_skin_tone:': '🧜🏿‍♀️', + ':mermaid_tone5:': '🧜🏿‍♀️', + ':merman_light_skin_tone:': '🧜🏻‍♂️', + ':merman_tone1:': '🧜🏻‍♂️', + ':merman_medium_light_skin_tone:': '🧜🏼‍♂️', + ':merman_tone2:': '🧜🏼‍♂️', + ':merman_medium_skin_tone:': '🧜🏽‍♂️', + ':merman_tone3:': '🧜🏽‍♂️', + ':merman_medium_dark_skin_tone:': '🧜🏾‍♂️', + ':merman_tone4:': '🧜🏾‍♂️', + ':merman_dark_skin_tone:': '🧜🏿‍♂️', + ':merman_tone5:': '🧜🏿‍♂️', + ':woman_biking_light_skin_tone:': '🚴🏻‍♀️', + ':woman_biking_tone1:': '🚴🏻‍♀️', + ':woman_biking_medium_light_skin_tone:': '🚴🏼‍♀️', + ':woman_biking_tone2:': '🚴🏼‍♀️', + ':woman_biking_medium_skin_tone:': '🚴🏽‍♀️', + ':woman_biking_tone3:': '🚴🏽‍♀️', + ':woman_biking_medium_dark_skin_tone:': '🚴🏾‍♀️', + ':woman_biking_tone4:': '🚴🏾‍♀️', + ':woman_biking_dark_skin_tone:': '🚴🏿‍♀️', + ':woman_biking_tone5:': '🚴🏿‍♀️', + ':woman_bowing_light_skin_tone:': '🙇🏻‍♀️', + ':woman_bowing_tone1:': '🙇🏻‍♀️', + ':woman_bowing_medium_light_skin_tone:': '🙇🏼‍♀️', + ':woman_bowing_tone2:': '🙇🏼‍♀️', + ':woman_bowing_medium_skin_tone:': '🙇🏽‍♀️', + ':woman_bowing_tone3:': '🙇🏽‍♀️', + ':woman_bowing_medium_dark_skin_tone:': '🙇🏾‍♀️', + ':woman_bowing_tone4:': '🙇🏾‍♀️', + ':woman_bowing_dark_skin_tone:': '🙇🏿‍♀️', + ':woman_bowing_tone5:': '🙇🏿‍♀️', + ':woman_cartwheeling_light_skin_tone:': '🤸🏻‍♀️', + ':woman_cartwheeling_tone1:': '🤸🏻‍♀️', + ':woman_cartwheeling_medium_light_skin_tone:': '🤸🏼‍♀️', + ':woman_cartwheeling_tone2:': '🤸🏼‍♀️', + ':woman_cartwheeling_medium_skin_tone:': '🤸🏽‍♀️', + ':woman_cartwheeling_tone3:': '🤸🏽‍♀️', + ':woman_cartwheeling_medium_dark_skin_tone:': '🤸🏾‍♀️', + ':woman_cartwheeling_tone4:': '🤸🏾‍♀️', + ':woman_cartwheeling_dark_skin_tone:': '🤸🏿‍♀️', + ':woman_cartwheeling_tone5:': '🤸🏿‍♀️', + ':woman_climbing_light_skin_tone:': '🧗🏻‍♀️', + ':woman_climbing_tone1:': '🧗🏻‍♀️', + ':woman_climbing_medium_light_skin_tone:': '🧗🏼‍♀️', + ':woman_climbing_tone2:': '🧗🏼‍♀️', + ':woman_climbing_medium_skin_tone:': '🧗🏽‍♀️', + ':woman_climbing_tone3:': '🧗🏽‍♀️', + ':woman_climbing_medium_dark_skin_tone:': '🧗🏾‍♀️', + ':woman_climbing_tone4:': '🧗🏾‍♀️', + ':woman_climbing_dark_skin_tone:': '🧗🏿‍♀️', + ':woman_climbing_tone5:': '🧗🏿‍♀️', + ':woman_construction_worker_light_skin_tone:': '👷🏻‍♀️', + ':woman_construction_worker_tone1:': '👷🏻‍♀️', + ':woman_construction_worker_medium_light_skin_tone:': '👷🏼‍♀️', + ':woman_construction_worker_tone2:': '👷🏼‍♀️', + ':woman_construction_worker_medium_skin_tone:': '👷🏽‍♀️', + ':woman_construction_worker_tone3:': '👷🏽‍♀️', + ':woman_construction_worker_medium_dark_skin_tone:': '👷🏾‍♀️', + ':woman_construction_worker_tone4:': '👷🏾‍♀️', + ':woman_construction_worker_dark_skin_tone:': '👷🏿‍♀️', + ':woman_construction_worker_tone5:': '👷🏿‍♀️', + ':woman_detective_light_skin_tone:': '🕵️🏻‍♀️', + ':woman_detective_tone1:': '🕵️🏻‍♀️', + ':woman_detective_medium_light_skin_tone:': '🕵️🏼‍♀️', + ':woman_detective_tone2:': '🕵️🏼‍♀️', + ':woman_detective_medium_skin_tone:': '🕵️🏽‍♀️', + ':woman_detective_tone3:': '🕵️🏽‍♀️', + ':woman_detective_medium_dark_skin_tone:': '🕵️🏾‍♀️', + ':woman_detective_tone4:': '🕵️🏾‍♀️', + ':woman_detective_dark_skin_tone:': '🕵️🏿‍♀️', + ':woman_detective_tone5:': '🕵️🏿‍♀️', + ':woman_elf_light_skin_tone:': '🧝🏻‍♀️', + ':woman_elf_tone1:': '🧝🏻‍♀️', + ':woman_elf_medium_light_skin_tone:': '🧝🏼‍♀️', + ':woman_elf_tone2:': '🧝🏼‍♀️', + ':woman_elf_medium_skin_tone:': '🧝🏽‍♀️', + ':woman_elf_tone3:': '🧝🏽‍♀️', + ':woman_elf_medium_dark_skin_tone:': '🧝🏾‍♀️', + ':woman_elf_tone4:': '🧝🏾‍♀️', + ':woman_elf_dark_skin_tone:': '🧝🏿‍♀️', + ':woman_elf_tone5:': '🧝🏿‍♀️', + ':woman_facepalming_light_skin_tone:': '🤦🏻‍♀️', + ':woman_facepalming_tone1:': '🤦🏻‍♀️', + ':woman_facepalming_medium_light_skin_tone:': '🤦🏼‍♀️', + ':woman_facepalming_tone2:': '🤦🏼‍♀️', + ':woman_facepalming_medium_skin_tone:': '🤦🏽‍♀️', + ':woman_facepalming_tone3:': '🤦🏽‍♀️', + ':woman_facepalming_medium_dark_skin_tone:': '🤦🏾‍♀️', + ':woman_facepalming_tone4:': '🤦🏾‍♀️', + ':woman_facepalming_dark_skin_tone:': '🤦🏿‍♀️', + ':woman_facepalming_tone5:': '🤦🏿‍♀️', + ':woman_fairy_light_skin_tone:': '🧚🏻‍♀️', + ':woman_fairy_tone1:': '🧚🏻‍♀️', + ':woman_fairy_medium_light_skin_tone:': '🧚🏼‍♀️', + ':woman_fairy_tone2:': '🧚🏼‍♀️', + ':woman_fairy_medium_skin_tone:': '🧚🏽‍♀️', + ':woman_fairy_tone3:': '🧚🏽‍♀️', + ':woman_fairy_medium_dark_skin_tone:': '🧚🏾‍♀️', + ':woman_fairy_tone4:': '🧚🏾‍♀️', + ':woman_fairy_dark_skin_tone:': '🧚🏿‍♀️', + ':woman_fairy_tone5:': '🧚🏿‍♀️', + ':woman_frowning_light_skin_tone:': '🙍🏻‍♀️', + ':woman_frowning_tone1:': '🙍🏻‍♀️', + ':woman_frowning_medium_light_skin_tone:': '🙍🏼‍♀️', + ':woman_frowning_tone2:': '🙍🏼‍♀️', + ':woman_frowning_medium_skin_tone:': '🙍🏽‍♀️', + ':woman_frowning_tone3:': '🙍🏽‍♀️', + ':woman_frowning_medium_dark_skin_tone:': '🙍🏾‍♀️', + ':woman_frowning_tone4:': '🙍🏾‍♀️', + ':woman_frowning_dark_skin_tone:': '🙍🏿‍♀️', + ':woman_frowning_tone5:': '🙍🏿‍♀️', + ':woman_gesturing_no_light_skin_tone:': '🙅🏻‍♀️', + ':woman_gesturing_no_tone1:': '🙅🏻‍♀️', + ':woman_gesturing_no_medium_light_skin_tone:': '🙅🏼‍♀️', + ':woman_gesturing_no_tone2:': '🙅🏼‍♀️', + ':woman_gesturing_no_medium_skin_tone:': '🙅🏽‍♀️', + ':woman_gesturing_no_tone3:': '🙅🏽‍♀️', + ':woman_gesturing_no_medium_dark_skin_tone:': '🙅🏾‍♀️', + ':woman_gesturing_no_tone4:': '🙅🏾‍♀️', + ':woman_gesturing_no_dark_skin_tone:': '🙅🏿‍♀️', + ':woman_gesturing_no_tone5:': '🙅🏿‍♀️', + ':woman_gesturing_ok_light_skin_tone:': '🙆🏻‍♀️', + ':woman_gesturing_ok_tone1:': '🙆🏻‍♀️', + ':woman_gesturing_ok_medium_light_skin_tone:': '🙆🏼‍♀️', + ':woman_gesturing_ok_tone2:': '🙆🏼‍♀️', + ':woman_gesturing_ok_medium_skin_tone:': '🙆🏽‍♀️', + ':woman_gesturing_ok_tone3:': '🙆🏽‍♀️', + ':woman_gesturing_ok_medium_dark_skin_tone:': '🙆🏾‍♀️', + ':woman_gesturing_ok_tone4:': '🙆🏾‍♀️', + ':woman_gesturing_ok_dark_skin_tone:': '🙆🏿‍♀️', + ':woman_gesturing_ok_tone5:': '🙆🏿‍♀️', + ':woman_getting_face_massage_light_skin_tone:': '💆🏻‍♀️', + ':woman_getting_face_massage_tone1:': '💆🏻‍♀️', + ':woman_getting_face_massage_medium_light_skin_tone:': '💆🏼‍♀️', + ':woman_getting_face_massage_tone2:': '💆🏼‍♀️', + ':woman_getting_face_massage_medium_skin_tone:': '💆🏽‍♀️', + ':woman_getting_face_massage_tone3:': '💆🏽‍♀️', + ':woman_getting_face_massage_medium_dark_skin_tone:': '💆🏾‍♀️', + ':woman_getting_face_massage_tone4:': '💆🏾‍♀️', + ':woman_getting_face_massage_dark_skin_tone:': '💆🏿‍♀️', + ':woman_getting_face_massage_tone5:': '💆🏿‍♀️', + ':woman_getting_haircut_light_skin_tone:': '💇🏻‍♀️', + ':woman_getting_haircut_tone1:': '💇🏻‍♀️', + ':woman_getting_haircut_medium_light_skin_tone:': '💇🏼‍♀️', + ':woman_getting_haircut_tone2:': '💇🏼‍♀️', + ':woman_getting_haircut_medium_skin_tone:': '💇🏽‍♀️', + ':woman_getting_haircut_tone3:': '💇🏽‍♀️', + ':woman_getting_haircut_medium_dark_skin_tone:': '💇🏾‍♀️', + ':woman_getting_haircut_tone4:': '💇🏾‍♀️', + ':woman_getting_haircut_dark_skin_tone:': '💇🏿‍♀️', + ':woman_getting_haircut_tone5:': '💇🏿‍♀️', + ':woman_golfing_light_skin_tone:': '🏌️🏻‍♀️', + ':woman_golfing_tone1:': '🏌️🏻‍♀️', + ':woman_golfing_medium_light_skin_tone:': '🏌️🏼‍♀️', + ':woman_golfing_tone2:': '🏌️🏼‍♀️', + ':woman_golfing_medium_skin_tone:': '🏌️🏽‍♀️', + ':woman_golfing_tone3:': '🏌️🏽‍♀️', + ':woman_golfing_medium_dark_skin_tone:': '🏌️🏾‍♀️', + ':woman_golfing_tone4:': '🏌️🏾‍♀️', + ':woman_golfing_dark_skin_tone:': '🏌️🏿‍♀️', + ':woman_golfing_tone5:': '🏌️🏿‍♀️', + ':woman_guard_light_skin_tone:': '💂🏻‍♀️', + ':woman_guard_tone1:': '💂🏻‍♀️', + ':woman_guard_medium_light_skin_tone:': '💂🏼‍♀️', + ':woman_guard_tone2:': '💂🏼‍♀️', + ':woman_guard_medium_skin_tone:': '💂🏽‍♀️', + ':woman_guard_tone3:': '💂🏽‍♀️', + ':woman_guard_medium_dark_skin_tone:': '💂🏾‍♀️', + ':woman_guard_tone4:': '💂🏾‍♀️', + ':woman_guard_dark_skin_tone:': '💂🏿‍♀️', + ':woman_guard_tone5:': '💂🏿‍♀️', + ':woman_health_worker_light_skin_tone:': '👩🏻‍⚕️', + ':woman_health_worker_tone1:': '👩🏻‍⚕️', + ':woman_health_worker_medium_light_skin_tone:': '👩🏼‍⚕️', + ':woman_health_worker_tone2:': '👩🏼‍⚕️', + ':woman_health_worker_medium_skin_tone:': '👩🏽‍⚕️', + ':woman_health_worker_tone3:': '👩🏽‍⚕️', + ':woman_health_worker_medium_dark_skin_tone:': '👩🏾‍⚕️', + ':woman_health_worker_tone4:': '👩🏾‍⚕️', + ':woman_health_worker_dark_skin_tone:': '👩🏿‍⚕️', + ':woman_health_worker_tone5:': '👩🏿‍⚕️', + ':woman_in_lotus_position_light_skin_tone:': '🧘🏻‍♀️', + ':woman_in_lotus_position_tone1:': '🧘🏻‍♀️', + ':woman_in_lotus_position_medium_light_skin_tone:': '🧘🏼‍♀️', + ':woman_in_lotus_position_tone2:': '🧘🏼‍♀️', + ':woman_in_lotus_position_medium_skin_tone:': '🧘🏽‍♀️', + ':woman_in_lotus_position_tone3:': '🧘🏽‍♀️', + ':woman_in_lotus_position_medium_dark_skin_tone:': '🧘🏾‍♀️', + ':woman_in_lotus_position_tone4:': '🧘🏾‍♀️', + ':woman_in_lotus_position_dark_skin_tone:': '🧘🏿‍♀️', + ':woman_in_lotus_position_tone5:': '🧘🏿‍♀️', + ':woman_in_steamy_room_light_skin_tone:': '🧖🏻‍♀️', + ':woman_in_steamy_room_tone1:': '🧖🏻‍♀️', + ':woman_in_steamy_room_medium_light_skin_tone:': '🧖🏼‍♀️', + ':woman_in_steamy_room_tone2:': '🧖🏼‍♀️', + ':woman_in_steamy_room_medium_skin_tone:': '🧖🏽‍♀️', + ':woman_in_steamy_room_tone3:': '🧖🏽‍♀️', + ':woman_in_steamy_room_medium_dark_skin_tone:': '🧖🏾‍♀️', + ':woman_in_steamy_room_tone4:': '🧖🏾‍♀️', + ':woman_in_steamy_room_dark_skin_tone:': '🧖🏿‍♀️', + ':woman_in_steamy_room_tone5:': '🧖🏿‍♀️', + ':woman_judge_light_skin_tone:': '👩🏻‍⚖️', + ':woman_judge_tone1:': '👩🏻‍⚖️', + ':woman_judge_medium_light_skin_tone:': '👩🏼‍⚖️', + ':woman_judge_tone2:': '👩🏼‍⚖️', + ':woman_judge_medium_skin_tone:': '👩🏽‍⚖️', + ':woman_judge_tone3:': '👩🏽‍⚖️', + ':woman_judge_medium_dark_skin_tone:': '👩🏾‍⚖️', + ':woman_judge_tone4:': '👩🏾‍⚖️', + ':woman_judge_dark_skin_tone:': '👩🏿‍⚖️', + ':woman_judge_tone5:': '👩🏿‍⚖️', + ':woman_juggling_light_skin_tone:': '🤹🏻‍♀️', + ':woman_juggling_tone1:': '🤹🏻‍♀️', + ':woman_juggling_medium_light_skin_tone:': '🤹🏼‍♀️', + ':woman_juggling_tone2:': '🤹🏼‍♀️', + ':woman_juggling_medium_skin_tone:': '🤹🏽‍♀️', + ':woman_juggling_tone3:': '🤹🏽‍♀️', + ':woman_juggling_medium_dark_skin_tone:': '🤹🏾‍♀️', + ':woman_juggling_tone4:': '🤹🏾‍♀️', + ':woman_juggling_dark_skin_tone:': '🤹🏿‍♀️', + ':woman_juggling_tone5:': '🤹🏿‍♀️', + ':woman_kneeling_light_skin_tone:': '🧎🏻‍♀️', + ':woman_kneeling_tone1:': '🧎🏻‍♀️', + ':woman_kneeling_medium_light_skin_tone:': '🧎🏼‍♀️', + ':woman_kneeling_tone2:': '🧎🏼‍♀️', + ':woman_kneeling_medium_skin_tone:': '🧎🏽‍♀️', + ':woman_kneeling_tone3:': '🧎🏽‍♀️', + ':woman_kneeling_medium_dark_skin_tone:': '🧎🏾‍♀️', + ':woman_kneeling_tone4:': '🧎🏾‍♀️', + ':woman_kneeling_dark_skin_tone:': '🧎🏿‍♀️', + ':woman_kneeling_tone5:': '🧎🏿‍♀️', + ':woman_lifting_weights_light_skin_tone:': '🏋️🏻‍♀️', + ':woman_lifting_weights_tone1:': '🏋️🏻‍♀️', + ':woman_lifting_weights_medium_light_skin_tone:': '🏋️🏼‍♀️', + ':woman_lifting_weights_tone2:': '🏋️🏼‍♀️', + ':woman_lifting_weights_medium_skin_tone:': '🏋️🏽‍♀️', + ':woman_lifting_weights_tone3:': '🏋️🏽‍♀️', + ':woman_lifting_weights_medium_dark_skin_tone:': '🏋️🏾‍♀️', + ':woman_lifting_weights_tone4:': '🏋️🏾‍♀️', + ':woman_lifting_weights_dark_skin_tone:': '🏋️🏿‍♀️', + ':woman_lifting_weights_tone5:': '🏋️🏿‍♀️', + ':woman_mage_light_skin_tone:': '🧙🏻‍♀️', + ':woman_mage_tone1:': '🧙🏻‍♀️', + ':woman_mage_medium_light_skin_tone:': '🧙🏼‍♀️', + ':woman_mage_tone2:': '🧙🏼‍♀️', + ':woman_mage_medium_skin_tone:': '🧙🏽‍♀️', + ':woman_mage_tone3:': '🧙🏽‍♀️', + ':woman_mage_medium_dark_skin_tone:': '🧙🏾‍♀️', + ':woman_mage_tone4:': '🧙🏾‍♀️', + ':woman_mage_dark_skin_tone:': '🧙🏿‍♀️', + ':woman_mage_tone5:': '🧙🏿‍♀️', + ':woman_mountain_biking_light_skin_tone:': '🚵🏻‍♀️', + ':woman_mountain_biking_tone1:': '🚵🏻‍♀️', + ':woman_mountain_biking_medium_light_skin_tone:': '🚵🏼‍♀️', + ':woman_mountain_biking_tone2:': '🚵🏼‍♀️', + ':woman_mountain_biking_medium_skin_tone:': '🚵🏽‍♀️', + ':woman_mountain_biking_tone3:': '🚵🏽‍♀️', + ':woman_mountain_biking_medium_dark_skin_tone:': '🚵🏾‍♀️', + ':woman_mountain_biking_tone4:': '🚵🏾‍♀️', + ':woman_mountain_biking_dark_skin_tone:': '🚵🏿‍♀️', + ':woman_mountain_biking_tone5:': '🚵🏿‍♀️', + ':woman_pilot_light_skin_tone:': '👩🏻‍✈️', + ':woman_pilot_tone1:': '👩🏻‍✈️', + ':woman_pilot_medium_light_skin_tone:': '👩🏼‍✈️', + ':woman_pilot_tone2:': '👩🏼‍✈️', + ':woman_pilot_medium_skin_tone:': '👩🏽‍✈️', + ':woman_pilot_tone3:': '👩🏽‍✈️', + ':woman_pilot_medium_dark_skin_tone:': '👩🏾‍✈️', + ':woman_pilot_tone4:': '👩🏾‍✈️', + ':woman_pilot_dark_skin_tone:': '👩🏿‍✈️', + ':woman_pilot_tone5:': '👩🏿‍✈️', + ':woman_playing_handball_light_skin_tone:': '🤾🏻‍♀️', + ':woman_playing_handball_tone1:': '🤾🏻‍♀️', + ':woman_playing_handball_medium_light_skin_tone:': '🤾🏼‍♀️', + ':woman_playing_handball_tone2:': '🤾🏼‍♀️', + ':woman_playing_handball_medium_skin_tone:': '🤾🏽‍♀️', + ':woman_playing_handball_tone3:': '🤾🏽‍♀️', + ':woman_playing_handball_medium_dark_skin_tone:': '🤾🏾‍♀️', + ':woman_playing_handball_tone4:': '🤾🏾‍♀️', + ':woman_playing_handball_dark_skin_tone:': '🤾🏿‍♀️', + ':woman_playing_handball_tone5:': '🤾🏿‍♀️', + ':woman_playing_water_polo_light_skin_tone:': '🤽🏻‍♀️', + ':woman_playing_water_polo_tone1:': '🤽🏻‍♀️', + ':woman_playing_water_polo_medium_light_skin_tone:': '🤽🏼‍♀️', + ':woman_playing_water_polo_tone2:': '🤽🏼‍♀️', + ':woman_playing_water_polo_medium_skin_tone:': '🤽🏽‍♀️', + ':woman_playing_water_polo_tone3:': '🤽🏽‍♀️', + ':woman_playing_water_polo_medium_dark_skin_tone:': '🤽🏾‍♀️', + ':woman_playing_water_polo_tone4:': '🤽🏾‍♀️', + ':woman_playing_water_polo_dark_skin_tone:': '🤽🏿‍♀️', + ':woman_playing_water_polo_tone5:': '🤽🏿‍♀️', + ':woman_police_officer_light_skin_tone:': '👮🏻‍♀️', + ':woman_police_officer_tone1:': '👮🏻‍♀️', + ':woman_police_officer_medium_light_skin_tone:': '👮🏼‍♀️', + ':woman_police_officer_tone2:': '👮🏼‍♀️', + ':woman_police_officer_medium_skin_tone:': '👮🏽‍♀️', + ':woman_police_officer_tone3:': '👮🏽‍♀️', + ':woman_police_officer_medium_dark_skin_tone:': '👮🏾‍♀️', + ':woman_police_officer_tone4:': '👮🏾‍♀️', + ':woman_police_officer_dark_skin_tone:': '👮🏿‍♀️', + ':woman_police_officer_tone5:': '👮🏿‍♀️', + ':woman_pouting_light_skin_tone:': '🙎🏻‍♀️', + ':woman_pouting_tone1:': '🙎🏻‍♀️', + ':woman_pouting_medium_light_skin_tone:': '🙎🏼‍♀️', + ':woman_pouting_tone2:': '🙎🏼‍♀️', + ':woman_pouting_medium_skin_tone:': '🙎🏽‍♀️', + ':woman_pouting_tone3:': '🙎🏽‍♀️', + ':woman_pouting_medium_dark_skin_tone:': '🙎🏾‍♀️', + ':woman_pouting_tone4:': '🙎🏾‍♀️', + ':woman_pouting_dark_skin_tone:': '🙎🏿‍♀️', + ':woman_pouting_tone5:': '🙎🏿‍♀️', + ':woman_raising_hand_light_skin_tone:': '🙋🏻‍♀️', + ':woman_raising_hand_tone1:': '🙋🏻‍♀️', + ':woman_raising_hand_medium_light_skin_tone:': '🙋🏼‍♀️', + ':woman_raising_hand_tone2:': '🙋🏼‍♀️', + ':woman_raising_hand_medium_skin_tone:': '🙋🏽‍♀️', + ':woman_raising_hand_tone3:': '🙋🏽‍♀️', + ':woman_raising_hand_medium_dark_skin_tone:': '🙋🏾‍♀️', + ':woman_raising_hand_tone4:': '🙋🏾‍♀️', + ':woman_raising_hand_dark_skin_tone:': '🙋🏿‍♀️', + ':woman_raising_hand_tone5:': '🙋🏿‍♀️', + ':woman_rowing_boat_light_skin_tone:': '🚣🏻‍♀️', + ':woman_rowing_boat_tone1:': '🚣🏻‍♀️', + ':woman_rowing_boat_medium_light_skin_tone:': '🚣🏼‍♀️', + ':woman_rowing_boat_tone2:': '🚣🏼‍♀️', + ':woman_rowing_boat_medium_skin_tone:': '🚣🏽‍♀️', + ':woman_rowing_boat_tone3:': '🚣🏽‍♀️', + ':woman_rowing_boat_medium_dark_skin_tone:': '🚣🏾‍♀️', + ':woman_rowing_boat_tone4:': '🚣🏾‍♀️', + ':woman_rowing_boat_dark_skin_tone:': '🚣🏿‍♀️', + ':woman_rowing_boat_tone5:': '🚣🏿‍♀️', + ':woman_running_light_skin_tone:': '🏃🏻‍♀️', + ':woman_running_tone1:': '🏃🏻‍♀️', + ':woman_running_medium_light_skin_tone:': '🏃🏼‍♀️', + ':woman_running_tone2:': '🏃🏼‍♀️', + ':woman_running_medium_skin_tone:': '🏃🏽‍♀️', + ':woman_running_tone3:': '🏃🏽‍♀️', + ':woman_running_medium_dark_skin_tone:': '🏃🏾‍♀️', + ':woman_running_tone4:': '🏃🏾‍♀️', + ':woman_running_dark_skin_tone:': '🏃🏿‍♀️', + ':woman_running_tone5:': '🏃🏿‍♀️', + ':woman_shrugging_light_skin_tone:': '🤷🏻‍♀️', + ':woman_shrugging_tone1:': '🤷🏻‍♀️', + ':woman_shrugging_medium_light_skin_tone:': '🤷🏼‍♀️', + ':woman_shrugging_tone2:': '🤷🏼‍♀️', + ':woman_shrugging_medium_skin_tone:': '🤷🏽‍♀️', + ':woman_shrugging_tone3:': '🤷🏽‍♀️', + ':woman_shrugging_medium_dark_skin_tone:': '🤷🏾‍♀️', + ':woman_shrugging_tone4:': '🤷🏾‍♀️', + ':woman_shrugging_dark_skin_tone:': '🤷🏿‍♀️', + ':woman_shrugging_tone5:': '🤷🏿‍♀️', + ':woman_standing_light_skin_tone:': '🧍🏻‍♀️', + ':woman_standing_tone1:': '🧍🏻‍♀️', + ':woman_standing_medium_light_skin_tone:': '🧍🏼‍♀️', + ':woman_standing_tone2:': '🧍🏼‍♀️', + ':woman_standing_medium_skin_tone:': '🧍🏽‍♀️', + ':woman_standing_tone3:': '🧍🏽‍♀️', + ':woman_standing_medium_dark_skin_tone:': '🧍🏾‍♀️', + ':woman_standing_tone4:': '🧍🏾‍♀️', + ':woman_standing_dark_skin_tone:': '🧍🏿‍♀️', + ':woman_standing_tone5:': '🧍🏿‍♀️', + ':woman_superhero_light_skin_tone:': '🦸🏻‍♀️', + ':woman_superhero_tone1:': '🦸🏻‍♀️', + ':woman_superhero_medium_light_skin_tone:': '🦸🏼‍♀️', + ':woman_superhero_tone2:': '🦸🏼‍♀️', + ':woman_superhero_medium_skin_tone:': '🦸🏽‍♀️', + ':woman_superhero_tone3:': '🦸🏽‍♀️', + ':woman_superhero_medium_dark_skin_tone:': '🦸🏾‍♀️', + ':woman_superhero_tone4:': '🦸🏾‍♀️', + ':woman_superhero_dark_skin_tone:': '🦸🏿‍♀️', + ':woman_superhero_tone5:': '🦸🏿‍♀️', + ':woman_supervillain_light_skin_tone:': '🦹🏻‍♀️', + ':woman_supervillain_tone1:': '🦹🏻‍♀️', + ':woman_supervillain_medium_light_skin_tone:': '🦹🏼‍♀️', + ':woman_supervillain_tone2:': '🦹🏼‍♀️', + ':woman_supervillain_medium_skin_tone:': '🦹🏽‍♀️', + ':woman_supervillain_tone3:': '🦹🏽‍♀️', + ':woman_supervillain_medium_dark_skin_tone:': '🦹🏾‍♀️', + ':woman_supervillain_tone4:': '🦹🏾‍♀️', + ':woman_supervillain_dark_skin_tone:': '🦹🏿‍♀️', + ':woman_supervillain_tone5:': '🦹🏿‍♀️', + ':woman_surfing_light_skin_tone:': '🏄🏻‍♀️', + ':woman_surfing_tone1:': '🏄🏻‍♀️', + ':woman_surfing_medium_light_skin_tone:': '🏄🏼‍♀️', + ':woman_surfing_tone2:': '🏄🏼‍♀️', + ':woman_surfing_medium_skin_tone:': '🏄🏽‍♀️', + ':woman_surfing_tone3:': '🏄🏽‍♀️', + ':woman_surfing_medium_dark_skin_tone:': '🏄🏾‍♀️', + ':woman_surfing_tone4:': '🏄🏾‍♀️', + ':woman_surfing_dark_skin_tone:': '🏄🏿‍♀️', + ':woman_surfing_tone5:': '🏄🏿‍♀️', + ':woman_swimming_light_skin_tone:': '🏊🏻‍♀️', + ':woman_swimming_tone1:': '🏊🏻‍♀️', + ':woman_swimming_medium_light_skin_tone:': '🏊🏼‍♀️', + ':woman_swimming_tone2:': '🏊🏼‍♀️', + ':woman_swimming_medium_skin_tone:': '🏊🏽‍♀️', + ':woman_swimming_tone3:': '🏊🏽‍♀️', + ':woman_swimming_medium_dark_skin_tone:': '🏊🏾‍♀️', + ':woman_swimming_tone4:': '🏊🏾‍♀️', + ':woman_swimming_dark_skin_tone:': '🏊🏿‍♀️', + ':woman_swimming_tone5:': '🏊🏿‍♀️', + ':woman_tipping_hand_light_skin_tone:': '💁🏻‍♀️', + ':woman_tipping_hand_tone1:': '💁🏻‍♀️', + ':woman_tipping_hand_medium_light_skin_tone:': '💁🏼‍♀️', + ':woman_tipping_hand_tone2:': '💁🏼‍♀️', + ':woman_tipping_hand_medium_skin_tone:': '💁🏽‍♀️', + ':woman_tipping_hand_tone3:': '💁🏽‍♀️', + ':woman_tipping_hand_medium_dark_skin_tone:': '💁🏾‍♀️', + ':woman_tipping_hand_tone4:': '💁🏾‍♀️', + ':woman_tipping_hand_dark_skin_tone:': '💁🏿‍♀️', + ':woman_tipping_hand_tone5:': '💁🏿‍♀️', + ':woman_vampire_light_skin_tone:': '🧛🏻‍♀️', + ':woman_vampire_tone1:': '🧛🏻‍♀️', + ':woman_vampire_medium_light_skin_tone:': '🧛🏼‍♀️', + ':woman_vampire_tone2:': '🧛🏼‍♀️', + ':woman_vampire_medium_skin_tone:': '🧛🏽‍♀️', + ':woman_vampire_tone3:': '🧛🏽‍♀️', + ':woman_vampire_medium_dark_skin_tone:': '🧛🏾‍♀️', + ':woman_vampire_tone4:': '🧛🏾‍♀️', + ':woman_vampire_dark_skin_tone:': '🧛🏿‍♀️', + ':woman_vampire_tone5:': '🧛🏿‍♀️', + ':woman_walking_light_skin_tone:': '🚶🏻‍♀️', + ':woman_walking_tone1:': '🚶🏻‍♀️', + ':woman_walking_medium_light_skin_tone:': '🚶🏼‍♀️', + ':woman_walking_tone2:': '🚶🏼‍♀️', + ':woman_walking_medium_skin_tone:': '🚶🏽‍♀️', + ':woman_walking_tone3:': '🚶🏽‍♀️', + ':woman_walking_medium_dark_skin_tone:': '🚶🏾‍♀️', + ':woman_walking_tone4:': '🚶🏾‍♀️', + ':woman_walking_dark_skin_tone:': '🚶🏿‍♀️', + ':woman_walking_tone5:': '🚶🏿‍♀️', + ':woman_wearing_turban_light_skin_tone:': '👳🏻‍♀️', + ':woman_wearing_turban_tone1:': '👳🏻‍♀️', + ':woman_wearing_turban_medium_light_skin_tone:': '👳🏼‍♀️', + ':woman_wearing_turban_tone2:': '👳🏼‍♀️', + ':woman_wearing_turban_medium_skin_tone:': '👳🏽‍♀️', + ':woman_wearing_turban_tone3:': '👳🏽‍♀️', + ':woman_wearing_turban_medium_dark_skin_tone:': '👳🏾‍♀️', + ':woman_wearing_turban_tone4:': '👳🏾‍♀️', + ':woman_wearing_turban_dark_skin_tone:': '👳🏿‍♀️', + ':woman_wearing_turban_tone5:': '👳🏿‍♀️', + ':man_bouncing_ball_light_skin_tone:': '⛹️🏻‍♂️', + ':man_bouncing_ball_tone1:': '⛹️🏻‍♂️', + ':man_bouncing_ball_medium_light_skin_tone:': '⛹️🏼‍♂️', + ':man_bouncing_ball_tone2:': '⛹️🏼‍♂️', + ':man_bouncing_ball_medium_skin_tone:': '⛹️🏽‍♂️', + ':man_bouncing_ball_tone3:': '⛹️🏽‍♂️', + ':man_bouncing_ball_medium_dark_skin_tone:': '⛹️🏾‍♂️', + ':man_bouncing_ball_tone4:': '⛹️🏾‍♂️', + ':man_bouncing_ball_dark_skin_tone:': '⛹️🏿‍♂️', + ':man_bouncing_ball_tone5:': '⛹️🏿‍♂️', + ':woman_bouncing_ball_light_skin_tone:': '⛹️🏻‍♀️', + ':woman_bouncing_ball_tone1:': '⛹️🏻‍♀️', + ':woman_bouncing_ball_medium_light_skin_tone:': '⛹️🏼‍♀️', + ':woman_bouncing_ball_tone2:': '⛹️🏼‍♀️', + ':woman_bouncing_ball_medium_skin_tone:': '⛹️🏽‍♀️', + ':woman_bouncing_ball_tone3:': '⛹️🏽‍♀️', + ':woman_bouncing_ball_medium_dark_skin_tone:': '⛹️🏾‍♀️', + ':woman_bouncing_ball_tone4:': '⛹️🏾‍♀️', + ':woman_bouncing_ball_dark_skin_tone:': '⛹️🏿‍♀️', + ':woman_bouncing_ball_tone5:': '⛹️🏿‍♀️', + ':adult_light_skin_tone:': '🧑🏻', + ':adult_tone1:': '🧑🏻', + ':adult_medium_light_skin_tone:': '🧑🏼', + ':adult_tone2:': '🧑🏼', + ':adult_medium_skin_tone:': '🧑🏽', + ':adult_tone3:': '🧑🏽', + ':adult_medium_dark_skin_tone:': '🧑🏾', + ':adult_tone4:': '🧑🏾', + ':adult_dark_skin_tone:': '🧑🏿', + ':adult_tone5:': '🧑🏿', + ':angel_tone1:': '👼🏻', + ':angel_tone2:': '👼🏼', + ':angel_tone3:': '👼🏽', + ':angel_tone4:': '👼🏾', + ':angel_tone5:': '👼🏿', + ':baby_tone1:': '👶🏻', + ':baby_tone2:': '👶🏼', + ':baby_tone3:': '👶🏽', + ':baby_tone4:': '👶🏾', + ':baby_tone5:': '👶🏿', + ':bath_tone1:': '🛀🏻', + ':bath_tone2:': '🛀🏼', + ':bath_tone3:': '🛀🏽', + ':bath_tone4:': '🛀🏾', + ':bath_tone5:': '🛀🏿', + ':bearded_person_light_skin_tone:': '🧔🏻', + ':bearded_person_tone1:': '🧔🏻', + ':bearded_person_medium_light_skin_tone:': '🧔🏼', + ':bearded_person_tone2:': '🧔🏼', + ':bearded_person_medium_skin_tone:': '🧔🏽', + ':bearded_person_tone3:': '🧔🏽', + ':bearded_person_medium_dark_skin_tone:': '🧔🏾', + ':bearded_person_tone4:': '🧔🏾', + ':bearded_person_dark_skin_tone:': '🧔🏿', + ':bearded_person_tone5:': '🧔🏿', + ':person_with_blond_hair_tone1:': '👱🏻', + ':blond_haired_person_tone1:': '👱🏻', + ':person_with_blond_hair_tone2:': '👱🏼', + ':blond_haired_person_tone2:': '👱🏼', + ':person_with_blond_hair_tone3:': '👱🏽', + ':blond_haired_person_tone3:': '👱🏽', + ':person_with_blond_hair_tone4:': '👱🏾', + ':blond_haired_person_tone4:': '👱🏾', + ':person_with_blond_hair_tone5:': '👱🏿', + ':blond_haired_person_tone5:': '👱🏿', + ':boy_tone1:': '👦🏻', + ':boy_tone2:': '👦🏼', + ':boy_tone3:': '👦🏽', + ':boy_tone4:': '👦🏾', + ':boy_tone5:': '👦🏿', + ':breast_feeding_light_skin_tone:': '🤱🏻', + ':breast_feeding_tone1:': '🤱🏻', + ':breast_feeding_medium_light_skin_tone:': '🤱🏼', + ':breast_feeding_tone2:': '🤱🏼', + ':breast_feeding_medium_skin_tone:': '🤱🏽', + ':breast_feeding_tone3:': '🤱🏽', + ':breast_feeding_medium_dark_skin_tone:': '🤱🏾', + ':breast_feeding_tone4:': '🤱🏾', + ':breast_feeding_dark_skin_tone:': '🤱🏿', + ':breast_feeding_tone5:': '🤱🏿', + ':bride_with_veil_tone1:': '👰🏻', + ':bride_with_veil_tone2:': '👰🏼', + ':bride_with_veil_tone3:': '👰🏽', + ':bride_with_veil_tone4:': '👰🏾', + ':bride_with_veil_tone5:': '👰🏿', + ':call_me_hand_tone1:': '🤙🏻', + ':call_me_tone1:': '🤙🏻', + ':call_me_hand_tone2:': '🤙🏼', + ':call_me_tone2:': '🤙🏼', + ':call_me_hand_tone3:': '🤙🏽', + ':call_me_tone3:': '🤙🏽', + ':call_me_hand_tone4:': '🤙🏾', + ':call_me_tone4:': '🤙🏾', + ':call_me_hand_tone5:': '🤙🏿', + ':call_me_tone5:': '🤙🏿', + ':child_light_skin_tone:': '🧒🏻', + ':child_tone1:': '🧒🏻', + ':child_medium_light_skin_tone:': '🧒🏼', + ':child_tone2:': '🧒🏼', + ':child_medium_skin_tone:': '🧒🏽', + ':child_tone3:': '🧒🏽', + ':child_medium_dark_skin_tone:': '🧒🏾', + ':child_tone4:': '🧒🏾', + ':child_dark_skin_tone:': '🧒🏿', + ':child_tone5:': '🧒🏿', + ':clap_tone1:': '👏🏻', + ':clap_tone2:': '👏🏼', + ':clap_tone3:': '👏🏽', + ':clap_tone4:': '👏🏾', + ':clap_tone5:': '👏🏿', + ':construction_worker_tone1:': '👷🏻', + ':construction_worker_tone2:': '👷🏼', + ':construction_worker_tone3:': '👷🏽', + ':construction_worker_tone4:': '👷🏾', + ':construction_worker_tone5:': '👷🏿', + ':dancer_tone1:': '💃🏻', + ':dancer_tone2:': '💃🏼', + ':dancer_tone3:': '💃🏽', + ':dancer_tone4:': '💃🏾', + ':dancer_tone5:': '💃🏿', + ':deaf_person_light_skin_tone:': '🧏🏻', + ':deaf_person_tone1:': '🧏🏻', + ':deaf_person_medium_light_skin_tone:': '🧏🏼', + ':deaf_person_tone2:': '🧏🏼', + ':deaf_person_medium_skin_tone:': '🧏🏽', + ':deaf_person_tone3:': '🧏🏽', + ':deaf_person_medium_dark_skin_tone:': '🧏🏾', + ':deaf_person_tone4:': '🧏🏾', + ':deaf_person_dark_skin_tone:': '🧏🏿', + ':deaf_person_tone5:': '🧏🏿', + ':spy_tone1:': '🕵️🏻', + ':sleuth_or_spy_tone1:': '🕵️🏻', + ':detective_tone1:': '🕵️🏻', + ':spy_tone2:': '🕵️🏼', + ':sleuth_or_spy_tone2:': '🕵️🏼', + ':detective_tone2:': '🕵️🏼', + ':spy_tone3:': '🕵️🏽', + ':sleuth_or_spy_tone3:': '🕵️🏽', + ':detective_tone3:': '🕵️🏽', + ':spy_tone4:': '🕵️🏾', + ':sleuth_or_spy_tone4:': '🕵️🏾', + ':detective_tone4:': '🕵️🏾', + ':spy_tone5:': '🕵️🏿', + ':sleuth_or_spy_tone5:': '🕵️🏿', + ':detective_tone5:': '🕵️🏿', + ':ear_tone1:': '👂🏻', + ':ear_tone2:': '👂🏼', + ':ear_tone3:': '👂🏽', + ':ear_tone4:': '👂🏾', + ':ear_tone5:': '👂🏿', + ':ear_with_hearing_aid_light_skin_tone:': '🦻🏻', + ':ear_with_hearing_aid_tone1:': '🦻🏻', + ':ear_with_hearing_aid_medium_light_skin_tone:': '🦻🏼', + ':ear_with_hearing_aid_tone2:': '🦻🏼', + ':ear_with_hearing_aid_medium_skin_tone:': '🦻🏽', + ':ear_with_hearing_aid_tone3:': '🦻🏽', + ':ear_with_hearing_aid_medium_dark_skin_tone:': '🦻🏾', + ':ear_with_hearing_aid_tone4:': '🦻🏾', + ':ear_with_hearing_aid_dark_skin_tone:': '🦻🏿', + ':ear_with_hearing_aid_tone5:': '🦻🏿', + ':elf_light_skin_tone:': '🧝🏻', + ':elf_tone1:': '🧝🏻', + ':elf_medium_light_skin_tone:': '🧝🏼', + ':elf_tone2:': '🧝🏼', + ':elf_medium_skin_tone:': '🧝🏽', + ':elf_tone3:': '🧝🏽', + ':elf_medium_dark_skin_tone:': '🧝🏾', + ':elf_tone4:': '🧝🏾', + ':elf_dark_skin_tone:': '🧝🏿', + ':elf_tone5:': '🧝🏿', + ':eye_in_speech_bubble:': '👁️‍🗨️', + ':fairy_light_skin_tone:': '🧚🏻', + ':fairy_tone1:': '🧚🏻', + ':fairy_medium_light_skin_tone:': '🧚🏼', + ':fairy_tone2:': '🧚🏼', + ':fairy_medium_skin_tone:': '🧚🏽', + ':fairy_tone3:': '🧚🏽', + ':fairy_medium_dark_skin_tone:': '🧚🏾', + ':fairy_tone4:': '🧚🏾', + ':fairy_dark_skin_tone:': '🧚🏿', + ':fairy_tone5:': '🧚🏿', + ':family_man_boy:': '👨‍👦', + ':family_man_girl:': '👨‍👧', + ':family_woman_boy:': '👩‍👦', + ':family_woman_girl:': '👩‍👧', + ':hand_with_index_and_middle_fingers_crossed_tone1:': '🤞🏻', + ':fingers_crossed_tone1:': '🤞🏻', + ':hand_with_index_and_middle_fingers_crossed_tone2:': '🤞🏼', + ':fingers_crossed_tone2:': '🤞🏼', + ':hand_with_index_and_middle_fingers_crossed_tone3:': '🤞🏽', + ':fingers_crossed_tone3:': '🤞🏽', + ':hand_with_index_and_middle_fingers_crossed_tone4:': '🤞🏾', + ':fingers_crossed_tone4:': '🤞🏾', + ':hand_with_index_and_middle_fingers_crossed_tone5:': '🤞🏿', + ':fingers_crossed_tone5:': '🤞🏿', + ':ac:': '🇦🇨', + ':flag_ac:': '🇦🇨', + ':ad:': '🇦🇩', + ':flag_ad:': '🇦🇩', + ':ae:': '🇦🇪', + ':flag_ae:': '🇦🇪', + ':af:': '🇦🇫', + ':flag_af:': '🇦🇫', + ':ag:': '🇦🇬', + ':flag_ag:': '🇦🇬', + ':ai:': '🇦🇮', + ':flag_ai:': '🇦🇮', + ':al:': '🇦🇱', + ':flag_al:': '🇦🇱', + ':am:': '🇦🇲', + ':flag_am:': '🇦🇲', + ':ao:': '🇦🇴', + ':flag_ao:': '🇦🇴', + ':aq:': '🇦🇶', + ':flag_aq:': '🇦🇶', + ':ar:': '🇦🇷', + ':flag_ar:': '🇦🇷', + ':as:': '🇦🇸', + ':flag_as:': '🇦🇸', + ':at:': '🇦🇹', + ':flag_at:': '🇦🇹', + ':au:': '🇦🇺', + ':flag_au:': '🇦🇺', + ':aw:': '🇦🇼', + ':flag_aw:': '🇦🇼', + ':ax:': '🇦🇽', + ':flag_ax:': '🇦🇽', + ':az:': '🇦🇿', + ':flag_az:': '🇦🇿', + ':ba:': '🇧🇦', + ':flag_ba:': '🇧🇦', + ':bb:': '🇧🇧', + ':flag_bb:': '🇧🇧', + ':bd:': '🇧🇩', + ':flag_bd:': '🇧🇩', + ':be:': '🇧🇪', + ':flag_be:': '🇧🇪', + ':bf:': '🇧🇫', + ':flag_bf:': '🇧🇫', + ':bg:': '🇧🇬', + ':flag_bg:': '🇧🇬', + ':bh:': '🇧🇭', + ':flag_bh:': '🇧🇭', + ':bi:': '🇧🇮', + ':flag_bi:': '🇧🇮', + ':bj:': '🇧🇯', + ':flag_bj:': '🇧🇯', + ':bm:': '🇧🇲', + ':flag_bm:': '🇧🇲', + ':bn:': '🇧🇳', + ':flag_bn:': '🇧🇳', + ':bo:': '🇧🇴', + ':flag_bo:': '🇧🇴', + ':br:': '🇧🇷', + ':flag_br:': '🇧🇷', + ':bs:': '🇧🇸', + ':flag_bs:': '🇧🇸', + ':bt:': '🇧🇹', + ':flag_bt:': '🇧🇹', + ':bv:': '🇧🇻', + ':flag_bv:': '🇧🇻', + ':bw:': '🇧🇼', + ':flag_bw:': '🇧🇼', + ':by:': '🇧🇾', + ':flag_by:': '🇧🇾', + ':bz:': '🇧🇿', + ':flag_bz:': '🇧🇿', + ':ca:': '🇨🇦', + ':flag_ca:': '🇨🇦', + ':cc:': '🇨🇨', + ':flag_cc:': '🇨🇨', + ':congo:': '🇨🇩', + ':flag_cd:': '🇨🇩', + ':cf:': '🇨🇫', + ':flag_cf:': '🇨🇫', + ':cg:': '🇨🇬', + ':flag_cg:': '🇨🇬', + ':ch:': '🇨🇭', + ':flag_ch:': '🇨🇭', + ':ci:': '🇨🇮', + ':flag_ci:': '🇨🇮', + ':ck:': '🇨🇰', + ':flag_ck:': '🇨🇰', + ':chile:': '🇨🇱', + ':flag_cl:': '🇨🇱', + ':cm:': '🇨🇲', + ':flag_cm:': '🇨🇲', + ':cn:': '🇨🇳', + ':flag_cn:': '🇨🇳', + ':co:': '🇨🇴', + ':flag_co:': '🇨🇴', + ':cp:': '🇨🇵', + ':flag_cp:': '🇨🇵', + ':cr:': '🇨🇷', + ':flag_cr:': '🇨🇷', + ':cu:': '🇨🇺', + ':flag_cu:': '🇨🇺', + ':cv:': '🇨🇻', + ':flag_cv:': '🇨🇻', + ':cw:': '🇨🇼', + ':flag_cw:': '🇨🇼', + ':cx:': '🇨🇽', + ':flag_cx:': '🇨🇽', + ':cy:': '🇨🇾', + ':flag_cy:': '🇨🇾', + ':cz:': '🇨🇿', + ':flag_cz:': '🇨🇿', + ':de:': '🇩🇪', + ':flag_de:': '🇩🇪', + ':dj:': '🇩🇯', + ':flag_dj:': '🇩🇯', + ':dk:': '🇩🇰', + ':flag_dk:': '🇩🇰', + ':dm:': '🇩🇲', + ':flag_dm:': '🇩🇲', + ':do:': '🇩🇴', + ':flag_do:': '🇩🇴', + ':dz:': '🇩🇿', + ':flag_dz:': '🇩🇿', + ':ec:': '🇪🇨', + ':flag_ec:': '🇪🇨', + ':ee:': '🇪🇪', + ':flag_ee:': '🇪🇪', + ':eg:': '🇪🇬', + ':flag_eg:': '🇪🇬', + ':er:': '🇪🇷', + ':flag_er:': '🇪🇷', + ':es:': '🇪🇸', + ':flag_es:': '🇪🇸', + ':et:': '🇪🇹', + ':flag_et:': '🇪🇹', + ':eu:': '🇪🇺', + ':flag_eu:': '🇪🇺', + ':fi:': '🇫🇮', + ':flag_fi:': '🇫🇮', + ':fj:': '🇫🇯', + ':flag_fj:': '🇫🇯', + ':fm:': '🇫🇲', + ':flag_fm:': '🇫🇲', + ':fo:': '🇫🇴', + ':flag_fo:': '🇫🇴', + ':fr:': '🇫🇷', + ':flag_fr:': '🇫🇷', + ':ga:': '🇬🇦', + ':flag_ga:': '🇬🇦', + ':gb:': '🇬🇧', + ':flag_gb:': '🇬🇧', + ':gd:': '🇬🇩', + ':flag_gd:': '🇬🇩', + ':ge:': '🇬🇪', + ':flag_ge:': '🇬🇪', + ':gg:': '🇬🇬', + ':flag_gg:': '🇬🇬', + ':gh:': '🇬🇭', + ':flag_gh:': '🇬🇭', + ':gi:': '🇬🇮', + ':flag_gi:': '🇬🇮', + ':gl:': '🇬🇱', + ':flag_gl:': '🇬🇱', + ':gm:': '🇬🇲', + ':flag_gm:': '🇬🇲', + ':gn:': '🇬🇳', + ':flag_gn:': '🇬🇳', + ':gq:': '🇬🇶', + ':flag_gq:': '🇬🇶', + ':gr:': '🇬🇷', + ':flag_gr:': '🇬🇷', + ':gt:': '🇬🇹', + ':flag_gt:': '🇬🇹', + ':gu:': '🇬🇺', + ':flag_gu:': '🇬🇺', + ':gw:': '🇬🇼', + ':flag_gw:': '🇬🇼', + ':gy:': '🇬🇾', + ':flag_gy:': '🇬🇾', + ':hk:': '🇭🇰', + ':flag_hk:': '🇭🇰', + ':hm:': '🇭🇲', + ':flag_hm:': '🇭🇲', + ':hn:': '🇭🇳', + ':flag_hn:': '🇭🇳', + ':hr:': '🇭🇷', + ':flag_hr:': '🇭🇷', + ':ht:': '🇭🇹', + ':flag_ht:': '🇭🇹', + ':hu:': '🇭🇺', + ':flag_hu:': '🇭🇺', + ':ic:': '🇮🇨', + ':flag_ic:': '🇮🇨', + ':indonesia:': '🇮🇩', + ':flag_id:': '🇮🇩', + ':ie:': '🇮🇪', + ':flag_ie:': '🇮🇪', + ':il:': '🇮🇱', + ':flag_il:': '🇮🇱', + ':im:': '🇮🇲', + ':flag_im:': '🇮🇲', + ':in:': '🇮🇳', + ':flag_in:': '🇮🇳', + ':io:': '🇮🇴', + ':flag_io:': '🇮🇴', + ':iq:': '🇮🇶', + ':flag_iq:': '🇮🇶', + ':ir:': '🇮🇷', + ':flag_ir:': '🇮🇷', + ':is:': '🇮🇸', + ':flag_is:': '🇮🇸', + ':it:': '🇮🇹', + ':flag_it:': '🇮🇹', + ':je:': '🇯🇪', + ':flag_je:': '🇯🇪', + ':jm:': '🇯🇲', + ':flag_jm:': '🇯🇲', + ':jo:': '🇯🇴', + ':flag_jo:': '🇯🇴', + ':jp:': '🇯🇵', + ':flag_jp:': '🇯🇵', + ':ke:': '🇰🇪', + ':flag_ke:': '🇰🇪', + ':kg:': '🇰🇬', + ':flag_kg:': '🇰🇬', + ':kh:': '🇰🇭', + ':flag_kh:': '🇰🇭', + ':ki:': '🇰🇮', + ':flag_ki:': '🇰🇮', + ':km:': '🇰🇲', + ':flag_km:': '🇰🇲', + ':kn:': '🇰🇳', + ':flag_kn:': '🇰🇳', + ':kp:': '🇰🇵', + ':flag_kp:': '🇰🇵', + ':kr:': '🇰🇷', + ':flag_kr:': '🇰🇷', + ':kw:': '🇰🇼', + ':flag_kw:': '🇰🇼', + ':ky:': '🇰🇾', + ':flag_ky:': '🇰🇾', + ':kz:': '🇰🇿', + ':flag_kz:': '🇰🇿', + ':la:': '🇱🇦', + ':flag_la:': '🇱🇦', + ':lb:': '🇱🇧', + ':flag_lb:': '🇱🇧', + ':lc:': '🇱🇨', + ':flag_lc:': '🇱🇨', + ':li:': '🇱🇮', + ':flag_li:': '🇱🇮', + ':lk:': '🇱🇰', + ':flag_lk:': '🇱🇰', + ':lr:': '🇱🇷', + ':flag_lr:': '🇱🇷', + ':ls:': '🇱🇸', + ':flag_ls:': '🇱🇸', + ':lt:': '🇱🇹', + ':flag_lt:': '🇱🇹', + ':lu:': '🇱🇺', + ':flag_lu:': '🇱🇺', + ':lv:': '🇱🇻', + ':flag_lv:': '🇱🇻', + ':ly:': '🇱🇾', + ':flag_ly:': '🇱🇾', + ':ma:': '🇲🇦', + ':flag_ma:': '🇲🇦', + ':mc:': '🇲🇨', + ':flag_mc:': '🇲🇨', + ':md:': '🇲🇩', + ':flag_md:': '🇲🇩', + ':me:': '🇲🇪', + ':flag_me:': '🇲🇪', + ':mg:': '🇲🇬', + ':flag_mg:': '🇲🇬', + ':mh:': '🇲🇭', + ':flag_mh:': '🇲🇭', + ':mk:': '🇲🇰', + ':flag_mk:': '🇲🇰', + ':ml:': '🇲🇱', + ':flag_ml:': '🇲🇱', + ':mm:': '🇲🇲', + ':flag_mm:': '🇲🇲', + ':mn:': '🇲🇳', + ':flag_mn:': '🇲🇳', + ':mo:': '🇲🇴', + ':flag_mo:': '🇲🇴', + ':mp:': '🇲🇵', + ':flag_mp:': '🇲🇵', + ':mr:': '🇲🇷', + ':flag_mr:': '🇲🇷', + ':ms:': '🇲🇸', + ':flag_ms:': '🇲🇸', + ':mt:': '🇲🇹', + ':flag_mt:': '🇲🇹', + ':mu:': '🇲🇺', + ':flag_mu:': '🇲🇺', + ':mv:': '🇲🇻', + ':flag_mv:': '🇲🇻', + ':mw:': '🇲🇼', + ':flag_mw:': '🇲🇼', + ':mx:': '🇲🇽', + ':flag_mx:': '🇲🇽', + ':my:': '🇲🇾', + ':flag_my:': '🇲🇾', + ':mz:': '🇲🇿', + ':flag_mz:': '🇲🇿', + ':na:': '🇳🇦', + ':flag_na:': '🇳🇦', + ':ne:': '🇳🇪', + ':flag_ne:': '🇳🇪', + ':nf:': '🇳🇫', + ':flag_nf:': '🇳🇫', + ':nigeria:': '🇳🇬', + ':flag_ng:': '🇳🇬', + ':ni:': '🇳🇮', + ':flag_ni:': '🇳🇮', + ':nl:': '🇳🇱', + ':flag_nl:': '🇳🇱', + ':no:': '🇳🇴', + ':flag_no:': '🇳🇴', + ':np:': '🇳🇵', + ':flag_np:': '🇳🇵', + ':nr:': '🇳🇷', + ':flag_nr:': '🇳🇷', + ':nu:': '🇳🇺', + ':flag_nu:': '🇳🇺', + ':nz:': '🇳🇿', + ':flag_nz:': '🇳🇿', + ':om:': '🇴🇲', + ':flag_om:': '🇴🇲', + ':pa:': '🇵🇦', + ':flag_pa:': '🇵🇦', + ':pe:': '🇵🇪', + ':flag_pe:': '🇵🇪', + ':pf:': '🇵🇫', + ':flag_pf:': '🇵🇫', + ':pg:': '🇵🇬', + ':flag_pg:': '🇵🇬', + ':ph:': '🇵🇭', + ':flag_ph:': '🇵🇭', + ':pk:': '🇵🇰', + ':flag_pk:': '🇵🇰', + ':pl:': '🇵🇱', + ':flag_pl:': '🇵🇱', + ':pn:': '🇵🇳', + ':flag_pn:': '🇵🇳', + ':pr:': '🇵🇷', + ':flag_pr:': '🇵🇷', + ':ps:': '🇵🇸', + ':flag_ps:': '🇵🇸', + ':pt:': '🇵🇹', + ':flag_pt:': '🇵🇹', + ':pw:': '🇵🇼', + ':flag_pw:': '🇵🇼', + ':py:': '🇵🇾', + ':flag_py:': '🇵🇾', + ':qa:': '🇶🇦', + ':flag_qa:': '🇶🇦', + ':ro:': '🇷🇴', + ':flag_ro:': '🇷🇴', + ':rs:': '🇷🇸', + ':flag_rs:': '🇷🇸', + ':ru:': '🇷🇺', + ':flag_ru:': '🇷🇺', + ':rw:': '🇷🇼', + ':flag_rw:': '🇷🇼', + ':saudiarabia:': '🇸🇦', + ':saudi:': '🇸🇦', + ':flag_sa:': '🇸🇦', + ':sb:': '🇸🇧', + ':flag_sb:': '🇸🇧', + ':sc:': '🇸🇨', + ':flag_sc:': '🇸🇨', + ':sd:': '🇸🇩', + ':flag_sd:': '🇸🇩', + ':se:': '🇸🇪', + ':flag_se:': '🇸🇪', + ':sg:': '🇸🇬', + ':flag_sg:': '🇸🇬', + ':sh:': '🇸🇭', + ':flag_sh:': '🇸🇭', + ':si:': '🇸🇮', + ':flag_si:': '🇸🇮', + ':sj:': '🇸🇯', + ':flag_sj:': '🇸🇯', + ':sk:': '🇸🇰', + ':flag_sk:': '🇸🇰', + ':sl:': '🇸🇱', + ':flag_sl:': '🇸🇱', + ':sm:': '🇸🇲', + ':flag_sm:': '🇸🇲', + ':sn:': '🇸🇳', + ':flag_sn:': '🇸🇳', + ':so:': '🇸🇴', + ':flag_so:': '🇸🇴', + ':sr:': '🇸🇷', + ':flag_sr:': '🇸🇷', + ':ss:': '🇸🇸', + ':flag_ss:': '🇸🇸', + ':st:': '🇸🇹', + ':flag_st:': '🇸🇹', + ':sv:': '🇸🇻', + ':flag_sv:': '🇸🇻', + ':sx:': '🇸🇽', + ':flag_sx:': '🇸🇽', + ':sy:': '🇸🇾', + ':flag_sy:': '🇸🇾', + ':sz:': '🇸🇿', + ':flag_sz:': '🇸🇿', + ':ta:': '🇹🇦', + ':flag_ta:': '🇹🇦', + ':tc:': '🇹🇨', + ':flag_tc:': '🇹🇨', + ':td:': '🇹🇩', + ':flag_td:': '🇹🇩', + ':tg:': '🇹🇬', + ':flag_tg:': '🇹🇬', + ':th:': '🇹🇭', + ':flag_th:': '🇹🇭', + ':tj:': '🇹🇯', + ':flag_tj:': '🇹🇯', + ':tk:': '🇹🇰', + ':flag_tk:': '🇹🇰', + ':tl:': '🇹🇱', + ':flag_tl:': '🇹🇱', + ':turkmenistan:': '🇹🇲', + ':flag_tm:': '🇹🇲', + ':tn:': '🇹🇳', + ':flag_tn:': '🇹🇳', + ':to:': '🇹🇴', + ':flag_to:': '🇹🇴', + ':tr:': '🇹🇷', + ':flag_tr:': '🇹🇷', + ':tt:': '🇹🇹', + ':flag_tt:': '🇹🇹', + ':tuvalu:': '🇹🇻', + ':flag_tv:': '🇹🇻', + ':tw:': '🇹🇼', + ':flag_tw:': '🇹🇼', + ':tz:': '🇹🇿', + ':flag_tz:': '🇹🇿', + ':ua:': '🇺🇦', + ':flag_ua:': '🇺🇦', + ':ug:': '🇺🇬', + ':flag_ug:': '🇺🇬', + ':um:': '🇺🇲', + ':flag_um:': '🇺🇲', + ':us:': '🇺🇸', + ':flag_us:': '🇺🇸', + ':uy:': '🇺🇾', + ':flag_uy:': '🇺🇾', + ':uz:': '🇺🇿', + ':flag_uz:': '🇺🇿', + ':va:': '🇻🇦', + ':flag_va:': '🇻🇦', + ':vc:': '🇻🇨', + ':flag_vc:': '🇻🇨', + ':ve:': '🇻🇪', + ':flag_ve:': '🇻🇪', + ':vg:': '🇻🇬', + ':flag_vg:': '🇻🇬', + ':vi:': '🇻🇮', + ':flag_vi:': '🇻🇮', + ':vn:': '🇻🇳', + ':flag_vn:': '🇻🇳', + ':vu:': '🇻🇺', + ':flag_vu:': '🇻🇺', + ':ws:': '🇼🇸', + ':flag_ws:': '🇼🇸', + ':ye:': '🇾🇪', + ':flag_ye:': '🇾🇪', + ':za:': '🇿🇦', + ':flag_za:': '🇿🇦', + ':zm:': '🇿🇲', + ':flag_zm:': '🇿🇲', + ':zw:': '🇿🇼', + ':flag_zw:': '🇿🇼', + ':foot_light_skin_tone:': '🦶🏻', + ':foot_tone1:': '🦶🏻', + ':foot_medium_light_skin_tone:': '🦶🏼', + ':foot_tone2:': '🦶🏼', + ':foot_medium_skin_tone:': '🦶🏽', + ':foot_tone3:': '🦶🏽', + ':foot_medium_dark_skin_tone:': '🦶🏾', + ':foot_tone4:': '🦶🏾', + ':foot_dark_skin_tone:': '🦶🏿', + ':foot_tone5:': '🦶🏿', + ':girl_tone1:': '👧🏻', + ':girl_tone2:': '👧🏼', + ':girl_tone3:': '👧🏽', + ':girl_tone4:': '👧🏾', + ':girl_tone5:': '👧🏿', + ':guardsman_tone1:': '💂🏻', + ':guard_tone1:': '💂🏻', + ':guardsman_tone2:': '💂🏼', + ':guard_tone2:': '💂🏼', + ':guardsman_tone3:': '💂🏽', + ':guard_tone3:': '💂🏽', + ':guardsman_tone4:': '💂🏾', + ':guard_tone4:': '💂🏾', + ':guardsman_tone5:': '💂🏿', + ':guard_tone5:': '💂🏿', + ':raised_hand_with_fingers_splayed_tone1:': '🖐️🏻', + ':hand_splayed_tone1:': '🖐️🏻', + ':raised_hand_with_fingers_splayed_tone2:': '🖐️🏼', + ':hand_splayed_tone2:': '🖐️🏼', + ':raised_hand_with_fingers_splayed_tone3:': '🖐️🏽', + ':hand_splayed_tone3:': '🖐️🏽', + ':raised_hand_with_fingers_splayed_tone4:': '🖐️🏾', + ':hand_splayed_tone4:': '🖐️🏾', + ':raised_hand_with_fingers_splayed_tone5:': '🖐️🏿', + ':hand_splayed_tone5:': '🖐️🏿', + ':horse_racing_tone1:': '🏇🏻', + ':horse_racing_tone2:': '🏇🏼', + ':horse_racing_tone3:': '🏇🏽', + ':horse_racing_tone4:': '🏇🏾', + ':horse_racing_tone5:': '🏇🏿', + ':left_fist_tone1:': '🤛🏻', + ':left_facing_fist_tone1:': '🤛🏻', + ':left_fist_tone2:': '🤛🏼', + ':left_facing_fist_tone2:': '🤛🏼', + ':left_fist_tone3:': '🤛🏽', + ':left_facing_fist_tone3:': '🤛🏽', + ':left_fist_tone4:': '🤛🏾', + ':left_facing_fist_tone4:': '🤛🏾', + ':left_fist_tone5:': '🤛🏿', + ':left_facing_fist_tone5:': '🤛🏿', + ':leg_light_skin_tone:': '🦵🏻', + ':leg_tone1:': '🦵🏻', + ':leg_medium_light_skin_tone:': '🦵🏼', + ':leg_tone2:': '🦵🏼', + ':leg_medium_skin_tone:': '🦵🏽', + ':leg_tone3:': '🦵🏽', + ':leg_medium_dark_skin_tone:': '🦵🏾', + ':leg_tone4:': '🦵🏾', + ':leg_dark_skin_tone:': '🦵🏿', + ':leg_tone5:': '🦵🏿', + ':man_in_business_suit_levitating_tone1:': '🕴️🏻', + ':man_in_business_suit_levitating_light_skin_tone:': '🕴️🏻', + ':levitate_tone1:': '🕴️🏻', + ':man_in_business_suit_levitating_tone2:': '🕴️🏼', + ':man_in_business_suit_levitating_medium_light_skin_tone:': '🕴️🏼', + ':levitate_tone2:': '🕴️🏼', + ':man_in_business_suit_levitating_tone3:': '🕴️🏽', + ':man_in_business_suit_levitating_medium_skin_tone:': '🕴️🏽', + ':levitate_tone3:': '🕴️🏽', + ':man_in_business_suit_levitating_tone4:': '🕴️🏾', + ':man_in_business_suit_levitating_medium_dark_skin_tone:': '🕴️🏾', + ':levitate_tone4:': '🕴️🏾', + ':man_in_business_suit_levitating_tone5:': '🕴️🏿', + ':man_in_business_suit_levitating_dark_skin_tone:': '🕴️🏿', + ':levitate_tone5:': '🕴️🏿', + ':love_you_gesture_light_skin_tone:': '🤟🏻', + ':love_you_gesture_tone1:': '🤟🏻', + ':love_you_gesture_medium_light_skin_tone:': '🤟🏼', + ':love_you_gesture_tone2:': '🤟🏼', + ':love_you_gesture_medium_skin_tone:': '🤟🏽', + ':love_you_gesture_tone3:': '🤟🏽', + ':love_you_gesture_medium_dark_skin_tone:': '🤟🏾', + ':love_you_gesture_tone4:': '🤟🏾', + ':love_you_gesture_dark_skin_tone:': '🤟🏿', + ':love_you_gesture_tone5:': '🤟🏿', + ':mage_light_skin_tone:': '🧙🏻', + ':mage_tone1:': '🧙🏻', + ':mage_medium_light_skin_tone:': '🧙🏼', + ':mage_tone2:': '🧙🏼', + ':mage_medium_skin_tone:': '🧙🏽', + ':mage_tone3:': '🧙🏽', + ':mage_medium_dark_skin_tone:': '🧙🏾', + ':mage_tone4:': '🧙🏾', + ':mage_dark_skin_tone:': '🧙🏿', + ':mage_tone5:': '🧙🏿', + ':man_artist:': '👨‍🎨', + ':man_astronaut:': '👨‍🚀', + ':man_bald:': '👨‍🦲', + ':man_cook:': '👨‍🍳', + ':man_curly_haired:': '👨‍🦱', + ':male_dancer_tone1:': '🕺🏻', + ':man_dancing_tone1:': '🕺🏻', + ':male_dancer_tone2:': '🕺🏼', + ':man_dancing_tone2:': '🕺🏼', + ':male_dancer_tone3:': '🕺🏽', + ':man_dancing_tone3:': '🕺🏽', + ':male_dancer_tone4:': '🕺🏾', + ':man_dancing_tone4:': '🕺🏾', + ':male_dancer_tone5:': '🕺🏿', + ':man_dancing_tone5:': '🕺🏿', + ':man_factory_worker:': '👨‍🏭', + ':man_farmer:': '👨‍🌾', + ':man_firefighter:': '👨‍🚒', + ':man_in_manual_wheelchair:': '👨‍🦽', + ':man_in_motorized_wheelchair:': '👨‍🦼', + ':tuxedo_tone1:': '🤵🏻', + ':man_in_tuxedo_tone1:': '🤵🏻', + ':tuxedo_tone2:': '🤵🏼', + ':man_in_tuxedo_tone2:': '🤵🏼', + ':tuxedo_tone3:': '🤵🏽', + ':man_in_tuxedo_tone3:': '🤵🏽', + ':tuxedo_tone4:': '🤵🏾', + ':man_in_tuxedo_tone4:': '🤵🏾', + ':tuxedo_tone5:': '🤵🏿', + ':man_in_tuxedo_tone5:': '🤵🏿', + ':man_mechanic:': '👨‍🔧', + ':man_office_worker:': '👨‍💼', + ':man_red_haired:': '👨‍🦰', + ':man_scientist:': '👨‍🔬', + ':man_singer:': '👨‍🎤', + ':man_student:': '👨‍🎓', + ':man_teacher:': '👨‍🏫', + ':man_technologist:': '👨‍💻', + ':man_tone1:': '👨🏻', + ':man_tone2:': '👨🏼', + ':man_tone3:': '👨🏽', + ':man_tone4:': '👨🏾', + ':man_tone5:': '👨🏿', + ':man_white_haired:': '👨‍🦳', + ':man_with_gua_pi_mao_tone1:': '👲🏻', + ':man_with_chinese_cap_tone1:': '👲🏻', + ':man_with_gua_pi_mao_tone2:': '👲🏼', + ':man_with_chinese_cap_tone2:': '👲🏼', + ':man_with_gua_pi_mao_tone3:': '👲🏽', + ':man_with_chinese_cap_tone3:': '👲🏽', + ':man_with_gua_pi_mao_tone4:': '👲🏾', + ':man_with_chinese_cap_tone4:': '👲🏾', + ':man_with_gua_pi_mao_tone5:': '👲🏿', + ':man_with_chinese_cap_tone5:': '👲🏿', + ':man_with_probing_cane:': '👨‍🦯', + ':men_holding_hands_light_skin_tone:': '👬🏻', + ':men_holding_hands_tone1:': '👬🏻', + ':men_holding_hands_medium_light_skin_tone:': '👬🏼', + ':men_holding_hands_tone2:': '👬🏼', + ':men_holding_hands_medium_skin_tone:': '👬🏽', + ':men_holding_hands_tone3:': '👬🏽', + ':men_holding_hands_medium_dark_skin_tone:': '👬🏾', + ':men_holding_hands_tone4:': '👬🏾', + ':men_holding_hands_dark_skin_tone:': '👬🏿', + ':men_holding_hands_tone5:': '👬🏿', + ':merperson_light_skin_tone:': '🧜🏻', + ':merperson_tone1:': '🧜🏻', + ':merperson_medium_light_skin_tone:': '🧜🏼', + ':merperson_tone2:': '🧜🏼', + ':merperson_medium_skin_tone:': '🧜🏽', + ':merperson_tone3:': '🧜🏽', + ':merperson_medium_dark_skin_tone:': '🧜🏾', + ':merperson_tone4:': '🧜🏾', + ':merperson_dark_skin_tone:': '🧜🏿', + ':merperson_tone5:': '🧜🏿', + ':sign_of_the_horns_tone1:': '🤘🏻', + ':metal_tone1:': '🤘🏻', + ':sign_of_the_horns_tone2:': '🤘🏼', + ':metal_tone2:': '🤘🏼', + ':sign_of_the_horns_tone3:': '🤘🏽', + ':metal_tone3:': '🤘🏽', + ':sign_of_the_horns_tone4:': '🤘🏾', + ':metal_tone4:': '🤘🏾', + ':sign_of_the_horns_tone5:': '🤘🏿', + ':metal_tone5:': '🤘🏿', + ':reversed_hand_with_middle_finger_extended_tone1:': '🖕🏻', + ':middle_finger_tone1:': '🖕🏻', + ':reversed_hand_with_middle_finger_extended_tone2:': '🖕🏼', + ':middle_finger_tone2:': '🖕🏼', + ':reversed_hand_with_middle_finger_extended_tone3:': '🖕🏽', + ':middle_finger_tone3:': '🖕🏽', + ':reversed_hand_with_middle_finger_extended_tone4:': '🖕🏾', + ':middle_finger_tone4:': '🖕🏾', + ':reversed_hand_with_middle_finger_extended_tone5:': '🖕🏿', + ':middle_finger_tone5:': '🖕🏿', + ':mother_christmas_tone1:': '🤶🏻', + ':mrs_claus_tone1:': '🤶🏻', + ':mother_christmas_tone2:': '🤶🏼', + ':mrs_claus_tone2:': '🤶🏼', + ':mother_christmas_tone3:': '🤶🏽', + ':mrs_claus_tone3:': '🤶🏽', + ':mother_christmas_tone4:': '🤶🏾', + ':mrs_claus_tone4:': '🤶🏾', + ':mother_christmas_tone5:': '🤶🏿', + ':mrs_claus_tone5:': '🤶🏿', + ':muscle_tone1:': '💪🏻', + ':muscle_tone2:': '💪🏼', + ':muscle_tone3:': '💪🏽', + ':muscle_tone4:': '💪🏾', + ':muscle_tone5:': '💪🏿', + ':nail_care_tone1:': '💅🏻', + ':nail_care_tone2:': '💅🏼', + ':nail_care_tone3:': '💅🏽', + ':nail_care_tone4:': '💅🏾', + ':nail_care_tone5:': '💅🏿', + ':nose_tone1:': '👃🏻', + ':nose_tone2:': '👃🏼', + ':nose_tone3:': '👃🏽', + ':nose_tone4:': '👃🏾', + ':nose_tone5:': '👃🏿', + ':ok_hand_tone1:': '👌🏻', + ':ok_hand_tone2:': '👌🏼', + ':ok_hand_tone3:': '👌🏽', + ':ok_hand_tone4:': '👌🏾', + ':ok_hand_tone5:': '👌🏿', + ':older_adult_light_skin_tone:': '🧓🏻', + ':older_adult_tone1:': '🧓🏻', + ':older_adult_medium_light_skin_tone:': '🧓🏼', + ':older_adult_tone2:': '🧓🏼', + ':older_adult_medium_skin_tone:': '🧓🏽', + ':older_adult_tone3:': '🧓🏽', + ':older_adult_medium_dark_skin_tone:': '🧓🏾', + ':older_adult_tone4:': '🧓🏾', + ':older_adult_dark_skin_tone:': '🧓🏿', + ':older_adult_tone5:': '🧓🏿', + ':older_man_tone1:': '👴🏻', + ':older_man_tone2:': '👴🏼', + ':older_man_tone3:': '👴🏽', + ':older_man_tone4:': '👴🏾', + ':older_man_tone5:': '👴🏿', + ':grandma_tone1:': '👵🏻', + ':older_woman_tone1:': '👵🏻', + ':grandma_tone2:': '👵🏼', + ':older_woman_tone2:': '👵🏼', + ':grandma_tone3:': '👵🏽', + ':older_woman_tone3:': '👵🏽', + ':grandma_tone4:': '👵🏾', + ':older_woman_tone4:': '👵🏾', + ':grandma_tone5:': '👵🏿', + ':older_woman_tone5:': '👵🏿', + ':open_hands_tone1:': '👐🏻', + ':open_hands_tone2:': '👐🏼', + ':open_hands_tone3:': '👐🏽', + ':open_hands_tone4:': '👐🏾', + ':open_hands_tone5:': '👐🏿', + ':palms_up_together_light_skin_tone:': '🤲🏻', + ':palms_up_together_tone1:': '🤲🏻', + ':palms_up_together_medium_light_skin_tone:': '🤲🏼', + ':palms_up_together_tone2:': '🤲🏼', + ':palms_up_together_medium_skin_tone:': '🤲🏽', + ':palms_up_together_tone3:': '🤲🏽', + ':palms_up_together_medium_dark_skin_tone:': '🤲🏾', + ':palms_up_together_tone4:': '🤲🏾', + ':palms_up_together_dark_skin_tone:': '🤲🏿', + ':palms_up_together_tone5:': '🤲🏿', + ':bicyclist_tone1:': '🚴🏻', + ':person_biking_tone1:': '🚴🏻', + ':bicyclist_tone2:': '🚴🏼', + ':person_biking_tone2:': '🚴🏼', + ':bicyclist_tone3:': '🚴🏽', + ':person_biking_tone3:': '🚴🏽', + ':bicyclist_tone4:': '🚴🏾', + ':person_biking_tone4:': '🚴🏾', + ':bicyclist_tone5:': '🚴🏿', + ':person_biking_tone5:': '🚴🏿', + ':bow_tone1:': '🙇🏻', + ':person_bowing_tone1:': '🙇🏻', + ':bow_tone2:': '🙇🏼', + ':person_bowing_tone2:': '🙇🏼', + ':bow_tone3:': '🙇🏽', + ':person_bowing_tone3:': '🙇🏽', + ':bow_tone4:': '🙇🏾', + ':person_bowing_tone4:': '🙇🏾', + ':bow_tone5:': '🙇🏿', + ':person_bowing_tone5:': '🙇🏿', + ':person_climbing_light_skin_tone:': '🧗🏻', + ':person_climbing_tone1:': '🧗🏻', + ':person_climbing_medium_light_skin_tone:': '🧗🏼', + ':person_climbing_tone2:': '🧗🏼', + ':person_climbing_medium_skin_tone:': '🧗🏽', + ':person_climbing_tone3:': '🧗🏽', + ':person_climbing_medium_dark_skin_tone:': '🧗🏾', + ':person_climbing_tone4:': '🧗🏾', + ':person_climbing_dark_skin_tone:': '🧗🏿', + ':person_climbing_tone5:': '🧗🏿', + ':cartwheel_tone1:': '🤸🏻', + ':person_doing_cartwheel_tone1:': '🤸🏻', + ':cartwheel_tone2:': '🤸🏼', + ':person_doing_cartwheel_tone2:': '🤸🏼', + ':cartwheel_tone3:': '🤸🏽', + ':person_doing_cartwheel_tone3:': '🤸🏽', + ':cartwheel_tone4:': '🤸🏾', + ':person_doing_cartwheel_tone4:': '🤸🏾', + ':cartwheel_tone5:': '🤸🏿', + ':person_doing_cartwheel_tone5:': '🤸🏿', + ':face_palm_tone1:': '🤦🏻', + ':facepalm_tone1:': '🤦🏻', + ':person_facepalming_tone1:': '🤦🏻', + ':face_palm_tone2:': '🤦🏼', + ':facepalm_tone2:': '🤦🏼', + ':person_facepalming_tone2:': '🤦🏼', + ':face_palm_tone3:': '🤦🏽', + ':facepalm_tone3:': '🤦🏽', + ':person_facepalming_tone3:': '🤦🏽', + ':face_palm_tone4:': '🤦🏾', + ':facepalm_tone4:': '🤦🏾', + ':person_facepalming_tone4:': '🤦🏾', + ':face_palm_tone5:': '🤦🏿', + ':facepalm_tone5:': '🤦🏿', + ':person_facepalming_tone5:': '🤦🏿', + ':person_frowning_tone1:': '🙍🏻', + ':person_frowning_tone2:': '🙍🏼', + ':person_frowning_tone3:': '🙍🏽', + ':person_frowning_tone4:': '🙍🏾', + ':person_frowning_tone5:': '🙍🏿', + ':no_good_tone1:': '🙅🏻', + ':person_gesturing_no_tone1:': '🙅🏻', + ':no_good_tone2:': '🙅🏼', + ':person_gesturing_no_tone2:': '🙅🏼', + ':no_good_tone3:': '🙅🏽', + ':person_gesturing_no_tone3:': '🙅🏽', + ':no_good_tone4:': '🙅🏾', + ':person_gesturing_no_tone4:': '🙅🏾', + ':no_good_tone5:': '🙅🏿', + ':person_gesturing_no_tone5:': '🙅🏿', + ':ok_woman_tone1:': '🙆🏻', + ':person_gesturing_ok_tone1:': '🙆🏻', + ':ok_woman_tone2:': '🙆🏼', + ':person_gesturing_ok_tone2:': '🙆🏼', + ':ok_woman_tone3:': '🙆🏽', + ':person_gesturing_ok_tone3:': '🙆🏽', + ':ok_woman_tone4:': '🙆🏾', + ':person_gesturing_ok_tone4:': '🙆🏾', + ':ok_woman_tone5:': '🙆🏿', + ':person_gesturing_ok_tone5:': '🙆🏿', + ':haircut_tone1:': '💇🏻', + ':person_getting_haircut_tone1:': '💇🏻', + ':haircut_tone2:': '💇🏼', + ':person_getting_haircut_tone2:': '💇🏼', + ':haircut_tone3:': '💇🏽', + ':person_getting_haircut_tone3:': '💇🏽', + ':haircut_tone4:': '💇🏾', + ':person_getting_haircut_tone4:': '💇🏾', + ':haircut_tone5:': '💇🏿', + ':person_getting_haircut_tone5:': '💇🏿', + ':massage_tone1:': '💆🏻', + ':person_getting_massage_tone1:': '💆🏻', + ':massage_tone2:': '💆🏼', + ':person_getting_massage_tone2:': '💆🏼', + ':massage_tone3:': '💆🏽', + ':person_getting_massage_tone3:': '💆🏽', + ':massage_tone4:': '💆🏾', + ':person_getting_massage_tone4:': '💆🏾', + ':massage_tone5:': '💆🏿', + ':person_getting_massage_tone5:': '💆🏿', + ':person_golfing_light_skin_tone:': '🏌️🏻', + ':person_golfing_tone1:': '🏌️🏻', + ':person_golfing_medium_light_skin_tone:': '🏌️🏼', + ':person_golfing_tone2:': '🏌️🏼', + ':person_golfing_medium_skin_tone:': '🏌️🏽', + ':person_golfing_tone3:': '🏌️🏽', + ':person_golfing_medium_dark_skin_tone:': '🏌️🏾', + ':person_golfing_tone4:': '🏌️🏾', + ':person_golfing_dark_skin_tone:': '🏌️🏿', + ':person_golfing_tone5:': '🏌️🏿', + ':person_in_bed_light_skin_tone:': '🛌🏻', + ':person_in_bed_tone1:': '🛌🏻', + ':person_in_bed_medium_light_skin_tone:': '🛌🏼', + ':person_in_bed_tone2:': '🛌🏼', + ':person_in_bed_medium_skin_tone:': '🛌🏽', + ':person_in_bed_tone3:': '🛌🏽', + ':person_in_bed_medium_dark_skin_tone:': '🛌🏾', + ':person_in_bed_tone4:': '🛌🏾', + ':person_in_bed_dark_skin_tone:': '🛌🏿', + ':person_in_bed_tone5:': '🛌🏿', + ':person_in_lotus_position_light_skin_tone:': '🧘🏻', + ':person_in_lotus_position_tone1:': '🧘🏻', + ':person_in_lotus_position_medium_light_skin_tone:': '🧘🏼', + ':person_in_lotus_position_tone2:': '🧘🏼', + ':person_in_lotus_position_medium_skin_tone:': '🧘🏽', + ':person_in_lotus_position_tone3:': '🧘🏽', + ':person_in_lotus_position_medium_dark_skin_tone:': '🧘🏾', + ':person_in_lotus_position_tone4:': '🧘🏾', + ':person_in_lotus_position_dark_skin_tone:': '🧘🏿', + ':person_in_lotus_position_tone5:': '🧘🏿', + ':person_in_steamy_room_light_skin_tone:': '🧖🏻', + ':person_in_steamy_room_tone1:': '🧖🏻', + ':person_in_steamy_room_medium_light_skin_tone:': '🧖🏼', + ':person_in_steamy_room_tone2:': '🧖🏼', + ':person_in_steamy_room_medium_skin_tone:': '🧖🏽', + ':person_in_steamy_room_tone3:': '🧖🏽', + ':person_in_steamy_room_medium_dark_skin_tone:': '🧖🏾', + ':person_in_steamy_room_tone4:': '🧖🏾', + ':person_in_steamy_room_dark_skin_tone:': '🧖🏿', + ':person_in_steamy_room_tone5:': '🧖🏿', + ':juggling_tone1:': '🤹🏻', + ':juggler_tone1:': '🤹🏻', + ':person_juggling_tone1:': '🤹🏻', + ':juggling_tone2:': '🤹🏼', + ':juggler_tone2:': '🤹🏼', + ':person_juggling_tone2:': '🤹🏼', + ':juggling_tone3:': '🤹🏽', + ':juggler_tone3:': '🤹🏽', + ':person_juggling_tone3:': '🤹🏽', + ':juggling_tone4:': '🤹🏾', + ':juggler_tone4:': '🤹🏾', + ':person_juggling_tone4:': '🤹🏾', + ':juggling_tone5:': '🤹🏿', + ':juggler_tone5:': '🤹🏿', + ':person_juggling_tone5:': '🤹🏿', + ':person_kneeling_light_skin_tone:': '🧎🏻', + ':person_kneeling_tone1:': '🧎🏻', + ':person_kneeling_medium_light_skin_tone:': '🧎🏼', + ':person_kneeling_tone2:': '🧎🏼', + ':person_kneeling_medium_skin_tone:': '🧎🏽', + ':person_kneeling_tone3:': '🧎🏽', + ':person_kneeling_medium_dark_skin_tone:': '🧎🏾', + ':person_kneeling_tone4:': '🧎🏾', + ':person_kneeling_dark_skin_tone:': '🧎🏿', + ':person_kneeling_tone5:': '🧎🏿', + ':lifter_tone1:': '🏋️🏻', + ':weight_lifter_tone1:': '🏋️🏻', + ':person_lifting_weights_tone1:': '🏋️🏻', + ':lifter_tone2:': '🏋️🏼', + ':weight_lifter_tone2:': '🏋️🏼', + ':person_lifting_weights_tone2:': '🏋️🏼', + ':lifter_tone3:': '🏋️🏽', + ':weight_lifter_tone3:': '🏋️🏽', + ':person_lifting_weights_tone3:': '🏋️🏽', + ':lifter_tone4:': '🏋️🏾', + ':weight_lifter_tone4:': '🏋️🏾', + ':person_lifting_weights_tone4:': '🏋️🏾', + ':lifter_tone5:': '🏋️🏿', + ':weight_lifter_tone5:': '🏋️🏿', + ':person_lifting_weights_tone5:': '🏋️🏿', + ':mountain_bicyclist_tone1:': '🚵🏻', + ':person_mountain_biking_tone1:': '🚵🏻', + ':mountain_bicyclist_tone2:': '🚵🏼', + ':person_mountain_biking_tone2:': '🚵🏼', + ':mountain_bicyclist_tone3:': '🚵🏽', + ':person_mountain_biking_tone3:': '🚵🏽', + ':mountain_bicyclist_tone4:': '🚵🏾', + ':person_mountain_biking_tone4:': '🚵🏾', + ':mountain_bicyclist_tone5:': '🚵🏿', + ':person_mountain_biking_tone5:': '🚵🏿', + ':handball_tone1:': '🤾🏻', + ':person_playing_handball_tone1:': '🤾🏻', + ':handball_tone2:': '🤾🏼', + ':person_playing_handball_tone2:': '🤾🏼', + ':handball_tone3:': '🤾🏽', + ':person_playing_handball_tone3:': '🤾🏽', + ':handball_tone4:': '🤾🏾', + ':person_playing_handball_tone4:': '🤾🏾', + ':handball_tone5:': '🤾🏿', + ':person_playing_handball_tone5:': '🤾🏿', + ':water_polo_tone1:': '🤽🏻', + ':person_playing_water_polo_tone1:': '🤽🏻', + ':water_polo_tone2:': '🤽🏼', + ':person_playing_water_polo_tone2:': '🤽🏼', + ':water_polo_tone3:': '🤽🏽', + ':person_playing_water_polo_tone3:': '🤽🏽', + ':water_polo_tone4:': '🤽🏾', + ':person_playing_water_polo_tone4:': '🤽🏾', + ':water_polo_tone5:': '🤽🏿', + ':person_playing_water_polo_tone5:': '🤽🏿', + ':person_with_pouting_face_tone1:': '🙎🏻', + ':person_pouting_tone1:': '🙎🏻', + ':person_with_pouting_face_tone2:': '🙎🏼', + ':person_pouting_tone2:': '🙎🏼', + ':person_with_pouting_face_tone3:': '🙎🏽', + ':person_pouting_tone3:': '🙎🏽', + ':person_with_pouting_face_tone4:': '🙎🏾', + ':person_pouting_tone4:': '🙎🏾', + ':person_with_pouting_face_tone5:': '🙎🏿', + ':person_pouting_tone5:': '🙎🏿', + ':raising_hand_tone1:': '🙋🏻', + ':person_raising_hand_tone1:': '🙋🏻', + ':raising_hand_tone2:': '🙋🏼', + ':person_raising_hand_tone2:': '🙋🏼', + ':raising_hand_tone3:': '🙋🏽', + ':person_raising_hand_tone3:': '🙋🏽', + ':raising_hand_tone4:': '🙋🏾', + ':person_raising_hand_tone4:': '🙋🏾', + ':raising_hand_tone5:': '🙋🏿', + ':person_raising_hand_tone5:': '🙋🏿', + ':rowboat_tone1:': '🚣🏻', + ':person_rowing_boat_tone1:': '🚣🏻', + ':rowboat_tone2:': '🚣🏼', + ':person_rowing_boat_tone2:': '🚣🏼', + ':rowboat_tone3:': '🚣🏽', + ':person_rowing_boat_tone3:': '🚣🏽', + ':rowboat_tone4:': '🚣🏾', + ':person_rowing_boat_tone4:': '🚣🏾', + ':rowboat_tone5:': '🚣🏿', + ':person_rowing_boat_tone5:': '🚣🏿', + ':runner_tone1:': '🏃🏻', + ':person_running_tone1:': '🏃🏻', + ':runner_tone2:': '🏃🏼', + ':person_running_tone2:': '🏃🏼', + ':runner_tone3:': '🏃🏽', + ':person_running_tone3:': '🏃🏽', + ':runner_tone4:': '🏃🏾', + ':person_running_tone4:': '🏃🏾', + ':runner_tone5:': '🏃🏿', + ':person_running_tone5:': '🏃🏿', + ':shrug_tone1:': '🤷🏻', + ':person_shrugging_tone1:': '🤷🏻', + ':shrug_tone2:': '🤷🏼', + ':person_shrugging_tone2:': '🤷🏼', + ':shrug_tone3:': '🤷🏽', + ':person_shrugging_tone3:': '🤷🏽', + ':shrug_tone4:': '🤷🏾', + ':person_shrugging_tone4:': '🤷🏾', + ':shrug_tone5:': '🤷🏿', + ':person_shrugging_tone5:': '🤷🏿', + ':person_standing_light_skin_tone:': '🧍🏻', + ':person_standing_tone1:': '🧍🏻', + ':person_standing_medium_light_skin_tone:': '🧍🏼', + ':person_standing_tone2:': '🧍🏼', + ':person_standing_medium_skin_tone:': '🧍🏽', + ':person_standing_tone3:': '🧍🏽', + ':person_standing_medium_dark_skin_tone:': '🧍🏾', + ':person_standing_tone4:': '🧍🏾', + ':person_standing_dark_skin_tone:': '🧍🏿', + ':person_standing_tone5:': '🧍🏿', + ':surfer_tone1:': '🏄🏻', + ':person_surfing_tone1:': '🏄🏻', + ':surfer_tone2:': '🏄🏼', + ':person_surfing_tone2:': '🏄🏼', + ':surfer_tone3:': '🏄🏽', + ':person_surfing_tone3:': '🏄🏽', + ':surfer_tone4:': '🏄🏾', + ':person_surfing_tone4:': '🏄🏾', + ':surfer_tone5:': '🏄🏿', + ':person_surfing_tone5:': '🏄🏿', + ':swimmer_tone1:': '🏊🏻', + ':person_swimming_tone1:': '🏊🏻', + ':swimmer_tone2:': '🏊🏼', + ':person_swimming_tone2:': '🏊🏼', + ':swimmer_tone3:': '🏊🏽', + ':person_swimming_tone3:': '🏊🏽', + ':swimmer_tone4:': '🏊🏾', + ':person_swimming_tone4:': '🏊🏾', + ':swimmer_tone5:': '🏊🏿', + ':person_swimming_tone5:': '🏊🏿', + ':information_desk_person_tone1:': '💁🏻', + ':person_tipping_hand_tone1:': '💁🏻', + ':information_desk_person_tone2:': '💁🏼', + ':person_tipping_hand_tone2:': '💁🏼', + ':information_desk_person_tone3:': '💁🏽', + ':person_tipping_hand_tone3:': '💁🏽', + ':information_desk_person_tone4:': '💁🏾', + ':person_tipping_hand_tone4:': '💁🏾', + ':information_desk_person_tone5:': '💁🏿', + ':person_tipping_hand_tone5:': '💁🏿', + ':walking_tone1:': '🚶🏻', + ':person_walking_tone1:': '🚶🏻', + ':walking_tone2:': '🚶🏼', + ':person_walking_tone2:': '🚶🏼', + ':walking_tone3:': '🚶🏽', + ':person_walking_tone3:': '🚶🏽', + ':walking_tone4:': '🚶🏾', + ':person_walking_tone4:': '🚶🏾', + ':walking_tone5:': '🚶🏿', + ':person_walking_tone5:': '🚶🏿', + ':man_with_turban_tone1:': '👳🏻', + ':person_wearing_turban_tone1:': '👳🏻', + ':man_with_turban_tone2:': '👳🏼', + ':person_wearing_turban_tone2:': '👳🏼', + ':man_with_turban_tone3:': '👳🏽', + ':person_wearing_turban_tone3:': '👳🏽', + ':man_with_turban_tone4:': '👳🏾', + ':person_wearing_turban_tone4:': '👳🏾', + ':man_with_turban_tone5:': '👳🏿', + ':person_wearing_turban_tone5:': '👳🏿', + ':pinching_hand_light_skin_tone:': '🤏🏻', + ':pinching_hand_tone1:': '🤏🏻', + ':pinching_hand_medium_light_skin_tone:': '🤏🏼', + ':pinching_hand_tone2:': '🤏🏼', + ':pinching_hand_medium_skin_tone:': '🤏🏽', + ':pinching_hand_tone3:': '🤏🏽', + ':pinching_hand_medium_dark_skin_tone:': '🤏🏾', + ':pinching_hand_tone4:': '🤏🏾', + ':pinching_hand_dark_skin_tone:': '🤏🏿', + ':pinching_hand_tone5:': '🤏🏿', + ':point_down_tone1:': '👇🏻', + ':point_down_tone2:': '👇🏼', + ':point_down_tone3:': '👇🏽', + ':point_down_tone4:': '👇🏾', + ':point_down_tone5:': '👇🏿', + ':point_left_tone1:': '👈🏻', + ':point_left_tone2:': '👈🏼', + ':point_left_tone3:': '👈🏽', + ':point_left_tone4:': '👈🏾', + ':point_left_tone5:': '👈🏿', + ':point_right_tone1:': '👉🏻', + ':point_right_tone2:': '👉🏼', + ':point_right_tone3:': '👉🏽', + ':point_right_tone4:': '👉🏾', + ':point_right_tone5:': '👉🏿', + ':point_up_2_tone1:': '👆🏻', + ':point_up_2_tone2:': '👆🏼', + ':point_up_2_tone3:': '👆🏽', + ':point_up_2_tone4:': '👆🏾', + ':point_up_2_tone5:': '👆🏿', + ':cop_tone1:': '👮🏻', + ':police_officer_tone1:': '👮🏻', + ':cop_tone2:': '👮🏼', + ':police_officer_tone2:': '👮🏼', + ':cop_tone3:': '👮🏽', + ':police_officer_tone3:': '👮🏽', + ':cop_tone4:': '👮🏾', + ':police_officer_tone4:': '👮🏾', + ':cop_tone5:': '👮🏿', + ':police_officer_tone5:': '👮🏿', + ':pray_tone1:': '🙏🏻', + ':pray_tone2:': '🙏🏼', + ':pray_tone3:': '🙏🏽', + ':pray_tone4:': '🙏🏾', + ':pray_tone5:': '🙏🏿', + ':expecting_woman_tone1:': '🤰🏻', + ':pregnant_woman_tone1:': '🤰🏻', + ':expecting_woman_tone2:': '🤰🏼', + ':pregnant_woman_tone2:': '🤰🏼', + ':expecting_woman_tone3:': '🤰🏽', + ':pregnant_woman_tone3:': '🤰🏽', + ':expecting_woman_tone4:': '🤰🏾', + ':pregnant_woman_tone4:': '🤰🏾', + ':expecting_woman_tone5:': '🤰🏿', + ':pregnant_woman_tone5:': '🤰🏿', + ':prince_tone1:': '🤴🏻', + ':prince_tone2:': '🤴🏼', + ':prince_tone3:': '🤴🏽', + ':prince_tone4:': '🤴🏾', + ':prince_tone5:': '🤴🏿', + ':princess_tone1:': '👸🏻', + ':princess_tone2:': '👸🏼', + ':princess_tone3:': '👸🏽', + ':princess_tone4:': '👸🏾', + ':princess_tone5:': '👸🏿', + ':punch_tone1:': '👊🏻', + ':punch_tone2:': '👊🏼', + ':punch_tone3:': '👊🏽', + ':punch_tone4:': '👊🏾', + ':punch_tone5:': '👊🏿', + ':gay_pride_flag:': '🏳️‍🌈', + ':rainbow_flag:': '🏳️‍🌈', + ':back_of_hand_tone1:': '🤚🏻', + ':raised_back_of_hand_tone1:': '🤚🏻', + ':back_of_hand_tone2:': '🤚🏼', + ':raised_back_of_hand_tone2:': '🤚🏼', + ':back_of_hand_tone3:': '🤚🏽', + ':raised_back_of_hand_tone3:': '🤚🏽', + ':back_of_hand_tone4:': '🤚🏾', + ':raised_back_of_hand_tone4:': '🤚🏾', + ':back_of_hand_tone5:': '🤚🏿', + ':raised_back_of_hand_tone5:': '🤚🏿', + ':raised_hands_tone1:': '🙌🏻', + ':raised_hands_tone2:': '🙌🏼', + ':raised_hands_tone3:': '🙌🏽', + ':raised_hands_tone4:': '🙌🏾', + ':raised_hands_tone5:': '🙌🏿', + ':right_fist_tone1:': '🤜🏻', + ':right_facing_fist_tone1:': '🤜🏻', + ':right_fist_tone2:': '🤜🏼', + ':right_facing_fist_tone2:': '🤜🏼', + ':right_fist_tone3:': '🤜🏽', + ':right_facing_fist_tone3:': '🤜🏽', + ':right_fist_tone4:': '🤜🏾', + ':right_facing_fist_tone4:': '🤜🏾', + ':right_fist_tone5:': '🤜🏿', + ':right_facing_fist_tone5:': '🤜🏿', + ':santa_tone1:': '🎅🏻', + ':santa_tone2:': '🎅🏼', + ':santa_tone3:': '🎅🏽', + ':santa_tone4:': '🎅🏾', + ':santa_tone5:': '🎅🏿', + ':selfie_tone1:': '🤳🏻', + ':selfie_tone2:': '🤳🏼', + ':selfie_tone3:': '🤳🏽', + ':selfie_tone4:': '🤳🏾', + ':selfie_tone5:': '🤳🏿', + ':service_dog:': '🐕‍🦺', + ':snowboarder_light_skin_tone:': '🏂🏻', + ':snowboarder_tone1:': '🏂🏻', + ':snowboarder_medium_light_skin_tone:': '🏂🏼', + ':snowboarder_tone2:': '🏂🏼', + ':snowboarder_medium_skin_tone:': '🏂🏽', + ':snowboarder_tone3:': '🏂🏽', + ':snowboarder_medium_dark_skin_tone:': '🏂🏾', + ':snowboarder_tone4:': '🏂🏾', + ':snowboarder_dark_skin_tone:': '🏂🏿', + ':snowboarder_tone5:': '🏂🏿', + ':superhero_light_skin_tone:': '🦸🏻', + ':superhero_tone1:': '🦸🏻', + ':superhero_medium_light_skin_tone:': '🦸🏼', + ':superhero_tone2:': '🦸🏼', + ':superhero_medium_skin_tone:': '🦸🏽', + ':superhero_tone3:': '🦸🏽', + ':superhero_medium_dark_skin_tone:': '🦸🏾', + ':superhero_tone4:': '🦸🏾', + ':superhero_dark_skin_tone:': '🦸🏿', + ':superhero_tone5:': '🦸🏿', + ':supervillain_light_skin_tone:': '🦹🏻', + ':supervillain_tone1:': '🦹🏻', + ':supervillain_medium_light_skin_tone:': '🦹🏼', + ':supervillain_tone2:': '🦹🏼', + ':supervillain_medium_skin_tone:': '🦹🏽', + ':supervillain_tone3:': '🦹🏽', + ':supervillain_medium_dark_skin_tone:': '🦹🏾', + ':supervillain_tone4:': '🦹🏾', + ':supervillain_dark_skin_tone:': '🦹🏿', + ':supervillain_tone5:': '🦹🏿', + ':-1_tone1:': '👎🏻', + ':thumbdown_tone1:': '👎🏻', + ':thumbsdown_tone1:': '👎🏻', + ':-1_tone2:': '👎🏼', + ':thumbdown_tone2:': '👎🏼', + ':thumbsdown_tone2:': '👎🏼', + ':-1_tone3:': '👎🏽', + ':thumbdown_tone3:': '👎🏽', + ':thumbsdown_tone3:': '👎🏽', + ':-1_tone4:': '👎🏾', + ':thumbdown_tone4:': '👎🏾', + ':thumbsdown_tone4:': '👎🏾', + ':-1_tone5:': '👎🏿', + ':thumbdown_tone5:': '👎🏿', + ':thumbsdown_tone5:': '👎🏿', + ':+1_tone1:': '👍🏻', + ':thumbup_tone1:': '👍🏻', + ':thumbsup_tone1:': '👍🏻', + ':+1_tone2:': '👍🏼', + ':thumbup_tone2:': '👍🏼', + ':thumbsup_tone2:': '👍🏼', + ':+1_tone3:': '👍🏽', + ':thumbup_tone3:': '👍🏽', + ':thumbsup_tone3:': '👍🏽', + ':+1_tone4:': '👍🏾', + ':thumbup_tone4:': '👍🏾', + ':thumbsup_tone4:': '👍🏾', + ':+1_tone5:': '👍🏿', + ':thumbup_tone5:': '👍🏿', + ':thumbsup_tone5:': '👍🏿', + ':united_nations:': '🇺🇳', + ':vampire_light_skin_tone:': '🧛🏻', + ':vampire_tone1:': '🧛🏻', + ':vampire_medium_light_skin_tone:': '🧛🏼', + ':vampire_tone2:': '🧛🏼', + ':vampire_medium_skin_tone:': '🧛🏽', + ':vampire_tone3:': '🧛🏽', + ':vampire_medium_dark_skin_tone:': '🧛🏾', + ':vampire_tone4:': '🧛🏾', + ':vampire_dark_skin_tone:': '🧛🏿', + ':vampire_tone5:': '🧛🏿', + ':raised_hand_with_part_between_middle_and_ring_fingers_tone1:': '🖖🏻', + ':vulcan_tone1:': '🖖🏻', + ':raised_hand_with_part_between_middle_and_ring_fingers_tone2:': '🖖🏼', + ':vulcan_tone2:': '🖖🏼', + ':raised_hand_with_part_between_middle_and_ring_fingers_tone3:': '🖖🏽', + ':vulcan_tone3:': '🖖🏽', + ':raised_hand_with_part_between_middle_and_ring_fingers_tone4:': '🖖🏾', + ':vulcan_tone4:': '🖖🏾', + ':raised_hand_with_part_between_middle_and_ring_fingers_tone5:': '🖖🏿', + ':vulcan_tone5:': '🖖🏿', + ':wave_tone1:': '👋🏻', + ':wave_tone2:': '👋🏼', + ':wave_tone3:': '👋🏽', + ':wave_tone4:': '👋🏾', + ':wave_tone5:': '👋🏿', + ':woman_and_man_holding_hands_light_skin_tone:': '👫🏻', + ':woman_and_man_holding_hands_tone1:': '👫🏻', + ':woman_and_man_holding_hands_medium_light_skin_tone:': '👫🏼', + ':woman_and_man_holding_hands_tone2:': '👫🏼', + ':woman_and_man_holding_hands_medium_skin_tone:': '👫🏽', + ':woman_and_man_holding_hands_tone3:': '👫🏽', + ':woman_and_man_holding_hands_medium_dark_skin_tone:': '👫🏾', + ':woman_and_man_holding_hands_tone4:': '👫🏾', + ':woman_and_man_holding_hands_dark_skin_tone:': '👫🏿', + ':woman_and_man_holding_hands_tone5:': '👫🏿', + ':woman_artist:': '👩‍🎨', + ':woman_astronaut:': '👩‍🚀', + ':woman_bald:': '👩‍🦲', + ':woman_cook:': '👩‍🍳', + ':woman_curly_haired:': '👩‍🦱', + ':woman_factory_worker:': '👩‍🏭', + ':woman_farmer:': '👩‍🌾', + ':woman_firefighter:': '👩‍🚒', + ':woman_in_manual_wheelchair:': '👩‍🦽', + ':woman_in_motorized_wheelchair:': '👩‍🦼', + ':woman_mechanic:': '👩‍🔧', + ':woman_office_worker:': '👩‍💼', + ':woman_red_haired:': '👩‍🦰', + ':woman_scientist:': '👩‍🔬', + ':woman_singer:': '👩‍🎤', + ':woman_student:': '👩‍🎓', + ':woman_teacher:': '👩‍🏫', + ':woman_technologist:': '👩‍💻', + ':woman_tone1:': '👩🏻', + ':woman_tone2:': '👩🏼', + ':woman_tone3:': '👩🏽', + ':woman_tone4:': '👩🏾', + ':woman_tone5:': '👩🏿', + ':woman_white_haired:': '👩‍🦳', + ':woman_with_headscarf_light_skin_tone:': '🧕🏻', + ':woman_with_headscarf_tone1:': '🧕🏻', + ':woman_with_headscarf_medium_light_skin_tone:': '🧕🏼', + ':woman_with_headscarf_tone2:': '🧕🏼', + ':woman_with_headscarf_medium_skin_tone:': '🧕🏽', + ':woman_with_headscarf_tone3:': '🧕🏽', + ':woman_with_headscarf_medium_dark_skin_tone:': '🧕🏾', + ':woman_with_headscarf_tone4:': '🧕🏾', + ':woman_with_headscarf_dark_skin_tone:': '🧕🏿', + ':woman_with_headscarf_tone5:': '🧕🏿', + ':woman_with_probing_cane:': '👩‍🦯', + ':women_holding_hands_light_skin_tone:': '👭🏻', + ':women_holding_hands_tone1:': '👭🏻', + ':women_holding_hands_medium_light_skin_tone:': '👭🏼', + ':women_holding_hands_tone2:': '👭🏼', + ':women_holding_hands_medium_skin_tone:': '👭🏽', + ':women_holding_hands_tone3:': '👭🏽', + ':women_holding_hands_medium_dark_skin_tone:': '👭🏾', + ':women_holding_hands_tone4:': '👭🏾', + ':women_holding_hands_dark_skin_tone:': '👭🏿', + ':women_holding_hands_tone5:': '👭🏿', + ':blond-haired_man:': '👱‍♂️', + ':blond-haired_woman:': '👱‍♀️', + ':deaf_man:': '🧏‍♂️', + ':deaf_woman:': '🧏‍♀️', + ':fist_tone1:': '✊🏻', + ':fist_tone2:': '✊🏼', + ':fist_tone3:': '✊🏽', + ':fist_tone4:': '✊🏾', + ':fist_tone5:': '✊🏿', + ':man_biking:': '🚴‍♂️', + ':man_bowing:': '🙇‍♂️', + ':man_cartwheeling:': '🤸‍♂️', + ':man_climbing:': '🧗‍♂️', + ':man_construction_worker:': '👷‍♂️', + ':man_detective:': '🕵️‍♂️', + ':man_elf:': '🧝‍♂️', + ':man_facepalming:': '🤦‍♂️', + ':man_fairy:': '🧚‍♂️', + ':man_frowning:': '🙍‍♂️', + ':man_genie:': '🧞‍♂️', + ':man_gesturing_no:': '🙅‍♂️', + ':man_gesturing_ok:': '🙆‍♂️', + ':man_getting_face_massage:': '💆‍♂️', + ':man_getting_haircut:': '💇‍♂️', + ':man_golfing:': '🏌️‍♂️', + ':man_guard:': '💂‍♂️', + ':man_health_worker:': '👨‍⚕️', + ':man_in_lotus_position:': '🧘‍♂️', + ':man_in_steamy_room:': '🧖‍♂️', + ':man_judge:': '👨‍⚖️', + ':man_juggling:': '🤹‍♂️', + ':man_kneeling:': '🧎‍♂️', + ':man_lifting_weights:': '🏋️‍♂️', + ':man_mage:': '🧙‍♂️', + ':man_mountain_biking:': '🚵‍♂️', + ':man_pilot:': '👨‍✈️', + ':man_playing_handball:': '🤾‍♂️', + ':man_playing_water_polo:': '🤽‍♂️', + ':man_police_officer:': '👮‍♂️', + ':man_pouting:': '🙎‍♂️', + ':man_raising_hand:': '🙋‍♂️', + ':man_rowing_boat:': '🚣‍♂️', + ':man_running:': '🏃‍♂️', + ':man_shrugging:': '🤷‍♂️', + ':man_standing:': '🧍‍♂️', + ':man_superhero:': '🦸‍♂️', + ':man_supervillain:': '🦹‍♂️', + ':man_surfing:': '🏄‍♂️', + ':man_swimming:': '🏊‍♂️', + ':man_tipping_hand:': '💁‍♂️', + ':man_vampire:': '🧛‍♂️', + ':man_walking:': '🚶‍♂️', + ':man_wearing_turban:': '👳‍♂️', + ':man_zombie:': '🧟‍♂️', + ':men_with_bunny_ears_partying:': '👯‍♂️', + ':men_wrestling:': '🤼♂️', + ':mermaid:': '🧜‍♀️', + ':merman:': '🧜‍♂️', + ':basketball_player_tone1:': '⛹️🏻', + ':person_with_ball_tone1:': '⛹️🏻', + ':person_bouncing_ball_tone1:': '⛹️🏻', + ':basketball_player_tone2:': '⛹️🏼', + ':person_with_ball_tone2:': '⛹️🏼', + ':person_bouncing_ball_tone2:': '⛹️🏼', + ':basketball_player_tone3:': '⛹️🏽', + ':person_with_ball_tone3:': '⛹️🏽', + ':person_bouncing_ball_tone3:': '⛹️🏽', + ':basketball_player_tone4:': '⛹️🏾', + ':person_with_ball_tone4:': '⛹️🏾', + ':person_bouncing_ball_tone4:': '⛹️🏾', + ':basketball_player_tone5:': '⛹️🏿', + ':person_with_ball_tone5:': '⛹️🏿', + ':person_bouncing_ball_tone5:': '⛹️🏿', + ':pirate_flag:': '🏴‍☠️', + ':point_up_tone1:': '☝️🏻', + ':point_up_tone2:': '☝️🏼', + ':point_up_tone3:': '☝️🏽', + ':point_up_tone4:': '☝️🏾', + ':point_up_tone5:': '☝️🏿', + ':raised_hand_tone1:': '✋🏻', + ':raised_hand_tone2:': '✋🏼', + ':raised_hand_tone3:': '✋🏽', + ':raised_hand_tone4:': '✋🏾', + ':raised_hand_tone5:': '✋🏿', + ':v_tone1:': '✌️🏻', + ':v_tone2:': '✌️🏼', + ':v_tone3:': '✌️🏽', + ':v_tone4:': '✌️🏾', + ':v_tone5:': '✌️🏿', + ':woman_biking:': '🚴‍♀️', + ':woman_bowing:': '🙇‍♀️', + ':woman_cartwheeling:': '🤸‍♀️', + ':woman_climbing:': '🧗‍♀️', + ':woman_construction_worker:': '👷‍♀️', + ':woman_detective:': '🕵️‍♀️', + ':woman_elf:': '🧝‍♀️', + ':woman_facepalming:': '🤦‍♀️', + ':woman_fairy:': '🧚‍♀️', + ':woman_frowning:': '🙍‍♀️', + ':woman_genie:': '🧞‍♀️', + ':woman_gesturing_no:': '🙅‍♀️', + ':woman_gesturing_ok:': '🙆‍♀️', + ':woman_getting_face_massage:': '💆‍♀️', + ':woman_getting_haircut:': '💇‍♀️', + ':woman_golfing:': '🏌️‍♀️', + ':woman_guard:': '💂‍♀️', + ':woman_health_worker:': '👩‍⚕️', + ':woman_in_lotus_position:': '🧘‍♀️', + ':woman_in_steamy_room:': '🧖‍♀️', + ':woman_judge:': '👩‍⚖️', + ':woman_juggling:': '🤹‍♀️', + ':woman_kneeling:': '🧎‍♀️', + ':woman_lifting_weights:': '🏋️‍♀️', + ':woman_mage:': '🧙‍♀️', + ':woman_mountain_biking:': '🚵‍♀️', + ':woman_pilot:': '👩‍✈️', + ':woman_playing_handball:': '🤾‍♀️', + ':woman_playing_water_polo:': '🤽‍♀️', + ':woman_police_officer:': '👮‍♀️', + ':woman_pouting:': '🙎‍♀️', + ':woman_raising_hand:': '🙋‍♀️', + ':woman_rowing_boat:': '🚣‍♀️', + ':woman_running:': '🏃‍♀️', + ':woman_shrugging:': '🤷‍♀️', + ':woman_standing:': '🧍‍♀️', + ':woman_superhero:': '🦸‍♀️', + ':woman_supervillain:': '🦹‍♀️', + ':woman_surfing:': '🏄‍♀️', + ':woman_swimming:': '🏊‍♀️', + ':woman_tipping_hand:': '💁‍♀️', + ':woman_vampire:': '🧛‍♀️', + ':woman_walking:': '🚶‍♀️', + ':woman_wearing_turban:': '👳‍♀️', + ':woman_zombie:': '🧟‍♀️', + ':women_with_bunny_ears_partying:': '👯‍♀️', + ':women_wrestling:': '🤼♀️', + ':writing_hand_tone1:': '✍️🏻', + ':writing_hand_tone2:': '✍️🏼', + ':writing_hand_tone3:': '✍️🏽', + ':writing_hand_tone4:': '✍️🏾', + ':writing_hand_tone5:': '✍️🏿', + ':keycap_asterisk:': '*️⃣', + ':asterisk:': '*️⃣', + ':eight:': '8️⃣', + ':five:': '5️⃣', + ':four:': '4️⃣', + ':hash:': '#️⃣', + ':man_bouncing_ball:': '⛹️‍♂️', + ':nine:': '9️⃣', + ':one:': '1️⃣', + ':seven:': '7️⃣', + ':six:': '6️⃣', + ':three:': '3️⃣', + ':two:': '2️⃣', + ':woman_bouncing_ball:': '⛹️‍♀️', + ':zero:': '0️⃣', + ':100:': '💯', + ':1234:': '🔢', + ':8ball:': '🎱', + ':a:': '🅰️', + ':ab:': '🆎', + ':abacus:': '🧮', + ':abc:': '🔤', + ':abcd:': '🔡', + ':accept:': '🉑', + ':adhesive_bandage:': '🩹', + ':adult:': '🧑', + ':aerial_tramway:': '🚡', + ':airplane_arriving:': '🛬', + ':airplane_departure:': '🛫', + ':small_airplane:': '🛩️', + ':airplane_small:': '🛩️', + ':alien:': '👽', + ':ambulance:': '🚑', + ':amphora:': '🏺', + ':angel:': '👼', + ':anger:': '💢', + ':right_anger_bubble:': '🗯️', + ':anger_right:': '🗯️', + ':angry:': '😠', + ':anguished:': '😧', + ':ant:': '🐜', + ':apple:': '🍎', + ':arrow_down_small:': '🔽', + ':arrow_up_small:': '🔼', + ':arrows_clockwise:': '🔃', + ':arrows_counterclockwise:': '🔄', + ':art:': '🎨', + ':articulated_lorry:': '🚛', + ':astonished:': '😲', + ':athletic_shoe:': '👟', + ':atm:': '🏧', + ':auto_rickshaw:': '🛺', + ':avocado:': '🥑', + ':axe:': '🪓', + ':b:': '🅱️', + ':baby:': '👶', + ':baby_bottle:': '🍼', + ':baby_chick:': '🐤', + ':baby_symbol:': '🚼', + ':back:': '🔙', + ':bacon:': '🥓', + ':badger:': '🦡', + ':badminton:': '🏸', + ':bagel:': '🥯', + ':baggage_claim:': '🛄', + ':bald:': '🦲', + ':ballet_shoes:': '🩰', + ':balloon:': '🎈', + ':ballot_box_with_ballot:': '🗳️', + ':ballot_box:': '🗳️', + ':bamboo:': '🎍', + ':banana:': '🍌', + ':banjo:': '🪕', + ':bank:': '🏦', + ':bar_chart:': '📊', + ':barber:': '💈', + ':basket:': '🧺', + ':basketball:': '🏀', + ':bat:': '🦇', + ':bath:': '🛀', + ':bathtub:': '🛁', + ':battery:': '🔋', + ':beach_with_umbrella:': '🏖️', + ':beach:': '🏖️', + ':bear:': '🐻', + ':bearded_person:': '🧔', + ':bed:': '🛏️', + ':bee:': '🐝', + ':beer:': '🍺', + ':beers:': '🍻', + ':beetle:': '🐞', + ':beginner:': '🔰', + ':bell:': '🔔', + ':bellhop_bell:': '🛎️', + ':bellhop:': '🛎️', + ':bento:': '🍱', + ':beverage_box:': '🧃', + ':bike:': '🚲', + ':bikini:': '👙', + ':billed_cap:': '🧢', + ':bird:': '🐦', + ':birthday:': '🎂', + ':black_heart:': '🖤', + ':black_joker:': '🃏', + ':black_square_button:': '🔲', + ':person_with_blond_hair:': '👱', + ':blond_haired_person:': '👱', + ':blossom:': '🌼', + ':blowfish:': '🐡', + ':blue_book:': '📘', + ':blue_car:': '🚙', + ':blue_circle:': '🔵', + ':blue_heart:': '💙', + ':blue_square:': '🟦', + ':blush:': '😊', + ':boar:': '🐗', + ':bomb:': '💣', + ':bone:': '🦴', + ':book:': '📖', + ':bookmark:': '🔖', + ':bookmark_tabs:': '📑', + ':books:': '📚', + ':boom:': '💥', + ':boot:': '👢', + ':bouquet:': '💐', + ':archery:': '🏹', + ':bow_and_arrow:': '🏹', + ':bowl_with_spoon:': '🥣', + ':bowling:': '🎳', + ':boxing_gloves:': '🥊', + ':boxing_glove:': '🥊', + ':boy:': '👦', + ':brain:': '🧠', + ':bread:': '🍞', + ':breast_feeding:': '🤱', + ':bricks:': '🧱', + ':bride_with_veil:': '👰', + ':bridge_at_night:': '🌉', + ':briefcase:': '💼', + ':briefs:': '🩲', + ':broccoli:': '🥦', + ':broken_heart:': '💔', + ':broom:': '🧹', + ':brown_circle:': '🟤', + ':brown_heart:': '🤎', + ':brown_square:': '🟫', + ':bug:': '🐛', + ':bulb:': '💡', + ':bullettrain_front:': '🚅', + ':bullettrain_side:': '🚄', + ':burrito:': '🌯', + ':bus:': '🚌', + ':busstop:': '🚏', + ':bust_in_silhouette:': '👤', + ':busts_in_silhouette:': '👥', + ':butter:': '🧈', + ':butterfly:': '🦋', + ':cactus:': '🌵', + ':cake:': '🍰', + ':calendar:': '📆', + ':spiral_calendar_pad:': '🗓️', + ':calendar_spiral:': '🗓️', + ':call_me_hand:': '🤙', + ':call_me:': '🤙', + ':calling:': '📲', + ':camel:': '🐫', + ':camera:': '📷', + ':camera_with_flash:': '📸', + ':camping:': '🏕️', + ':candle:': '🕯️', + ':candy:': '🍬', + ':canned_food:': '🥫', + ':kayak:': '🛶', + ':canoe:': '🛶', + ':capital_abcd:': '🔠', + ':card_file_box:': '🗃️', + ':card_box:': '🗃️', + ':card_index:': '📇', + ':carousel_horse:': '🎠', + ':carrot:': '🥕', + ':cat2:': '🐈', + ':cat:': '🐱', + ':cd:': '💿', + ':chair:': '🪑', + ':bottle_with_popping_cork:': '🍾', + ':champagne:': '🍾', + ':clinking_glass:': '🥂', + ':champagne_glass:': '🥂', + ':chart:': '💹', + ':chart_with_downwards_trend:': '📉', + ':chart_with_upwards_trend:': '📈', + ':checkered_flag:': '🏁', + ':cheese_wedge:': '🧀', + ':cheese:': '🧀', + ':cherries:': '🍒', + ':cherry_blossom:': '🌸', + ':chestnut:': '🌰', + ':chicken:': '🐔', + ':child:': '🧒', + ':children_crossing:': '🚸', + ':chipmunk:': '🐿️', + ':chocolate_bar:': '🍫', + ':chopsticks:': '🥢', + ':christmas_tree:': '🎄', + ':cinema:': '🎦', + ':circus_tent:': '🎪', + ':city_dusk:': '🌆', + ':city_sunrise:': '🌇', + ':city_sunset:': '🌇', + ':cityscape:': '🏙️', + ':cl:': '🆑', + ':clap:': '👏', + ':clapper:': '🎬', + ':classical_building:': '🏛️', + ':clipboard:': '📋', + ':clock1030:': '🕥', + ':clock10:': '🕙', + ':clock1130:': '🕦', + ':clock11:': '🕚', + ':clock1230:': '🕧', + ':clock12:': '🕛', + ':clock130:': '🕜', + ':clock1:': '🕐', + ':clock230:': '🕝', + ':clock2:': '🕑', + ':clock330:': '🕞', + ':clock3:': '🕒', + ':clock430:': '🕟', + ':clock4:': '🕓', + ':clock530:': '🕠', + ':clock5:': '🕔', + ':clock630:': '🕡', + ':clock6:': '🕕', + ':clock730:': '🕢', + ':clock7:': '🕖', + ':clock830:': '🕣', + ':clock8:': '🕗', + ':clock930:': '🕤', + ':clock9:': '🕘', + ':mantlepiece_clock:': '🕰️', + ':clock:': '🕰️', + ':closed_book:': '📕', + ':closed_lock_with_key:': '🔐', + ':closed_umbrella:': '🌂', + ':cloud_with_lightning:': '🌩️', + ':cloud_lightning:': '🌩️', + ':cloud_with_rain:': '🌧️', + ':cloud_rain:': '🌧️', + ':cloud_with_snow:': '🌨️', + ':cloud_snow:': '🌨️', + ':cloud_with_tornado:': '🌪️', + ':cloud_tornado:': '🌪️', + ':clown_face:': '🤡', + ':clown:': '🤡', + ':coat:': '🧥', + ':cocktail:': '🍸', + ':coconut:': '🥥', + ':cold_face:': '🥶', + ':cold_sweat:': '😰', + ':compass:': '🧭', + ':compression:': '🗜️', + ':computer:': '💻', + ':confetti_ball:': '🎊', + ':confounded:': '😖', + ':confused:': '😕', + ':construction:': '🚧', + ':building_construction:': '🏗️', + ':construction_site:': '🏗️', + ':construction_worker:': '👷', + ':control_knobs:': '🎛️', + ':convenience_store:': '🏪', + ':cookie:': '🍪', + ':cooking:': '🍳', + ':cool:': '🆒', + ':corn:': '🌽', + ':couch_and_lamp:': '🛋️', + ':couch:': '🛋️', + ':couple:': '👫', + ':couple_with_heart:': '💑', + ':couplekiss:': '💏', + ':cow2:': '🐄', + ':cow:': '🐮', + ':face_with_cowboy_hat:': '🤠', + ':cowboy:': '🤠', + ':crab:': '🦀', + ':lower_left_crayon:': '🖍️', + ':crayon:': '🖍️', + ':credit_card:': '💳', + ':crescent_moon:': '🌙', + ':cricket:': '🦗', + ':cricket_bat_ball:': '🏏', + ':cricket_game:': '🏏', + ':crocodile:': '🐊', + ':croissant:': '🥐', + ':crossed_flags:': '🎌', + ':crown:': '👑', + ':passenger_ship:': '🛳️', + ':cruise_ship:': '🛳️', + ':cry:': '😢', + ':crying_cat_face:': '😿', + ':crystal_ball:': '🔮', + ':cucumber:': '🥒', + ':cup_with_straw:': '🥤', + ':cupcake:': '🧁', + ':cupid:': '💘', + ':curling_stone:': '🥌', + ':curly_haired:': '🦱', + ':currency_exchange:': '💱', + ':curry:': '🍛', + ':pudding:': '🍮', + ':flan:': '🍮', + ':custard:': '🍮', + ':customs:': '🛃', + ':cut_of_meat:': '🥩', + ':cyclone:': '🌀', + ':dagger_knife:': '🗡️', + ':dagger:': '🗡️', + ':dancer:': '💃', + ':dango:': '🍡', + ':dark_sunglasses:': '🕶️', + ':dart:': '🎯', + ':dash:': '💨', + ':date:': '📅', + ':deaf_person:': '🧏', + ':deciduous_tree:': '🌳', + ':deer:': '🦌', + ':department_store:': '🏬', + ':desert:': '🏜️', + ':desktop_computer:': '🖥️', + ':desktop:': '🖥️', + ':spy:': '🕵️', + ':sleuth_or_spy:': '🕵️', + ':detective:': '🕵️', + ':diamond_shape_with_a_dot_inside:': '💠', + ':disappointed:': '😞', + ':disappointed_relieved:': '😥', + ':card_index_dividers:': '🗂️', + ':dividers:': '🗂️', + ':diving_mask:': '🤿', + ':diya_lamp:': '🪔', + ':dizzy:': '💫', + ':dizzy_face:': '😵', + ':dna:': '🧬', + ':do_not_litter:': '🚯', + ':dog2:': '🐕', + ':dog:': '🐶', + ':dollar:': '💵', + ':dolls:': '🎎', + ':dolphin:': '🐬', + ':door:': '🚪', + ':doughnut:': '🍩', + ':dove_of_peace:': '🕊️', + ':dove:': '🕊️', + ':dragon:': '🐉', + ':dragon_face:': '🐲', + ':dress:': '👗', + ':dromedary_camel:': '🐪', + ':drool:': '🤤', + ':drooling_face:': '🤤', + ':drop_of_blood:': '🩸', + ':droplet:': '💧', + ':drum_with_drumsticks:': '🥁', + ':drum:': '🥁', + ':duck:': '🦆', + ':dumpling:': '🥟', + ':dvd:': '📀', + ':email:': '📧', + ':e-mail:': '📧', + ':eagle:': '🦅', + ':ear:': '👂', + ':ear_of_rice:': '🌾', + ':ear_with_hearing_aid:': '🦻', + ':earth_africa:': '🌍', + ':earth_americas:': '🌎', + ':earth_asia:': '🌏', + ':egg:': '🥚', + ':eggplant:': '🍆', + ':electric_plug:': '🔌', + ':elephant:': '🐘', + ':elf:': '🧝', + ':end:': '🔚', + ':envelope_with_arrow:': '📩', + ':euro:': '💶', + ':european_castle:': '🏰', + ':european_post_office:': '🏤', + ':evergreen_tree:': '🌲', + ':exploding_head:': '🤯', + ':expressionless:': '😑', + ':eye:': '👁️', + ':eyeglasses:': '👓', + ':eyes:': '👀', + ':face_vomiting:': '🤮', + ':face_with_hand_over_mouth:': '🤭', + ':face_with_monocle:': '🧐', + ':face_with_raised_eyebrow:': '🤨', + ':face_with_symbols_over_mouth:': '🤬', + ':factory:': '🏭', + ':fairy:': '🧚', + ':falafel:': '🧆', + ':fallen_leaf:': '🍂', + ':family:': '👪', + ':fax:': '📠', + ':fearful:': '😨', + ':paw_prints:': '🐾', + ':feet:': '🐾', + ':ferris_wheel:': '🎡', + ':field_hockey:': '🏑', + ':file_cabinet:': '🗄️', + ':file_folder:': '📁', + ':film_frames:': '🎞️', + ':hand_with_index_and_middle_finger_crossed:': '🤞', + ':fingers_crossed:': '🤞', + ':flame:': '🔥', + ':fire:': '🔥', + ':fire_engine:': '🚒', + ':fire_extinguisher:': '🧯', + ':firecracker:': '🧨', + ':fireworks:': '🎆', + ':first_place_medal:': '🥇', + ':first_place:': '🥇', + ':first_quarter_moon:': '🌓', + ':first_quarter_moon_with_face:': '🌛', + ':fish:': '🐟', + ':fish_cake:': '🍥', + ':fishing_pole_and_fish:': '🎣', + ':waving_black_flag:': '🏴', + ':flag_black:': '🏴', + ':waving_white_flag:': '🏳️', + ':flag_white:': '🏳️', + ':flags:': '🎏', + ':flamingo:': '🦩', + ':flashlight:': '🔦', + ':floppy_disk:': '💾', + ':flower_playing_cards:': '🎴', + ':flushed:': '😳', + ':flying_disc:': '🥏', + ':flying_saucer:': '🛸', + ':fog:': '🌫️', + ':foggy:': '🌁', + ':foot:': '🦶', + ':football:': '🏈', + ':footprints:': '👣', + ':fork_and_knife:': '🍴', + ':fork_and_knife_with_plate:': '🍽️', + ':fork_knife_plate:': '🍽️', + ':fortune_cookie:': '🥠', + ':four_leaf_clover:': '🍀', + ':fox_face:': '🦊', + ':fox:': '🦊', + ':frame_with_picture:': '🖼️', + ':frame_photo:': '🖼️', + ':free:': '🆓', + ':baguette_bread:': '🥖', + ':french_bread:': '🥖', + ':fried_shrimp:': '🍤', + ':fries:': '🍟', + ':frog:': '🐸', + ':frowning:': '😦', + ':full_moon:': '🌕', + ':full_moon_with_face:': '🌝', + ':game_die:': '🎲', + ':garlic:': '🧄', + ':gem:': '💎', + ':genie:': '🧞', + ':ghost:': '👻', + ':gift:': '🎁', + ':gift_heart:': '💝', + ':giraffe:': '🦒', + ':girl:': '👧', + ':globe_with_meridians:': '🌐', + ':gloves:': '🧤', + ':goal_net:': '🥅', + ':goal:': '🥅', + ':goat:': '🐐', + ':goggles:': '🥽', + ':gorilla:': '🦍', + ':grapes:': '🍇', + ':green_apple:': '🍏', + ':green_book:': '📗', + ':green_circle:': '🟢', + ':green_heart:': '💚', + ':green_square:': '🟩', + ':grimacing:': '😬', + ':grin:': '😁', + ':grinning:': '😀', + ':guardsman:': '💂', + ':guard:': '💂', + ':guide_dog:': '🦮', + ':guitar:': '🎸', + ':gun:': '🔫', + ':hamburger:': '🍔', + ':hammer:': '🔨', + ':hamster:': '🐹', + ':raised_hand_with_fingers_splayed:': '🖐️', + ':hand_splayed:': '🖐️', + ':handbag:': '👜', + ':shaking_hands:': '🤝', + ':handshake:': '🤝', + ':hatched_chick:': '🐥', + ':hatching_chick:': '🐣', + ':face_with_head_bandage:': '🤕', + ':head_bandage:': '🤕', + ':headphones:': '🎧', + ':hear_no_evil:': '🙉', + ':heart_decoration:': '💟', + ':heart_eyes:': '😍', + ':heart_eyes_cat:': '😻', + ':heartbeat:': '💓', + ':heartpulse:': '💗', + ':heavy_dollar_sign:': '💲', + ':hedgehog:': '🦔', + ':helicopter:': '🚁', + ':herb:': '🌿', + ':hibiscus:': '🌺', + ':high_brightness:': '🔆', + ':high_heel:': '👠', + ':hiking_boot:': '🥾', + ':hindu_temple:': '🛕', + ':hippopotamus:': '🦛', + ':hockey:': '🏒', + ':hole:': '🕳️', + ':house_buildings:': '🏘️', + ':homes:': '🏘️', + ':honey_pot:': '🍯', + ':horse:': '🐴', + ':horse_racing:': '🏇', + ':hospital:': '🏥', + ':hot_face:': '🥵', + ':hot_pepper:': '🌶️', + ':hot_dog:': '🌭', + ':hotdog:': '🌭', + ':hotel:': '🏨', + ':house:': '🏠', + ':derelict_house_building:': '🏚️', + ':house_abandoned:': '🏚️', + ':house_with_garden:': '🏡', + ':hugging_face:': '🤗', + ':hugging:': '🤗', + ':hushed:': '😯', + ':ice_cream:': '🍨', + ':ice_cube:': '🧊', + ':icecream:': '🍦', + ':id:': '🆔', + ':ideograph_advantage:': '🉐', + ':imp:': '👿', + ':inbox_tray:': '📥', + ':incoming_envelope:': '📨', + ':innocent:': '😇', + ':iphone:': '📱', + ':desert_island:': '🏝️', + ':island:': '🏝️', + ':izakaya_lantern:': '🏮', + ':jack_o_lantern:': '🎃', + ':japan:': '🗾', + ':japanese_castle:': '🏯', + ':japanese_goblin:': '👺', + ':japanese_ogre:': '👹', + ':jeans:': '👖', + ':jigsaw:': '🧩', + ':joy:': '😂', + ':joy_cat:': '😹', + ':joystick:': '🕹️', + ':kaaba:': '🕋', + ':kangaroo:': '🦘', + ':old_key:': '🗝️', + ':key2:': '🗝️', + ':key:': '🔑', + ':keycap_ten:': '🔟', + ':kimono:': '👘', + ':kiss:': '💋', + ':kissing:': '😗', + ':kissing_cat:': '😽', + ':kissing_closed_eyes:': '😚', + ':kissing_heart:': '😘', + ':kissing_smiling_eyes:': '😙', + ':kite:': '🪁', + ':kiwifruit:': '🥝', + ':kiwi:': '🥝', + ':knife:': '🔪', + ':koala:': '🐨', + ':koko:': '🈁', + ':lab_coat:': '🥼', + ':label:': '🏷️', + ':lacrosse:': '🥍', + ':large_blue_diamond:': '🔷', + ':large_orange_diamond:': '🔶', + ':last_quarter_moon:': '🌗', + ':last_quarter_moon_with_face:': '🌜', + ':satisfied:': '😆', + ':laughing:': '😆', + ':leafy_green:': '🥬', + ':leaves:': '🍃', + ':ledger:': '📒', + ':left_fist:': '🤛', + ':left_facing_fist:': '🤛', + ':left_luggage:': '🛅', + ':leg:': '🦵', + ':lemon:': '🍋', + ':leopard:': '🐆', + ':level_slider:': '🎚️', + ':man_in_business_suit_levitating:': '🕴️', + ':levitate:': '🕴️', + ':light_rail:': '🚈', + ':link:': '🔗', + ':lion:': '🦁', + ':lion_face:': '🦁', + ':lips:': '👄', + ':lipstick:': '💄', + ':lizard:': '🦎', + ':llama:': '🦙', + ':lobster:': '🦞', + ':lock:': '🔒', + ':lock_with_ink_pen:': '🔏', + ':lollipop:': '🍭', + ':loud_sound:': '🔊', + ':loudspeaker:': '📢', + ':love_hotel:': '🏩', + ':love_letter:': '💌', + ':love_you_gesture:': '🤟', + ':low_brightness:': '🔅', + ':luggage:': '🧳', + ':liar:': '🤥', + ':lying_face:': '🤥', + ':mag:': '🔍', + ':mag_right:': '🔎', + ':mage:': '🧙', + ':magnet:': '🧲', + ':mahjong:': '🀄', + ':mailbox:': '📫', + ':mailbox_closed:': '📪', + ':mailbox_with_mail:': '📬', + ':mailbox_with_no_mail:': '📭', + ':man:': '👨', + ':male_dancer:': '🕺', + ':man_dancing:': '🕺', + ':man_in_tuxedo:': '🤵', + ':man_with_gua_pi_mao:': '👲', + ':man_with_chinese_cap:': '👲', + ':mango:': '🥭', + ':mans_shoe:': '👞', + ':manual_wheelchair:': '🦽', + ':world_map:': '🗺️', + ':map:': '🗺️', + ':maple_leaf:': '🍁', + ':karate_uniform:': '🥋', + ':martial_arts_uniform:': '🥋', + ':mask:': '😷', + ':mate:': '🧉', + ':meat_on_bone:': '🍖', + ':mechanical_arm:': '🦾', + ':mechanical_leg:': '🦿', + ':sports_medal:': '🏅', + ':medal:': '🏅', + ':mega:': '📣', + ':melon:': '🍈', + ':menorah:': '🕎', + ':mens:': '🚹', + ':merperson:': '🧜', + ':sign_of_the_horns:': '🤘', + ':metal:': '🤘', + ':metro:': '🚇', + ':microbe:': '🦠', + ':studio_microphone:': '🎙️', + ':microphone2:': '🎙️', + ':microphone:': '🎤', + ':microscope:': '🔬', + ':reversed_hand_with_middle_finger_extended:': '🖕', + ':middle_finger:': '🖕', + ':military_medal:': '🎖️', + ':glass_of_milk:': '🥛', + ':milk:': '🥛', + ':milky_way:': '🌌', + ':minibus:': '🚐', + ':minidisc:': '💽', + ':mobile_phone_off:': '📴', + ':money_mouth_face:': '🤑', + ':money_mouth:': '🤑', + ':money_with_wings:': '💸', + ':moneybag:': '💰', + ':monkey:': '🐒', + ':monkey_face:': '🐵', + ':monorail:': '🚝', + ':moon_cake:': '🥮', + ':mortar_board:': '🎓', + ':mosque:': '🕌', + ':mosquito:': '🦟', + ':motorbike:': '🛵', + ':motor_scooter:': '🛵', + ':motorboat:': '🛥️', + ':racing_motorcycle:': '🏍️', + ':motorcycle:': '🏍️', + ':motorized_wheelchair:': '🦼', + ':motorway:': '🛣️', + ':mount_fuji:': '🗻', + ':mountain_cableway:': '🚠', + ':mountain_railway:': '🚞', + ':snow_capped_mountain:': '🏔️', + ':mountain_snow:': '🏔️', + ':mouse2:': '🐁', + ':mouse:': '🐭', + ':three_button_mouse:': '🖱️', + ':mouse_three_button:': '🖱️', + ':movie_camera:': '🎥', + ':moyai:': '🗿', + ':mother_christmas:': '🤶', + ':mrs_claus:': '🤶', + ':muscle:': '💪', + ':mushroom:': '🍄', + ':musical_keyboard:': '🎹', + ':musical_note:': '🎵', + ':musical_score:': '🎼', + ':mute:': '🔇', + ':nail_care:': '💅', + ':name_badge:': '📛', + ':sick:': '🤢', + ':nauseated_face:': '🤢', + ':nazar_amulet:': '🧿', + ':necktie:': '👔', + ':nerd_face:': '🤓', + ':nerd:': '🤓', + ':neutral_face:': '😐', + ':new:': '🆕', + ':new_moon:': '🌑', + ':new_moon_with_face:': '🌚', + ':rolled_up_newspaper:': '🗞️', + ':newspaper2:': '🗞️', + ':newspaper:': '📰', + ':ng:': '🆖', + ':night_with_stars:': '🌃', + ':no_bell:': '🔕', + ':no_bicycles:': '🚳', + ':no_entry_sign:': '🚫', + ':no_mobile_phones:': '📵', + ':no_mouth:': '😶', + ':no_pedestrians:': '🚷', + ':no_smoking:': '🚭', + ':non-potable_water:': '🚱', + ':nose:': '👃', + ':notebook:': '📓', + ':notebook_with_decorative_cover:': '📔', + ':spiral_note_pad:': '🗒️', + ':notepad_spiral:': '🗒️', + ':notes:': '🎶', + ':nut_and_bolt:': '🔩', + ':o2:': '🅾️', + ':ocean:': '🌊', + ':stop_sign:': '🛑', + ':octagonal_sign:': '🛑', + ':octopus:': '🐙', + ':oden:': '🍢', + ':office:': '🏢', + ':oil_drum:': '🛢️', + ':oil:': '🛢️', + ':ok:': '🆗', + ':ok_hand:': '👌', + ':older_adult:': '🧓', + ':older_man:': '👴', + ':grandma:': '👵', + ':older_woman:': '👵', + ':om_symbol:': '🕉️', + ':on:': '🔛', + ':oncoming_automobile:': '🚘', + ':oncoming_bus:': '🚍', + ':oncoming_police_car:': '🚔', + ':oncoming_taxi:': '🚖', + ':one_piece_swimsuit:': '🩱', + ':onion:': '🧅', + ':open_file_folder:': '📂', + ':open_hands:': '👐', + ':open_mouth:': '😮', + ':orange_book:': '📙', + ':orange_circle:': '🟠', + ':orange_heart:': '🧡', + ':orange_square:': '🟧', + ':orangutan:': '🦧', + ':otter:': '🦦', + ':outbox_tray:': '📤', + ':owl:': '🦉', + ':ox:': '🐂', + ':oyster:': '🦪', + ':package:': '📦', + ':page_facing_up:': '📄', + ':page_with_curl:': '📃', + ':pager:': '📟', + ':lower_left_paintbrush:': '🖌️', + ':paintbrush:': '🖌️', + ':palm_tree:': '🌴', + ':palms_up_together:': '🤲', + ':pancakes:': '🥞', + ':panda_face:': '🐼', + ':paperclip:': '📎', + ':linked_paperclips:': '🖇️', + ':paperclips:': '🖇️', + ':parachute:': '🪂', + ':national_park:': '🏞️', + ':park:': '🏞️', + ':parking:': '🅿️', + ':parrot:': '🦜', + ':partying_face:': '🥳', + ':passport_control:': '🛂', + ':peach:': '🍑', + ':peacock:': '🦚', + ':shelled_peanut:': '🥜', + ':peanuts:': '🥜', + ':pear:': '🍐', + ':lower_left_ballpoint_pen:': '🖊️', + ':pen_ballpoint:': '🖊️', + ':lower_left_fountain_pen:': '🖋️', + ':pen_fountain:': '🖋️', + ':memo:': '📝', + ':pencil:': '📝', + ':penguin:': '🐧', + ':pensive:': '😔', + ':dancers:': '👯', + ':people_with_bunny_ears_partying:': '👯', + ':wrestlers:': '🤼', + ':wrestling:': '🤼', + ':people_wrestling:': '🤼', + ':performing_arts:': '🎭', + ':persevere:': '😣', + ':bicyclist:': '🚴', + ':person_biking:': '🚴', + ':bow:': '🙇', + ':person_bowing:': '🙇', + ':person_climbing:': '🧗', + ':cartwheel:': '🤸', + ':person_doing_cartwheel:': '🤸', + ':face_palm:': '🤦', + ':facepalm:': '🤦', + ':person_facepalming:': '🤦', + ':fencer:': '🤺', + ':fencing:': '🤺', + ':person_fencing:': '🤺', + ':person_frowning:': '🙍', + ':no_good:': '🙅', + ':person_gesturing_no:': '🙅', + ':ok_woman:': '🙆', + ':person_gesturing_ok:': '🙆', + ':haircut:': '💇', + ':person_getting_haircut:': '💇', + ':massage:': '💆', + ':person_getting_massage:': '💆', + ':golfer:': '🏌️', + ':person_golfing:': '🏌️', + ':person_in_lotus_position:': '🧘', + ':person_in_steamy_room:': '🧖', + ':juggling:': '🤹', + ':juggler:': '🤹', + ':person_juggling:': '🤹', + ':person_kneeling:': '🧎', + ':lifter:': '🏋️', + ':weight_lifter:': '🏋️', + ':person_lifting_weights:': '🏋️', + ':mountain_bicyclist:': '🚵', + ':person_mountain_biking:': '🚵', + ':handball:': '🤾', + ':person_playing_handball:': '🤾', + ':water_polo:': '🤽', + ':person_playing_water_polo:': '🤽', + ':person_with_pouting_face:': '🙎', + ':person_pouting:': '🙎', + ':raising_hand:': '🙋', + ':person_raising_hand:': '🙋', + ':rowboat:': '🚣', + ':person_rowing_boat:': '🚣', + ':runner:': '🏃', + ':person_running:': '🏃', + ':shrug:': '🤷', + ':person_shrugging:': '🤷', + ':person_standing:': '🧍', + ':surfer:': '🏄', + ':person_surfing:': '🏄', + ':swimmer:': '🏊', + ':person_swimming:': '🏊', + ':information_desk_person:': '💁', + ':person_tipping_hand:': '💁', + ':walking:': '🚶', + ':person_walking:': '🚶', + ':man_with_turban:': '👳', + ':person_wearing_turban:': '👳', + ':petri_dish:': '🧫', + ':pie:': '🥧', + ':pig2:': '🐖', + ':pig:': '🐷', + ':pig_nose:': '🐽', + ':pill:': '💊', + ':pinching_hand:': '🤏', + ':pineapple:': '🍍', + ':table_tennis:': '🏓', + ':ping_pong:': '🏓', + ':pizza:': '🍕', + ':worship_symbol:': '🛐', + ':place_of_worship:': '🛐', + ':pleading_face:': '🥺', + ':point_down:': '👇', + ':point_left:': '👈', + ':point_right:': '👉', + ':point_up_2:': '👆', + ':police_car:': '🚓', + ':cop:': '👮', + ':police_officer:': '👮', + ':poodle:': '🐩', + ':shit:': '💩', + ':hankey:': '💩', + ':poo:': '💩', + ':poop:': '💩', + ':popcorn:': '🍿', + ':post_office:': '🏣', + ':postal_horn:': '📯', + ':postbox:': '📮', + ':potable_water:': '🚰', + ':potato:': '🥔', + ':pouch:': '👝', + ':poultry_leg:': '🍗', + ':pound:': '💷', + ':pouting_cat:': '😾', + ':pray:': '🙏', + ':prayer_beads:': '📿', + ':expecting_woman:': '🤰', + ':pregnant_woman:': '🤰', + ':pretzel:': '🥨', + ':prince:': '🤴', + ':princess:': '👸', + ':printer:': '🖨️', + ':probing_cane:': '🦯', + ':film_projector:': '📽️', + ':projector:': '📽️', + ':punch:': '👊', + ':purple_circle:': '🟣', + ':purple_heart:': '💜', + ':purple_square:': '🟪', + ':purse:': '👛', + ':pushpin:': '📌', + ':put_litter_in_its_place:': '🚮', + ':rabbit2:': '🐇', + ':rabbit:': '🐰', + ':raccoon:': '🦝', + ':racing_car:': '🏎️', + ':race_car:': '🏎️', + ':racehorse:': '🐎', + ':radio:': '📻', + ':radio_button:': '🔘', + ':rage:': '😡', + ':railway_car:': '🚃', + ':railroad_track:': '🛤️', + ':railway_track:': '🛤️', + ':rainbow:': '🌈', + ':back_of_hand:': '🤚', + ':raised_back_of_hand:': '🤚', + ':raised_hands:': '🙌', + ':ram:': '🐏', + ':ramen:': '🍜', + ':rat:': '🐀', + ':razor:': '🪒', + ':receipt:': '🧾', + ':red_car:': '🚗', + ':red_circle:': '🔴', + ':red_envelope:': '🧧', + ':red_haired:': '🦰', + ':red_square:': '🟥', + ':regional_indicator_a:': '🇦', + ':regional_indicator_b:': '🇧', + ':regional_indicator_c:': '🇨', + ':regional_indicator_d:': '🇩', + ':regional_indicator_e:': '🇪', + ':regional_indicator_f:': '🇫', + ':regional_indicator_g:': '🇬', + ':regional_indicator_h:': '🇭', + ':regional_indicator_i:': '🇮', + ':regional_indicator_j:': '🇯', + ':regional_indicator_k:': '🇰', + ':regional_indicator_l:': '🇱', + ':regional_indicator_m:': '🇲', + ':regional_indicator_n:': '🇳', + ':regional_indicator_o:': '🇴', + ':regional_indicator_p:': '🇵', + ':regional_indicator_q:': '🇶', + ':regional_indicator_r:': '🇷', + ':regional_indicator_s:': '🇸', + ':regional_indicator_t:': '🇹', + ':regional_indicator_u:': '🇺', + ':regional_indicator_v:': '🇻', + ':regional_indicator_w:': '🇼', + ':regional_indicator_x:': '🇽', + ':regional_indicator_y:': '🇾', + ':regional_indicator_z:': '🇿', + ':relieved:': '😌', + ':reminder_ribbon:': '🎗️', + ':repeat:': '🔁', + ':repeat_one:': '🔂', + ':restroom:': '🚻', + ':revolving_hearts:': '💞', + ':rhinoceros:': '🦏', + ':rhino:': '🦏', + ':ribbon:': '🎀', + ':rice:': '🍚', + ':rice_ball:': '🍙', + ':rice_cracker:': '🍘', + ':rice_scene:': '🎑', + ':right_fist:': '🤜', + ':right_facing_fist:': '🤜', + ':ring:': '💍', + ':ringed_planet:': '🪐', + ':robot_face:': '🤖', + ':robot:': '🤖', + ':rocket:': '🚀', + ':rolling_on_the_floor_laughing:': '🤣', + ':rofl:': '🤣', + ':roll_of_paper:': '🧻', + ':roller_coaster:': '🎢', + ':face_with_rolling_eyes:': '🙄', + ':rolling_eyes:': '🙄', + ':rooster:': '🐓', + ':rose:': '🌹', + ':rosette:': '🏵️', + ':rotating_light:': '🚨', + ':round_pushpin:': '📍', + ':rugby_football:': '🏉', + ':running_shirt_with_sash:': '🎽', + ':sa:': '🈂️', + ':safety_pin:': '🧷', + ':safety_vest:': '🦺', + ':sake:': '🍶', + ':green_salad:': '🥗', + ':salad:': '🥗', + ':salt:': '🧂', + ':sandal:': '👡', + ':sandwich:': '🥪', + ':santa:': '🎅', + ':sari:': '🥻', + ':satellite:': '📡', + ':satellite_orbital:': '🛰️', + ':sauropod:': '🦕', + ':saxophone:': '🎷', + ':scarf:': '🧣', + ':school:': '🏫', + ':school_satchel:': '🎒', + ':scooter:': '🛴', + ':scorpion:': '🦂', + ':scream:': '😱', + ':scream_cat:': '🙀', + ':scroll:': '📜', + ':seat:': '💺', + ':second_place_medal:': '🥈', + ':second_place:': '🥈', + ':see_no_evil:': '🙈', + ':seedling:': '🌱', + ':selfie:': '🤳', + ':paella:': '🥘', + ':shallow_pan_of_food:': '🥘', + ':shark:': '🦈', + ':shaved_ice:': '🍧', + ':sheep:': '🐑', + ':shell:': '🐚', + ':shield:': '🛡️', + ':ship:': '🚢', + ':shirt:': '👕', + ':shopping_bags:': '🛍️', + ':shopping_trolley:': '🛒', + ':shopping_cart:': '🛒', + ':shorts:': '🩳', + ':shower:': '🚿', + ':shrimp:': '🦐', + ':shushing_face:': '🤫', + ':signal_strength:': '📶', + ':six_pointed_star:': '🔯', + ':skateboard:': '🛹', + ':ski:': '🎿', + ':skeleton:': '💀', + ':skull:': '💀', + ':skunk:': '🦨', + ':sled:': '🛷', + ':sleeping:': '😴', + ':sleeping_accommodation:': '🛌', + ':sleepy:': '😪', + ':slightly_frowning_face:': '🙁', + ':slight_frown:': '🙁', + ':slightly_smiling_face:': '🙂', + ':slight_smile:': '🙂', + ':slot_machine:': '🎰', + ':sloth:': '🦥', + ':small_blue_diamond:': '🔹', + ':small_orange_diamond:': '🔸', + ':small_red_triangle:': '🔺', + ':small_red_triangle_down:': '🔻', + ':smile:': '😄', + ':smile_cat:': '😸', + ':smiley:': '😃', + ':smiley_cat:': '😺', + ':smiling_face_with_3_hearts:': '🥰', + ':smiling_imp:': '😈', + ':smirk:': '😏', + ':smirk_cat:': '😼', + ':smoking:': '🚬', + ':snail:': '🐌', + ':snake:': '🐍', + ':sneeze:': '🤧', + ':sneezing_face:': '🤧', + ':snowboarder:': '🏂', + ':soap:': '🧼', + ':sob:': '😭', + ':socks:': '🧦', + ':softball:': '🥎', + ':soon:': '🔜', + ':sos:': '🆘', + ':sound:': '🔉', + ':space_invader:': '👾', + ':spaghetti:': '🍝', + ':sparkler:': '🎇', + ':sparkling_heart:': '💖', + ':speak_no_evil:': '🙊', + ':speaker:': '🔈', + ':speaking_head_in_silhouette:': '🗣️', + ':speaking_head:': '🗣️', + ':speech_balloon:': '💬', + ':left_speech_bubble:': '🗨️', + ':speech_left:': '🗨️', + ':speedboat:': '🚤', + ':spider:': '🕷️', + ':spider_web:': '🕸️', + ':sponge:': '🧽', + ':spoon:': '🥄', + ':squeeze_bottle:': '🧴', + ':squid:': '🦑', + ':stadium:': '🏟️', + ':star2:': '🌟', + ':star_struck:': '🤩', + ':stars:': '🌠', + ':station:': '🚉', + ':statue_of_liberty:': '🗽', + ':steam_locomotive:': '🚂', + ':stethoscope:': '🩺', + ':stew:': '🍲', + ':straight_ruler:': '📏', + ':strawberry:': '🍓', + ':stuck_out_tongue:': '😛', + ':stuck_out_tongue_closed_eyes:': '😝', + ':stuck_out_tongue_winking_eye:': '😜', + ':stuffed_pita:': '🥙', + ':stuffed_flatbread:': '🥙', + ':sun_with_face:': '🌞', + ':sunflower:': '🌻', + ':sunglasses:': '😎', + ':sunrise:': '🌅', + ':sunrise_over_mountains:': '🌄', + ':superhero:': '🦸', + ':supervillain:': '🦹', + ':sushi:': '🍣', + ':suspension_railway:': '🚟', + ':swan:': '🦢', + ':sweat:': '😓', + ':sweat_drops:': '💦', + ':sweat_smile:': '😅', + ':sweet_potato:': '🍠', + ':symbols:': '🔣', + ':synagogue:': '🕍', + ':syringe:': '💉', + ':t_rex:': '🦖', + ':taco:': '🌮', + ':tada:': '🎉', + ':takeout_box:': '🥡', + ':tanabata_tree:': '🎋', + ':tangerine:': '🍊', + ':taxi:': '🚕', + ':tea:': '🍵', + ':teddy_bear:': '🧸', + ':telephone_receiver:': '📞', + ':telescope:': '🔭', + ':tennis:': '🎾', + ':test_tube:': '🧪', + ':thermometer:': '🌡️', + ':face_with_thermometer:': '🤒', + ':thermometer_face:': '🤒', + ':thinking_face:': '🤔', + ':thinking:': '🤔', + ':third_place_medal:': '🥉', + ':third_place:': '🥉', + ':thought_balloon:': '💭', + ':thread:': '🧵', + ':-1:': '👎', + ':thumbdown:': '👎', + ':thumbsdown:': '👎', + ':+1:': '👍', + ':thumbup:': '👍', + ':thumbsup:': '👍', + ':ticket:': '🎫', + ':admission_tickets:': '🎟️', + ':tickets:': '🎟️', + ':tiger2:': '🐅', + ':tiger:': '🐯', + ':tired_face:': '😫', + ':toilet:': '🚽', + ':tokyo_tower:': '🗼', + ':tomato:': '🍅', + ':tone1:': '🏻', + ':tone2:': '🏼', + ':tone3:': '🏽', + ':tone4:': '🏾', + ':tone5:': '🏿', + ':tongue:': '👅', + ':toolbox:': '🧰', + ':hammer_and_wrench:': '🛠️', + ':tools:': '🛠️', + ':tooth:': '🦷', + ':top:': '🔝', + ':tophat:': '🎩', + ':trackball:': '🖲️', + ':tractor:': '🚜', + ':traffic_light:': '🚥', + ':train2:': '🚆', + ':train:': '🚋', + ':tram:': '🚊', + ':triangular_flag_on_post:': '🚩', + ':triangular_ruler:': '📐', + ':trident:': '🔱', + ':triumph:': '😤', + ':trolleybus:': '🚎', + ':trophy:': '🏆', + ':tropical_drink:': '🍹', + ':tropical_fish:': '🐠', + ':truck:': '🚚', + ':trumpet:': '🎺', + ':tulip:': '🌷', + ':whisky:': '🥃', + ':tumbler_glass:': '🥃', + ':turkey:': '🦃', + ':turtle:': '🐢', + ':tv:': '📺', + ':twisted_rightwards_arrows:': '🔀', + ':two_hearts:': '💕', + ':two_men_holding_hands:': '👬', + ':two_women_holding_hands:': '👭', + ':u5272:': '🈹', + ':u5408:': '🈴', + ':u55b6:': '🈺', + ':u6307:': '🈯', + ':u6708:': '🈷️', + ':u6709:': '🈶', + ':u6e80:': '🈵', + ':u7121:': '🈚', + ':u7533:': '🈸', + ':u7981:': '🈲', + ':u7a7a:': '🈳', + ':unamused:': '😒', + ':underage:': '🔞', + ':unicorn_face:': '🦄', + ':unicorn:': '🦄', + ':unlock:': '🔓', + ':up:': '🆙', + ':upside_down_face:': '🙃', + ':upside_down:': '🙃', + ':vampire:': '🧛', + ':vertical_traffic_light:': '🚦', + ':vhs:': '📼', + ':vibration_mode:': '📳', + ':video_camera:': '📹', + ':video_game:': '🎮', + ':violin:': '🎻', + ':volcano:': '🌋', + ':volleyball:': '🏐', + ':vs:': '🆚', + ':raised_hand_with_part_between_middle_and_ring_fingers:': '🖖', + ':vulcan:': '🖖', + ':waffle:': '🧇', + ':waning_crescent_moon:': '🌘', + ':waning_gibbous_moon:': '🌖', + ':wastebasket:': '🗑️', + ':water_buffalo:': '🐃', + ':watermelon:': '🍉', + ':wave:': '👋', + ':waxing_crescent_moon:': '🌒', + ':waxing_gibbous_moon:': '🌔', + ':wc:': '🚾', + ':weary:': '😩', + ':wedding:': '💒', + ':whale2:': '🐋', + ':whale:': '🐳', + ':white_flower:': '💮', + ':white_haired:': '🦳', + ':white_heart:': '🤍', + ':white_square_button:': '🔳', + ':white_sun_behind_cloud:': '🌥️', + ':white_sun_cloud:': '🌥️', + ':white_sun_behind_cloud_with_rain:': '🌦️', + ':white_sun_rain_cloud:': '🌦️', + ':white_sun_with_small_cloud:': '🌤️', + ':white_sun_small_cloud:': '🌤️', + ':wilted_flower:': '🥀', + ':wilted_rose:': '🥀', + ':wind_blowing_face:': '🌬️', + ':wind_chime:': '🎐', + ':wine_glass:': '🍷', + ':wink:': '😉', + ':wolf:': '🐺', + ':woman:': '👩', + ':woman_with_headscarf:': '🧕', + ':womans_clothes:': '👚', + ':womans_flat_shoe:': '🥿', + ':womans_hat:': '👒', + ':womens:': '🚺', + ':woozy_face:': '🥴', + ':worried:': '😟', + ':wrench:': '🔧', + ':yarn:': '🧶', + ':yawning_face:': '🥱', + ':yellow_circle:': '🟡', + ':yellow_heart:': '💛', + ':yellow_square:': '🟨', + ':yen:': '💴', + ':yo_yo:': '🪀', + ':yum:': '😋', + ':zany_face:': '🤪', + ':zebra:': '🦓', + ':zipper_mouth_face:': '🤐', + ':zipper_mouth:': '🤐', + ':zombie:': '🧟', + ':zzz:': '💤', + ':airplane:': '✈️', + ':alarm_clock:': '⏰', + ':alembic:': '⚗️', + ':anchor:': '⚓', + ':aquarius:': '♒', + ':aries:': '♈', + ':arrow_backward:': '◀️', + ':arrow_double_down:': '⏬', + ':arrow_double_up:': '⏫', + ':arrow_down:': '⬇️', + ':arrow_forward:': '▶️', + ':arrow_heading_down:': '⤵️', + ':arrow_heading_up:': '⤴️', + ':arrow_left:': '⬅️', + ':arrow_lower_left:': '↙️', + ':arrow_lower_right:': '↘️', + ':arrow_right:': '➡️', + ':arrow_right_hook:': '↪️', + ':arrow_up:': '⬆️', + ':arrow_up_down:': '↕️', + ':arrow_upper_left:': '↖️', + ':arrow_upper_right:': '↗️', + ':asterisk_symbol:': '*️', + ':atom_symbol:': '⚛️', + ':atom:': '⚛️', + ':ballot_box_with_check:': '☑️', + ':bangbang:': '‼️', + ':baseball:': '⚾', + ':umbrella_on_ground:': '⛱️', + ':beach_umbrella:': '⛱️', + ':biohazard_sign:': '☣️', + ':biohazard:': '☣️', + ':black_circle:': '⚫', + ':black_large_square:': '⬛', + ':black_medium_small_square:': '◾', + ':black_medium_square:': '◼️', + ':black_nib:': '✒️', + ':black_small_square:': '▪️', + ':cancer:': '♋', + ':capricorn:': '♑', + ':chains:': '⛓️', + ':chess_pawn:': '♟️', + ':church:': '⛪', + ':cloud:': '☁️', + ':clubs:': '♣️', + ':coffee:': '☕', + ':coffin:': '⚰️', + ':comet:': '☄️', + ':congratulations:': '㊗️', + ':copyright:': '©️', + ':latin_cross:': '✝️', + ':cross:': '✝️', + ':crossed_swords:': '⚔️', + ':curly_loop:': '➰', + ':diamonds:': '♦️', + ':digit_eight:': '8️', + ':digit_five:': '5️', + ':digit_four:': '4️', + ':digit_nine:': '9️', + ':digit_one:': '1️', + ':digit_seven:': '7️', + ':digit_six:': '6️', + ':digit_three:': '3️', + ':digit_two:': '2️', + ':digit_zero:': '0️', + ':eight_pointed_black_star:': '✴️', + ':eight_spoked_asterisk:': '✳️', + ':eject_symbol:': '⏏️', + ':eject:': '⏏️', + ':envelope:': '✉️', + ':exclamation:': '❗', + ':fast_forward:': '⏩', + ':female_sign:': '♀️', + ':ferry:': '⛴️', + ':fist:': '✊', + ':fleur-de-lis:': '⚜️', + ':fountain:': '⛲', + ':white_frowning_face:': '☹️', + ':frowning2:': '☹️', + ':fuelpump:': '⛽', + ':gear:': '⚙️', + ':gemini:': '♊', + ':golf:': '⛳', + ':grey_exclamation:': '❕', + ':grey_question:': '❔', + ':hammer_and_pick:': '⚒️', + ':hammer_pick:': '⚒️', + ':heart:': '❤️', + ':heavy_heart_exclamation_mark_ornament:': '❣️', + ':heart_exclamation:': '❣️', + ':hearts:': '♥️', + ':heavy_check_mark:': '✔️', + ':heavy_division_sign:': '➗', + ':heavy_minus_sign:': '➖', + ':heavy_multiplication_x:': '✖️', + ':heavy_plus_sign:': '➕', + ':helmet_with_white_cross:': '⛑️', + ':helmet_with_cross:': '⛑️', + ':hotsprings:': '♨️', + ':hourglass:': '⌛', + ':hourglass_flowing_sand:': '⏳', + ':ice_skate:': '⛸️', + ':infinity:': '♾️', + ':information_source:': 'ℹ️', + ':interrobang:': '⁉️', + ':keyboard:': '⌨️', + ':left_right_arrow:': '↔️', + ':leftwards_arrow_with_hook:': '↩️', + ':leo:': '♌', + ':libra:': '♎', + ':loop:': '➿', + ':m:': 'Ⓜ️', + ':male_sign:': '♂️', + ':medical_symbol:': '⚕️', + ':mountain:': '⛰️', + ':negative_squared_cross_mark:': '❎', + ':no_entry:': '⛔', + ':o:': '⭕', + ':ophiuchus:': '⛎', + ':orthodox_cross:': '☦️', + ':part_alternation_mark:': '〽️', + ':partly_sunny:': '⛅', + ':double_vertical_bar:': '⏸️', + ':pause_button:': '⏸️', + ':peace_symbol:': '☮️', + ':peace:': '☮️', + ':pencil2:': '✏️', + ':basketball_player:': '⛹️', + ':person_with_ball:': '⛹️', + ':person_bouncing_ball:': '⛹️', + ':pick:': '⛏️', + ':pisces:': '♓', + ':play_pause:': '⏯️', + ':point_up:': '☝️', + ':pound_symbol:': '#️', + ':question:': '❓', + ':radioactive_sign:': '☢️', + ':radioactive:': '☢️', + ':raised_hand:': '✋', + ':record_button:': '⏺️', + ':recycle:': '♻️', + ':registered:': '®️', + ':relaxed:': '☺️', + ':rewind:': '⏪', + ':sagittarius:': '♐', + ':sailboat:': '⛵', + ':scales:': '⚖️', + ':scissors:': '✂️', + ':scorpius:': '♏', + ':secret:': '㊙️', + ':shamrock:': '☘️', + ':shinto_shrine:': '⛩️', + ':skier:': '⛷️', + ':skull_and_crossbones:': '☠️', + ':skull_crossbones:': '☠️', + ':snowflake:': '❄️', + ':snowman2:': '☃️', + ':snowman:': '⛄', + ':soccer:': '⚽', + ':spades:': '♠️', + ':sparkle:': '❇️', + ':sparkles:': '✨', + ':star:': '⭐', + ':star_and_crescent:': '☪️', + ':star_of_david:': '✡️', + ':stop_button:': '⏹️', + ':stopwatch:': '⏱️', + ':sunny:': '☀️', + ':taurus:': '♉', + ':telephone:': '☎️', + ':tent:': '⛺', + ':thunder_cloud_and_rain:': '⛈️', + ':thunder_cloud_rain:': '⛈️', + ':timer_clock:': '⏲️', + ':timer:': '⏲️', + ':tm:': '™️', + ':next_track:': '⏭️', + ':track_next:': '⏭️', + ':previous_track:': '⏮️', + ':track_previous:': '⏮️', + ':umbrella2:': '☂️', + ':umbrella:': '☔', + ':funeral_urn:': '⚱️', + ':urn:': '⚱️', + ':v:': '✌️', + ':virgo:': '♍', + ':warning:': '⚠️', + ':watch:': '⌚', + ':wavy_dash:': '〰️', + ':wheel_of_dharma:': '☸️', + ':wheelchair:': '♿', + ':white_check_mark:': '✅', + ':white_circle:': '⚪', + ':white_large_square:': '⬜', + ':white_medium_small_square:': '◽', + ':white_medium_square:': '◻️', + ':white_small_square:': '▫️', + ':writing_hand:': '✍️', + ':x:': '❌', + ':yin_yang:': '☯️', + ':zap:': '⚡' +}; export default emojis; diff --git a/app/utils/shortnameToUnicode/index.js b/app/utils/shortnameToUnicode/index.js index 5c6f7093e..0a54aa3a3 100644 --- a/app/utils/shortnameToUnicode/index.js +++ b/app/utils/shortnameToUnicode/index.js @@ -3,9 +3,9 @@ import ascii, { asciiRegexp } from './ascii'; const shortnamePattern = new RegExp(/:[-+_a-z0-9]+:/, 'gi'); const replaceShortNameWithUnicode = shortname => emojis[shortname] || shortname; -const regAscii = new RegExp(`((\\s|^)${ asciiRegexp }(?=\\s|$|[!,.?]))`, 'gi'); +const regAscii = new RegExp(`((\\s|^)${asciiRegexp}(?=\\s|$|[!,.?]))`, 'gi'); -const unescapeHTML = (string) => { +const unescapeHTML = string => { const unescaped = { '&': '&', '&': '&', @@ -19,19 +19,19 @@ const unescapeHTML = (string) => { '"': '"', '"': '"', '"': '"', - ''': '\'', - ''': '\'', - ''': '\'' + ''': "'", + ''': "'", + ''': "'" }; - return string.replace(/&(?:amp|#38|#x26|lt|#60|#x3C|gt|#62|#x3E|apos|#39|#x27|quot|#34|#x22);/ig, match => unescaped[match]); + return string.replace(/&(?:amp|#38|#x26|lt|#60|#x3C|gt|#62|#x3E|apos|#39|#x27|quot|#34|#x22);/gi, match => unescaped[match]); }; -const shortnameToUnicode = (str) => { +const shortnameToUnicode = str => { str = str.replace(shortnamePattern, replaceShortNameWithUnicode); str = str.replace(regAscii, (entire, m1, m2, m3) => { - if (!m3 || (!(unescapeHTML(m3) in ascii))) { + if (!m3 || !(unescapeHTML(m3) in ascii)) { // if the ascii doesnt exist just return the entire match return entire; } diff --git a/app/utils/shortnameToUnicode/shortnameToUnicode.test.js b/app/utils/shortnameToUnicode/shortnameToUnicode.test.js index 62da51ca6..b76d1bf84 100644 --- a/app/utils/shortnameToUnicode/shortnameToUnicode.test.js +++ b/app/utils/shortnameToUnicode/shortnameToUnicode.test.js @@ -26,7 +26,7 @@ test('render ascii smile', () => { }); test('render several ascii emojis', () => { - expect(shortnameToUnicode(':) :( -_- \':-D')).toBe('🙂😞😑😅'); + expect(shortnameToUnicode(":) :( -_- ':-D")).toBe('🙂😞😑😅'); }); test('render text with ascii emoji', () => { @@ -34,5 +34,5 @@ test('render text with ascii emoji', () => { }); test('render emoji and ascii emoji', () => { - expect(shortnameToUnicode('\':-D :joy:')).toBe('😅 😂'); + expect(shortnameToUnicode("':-D :joy:")).toBe('😅 😂'); }); diff --git a/app/utils/sslPinning.js b/app/utils/sslPinning.js index 00d807d4e..50f944e63 100644 --- a/app/utils/sslPinning.js +++ b/app/utils/sslPinning.js @@ -1,54 +1,55 @@ -import { NativeModules, Platform, Alert } from 'react-native'; +import { Alert, NativeModules, Platform } from 'react-native'; import DocumentPicker from 'react-native-document-picker'; import * as FileSystem from 'expo-file-system'; -import { extractHostname } from './server'; import UserPreferences from '../lib/userPreferences'; import I18n from '../i18n'; +import { extractHostname } from './server'; const { SSLPinning } = NativeModules; const RCSSLPinning = Platform.select({ ios: { - pickCertificate: () => new Promise(async(resolve, reject) => { - try { - const res = await DocumentPicker.pick({ - type: ['com.rsa.pkcs-12'] - }); - const { uri, name } = res; - Alert.prompt( - I18n.t('Certificate_password'), - I18n.t('Whats_the_password_for_your_certificate'), - [ - { - text: 'OK', - onPress: async(password) => { - try { - const certificatePath = `${ FileSystem.documentDirectory }/${ name }`; + pickCertificate: () => + new Promise(async (resolve, reject) => { + try { + const res = await DocumentPicker.pick({ + type: ['com.rsa.pkcs-12'] + }); + const { uri, name } = res; + Alert.prompt( + I18n.t('Certificate_password'), + I18n.t('Whats_the_password_for_your_certificate'), + [ + { + text: 'OK', + onPress: async password => { + try { + const certificatePath = `${FileSystem.documentDirectory}/${name}`; - await FileSystem.copyAsync({ from: uri, to: certificatePath }); + await FileSystem.copyAsync({ from: uri, to: certificatePath }); - const certificate = { - path: certificatePath.replace('file://', ''), // file:// isn't allowed by obj-C - password - }; + const certificate = { + path: certificatePath.replace('file://', ''), // file:// isn't allowed by obj-C + password + }; - await UserPreferences.setMapAsync(name, certificate); + await UserPreferences.setMapAsync(name, certificate); - resolve(name); - } catch (e) { - reject(e); + resolve(name); + } catch (e) { + reject(e); + } } } - } - ], - 'secure-text' - ); - } catch (e) { - reject(e); - } - }), - setCertificate: async(alias, server) => { + ], + 'secure-text' + ); + } catch (e) { + reject(e); + } + }), + setCertificate: async (alias, server) => { if (alias) { const certificate = await UserPreferences.getMapAsync(alias); await UserPreferences.setMapAsync(extractHostname(server), certificate); diff --git a/app/utils/theme.js b/app/utils/theme.js index 3c265eb5a..c9038941d 100644 --- a/app/utils/theme.js +++ b/app/utils/theme.js @@ -2,8 +2,8 @@ import { Appearance } from 'react-native-appearance'; import changeNavigationBarColor from 'react-native-navigation-bar-color'; import setRootViewColor from 'rn-root-view'; -import { isAndroid } from './deviceInfo'; import { themes } from '../constants/colors'; +import { isAndroid } from './deviceInfo'; let themeListener; @@ -15,7 +15,7 @@ export const defaultTheme = () => { return 'light'; }; -export const getTheme = (themePreferences) => { +export const getTheme = themePreferences => { const { darkLevel, currentTheme } = themePreferences; let theme = currentTheme; if (currentTheme === 'automatic') { @@ -35,7 +35,7 @@ export const newThemeState = (prevState, newTheme) => { return { themePreferences, theme: getTheme(themePreferences) }; }; -export const setNativeTheme = async(themePreferences) => { +export const setNativeTheme = async themePreferences => { const theme = getTheme(themePreferences); if (isAndroid) { const iconsLight = theme === 'light'; diff --git a/app/utils/touch.js b/app/utils/touch.js index d5c22d4df..0bfece04a 100644 --- a/app/utils/touch.js +++ b/app/utils/touch.js @@ -9,14 +9,12 @@ class Touch extends React.Component { this.ref.setNativeProps(props); } - getRef = (ref) => { + getRef = ref => { this.ref = ref; }; render() { - const { - children, onPress, theme, underlayColor, ...props - } = this.props; + const { children, onPress, theme, underlayColor, ...props } = this.props; return ( <RectButton @@ -25,8 +23,7 @@ class Touch extends React.Component { activeOpacity={1} underlayColor={underlayColor || themes[theme].bannerBackground} rippleColor={themes[theme].bannerBackground} - {...props} - > + {...props}> {children} </RectButton> ); diff --git a/app/utils/twoFactor.js b/app/utils/twoFactor.js index ae5bc9b07..6f2fa9c9d 100644 --- a/app/utils/twoFactor.js +++ b/app/utils/twoFactor.js @@ -1,20 +1,21 @@ import { settings } from '@rocket.chat/sdk'; -import EventEmitter from './events'; import { TWO_FACTOR } from '../containers/TwoFactor'; +import EventEmitter from './events'; -export const twoFactor = ({ method, invalid }) => new Promise((resolve, reject) => { - EventEmitter.emit(TWO_FACTOR, { - method, - invalid, - cancel: () => reject(), - submit: (code) => { - settings.customHeaders = { - ...settings.customHeaders, - 'x-2fa-code': code, - 'x-2fa-method': method - }; - resolve({ twoFactorCode: code, twoFactorMethod: method }); - } +export const twoFactor = ({ method, invalid }) => + new Promise((resolve, reject) => { + EventEmitter.emit(TWO_FACTOR, { + method, + invalid, + cancel: () => reject(), + submit: code => { + settings.customHeaders = { + ...settings.customHeaders, + 'x-2fa-code': code, + 'x-2fa-method': method + }; + resolve({ twoFactorCode: code, twoFactorMethod: method }); + } + }); }); -}); diff --git a/app/utils/url.js b/app/utils/url.js index 16506ed13..623524d7a 100644 --- a/app/utils/url.js +++ b/app/utils/url.js @@ -1,10 +1,13 @@ -export const isValidURL = (url) => { - const pattern = new RegExp('^(https?:\\/\\/)?' // protocol - + '((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,}|' // domain name - + '((\\d{1,3}\\.){3}\\d{1,3}))' // OR ip (v4) address - + '(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*' // port and path - + '(\\?[;&a-z\\d%_.~+=-]*)?' // query string - + '(\\#[-a-z\\d_]*)?$', 'i'); // fragment locator +export const isValidURL = url => { + const pattern = new RegExp( + '^(https?:\\/\\/)?' + // protocol + '((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,}|' + // domain name + '((\\d{1,3}\\.){3}\\d{1,3}))' + // OR ip (v4) address + '(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*' + // port and path + '(\\?[;&a-z\\d%_.~+=-]*)?' + // query string + '(\\#[-a-z\\d_]*)?$', + 'i' + ); // fragment locator return !!pattern.test(url); }; diff --git a/app/views/AddChannelTeamView.js b/app/views/AddChannelTeamView.js index 76439b412..b93f5fb78 100644 --- a/app/views/AddChannelTeamView.js +++ b/app/views/AddChannelTeamView.js @@ -21,9 +21,7 @@ const setHeader = (navigation, isMasterDetail) => { navigation.setOptions(options); }; -const AddChannelTeamView = ({ - navigation, route, isMasterDetail -}) => { +const AddChannelTeamView = ({ navigation, route, isMasterDetail }) => { const { teamId, teamChannels } = route.params; const { theme } = useTheme(); @@ -38,9 +36,15 @@ const AddChannelTeamView = ({ <List.Separator /> <List.Item title='Create_New' - onPress={() => (isMasterDetail - ? navigation.navigate('SelectedUsersViewCreateChannel', { nextAction: () => navigation.navigate('CreateChannelView', { teamId }) }) - : navigation.navigate('SelectedUsersView', { nextAction: () => navigation.navigate('ChatsStackNavigator', { screen: 'CreateChannelView', params: { teamId } }) })) + onPress={() => + isMasterDetail + ? navigation.navigate('SelectedUsersViewCreateChannel', { + nextAction: () => navigation.navigate('CreateChannelView', { teamId }) + }) + : navigation.navigate('SelectedUsersView', { + nextAction: () => + navigation.navigate('ChatsStackNavigator', { screen: 'CreateChannelView', params: { teamId } }) + }) } testID='add-channel-team-view-create-channel' left={() => <List.Icon name='team' />} diff --git a/app/views/AddExistingChannelView.js b/app/views/AddExistingChannelView.js index 0edced4b0..ed9a95ac4 100644 --- a/app/views/AddExistingChannelView.js +++ b/app/views/AddExistingChannelView.js @@ -1,8 +1,6 @@ import React from 'react'; import PropTypes from 'prop-types'; -import { - View, FlatList -} from 'react-native'; +import { FlatList, View } from 'react-native'; import { connect } from 'react-redux'; import { Q } from '@nozbe/watermelondb'; @@ -59,16 +57,17 @@ class AddExistingChannelView extends React.Component { options.headerLeft = () => <HeaderButton.CloseModal navigation={navigation} />; } - options.headerRight = () => selected.length > 0 && ( - <HeaderButton.Container> - <HeaderButton.Item title={I18n.t('Next')} onPress={this.submit} testID='add-existing-channel-view-submit' /> - </HeaderButton.Container> - ); + options.headerRight = () => + selected.length > 0 && ( + <HeaderButton.Container> + <HeaderButton.Item title={I18n.t('Next')} onPress={this.submit} testID='add-existing-channel-view-submit' /> + </HeaderButton.Container> + ); navigation.setOptions(options); - } + }; - query = async(stringToSearch = '') => { + query = async (stringToSearch = '') => { try { const { addTeamChannelPermission } = this.props; const db = database.active; @@ -77,23 +76,25 @@ class AddExistingChannelView extends React.Component { .query( Q.where('team_id', ''), Q.where('t', Q.oneOf(['c', 'p'])), - Q.where('name', Q.like(`%${ stringToSearch }%`)), + Q.where('name', Q.like(`%${stringToSearch}%`)), Q.experimentalTake(QUERY_SIZE), Q.experimentalSortBy('room_updated_at', Q.desc) ) .fetch(); - const asyncFilter = async(channelsArray) => { - const results = await Promise.all(channelsArray.map(async(channel) => { - if (channel.prid) { - return false; - } - const permissions = await RocketChat.hasPermission([addTeamChannelPermission], channel.rid); - if (!permissions[0]) { - return false; - } - return true; - })); + const asyncFilter = async channelsArray => { + const results = await Promise.all( + channelsArray.map(async channel => { + if (channel.prid) { + return false; + } + const permissions = await RocketChat.hasPermission([addTeamChannelPermission], channel.rid); + if (!permissions[0]) { + return false; + } + return true; + }) + ); return channelsArray.filter((_v, index) => results[index]); }; @@ -102,18 +103,18 @@ class AddExistingChannelView extends React.Component { } catch (e) { log(e); } - } + }; - onSearchChangeText = debounce((text) => { + onSearchChangeText = debounce(text => { this.query(text); - }, 300) + }, 300); dismiss = () => { const { navigation } = this.props; return navigation.pop(); - } + }; - submit = async() => { + submit = async () => { const { selected } = this.state; const { isMasterDetail } = this.props; @@ -130,7 +131,7 @@ class AddExistingChannelView extends React.Component { showErrorAlert(I18n.t(e.data.error), I18n.t('Add_Existing_Channel'), () => {}); this.setState({ loading: false }); } - } + }; renderHeader = () => { const { theme } = this.props; @@ -139,14 +140,14 @@ class AddExistingChannelView extends React.Component { <SearchBox onChangeText={text => this.onSearchChangeText(text)} testID='add-existing-channel-view-search' /> </View> ); - } + }; - isChecked = (rid) => { + isChecked = rid => { const { selected } = this.state; return selected.includes(rid); - } + }; - toggleChannel = (rid) => { + toggleChannel = rid => { const { selected } = this.state; animateNextTransition(); @@ -158,7 +159,7 @@ class AddExistingChannelView extends React.Component { const filterSelected = selected.filter(el => el !== rid); this.setState({ selected: filterSelected }, () => this.setHeader()); } - } + }; renderItem = ({ item }) => { const isChecked = this.isChecked(item.rid); @@ -169,13 +170,12 @@ class AddExistingChannelView extends React.Component { title={RocketChat.getRoomTitle(item)} translateTitle={false} onPress={() => this.toggleChannel(item.rid)} - testID={`add-existing-channel-view-item-${ item.name }`} + testID={`add-existing-channel-view-item-${item.name}`} left={() => <List.Icon name={icon} />} right={() => (isChecked ? <List.Icon name='check' /> : null)} /> - ); - } + }; renderList = () => { const { search, channels } = this.state; @@ -192,7 +192,7 @@ class AddExistingChannelView extends React.Component { keyboardShouldPersistTaps='always' /> ); - } + }; render() { const { loading } = this.state; diff --git a/app/views/AdminPanelView/index.js b/app/views/AdminPanelView/index.tsx similarity index 63% rename from app/views/AdminPanelView/index.js rename to app/views/AdminPanelView/index.tsx index bb790df43..80f728e12 100644 --- a/app/views/AdminPanelView/index.js +++ b/app/views/AdminPanelView/index.tsx @@ -1,7 +1,7 @@ import React from 'react'; -import PropTypes from 'prop-types'; import { WebView } from 'react-native-webview'; import { connect } from 'react-redux'; +import { DrawerScreenProps } from '@react-navigation/drawer'; import I18n from '../../i18n'; import StatusBar from '../../containers/StatusBar'; @@ -10,16 +10,21 @@ import { withTheme } from '../../theme'; import { getUserSelector } from '../../selectors/login'; import SafeAreaView from '../../containers/SafeAreaView'; -class AdminPanelView extends React.Component { - static navigationOptions = ({ navigation, isMasterDetail }) => ({ +interface IAdminPanelViewProps { + baseUrl: string; + token: string; +} + +interface INavigationOptions { + navigation: DrawerScreenProps<any>; + isMasterDetail: boolean; +} + +class AdminPanelView extends React.Component<IAdminPanelViewProps, any> { + static navigationOptions = ({ navigation, isMasterDetail }: INavigationOptions) => ({ headerLeft: isMasterDetail ? undefined : () => <HeaderButton.Drawer navigation={navigation} />, title: I18n.t('Admin_Panel') - }) - - static propTypes = { - baseUrl: PropTypes.string, - token: PropTypes.string - } + }); render() { const { baseUrl, token } = this.props; @@ -32,15 +37,15 @@ class AdminPanelView extends React.Component { <WebView // https://github.com/react-native-community/react-native-webview/issues/1311 onMessage={() => {}} - source={{ uri: `${ baseUrl }/admin/info?layout=embedded` }} - injectedJavaScript={`Meteor.loginWithToken('${ token }', function() { })`} + source={{ uri: `${baseUrl}/admin/info?layout=embedded` }} + injectedJavaScript={`Meteor.loginWithToken('${token}', function() { })`} /> </SafeAreaView> ); } } -const mapStateToProps = state => ({ +const mapStateToProps = (state: any) => ({ baseUrl: state.server.server, token: getUserSelector(state).token }); diff --git a/app/views/AttachmentView.js b/app/views/AttachmentView.js index c07ed71b9..8f199b237 100644 --- a/app/views/AttachmentView.js +++ b/app/views/AttachmentView.js @@ -1,5 +1,5 @@ import React from 'react'; -import { StyleSheet, View, PermissionsAndroid } from 'react-native'; +import { PermissionsAndroid, StyleSheet, View } from 'react-native'; import { connect } from 'react-redux'; import PropTypes from 'prop-types'; import CameraRoll from '@react-native-community/cameraroll'; @@ -44,7 +44,7 @@ class AttachmentView extends React.Component { token: PropTypes.string }), Allow_Save_Media_to_Gallery: PropTypes.bool - } + }; constructor(props) { super(props); @@ -69,9 +69,7 @@ class AttachmentView extends React.Component { } setHeader = () => { - const { - route, navigation, theme, Allow_Save_Media_to_Gallery - } = this.props; + const { route, navigation, theme, Allow_Save_Media_to_Gallery } = this.props; const attachment = route.params?.attachment; let { title } = attachment; try { @@ -81,28 +79,35 @@ class AttachmentView extends React.Component { } const options = { title, - headerLeft: () => <HeaderButton.CloseModal testID='close-attachment-view' navigation={navigation} buttonStyle={{ color: themes[theme].previewTintColor }} />, - headerRight: () => ( - Allow_Save_Media_to_Gallery - ? <HeaderButton.Download testID='save-image' onPress={this.handleSave} buttonStyle={{ color: themes[theme].previewTintColor }} /> - : null + headerLeft: () => ( + <HeaderButton.CloseModal + testID='close-attachment-view' + navigation={navigation} + buttonStyle={{ color: themes[theme].previewTintColor }} + /> ), + headerRight: () => + Allow_Save_Media_to_Gallery ? ( + <HeaderButton.Download + testID='save-image' + onPress={this.handleSave} + buttonStyle={{ color: themes[theme].previewTintColor }} + /> + ) : null, headerBackground: () => <View style={{ flex: 1, backgroundColor: themes[theme].previewBackground }} />, headerTintColor: themes[theme].previewTintColor, headerTitleStyle: { color: themes[theme].previewTintColor, marginHorizontal: 10 } }; navigation.setOptions(options); - } + }; - getVideoRef = ref => this.videoRef = ref; + getVideoRef = ref => (this.videoRef = ref); - handleSave = async() => { + handleSave = async () => { const { attachment } = this.state; const { user, baseUrl } = this.props; - const { - image_url, image_type, video_url, video_type - } = attachment; - const url = image_url || video_url; + const { title_link, image_url, image_type, video_url, video_type } = attachment; + const url = title_link || image_url || video_url; const mediaAttachment = formatAttachmentUrl(url, user.id, user.token, baseUrl); if (isAndroid) { @@ -118,9 +123,9 @@ class AttachmentView extends React.Component { this.setState({ loading: true }); try { - const extension = image_url ? `.${ mime.extension(image_type) || 'jpg' }` : `.${ mime.extension(video_type) || 'mp4' }`; - const documentDir = `${ RNFetchBlob.fs.dirs.DocumentDir }/`; - const path = `${ documentDir + SHA256(url) + extension }`; + const extension = image_url ? `.${mime.extension(image_type) || 'jpg'}` : `.${mime.extension(video_type) || 'mp4'}`; + const documentDir = `${RNFetchBlob.fs.dirs.DocumentDir}/`; + const path = `${documentDir + SHA256(url) + extension}`; const file = await RNFetchBlob.config({ path }).fetch('GET', mediaAttachment); await CameraRoll.save(path, { album: 'Rocket.Chat' }); await file.flush(); @@ -131,10 +136,8 @@ class AttachmentView extends React.Component { this.setState({ loading: false }); }; - renderImage = (uri) => { - const { - theme, width, height, insets - } = this.props; + renderImage = uri => { + const { theme, width, height, insets } = this.props; const headerHeight = getHeaderHeight(width > height); return ( <ImageViewer @@ -145,7 +148,7 @@ class AttachmentView extends React.Component { height={height - insets.top - insets.bottom - headerHeight} /> ); - } + }; renderVideo = uri => ( <Video @@ -170,7 +173,7 @@ class AttachmentView extends React.Component { let content = null; if (attachment && attachment.image_url) { - const uri = formatAttachmentUrl(attachment.image_url, user.id, user.token, baseUrl); + const uri = formatAttachmentUrl(attachment.title_link || attachment.image_url, user.id, user.token, baseUrl); content = this.renderImage(encodeURI(uri)); } else if (attachment && attachment.video_url) { const uri = formatAttachmentUrl(attachment.video_url, user.id, user.token, baseUrl); diff --git a/app/views/AuthLoadingView.js b/app/views/AuthLoadingView.js index b0ecf2474..e97c10d94 100644 --- a/app/views/AuthLoadingView.js +++ b/app/views/AuthLoadingView.js @@ -1,7 +1,5 @@ import React from 'react'; -import { - View, Text, StyleSheet, ActivityIndicator -} from 'react-native'; +import { ActivityIndicator, StyleSheet, Text, View } from 'react-native'; import PropTypes from 'prop-types'; import { connect } from 'react-redux'; @@ -9,7 +7,6 @@ import I18n from '../i18n'; import StatusBar from '../containers/StatusBar'; import { withTheme } from '../theme'; import { themes } from '../constants/colors'; - import sharedStyles from './Styles'; const styles = StyleSheet.create({ @@ -32,7 +29,7 @@ const AuthLoadingView = React.memo(({ theme, text }) => ( {text && ( <> <ActivityIndicator color={themes[theme].auxiliaryText} size='large' /> - <Text style={[styles.text, { color: themes[theme].bodyText }]}>{`${ text }\n${ I18n.t('Please_wait') }`}</Text> + <Text style={[styles.text, { color: themes[theme].bodyText }]}>{`${text}\n${I18n.t('Please_wait')}`}</Text> </> )} </View> diff --git a/app/views/AuthenticationWebView.js b/app/views/AuthenticationWebView.js index 99978b7d7..bba518d75 100644 --- a/app/views/AuthenticationWebView.js +++ b/app/views/AuthenticationWebView.js @@ -48,7 +48,7 @@ class AuthenticationWebView extends React.PureComponent { Accounts_Iframe_api_url: PropTypes.bool, Accounts_Iframe_api_method: PropTypes.bool, theme: PropTypes.string - } + }; constructor(props) { super(props); @@ -56,8 +56,8 @@ class AuthenticationWebView extends React.PureComponent { logging: false, loading: false }; - this.oauthRedirectRegex = new RegExp(`(?=.*(${ props.server }))(?=.*(credentialToken))(?=.*(credentialSecret))`, 'g'); - this.iframeRedirectRegex = new RegExp(`(?=.*(${ props.server }))(?=.*(event|loginToken|token))`, 'g'); + this.oauthRedirectRegex = new RegExp(`(?=.*(${props.server}))(?=.*(credentialToken))(?=.*(credentialSecret))`, 'g'); + this.iframeRedirectRegex = new RegExp(`(?=.*(${props.server}))(?=.*(event|loginToken|token))`, 'g'); } componentWillUnmount() { @@ -69,9 +69,9 @@ class AuthenticationWebView extends React.PureComponent { dismiss = () => { const { navigation } = this.props; navigation.pop(); - } + }; - login = (params) => { + login = params => { const { logging } = this.state; if (logging) { return; @@ -86,21 +86,25 @@ class AuthenticationWebView extends React.PureComponent { } this.setState({ logging: false }); this.dismiss(); - } + }; // Force 3s delay so the server has time to evaluate the token debouncedLogin = debounce(params => this.login(params), 3000); - tryLogin = debounce(async() => { - const { Accounts_Iframe_api_url, Accounts_Iframe_api_method } = this.props; - const data = await fetch(Accounts_Iframe_api_url, { method: Accounts_Iframe_api_method }).then(response => response.json()); - const resume = data?.login || data?.loginToken; - if (resume) { - this.login({ resume }); - } - }, 3000, true) + tryLogin = debounce( + async () => { + const { Accounts_Iframe_api_url, Accounts_Iframe_api_method } = this.props; + const data = await fetch(Accounts_Iframe_api_url, { method: Accounts_Iframe_api_method }).then(response => response.json()); + const resume = data?.login || data?.loginToken; + if (resume) { + this.login({ resume }); + } + }, + 3000, + true + ); - onNavigationStateChange = (webViewState) => { + onNavigationStateChange = webViewState => { const url = decodeURIComponent(webViewState.url); const { route } = this.props; const { authType } = route.params; @@ -141,11 +145,11 @@ class AuthenticationWebView extends React.PureComponent { this.debouncedLogin({ resume: credentials.token || credentials.loginToken }); break; default: - // Do nothing + // Do nothing } } } - } + }; render() { const { loading } = this.state; @@ -170,7 +174,7 @@ class AuthenticationWebView extends React.PureComponent { this.setState({ loading: false }); }} /> - { loading ? <ActivityIndicator size='large' theme={theme} absolute /> : null } + {loading ? <ActivityIndicator size='large' theme={theme} absolute /> : null} </> ); } diff --git a/app/views/AutoTranslateView/index.js b/app/views/AutoTranslateView/index.tsx similarity index 64% rename from app/views/AutoTranslateView/index.js rename to app/views/AutoTranslateView/index.tsx index 962c4883a..92a77543a 100644 --- a/app/views/AutoTranslateView/index.js +++ b/app/views/AutoTranslateView/index.tsx @@ -1,6 +1,5 @@ import React from 'react'; -import PropTypes from 'prop-types'; -import { FlatList, Switch, StyleSheet } from 'react-native'; +import { FlatList, StyleSheet, Switch } from 'react-native'; import RocketChat from '../../lib/rocketchat'; import I18n from '../../i18n'; @@ -9,7 +8,7 @@ import * as List from '../../containers/List'; import { SWITCH_TRACK_COLOR, themes } from '../../constants/colors'; import { withTheme } from '../../theme'; import SafeAreaView from '../../containers/SafeAreaView'; -import { logEvent, events } from '../../utils/log'; +import { events, logEvent } from '../../utils/log'; const styles = StyleSheet.create({ list: { @@ -17,17 +16,33 @@ const styles = StyleSheet.create({ } }); -class AutoTranslateView extends React.Component { +interface IRoom { + observe: Function; + autoTranslateLanguage: boolean; + autoTranslate: boolean; +} + +interface IAutoTranslateViewProps { + route: { + params: { + rid?: string; + room?: IRoom; + }; + }; + theme: string; +} + +class AutoTranslateView extends React.Component<IAutoTranslateViewProps, any> { static navigationOptions = () => ({ title: I18n.t('Auto_Translate') - }) + }); - static propTypes = { - route: PropTypes.object, - theme: PropTypes.string - } + private mounted: boolean; + private rid: string | undefined; + private roomObservable: any; + private subscription: any; - constructor(props) { + constructor(props: IAutoTranslateViewProps) { super(props); this.mounted = false; this.rid = props.route.params?.rid; @@ -35,23 +50,22 @@ class AutoTranslateView extends React.Component { if (room && room.observe) { this.roomObservable = room.observe(); - this.subscription = this.roomObservable - .subscribe((changes) => { - if (this.mounted) { - const { selectedLanguage, enableAutoTranslate } = this.state; - if (selectedLanguage !== changes.autoTranslateLanguage) { - this.setState({ selectedLanguage: changes.autoTranslateLanguage }); - } - if (enableAutoTranslate !== changes.autoTranslate) { - this.setState({ enableAutoTranslate: changes.autoTranslate }); - } + this.subscription = this.roomObservable.subscribe((changes: IRoom) => { + if (this.mounted) { + const { selectedLanguage, enableAutoTranslate } = this.state; + if (selectedLanguage !== changes.autoTranslateLanguage) { + this.setState({ selectedLanguage: changes.autoTranslateLanguage }); } - }); + if (enableAutoTranslate !== changes.autoTranslate) { + this.setState({ enableAutoTranslate: changes.autoTranslate }); + } + } + }); } this.state = { languages: [], - selectedLanguage: room.autoTranslateLanguage, - enableAutoTranslate: room.autoTranslate + selectedLanguage: room?.autoTranslateLanguage, + enableAutoTranslate: room?.autoTranslate }; } @@ -71,7 +85,7 @@ class AutoTranslateView extends React.Component { } } - toggleAutoTranslate = async() => { + toggleAutoTranslate = async () => { logEvent(events.AT_TOGGLE_TRANSLATE); const { enableAutoTranslate } = this.state; try { @@ -86,40 +100,36 @@ class AutoTranslateView extends React.Component { logEvent(events.AT_TOGGLE_TRANSLATE_F); console.log(error); } - } + }; - saveAutoTranslateLanguage = async(language) => { + saveAutoTranslateLanguage = async (language: string) => { logEvent(events.AT_SET_LANG); try { + // TODO: remove the parameter options, after migrate the RocketChat await RocketChat.saveAutoTranslate({ rid: this.rid, field: 'autoTranslateLanguage', - value: language + value: language, + options: null }); this.setState({ selectedLanguage: language }); } catch (error) { logEvent(events.AT_SET_LANG_F); console.log(error); } - } + }; renderIcon = () => { const { theme } = this.props; return <List.Icon name='check' style={{ color: themes[theme].tintColor }} />; - } + }; renderSwitch = () => { const { enableAutoTranslate } = this.state; - return ( - <Switch - value={enableAutoTranslate} - trackColor={SWITCH_TRACK_COLOR} - onValueChange={this.toggleAutoTranslate} - /> - ); - } + return <Switch value={enableAutoTranslate} trackColor={SWITCH_TRACK_COLOR} onValueChange={this.toggleAutoTranslate} />; + }; - renderItem = ({ item }) => { + renderItem = ({ item }: { item: { language: string; name: string } }) => { const { selectedLanguage } = this.state; const { language, name } = item; const isSelected = selectedLanguage === language; @@ -128,12 +138,12 @@ class AutoTranslateView extends React.Component { <List.Item title={name || language} onPress={() => this.saveAutoTranslateLanguage(language)} - testID={`auto-translate-view-${ language }`} + testID={`auto-translate-view-${language}`} right={isSelected ? this.renderIcon : null} translateTitle={false} /> ); - } + }; render() { const { languages } = this.state; @@ -143,11 +153,7 @@ class AutoTranslateView extends React.Component { <List.Container testID='auto-translate-view-list'> <List.Section> <List.Separator /> - <List.Item - title='Enable_Auto_Translate' - testID='auto-translate-view-switch' - right={() => this.renderSwitch()} - /> + <List.Item title='Enable_Auto_Translate' testID='auto-translate-view-switch' right={() => this.renderSwitch()} /> <List.Separator /> </List.Section> <FlatList diff --git a/app/views/CannedResponseDetail.js b/app/views/CannedResponseDetail.js new file mode 100644 index 000000000..67002bbdb --- /dev/null +++ b/app/views/CannedResponseDetail.js @@ -0,0 +1,171 @@ +import React, { useEffect } from 'react'; +import PropTypes from 'prop-types'; +import { StyleSheet, Text, View, ScrollView } from 'react-native'; +import { useSelector } from 'react-redux'; + +import I18n from '../i18n'; +import SafeAreaView from '../containers/SafeAreaView'; +import StatusBar from '../containers/StatusBar'; +import Button from '../containers/Button'; +import { useTheme } from '../theme'; +import RocketChat from '../lib/rocketchat'; +import Navigation from '../lib/Navigation'; +import { goRoom } from '../utils/goRoom'; +import { themes } from '../constants/colors'; +import Markdown from '../containers/markdown'; +import sharedStyles from './Styles'; + +const styles = StyleSheet.create({ + scroll: { + flex: 1 + }, + container: { + flex: 1, + marginTop: 12, + marginHorizontal: 15 + }, + cannedText: { + marginTop: 8, + marginBottom: 16, + fontSize: 14, + paddingTop: 0, + paddingBottom: 0, + ...sharedStyles.textRegular + }, + cannedTagWrap: { + borderRadius: 4, + marginRight: 4, + marginTop: 8, + height: 16 + }, + cannedTagContainer: { + flexDirection: 'row', + flexWrap: 'wrap' + }, + cannedTag: { + fontSize: 12, + paddingTop: 0, + paddingBottom: 0, + paddingHorizontal: 4, + ...sharedStyles.textRegular + }, + button: { + margin: 24, + marginBottom: 24 + }, + item: { + paddingVertical: 10, + justifyContent: 'center' + }, + itemLabel: { + marginBottom: 10, + fontSize: 14, + ...sharedStyles.textMedium + }, + itemContent: { + fontSize: 14, + ...sharedStyles.textRegular + } +}); + +const Item = ({ label, content, theme, testID }) => + content ? ( + <View style={styles.item} testID={testID}> + <Text accessibilityLabel={label} style={[styles.itemLabel, { color: themes[theme].titleText }]}> + {label} + </Text> + <Markdown style={[styles.itemContent, { color: themes[theme].auxiliaryText }]} msg={content} theme={theme} /> + </View> + ) : null; +Item.propTypes = { + label: PropTypes.string, + content: PropTypes.string, + theme: PropTypes.string, + testID: PropTypes.string +}; + +const CannedResponseDetail = ({ navigation, route }) => { + const { cannedResponse } = route?.params; + const { theme } = useTheme(); + const { isMasterDetail } = useSelector(state => state.app); + const { rooms } = useSelector(state => state.room); + + useEffect(() => { + navigation.setOptions({ + title: `!${cannedResponse?.shortcut}` + }); + }, []); + + const navigateToRoom = item => { + const { room } = route.params; + const { name, username } = room; + const params = { + rid: room.rid, + name: RocketChat.getRoomTitle({ + t: room.t, + fname: name, + name: username + }), + t: room.t, + roomUserId: RocketChat.getUidDirectMessage(room), + usedCannedResponse: item.text + }; + + if (room.rid) { + // if it's on master detail layout, we close the modal and replace RoomView + if (isMasterDetail) { + Navigation.navigate('DrawerNavigator'); + goRoom({ item: params, isMasterDetail, usedCannedResponse: item.text }); + } else { + let navigate = navigation.push; + // if this is a room focused + if (rooms.includes(room.rid)) { + ({ navigate } = navigation); + } + navigate('RoomView', params); + } + } + }; + + return ( + <SafeAreaView> + <ScrollView contentContainerStyle={[styles.scroll, { backgroundColor: themes[theme].messageboxBackground }]}> + <StatusBar /> + <View style={styles.container}> + <Item label={I18n.t('Shortcut')} content={`!${cannedResponse?.shortcut}`} theme={theme} /> + <Item label={I18n.t('Content')} content={cannedResponse?.text} theme={theme} /> + <Item label={I18n.t('Sharing')} content={cannedResponse?.scopeName} theme={theme} /> + + <View style={styles.item}> + <Text style={[styles.itemLabel, { color: themes[theme].titleText }]}>{I18n.t('Tags')}</Text> + <View style={styles.cannedTagContainer}> + {cannedResponse?.tags?.length > 0 ? ( + cannedResponse.tags.map(t => ( + <View style={[styles.cannedTagWrap, { backgroundColor: themes[theme].searchboxBackground }]}> + <Text style={[styles.cannedTag, { color: themes[theme].auxiliaryTintColor }]}>{t}</Text> + </View> + )) + ) : ( + <Text style={[styles.cannedText, { color: themes[theme].auxiliaryTintColor }]}>-</Text> + )} + </View> + </View> + </View> + <Button + title={I18n.t('Use')} + theme={theme} + style={styles.button} + type='primary' + onPress={() => navigateToRoom(cannedResponse)} + /> + </ScrollView> + </SafeAreaView> + ); +}; + +CannedResponseDetail.propTypes = { + navigation: PropTypes.object, + route: PropTypes.object +}; + +export default CannedResponseDetail; diff --git a/app/views/CannedResponsesListView/CannedResponseItem.js b/app/views/CannedResponsesListView/CannedResponseItem.js new file mode 100644 index 000000000..cc5f3c39a --- /dev/null +++ b/app/views/CannedResponsesListView/CannedResponseItem.js @@ -0,0 +1,61 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { View, Text } from 'react-native'; + +import Touchable from 'react-native-platform-touchable'; +import { themes } from '../../constants/colors'; +import Button from '../../containers/Button'; +import I18n from '../../i18n'; +import styles from './styles'; + +const CannedResponseItem = ({ theme, onPressDetail, shortcut, scope, onPressUse, text, tags }) => ( + <Touchable onPress={onPressDetail} style={[styles.wrapCannedItem, { backgroundColor: themes[theme].messageboxBackground }]}> + <> + <View style={styles.cannedRow}> + <View style={styles.cannedWrapShortcutScope}> + <Text style={[styles.cannedShortcut, { color: themes[theme].titleText }]}>!{shortcut}</Text> + <Text style={[styles.cannedScope, { color: themes[theme].auxiliaryTintColor }]}>{scope}</Text> + </View> + + <Button + title={I18n.t('Use')} + fontSize={12} + color={themes[theme].titleText} + style={[styles.cannedUseButton, { backgroundColor: themes[theme].chatComponentBackground }]} + theme={theme} + onPress={onPressUse} + /> + </View> + + <Text ellipsizeMode='tail' numberOfLines={2} style={[styles.cannedText, { color: themes[theme].auxiliaryTintColor }]}> + “{text}” + </Text> + <View style={styles.cannedTagContainer}> + {tags?.length > 0 + ? tags.map(t => ( + <View style={[styles.cannedTagWrap, { backgroundColor: themes[theme].searchboxBackground }]}> + <Text style={[styles.cannedTag, { color: themes[theme].auxiliaryTintColor }]}>{t}</Text> + </View> + )) + : null} + </View> + </> + </Touchable> +); + +CannedResponseItem.propTypes = { + theme: PropTypes.string, + onPressDetail: PropTypes.func, + shortcut: PropTypes.string, + scope: PropTypes.string, + onPressUse: PropTypes.func, + text: PropTypes.string, + tags: PropTypes.array +}; + +CannedResponseItem.defaultProps = { + onPressDetail: () => {}, + onPressUse: () => {} +}; + +export default CannedResponseItem; diff --git a/app/views/CannedResponsesListView/CannedResponseItem.stories.js b/app/views/CannedResponsesListView/CannedResponseItem.stories.js new file mode 100644 index 000000000..5e49f8507 --- /dev/null +++ b/app/views/CannedResponsesListView/CannedResponseItem.stories.js @@ -0,0 +1,64 @@ +/* eslint-disable import/no-extraneous-dependencies */ +import React from 'react'; +import { storiesOf } from '@storybook/react-native'; + +import CannedResponseItem from './CannedResponseItem'; + +const stories = storiesOf('CannedResponseItem', module); + +const item = [ + { + _id: 'x1-x1-x1', + shortcut: '!FAQ4', + text: 'ZCVXZVXCZVZXVZXCVZXCVXZCVZX', + scope: 'user', + userId: 'xxx-x-xx-x-x-', + createdBy: { + _id: 'xxx-x-xx-x-x-', + username: 'rocket.cat' + }, + _createdAt: '2021-08-11T01:23:17.379Z', + _updatedAt: '2021-08-11T01:23:17.379Z', + scopeName: 'Private' + }, + { + _id: 'x1-1x-1x', + shortcut: 'test4mobilePrivate', + text: 'test for mobile private', + scope: 'user', + tags: ['HQ', 'Closed', 'HQ', 'Problem in Product Y', 'HQ', 'Closed', 'Problem in Product Y'], + userId: 'laslsaklasal', + createdBy: { + _id: 'laslsaklasal', + username: 'reinaldo.neto' + }, + _createdAt: '2021-09-02T17:44:52.095Z', + _updatedAt: '2021-09-02T18:24:40.436Z', + scopeName: 'Private' + } +]; + +const theme = 'light'; + +stories.add('Itens', () => ( + <> + <CannedResponseItem + theme={theme} + scope={item[0].scopeName} + shortcut={item[0].shortcut} + tags={item[0]?.tags} + text={item[0].text} + onPressDetail={() => alert('navigation to CannedResponseDetail')} + onPressUse={() => alert('Back to RoomView and wrote in MessageBox')} + /> + <CannedResponseItem + theme={theme} + scope={item[1].scopeName} + shortcut={item[1].shortcut} + tags={item[1]?.tags} + text={item[1].text} + onPressDetail={() => alert('navigation to CannedResponseDetail')} + onPressUse={() => alert('Back to RoomView and wrote in MessageBox')} + /> + </> +)); diff --git a/app/views/CannedResponsesListView/Dropdown/DropdownItem.js b/app/views/CannedResponsesListView/Dropdown/DropdownItem.js new file mode 100644 index 000000000..85b9aa703 --- /dev/null +++ b/app/views/CannedResponsesListView/Dropdown/DropdownItem.js @@ -0,0 +1,44 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { StyleSheet, Text, View } from 'react-native'; + +import { themes } from '../../../constants/colors'; +import { withTheme } from '../../../theme'; +import Touch from '../../../utils/touch'; +import { CustomIcon } from '../../../lib/Icons'; +import sharedStyles from '../../Styles'; + +export const ROW_HEIGHT = 44; + +const styles = StyleSheet.create({ + container: { + paddingVertical: 11, + height: ROW_HEIGHT, + paddingHorizontal: 16, + flexDirection: 'row', + alignItems: 'center' + }, + text: { + flex: 1, + fontSize: 16, + ...sharedStyles.textRegular + } +}); + +const DropdownItem = React.memo(({ theme, onPress, iconName, text }) => ( + <Touch theme={theme} onPress={onPress} style={{ backgroundColor: themes[theme].backgroundColor }}> + <View style={styles.container}> + <Text style={[styles.text, { color: themes[theme].auxiliaryText }]}>{text}</Text> + {iconName ? <CustomIcon name={iconName} size={22} color={themes[theme].auxiliaryText} /> : null} + </View> + </Touch> +)); + +DropdownItem.propTypes = { + text: PropTypes.string, + iconName: PropTypes.string, + theme: PropTypes.string, + onPress: PropTypes.func +}; + +export default withTheme(DropdownItem); diff --git a/app/views/CannedResponsesListView/Dropdown/DropdownItemFilter.js b/app/views/CannedResponsesListView/Dropdown/DropdownItemFilter.js new file mode 100644 index 000000000..d4e457805 --- /dev/null +++ b/app/views/CannedResponsesListView/Dropdown/DropdownItemFilter.js @@ -0,0 +1,20 @@ +import React from 'react'; +import PropTypes from 'prop-types'; + +import DropdownItem from './DropdownItem'; + +const DropdownItemFilter = ({ currentDepartment, value, onPress }) => ( + <DropdownItem + text={value?.name} + iconName={currentDepartment?._id === value?._id ? 'check' : null} + onPress={() => onPress(value)} + /> +); + +DropdownItemFilter.propTypes = { + currentDepartment: PropTypes.object, + value: PropTypes.string, + onPress: PropTypes.func +}; + +export default DropdownItemFilter; diff --git a/app/views/CannedResponsesListView/Dropdown/DropdownItemHeader.js b/app/views/CannedResponsesListView/Dropdown/DropdownItemHeader.js new file mode 100644 index 000000000..4f1f2b68f --- /dev/null +++ b/app/views/CannedResponsesListView/Dropdown/DropdownItemHeader.js @@ -0,0 +1,15 @@ +import React from 'react'; +import PropTypes from 'prop-types'; + +import DropdownItem from './DropdownItem'; + +const DropdownItemHeader = ({ department, onPress }) => ( + <DropdownItem text={department?.name} iconName='filter' onPress={onPress} /> +); + +DropdownItemHeader.propTypes = { + department: PropTypes.object, + onPress: PropTypes.func +}; + +export default DropdownItemHeader; diff --git a/app/views/CannedResponsesListView/Dropdown/index.js b/app/views/CannedResponsesListView/Dropdown/index.js new file mode 100644 index 000000000..e2735e5c5 --- /dev/null +++ b/app/views/CannedResponsesListView/Dropdown/index.js @@ -0,0 +1,106 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { Animated, Easing, FlatList, TouchableWithoutFeedback } from 'react-native'; +import { withSafeAreaInsets } from 'react-native-safe-area-context'; + +import styles from '../styles'; +import { themes } from '../../../constants/colors'; +import { withTheme } from '../../../theme'; +import { headerHeight } from '../../../containers/Header'; +import * as List from '../../../containers/List'; +import DropdownItemFilter from './DropdownItemFilter'; +import DropdownItemHeader from './DropdownItemHeader'; +import { ROW_HEIGHT } from './DropdownItem'; + +const ANIMATION_DURATION = 200; + +class Dropdown extends React.Component { + static propTypes = { + isMasterDetail: PropTypes.bool, + theme: PropTypes.string, + insets: PropTypes.object, + currentDepartment: PropTypes.object, + onClose: PropTypes.func, + onDepartmentSelected: PropTypes.func, + departments: PropTypes.array + }; + + constructor(props) { + super(props); + this.animatedValue = new Animated.Value(0); + } + + componentDidMount() { + Animated.timing(this.animatedValue, { + toValue: 1, + duration: ANIMATION_DURATION, + easing: Easing.inOut(Easing.quad), + useNativeDriver: true + }).start(); + } + + close = () => { + const { onClose } = this.props; + Animated.timing(this.animatedValue, { + toValue: 0, + duration: ANIMATION_DURATION, + easing: Easing.inOut(Easing.quad), + useNativeDriver: true + }).start(() => onClose()); + }; + + render() { + const { isMasterDetail, insets, theme, currentDepartment, onDepartmentSelected, departments } = this.props; + const statusBarHeight = insets?.top ?? 0; + const heightDestination = isMasterDetail ? headerHeight + statusBarHeight : 0; + const translateY = this.animatedValue.interpolate({ + inputRange: [0, 1], + outputRange: [-300, heightDestination] // approximated height of the component when closed/open + }); + const backdropOpacity = this.animatedValue.interpolate({ + inputRange: [0, 1], + outputRange: [0, themes[theme].backdropOpacity] + }); + + const maxRows = 5; + return ( + <> + <TouchableWithoutFeedback onPress={this.close}> + <Animated.View + style={[ + styles.backdrop, + { + backgroundColor: themes[theme].backdropColor, + opacity: backdropOpacity, + top: heightDestination + } + ]} + /> + </TouchableWithoutFeedback> + <Animated.View + style={[ + styles.dropdownContainer, + { + transform: [{ translateY }], + backgroundColor: themes[theme].backgroundColor, + borderColor: themes[theme].separatorColor + } + ]}> + <DropdownItemHeader department={currentDepartment} onPress={this.close} /> + <List.Separator /> + <FlatList + style={{ maxHeight: maxRows * ROW_HEIGHT }} + data={departments} + keyExtractor={item => item._id} + renderItem={({ item }) => ( + <DropdownItemFilter onPress={onDepartmentSelected} currentDepartment={currentDepartment} value={item} /> + )} + keyboardShouldPersistTaps='always' + /> + </Animated.View> + </> + ); + } +} + +export default withTheme(withSafeAreaInsets(Dropdown)); diff --git a/app/views/CannedResponsesListView/index.js b/app/views/CannedResponsesListView/index.js new file mode 100644 index 000000000..f9f515deb --- /dev/null +++ b/app/views/CannedResponsesListView/index.js @@ -0,0 +1,363 @@ +import React, { useEffect, useState, useCallback } from 'react'; +import PropTypes from 'prop-types'; +import { FlatList } from 'react-native'; +import { useSelector } from 'react-redux'; +import { useSafeAreaInsets } from 'react-native-safe-area-context'; +import { HeaderBackButton } from '@react-navigation/stack'; + +import database from '../../lib/database'; +import I18n from '../../i18n'; +import SafeAreaView from '../../containers/SafeAreaView'; +import StatusBar from '../../containers/StatusBar'; +import ActivityIndicator from '../../containers/ActivityIndicator'; +import SearchHeader from '../../containers/SearchHeader'; +import BackgroundContainer from '../../containers/BackgroundContainer'; +import { getHeaderTitlePosition } from '../../containers/Header'; +import { useTheme } from '../../theme'; +import RocketChat from '../../lib/rocketchat'; +import debounce from '../../utils/debounce'; +import Navigation from '../../lib/Navigation'; +import { goRoom } from '../../utils/goRoom'; +import * as HeaderButton from '../../containers/HeaderButton'; +import * as List from '../../containers/List'; +import { themes } from '../../constants/colors'; +import log from '../../utils/log'; +import CannedResponseItem from './CannedResponseItem'; +import Dropdown from './Dropdown'; +import DropdownItemHeader from './Dropdown/DropdownItemHeader'; +import styles from './styles'; + +const COUNT = 25; + +const fixedScopes = [ + { + _id: 'all', + name: I18n.t('All') + }, + { + _id: 'global', + name: I18n.t('Public') + }, + { + _id: 'user', + name: I18n.t('Private') + } +]; + +const CannedResponsesListView = ({ navigation, route }) => { + const [room, setRoom] = useState(null); + + const [cannedResponses, setCannedResponses] = useState([]); + const [cannedResponsesScopeName, setCannedResponsesScopeName] = useState([]); + const [departments, setDepartments] = useState([]); + + // states used by the filter in Header and Dropdown + const [isSearching, setIsSearching] = useState(false); + const [currentDepartment, setCurrentDepartment] = useState(fixedScopes[0]); + const [showFilterDropdown, setShowFilterDropDown] = useState(false); + + // states used to do a fetch by onChangeText, onDepartmentSelect and onEndReached + const [searchText, setSearchText] = useState(''); + const [scope, setScope] = useState(''); + const [departmentId, setDepartmentId] = useState(''); + const [loading, setLoading] = useState(true); + const [offset, setOffset] = useState(0); + + const insets = useSafeAreaInsets(); + const { theme } = useTheme(); + const { isMasterDetail } = useSelector(state => state.app); + const { rooms } = useSelector(state => state.room); + + const getRoomFromDb = async () => { + const { rid } = route.params; + const db = database.active; + const subsCollection = db.get('subscriptions'); + try { + const r = await subsCollection.find(rid); + setRoom(r); + } catch (error) { + console.log('CannedResponsesListView: Room not found'); + log(error); + } + }; + + const getDepartments = debounce(async () => { + try { + const res = await RocketChat.getDepartments(); + if (res.success) { + setDepartments([...fixedScopes, ...res.departments]); + } + } catch (e) { + setDepartments(fixedScopes); + log(e); + } + }, 300); + + const goToDetail = item => { + navigation.navigate('CannedResponseDetail', { cannedResponse: item, room }); + }; + + const navigateToRoom = item => { + if (!room) { + return; + } + const { name, username } = room; + const params = { + rid: room.rid, + name: RocketChat.getRoomTitle({ + t: room.t, + fname: name, + name: username + }), + t: room.t, + roomUserId: RocketChat.getUidDirectMessage(room), + usedCannedResponse: item.text + }; + + if (room.rid) { + // if it's on master detail layout, we close the modal and replace RoomView + if (isMasterDetail) { + Navigation.navigate('DrawerNavigator'); + goRoom({ item: params, isMasterDetail, usedCannedResponse: item.text }); + } else { + let navigate = navigation.push; + // if this is a room focused + if (rooms.includes(room.rid)) { + ({ navigate } = navigation); + } + navigate('RoomView', params); + } + } + }; + + const getListCannedResponse = async ({ text, department, depId, debounced }) => { + try { + const res = await RocketChat.getListCannedResponse({ + text, + offset, + count: COUNT, + departmentId: depId, + scope: department + }); + if (res.success) { + // search with changes on text or scope are debounced + // the begin result and pagination aren't debounced + setCannedResponses(prevCanned => (debounced ? res.cannedResponses : [...prevCanned, ...res.cannedResponses])); + setLoading(false); + setOffset(prevOffset => prevOffset + COUNT); + } + } catch (e) { + log(e); + } + }; + + useEffect(() => { + if (departments.length > 0) { + const newCannedResponses = cannedResponses.map(cr => { + let scopeName = ''; + + if (cr?.departmentId) { + scopeName = departments.filter(dep => dep._id === cr.departmentId)[0]?.name || 'Department'; + } else { + scopeName = departments.filter(dep => dep._id === cr.scope)[0]?.name; + } + cr.scopeName = scopeName; + + return cr; + }); + setCannedResponsesScopeName(newCannedResponses); + } + }, [departments, cannedResponses]); + + const searchCallback = useCallback( + debounce(async (text = '', department = '', depId = '') => { + await getListCannedResponse({ text, department, depId, debounced: true }); + }, 1000), + [] + ); // use debounce with useCallback https://stackoverflow.com/a/58594890 + + useEffect(() => { + getRoomFromDb(); + getDepartments(); + getListCannedResponse({ text: '', department: '', depId: '', debounced: false }); + }, []); + + const newSearch = () => { + setCannedResponses([]); + setLoading(true); + setOffset(0); + }; + + const onChangeText = text => { + newSearch(); + setSearchText(text); + searchCallback(text, scope, departmentId); + }; + + const onDepartmentSelect = value => { + let department = ''; + let depId = ''; + + if (value._id === fixedScopes[0]._id) { + department = ''; + } else if (value._id === fixedScopes[1]._id) { + department = 'global'; + } else if (value._id === fixedScopes[2]._id) { + department = 'user'; + } else { + department = 'department'; + depId = value._id; + } + + newSearch(); + setCurrentDepartment(value); + setScope(department); + setDepartmentId(depId); + setShowFilterDropDown(false); + searchCallback(searchText, department, depId); + }; + + const onEndReached = async () => { + if (cannedResponses.length < offset || loading) { + return; + } + setLoading(true); + await getListCannedResponse({ text: searchText, department: scope, depId: departmentId, debounced: false }); + }; + + const getHeader = () => { + if (isSearching) { + const headerTitlePosition = getHeaderTitlePosition({ insets, numIconsRight: 1 }); + return { + headerTitleAlign: 'left', + headerLeft: () => ( + <HeaderButton.Container left> + <HeaderButton.Item + iconName='close' + onPress={() => { + onChangeText(); + setIsSearching(false); + }} + /> + </HeaderButton.Container> + ), + headerTitle: () => <SearchHeader onSearchChangeText={onChangeText} />, + headerTitleContainerStyle: { + left: headerTitlePosition.left, + right: headerTitlePosition.right + }, + headerRight: () => null + }; + } + + const options = { + headerLeft: () => ( + <HeaderBackButton labelVisible={false} onPress={() => navigation.pop()} tintColor={themes[theme].headerTintColor} /> + ), + headerTitleAlign: 'center', + headerTitle: I18n.t('Canned_Responses'), + headerTitleContainerStyle: { + left: null, + right: null + } + }; + + if (isMasterDetail) { + options.headerLeft = () => <HeaderButton.CloseModal navigation={navigation} />; + } + + options.headerRight = () => ( + <HeaderButton.Container> + <HeaderButton.Item iconName='search' onPress={() => setIsSearching(true)} /> + </HeaderButton.Container> + ); + return options; + }; + + const setHeader = () => { + const options = getHeader(); + navigation.setOptions(options); + }; + + useEffect(() => { + setHeader(); + }, [isSearching]); + + const showDropdown = () => { + if (isSearching) { + setSearchText(''); + setIsSearching(false); + } + setShowFilterDropDown(true); + }; + + const renderFlatListHeader = () => { + if (!departments.length) { + return null; + } + return ( + <> + <DropdownItemHeader department={currentDepartment} onPress={showDropdown} /> + <List.Separator /> + </> + ); + }; + + const renderContent = () => { + if (!cannedResponsesScopeName.length && !loading) { + return ( + <> + {renderFlatListHeader()} + <BackgroundContainer text={I18n.t('No_canned_responses')} /> + </> + ); + } + return ( + <FlatList + data={cannedResponsesScopeName} + extraData={cannedResponsesScopeName} + style={[styles.list, { backgroundColor: themes[theme].backgroundColor }]} + renderItem={({ item }) => ( + <CannedResponseItem + theme={theme} + scope={item.scopeName} + shortcut={item.shortcut} + tags={item?.tags} + text={item.text} + onPressDetail={() => goToDetail(item)} + onPressUse={() => navigateToRoom(item)} + /> + )} + keyExtractor={item => item._id || item.shortcut} + ListHeaderComponent={renderFlatListHeader} + stickyHeaderIndices={[0]} + onEndReached={onEndReached} + onEndReachedThreshold={0.5} + ItemSeparatorComponent={List.Separator} + ListFooterComponent={loading ? <ActivityIndicator theme={theme} /> : null} + /> + ); + }; + + return ( + <SafeAreaView> + <StatusBar /> + {renderContent()} + {showFilterDropdown ? ( + <Dropdown + departments={departments} + currentDepartment={currentDepartment} + onDepartmentSelected={onDepartmentSelect} + onClose={() => setShowFilterDropDown(false)} + /> + ) : null} + </SafeAreaView> + ); +}; + +CannedResponsesListView.propTypes = { + navigation: PropTypes.object, + route: PropTypes.object +}; + +export default CannedResponsesListView; diff --git a/app/views/CannedResponsesListView/styles.js b/app/views/CannedResponsesListView/styles.js new file mode 100644 index 000000000..d9b8f580c --- /dev/null +++ b/app/views/CannedResponsesListView/styles.js @@ -0,0 +1,73 @@ +import { StyleSheet } from 'react-native'; + +import sharedStyles from '../Styles'; + +export default StyleSheet.create({ + list: { + flex: 1 + }, + dropdownContainer: { + width: '100%', + position: 'absolute', + top: 0, + borderBottomWidth: StyleSheet.hairlineWidth + }, + backdrop: { + ...StyleSheet.absoluteFill + }, + wrapCannedItem: { + minHeight: 117, + maxHeight: 141, + padding: 16 + }, + cannedRow: { + flexDirection: 'row', + height: 36 + }, + cannedWrapShortcutScope: { + flex: 1 + }, + cannedShortcut: { + flex: 1, + fontSize: 14, + paddingTop: 0, + paddingBottom: 0, + ...sharedStyles.textMedium + }, + cannedScope: { + flex: 1, + fontSize: 12, + paddingTop: 0, + paddingBottom: 0, + ...sharedStyles.textRegular + }, + cannedText: { + marginTop: 8, + fontSize: 14, + paddingTop: 0, + paddingBottom: 0, + ...sharedStyles.textRegular + }, + cannedTagContainer: { + flexDirection: 'row', + overflow: 'hidden' + }, + cannedTagWrap: { + borderRadius: 4, + marginRight: 4, + marginTop: 8, + height: 16 + }, + cannedTag: { + fontSize: 12, + paddingTop: 0, + paddingBottom: 0, + paddingHorizontal: 4, + ...sharedStyles.textRegular + }, + cannedUseButton: { + height: 28, + width: 56, + marginLeft: 8 + } +}); diff --git a/app/views/ChangePasscodeView.js b/app/views/ChangePasscodeView.js index 535ba71d4..ef29abe18 100644 --- a/app/views/ChangePasscodeView.js +++ b/app/views/ChangePasscodeView.js @@ -8,7 +8,7 @@ import Modal from 'react-native-modal'; import Touchable from 'react-native-platform-touchable'; import { withTheme } from '../theme'; -import { isTablet, hasNotch } from '../utils/deviceInfo'; +import { hasNotch, isTablet } from '../utils/deviceInfo'; import { TYPE } from '../containers/Passcode/constants'; import { PasscodeChoose } from '../containers/Passcode'; import EventEmitter from '../utils/events'; @@ -39,11 +39,11 @@ const ChangePasscodeView = React.memo(({ theme }) => { } }, [data]); - const showChangePasscode = (args) => { + const showChangePasscode = args => { setData(args); }; - const onSubmit = (passcode) => { + const onSubmit = passcode => { const { submit } = data; if (submit) { submit(passcode); @@ -64,29 +64,22 @@ const ChangePasscodeView = React.memo(({ theme }) => { Orientation.lockToPortrait(); } const listener = EventEmitter.addEventListener(CHANGE_PASSCODE_EMITTER, showChangePasscode); - return (() => { + return () => { if (!isTablet) { Orientation.unlockAllOrientations(); } EventEmitter.removeListener(CHANGE_PASSCODE_EMITTER, listener); - }); + }; }, []); return ( - <Modal - useNativeDriver - isVisible={visible} - hideModalContentWhileAnimating - style={styles.modal} - > + <Modal useNativeDriver isVisible={visible} hideModalContentWhileAnimating style={styles.modal}> <PasscodeChoose theme={theme} type={TYPE.choose} finishProcess={onSubmit} force={data?.force} /> - {!data?.force - ? ( - <Touchable onPress={onCancel} style={styles.close}> - <CustomIcon name='close' color={themes[theme].passcodePrimary} size={30} /> - </Touchable> - ) - : null} + {!data?.force ? ( + <Touchable onPress={onCancel} style={styles.close}> + <CustomIcon name='close' color={themes[theme].passcodePrimary} size={30} /> + </Touchable> + ) : null} </Modal> ); }); diff --git a/app/views/CreateChannelView.js b/app/views/CreateChannelView.js index 78f4ea42a..af17961fb 100644 --- a/app/views/CreateChannelView.js +++ b/app/views/CreateChannelView.js @@ -1,17 +1,14 @@ import React from 'react'; import { connect } from 'react-redux'; import PropTypes from 'prop-types'; -import { - View, Text, Switch, ScrollView, StyleSheet, FlatList -} from 'react-native'; +import { FlatList, ScrollView, StyleSheet, Switch, Text, View } from 'react-native'; import { dequal } from 'dequal'; -import * as List from '../containers/List'; +import * as List from '../containers/List'; import TextInput from '../presentation/TextInput'; import Loading from '../containers/Loading'; import { createChannelRequest as createChannelRequestAction } from '../actions/createChannel'; import { removeUser as removeUserAction } from '../actions/selectedUsers'; -import sharedStyles from './Styles'; import KeyboardView from '../presentation/KeyboardView'; import scrollPersistTaps from '../utils/scrollPersistTaps'; import I18n from '../i18n'; @@ -22,8 +19,9 @@ import { SWITCH_TRACK_COLOR, themes } from '../constants/colors'; import { withTheme } from '../theme'; import { Review } from '../utils/review'; import { getUserSelector } from '../selectors/login'; -import { logEvent, events } from '../utils/log'; +import { events, logEvent } from '../utils/log'; import SafeAreaView from '../containers/SafeAreaView'; +import sharedStyles from './Styles'; const styles = StyleSheet.create({ container: { @@ -104,12 +102,8 @@ class CreateChannelView extends React.Component { } shouldComponentUpdate(nextProps, nextState) { - const { - channelName, type, readOnly, broadcast, encrypted - } = this.state; - const { - users, isFetching, encryptionEnabled, theme - } = this.props; + const { channelName, type, readOnly, broadcast, encrypted } = this.state; + const { users, isFetching, encryptionEnabled, theme } = this.props; if (nextProps.theme !== theme) { return true; } @@ -147,31 +141,28 @@ class CreateChannelView extends React.Component { navigation.setOptions({ title: isTeam ? I18n.t('Create_Team') : I18n.t('Create_Channel') }); - } + }; - toggleRightButton = (channelName) => { + toggleRightButton = channelName => { const { navigation } = this.props; navigation.setOptions({ - headerRight: () => channelName.trim().length > 0 && ( - <HeaderButton.Container> - <HeaderButton.Item title={I18n.t('Create')} onPress={this.submit} testID='create-channel-submit' /> - </HeaderButton.Container> - ) + headerRight: () => + channelName.trim().length > 0 && ( + <HeaderButton.Container> + <HeaderButton.Item title={I18n.t('Create')} onPress={this.submit} testID='create-channel-submit' /> + </HeaderButton.Container> + ) }); - } + }; - onChangeText = (channelName) => { + onChangeText = channelName => { this.toggleRightButton(channelName); this.setState({ channelName }); - } + }; submit = () => { - const { - channelName, type, readOnly, broadcast, encrypted, isTeam - } = this.state; - const { - users: usersProps, isFetching, create - } = this.props; + const { channelName, type, readOnly, broadcast, encrypted, isTeam } = this.state; + const { users: usersProps, isFetching, create } = this.props; if (!channelName.trim() || isFetching) { return; @@ -182,21 +173,26 @@ class CreateChannelView extends React.Component { // create channel or team create({ - name: channelName, users, type, readOnly, broadcast, encrypted, isTeam, teamId: this.teamId + name: channelName, + users, + type, + readOnly, + broadcast, + encrypted, + isTeam, + teamId: this.teamId }); Review.pushPositiveEvent(); - } + }; - removeUser = (user) => { + removeUser = user => { logEvent(events.CR_REMOVE_USER); const { removeUser } = this.props; removeUser(user); - } + }; - renderSwitch = ({ - id, value, label, onValueChange, disabled = false - }) => { + renderSwitch = ({ id, value, label, onValueChange, disabled = false }) => { const { theme } = this.props; return ( <View style={[styles.switchContainer, { backgroundColor: themes[theme].backgroundColor }]}> @@ -204,13 +200,13 @@ class CreateChannelView extends React.Component { <Switch value={value} onValueChange={onValueChange} - testID={`create-channel-${ id }`} + testID={`create-channel-${id}`} trackColor={SWITCH_TRACK_COLOR} disabled={disabled} /> </View> ); - } + }; renderType() { const { type, isTeam } = this.state; @@ -219,7 +215,7 @@ class CreateChannelView extends React.Component { id: 'type', value: type, label: isTeam ? 'Private_Team' : 'Private_Channel', - onValueChange: (value) => { + onValueChange: value => { logEvent(events.CR_TOGGLE_TYPE); // If we set the channel as public, encrypted status should be false this.setState(({ encrypted }) => ({ type: value, encrypted: value && encrypted })); @@ -234,7 +230,7 @@ class CreateChannelView extends React.Component { id: 'readonly', value: readOnly, label: isTeam ? 'Read_Only_Team' : 'Read_Only_Channel', - onValueChange: (value) => { + onValueChange: value => { logEvent(events.CR_TOGGLE_READ_ONLY); this.setState({ readOnly: value }); }, @@ -254,7 +250,7 @@ class CreateChannelView extends React.Component { id: 'encrypted', value: encrypted, label: 'Encrypted', - onValueChange: (value) => { + onValueChange: value => { logEvent(events.CR_TOGGLE_ENCRYPTED); this.setState({ encrypted: value }); }, @@ -269,7 +265,7 @@ class CreateChannelView extends React.Component { id: 'broadcast', value: broadcast, label: isTeam ? 'Broadcast_Team' : 'Broadcast_Channel', - onValueChange: (value) => { + onValueChange: value => { logEvent(events.CR_TOGGLE_BROADCAST); this.setState({ broadcast: value, @@ -287,14 +283,14 @@ class CreateChannelView extends React.Component { name={item.fname} username={item.name} onPress={() => this.removeUser(item)} - testID={`create-channel-view-item-${ item.name }`} + testID={`create-channel-view-item-${item.name}`} icon='check' baseUrl={baseUrl} user={user} theme={theme} /> ); - } + }; renderInvitedList = () => { const { users, theme } = this.props; @@ -318,21 +314,18 @@ class CreateChannelView extends React.Component { keyboardShouldPersistTaps='always' /> ); - } + }; render() { const { channelName, isTeam } = this.state; - const { - users, isFetching, theme - } = this.props; + const { users, isFetching, theme } = this.props; const userCount = users.length; return ( <KeyboardView style={{ backgroundColor: themes[theme].auxiliaryBackground }} contentContainerStyle={[sharedStyles.container, styles.container]} - keyboardVerticalOffset={128} - > + keyboardVerticalOffset={128}> <StatusBar /> <SafeAreaView testID='create-channel-view'> <ScrollView {...scrollPersistTaps}> @@ -362,7 +355,9 @@ class CreateChannelView extends React.Component { </View> <View style={styles.invitedHeader}> <Text style={[styles.invitedTitle, { color: themes[theme].titleText }]}>{I18n.t('Invite')}</Text> - <Text style={[styles.invitedCount, { color: themes[theme].auxiliaryText }]}>{userCount === 1 ? I18n.t('1_user') : I18n.t('N_users', { n: userCount })}</Text> + <Text style={[styles.invitedCount, { color: themes[theme].auxiliaryText }]}> + {userCount === 1 ? I18n.t('1_user') : I18n.t('N_users', { n: userCount })} + </Text> </View> {this.renderInvitedList()} <Loading visible={isFetching} /> diff --git a/app/views/CreateDiscussionView/SelectChannel.js b/app/views/CreateDiscussionView/SelectChannel.tsx similarity index 59% rename from app/views/CreateDiscussionView/SelectChannel.js rename to app/views/CreateDiscussionView/SelectChannel.tsx index c7b6acbfb..e7653973f 100644 --- a/app/views/CreateDiscussionView/SelectChannel.js +++ b/app/views/CreateDiscussionView/SelectChannel.tsx @@ -1,6 +1,5 @@ import React, { useState } from 'react'; import { Text } from 'react-native'; -import PropTypes from 'prop-types'; import debounce from '../../utils/debounce'; import { avatarURL } from '../../utils/avatar'; @@ -8,15 +7,22 @@ import RocketChat from '../../lib/rocketchat'; import I18n from '../../i18n'; import { MultiSelect } from '../../containers/UIKit/MultiSelect'; import { themes } from '../../constants/colors'; - import styles from './styles'; +import { ICreateDiscussionViewSelectChannel } from './interfaces'; const SelectChannel = ({ - server, token, userId, onChannelSelect, initial, blockUnauthenticatedAccess, serverVersion, theme -}) => { + server, + token, + userId, + onChannelSelect, + initial, + blockUnauthenticatedAccess, + serverVersion, + theme +}: ICreateDiscussionViewSelectChannel): JSX.Element => { const [channels, setChannels] = useState([]); - const getChannels = debounce(async(keyword = '') => { + const getChannels = debounce(async (keyword = '') => { try { const res = await RocketChat.localSearch({ text: keyword }); setChannels(res); @@ -25,16 +31,19 @@ const SelectChannel = ({ } }, 300); - const getAvatar = item => avatarURL({ - text: RocketChat.getRoomAvatar(item), - type: item.t, - user: { id: userId, token }, - server, - avatarETag: item.avatarETag, - rid: item.rid, - blockUnauthenticatedAccess, - serverVersion - }); + const getAvatar = (item: any) => + // TODO: remove this ts-ignore when migrate the file: app/utils/avatar.js + // @ts-ignore + avatarURL({ + text: RocketChat.getRoomAvatar(item), + type: item.t, + user: { id: userId, token }, + server, + avatarETag: item.avatarETag, + rid: item.rid, + blockUnauthenticatedAccess, + serverVersion + }); return ( <> @@ -52,20 +61,10 @@ const SelectChannel = ({ imageUrl: getAvatar(channel) }))} onClose={() => setChannels([])} - placeholder={{ text: `${ I18n.t('Select_a_Channel') }...` }} + placeholder={{ text: `${I18n.t('Select_a_Channel')}...` }} /> </> ); }; -SelectChannel.propTypes = { - server: PropTypes.string, - token: PropTypes.string, - userId: PropTypes.string, - initial: PropTypes.object, - onChannelSelect: PropTypes.func, - blockUnauthenticatedAccess: PropTypes.bool, - serverVersion: PropTypes.string, - theme: PropTypes.string -}; export default SelectChannel; diff --git a/app/views/CreateDiscussionView/SelectUsers.js b/app/views/CreateDiscussionView/SelectUsers.tsx similarity index 58% rename from app/views/CreateDiscussionView/SelectUsers.js rename to app/views/CreateDiscussionView/SelectUsers.tsx index 8500ee15c..65a4e0a4a 100644 --- a/app/views/CreateDiscussionView/SelectUsers.js +++ b/app/views/CreateDiscussionView/SelectUsers.tsx @@ -1,6 +1,5 @@ import React, { useState } from 'react'; import { Text } from 'react-native'; -import PropTypes from 'prop-types'; import { BLOCK_CONTEXT } from '@rocket.chat/ui-kit'; import { Q } from '@nozbe/watermelondb'; @@ -10,24 +9,39 @@ import RocketChat from '../../lib/rocketchat'; import database from '../../lib/database'; import I18n from '../../i18n'; import { MultiSelect } from '../../containers/UIKit/MultiSelect'; - -import styles from './styles'; import { themes } from '../../constants/colors'; +import styles from './styles'; +import { ICreateDiscussionViewSelectUsers } from './interfaces'; + +interface IUser { + name: string; + username: string; +} const SelectUsers = ({ - server, token, userId, selected, onUserSelect, blockUnauthenticatedAccess, serverVersion, theme -}) => { - const [users, setUsers] = useState([]); + server, + token, + userId, + selected, + onUserSelect, + blockUnauthenticatedAccess, + serverVersion, + theme +}: ICreateDiscussionViewSelectUsers): JSX.Element => { + const [users, setUsers] = useState<any[]>([]); - const getUsers = debounce(async(keyword = '') => { + const getUsers = debounce(async (keyword = '') => { try { const db = database.active; const usersCollection = db.get('users'); const res = await RocketChat.search({ text: keyword, filterRooms: false }); - let items = [...users.filter(u => selected.includes(u.name)), ...res.filter(r => !users.find(u => u.name === r.name))]; + let items = [ + ...users.filter((u: IUser) => selected.includes(u.name)), + ...res.filter((r: IUser) => !users.find((u: IUser) => u.name === r.name)) + ]; const records = await usersCollection.query(Q.where('username', Q.oneOf(items.map(u => u.name)))).fetch(); - items = items.map((item) => { - const index = records.findIndex(r => r.username === item.name); + items = items.map(item => { + const index = records.findIndex((r: IUser) => r.username === item.name); if (index > -1) { const record = records[index]; return { @@ -47,15 +61,18 @@ const SelectUsers = ({ } }, 300); - const getAvatar = item => avatarURL({ - text: RocketChat.getRoomAvatar(item), - type: 'd', - user: { id: userId, token }, - server, - avatarETag: item.avatarETag, - blockUnauthenticatedAccess, - serverVersion - }); + const getAvatar = (item: any) => + // TODO: remove this ts-ignore when migrate the file: app/utils/avatar.js + // @ts-ignore + avatarURL({ + text: RocketChat.getRoomAvatar(item), + type: 'd', + user: { id: userId, token }, + server, + avatarETag: item.avatarETag, + blockUnauthenticatedAccess, + serverVersion + }); return ( <> @@ -65,28 +82,18 @@ const SelectUsers = ({ inputStyle={styles.inputStyle} onSearch={getUsers} onChange={onUserSelect} - options={users.map(user => ({ + options={users.map((user: IUser) => ({ value: user.name, text: { text: RocketChat.getRoomTitle(user) }, imageUrl: getAvatar(user) }))} - onClose={() => setUsers(users.filter(u => selected.includes(u.name)))} - placeholder={{ text: `${ I18n.t('Select_Users') }...` }} + onClose={() => setUsers(users.filter((u: IUser) => selected.includes(u.name)))} + placeholder={{ text: `${I18n.t('Select_Users')}...` }} context={BLOCK_CONTEXT.FORM} multiselect /> </> ); }; -SelectUsers.propTypes = { - server: PropTypes.string, - token: PropTypes.string, - userId: PropTypes.string, - selected: PropTypes.array, - onUserSelect: PropTypes.func, - blockUnauthenticatedAccess: PropTypes.bool, - serverVersion: PropTypes.string, - theme: PropTypes.string -}; export default SelectUsers; diff --git a/app/views/CreateDiscussionView/index.js b/app/views/CreateDiscussionView/index.tsx similarity index 70% rename from app/views/CreateDiscussionView/index.js rename to app/views/CreateDiscussionView/index.tsx index a70caff9b..98b461f0a 100644 --- a/app/views/CreateDiscussionView/index.js +++ b/app/views/CreateDiscussionView/index.tsx @@ -1,7 +1,6 @@ import React from 'react'; import { connect } from 'react-redux'; -import PropTypes from 'prop-types'; -import { ScrollView, Text, Switch } from 'react-native'; +import { ScrollView, Switch, Text } from 'react-native'; import Loading from '../../containers/Loading'; import KeyboardView from '../../presentation/KeyboardView'; @@ -17,39 +16,23 @@ import RocketChat from '../../lib/rocketchat'; import Navigation from '../../lib/Navigation'; import { createDiscussionRequest } from '../../actions/createDiscussion'; import { showErrorAlert } from '../../utils/info'; - -import SelectChannel from './SelectChannel'; -import SelectUsers from './SelectUsers'; - -import styles from './styles'; import SafeAreaView from '../../containers/SafeAreaView'; import { goRoom } from '../../utils/goRoom'; -import { logEvent, events } from '../../utils/log'; +import { events, logEvent } from '../../utils/log'; import { E2E_ROOM_TYPES } from '../../lib/encryption/constants'; +import styles from './styles'; +import SelectUsers from './SelectUsers'; +import SelectChannel from './SelectChannel'; +import { ICreateChannelViewProps } from './interfaces'; -class CreateChannelView extends React.Component { - propTypes = { - navigation: PropTypes.object, - route: PropTypes.object, - server: PropTypes.string, - user: PropTypes.object, - create: PropTypes.func, - loading: PropTypes.bool, - result: PropTypes.object, - failure: PropTypes.bool, - error: PropTypes.object, - theme: PropTypes.string, - isMasterDetail: PropTypes.bool, - blockUnauthenticatedAccess: PropTypes.bool, - serverVersion: PropTypes.string, - encryptionEnabled: PropTypes.bool - } +class CreateChannelView extends React.Component<ICreateChannelViewProps, any> { + private channel: any; - constructor(props) { + constructor(props: ICreateChannelViewProps) { super(props); const { route } = props; this.channel = route.params?.channel; - const message = route.params?.message ?? {}; + const message: any = route.params?.message ?? {}; this.state = { channel: this.channel, message, @@ -61,11 +44,9 @@ class CreateChannelView extends React.Component { this.setHeader(); } - componentDidUpdate(prevProps, prevState) { + componentDidUpdate(prevProps: any, prevState: any) { const { channel, name } = this.state; - const { - loading, failure, error, result, isMasterDetail - } = this.props; + const { loading, failure, error, result, isMasterDetail } = this.props; if (channel?.rid !== prevState.channel?.rid || name !== prevState.name) { this.setHeader(); @@ -84,7 +65,10 @@ class CreateChannelView extends React.Component { Navigation.navigate('RoomsListView'); } const item = { - rid, name: RocketChat.getRoomTitle(result), t, prid + rid, + name: RocketChat.getRoomTitle(result), + t, + prid }; goRoom({ item, isMasterDetail }); } @@ -97,27 +81,34 @@ class CreateChannelView extends React.Component { const showCloseModal = route.params?.showCloseModal; navigation.setOptions({ title: I18n.t('Create_Discussion'), - headerRight: ( - this.valid() - ? () => ( + headerRight: this.valid() + ? () => ( <HeaderButton.Container> <HeaderButton.Item title={I18n.t('Create')} onPress={this.submit} testID='create-discussion-submit' /> </HeaderButton.Container> - ) - : null - ), + ) + : null, headerLeft: showCloseModal ? () => <HeaderButton.CloseModal navigation={navigation} /> : undefined }); - } + }; submit = () => { const { - name: t_name, channel: { prid, rid }, message: { id: pmid }, reply, users, encrypted + name: t_name, + channel: { prid, rid }, + message: { id: pmid }, + reply, + users, + encrypted } = this.state; const { create } = this.props; - const params = { - prid: prid || rid, pmid, t_name, reply, users + const params: any = { + prid: prid || rid, + pmid, + t_name, + reply, + users }; if (this.isEncryptionEnabled) { params.encrypted = encrypted ?? false; @@ -127,54 +118,46 @@ class CreateChannelView extends React.Component { }; valid = () => { - const { - channel, name - } = this.state; + const { channel, name } = this.state; - return ( - channel - && channel.rid - && channel.rid.trim().length - && name.trim().length - ); + return channel && channel.rid && channel.rid.trim().length && name.trim().length; }; - selectChannel = ({ value }) => { + selectChannel = ({ value }: any) => { logEvent(events.CD_SELECT_CHANNEL); this.setState({ channel: value, encrypted: value?.encrypted }); - } + }; - selectUsers = ({ value }) => { + selectUsers = ({ value }: any) => { logEvent(events.CD_SELECT_USERS); this.setState({ users: value }); - } + }; get isEncryptionEnabled() { const { channel } = this.state; const { encryptionEnabled } = this.props; + // TODO: remove this ts-ignore when migrate the file: app/lib/encryption/constants.js + // @ts-ignore return encryptionEnabled && E2E_ROOM_TYPES[channel?.t]; } - onEncryptedChange = (value) => { + onEncryptedChange = (value: any) => { logEvent(events.CD_TOGGLE_ENCRY); this.setState({ encrypted: value }); - } + }; render() { - const { - name, users, encrypted - } = this.state; - const { - server, user, loading, blockUnauthenticatedAccess, theme, serverVersion - } = this.props; + const { name, users, encrypted } = this.state; + const { server, user, loading, blockUnauthenticatedAccess, theme, serverVersion } = this.props; return ( + // @ts-ignore <KeyboardView style={{ backgroundColor: themes[theme].auxiliaryBackground }} contentContainerStyle={styles.container} - keyboardVerticalOffset={128} - > + keyboardVerticalOffset={128}> <StatusBar /> <SafeAreaView testID='create-discussion-view' style={styles.container}> + {/* @ts-ignore*/} <ScrollView {...scrollPersistTaps}> <Text style={[styles.description, { color: themes[theme].auxiliaryText }]}>{I18n.t('Discussion_Desc')}</Text> <SelectChannel @@ -192,8 +175,9 @@ class CreateChannelView extends React.Component { testID='multi-select-discussion-name' placeholder={I18n.t('A_meaningful_name_for_the_discussion_room')} containerStyle={styles.inputStyle} + /* @ts-ignore*/ defaultValue={name} - onChangeText={text => this.setState({ name: text })} + onChangeText={(text: string) => this.setState({ name: text })} theme={theme} /> <SelectUsers @@ -206,17 +190,12 @@ class CreateChannelView extends React.Component { serverVersion={serverVersion} theme={theme} /> - {this.isEncryptionEnabled - ? ( - <> - <Text style={[styles.label, { color: themes[theme].titleText }]}>{I18n.t('Encrypted')}</Text> - <Switch - value={encrypted} - onValueChange={this.onEncryptedChange} - trackColor={SWITCH_TRACK_COLOR} - /> - </> - ) : null} + {this.isEncryptionEnabled ? ( + <> + <Text style={[styles.label, { color: themes[theme].titleText }]}>{I18n.t('Encrypted')}</Text> + <Switch value={encrypted} onValueChange={this.onEncryptedChange} trackColor={SWITCH_TRACK_COLOR} /> + </> + ) : null} <Loading visible={loading} /> </ScrollView> </SafeAreaView> @@ -225,7 +204,7 @@ class CreateChannelView extends React.Component { } } -const mapStateToProps = state => ({ +const mapStateToProps = (state: any) => ({ user: getUserSelector(state), server: state.server.server, error: state.createDiscussion.error, @@ -238,8 +217,8 @@ const mapStateToProps = state => ({ encryptionEnabled: state.encryption.enabled }); -const mapDispatchToProps = dispatch => ({ - create: data => dispatch(createDiscussionRequest(data)) +const mapDispatchToProps = (dispatch: any) => ({ + create: (data: any) => dispatch(createDiscussionRequest(data)) }); export default connect(mapStateToProps, mapDispatchToProps)(withTheme(CreateChannelView)); diff --git a/app/views/CreateDiscussionView/interfaces.ts b/app/views/CreateDiscussionView/interfaces.ts new file mode 100644 index 000000000..468833119 --- /dev/null +++ b/app/views/CreateDiscussionView/interfaces.ts @@ -0,0 +1,55 @@ +export interface ICreateChannelViewProps { + navigation: any; + route: { + params?: { + channel: string; + message: { + msg: string; + }; + showCloseModal: boolean; + }; + }; + server: string; + user: { + id: string; + token: string; + }; + create: Function; + loading: boolean; + result: { + rid: string; + t: string; + prid: string; + }; + failure: boolean; + error: { + reason: string; + }; + theme: string; + isMasterDetail: boolean; + blockUnauthenticatedAccess: boolean; + serverVersion: string; + encryptionEnabled: boolean; +} + +export interface ICreateDiscussionViewSelectChannel { + server: string; + token: string; + userId: string; + initial: object; + onChannelSelect: Function; + blockUnauthenticatedAccess: boolean; + serverVersion: string; + theme: string; +} + +export interface ICreateDiscussionViewSelectUsers { + server: string; + token: string; + userId: string; + selected: any[]; + onUserSelect: Function; + blockUnauthenticatedAccess: boolean; + serverVersion: string; + theme: string; +} diff --git a/app/views/CreateDiscussionView/styles.js b/app/views/CreateDiscussionView/styles.ts similarity index 100% rename from app/views/CreateDiscussionView/styles.js rename to app/views/CreateDiscussionView/styles.ts diff --git a/app/views/DefaultBrowserView.js b/app/views/DefaultBrowserView.js index 568802360..245e16c82 100644 --- a/app/views/DefaultBrowserView.js +++ b/app/views/DefaultBrowserView.js @@ -11,7 +11,7 @@ import { DEFAULT_BROWSER_KEY } from '../utils/openLink'; import { isIOS } from '../utils/deviceInfo'; import SafeAreaView from '../containers/SafeAreaView'; import UserPreferences from '../lib/userPreferences'; -import { logEvent, events } from '../utils/log'; +import { events, logEvent } from '../utils/log'; const DEFAULT_BROWSERS = [ { @@ -42,11 +42,11 @@ const BROWSERS = [ class DefaultBrowserView extends React.Component { static navigationOptions = () => ({ title: I18n.t('Default_browser') - }) + }); static propTypes = { theme: PropTypes.string - } + }; constructor(props) { super(props); @@ -66,9 +66,9 @@ class DefaultBrowserView extends React.Component { } init = () => { - BROWSERS.forEach((browser) => { + BROWSERS.forEach(browser => { const { value } = browser; - Linking.canOpenURL(value).then((installed) => { + Linking.canOpenURL(value).then(installed => { if (installed) { if (this.mounted) { this.setState(({ supported }) => ({ supported: [...supported, browser] })); @@ -79,17 +79,17 @@ class DefaultBrowserView extends React.Component { } }); }); - } + }; - isSelected = (value) => { + isSelected = value => { const { browser } = this.state; if (!browser && value === 'systemDefault:') { return true; } return browser === value; - } + }; - changeDefaultBrowser = async(newBrowser) => { + changeDefaultBrowser = async newBrowser => { logEvent(events.DB_CHANGE_DEFAULT_BROWSER, { browser: newBrowser }); try { const browser = newBrowser !== 'systemDefault:' ? newBrowser : null; @@ -98,12 +98,12 @@ class DefaultBrowserView extends React.Component { } catch { logEvent(events.DB_CHANGE_DEFAULT_BROWSER_F); } - } + }; renderIcon = () => { const { theme } = this.props; return <List.Icon name='check' color={themes[theme].tintColor} />; - } + }; renderItem = ({ item }) => { const { title, value } = item; @@ -111,19 +111,19 @@ class DefaultBrowserView extends React.Component { <List.Item title={I18n.t(title, { defaultValue: title })} onPress={() => this.changeDefaultBrowser(value)} - testID={`default-browser-view-${ title }`} + testID={`default-browser-view-${title}`} right={this.isSelected(value) ? this.renderIcon : null} translateTitle={false} /> ); - } + }; renderHeader = () => ( <> <List.Header title='Choose_where_you_want_links_be_opened' /> <List.Separator /> </> - ) + ); render() { const { supported } = this.state; diff --git a/app/views/DirectoryView/Options.js b/app/views/DirectoryView/Options.js deleted file mode 100644 index a88bf42be..000000000 --- a/app/views/DirectoryView/Options.js +++ /dev/null @@ -1,132 +0,0 @@ -import React, { PureComponent } from 'react'; -import { - View, Text, Animated, Easing, TouchableWithoutFeedback, Switch -} from 'react-native'; -import PropTypes from 'prop-types'; - -import styles from './styles'; -import Touch from '../../utils/touch'; -import { CustomIcon } from '../../lib/Icons'; -import Check from '../../containers/Check'; -import I18n from '../../i18n'; -import { SWITCH_TRACK_COLOR, themes } from '../../constants/colors'; - -const ANIMATION_DURATION = 200; -const ANIMATION_PROPS = { - duration: ANIMATION_DURATION, - easing: Easing.inOut(Easing.quad), - useNativeDriver: true -}; - -export default class DirectoryOptions extends PureComponent { - static propTypes = { - type: PropTypes.string, - globalUsers: PropTypes.bool, - isFederationEnabled: PropTypes.bool, - close: PropTypes.func, - changeType: PropTypes.func, - toggleWorkspace: PropTypes.func, - theme: PropTypes.string - } - - constructor(props) { - super(props); - this.animatedValue = new Animated.Value(0); - } - - componentDidMount() { - Animated.timing( - this.animatedValue, - { - toValue: 1, - ...ANIMATION_PROPS - } - ).start(); - } - - close = () => { - const { close } = this.props; - Animated.timing( - this.animatedValue, - { - toValue: 0, - ...ANIMATION_PROPS - } - ).start(() => close()); - } - - renderItem = (itemType) => { - const { changeType, type: propType, theme } = this.props; - let text = 'Users'; - let icon = 'user'; - if (itemType === 'channels') { - text = 'Channels'; - icon = 'channel-public'; - } - - if (itemType === 'teams') { - text = 'Teams'; - icon = 'teams'; - } - - return ( - <Touch - onPress={() => changeType(itemType)} - style={styles.dropdownItemButton} - theme={theme} - > - <View style={styles.dropdownItemContainer}> - <CustomIcon style={[styles.dropdownItemIcon, { color: themes[theme].bodyText }]} size={22} name={icon} /> - <Text style={[styles.dropdownItemText, { color: themes[theme].bodyText }]}>{I18n.t(text)}</Text> - {propType === itemType ? <Check theme={theme} /> : null} - </View> - </Touch> - ); - } - - render() { - const translateY = this.animatedValue.interpolate({ - inputRange: [0, 1], - outputRange: [-326, 0] - }); - const { - globalUsers, toggleWorkspace, isFederationEnabled, theme - } = this.props; - const backdropOpacity = this.animatedValue.interpolate({ - inputRange: [0, 1], - outputRange: [0, themes[theme].backdropOpacity] - }); - return ( - <> - <TouchableWithoutFeedback onPress={this.close}> - <Animated.View style={[styles.backdrop, { backgroundColor: themes[theme].backdropColor, opacity: backdropOpacity }]} /> - </TouchableWithoutFeedback> - <Animated.View style={[styles.dropdownContainer, { transform: [{ translateY }], backgroundColor: themes[theme].backgroundColor }]}> - <Touch onPress={this.close} theme={theme}> - <View style={[styles.dropdownContainerHeader, styles.dropdownItemContainer, { borderColor: themes[theme].separatorColor }]}> - <Text style={[styles.dropdownToggleText, { color: themes[theme].auxiliaryText }]}>{I18n.t('Search_by')}</Text> - <CustomIcon style={[styles.dropdownItemIcon, styles.inverted, { color: themes[theme].auxiliaryTintColor }]} size={22} name='chevron-down' /> - </View> - </Touch> - {this.renderItem('channels')} - {this.renderItem('users')} - {this.renderItem('teams')} - {isFederationEnabled - ? ( - <> - <View style={[styles.dropdownSeparator, { backgroundColor: themes[theme].separatorColor }]} /> - <View style={[styles.dropdownItemContainer, styles.globalUsersContainer]}> - <View style={styles.globalUsersTextContainer}> - <Text style={[styles.dropdownItemText, { color: themes[theme].infoText }]}>{I18n.t('Search_global_users')}</Text> - <Text style={[styles.dropdownItemDescription, { color: themes[theme].infoText }]}>{I18n.t('Search_global_users_description')}</Text> - </View> - <Switch value={globalUsers} onValueChange={toggleWorkspace} trackColor={SWITCH_TRACK_COLOR} /> - </View> - </> - ) - : null} - </Animated.View> - </> - ); - } -} diff --git a/app/views/DirectoryView/Options.tsx b/app/views/DirectoryView/Options.tsx new file mode 100644 index 000000000..fcc0f7bf6 --- /dev/null +++ b/app/views/DirectoryView/Options.tsx @@ -0,0 +1,131 @@ +import React, { PureComponent } from 'react'; +import { Animated, Easing, Switch, Text, TouchableWithoutFeedback, View } from 'react-native'; + +import Touch from '../../utils/touch'; +import { CustomIcon } from '../../lib/Icons'; +import Check from '../../containers/Check'; +import I18n from '../../i18n'; +import { SWITCH_TRACK_COLOR, themes } from '../../constants/colors'; +import styles from './styles'; + +const ANIMATION_DURATION = 200; +const ANIMATION_PROPS = { + duration: ANIMATION_DURATION, + easing: Easing.inOut(Easing.quad), + useNativeDriver: true +}; + +interface IDirectoryOptionsProps { + type: string; + globalUsers: boolean; + isFederationEnabled: boolean; + close: Function; + changeType: Function; + toggleWorkspace(): void; + theme: string; +} + +export default class DirectoryOptions extends PureComponent<IDirectoryOptionsProps, any> { + private animatedValue: Animated.Value; + + constructor(props: IDirectoryOptionsProps) { + super(props); + this.animatedValue = new Animated.Value(0); + } + + componentDidMount() { + Animated.timing(this.animatedValue, { + toValue: 1, + ...ANIMATION_PROPS + }).start(); + } + + close = () => { + const { close } = this.props; + Animated.timing(this.animatedValue, { + toValue: 0, + ...ANIMATION_PROPS + }).start(() => close()); + }; + + renderItem = (itemType: string) => { + const { changeType, type: propType, theme } = this.props; + let text = 'Users'; + let icon = 'user'; + if (itemType === 'channels') { + text = 'Channels'; + icon = 'channel-public'; + } + + if (itemType === 'teams') { + text = 'Teams'; + icon = 'teams'; + } + + return ( + <Touch onPress={() => changeType(itemType)} style={styles.dropdownItemButton} theme={theme}> + <View style={styles.dropdownItemContainer}> + <CustomIcon style={[styles.dropdownItemIcon, { color: themes[theme].bodyText }]} size={22} name={icon} /> + <Text style={[styles.dropdownItemText, { color: themes[theme].bodyText }]}>{I18n.t(text)}</Text> + {propType === itemType ? <Check theme={theme} /> : null} + </View> + </Touch> + ); + }; + + render() { + const translateY = this.animatedValue.interpolate({ + inputRange: [0, 1], + outputRange: [-326, 0] + }); + const { globalUsers, toggleWorkspace, isFederationEnabled, theme } = this.props; + const backdropOpacity = this.animatedValue.interpolate({ + inputRange: [0, 1], + outputRange: [0, themes[theme].backdropOpacity] + }); + return ( + <> + <TouchableWithoutFeedback onPress={this.close}> + <Animated.View style={[styles.backdrop, { backgroundColor: themes[theme].backdropColor, opacity: backdropOpacity }]} /> + </TouchableWithoutFeedback> + <Animated.View + style={[styles.dropdownContainer, { transform: [{ translateY }], backgroundColor: themes[theme].backgroundColor }]}> + <Touch onPress={this.close} theme={theme}> + <View + style={[ + styles.dropdownContainerHeader, + styles.dropdownItemContainer, + { borderColor: themes[theme].separatorColor } + ]}> + <Text style={[styles.dropdownToggleText, { color: themes[theme].auxiliaryText }]}>{I18n.t('Search_by')}</Text> + <CustomIcon + style={[styles.dropdownItemIcon, styles.inverted, { color: themes[theme].auxiliaryTintColor }]} + size={22} + name='chevron-down' + /> + </View> + </Touch> + {this.renderItem('channels')} + {this.renderItem('users')} + {this.renderItem('teams')} + {isFederationEnabled ? ( + <> + <View style={[styles.dropdownSeparator, { backgroundColor: themes[theme].separatorColor }]} /> + <View style={[styles.dropdownItemContainer, styles.globalUsersContainer]}> + <View style={styles.globalUsersTextContainer}> + <Text style={[styles.dropdownItemText, { color: themes[theme].infoText }]}> + {I18n.t('Search_global_users')} + </Text> + <Text style={[styles.dropdownItemDescription, { color: themes[theme].infoText }]}> + {I18n.t('Search_global_users_description')} + </Text> + </View> + <Switch value={globalUsers} onValueChange={toggleWorkspace} trackColor={SWITCH_TRACK_COLOR} /> + </View> + </> + ) : null} + </Animated.View> + </> + ); + } +} diff --git a/app/views/DirectoryView/index.js b/app/views/DirectoryView/index.tsx similarity index 68% rename from app/views/DirectoryView/index.js rename to app/views/DirectoryView/index.tsx index 6e60b44bc..25d53b831 100644 --- a/app/views/DirectoryView/index.js +++ b/app/views/DirectoryView/index.tsx @@ -1,11 +1,8 @@ import React from 'react'; -import PropTypes from 'prop-types'; -import { - View, FlatList, Text -} from 'react-native'; +import { FlatList, Text, View } from 'react-native'; import { connect } from 'react-redux'; -import * as List from '../../containers/List'; +import * as List from '../../containers/List'; import Touch from '../../utils/touch'; import RocketChat from '../../lib/rocketchat'; import DirectoryItem from '../../presentation/DirectoryItem'; @@ -17,40 +14,40 @@ import StatusBar from '../../containers/StatusBar'; import ActivityIndicator from '../../containers/ActivityIndicator'; import * as HeaderButton from '../../containers/HeaderButton'; import debounce from '../../utils/debounce'; -import log, { logEvent, events } from '../../utils/log'; -import Options from './Options'; +import log, { events, logEvent } from '../../utils/log'; import { withTheme } from '../../theme'; import { themes } from '../../constants/colors'; -import styles from './styles'; import { getUserSelector } from '../../selectors/login'; import SafeAreaView from '../../containers/SafeAreaView'; import { goRoom } from '../../utils/goRoom'; +import styles from './styles'; +import Options from './Options'; -class DirectoryView extends React.Component { - static navigationOptions = ({ navigation, isMasterDetail }) => { - const options = { +interface IDirectoryViewProps { + navigation: object; + baseUrl: string; + isFederationEnabled: boolean; + user: { + id: string; + token: string; + }; + theme: string; + directoryDefaultView: string; + isMasterDetail: boolean; +} + +class DirectoryView extends React.Component<IDirectoryViewProps, any> { + static navigationOptions = ({ navigation, isMasterDetail }: any) => { + const options: any = { title: I18n.t('Directory') }; if (isMasterDetail) { options.headerLeft = () => <HeaderButton.CloseModal navigation={navigation} testID='directory-view-close' />; } return options; - } - - static propTypes = { - navigation: PropTypes.object, - baseUrl: PropTypes.string, - isFederationEnabled: PropTypes.bool, - user: PropTypes.shape({ - id: PropTypes.string, - token: PropTypes.string - }), - theme: PropTypes.string, - directoryDefaultView: PropTypes.string, - isMasterDetail: PropTypes.bool }; - constructor(props) { + constructor(props: IDirectoryViewProps) { super(props); this.state = { data: [], @@ -67,18 +64,21 @@ class DirectoryView extends React.Component { this.load({}); } - onSearchChangeText = (text) => { + onSearchChangeText = (text: string) => { this.setState({ text }, this.search); - } + }; // eslint-disable-next-line react/sort-comp - load = debounce(async({ newSearch = false }) => { + load = debounce(async ({ newSearch = false }) => { if (newSearch) { this.setState({ data: [], total: -1, loading: false }); } const { - loading, text, total, data: { length } + loading, + text, + total, + data: { length } } = this.state; if (loading || length === total) { return; @@ -93,7 +93,7 @@ class DirectoryView extends React.Component { query, offset: data.length, count: 50, - sort: (type === 'users') ? { username: 1 } : { usersCount: -1 } + sort: type === 'users' ? { username: 1 } : { usersCount: -1 } }); if (directories.success) { this.setState({ @@ -108,13 +108,13 @@ class DirectoryView extends React.Component { log(e); this.setState({ loading: false }); } - }, 200) + }, 200); search = () => { this.load({ newSearch: true }); - } + }; - changeType = (type) => { + changeType = (type: string) => { this.setState({ type, data: [] }, () => this.search()); if (type === 'users') { @@ -124,27 +124,30 @@ class DirectoryView extends React.Component { } else if (type === 'teams') { logEvent(events.DIRECTORY_SEARCH_TEAMS); } - } + }; toggleWorkspace = () => { - this.setState(({ globalUsers }) => ({ globalUsers: !globalUsers, data: [] }), () => this.search()); - } + this.setState( + ({ globalUsers }: any) => ({ globalUsers: !globalUsers, data: [] }), + () => this.search() + ); + }; toggleDropdown = () => { - this.setState(({ showOptionsDropdown }) => ({ showOptionsDropdown: !showOptionsDropdown })); - } + this.setState(({ showOptionsDropdown }: any) => ({ showOptionsDropdown: !showOptionsDropdown })); + }; - goRoom = (item) => { - const { navigation, isMasterDetail } = this.props; + goRoom = (item: any) => { + const { navigation, isMasterDetail }: any = this.props; if (isMasterDetail) { navigation.navigate('DrawerNavigator'); } else { navigation.navigate('RoomsListView'); } goRoom({ item, isMasterDetail }); - } + }; - onPressItem = async(item) => { + onPressItem = async (item: any) => { const { type } = this.state; if (type === 'users') { const result = await RocketChat.createDirectMessage(item.username); @@ -154,14 +157,23 @@ class DirectoryView extends React.Component { } else if (['p', 'c'].includes(item.t) && !item.teamMain) { const { room } = await RocketChat.getRoomInfo(item._id); this.goRoom({ - rid: item._id, name: item.name, joinCodeRequired: room.joinCodeRequired, t: item.t, search: true + rid: item._id, + name: item.name, + joinCodeRequired: room.joinCodeRequired, + t: item.t, + search: true }); } else { this.goRoom({ - rid: item._id, name: item.name, t: item.t, search: true, teamMain: item.teamMain, teamId: item.teamId + rid: item._id, + name: item.name, + t: item.t, + search: true, + teamMain: item.teamMain, + teamId: item.teamId }); } - } + }; renderHeader = () => { const { type } = this.state; @@ -181,28 +193,28 @@ class DirectoryView extends React.Component { return ( <> - <SearchBox - onChangeText={this.onSearchChangeText} - onSubmitEditing={this.search} - testID='directory-view-search' - /> - <Touch - onPress={this.toggleDropdown} - style={styles.dropdownItemButton} - testID='directory-view-dropdown' - theme={theme} - > - <View style={[sharedStyles.separatorVertical, styles.toggleDropdownContainer, { borderColor: themes[theme].separatorColor }]}> + <SearchBox onChangeText={this.onSearchChangeText} onSubmitEditing={this.search} testID='directory-view-search' /> + <Touch onPress={this.toggleDropdown} style={styles.dropdownItemButton} testID='directory-view-dropdown' theme={theme}> + <View + style={[ + sharedStyles.separatorVertical, + styles.toggleDropdownContainer, + { borderColor: themes[theme].separatorColor } + ]}> <CustomIcon style={[styles.toggleDropdownIcon, { color: themes[theme].tintColor }]} size={20} name={icon} /> <Text style={[styles.toggleDropdownText, { color: themes[theme].tintColor }]}>{I18n.t(text)}</Text> - <CustomIcon name='chevron-down' size={20} style={[styles.toggleDropdownArrow, { color: themes[theme].auxiliaryTintColor }]} /> + <CustomIcon + name='chevron-down' + size={20} + style={[styles.toggleDropdownArrow, { color: themes[theme].auxiliaryTintColor }]} + /> </View> </Touch> </> ); - } + }; - renderItem = ({ item, index }) => { + renderItem = ({ item, index }: any) => { const { data, type } = this.state; const { baseUrl, user, theme } = this.props; @@ -218,7 +230,7 @@ class DirectoryView extends React.Component { title: item.name, onPress: () => this.onPressItem(item), baseUrl, - testID: `directory-view-item-${ item.name }`.toLowerCase(), + testID: `directory-view-item-${item.name}`.toLowerCase(), style, user, theme, @@ -258,18 +270,13 @@ class DirectoryView extends React.Component { {...commonProps} /> ); - } + }; render = () => { - const { - data, loading, showOptionsDropdown, type, globalUsers - } = this.state; + const { data, loading, showOptionsDropdown, type, globalUsers } = this.state; const { isFederationEnabled, theme } = this.props; return ( - <SafeAreaView - style={{ backgroundColor: themes[theme].backgroundColor }} - testID='directory-view' - > + <SafeAreaView style={{ backgroundColor: themes[theme].backgroundColor }} testID='directory-view'> <StatusBar /> <FlatList data={data} @@ -284,25 +291,23 @@ class DirectoryView extends React.Component { ListFooterComponent={loading ? <ActivityIndicator theme={theme} /> : null} onEndReached={() => this.load({})} /> - {showOptionsDropdown - ? ( - <Options - theme={theme} - type={type} - globalUsers={globalUsers} - close={this.toggleDropdown} - changeType={this.changeType} - toggleWorkspace={this.toggleWorkspace} - isFederationEnabled={isFederationEnabled} - /> - ) - : null} + {showOptionsDropdown ? ( + <Options + theme={theme} + type={type} + globalUsers={globalUsers} + close={this.toggleDropdown} + changeType={this.changeType} + toggleWorkspace={this.toggleWorkspace} + isFederationEnabled={isFederationEnabled} + /> + ) : null} </SafeAreaView> ); - } + }; } -const mapStateToProps = state => ({ +const mapStateToProps = (state: any) => ({ baseUrl: state.server.server, user: getUserSelector(state), isFederationEnabled: state.settings.FEDERATION_Enabled, diff --git a/app/views/DirectoryView/styles.js b/app/views/DirectoryView/styles.ts similarity index 98% rename from app/views/DirectoryView/styles.js rename to app/views/DirectoryView/styles.ts index b026041c3..543e6babb 100644 --- a/app/views/DirectoryView/styles.js +++ b/app/views/DirectoryView/styles.ts @@ -35,6 +35,7 @@ export default StyleSheet.create({ top: 0 }, backdrop: { + // @ts-ignore ...StyleSheet.absoluteFill }, dropdownContainerHeader: { diff --git a/app/views/E2EEncryptionSecurityView.js b/app/views/E2EEncryptionSecurityView.js index 661d6e84d..67a42453b 100644 --- a/app/views/E2EEncryptionSecurityView.js +++ b/app/views/E2EEncryptionSecurityView.js @@ -1,19 +1,18 @@ import React from 'react'; -import { View, Text, StyleSheet } from 'react-native'; +import { StyleSheet, Text, View } from 'react-native'; import PropTypes from 'prop-types'; import { connect } from 'react-redux'; import StatusBar from '../containers/StatusBar'; import * as List from '../containers/List'; import I18n from '../i18n'; -import log, { logEvent, events } from '../utils/log'; +import log, { events, logEvent } from '../utils/log'; import { withTheme } from '../theme'; import SafeAreaView from '../containers/SafeAreaView'; import TextInput from '../containers/TextInput'; import Button from '../containers/Button'; import { getUserSelector } from '../selectors/login'; import { PADDING_HORIZONTAL } from '../containers/List/constants'; -import sharedStyles from './Styles'; import { themes } from '../constants/colors'; import { Encryption } from '../lib/encryption'; import RocketChat from '../lib/rocketchat'; @@ -22,6 +21,7 @@ import { showConfirmationAlert, showErrorAlert } from '../utils/info'; import EventEmitter from '../utils/events'; import { LISTENER } from '../containers/Toast'; import debounce from '../utils/debounce'; +import sharedStyles from './Styles'; const styles = StyleSheet.create({ container: { @@ -42,13 +42,13 @@ const styles = StyleSheet.create({ }); class E2EEncryptionSecurityView extends React.Component { - state = { newPassword: '' } + state = { newPassword: '' }; newPasswordInputRef = React.createRef(); - onChangePasswordText = debounce(text => this.setState({ newPassword: text }), 300) + onChangePasswordText = debounce(text => this.setState({ newPassword: text }), 300); - setNewPasswordRef = ref => this.newPasswordInputRef = ref; + setNewPasswordRef = ref => (this.newPasswordInputRef = ref); changePassword = () => { const { newPassword } = this.state; @@ -59,7 +59,7 @@ class E2EEncryptionSecurityView extends React.Component { title: I18n.t('Are_you_sure_question_mark'), message: I18n.t('E2E_encryption_change_password_message'), confirmationText: I18n.t('E2E_encryption_change_password_confirmation'), - onPress: async() => { + onPress: async () => { logEvent(events.E2E_SEC_CHANGE_PASSWORD); try { const { server } = this.props; @@ -73,14 +73,14 @@ class E2EEncryptionSecurityView extends React.Component { } } }); - } + }; resetOwnKey = () => { showConfirmationAlert({ title: I18n.t('Are_you_sure_question_mark'), message: I18n.t('E2E_encryption_reset_message'), confirmationText: I18n.t('E2E_encryption_reset_confirmation'), - onPress: async() => { + onPress: async () => { logEvent(events.E2E_SEC_RESET_OWN_KEY); try { const res = await RocketChat.e2eResetOwnKey(); @@ -98,7 +98,7 @@ class E2EEncryptionSecurityView extends React.Component { } } }); - } + }; renderChangePassword = () => { const { newPassword } = this.state; @@ -109,8 +109,12 @@ class E2EEncryptionSecurityView extends React.Component { return ( <> <List.Section> - <Text style={[styles.title, { color: themes[theme].titleColor }]}>{I18n.t('E2E_encryption_change_password_title')}</Text> - <Text style={[styles.description, { color: themes[theme].bodyText }]}>{I18n.t('E2E_encryption_change_password_description')}</Text> + <Text style={[styles.title, { color: themes[theme].titleColor }]}> + {I18n.t('E2E_encryption_change_password_title')} + </Text> + <Text style={[styles.description, { color: themes[theme].bodyText }]}> + {I18n.t('E2E_encryption_change_password_description')} + </Text> <TextInput inputRef={this.setNewPasswordRef} placeholder={I18n.t('New_Password')} @@ -134,7 +138,7 @@ class E2EEncryptionSecurityView extends React.Component { <List.Separator /> </> ); - } + }; render() { const { theme } = this.props; @@ -147,7 +151,9 @@ class E2EEncryptionSecurityView extends React.Component { <List.Section> <Text style={[styles.title, { color: themes[theme].titleColor }]}>{I18n.t('E2E_encryption_reset_title')}</Text> - <Text style={[styles.description, { color: themes[theme].bodyText }]}>{I18n.t('E2E_encryption_reset_description')}</Text> + <Text style={[styles.description, { color: themes[theme].bodyText }]}> + {I18n.t('E2E_encryption_reset_description')} + </Text> <Button onPress={this.resetOwnKey} title={I18n.t('E2E_encryption_reset_button')} diff --git a/app/views/E2EEnterYourPasswordView.js b/app/views/E2EEnterYourPasswordView.js index 563b1d9a7..ebf5a890d 100644 --- a/app/views/E2EEnterYourPasswordView.js +++ b/app/views/E2EEnterYourPasswordView.js @@ -1,10 +1,9 @@ import React from 'react'; import PropTypes from 'prop-types'; -import { Text, StyleSheet, ScrollView } from 'react-native'; +import { ScrollView, StyleSheet, Text } from 'react-native'; import { connect } from 'react-redux'; import I18n from '../i18n'; -import sharedStyles from './Styles'; import { withTheme } from '../theme'; import Button from '../containers/Button'; import { themes } from '../constants/colors'; @@ -15,7 +14,8 @@ import { encryptionDecodeKey as encryptionDecodeKeyAction } from '../actions/enc import scrollPersistTaps from '../utils/scrollPersistTaps'; import KeyboardView from '../presentation/KeyboardView'; import StatusBar from '../containers/StatusBar'; -import { logEvent, events } from '../utils/log'; +import { events, logEvent } from '../utils/log'; +import sharedStyles from './Styles'; const styles = StyleSheet.create({ container: { @@ -31,12 +31,12 @@ class E2EEnterYourPasswordView extends React.Component { static navigationOptions = ({ navigation }) => ({ headerLeft: () => <HeaderButton.CloseModal navigation={navigation} testID='e2e-enter-your-password-view-close' />, title: I18n.t('Enter_Your_E2E_Password') - }) + }); static propTypes = { encryptionDecodeKey: PropTypes.func, theme: PropTypes.string - } + }; constructor(props) { super(props); @@ -50,7 +50,7 @@ class E2EEnterYourPasswordView extends React.Component { const { password } = this.state; const { encryptionDecodeKey } = this.props; encryptionDecodeKey(password); - } + }; render() { const { password } = this.state; @@ -60,13 +60,19 @@ class E2EEnterYourPasswordView extends React.Component { <KeyboardView style={{ backgroundColor: themes[theme].backgroundColor }} contentContainerStyle={sharedStyles.container} - keyboardVerticalOffset={128} - > + keyboardVerticalOffset={128}> <StatusBar /> - <ScrollView {...scrollPersistTaps} style={sharedStyles.container} contentContainerStyle={[sharedStyles.containerScrollView, styles.scrollView]}> - <SafeAreaView style={[styles.container, { backgroundColor: themes[theme].backgroundColor }]} testID='e2e-enter-your-password-view'> + <ScrollView + {...scrollPersistTaps} + style={sharedStyles.container} + contentContainerStyle={[sharedStyles.containerScrollView, styles.scrollView]}> + <SafeAreaView + style={[styles.container, { backgroundColor: themes[theme].backgroundColor }]} + testID='e2e-enter-your-password-view'> <TextInput - inputRef={(e) => { this.passwordInput = e; }} + inputRef={e => { + this.passwordInput = e; + }} placeholder={I18n.t('Password')} returnKeyType='send' secureTextEntry diff --git a/app/views/E2EHowItWorksView.js b/app/views/E2EHowItWorksView.js index f72b9c6dd..0ac1c756b 100644 --- a/app/views/E2EHowItWorksView.js +++ b/app/views/E2EHowItWorksView.js @@ -28,11 +28,11 @@ class E2EHowItWorksView extends React.Component { title: I18n.t('How_It_Works'), headerLeft: showCloseModal ? () => <HeaderButton.CloseModal navigation={navigation} /> : undefined }; - } + }; static propTypes = { theme: PropTypes.string - } + }; render() { const { theme } = this.props; @@ -40,30 +40,11 @@ class E2EHowItWorksView extends React.Component { const infoStyle = [styles.info, { color: themes[theme].bodyText }]; return ( - <SafeAreaView - style={[styles.container, { backgroundColor: themes[theme].backgroundColor }]} - testID='e2e-how-it-works-view' - > - <Markdown - msg={I18n.t('E2E_How_It_Works_info1')} - style={infoStyle} - theme={theme} - /> - <Markdown - msg={I18n.t('E2E_How_It_Works_info2')} - style={infoStyle} - theme={theme} - /> - <Markdown - msg={I18n.t('E2E_How_It_Works_info3')} - style={infoStyle} - theme={theme} - /> - <Markdown - msg={I18n.t('E2E_How_It_Works_info4')} - style={infoStyle} - theme={theme} - /> + <SafeAreaView style={[styles.container, { backgroundColor: themes[theme].backgroundColor }]} testID='e2e-how-it-works-view'> + <Markdown msg={I18n.t('E2E_How_It_Works_info1')} style={infoStyle} theme={theme} /> + <Markdown msg={I18n.t('E2E_How_It_Works_info2')} style={infoStyle} theme={theme} /> + <Markdown msg={I18n.t('E2E_How_It_Works_info3')} style={infoStyle} theme={theme} /> + <Markdown msg={I18n.t('E2E_How_It_Works_info4')} style={infoStyle} theme={theme} /> </SafeAreaView> ); } diff --git a/app/views/E2ESaveYourPasswordView.js b/app/views/E2ESaveYourPasswordView.js index 9163047cd..167d21dc2 100644 --- a/app/views/E2ESaveYourPasswordView.js +++ b/app/views/E2ESaveYourPasswordView.js @@ -1,13 +1,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import { connect } from 'react-redux'; -import { - Text, - View, - Clipboard, - ScrollView, - StyleSheet -} from 'react-native'; +import { Clipboard, ScrollView, StyleSheet, Text, View } from 'react-native'; import { encryptionSetBanner as encryptionSetBannerAction } from '../actions/encryption'; import { E2E_RANDOM_PASSWORD_KEY } from '../lib/encryption/constants'; @@ -15,15 +9,15 @@ import * as HeaderButton from '../containers/HeaderButton'; import scrollPersistTaps from '../utils/scrollPersistTaps'; import SafeAreaView from '../containers/SafeAreaView'; import UserPreferences from '../lib/userPreferences'; -import { logEvent, events } from '../utils/log'; +import { events, logEvent } from '../utils/log'; import StatusBar from '../containers/StatusBar'; import { LISTENER } from '../containers/Toast'; import { themes } from '../constants/colors'; import EventEmitter from '../utils/events'; import Button from '../containers/Button'; import { withTheme } from '../theme'; -import sharedStyles from './Styles'; import I18n from '../i18n'; +import sharedStyles from './Styles'; const styles = StyleSheet.create({ container: { @@ -63,14 +57,14 @@ class E2ESaveYourPasswordView extends React.Component { static navigationOptions = ({ navigation }) => ({ headerLeft: () => <HeaderButton.CloseModal navigation={navigation} testID='e2e-save-your-password-view-close' />, title: I18n.t('Save_Your_E2E_Password') - }) + }); static propTypes = { server: PropTypes.string, navigation: PropTypes.object, encryptionSetBanner: PropTypes.func, theme: PropTypes.string - } + }; constructor(props) { super(props); @@ -83,11 +77,11 @@ class E2ESaveYourPasswordView extends React.Component { this.mounted = true; } - init = async() => { + init = async () => { const { server } = this.props; try { // Set stored password on local state - const password = await UserPreferences.getStringAsync(`${ server }-${ E2E_RANDOM_PASSWORD_KEY }`); + const password = await UserPreferences.getStringAsync(`${server}-${E2E_RANDOM_PASSWORD_KEY}`); if (this.mounted) { this.setState({ password }); } else { @@ -96,30 +90,30 @@ class E2ESaveYourPasswordView extends React.Component { } catch { // Do nothing } - } + }; - onSaved = async() => { + onSaved = async () => { logEvent(events.E2E_SAVE_PW_SAVED); const { navigation, server, encryptionSetBanner } = this.props; // Remove stored password - await UserPreferences.removeItem(`${ server }-${ E2E_RANDOM_PASSWORD_KEY }`); + await UserPreferences.removeItem(`${server}-${E2E_RANDOM_PASSWORD_KEY}`); // Hide encryption banner encryptionSetBanner(); navigation.pop(); - } + }; onCopy = () => { logEvent(events.E2E_SAVE_PW_COPY); const { password } = this.state; Clipboard.setString(password); EventEmitter.emit(LISTENER, { message: I18n.t('Copied_to_clipboard') }); - } + }; onHowItWorks = () => { logEvent(events.E2E_SAVE_PW_HOW_IT_WORKS); const { navigation } = this.props; navigation.navigate('E2EHowItWorksView'); - } + }; render() { const { password } = this.state; @@ -128,9 +122,14 @@ class E2ESaveYourPasswordView extends React.Component { return ( <SafeAreaView style={{ backgroundColor: themes[theme].backgroundColor }} testID='e2e-save-password-view'> <StatusBar /> - <ScrollView {...scrollPersistTaps} style={sharedStyles.container} contentContainerStyle={sharedStyles.containerScrollView}> + <ScrollView + {...scrollPersistTaps} + style={sharedStyles.container} + contentContainerStyle={sharedStyles.containerScrollView}> <View style={[styles.container, { backgroundColor: themes[theme].backgroundColor }]}> - <Text style={[styles.warning, { color: themes[theme].dangerColor }]}>{I18n.t('Save_Your_Encryption_Password_warning')}</Text> + <Text style={[styles.warning, { color: themes[theme].dangerColor }]}> + {I18n.t('Save_Your_Encryption_Password_warning')} + </Text> <View style={styles.content}> <Text style={[styles.passwordText, { color: themes[theme].bodyText }]}>{I18n.t('Your_password_is')}</Text> <Text style={[styles.password, { color: themes[theme].bodyText }]}>{password}</Text> diff --git a/app/views/ForgotPasswordView.js b/app/views/ForgotPasswordView.js index 319682c75..bd92b56bc 100644 --- a/app/views/ForgotPasswordView.js +++ b/app/views/ForgotPasswordView.js @@ -4,7 +4,6 @@ import PropTypes from 'prop-types'; import TextInput from '../containers/TextInput'; import Button from '../containers/Button'; -import sharedStyles from './Styles'; import { showErrorAlert } from '../utils/info'; import isValidEmail from '../utils/isValidEmail'; import I18n from '../i18n'; @@ -12,23 +11,24 @@ import RocketChat from '../lib/rocketchat'; import { withTheme } from '../theme'; import { themes } from '../constants/colors'; import FormContainer, { FormContainerInner } from '../containers/FormContainer'; -import { logEvent, events } from '../utils/log'; +import { events, logEvent } from '../utils/log'; +import sharedStyles from './Styles'; class ForgotPasswordView extends React.Component { static navigationOptions = ({ route }) => ({ title: route.params?.title ?? 'Rocket.Chat' - }) + }); static propTypes = { navigation: PropTypes.object, theme: PropTypes.string - } + }; state = { email: '', invalidEmail: true, isFetching: false - } + }; shouldComponentUpdate(nextProps, nextState) { const { email, invalidEmail, isFetching } = this.state; @@ -48,15 +48,15 @@ class ForgotPasswordView extends React.Component { return false; } - validate = (email) => { + validate = email => { if (!isValidEmail(email)) { this.setState({ invalidEmail: true }); return; } this.setState({ email, invalidEmail: false }); - } + }; - resetPassword = async() => { + resetPassword = async () => { logEvent(events.FP_FORGOT_PASSWORD); const { email, invalidEmail } = this.state; if (invalidEmail || !email) { @@ -76,7 +76,7 @@ class ForgotPasswordView extends React.Component { showErrorAlert(msg, I18n.t('Alert')); } this.setState({ isFetching: false }); - } + }; render() { const { invalidEmail, isFetching } = this.state; @@ -85,7 +85,9 @@ class ForgotPasswordView extends React.Component { return ( <FormContainer theme={theme} testID='forgot-password-view'> <FormContainerInner> - <Text style={[sharedStyles.loginTitle, sharedStyles.textBold, { color: themes[theme].titleText }]}>{I18n.t('Forgot_password')}</Text> + <Text style={[sharedStyles.loginTitle, sharedStyles.textBold, { color: themes[theme].titleText }]}> + {I18n.t('Forgot_password')} + </Text> <TextInput autoFocus placeholder={I18n.t('Email')} diff --git a/app/views/ForwardLivechatView.js b/app/views/ForwardLivechatView.js index 3807c9894..4f61272d4 100644 --- a/app/views/ForwardLivechatView.js +++ b/app/views/ForwardLivechatView.js @@ -1,6 +1,6 @@ import React, { useEffect, useState } from 'react'; import PropTypes from 'prop-types'; -import { View, StyleSheet } from 'react-native'; +import { StyleSheet, View } from 'react-native'; import { connect } from 'react-redux'; import I18n from '../i18n'; @@ -18,9 +18,7 @@ const styles = StyleSheet.create({ } }); -const ForwardLivechatView = ({ - forwardRoom, navigation, route, theme -}) => { +const ForwardLivechatView = ({ forwardRoom, navigation, route, theme }) => { const [departments, setDepartments] = useState([]); const [departmentId, setDepartment] = useState(); const [users, setUsers] = useState([]); @@ -29,7 +27,7 @@ const ForwardLivechatView = ({ const rid = route.params?.rid; - const getDepartments = async() => { + const getDepartments = async () => { try { const result = await RocketChat.getDepartments(); if (result.success) { @@ -40,11 +38,14 @@ const ForwardLivechatView = ({ } }; - const getUsers = async(term = '') => { + const getUsers = async (term = '') => { try { const { servedBy: { _id: agentId } = {} } = room; const _id = agentId && { $ne: agentId }; - const result = await RocketChat.usersAutoComplete({ conditions: { _id, status: { $ne: 'offline' }, statusLivechat: 'available' }, term }); + const result = await RocketChat.usersAutoComplete({ + conditions: { _id, status: { $ne: 'offline' }, statusLivechat: 'available' }, + term + }); if (result.success) { const parsedUsers = result.items.map(user => ({ label: user.username, value: user._id })); setUsers(parsedUsers); @@ -56,7 +57,7 @@ const ForwardLivechatView = ({ return []; }; - const getRoom = async() => { + const getRoom = async () => { try { const result = await RocketChat.getRoomInfo(rid); if (result.success) { @@ -122,17 +123,9 @@ const ForwardLivechatView = ({ return ( <View style={[styles.container, { backgroundColor: themes[theme].auxiliaryBackground }]}> - <Input - onPress={onPressDepartment} - placeholder={I18n.t('Select_a_Department')} - theme={theme} - /> + <Input onPress={onPressDepartment} placeholder={I18n.t('Select_a_Department')} theme={theme} /> <OrSeparator theme={theme} /> - <Input - onPress={onPressUser} - placeholder={I18n.t('Select_a_User')} - theme={theme} - /> + <Input onPress={onPressUser} placeholder={I18n.t('Select_a_User')} theme={theme} /> </View> ); }; diff --git a/app/views/InviteUsersEditView/index.js b/app/views/InviteUsersEditView/index.js index bba6ef247..105a8809d 100644 --- a/app/views/InviteUsersEditView/index.js +++ b/app/views/InviteUsersEditView/index.js @@ -5,56 +5,70 @@ import { connect } from 'react-redux'; import RNPickerSelect from 'react-native-picker-select'; import { - inviteLinksSetParams as inviteLinksSetParamsAction, - inviteLinksCreate as inviteLinksCreateAction + inviteLinksCreate as inviteLinksCreateAction, + inviteLinksSetParams as inviteLinksSetParamsAction } from '../../actions/inviteLinks'; import * as List from '../../containers/List'; -import styles from './styles'; import Button from '../../containers/Button'; import I18n from '../../i18n'; import StatusBar from '../../containers/StatusBar'; import { themes } from '../../constants/colors'; import { withTheme } from '../../theme'; import SafeAreaView from '../../containers/SafeAreaView'; -import { logEvent, events } from '../../utils/log'; +import { events, logEvent } from '../../utils/log'; +import styles from './styles'; const OPTIONS = { - days: [{ - label: '1', value: 1 - }, - { - label: '7', value: 7 - }, - { - label: '15', value: 15 - }, - { - label: '30', value: 30 - }], - maxUses: [{ - label: '1', value: 1 - }, - { - label: '5', value: 5 - }, - { - label: '10', value: 10 - }, - { - label: '25', value: 25 - }, - { - label: '50', value: 50 - }, - { - label: '100', value: 100 - }] + days: [ + { + label: '1', + value: 1 + }, + { + label: '7', + value: 7 + }, + { + label: '15', + value: 15 + }, + { + label: '30', + value: 30 + } + ], + maxUses: [ + { + label: '1', + value: 1 + }, + { + label: '5', + value: 5 + }, + { + label: '10', + value: 10 + }, + { + label: '25', + value: 25 + }, + { + label: '50', + value: 50 + }, + { + label: '100', + value: 100 + } + ] }; class InviteUsersView extends React.Component { static navigationOptions = () => ({ title: I18n.t('Invite_users') - }) + }); static propTypes = { navigation: PropTypes.object, @@ -62,7 +76,7 @@ class InviteUsersView extends React.Component { theme: PropTypes.string, createInviteLink: PropTypes.func, inviteLinksSetParams: PropTypes.func - } + }; constructor(props) { super(props); @@ -76,21 +90,24 @@ class InviteUsersView extends React.Component { [key]: value }; inviteLinksSetParams(params); - } + }; createInviteLink = () => { logEvent(events.IU_EDIT_CREATE_LINK); const { createInviteLink, navigation } = this.props; createInviteLink(this.rid); navigation.pop(); - } + }; renderPicker = (key, first) => { const { props } = this; const { theme } = props; - const firstEl = [{ - label: I18n.t(first), value: 0 - }]; + const firstEl = [ + { + label: I18n.t(first), + value: 0 + } + ]; return ( <RNPickerSelect style={{ viewContainer: styles.viewContainer }} @@ -102,7 +119,7 @@ class InviteUsersView extends React.Component { items={firstEl.concat(OPTIONS[key])} /> ); - } + }; render() { const { theme } = this.props; @@ -112,24 +129,13 @@ class InviteUsersView extends React.Component { <StatusBar /> <List.Section> <List.Separator /> - <List.Item - title='Expiration_Days' - right={() => this.renderPicker('days', 'Never')} - /> + <List.Item title='Expiration_Days' right={() => this.renderPicker('days', 'Never')} /> <List.Separator /> - <List.Item - title='Max_number_of_uses' - right={() => this.renderPicker('maxUses', 'No_limit')} - /> + <List.Item title='Max_number_of_uses' right={() => this.renderPicker('maxUses', 'No_limit')} /> <List.Separator /> </List.Section> <View style={styles.innerContainer}> - <Button - title={I18n.t('Generate_New_Link')} - type='primary' - onPress={this.createInviteLink} - theme={theme} - /> + <Button title={I18n.t('Generate_New_Link')} type='primary' onPress={this.createInviteLink} theme={theme} /> </View> </List.Container> </SafeAreaView> diff --git a/app/views/InviteUsersEditView/styles.js b/app/views/InviteUsersEditView/styles.js index bb2950300..3183999e3 100644 --- a/app/views/InviteUsersEditView/styles.js +++ b/app/views/InviteUsersEditView/styles.js @@ -1,6 +1,6 @@ import { StyleSheet } from 'react-native'; -import { PADDING_HORIZONTAL } from '../../containers/List/constants'; +import { PADDING_HORIZONTAL } from '../../containers/List/constants'; import sharedStyles from '../Styles'; export default StyleSheet.create({ diff --git a/app/views/InviteUsersView/index.js b/app/views/InviteUsersView/index.js index c6be109a9..2fe18f39a 100644 --- a/app/views/InviteUsersView/index.js +++ b/app/views/InviteUsersView/index.js @@ -1,15 +1,14 @@ import React from 'react'; import PropTypes from 'prop-types'; -import { View, Share, ScrollView } from 'react-native'; +import { ScrollView, Share, View } from 'react-native'; import moment from 'moment'; import { connect } from 'react-redux'; import { - inviteLinksCreate as inviteLinksCreateAction, - inviteLinksClear as inviteLinksClearAction + inviteLinksClear as inviteLinksClearAction, + inviteLinksCreate as inviteLinksCreateAction } from '../../actions/inviteLinks'; import RCTextInput from '../../containers/TextInput'; -import styles from './styles'; import Markdown from '../../containers/markdown'; import Button from '../../containers/Button'; import scrollPersistTaps from '../../utils/scrollPersistTaps'; @@ -18,12 +17,13 @@ import StatusBar from '../../containers/StatusBar'; import { themes } from '../../constants/colors'; import { withTheme } from '../../theme'; import SafeAreaView from '../../containers/SafeAreaView'; -import { logEvent, events } from '../../utils/log'; +import { events, logEvent } from '../../utils/log'; +import styles from './styles'; class InviteUsersView extends React.Component { static navigationOptions = () => ({ title: I18n.t('Invite_users') - }) + }); static propTypes = { navigation: PropTypes.object, @@ -33,7 +33,7 @@ class InviteUsersView extends React.Component { invite: PropTypes.object, createInviteLink: PropTypes.func, clearInviteLink: PropTypes.func - } + }; constructor(props) { super(props); @@ -57,13 +57,13 @@ class InviteUsersView extends React.Component { return; } Share.share({ message: invite.url }); - } + }; edit = () => { logEvent(events.IU_GO_IU_EDIT); const { navigation } = this.props; navigation.navigate('InviteUsersEditView', { rid: this.rid }); - } + }; linkExpirationText = () => { const { timeDateFormat, invite } = this.props; @@ -77,7 +77,10 @@ class InviteUsersView extends React.Component { if (invite.maxUses) { const usesLeft = invite.maxUses - invite.uses; - return I18n.t('Your_invite_link_will_expire_on__date__or_after__usesLeft__uses', { date: moment(expiration).format(timeDateFormat), usesLeft }); + return I18n.t('Your_invite_link_will_expire_on__date__or_after__usesLeft__uses', { + date: moment(expiration).format(timeDateFormat), + usesLeft + }); } return I18n.t('Your_invite_link_will_expire_on__date__', { date: moment(expiration).format(timeDateFormat) }); @@ -89,48 +92,30 @@ class InviteUsersView extends React.Component { } return I18n.t('Your_invite_link_will_never_expire'); - } + }; renderExpiration = () => { const { theme } = this.props; const expirationMessage = this.linkExpirationText(); return <Markdown msg={expirationMessage} username='' baseUrl='' theme={theme} />; - } + }; render() { - const { - theme, invite - } = this.props; + const { theme, invite } = this.props; return ( <SafeAreaView style={{ backgroundColor: themes[theme].backgroundColor }}> <ScrollView {...scrollPersistTaps} style={{ backgroundColor: themes[theme].auxiliaryBackground }} contentContainerStyle={styles.contentContainer} - showsVerticalScrollIndicator={false} - > + showsVerticalScrollIndicator={false}> <StatusBar /> <View style={styles.innerContainer}> - <RCTextInput - label={I18n.t('Invite_Link')} - theme={theme} - value={invite && invite.url} - editable={false} - /> + <RCTextInput label={I18n.t('Invite_Link')} theme={theme} value={invite && invite.url} editable={false} /> {this.renderExpiration()} <View style={[styles.divider, { backgroundColor: themes[theme].separatorColor }]} /> - <Button - title={I18n.t('Share_Link')} - type='primary' - onPress={this.share} - theme={theme} - /> - <Button - title={I18n.t('Edit_Invite')} - type='secondary' - onPress={this.edit} - theme={theme} - /> + <Button title={I18n.t('Share_Link')} type='primary' onPress={this.share} theme={theme} /> + <Button title={I18n.t('Edit_Invite')} type='secondary' onPress={this.edit} theme={theme} /> </View> </ScrollView> </SafeAreaView> diff --git a/app/views/InviteUsersView/styles.js b/app/views/InviteUsersView/styles.js index eb8648e50..d79ba2934 100644 --- a/app/views/InviteUsersView/styles.js +++ b/app/views/InviteUsersView/styles.js @@ -1,4 +1,5 @@ import { StyleSheet } from 'react-native'; + import { PADDING_HORIZONTAL } from '../../containers/List/constants'; export default StyleSheet.create({ diff --git a/app/views/JitsiMeetView.js b/app/views/JitsiMeetView.js index 8d9c853f1..e80cdae85 100644 --- a/app/views/JitsiMeetView.js +++ b/app/views/JitsiMeetView.js @@ -8,14 +8,12 @@ import { connect } from 'react-redux'; import RocketChat from '../lib/rocketchat'; import { getUserSelector } from '../selectors/login'; import ActivityIndicator from '../containers/ActivityIndicator'; -import { logEvent, events } from '../utils/log'; +import { events, logEvent } from '../utils/log'; import { isAndroid, isIOS } from '../utils/deviceInfo'; import { withTheme } from '../theme'; -const formatUrl = (url, baseUrl, uriSize, avatarAuthURLFragment) => ( - `${ baseUrl }/avatar/${ url }?format=png&width=${ uriSize }&height=${ uriSize }${ avatarAuthURLFragment }` -); - +const formatUrl = (url, baseUrl, uriSize, avatarAuthURLFragment) => + `${baseUrl}/avatar/${url}?format=png&width=${uriSize}&height=${uriSize}${avatarAuthURLFragment}`; class JitsiMeetView extends React.Component { static propTypes = { navigation: PropTypes.object, @@ -28,7 +26,7 @@ class JitsiMeetView extends React.Component { name: PropTypes.string, token: PropTypes.string }) - } + }; constructor(props) { super(props); @@ -37,10 +35,8 @@ class JitsiMeetView extends React.Component { this.jitsiTimeout = null; const { user, baseUrl } = props; - const { - name: displayName, id: userId, token, username - } = user; - const avatarAuthURLFragment = `&rc_token=${ token }&rc_uid=${ userId }`; + const { name: displayName, id: userId, token, username } = user; + const avatarAuthURLFragment = `&rc_token=${token}&rc_uid=${userId}`; const avatar = formatUrl(username, baseUrl, 100, avatarAuthURLFragment); this.state = { userInfo: { @@ -79,7 +75,7 @@ class JitsiMeetView extends React.Component { onConferenceWillJoin = () => { this.setState({ loading: false }); - } + }; // Jitsi Update Timeout needs to be called every 10 seconds to make sure // call is not ended and is available to web users. @@ -94,13 +90,13 @@ class JitsiMeetView extends React.Component { this.jitsiTimeout = BackgroundTimer.setInterval(() => { RocketChat.updateJitsiTimeout(this.rid).catch(e => console.log(e)); }, 10000); - } + }; onConferenceTerminated = () => { logEvent(events.JM_CONFERENCE_TERMINATE); const { navigation } = this.props; navigation.pop(); - } + }; render() { const { userInfo, loading } = this.state; diff --git a/app/views/LanguageView/index.js b/app/views/LanguageView/index.js index b2e6e1079..29f65bf55 100644 --- a/app/views/LanguageView/index.js +++ b/app/views/LanguageView/index.js @@ -7,13 +7,13 @@ import RNRestart from 'react-native-restart'; import RocketChat from '../../lib/rocketchat'; import I18n, { LANGUAGES, isRTL } from '../../i18n'; import { showErrorAlert } from '../../utils/info'; -import log, { logEvent, events } from '../../utils/log'; +import log, { events, logEvent } from '../../utils/log'; import { setUser as setUserAction } from '../../actions/login'; import StatusBar from '../../containers/StatusBar'; import * as List from '../../containers/List'; import { themes } from '../../constants/colors'; import { withTheme } from '../../theme'; -import { appStart as appStartAction, ROOT_LOADING, ROOT_INSIDE } from '../../actions/app'; +import { ROOT_INSIDE, ROOT_LOADING, appStart as appStartAction } from '../../actions/app'; import { getUserSelector } from '../../selectors/login'; import database from '../../lib/database'; import SafeAreaView from '../../containers/SafeAreaView'; @@ -21,14 +21,14 @@ import SafeAreaView from '../../containers/SafeAreaView'; class LanguageView extends React.Component { static navigationOptions = () => ({ title: I18n.t('Change_Language') - }) + }); static propTypes = { user: PropTypes.object, setUser: PropTypes.func, appStart: PropTypes.func, theme: PropTypes.string - } + }; constructor(props) { super(props); @@ -52,12 +52,12 @@ class LanguageView extends React.Component { return false; } - formIsChanged = (language) => { + formIsChanged = language => { const { user } = this.props; - return (user.language !== language); - } + return user.language !== language; + }; - submit = async(language) => { + submit = async language => { if (!this.formIsChanged(language)) { return; } @@ -76,9 +76,9 @@ class LanguageView extends React.Component { } else { await appStart({ root: ROOT_INSIDE }); } - } + }; - changeLanguage = async(language) => { + changeLanguage = async language => { logEvent(events.LANG_SET_LANGUAGE); const { user, setUser } = this.props; @@ -95,10 +95,10 @@ class LanguageView extends React.Component { const serversDB = database.servers; const usersCollection = serversDB.get('users'); - await serversDB.action(async() => { + await serversDB.action(async () => { try { const userRecord = await usersCollection.find(user.id); - await userRecord.update((record) => { + await userRecord.update(record => { record.language = params.language; }); } catch (e) { @@ -110,12 +110,12 @@ class LanguageView extends React.Component { showErrorAlert(I18n.t('There_was_an_error_while_action', { action: I18n.t('saving_preferences') })); log(e); } - } + }; renderIcon = () => { const { theme } = this.props; return <List.Icon name='check' color={themes[theme].tintColor} />; - } + }; renderItem = ({ item }) => { const { value, label } = item; @@ -126,12 +126,12 @@ class LanguageView extends React.Component { <List.Item title={label} onPress={() => this.submit(value)} - testID={`language-view-${ value }`} + testID={`language-view-${value}`} right={isSelected ? this.renderIcon : null} translateTitle={false} /> ); - } + }; render() { return ( diff --git a/app/views/LegalView.js b/app/views/LegalView.js index e8d61fda0..b9db90572 100644 --- a/app/views/LegalView.js +++ b/app/views/LegalView.js @@ -13,15 +13,15 @@ class LegalView extends React.Component { static propTypes = { server: PropTypes.string, theme: PropTypes.string - } + }; onPressItem = ({ route }) => { const { server, theme } = this.props; if (!server) { return; } - openLink(`${ server }/${ route }`, theme); - } + openLink(`${server}/${route}`, theme); + }; render() { return ( diff --git a/app/views/LivechatEditView.js b/app/views/LivechatEditView.js index d5bae1a15..8a6108647 100644 --- a/app/views/LivechatEditView.js +++ b/app/views/LivechatEditView.js @@ -1,6 +1,6 @@ -import React, { useState, useEffect } from 'react'; +import React, { useEffect, useState } from 'react'; import PropTypes from 'prop-types'; -import { Text, StyleSheet, ScrollView } from 'react-native'; +import { ScrollView, StyleSheet, Text } from 'react-native'; import { connect } from 'react-redux'; import { BLOCK_CONTEXT } from '@rocket.chat/ui-kit'; @@ -11,7 +11,6 @@ import KeyboardView from '../presentation/KeyboardView'; import RocketChat from '../lib/rocketchat'; import I18n from '../i18n'; -import sharedStyles from './Styles'; import { LISTENER } from '../containers/Toast'; import EventEmitter from '../utils/events'; import scrollPersistTaps from '../utils/scrollPersistTaps'; @@ -19,6 +18,7 @@ import { getUserSelector } from '../selectors/login'; import Button from '../containers/Button'; import SafeAreaView from '../containers/SafeAreaView'; import { MultiSelect } from '../containers/UIKit/MultiSelect'; +import sharedStyles from './Styles'; const styles = StyleSheet.create({ container: { @@ -39,15 +39,14 @@ const styles = StyleSheet.create({ } }); -const Title = ({ title, theme }) => (title ? <Text style={[styles.title, { color: themes[theme].titleText }]}>{title}</Text> : null); +const Title = ({ title, theme }) => + title ? <Text style={[styles.title, { color: themes[theme].titleText }]}>{title}</Text> : null; Title.propTypes = { title: PropTypes.string, theme: PropTypes.string }; -const LivechatEditView = ({ - user, navigation, route, theme, editOmnichannelContact, editLivechatRoomCustomfields -}) => { +const LivechatEditView = ({ user, navigation, route, theme, editOmnichannelContact, editLivechatRoomCustomfields }) => { const [customFields, setCustomFields] = useState({}); const [availableUserTags, setAvailableUserTags] = useState([]); const [permissions, setPermissions] = useState([]); @@ -58,7 +57,7 @@ const LivechatEditView = ({ const livechat = route.params?.room ?? {}; const visitor = route.params?.roomUser ?? {}; - const getCustomFields = async() => { + const getCustomFields = async () => { const result = await RocketChat.getCustomFields(); if (result.success && result.customFields?.length) { const visitorCustomFields = result.customFields @@ -84,16 +83,16 @@ const LivechatEditView = ({ setTags(uniqueArray); }, [availableUserTags]); - const getTagsList = async(agentDepartments) => { + const getTagsList = async agentDepartments => { const tags = await RocketChat.getTagsList(); const isAdmin = ['admin', 'livechat-manager'].find(role => user.roles.includes(role)); const availableTags = tags - .filter(({ departments }) => isAdmin || (departments.length === 0 || departments.some(i => agentDepartments.indexOf(i) > -1))) + .filter(({ departments }) => isAdmin || departments.length === 0 || departments.some(i => agentDepartments.indexOf(i) > -1)) .map(({ name }) => name); setAvailableUserTags(availableTags); }; - const getAgentDepartments = async() => { + const getAgentDepartments = async () => { const result = await RocketChat.getAgentDepartments(visitor?._id); if (result.success) { const agentDepartments = result.departments.map(dept => dept.departmentId); @@ -101,7 +100,7 @@ const LivechatEditView = ({ } }; - const submit = async() => { + const submit = async () => { const userData = { _id: visitor?._id }; const { rid, sms } = livechat; @@ -150,9 +149,11 @@ const LivechatEditView = ({ } }; - const onChangeText = (key, text) => { params[key] = text; }; + const onChangeText = (key, text) => { + params[key] = text; + }; - const getPermissions = async() => { + const getPermissions = async () => { const permissionsArray = await RocketChat.hasPermission([editOmnichannelContact, editLivechatRoomCustomfields], livechat.rid); setPermissions(permissionsArray); }; @@ -167,34 +168,38 @@ const LivechatEditView = ({ <KeyboardView style={{ backgroundColor: themes[theme].auxiliaryBackground }} contentContainerStyle={sharedStyles.container} - keyboardVerticalOffset={128} - > + keyboardVerticalOffset={128}> <ScrollView {...scrollPersistTaps} style={styles.container}> <SafeAreaView> - <Title - title={visitor?.username} - theme={theme} - /> + <Title title={visitor?.username} theme={theme} /> <TextInput label={I18n.t('Name')} defaultValue={visitor?.name} onChangeText={text => onChangeText('name', text)} - onSubmitEditing={() => { inputs.name.focus(); }} + onSubmitEditing={() => { + inputs.name.focus(); + }} theme={theme} editable={!!permissions[0]} /> <TextInput label={I18n.t('Email')} - inputRef={(e) => { inputs.name = e; }} + inputRef={e => { + inputs.name = e; + }} defaultValue={visitor?.visitorEmails && visitor?.visitorEmails[0]?.address} onChangeText={text => onChangeText('email', text)} - onSubmitEditing={() => { inputs.phone.focus(); }} + onSubmitEditing={() => { + inputs.phone.focus(); + }} theme={theme} editable={!!permissions[0]} /> <TextInput label={I18n.t('Phone')} - inputRef={(e) => { inputs.phone = e; }} + inputRef={e => { + inputs.phone = e; + }} defaultValue={visitor?.phone && visitor?.phone[0]?.phoneNumber} onChangeText={text => onChangeText('phone', text)} onSubmitEditing={() => { @@ -213,7 +218,9 @@ const LivechatEditView = ({ <TextInput label={key} defaultValue={value} - inputRef={(e) => { inputs[key] = e; }} + inputRef={e => { + inputs[key] = e; + }} onChangeText={text => onChangeText(key, text)} onSubmitEditing={() => { if (array.length - 1 > index) { @@ -225,13 +232,12 @@ const LivechatEditView = ({ editable={!!permissions[0]} /> ))} - <Title - title={I18n.t('Conversation')} - theme={theme} - /> + <Title title={I18n.t('Conversation')} theme={theme} /> <TextInput label={I18n.t('Topic')} - inputRef={(e) => { inputs.topic = e; }} + inputRef={e => { + inputs.topic = e; + }} defaultValue={livechat?.topic} onChangeText={text => onChangeText('topic', text)} onSubmitEditing={() => inputs.tags.focus()} @@ -239,14 +245,7 @@ const LivechatEditView = ({ editable={!!permissions[1]} /> - <Text - style={[ - styles.label, - { color: themes[theme].titleText } - ]} - > - { I18n.t('Tags') } - </Text> + <Text style={[styles.label, { color: themes[theme].titleText }]}>{I18n.t('Tags')}</Text> <MultiSelect options={tagParam.map(tag => ({ text: { text: tag }, value: tag }))} onChange={({ value }) => { @@ -265,7 +264,9 @@ const LivechatEditView = ({ <TextInput label={key} defaultValue={value} - inputRef={(e) => { inputs[key] = e; }} + inputRef={e => { + inputs[key] = e; + }} onChangeText={text => onChangeText(key, text)} onSubmitEditing={() => { if (array.length - 1 > index) { @@ -278,11 +279,7 @@ const LivechatEditView = ({ /> ))} - <Button - title={I18n.t('Save')} - onPress={submit} - theme={theme} - /> + <Button title={I18n.t('Save')} onPress={submit} theme={theme} /> </SafeAreaView> </ScrollView> </KeyboardView> @@ -296,9 +293,9 @@ LivechatEditView.propTypes = { editOmnichannelContact: PropTypes.array, editLivechatRoomCustomfields: PropTypes.array }; -LivechatEditView.navigationOptions = ({ +LivechatEditView.navigationOptions = { title: I18n.t('Edit') -}); +}; const mapStateToProps = state => ({ server: state.server.server, diff --git a/app/views/LoginView.js b/app/views/LoginView.js index 31a56ae13..c240ab8f4 100644 --- a/app/views/LoginView.js +++ b/app/views/LoginView.js @@ -1,12 +1,9 @@ import React from 'react'; import PropTypes from 'prop-types'; -import { - Text, View, StyleSheet, Keyboard, Alert -} from 'react-native'; +import { Alert, Keyboard, StyleSheet, Text, View } from 'react-native'; import { connect } from 'react-redux'; import { dequal } from 'dequal'; -import sharedStyles from './Styles'; import Button from '../containers/Button'; import I18n from '../i18n'; import * as HeaderButton from '../containers/HeaderButton'; @@ -16,6 +13,7 @@ import FormContainer, { FormContainerInner } from '../containers/FormContainer'; import TextInput from '../containers/TextInput'; import { loginRequest as loginRequestAction } from '../actions/login'; import LoginServices from '../containers/LoginServices'; +import sharedStyles from './Styles'; const styles = StyleSheet.create({ registerDisabled: { @@ -52,7 +50,7 @@ class LoginView extends React.Component { static navigationOptions = ({ route, navigation }) => ({ title: route.params?.title ?? 'Rocket.Chat', headerRight: () => <HeaderButton.Legal testID='login-view-more' navigation={navigation} /> - }) + }); static propTypes = { navigation: PropTypes.object, @@ -70,7 +68,7 @@ class LoginView extends React.Component { theme: PropTypes.string, loginRequest: PropTypes.func, inviteLinkToken: PropTypes.string - } + }; constructor(props) { super(props); @@ -95,22 +93,22 @@ class LoginView extends React.Component { login = () => { const { navigation, Site_Name } = this.props; navigation.navigate('LoginView', { title: Site_Name }); - } + }; register = () => { const { navigation, Site_Name } = this.props; navigation.navigate('RegisterView', { title: Site_Name }); - } + }; forgotPassword = () => { const { navigation, Site_Name } = this.props; navigation.navigate('ForgotPasswordView', { title: Site_Name }); - } + }; valid = () => { const { user, password } = this.state; return user.trim() && password.trim(); - } + }; submit = () => { if (!this.valid()) { @@ -121,12 +119,18 @@ class LoginView extends React.Component { const { loginRequest } = this.props; Keyboard.dismiss(); loginRequest({ user, password }); - } + }; renderUserForm = () => { const { user } = this.state; const { - Accounts_EmailOrUsernamePlaceholder, Accounts_PasswordPlaceholder, Accounts_PasswordReset, Accounts_RegistrationForm_LinkReplacementText, isFetching, theme, Accounts_ShowFormLogin + Accounts_EmailOrUsernamePlaceholder, + Accounts_PasswordPlaceholder, + Accounts_PasswordReset, + Accounts_RegistrationForm_LinkReplacementText, + isFetching, + theme, + Accounts_ShowFormLogin } = this.props; if (!Accounts_ShowFormLogin) { @@ -143,7 +147,9 @@ class LoginView extends React.Component { keyboardType='email-address' returnKeyType='next' onChangeText={value => this.setState({ user: value })} - onSubmitEditing={() => { this.passwordInput.focus(); }} + onSubmitEditing={() => { + this.passwordInput.focus(); + }} testID='login-view-email' textContentType='username' autoCompleteType='username' @@ -153,7 +159,9 @@ class LoginView extends React.Component { <TextInput label={I18n.t('Password')} containerStyle={styles.inputContainer} - inputRef={(e) => { this.passwordInput = e; }} + inputRef={e => { + this.passwordInput = e; + }} placeholder={Accounts_PasswordPlaceholder || I18n.t('Password')} returnKeyType='send' secureTextEntry @@ -187,18 +195,24 @@ class LoginView extends React.Component { )} {this.showRegistrationButton ? ( <View style={styles.bottomContainer}> - <Text style={[styles.bottomContainerText, { color: themes[theme].auxiliaryText }]}>{I18n.t('Dont_Have_An_Account')}</Text> + <Text style={[styles.bottomContainerText, { color: themes[theme].auxiliaryText }]}> + {I18n.t('Dont_Have_An_Account')} + </Text> <Text style={[styles.bottomContainerTextBold, { color: themes[theme].actionTintColor }]} onPress={this.register} - testID='login-view-register' - >{I18n.t('Create_account')} + testID='login-view-register'> + {I18n.t('Create_account')} </Text> </View> - ) : (<Text style={[styles.registerDisabled, { color: themes[theme].auxiliaryText }]}>{Accounts_RegistrationForm_LinkReplacementText}</Text>)} + ) : ( + <Text style={[styles.registerDisabled, { color: themes[theme].auxiliaryText }]}> + {Accounts_RegistrationForm_LinkReplacementText} + </Text> + )} </> ); - } + }; render() { const { Accounts_ShowFormLogin, theme, navigation } = this.props; diff --git a/app/views/MarkdownTableView.js b/app/views/MarkdownTableView.js index 3c552bcf1..a95118d30 100644 --- a/app/views/MarkdownTableView.js +++ b/app/views/MarkdownTableView.js @@ -10,12 +10,12 @@ import { withTheme } from '../theme'; class MarkdownTableView extends React.Component { static navigationOptions = () => ({ title: I18n.t('Table') - }) + }); static propTypes = { route: PropTypes.object, theme: PropTypes.string - } + }; render() { const { route, theme } = this.props; @@ -32,9 +32,7 @@ class MarkdownTableView extends React.Component { return ( <ScrollView style={{ backgroundColor: themes[theme].backgroundColor }}> - <ScrollView horizontal> - {renderRows()} - </ScrollView> + <ScrollView horizontal>{renderRows()}</ScrollView> </ScrollView> ); } diff --git a/app/views/MessagesView/index.js b/app/views/MessagesView/index.js index f6ea91942..f0d0d89d6 100644 --- a/app/views/MessagesView/index.js +++ b/app/views/MessagesView/index.js @@ -1,10 +1,9 @@ import React from 'react'; import PropTypes from 'prop-types'; -import { FlatList, View, Text } from 'react-native'; +import { FlatList, Text, View } from 'react-native'; import { connect } from 'react-redux'; import { dequal } from 'dequal'; -import styles from './styles'; import Message from '../../containers/message'; import ActivityIndicator from '../../containers/ActivityIndicator'; import I18n from '../../i18n'; @@ -17,6 +16,7 @@ import { getUserSelector } from '../../selectors/login'; import { withActionSheet } from '../../containers/ActionSheet'; import SafeAreaView from '../../containers/SafeAreaView'; import getThreadName from '../../lib/methods/getThreadName'; +import styles from './styles'; class MessagesView extends React.Component { static propTypes = { @@ -29,7 +29,7 @@ class MessagesView extends React.Component { showActionSheet: PropTypes.func, useRealName: PropTypes.bool, isMasterDetail: PropTypes.bool - } + }; constructor(props) { super(props); @@ -49,9 +49,7 @@ class MessagesView extends React.Component { } shouldComponentUpdate(nextProps, nextState) { - const { - loading, messages, fileLoading - } = this.state; + const { loading, messages, fileLoading } = this.state; const { theme } = this.props; if (nextProps.theme !== theme) { return true; @@ -73,17 +71,17 @@ class MessagesView extends React.Component { navigation.setOptions({ title: I18n.t(route.params?.name) }); - } + }; - navToRoomInfo = (navParam) => { + navToRoomInfo = navParam => { const { navigation, user } = this.props; if (navParam.rid === user.id) { return; } navigation.navigate('RoomInfoView', navParam); - } + }; - jumpToMessage = async({ item }) => { + jumpToMessage = async ({ item }) => { const { navigation, isMasterDetail } = this.props; let params = { rid: this.rid, @@ -107,12 +105,10 @@ class MessagesView extends React.Component { } else { navigation.navigate('RoomView', params); } - } + }; - defineMessagesViewContent = (name) => { - const { - user, baseUrl, theme, useRealName - } = this.props; + defineMessagesViewContent = name => { + const { user, baseUrl, theme, useRealName } = this.props; const renderItemCommonProps = item => ({ item, baseUrl, @@ -130,11 +126,11 @@ class MessagesView extends React.Component { onPress: () => this.jumpToMessage({ item }) }); - return ({ + return { // Files Messages Screen Files: { name: I18n.t('Files'), - fetchFunc: async() => { + fetchFunc: async () => { const { messages } = this.state; const result = await RocketChat.getFiles(this.rid, this.t, messages.length); return { ...result, messages: result.files }; @@ -148,11 +144,13 @@ class MessagesView extends React.Component { ...item, u: item.user, ts: item.ts || item.uploadedAt, - attachments: [{ - title: item.name, - description: item.description, - ...getFileUrlFromMessage(item) - }] + attachments: [ + { + title: item.name, + description: item.description, + ...getFileUrlFromMessage(item) + } + ] }} theme={theme} /> @@ -163,46 +161,29 @@ class MessagesView extends React.Component { name: I18n.t('Mentions'), fetchFunc: () => { const { messages } = this.state; - return RocketChat.getMessages( - this.rid, - this.t, - { 'mentions._id': { $in: [user.id] } }, - messages.length - ); + return RocketChat.getMessages(this.rid, this.t, { 'mentions._id': { $in: [user.id] } }, messages.length); }, noDataMsg: I18n.t('No_mentioned_messages'), testID: 'mentioned-messages-view', - renderItem: item => ( - <Message - {...renderItemCommonProps(item)} - msg={item.msg} - theme={theme} - /> - ) + renderItem: item => <Message {...renderItemCommonProps(item)} msg={item.msg} theme={theme} /> }, // Starred Messages Screen Starred: { name: I18n.t('Starred'), fetchFunc: () => { const { messages } = this.state; - return RocketChat.getMessages( - this.rid, - this.t, - { 'starred._id': { $in: [user.id] } }, - messages.length - ); + return RocketChat.getMessages(this.rid, this.t, { 'starred._id': { $in: [user.id] } }, messages.length); }, noDataMsg: I18n.t('No_starred_messages'), testID: 'starred-messages-view', renderItem: item => ( - <Message - {...renderItemCommonProps(item)} - msg={item.msg} - onLongPress={() => this.onLongPress(item)} - theme={theme} - /> + <Message {...renderItemCommonProps(item)} msg={item.msg} onLongPress={() => this.onLongPress(item)} theme={theme} /> ), - action: message => ({ title: I18n.t('Unstar'), icon: message.starred ? 'star-filled' : 'star', onPress: this.handleActionPress }), + action: message => ({ + title: I18n.t('Unstar'), + icon: message.starred ? 'star-filled' : 'star', + onPress: this.handleActionPress + }), handleActionPress: message => RocketChat.toggleStarMessage(message._id, message.starred) }, // Pinned Messages Screen @@ -215,23 +196,16 @@ class MessagesView extends React.Component { noDataMsg: I18n.t('No_pinned_messages'), testID: 'pinned-messages-view', renderItem: item => ( - <Message - {...renderItemCommonProps(item)} - msg={item.msg} - onLongPress={() => this.onLongPress(item)} - theme={theme} - /> + <Message {...renderItemCommonProps(item)} msg={item.msg} onLongPress={() => this.onLongPress(item)} theme={theme} /> ), action: () => ({ title: I18n.t('Unpin'), icon: 'pin', onPress: this.handleActionPress }), handleActionPress: message => RocketChat.togglePinMessage(message._id, message.pinned) } - }[name]); - } + }[name]; + }; - load = async() => { - const { - messages, total, loading - } = this.state; + load = async () => { + const { messages, total, loading } = this.state; if (messages.length === total || loading) { return; } @@ -251,33 +225,33 @@ class MessagesView extends React.Component { this.setState({ loading: false }); console.warn('MessagesView -> catch -> error', error); } - } + }; - getCustomEmoji = (name) => { + getCustomEmoji = name => { const { customEmojis } = this.props; const emoji = customEmojis[name]; if (emoji) { return emoji; } return null; - } + }; - showAttachment = (attachment) => { + showAttachment = attachment => { const { navigation } = this.props; navigation.navigate('AttachmentView', { attachment }); - } + }; - onLongPress = (message) => { + onLongPress = message => { this.setState({ message }, this.showActionSheet); - } + }; showActionSheet = () => { const { message } = this.state; const { showActionSheet } = this.props; showActionSheet({ options: [this.content.action(message)], hasCancel: true }); - } + }; - handleActionPress = async() => { + handleActionPress = async () => { const { message } = this.state; try { @@ -291,28 +265,22 @@ class MessagesView extends React.Component { } catch { // Do nothing } - } + }; - setFileLoading = (fileLoading) => { + setFileLoading = fileLoading => { this.setState({ fileLoading }); - } + }; renderEmpty = () => { const { theme } = this.props; return ( - <View - style={[ - styles.listEmptyContainer, - { backgroundColor: themes[theme].backgroundColor } - ]} - testID={this.content.testID} - > + <View style={[styles.listEmptyContainer, { backgroundColor: themes[theme].backgroundColor }]} testID={this.content.testID}> <Text style={[styles.noDataFound, { color: themes[theme].titleText }]}>{this.content.noDataMsg}</Text> </View> ); - } + }; - renderItem = ({ item }) => this.content.renderItem(item) + renderItem = ({ item }) => this.content.renderItem(item); render() { const { messages, loading } = this.state; @@ -323,10 +291,7 @@ class MessagesView extends React.Component { } return ( - <SafeAreaView - style={{ backgroundColor: themes[theme].backgroundColor }} - testID={this.content.testID} - > + <SafeAreaView style={{ backgroundColor: themes[theme].backgroundColor }} testID={this.content.testID}> <StatusBar /> <FlatList data={messages} diff --git a/app/views/ModalBlockView.js b/app/views/ModalBlockView.js index 85eee8492..c87bf3317 100644 --- a/app/views/ModalBlockView.js +++ b/app/views/ModalBlockView.js @@ -11,11 +11,10 @@ import * as HeaderButton from '../containers/HeaderButton'; import { modalBlockWithContext } from '../containers/UIKit/MessageBlock'; import RocketChat from '../lib/rocketchat'; import ActivityIndicator from '../containers/ActivityIndicator'; -import { MODAL_ACTIONS, CONTAINER_TYPES } from '../lib/methods/actions'; - -import sharedStyles from './Styles'; +import { CONTAINER_TYPES, MODAL_ACTIONS } from '../lib/methods/actions'; import { textParser } from '../containers/UIKit/utils'; import Navigation from '../lib/Navigation'; +import sharedStyles from './Styles'; const styles = StyleSheet.create({ container: { @@ -31,7 +30,7 @@ const styles = StyleSheet.create({ } }); -Object.fromEntries = Object.fromEntries || (arr => arr.reduce((acc, [k, v]) => ((acc[k] = v, acc)), {})); +Object.fromEntries = Object.fromEntries || (arr => arr.reduce((acc, [k, v]) => ((acc[k] = v), acc), {})); const groupStateByBlockIdMap = (obj, [key, { blockId, value }]) => { obj[blockId] = obj[blockId] || {}; obj[blockId][key] = value; @@ -48,7 +47,10 @@ const filterInputFields = ({ element, elements = [] }) => { }; const mapElementToState = ({ element, blockId, elements = [] }) => { if (elements.length) { - return elements.map(e => ({ element: e, blockId })).filter(filterInputFields).map(mapElementToState); + return elements + .map(e => ({ element: e, blockId })) + .filter(filterInputFields) + .map(mapElementToState); } return [element.actionId, { value: element.initialValue, blockId }]; }; @@ -62,7 +64,7 @@ class ModalBlockView extends React.Component { return { title: textParser([title]) }; - } + }; static propTypes = { navigation: PropTypes.object, @@ -73,7 +75,7 @@ class ModalBlockView extends React.Component { id: PropTypes.string, token: PropTypes.string }) - } + }; constructor(props) { super(props); @@ -115,28 +117,32 @@ class ModalBlockView extends React.Component { const { title, close, submit } = view; navigation.setOptions({ title: textParser([title]), - headerLeft: close ? () => ( - <HeaderButton.Container> - <HeaderButton.Item - title={textParser([close.text])} - style={styles.submit} - onPress={this.cancel} - testID='close-modal-uikit' - /> - </HeaderButton.Container> - ) : null, - headerRight: submit ? () => ( - <HeaderButton.Container> - <HeaderButton.Item - title={textParser([submit.text])} - style={styles.submit} - onPress={this.submit} - testID='submit-modal-uikit' - /> - </HeaderButton.Container> - ) : null + headerLeft: close + ? () => ( + <HeaderButton.Container> + <HeaderButton.Item + title={textParser([close.text])} + style={styles.submit} + onPress={this.cancel} + testID='close-modal-uikit' + /> + </HeaderButton.Container> + ) + : null, + headerRight: submit + ? () => ( + <HeaderButton.Container> + <HeaderButton.Item + title={textParser([submit.text])} + style={styles.submit} + onPress={this.submit} + testID='submit-modal-uikit' + /> + </HeaderButton.Container> + ) + : null }); - } + }; handleUpdate = ({ type, ...data }) => { if ([MODAL_ACTIONS.ERRORS].includes(type)) { @@ -148,7 +154,7 @@ class ModalBlockView extends React.Component { } }; - cancel = async({ closeModal }) => { + cancel = async ({ closeModal }) => { const { data } = this.state; const { appId, viewId, view } = data; @@ -173,9 +179,9 @@ class ModalBlockView extends React.Component { } catch (e) { // do nothing } - } + }; - submit = async() => { + submit = async () => { const { data } = this.state; if (this.submitting) { return; @@ -204,7 +210,7 @@ class ModalBlockView extends React.Component { this.setState({ loading: false }); }; - action = async({ actionId, value, blockId }) => { + action = async ({ actionId, value, blockId }) => { const { data } = this.state; const { mid, appId, viewId } = data; await RocketChat.triggerBlockAction({ @@ -219,7 +225,7 @@ class ModalBlockView extends React.Component { mid }); this.changeState({ actionId, value, blockId }); - } + }; changeState = ({ actionId, value, blockId = 'default' }) => { this.values[actionId] = { @@ -237,28 +243,22 @@ class ModalBlockView extends React.Component { return ( <KeyboardAwareScrollView - style={[ - styles.container, - { backgroundColor: themes[theme].auxiliaryBackground } - ]} - keyboardShouldPersistTaps='always' - > + style={[styles.container, { backgroundColor: themes[theme].auxiliaryBackground }]} + keyboardShouldPersistTaps='always'> <View style={styles.content}> - { - React.createElement( - modalBlockWithContext({ - action: this.action, - state: this.changeState, - ...data - }), - { - blocks, - errors, - language, - values - } - ) - } + {React.createElement( + modalBlockWithContext({ + action: this.action, + state: this.changeState, + ...data + }), + { + blocks, + errors, + language, + values + } + )} </View> {loading ? <ActivityIndicator absolute size='large' theme={theme} /> : null} </KeyboardAwareScrollView> diff --git a/app/views/NewMessageView.js b/app/views/NewMessageView.js index 8d5f62ceb..4b210e7cb 100644 --- a/app/views/NewMessageView.js +++ b/app/views/NewMessageView.js @@ -1,19 +1,16 @@ import React from 'react'; import PropTypes from 'prop-types'; -import { - View, StyleSheet, FlatList, Text -} from 'react-native'; +import { FlatList, StyleSheet, Text, View } from 'react-native'; import { connect } from 'react-redux'; import { Q } from '@nozbe/watermelondb'; -import * as List from '../containers/List'; +import * as List from '../containers/List'; import Touch from '../utils/touch'; import database from '../lib/database'; import RocketChat from '../lib/rocketchat'; import UserItem from '../presentation/UserItem'; -import sharedStyles from './Styles'; import I18n from '../i18n'; -import log, { logEvent, events } from '../utils/log'; +import log, { events, logEvent } from '../utils/log'; import SearchBox from '../containers/SearchBox'; import { CustomIcon } from '../lib/Icons'; import * as HeaderButton from '../containers/HeaderButton'; @@ -26,6 +23,7 @@ import { createChannelRequest } from '../actions/createChannel'; import { goRoom } from '../utils/goRoom'; import SafeAreaView from '../containers/SafeAreaView'; import { compareServerVersion, methods } from '../lib/utils'; +import sharedStyles from './Styles'; const QUERY_SIZE = 50; @@ -52,7 +50,7 @@ class NewMessageView extends React.Component { static navigationOptions = ({ navigation }) => ({ headerLeft: () => <HeaderButton.CloseModal navigation={navigation} testID='new-message-view-close' />, title: I18n.t('New_Message') - }) + }); static propTypes = { navigation: PropTypes.object, @@ -78,23 +76,19 @@ class NewMessageView extends React.Component { } // eslint-disable-next-line react/sort-comp - init = async() => { + init = async () => { try { const db = database.active; const chats = await db.collections .get('subscriptions') - .query( - Q.where('t', 'd'), - Q.experimentalTake(QUERY_SIZE), - Q.experimentalSortBy('room_updated_at', Q.desc) - ) + .query(Q.where('t', 'd'), Q.experimentalTake(QUERY_SIZE), Q.experimentalSortBy('room_updated_at', Q.desc)) .fetch(); this.setState({ chats }); } catch (e) { log(e); } - } + }; onSearchChangeText(text) { this.search(text); @@ -103,26 +97,28 @@ class NewMessageView extends React.Component { dismiss = () => { const { navigation } = this.props; return navigation.pop(); - } + }; - search = async(text) => { + search = async text => { const result = await RocketChat.search({ text, filterRooms: false }); this.setState({ search: result }); - } + }; createChannel = () => { logEvent(events.NEW_MSG_CREATE_CHANNEL); const { navigation } = this.props; navigation.navigate('SelectedUsersViewCreateChannel', { nextAction: () => navigation.navigate('CreateChannelView') }); - } + }; createTeam = () => { logEvent(events.NEW_MSG_CREATE_TEAM); const { navigation } = this.props; - navigation.navigate('SelectedUsersViewCreateChannel', { nextAction: () => navigation.navigate('CreateChannelView', { isTeam: true }) }); - } + navigation.navigate('SelectedUsersViewCreateChannel', { + nextAction: () => navigation.navigate('CreateChannelView', { isTeam: true }) + }); + }; createGroupChat = () => { logEvent(events.NEW_MSG_CREATE_GROUP_CHAT); @@ -132,40 +128,38 @@ class NewMessageView extends React.Component { buttonText: I18n.t('Create'), maxUsers }); - } + }; - goRoom = (item) => { + goRoom = item => { logEvent(events.NEW_MSG_CHAT_WITH_USER); const { isMasterDetail, navigation } = this.props; if (isMasterDetail) { navigation.pop(); } goRoom({ item, isMasterDetail }); - } + }; - renderButton = ({ - onPress, testID, title, icon, first - }) => { + renderButton = ({ onPress, testID, title, icon, first }) => { const { theme } = this.props; return ( - <Touch - onPress={onPress} - style={{ backgroundColor: themes[theme].backgroundColor }} - testID={testID} - theme={theme} - > - <View style={[first ? sharedStyles.separatorVertical : sharedStyles.separatorBottom, styles.button, { borderColor: themes[theme].separatorColor }]}> + <Touch onPress={onPress} style={{ backgroundColor: themes[theme].backgroundColor }} testID={testID} theme={theme}> + <View + style={[ + first ? sharedStyles.separatorVertical : sharedStyles.separatorBottom, + styles.button, + { borderColor: themes[theme].separatorColor } + ]}> <CustomIcon style={[styles.buttonIcon, { color: themes[theme].tintColor }]} size={24} name={icon} /> <Text style={[styles.buttonText, { color: themes[theme].tintColor }]}>{title}</Text> </View> </Touch> ); - } + }; createDiscussion = () => { logEvent(events.NEW_MSG_CREATE_DISCUSSION); Navigation.navigate('CreateDiscussionView'); - } + }; renderHeader = () => { const { maxUsers, theme, serverVersion } = this.props; @@ -181,18 +175,21 @@ class NewMessageView extends React.Component { first: true })} {compareServerVersion(serverVersion, '3.13.0', methods.greaterThanOrEqualTo) - ? (this.renderButton({ - onPress: this.createTeam, - title: I18n.t('Create_Team'), - icon: 'teams', - testID: 'new-message-view-create-team' - })) : null} - {maxUsers > 2 ? this.renderButton({ - onPress: this.createGroupChat, - title: I18n.t('Create_Direct_Messages'), - icon: 'message', - testID: 'new-message-view-create-direct-message' - }) : null} + ? this.renderButton({ + onPress: this.createTeam, + title: I18n.t('Create_Team'), + icon: 'teams', + testID: 'new-message-view-create-team' + }) + : null} + {maxUsers > 2 + ? this.renderButton({ + onPress: this.createGroupChat, + title: I18n.t('Create_Direct_Messages'), + icon: 'message', + testID: 'new-message-view-create-direct-message' + }) + : null} {this.renderButton({ onPress: this.createDiscussion, title: I18n.t('Create_Discussion'), @@ -202,8 +199,7 @@ class NewMessageView extends React.Component { </View> </View> ); - } - + }; renderItem = ({ item, index }) => { const { search, chats } = this.state; @@ -225,13 +221,13 @@ class NewMessageView extends React.Component { username={item.search ? item.username : item.name} onPress={() => this.goRoom(item)} baseUrl={baseUrl} - testID={`new-message-view-item-${ item.name }`} + testID={`new-message-view-item-${item.name}`} style={style} user={user} theme={theme} /> ); - } + }; renderList = () => { const { search, chats } = this.state; @@ -248,7 +244,7 @@ class NewMessageView extends React.Component { keyboardShouldPersistTaps='always' /> ); - } + }; render() { return ( diff --git a/app/views/NewServerView/ServerInput/Item.js b/app/views/NewServerView/ServerInput/Item.js index b249c88e0..59ae77f07 100644 --- a/app/views/NewServerView/ServerInput/Item.js +++ b/app/views/NewServerView/ServerInput/Item.js @@ -1,7 +1,5 @@ import React from 'react'; -import { - View, StyleSheet, Text -} from 'react-native'; +import { StyleSheet, Text, View } from 'react-native'; import PropTypes from 'prop-types'; import { BorderlessButton } from 'react-native-gesture-handler'; @@ -29,15 +27,17 @@ const styles = StyleSheet.create({ } }); -const Item = ({ - item, theme, onPress, onDelete -}) => ( - <Touch style={styles.container} onPress={() => onPress(item.url)} theme={theme} testID={`server-history-${ item.url }`}> +const Item = ({ item, theme, onPress, onDelete }) => ( + <Touch style={styles.container} onPress={() => onPress(item.url)} theme={theme} testID={`server-history-${item.url}`}> <View style={styles.content}> - <Text numberOfLines={1} style={[styles.server, { color: themes[theme].bodyText }]}>{item.url}</Text> - <Text numberOfLines={1} style={[styles.username, { color: themes[theme].auxiliaryText }]}>{item.username}</Text> + <Text numberOfLines={1} style={[styles.server, { color: themes[theme].bodyText }]}> + {item.url} + </Text> + <Text numberOfLines={1} style={[styles.username, { color: themes[theme].auxiliaryText }]}> + {item.username} + </Text> </View> - <BorderlessButton onPress={() => onDelete(item)} testID={`server-history-delete-${ item.url }`}> + <BorderlessButton onPress={() => onDelete(item)} testID={`server-history-delete-${item.url}`}> <CustomIcon name='delete' size={24} color={themes[theme].auxiliaryText} /> </BorderlessButton> </Touch> diff --git a/app/views/NewServerView/ServerInput/index.js b/app/views/NewServerView/ServerInput/index.js index 6088e4ef6..ef80c4685 100644 --- a/app/views/NewServerView/ServerInput/index.js +++ b/app/views/NewServerView/ServerInput/index.js @@ -1,18 +1,16 @@ import React, { useState } from 'react'; -import { View, FlatList, StyleSheet } from 'react-native'; +import { FlatList, StyleSheet, View } from 'react-native'; import PropTypes from 'prop-types'; import TextInput from '../../../containers/TextInput'; import * as List from '../../../containers/List'; import { themes } from '../../../constants/colors'; -import Item from './Item'; import I18n from '../../../i18n'; +import Item from './Item'; const styles = StyleSheet.create({ container: { - zIndex: 1, - marginTop: 24, - marginBottom: 32 + zIndex: 1 }, inputContainer: { marginTop: 0, @@ -30,15 +28,7 @@ const styles = StyleSheet.create({ } }); -const ServerInput = ({ - text, - theme, - serversHistory, - onChangeText, - onSubmit, - onDelete, - onPressServerHistory -}) => { +const ServerInput = ({ text, theme, serversHistory, onChangeText, onSubmit, onDelete, onPressServerHistory }) => { const [focused, setFocused] = useState(false); return ( <View style={styles.container}> @@ -58,19 +48,22 @@ const ServerInput = ({ onFocus={() => setFocused(true)} onBlur={() => setFocused(false)} /> - { - focused && serversHistory?.length - ? ( - <View style={[styles.serverHistory, { backgroundColor: themes[theme].backgroundColor, borderColor: themes[theme].separatorColor }]}> - <FlatList - data={serversHistory} - renderItem={({ item }) => <Item item={item} theme={theme} onPress={() => onPressServerHistory(item)} onDelete={onDelete} />} - ItemSeparatorComponent={List.Separator} - keyExtractor={item => item.id} - /> - </View> - ) : null - } + {focused && serversHistory?.length ? ( + <View + style={[ + styles.serverHistory, + { backgroundColor: themes[theme].backgroundColor, borderColor: themes[theme].separatorColor } + ]}> + <FlatList + data={serversHistory} + renderItem={({ item }) => ( + <Item item={item} theme={theme} onPress={() => onPressServerHistory(item)} onDelete={onDelete} /> + )} + ItemSeparatorComponent={List.Separator} + keyExtractor={item => item.id} + /> + </View> + ) : null} </View> ); }; diff --git a/app/views/NewServerView/index.js b/app/views/NewServerView/index.js index 27422dec8..8eaba349e 100644 --- a/app/views/NewServerView/index.js +++ b/app/views/NewServerView/index.js @@ -1,17 +1,16 @@ import React from 'react'; import PropTypes from 'prop-types'; -import { - Text, Keyboard, StyleSheet, View, BackHandler -} from 'react-native'; +import { Text, Keyboard, StyleSheet, View, BackHandler, Image } from 'react-native'; import { connect } from 'react-redux'; import { Base64 } from 'js-base64'; import parse from 'url-parse'; import { Q } from '@nozbe/watermelondb'; - import { TouchableOpacity } from 'react-native-gesture-handler'; +import Orientation from 'react-native-orientation-locker'; + import UserPreferences from '../../lib/userPreferences'; import EventEmitter from '../../utils/events'; -import { selectServerRequest, serverRequest } from '../../actions/server'; +import { selectServerRequest, serverRequest, serverFinishAdd as serverFinishAddAction } from '../../actions/server'; import { inviteLinksClear as inviteLinksClearAction } from '../../actions/inviteLinks'; import sharedStyles from '../Styles'; import Button from '../../containers/Button'; @@ -19,41 +18,48 @@ import OrSeparator from '../../containers/OrSeparator'; import FormContainer, { FormContainerInner } from '../../containers/FormContainer'; import I18n from '../../i18n'; import { themes } from '../../constants/colors'; -import { logEvent, events } from '../../utils/log'; +import { events, logEvent } from '../../utils/log'; import { animateNextTransition } from '../../utils/layoutAnimation'; import { withTheme } from '../../theme'; -import { setBasicAuth, BASIC_AUTH_KEY } from '../../utils/fetch'; +import { BASIC_AUTH_KEY, setBasicAuth } from '../../utils/fetch'; import * as HeaderButton from '../../containers/HeaderButton'; import { showConfirmationAlert } from '../../utils/info'; import database from '../../lib/database'; -import ServerInput from './ServerInput'; import { sanitizeLikeString } from '../../lib/database/utils'; import SSLPinning from '../../utils/sslPinning'; import RocketChat from '../../lib/rocketchat'; +import { isTablet } from '../../utils/deviceInfo'; +import { verticalScale, moderateScale } from '../../utils/scaling'; +import { withDimensions } from '../../dimensions'; +import ServerInput from './ServerInput'; const styles = StyleSheet.create({ + onboardingImage: { + alignSelf: 'center', + resizeMode: 'contain' + }, title: { ...sharedStyles.textBold, - fontSize: 22 + letterSpacing: 0, + alignSelf: 'center' + }, + subtitle: { + ...sharedStyles.textRegular, + alignSelf: 'center' }, certificatePicker: { - marginBottom: 32, alignItems: 'center', justifyContent: 'flex-end' }, chooseCertificateTitle: { - fontSize: 13, ...sharedStyles.textRegular }, chooseCertificate: { - fontSize: 13, ...sharedStyles.textSemibold }, description: { ...sharedStyles.textRegular, - fontSize: 14, - textAlign: 'left', - marginBottom: 24 + textAlign: 'center' }, connectButton: { marginBottom: 0 @@ -61,23 +67,22 @@ const styles = StyleSheet.create({ }); class NewServerView extends React.Component { - static navigationOptions = () => ({ - title: I18n.t('Workspaces') - }) - static propTypes = { navigation: PropTypes.object, theme: PropTypes.string, connecting: PropTypes.bool.isRequired, connectServer: PropTypes.func.isRequired, selectServer: PropTypes.func.isRequired, - adding: PropTypes.bool, previousServer: PropTypes.string, - inviteLinksClear: PropTypes.func - } + inviteLinksClear: PropTypes.func, + serverFinishAdd: PropTypes.func + }; constructor(props) { super(props); + if (!isTablet) { + Orientation.lockToPortrait(); + } this.setHeader(); this.state = { @@ -94,26 +99,28 @@ class NewServerView extends React.Component { this.queryServerHistory(); } - componentDidUpdate(prevProps) { - const { adding } = this.props; - if (prevProps.adding !== adding) { - this.setHeader(); - } - } - componentWillUnmount() { EventEmitter.removeListener('NewServer', this.handleNewServerEvent); BackHandler.removeEventListener('hardwareBackPress', this.handleBackPress); + const { previousServer, serverFinishAdd } = this.props; + if (previousServer) { + serverFinishAdd(); + } } setHeader = () => { - const { adding, navigation } = this.props; - if (adding) { - navigation.setOptions({ + const { previousServer, navigation } = this.props; + if (previousServer) { + return navigation.setOptions({ + headerTitle: I18n.t('Workspaces'), headerLeft: () => <HeaderButton.CloseModal navigation={navigation} onPress={this.close} testID='new-server-view-close' /> }); } - } + + return navigation.setOptions({ + headerShown: false + }); + }; handleBackPress = () => { const { navigation, previousServer } = this.props; @@ -122,43 +129,36 @@ class NewServerView extends React.Component { return true; } return false; - } + }; - onChangeText = (text) => { + onChangeText = text => { this.setState({ text }); this.queryServerHistory(text); - } + }; - queryServerHistory = async(text) => { + queryServerHistory = async text => { const db = database.servers; try { const serversHistoryCollection = db.get('servers_history'); - let whereClause = [ - Q.where('username', Q.notEq(null)), - Q.experimentalSortBy('updated_at', Q.desc), - Q.experimentalTake(3) - ]; + let whereClause = [Q.where('username', Q.notEq(null)), Q.experimentalSortBy('updated_at', Q.desc), Q.experimentalTake(3)]; const likeString = sanitizeLikeString(text); if (text) { - whereClause = [ - ...whereClause, - Q.where('url', Q.like(`%${ likeString }%`)) - ]; + whereClause = [...whereClause, Q.where('url', Q.like(`%${likeString}%`))]; } const serversHistory = await serversHistoryCollection.query(...whereClause).fetch(); this.setState({ serversHistory }); } catch { // Do nothing } - } + }; close = () => { const { selectServer, previousServer, inviteLinksClear } = this.props; inviteLinksClear(); selectServer(previousServer); - } + }; - handleNewServerEvent = (event) => { + handleNewServerEvent = event => { let { server } = event; if (!server) { return; @@ -167,13 +167,15 @@ class NewServerView extends React.Component { this.setState({ text: server }); server = this.completeUrl(server); connectServer(server); - } + }; - onPressServerHistory = (serverHistory) => { - this.setState({ text: serverHistory?.url }, () => this.submit({ fromServerHistory: true, username: serverHistory?.username })); - } + onPressServerHistory = serverHistory => { + this.setState({ text: serverHistory?.url }, () => + this.submit({ fromServerHistory: true, username: serverHistory?.username }) + ); + }; - submit = async({ fromServerHistory = false, username }) => { + submit = async ({ fromServerHistory = false, username }) => { logEvent(events.NS_CONNECT_TO_WORKSPACE); const { text, certificate } = this.state; const { connectServer } = this.props; @@ -185,7 +187,7 @@ class NewServerView extends React.Component { const server = this.completeUrl(text); // Save info - SSL Pinning - await UserPreferences.setStringAsync(`${ RocketChat.CERTIFICATE_KEY }-${ server }`, certificate); + await UserPreferences.setStringAsync(`${RocketChat.CERTIFICATE_KEY}-${server}`, certificate); // Save info - HTTP Basic Authentication await this.basicAuth(server, text); @@ -196,38 +198,38 @@ class NewServerView extends React.Component { connectServer(server); } } - } + }; connectOpen = () => { logEvent(events.NS_JOIN_OPEN_WORKSPACE); this.setState({ connectingOpen: true }); const { connectServer } = this.props; connectServer('https://open.rocket.chat'); - } + }; - basicAuth = async(server, text) => { + basicAuth = async (server, text) => { try { const parsedUrl = parse(text, true); if (parsedUrl.auth.length) { const credentials = Base64.encode(parsedUrl.auth); - await UserPreferences.setStringAsync(`${ BASIC_AUTH_KEY }-${ server }`, credentials); + await UserPreferences.setStringAsync(`${BASIC_AUTH_KEY}-${server}`, credentials); setBasicAuth(credentials); } } catch { // do nothing } - } + }; - chooseCertificate = async() => { + chooseCertificate = async () => { try { const certificate = await SSLPinning.pickCertificate(); this.setState({ certificate }); } catch { // Do nothing } - } + }; - completeUrl = (url) => { + completeUrl = url => { const parsedUrl = parse(url, true); if (parsedUrl.auth.length) { url = parsedUrl.origin; @@ -235,28 +237,27 @@ class NewServerView extends React.Component { url = url && url.replace(/\s/g, ''); - if (/^(\w|[0-9-_]){3,}$/.test(url) - && /^(htt(ps?)?)|(loca((l)?|(lh)?|(lho)?|(lhos)?|(lhost:?\d*)?)$)/.test(url) === false) { - url = `${ url }.rocket.chat`; + if (/^(\w|[0-9-_]){3,}$/.test(url) && /^(htt(ps?)?)|(loca((l)?|(lh)?|(lho)?|(lhos)?|(lhost:?\d*)?)$)/.test(url) === false) { + url = `${url}.rocket.chat`; } if (/^(https?:\/\/)?(((\w|[0-9-_])+(\.(\w|[0-9-_])+)+)|localhost)(:\d+)?$/.test(url)) { if (/^localhost(:\d+)?/.test(url)) { - url = `http://${ url }`; + url = `http://${url}`; } else if (/^https?:\/\//.test(url) === false) { - url = `https://${ url }`; + url = `https://${url}`; } } return url.replace(/\/+$/, '').replace(/\\/g, '/'); - } + }; uriToPath = uri => uri.replace('file://', ''); - saveCertificate = (certificate) => { + saveCertificate = certificate => { animateNextTransition(); this.setState({ certificate }); - } + }; handleRemove = () => { showConfirmationAlert({ @@ -264,64 +265,93 @@ class NewServerView extends React.Component { confirmationText: I18n.t('Remove'), onPress: this.setState({ certificate: null }) // We not need delete file from DocumentPicker because it is a temp file }); - } + }; - deleteServerHistory = async(item) => { + deleteServerHistory = async item => { const { serversHistory } = this.state; const db = database.servers; try { - await db.action(async() => { + await db.action(async () => { await item.destroyPermanently(); }); this.setState({ serversHistory: serversHistory.filter(server => server.id !== item.id) }); } catch { // Nothing } - } + }; renderCertificatePicker = () => { const { certificate } = this.state; - const { theme } = this.props; + const { theme, width, height, previousServer } = this.props; return ( - <View style={styles.certificatePicker}> + <View + style={[ + styles.certificatePicker, + { + marginBottom: verticalScale(previousServer && !isTablet ? 10 : 30, height) + } + ]}> <Text style={[ styles.chooseCertificateTitle, - { color: themes[theme].auxiliaryText } - ]} - > + { color: themes[theme].auxiliaryText, fontSize: moderateScale(13, null, width) } + ]}> {certificate ? I18n.t('Your_certificate') : I18n.t('Do_you_have_a_certificate')} </Text> <TouchableOpacity onPress={certificate ? this.handleRemove : this.chooseCertificate} - testID='new-server-choose-certificate' - > - <Text - style={[ - styles.chooseCertificate, - { color: themes[theme].tintColor } - ]} - > + testID='new-server-choose-certificate'> + <Text style={[styles.chooseCertificate, { color: themes[theme].tintColor, fontSize: moderateScale(13, null, width) }]}> {certificate ?? I18n.t('Apply_Your_Certificate')} </Text> </TouchableOpacity> </View> ); - } + }; render() { - const { connecting, theme } = this.props; - const { - text, connectingOpen, serversHistory - } = this.state; + const { connecting, theme, previousServer, width, height } = this.props; + const { text, connectingOpen, serversHistory } = this.state; + const marginTop = previousServer ? 0 : 35; + return ( - <FormContainer - theme={theme} - testID='new-server-view' - keyboardShouldPersistTaps='never' - > + <FormContainer theme={theme} testID='new-server-view' keyboardShouldPersistTaps='never'> <FormContainerInner> - <Text style={[styles.title, { color: themes[theme].titleText }]}>{I18n.t('Join_your_workspace')}</Text> + <Image + style={[ + styles.onboardingImage, + { + marginBottom: verticalScale(10, height), + marginTop: isTablet ? 0 : verticalScale(marginTop, height), + width: verticalScale(100, height), + height: verticalScale(100, height) + } + ]} + source={require('../../static/images/logo.png')} + fadeDuration={0} + /> + <Text + style={[ + styles.title, + { + color: themes[theme].titleText, + fontSize: moderateScale(22, null, width), + marginBottom: verticalScale(8, height) + } + ]}> + Rocket.Chat + </Text> + <Text + style={[ + styles.subtitle, + { + color: themes[theme].controlText, + fontSize: moderateScale(16, null, width), + marginBottom: verticalScale(30, height) + } + ]}> + {I18n.t('Onboarding_subtitle')} + </Text> <ServerInput text={text} theme={theme} @@ -337,12 +367,22 @@ class NewServerView extends React.Component { onPress={this.submit} disabled={!text || connecting} loading={!connectingOpen && connecting} - style={styles.connectButton} + style={[styles.connectButton, { marginTop: verticalScale(16, height) }]} theme={theme} testID='new-server-view-button' /> <OrSeparator theme={theme} /> - <Text style={[styles.description, { color: themes[theme].auxiliaryText }]}>{I18n.t('Onboarding_join_open_description')}</Text> + <Text + style={[ + styles.description, + { + color: themes[theme].auxiliaryText, + fontSize: moderateScale(14, null, width), + marginBottom: verticalScale(16, height) + } + ]}> + {I18n.t('Onboarding_join_open_description')} + </Text> <Button title={I18n.t('Join_our_open_workspace')} type='secondary' @@ -362,14 +402,14 @@ class NewServerView extends React.Component { const mapStateToProps = state => ({ connecting: state.server.connecting, - adding: state.server.adding, previousServer: state.server.previousServer }); const mapDispatchToProps = dispatch => ({ connectServer: (...params) => dispatch(serverRequest(...params)), selectServer: server => dispatch(selectServerRequest(server)), - inviteLinksClear: () => dispatch(inviteLinksClearAction()) + inviteLinksClear: () => dispatch(inviteLinksClearAction()), + serverFinishAdd: () => dispatch(serverFinishAddAction()) }); -export default connect(mapStateToProps, mapDispatchToProps)(withTheme(NewServerView)); +export default connect(mapStateToProps, mapDispatchToProps)(withDimensions(withTheme(NewServerView))); diff --git a/app/views/NotificationPreferencesView/index.js b/app/views/NotificationPreferencesView/index.js index debea1492..f0d7437a6 100644 --- a/app/views/NotificationPreferencesView/index.js +++ b/app/views/NotificationPreferencesView/index.js @@ -1,5 +1,5 @@ import React from 'react'; -import { Switch, Text, StyleSheet } from 'react-native'; +import { StyleSheet, Switch, Text } from 'react-native'; import PropTypes from 'prop-types'; import database from '../../lib/database'; @@ -12,8 +12,8 @@ import { withTheme } from '../../theme'; import protectedFunction from '../../lib/methods/helpers/protectedFunction'; import SafeAreaView from '../../containers/SafeAreaView'; import log, { events, logEvent } from '../../utils/log'; -import { OPTIONS } from './options'; import sharedStyles from '../Styles'; +import { OPTIONS } from './options'; const styles = StyleSheet.create({ pickerText: { @@ -25,7 +25,7 @@ const styles = StyleSheet.create({ class NotificationPreferencesView extends React.Component { static navigationOptions = () => ({ title: I18n.t('Notification_Preferences') - }) + }); static propTypes = { navigation: PropTypes.object, @@ -43,14 +43,13 @@ class NotificationPreferencesView extends React.Component { }; if (room && room.observe) { this.roomObservable = room.observe(); - this.subscription = this.roomObservable - .subscribe((changes) => { - if (this.mounted) { - this.setState({ room: changes }); - } else { - this.state.room = changes; - } - }); + this.subscription = this.roomObservable.subscribe(changes => { + if (this.mounted) { + this.setState({ room: changes }); + } else { + this.state.room = changes; + } + }); } } @@ -64,16 +63,18 @@ class NotificationPreferencesView extends React.Component { } } - saveNotificationSettings = async(key, value, params) => { - logEvent(events[`NP_${ key.toUpperCase() }`]); + saveNotificationSettings = async (key, value, params) => { + logEvent(events[`NP_${key.toUpperCase()}`]); const { room } = this.state; const db = database.active; try { - await db.action(async() => { - await room.update(protectedFunction((r) => { - r[key] = value; - })); + await db.action(async () => { + await room.update( + protectedFunction(r => { + r[key] = value; + }) + ); }); try { @@ -85,16 +86,18 @@ class NotificationPreferencesView extends React.Component { // do nothing } - await db.action(async() => { - await room.update(protectedFunction((r) => { - r[key] = room[key]; - })); + await db.action(async () => { + await room.update( + protectedFunction(r => { + r[key] = room[key]; + }) + ); }); } catch (e) { - logEvent(events[`NP_${ key.toUpperCase() }_F`]); + logEvent(events[`NP_${key.toUpperCase()}_F`]); log(e); } - } + }; onValueChangeSwitch = (key, value) => this.saveNotificationSettings(key, value, { [key]: value ? '1' : '0' }); @@ -109,16 +112,20 @@ class NotificationPreferencesView extends React.Component { value: room[key], onChangeValue: value => this.onValueChangePicker(key, value) }); - } + }; - renderPickerOption = (key) => { + renderPickerOption = key => { const { room } = this.state; const { theme } = this.props; const text = room[key] ? OPTIONS[key].find(option => option.value === room[key]) : OPTIONS[key][0]; - return <Text style={[styles.pickerText, { color: themes[theme].actionTintColor }]}>{I18n.t(text?.label, { defaultValue: text?.label, second: text?.second })}</Text>; - } + return ( + <Text style={[styles.pickerText, { color: themes[theme].actionTintColor }]}> + {I18n.t(text?.label, { defaultValue: text?.label, second: text?.second })} + </Text> + ); + }; - renderSwitch = (key) => { + renderSwitch = key => { const { room } = this.state; return ( <Switch @@ -128,7 +135,7 @@ class NotificationPreferencesView extends React.Component { onValueChange={value => this.onValueChangeSwitch(key, !value)} /> ); - } + }; render() { const { room } = this.state; diff --git a/app/views/NotificationPreferencesView/options.js b/app/views/NotificationPreferencesView/options.js index 7c572718f..660ff6df0 100644 --- a/app/views/NotificationPreferencesView/options.js +++ b/app/views/NotificationPreferencesView/options.js @@ -1,68 +1,139 @@ export const OPTIONS = { - desktopNotifications: [{ - label: 'Default', value: 'default' - }, { - label: 'All_Messages', value: 'all' - }, { - label: 'Mentions', value: 'mentions' - }, { - label: 'Nothing', value: 'nothing' - }], - audioNotifications: [{ - label: 'Default', value: 'default' - }, { - label: 'All_Messages', value: 'all' - }, { - label: 'Mentions', value: 'mentions' - }, { - label: 'Nothing', value: 'nothing' - }], - mobilePushNotifications: [{ - label: 'Default', value: 'default' - }, { - label: 'All_Messages', value: 'all' - }, { - label: 'Mentions', value: 'mentions' - }, { - label: 'Nothing', value: 'nothing' - }], - emailNotifications: [{ - label: 'Default', value: 'default' - }, { - label: 'All_Messages', value: 'all' - }, { - label: 'Mentions', value: 'mentions' - }, { - label: 'Nothing', value: 'nothing' - }], - desktopNotificationDuration: [{ - label: 'Default', value: 0 - }, { - label: 'Seconds', second: 1, value: 1 - }, { - label: 'Seconds', second: 2, value: 2 - }, { - label: 'Seconds', second: 3, value: 3 - }, { - label: 'Seconds', second: 4, value: 4 - }, { - label: 'Seconds', second: 5, value: 5 - }], - audioNotificationValue: [{ - label: 'None', value: 'none None' - }, { - label: 'Default', value: '0 Default' - }, { - label: 'Beep', value: 'beep Beep' - }, { - label: 'Ding', value: 'ding Ding' - }, { - label: 'Chelle', value: 'chelle Chelle' - }, { - label: 'Droplet', value: 'droplet Droplet' - }, { - label: 'Highbell', value: 'highbell Highbell' - }, { - label: 'Seasons', value: 'seasons Seasons' - }] + desktopNotifications: [ + { + label: 'Default', + value: 'default' + }, + { + label: 'All_Messages', + value: 'all' + }, + { + label: 'Mentions', + value: 'mentions' + }, + { + label: 'Nothing', + value: 'nothing' + } + ], + audioNotifications: [ + { + label: 'Default', + value: 'default' + }, + { + label: 'All_Messages', + value: 'all' + }, + { + label: 'Mentions', + value: 'mentions' + }, + { + label: 'Nothing', + value: 'nothing' + } + ], + mobilePushNotifications: [ + { + label: 'Default', + value: 'default' + }, + { + label: 'All_Messages', + value: 'all' + }, + { + label: 'Mentions', + value: 'mentions' + }, + { + label: 'Nothing', + value: 'nothing' + } + ], + emailNotifications: [ + { + label: 'Default', + value: 'default' + }, + { + label: 'All_Messages', + value: 'all' + }, + { + label: 'Mentions', + value: 'mentions' + }, + { + label: 'Nothing', + value: 'nothing' + } + ], + desktopNotificationDuration: [ + { + label: 'Default', + value: 0 + }, + { + label: 'Seconds', + second: 1, + value: 1 + }, + { + label: 'Seconds', + second: 2, + value: 2 + }, + { + label: 'Seconds', + second: 3, + value: 3 + }, + { + label: 'Seconds', + second: 4, + value: 4 + }, + { + label: 'Seconds', + second: 5, + value: 5 + } + ], + audioNotificationValue: [ + { + label: 'None', + value: 'none None' + }, + { + label: 'Default', + value: '0 Default' + }, + { + label: 'Beep', + value: 'beep Beep' + }, + { + label: 'Ding', + value: 'ding Ding' + }, + { + label: 'Chelle', + value: 'chelle Chelle' + }, + { + label: 'Droplet', + value: 'droplet Droplet' + }, + { + label: 'Highbell', + value: 'highbell Highbell' + }, + { + label: 'Seasons', + value: 'seasons Seasons' + } + ] }; diff --git a/app/views/OnboardingView/index.js b/app/views/OnboardingView/index.js deleted file mode 100644 index a3cd50c28..000000000 --- a/app/views/OnboardingView/index.js +++ /dev/null @@ -1,89 +0,0 @@ -import React from 'react'; -import { - View, Text, Image, Linking -} from 'react-native'; -import PropTypes from 'prop-types'; -import Orientation from 'react-native-orientation-locker'; - -import I18n from '../../i18n'; -import Button from '../../containers/Button'; -import styles from './styles'; -import { isTablet } from '../../utils/deviceInfo'; -import { themes } from '../../constants/colors'; -import { withTheme } from '../../theme'; -import FormContainer, { FormContainerInner } from '../../containers/FormContainer'; -import { logEvent, events } from '../../utils/log'; - -class OnboardingView extends React.Component { - static navigationOptions = { - headerShown: false - }; - - static propTypes = { - navigation: PropTypes.object, - theme: PropTypes.string - } - - constructor(props) { - super(props); - if (!isTablet) { - Orientation.lockToPortrait(); - } - } - - shouldComponentUpdate(nextProps) { - const { theme } = this.props; - if (theme !== nextProps.theme) { - return true; - } - return false; - } - - connectServer = () => { - logEvent(events.ONBOARD_JOIN_A_WORKSPACE); - const { navigation } = this.props; - navigation.navigate('NewServerView'); - } - - createWorkspace = async() => { - logEvent(events.ONBOARD_CREATE_NEW_WORKSPACE); - try { - await Linking.openURL('https://cloud.rocket.chat/trial'); - } catch { - logEvent(events.ONBOARD_CREATE_NEW_WORKSPACE_F); - } - } - - render() { - const { theme } = this.props; - return ( - <FormContainer theme={theme} testID='onboarding-view'> - <FormContainerInner> - <Image style={styles.onboarding} source={require('../../static/images/logo.png')} fadeDuration={0} /> - <Text style={[styles.title, { color: themes[theme].titleText }]}>{I18n.t('Onboarding_title')}</Text> - <Text style={[styles.subtitle, { color: themes[theme].controlText }]}>{I18n.t('Onboarding_subtitle')}</Text> - <Text style={[styles.description, { color: themes[theme].auxiliaryText }]}>{I18n.t('Onboarding_description')}</Text> - <View style={styles.buttonsContainer}> - <Button - title={I18n.t('Onboarding_join_workspace')} - type='primary' - onPress={this.connectServer} - theme={theme} - testID='join-workspace' - /> - <Button - title={I18n.t('Create_a_new_workspace')} - type='secondary' - backgroundColor={themes[theme].chatComponentBackground} - onPress={this.createWorkspace} - theme={theme} - testID='create-workspace-button' - /> - </View> - </FormContainerInner> - </FormContainer> - ); - } -} - -export default withTheme(OnboardingView); diff --git a/app/views/OnboardingView/styles.js b/app/views/OnboardingView/styles.js deleted file mode 100644 index 809fcf36e..000000000 --- a/app/views/OnboardingView/styles.js +++ /dev/null @@ -1,41 +0,0 @@ -import { StyleSheet } from 'react-native'; - -import { verticalScale, moderateScale } from '../../utils/scaling'; -import { isTablet } from '../../utils/deviceInfo'; -import sharedStyles from '../Styles'; - -export default StyleSheet.create({ - onboarding: { - alignSelf: 'center', - marginTop: isTablet ? 0 : verticalScale(116), - marginBottom: verticalScale(50), - maxHeight: verticalScale(150), - resizeMode: 'contain', - width: 100, - height: 100 - }, - title: { - ...sharedStyles.textBold, - letterSpacing: 0, - fontSize: moderateScale(24), - alignSelf: 'center', - marginBottom: verticalScale(8) - }, - subtitle: { - ...sharedStyles.textRegular, - fontSize: moderateScale(16), - alignSelf: 'center', - marginBottom: verticalScale(24) - }, - description: { - ...sharedStyles.textRegular, - ...sharedStyles.textAlignCenter, - fontSize: moderateScale(14), - alignSelf: 'center', - marginHorizontal: 20 - }, - buttonsContainer: { - marginBottom: verticalScale(10), - marginTop: verticalScale(30) - } -}); diff --git a/app/views/PickerView.js b/app/views/PickerView.js index b11ae6247..ed62fe65e 100644 --- a/app/views/PickerView.js +++ b/app/views/PickerView.js @@ -1,18 +1,15 @@ import React from 'react'; import PropTypes from 'prop-types'; -import { - View, FlatList, StyleSheet, Text -} from 'react-native'; +import { FlatList, StyleSheet, Text, View } from 'react-native'; import I18n from '../i18n'; import { withTheme } from '../theme'; import { themes } from '../constants/colors'; import debounce from '../utils/debounce'; -import sharedStyles from './Styles'; - import * as List from '../containers/List'; import SearchBox from '../containers/SearchBox'; import SafeAreaView from '../containers/SafeAreaView'; +import sharedStyles from './Styles'; const styles = StyleSheet.create({ search: { @@ -27,12 +24,7 @@ const styles = StyleSheet.create({ } }); -const Item = React.memo(({ - item, - selected, - onItemPress, - theme -}) => ( +const Item = React.memo(({ item, selected, onItemPress, theme }) => ( <List.Item title={I18n.t(item.label, { defaultValue: item.label, second: item?.second })} right={selected && (() => <List.Icon name='check' color={themes[theme].tintColor} />)} @@ -50,13 +42,13 @@ Item.propTypes = { class PickerView extends React.PureComponent { static navigationOptions = ({ route }) => ({ title: route.params?.title ?? I18n.t('Select_an_option') - }) + }); static propTypes = { navigation: PropTypes.object, route: PropTypes.object, theme: PropTypes.string - } + }; constructor(props) { super(props); @@ -67,7 +59,7 @@ class PickerView extends React.PureComponent { this.onSearch = props.route.params?.onChangeText; } - onChangeValue = (value) => { + onChangeValue = value => { const { navigation, route } = this.props; const goBack = route.params?.goBack ?? true; const onChange = route.params?.onChangeValue ?? (() => {}); @@ -75,14 +67,18 @@ class PickerView extends React.PureComponent { if (goBack) { navigation.goBack(); } - } + }; - onChangeText = debounce(async(text) => { - if (this.onSearch) { - const data = await this.onSearch(text); - this.setState({ data }); - } - }, 300, true) + onChangeText = debounce( + async text => { + if (this.onSearch) { + const data = await this.onSearch(text); + this.setState({ data }); + } + }, + 300, + true + ); renderSearch() { if (!this.onSearch) { @@ -117,7 +113,9 @@ class PickerView extends React.PureComponent { ItemSeparatorComponent={List.Separator} ListHeaderComponent={List.Separator} ListFooterComponent={List.Separator} - ListEmptyComponent={() => <Text style={[styles.noResult, { color: themes[theme].titleText }]}>{I18n.t('No_results_found')}</Text>} + ListEmptyComponent={() => ( + <Text style={[styles.noResult, { color: themes[theme].titleText }]}>{I18n.t('No_results_found')}</Text> + )} contentContainerStyle={[List.styles.contentContainerStyleFlatList]} /> </SafeAreaView> diff --git a/app/views/ProfileView/index.js b/app/views/ProfileView/index.js index 7e97422df..630d8c7aa 100644 --- a/app/views/ProfileView/index.js +++ b/app/views/ProfileView/index.js @@ -1,6 +1,6 @@ import React from 'react'; import PropTypes from 'prop-types'; -import { View, ScrollView, Keyboard } from 'react-native'; +import { Keyboard, ScrollView, View } from 'react-native'; import { connect } from 'react-redux'; import prompt from 'react-native-prompt-android'; import SHA256 from 'js-sha256'; @@ -12,14 +12,13 @@ import omit from 'lodash/omit'; import Touch from '../../utils/touch'; import KeyboardView from '../../presentation/KeyboardView'; import sharedStyles from '../Styles'; -import styles from './styles'; import scrollPersistTaps from '../../utils/scrollPersistTaps'; -import { showErrorAlert, showConfirmationAlert } from '../../utils/info'; +import { showConfirmationAlert, showErrorAlert } from '../../utils/info'; import { LISTENER } from '../../containers/Toast'; import EventEmitter from '../../utils/events'; import RocketChat from '../../lib/rocketchat'; import RCTextInput from '../../containers/TextInput'; -import log, { logEvent, events } from '../../utils/log'; +import log, { events, logEvent } from '../../utils/log'; import I18n from '../../i18n'; import Button from '../../containers/Button'; import Avatar from '../../containers/Avatar'; @@ -31,6 +30,7 @@ import { themes } from '../../constants/colors'; import { withTheme } from '../../theme'; import { getUserSelector } from '../../selectors/login'; import SafeAreaView from '../../containers/SafeAreaView'; +import styles from './styles'; class ProfileView extends React.Component { static navigationOptions = ({ navigation, isMasterDetail }) => { @@ -44,7 +44,7 @@ class ProfileView extends React.Component { <HeaderButton.Preferences onPress={() => navigation.navigate('UserPreferencesView')} testID='preferences-view-open' /> ); return options; - } + }; static propTypes = { baseUrl: PropTypes.string, @@ -57,7 +57,7 @@ class ProfileView extends React.Component { Accounts_CustomFields: PropTypes.string, setUser: PropTypes.func, theme: PropTypes.string - } + }; state = { saving: false, @@ -70,7 +70,7 @@ class ProfileView extends React.Component { avatar: {}, avatarSuggestions: {}, customFields: {} - } + }; async componentDidMount() { this.init(); @@ -96,7 +96,7 @@ class ProfileView extends React.Component { } } - setAvatar = (avatar) => { + setAvatar = avatar => { const { Accounts_AllowUserAvatarChange } = this.props; if (!Accounts_AllowUserAvatarChange) { @@ -104,13 +104,11 @@ class ProfileView extends React.Component { } this.setState({ avatar }); - } + }; - init = (user) => { + init = user => { const { user: userProps } = this.props; - const { - name, username, emails, customFields - } = user || userProps; + const { name, username, emails, customFields } = user || userProps; this.setState({ name, @@ -122,41 +120,41 @@ class ProfileView extends React.Component { avatar: {}, customFields: customFields || {} }); - } + }; formIsChanged = () => { - const { - name, username, email, newPassword, avatar, customFields - } = this.state; + const { name, username, email, newPassword, avatar, customFields } = this.state; const { user } = this.props; let customFieldsChanged = false; const customFieldsKeys = Object.keys(customFields); if (customFieldsKeys.length) { - customFieldsKeys.forEach((key) => { + customFieldsKeys.forEach(key => { if (!user.customFields || user.customFields[key] !== customFields[key]) { customFieldsChanged = true; } }); } - return !(user.name === name - && user.username === username - && !newPassword - && (user.emails && user.emails[0].address === email) - && !avatar.data - && !customFieldsChanged + return !( + user.name === name && + user.username === username && + !newPassword && + user.emails && + user.emails[0].address === email && + !avatar.data && + !customFieldsChanged ); - } + }; handleError = (e, func, action) => { if (e.data && e.data.error.includes('[error-too-many-requests]')) { return showErrorAlert(e.data.error); } showErrorAlert(I18n.t('There_was_an_error_while_action', { action: I18n.t(action) })); - } + }; - submit = async() => { + submit = async () => { Keyboard.dismiss(); if (!this.formIsChanged()) { @@ -165,9 +163,7 @@ class ProfileView extends React.Component { this.setState({ saving: true }); - const { - name, username, email, newPassword, currentPassword, avatar, customFields - } = this.state; + const { name, username, email, newPassword, currentPassword, avatar, customFields } = this.state; const { user, setUser } = this.props; const params = {}; @@ -206,7 +202,7 @@ class ProfileView extends React.Component { { text: I18n.t('Cancel'), onPress: () => {}, style: 'cancel' }, { text: I18n.t('Save'), - onPress: (p) => { + onPress: p => { this.setState({ currentPassword: p }); this.submit(); } @@ -250,9 +246,9 @@ class ProfileView extends React.Component { this.setState({ saving: false, currentPassword: null }); this.handleError(e, 'saveUserProfile', 'saving_profile'); } - } + }; - resetAvatar = async() => { + resetAvatar = async () => { const { Accounts_AllowUserAvatarChange } = this.props; if (!Accounts_AllowUserAvatarChange) { @@ -267,9 +263,9 @@ class ProfileView extends React.Component { } catch (e) { this.handleError(e, 'resetAvatar', 'changing_avatar'); } - } + }; - pickImage = async() => { + pickImage = async () => { const { Accounts_AllowUserAvatarChange } = this.props; if (!Accounts_AllowUserAvatarChange) { @@ -288,21 +284,19 @@ class ProfileView extends React.Component { try { logEvent(events.PROFILE_PICK_AVATAR); const response = await ImagePicker.openPicker(options); - this.setAvatar({ url: response.path, data: `data:image/jpeg;base64,${ response.data }`, service: 'upload' }); + this.setAvatar({ url: response.path, data: `data:image/jpeg;base64,${response.data}`, service: 'upload' }); } catch (error) { logEvent(events.PROFILE_PICK_AVATAR_F); console.warn(error); } - } + }; - pickImageWithURL = (avatarUrl) => { + pickImageWithURL = avatarUrl => { logEvent(events.PROFILE_PICK_AVATAR_WITH_URL); this.setAvatar({ url: avatarUrl, data: avatarUrl, service: 'url' }); - } + }; - renderAvatarButton = ({ - key, child, onPress, disabled = false - }) => { + renderAvatarButton = ({ key, child, onPress, disabled = false }) => { const { theme } = this.props; return ( <Touch @@ -311,25 +305,20 @@ class ProfileView extends React.Component { onPress={onPress} style={[styles.avatarButton, { opacity: disabled ? 0.5 : 1 }, { backgroundColor: themes[theme].borderColor }]} enabled={!disabled} - theme={theme} - > + theme={theme}> {child} </Touch> ); - } + }; renderAvatarButtons = () => { const { avatarUrl, avatarSuggestions } = this.state; - const { - user, - theme, - Accounts_AllowUserAvatarChange - } = this.props; + const { user, theme, Accounts_AllowUserAvatarChange } = this.props; return ( <View style={styles.avatarButtons}> {this.renderAvatarButton({ - child: <Avatar text={`@${ user.username }`} size={50} />, + child: <Avatar text={`@${user.username}`} size={50} />, onPress: () => this.resetAvatar(), disabled: !Accounts_AllowUserAvatarChange, key: 'profile-view-reset-avatar' @@ -346,20 +335,24 @@ class ProfileView extends React.Component { disabled: !avatarUrl, key: 'profile-view-avatar-url-button' })} - {Object.keys(avatarSuggestions).map((service) => { + {Object.keys(avatarSuggestions).map(service => { const { url, blob, contentType } = avatarSuggestions[service]; return this.renderAvatarButton({ disabled: !Accounts_AllowUserAvatarChange, - key: `profile-view-avatar-${ service }`, + key: `profile-view-avatar-${service}`, child: <Avatar avatar={url} size={50} />, - onPress: () => this.setAvatar({ - url, data: blob, service, contentType - }) + onPress: () => + this.setAvatar({ + url, + data: blob, + service, + contentType + }) }); })} </View> ); - } + }; renderCustomFields = () => { const { customFields } = this.state; @@ -377,15 +370,16 @@ class ProfileView extends React.Component { <RNPickerSelect key={key} items={options} - onValueChange={(value) => { + onValueChange={value => { const newValue = {}; newValue[key] = value; this.setState({ customFields: { ...customFields, ...newValue } }); }} - value={customFields[key]} - > + value={customFields[key]}> <RCTextInput - inputRef={(e) => { this[key] = e; }} + inputRef={e => { + this[key] = e; + }} label={key} placeholder={key} value={customFields[key]} @@ -398,12 +392,14 @@ class ProfileView extends React.Component { return ( <RCTextInput - inputRef={(e) => { this[key] = e; }} + inputRef={e => { + this[key] = e; + }} key={key} label={key} placeholder={key} value={customFields[key]} - onChangeText={(value) => { + onChangeText={value => { const newValue = {}; newValue[key] = value; this.setState({ customFields: { ...customFields, ...newValue } }); @@ -421,14 +417,14 @@ class ProfileView extends React.Component { } catch (error) { return null; } - } + }; logoutOtherLocations = () => { logEvent(events.PL_OTHER_LOCATIONS); showConfirmationAlert({ message: I18n.t('You_will_be_logged_out_from_other_locations'), confirmationText: I18n.t('Logout'), - onPress: async() => { + onPress: async () => { try { await RocketChat.logoutOtherLocations(); EventEmitter.emit(LISTENER, { message: I18n.t('Logged_out_of_other_clients_successfully') }); @@ -438,12 +434,10 @@ class ProfileView extends React.Component { } } }); - } + }; render() { - const { - name, username, email, newPassword, avatarUrl, customFields, avatar, saving - } = this.state; + const { name, username, email, newPassword, avatarUrl, customFields, avatar, saving } = this.state; const { user, theme, @@ -459,71 +453,67 @@ class ProfileView extends React.Component { <KeyboardView style={{ backgroundColor: themes[theme].auxiliaryBackground }} contentContainerStyle={sharedStyles.container} - keyboardVerticalOffset={128} - > + keyboardVerticalOffset={128}> <StatusBar /> <SafeAreaView testID='profile-view'> - <ScrollView - contentContainerStyle={sharedStyles.containerScrollView} - testID='profile-view-list' - {...scrollPersistTaps} - > + <ScrollView contentContainerStyle={sharedStyles.containerScrollView} testID='profile-view-list' {...scrollPersistTaps}> <View style={styles.avatarContainer} testID='profile-view-avatar'> - <Avatar - text={user.username} - avatar={avatar?.url} - isStatic={avatar?.url} - size={100} - /> + <Avatar text={user.username} avatar={avatar?.url} isStatic={avatar?.url} size={100} /> </View> <RCTextInput editable={Accounts_AllowRealNameChange} - inputStyle={[ - !Accounts_AllowRealNameChange && styles.disabled - ]} - inputRef={(e) => { this.name = e; }} + inputStyle={[!Accounts_AllowRealNameChange && styles.disabled]} + inputRef={e => { + this.name = e; + }} label={I18n.t('Name')} placeholder={I18n.t('Name')} value={name} onChangeText={value => this.setState({ name: value })} - onSubmitEditing={() => { this.username.focus(); }} + onSubmitEditing={() => { + this.username.focus(); + }} testID='profile-view-name' theme={theme} /> <RCTextInput editable={Accounts_AllowUsernameChange} - inputStyle={[ - !Accounts_AllowUsernameChange && styles.disabled - ]} - inputRef={(e) => { this.username = e; }} + inputStyle={[!Accounts_AllowUsernameChange && styles.disabled]} + inputRef={e => { + this.username = e; + }} label={I18n.t('Username')} placeholder={I18n.t('Username')} value={username} onChangeText={value => this.setState({ username: value })} - onSubmitEditing={() => { this.email.focus(); }} + onSubmitEditing={() => { + this.email.focus(); + }} testID='profile-view-username' theme={theme} /> <RCTextInput editable={Accounts_AllowEmailChange} - inputStyle={[ - !Accounts_AllowEmailChange && styles.disabled - ]} - inputRef={(e) => { this.email = e; }} + inputStyle={[!Accounts_AllowEmailChange && styles.disabled]} + inputRef={e => { + this.email = e; + }} label={I18n.t('Email')} placeholder={I18n.t('Email')} value={email} onChangeText={value => this.setState({ email: value })} - onSubmitEditing={() => { this.newPassword.focus(); }} + onSubmitEditing={() => { + this.newPassword.focus(); + }} testID='profile-view-email' theme={theme} /> <RCTextInput editable={Accounts_AllowPasswordChange} - inputStyle={[ - !Accounts_AllowPasswordChange && styles.disabled - ]} - inputRef={(e) => { this.newPassword = e; }} + inputStyle={[!Accounts_AllowPasswordChange && styles.disabled]} + inputRef={e => { + this.newPassword = e; + }} label={I18n.t('New_Password')} placeholder={I18n.t('New_Password')} value={newPassword} @@ -541,10 +531,10 @@ class ProfileView extends React.Component { {this.renderCustomFields()} <RCTextInput editable={Accounts_AllowUserAvatarChange} - inputStyle={[ - !Accounts_AllowUserAvatarChange && styles.disabled - ]} - inputRef={(e) => { this.avatarUrl = e; }} + inputStyle={[!Accounts_AllowUserAvatarChange && styles.disabled]} + inputRef={e => { + this.avatarUrl = e; + }} label={I18n.t('Avatar_Url')} placeholder={I18n.t('Avatar_Url')} value={avatarUrl} diff --git a/app/views/ReadReceiptView/index.js b/app/views/ReadReceiptView/index.js index b413a7674..d407b3420 100644 --- a/app/views/ReadReceiptView/index.js +++ b/app/views/ReadReceiptView/index.js @@ -1,13 +1,12 @@ import React from 'react'; import PropTypes from 'prop-types'; -import { FlatList, View, Text } from 'react-native'; +import { FlatList, Text, View } from 'react-native'; import { dequal } from 'dequal'; import moment from 'moment'; import { connect } from 'react-redux'; -import * as List from '../../containers/List'; +import * as List from '../../containers/List'; import Avatar from '../../containers/Avatar'; -import styles from './styles'; import ActivityIndicator from '../../containers/ActivityIndicator'; import * as HeaderButton from '../../containers/HeaderButton'; import I18n from '../../i18n'; @@ -16,6 +15,7 @@ import StatusBar from '../../containers/StatusBar'; import { withTheme } from '../../theme'; import { themes } from '../../constants/colors'; import SafeAreaView from '../../containers/SafeAreaView'; +import styles from './styles'; class ReadReceiptView extends React.Component { static navigationOptions = ({ navigation, isMasterDetail }) => { @@ -26,13 +26,13 @@ class ReadReceiptView extends React.Component { options.headerLeft = () => <HeaderButton.CloseModal navigation={navigation} testID='read-receipt-view-close' />; } return options; - } + }; static propTypes = { route: PropTypes.object, Message_TimeAndDateFormat: PropTypes.string, theme: PropTypes.string - } + }; constructor(props) { super(props); @@ -62,7 +62,7 @@ class ReadReceiptView extends React.Component { return false; } - load = async() => { + load = async () => { const { loading } = this.state; if (loading) { return; @@ -82,16 +82,18 @@ class ReadReceiptView extends React.Component { this.setState({ loading: false }); console.log('err_fetch_read_receipts', error); } - } + }; renderEmpty = () => { const { theme } = this.props; return ( - <View style={[styles.listEmptyContainer, { backgroundColor: themes[theme].chatComponentBackground }]} testID='read-receipt-view'> + <View + style={[styles.listEmptyContainer, { backgroundColor: themes[theme].chatComponentBackground }]} + testID='read-receipt-view'> <Text style={{ color: themes[theme].titleText }}>{I18n.t('No_Read_Receipts')}</Text> </View> ); - } + }; renderItem = ({ item }) => { const { theme, Message_TimeAndDateFormat } = this.props; @@ -101,26 +103,17 @@ class ReadReceiptView extends React.Component { } return ( <View style={[styles.itemContainer, { backgroundColor: themes[theme].backgroundColor }]}> - <Avatar - text={item.user.username} - size={40} - /> + <Avatar text={item.user.username} size={40} /> <View style={styles.infoContainer}> <View style={styles.item}> - <Text style={[styles.name, { color: themes[theme].titleText }]}> - {item?.user?.name} - </Text> - <Text style={{ color: themes[theme].auxiliaryText }}> - {time} - </Text> + <Text style={[styles.name, { color: themes[theme].titleText }]}>{item?.user?.name}</Text> + <Text style={{ color: themes[theme].auxiliaryText }}>{time}</Text> </View> - <Text style={{ color: themes[theme].auxiliaryText }}> - {`@${ item.user.username }`} - </Text> + <Text style={{ color: themes[theme].auxiliaryText }}>{`@${item.user.username}`}</Text> </View> </View> ); - } + }; render() { const { receipts, loading } = this.state; @@ -133,23 +126,23 @@ class ReadReceiptView extends React.Component { return ( <SafeAreaView testID='read-receipt-view'> <StatusBar /> - {loading - ? <ActivityIndicator theme={theme} /> - : ( - <FlatList - data={receipts} - renderItem={this.renderItem} - ItemSeparatorComponent={List.Separator} - style={[ - styles.list, - { - backgroundColor: themes[theme].chatComponentBackground, - borderColor: themes[theme].separatorColor - } - ]} - keyExtractor={item => item._id} - /> - )} + {loading ? ( + <ActivityIndicator theme={theme} /> + ) : ( + <FlatList + data={receipts} + renderItem={this.renderItem} + ItemSeparatorComponent={List.Separator} + style={[ + styles.list, + { + backgroundColor: themes[theme].chatComponentBackground, + borderColor: themes[theme].separatorColor + } + ]} + keyExtractor={item => item._id} + /> + )} </SafeAreaView> ); } diff --git a/app/views/ReadReceiptView/styles.js b/app/views/ReadReceiptView/styles.js index 828e814c0..dbf31e6a0 100644 --- a/app/views/ReadReceiptView/styles.js +++ b/app/views/ReadReceiptView/styles.js @@ -1,4 +1,5 @@ import { StyleSheet } from 'react-native'; + import sharedStyles from '../Styles'; export default StyleSheet.create({ diff --git a/app/views/RegisterView.js b/app/views/RegisterView.js index 685f1449a..5086bc9ee 100644 --- a/app/views/RegisterView.js +++ b/app/views/RegisterView.js @@ -1,13 +1,10 @@ import React from 'react'; import PropTypes from 'prop-types'; -import { - Text, View, StyleSheet, Keyboard -} from 'react-native'; +import { Keyboard, StyleSheet, Text, View } from 'react-native'; import { connect } from 'react-redux'; import RNPickerSelect from 'react-native-picker-select'; -import log, { logEvent, events } from '../utils/log'; -import sharedStyles from './Styles'; +import log, { events, logEvent } from '../utils/log'; import Button from '../containers/Button'; import I18n from '../i18n'; import * as HeaderButton from '../containers/HeaderButton'; @@ -22,6 +19,7 @@ import { loginRequest as loginRequestAction } from '../actions/login'; import openLink from '../utils/openLink'; import LoginServices from '../containers/LoginServices'; import { getShowLoginButton } from '../selectors/login'; +import sharedStyles from './Styles'; const styles = StyleSheet.create({ title: { @@ -67,7 +65,7 @@ class RegisterView extends React.Component { Site_Name: PropTypes.string, loginRequest: PropTypes.func, showLoginButton: PropTypes.bool - } + }; constructor(props) { super(props); @@ -80,7 +78,7 @@ class RegisterView extends React.Component { log(e); } } - Object.keys(this.parsedCustomFields).forEach((key) => { + Object.keys(this.parsedCustomFields).forEach(key => { if (this.parsedCustomFields[key].defaultValue) { customFields[key] = this.parsedCustomFields[key].defaultValue; } @@ -98,22 +96,20 @@ class RegisterView extends React.Component { login = () => { const { navigation, Site_Name } = this.props; navigation.navigate('LoginView', { title: Site_Name }); - } + }; valid = () => { - const { - name, email, password, username, customFields - } = this.state; + const { name, email, password, username, customFields } = this.state; let requiredCheck = true; - Object.keys(this.parsedCustomFields).forEach((key) => { + Object.keys(this.parsedCustomFields).forEach(key => { if (this.parsedCustomFields[key].required) { requiredCheck = requiredCheck && customFields[key] && Boolean(customFields[key].trim()); } }); return name.trim() && email.trim() && password.trim() && username.trim() && isValidEmail(email) && requiredCheck; - } + }; - submit = async() => { + submit = async () => { logEvent(events.REGISTER_DEFAULT_SIGN_UP); if (!this.valid()) { return; @@ -121,19 +117,16 @@ class RegisterView extends React.Component { this.setState({ saving: true }); Keyboard.dismiss(); - const { - name, email, password, username, customFields - } = this.state; - const { - loginRequest, - Accounts_EmailVerification, - navigation, - Accounts_ManuallyApproveNewUsers - } = this.props; + const { name, email, password, username, customFields } = this.state; + const { loginRequest, Accounts_EmailVerification, navigation, Accounts_ManuallyApproveNewUsers } = this.props; try { await RocketChat.register({ - name, email, pass: password, username, ...customFields + name, + email, + pass: password, + username, + ...customFields }); if (Accounts_EmailVerification) { @@ -155,15 +148,15 @@ class RegisterView extends React.Component { } } this.setState({ saving: false }); - } + }; - openContract = (route) => { + openContract = route => { const { server, theme } = this.props; if (!server) { return; } - openLink(`${ server }/${ route }`, theme); - } + openLink(`${server}/${route}`, theme); + }; renderCustomFields = () => { const { customFields } = this.state; @@ -179,15 +172,16 @@ class RegisterView extends React.Component { <RNPickerSelect key={key} items={options} - onValueChange={(value) => { + onValueChange={value => { const newValue = {}; newValue[key] = value; this.setState({ customFields: { ...customFields, ...newValue } }); }} - value={customFields[key]} - > + value={customFields[key]}> <TextInput - inputRef={(e) => { this[key] = e; }} + inputRef={e => { + this[key] = e; + }} placeholder={key} value={customFields[key]} testID='register-view-custom-picker' @@ -199,12 +193,14 @@ class RegisterView extends React.Component { return ( <TextInput - inputRef={(e) => { this[key] = e; }} + inputRef={e => { + this[key] = e; + }} key={key} label={key} placeholder={key} value={customFields[key]} - onChangeText={(value) => { + onChangeText={value => { const newValue = {}; newValue[key] = value; this.setState({ customFields: { ...customFields, ...newValue } }); @@ -223,7 +219,7 @@ class RegisterView extends React.Component { } catch (error) { return null; } - } + }; render() { const { saving } = this.state; @@ -239,37 +235,49 @@ class RegisterView extends React.Component { placeholder={I18n.t('Name')} returnKeyType='next' onChangeText={name => this.setState({ name })} - onSubmitEditing={() => { this.usernameInput.focus(); }} + onSubmitEditing={() => { + this.usernameInput.focus(); + }} testID='register-view-name' theme={theme} /> <TextInput label={I18n.t('Username')} containerStyle={styles.inputContainer} - inputRef={(e) => { this.usernameInput = e; }} + inputRef={e => { + this.usernameInput = e; + }} placeholder={I18n.t('Username')} returnKeyType='next' onChangeText={username => this.setState({ username })} - onSubmitEditing={() => { this.emailInput.focus(); }} + onSubmitEditing={() => { + this.emailInput.focus(); + }} testID='register-view-username' theme={theme} /> <TextInput label={I18n.t('Email')} containerStyle={styles.inputContainer} - inputRef={(e) => { this.emailInput = e; }} + inputRef={e => { + this.emailInput = e; + }} placeholder={I18n.t('Email')} returnKeyType='next' keyboardType='email-address' onChangeText={email => this.setState({ email })} - onSubmitEditing={() => { this.passwordInput.focus(); }} + onSubmitEditing={() => { + this.passwordInput.focus(); + }} testID='register-view-email' theme={theme} /> <TextInput label={I18n.t('Password')} containerStyle={styles.inputContainer} - inputRef={(e) => { this.passwordInput = e; }} + inputRef={e => { + this.passwordInput = e; + }} placeholder={I18n.t('Password')} returnKeyType='send' secureTextEntry @@ -294,31 +302,32 @@ class RegisterView extends React.Component { <View style={styles.bottomContainer}> <Text style={[styles.bottomContainerText, { color: themes[theme].auxiliaryText }]}> - {`${ I18n.t('Onboarding_agree_terms') }\n`} + {`${I18n.t('Onboarding_agree_terms')}\n`} <Text style={[styles.bottomContainerTextBold, { color: themes[theme].actionTintColor }]} - onPress={() => this.openContract('terms-of-service')} - >{I18n.t('Terms_of_Service')} - </Text> {I18n.t('and')} + onPress={() => this.openContract('terms-of-service')}> + {I18n.t('Terms_of_Service')} + </Text>{' '} + {I18n.t('and')} <Text style={[styles.bottomContainerTextBold, { color: themes[theme].actionTintColor }]} - onPress={() => this.openContract('privacy-policy')} - > {I18n.t('Privacy_Policy')} + onPress={() => this.openContract('privacy-policy')}> + {' '} + {I18n.t('Privacy_Policy')} </Text> </Text> </View> - {showLoginButton - ? ( - <View style={styles.bottomContainer}> - <Text style={[styles.bottomContainerText, { color: themes[theme].auxiliaryText }]}>{I18n.t('Do_you_have_an_account')}</Text> - <Text - style={[styles.bottomContainerTextBold, { color: themes[theme].actionTintColor }]} - onPress={this.login} - >{I18n.t('Login')} - </Text> - </View> - ) : null} + {showLoginButton ? ( + <View style={styles.bottomContainer}> + <Text style={[styles.bottomContainerText, { color: themes[theme].auxiliaryText }]}> + {I18n.t('Do_you_have_an_account')} + </Text> + <Text style={[styles.bottomContainerTextBold, { color: themes[theme].actionTintColor }]} onPress={this.login}> + {I18n.t('Login')} + </Text> + </View> + ) : null} </FormContainerInner> </FormContainer> ); diff --git a/app/views/RoomActionsView/index.js b/app/views/RoomActionsView/index.js index 914a42913..c3bd105af 100644 --- a/app/views/RoomActionsView/index.js +++ b/app/views/RoomActionsView/index.js @@ -1,8 +1,6 @@ import React from 'react'; import PropTypes from 'prop-types'; -import { - View, Text, Share, Switch -} from 'react-native'; +import { Share, Switch, Text, View } from 'react-native'; import { connect } from 'react-redux'; import isEmpty from 'lodash/isEmpty'; import { Q } from '@nozbe/watermelondb'; @@ -10,20 +8,17 @@ import { Q } from '@nozbe/watermelondb'; import { compareServerVersion, methods } from '../../lib/utils'; import Touch from '../../utils/touch'; import { setLoading as setLoadingAction } from '../../actions/selectedUsers'; -import { - leaveRoom as leaveRoomAction, closeRoom as closeRoomAction -} from '../../actions/room'; -import styles from './styles'; +import { closeRoom as closeRoomAction, leaveRoom as leaveRoomAction } from '../../actions/room'; import sharedStyles from '../Styles'; import Avatar from '../../containers/Avatar'; import Status from '../../containers/Status'; import * as List from '../../containers/List'; import RocketChat from '../../lib/rocketchat'; -import log, { logEvent, events } from '../../utils/log'; +import log, { events, logEvent } from '../../utils/log'; import RoomTypeIcon from '../../containers/RoomTypeIcon'; import I18n from '../../i18n'; import StatusBar from '../../containers/StatusBar'; -import { themes, SWITCH_TRACK_COLOR } from '../../constants/colors'; +import { SWITCH_TRACK_COLOR, themes } from '../../constants/colors'; import { withTheme } from '../../theme'; import * as HeaderButton from '../../containers/HeaderButton'; import Markdown from '../../containers/markdown'; @@ -33,6 +28,7 @@ import { E2E_ROOM_TYPES } from '../../lib/encryption/constants'; import protectedFunction from '../../lib/methods/helpers/protectedFunction'; import database from '../../lib/database'; import { withDimensions } from '../../dimensions'; +import styles from './styles'; class RoomActionsView extends React.Component { static navigationOptions = ({ navigation, isMasterDetail }) => { @@ -43,7 +39,7 @@ class RoomActionsView extends React.Component { options.headerLeft = () => <HeaderButton.CloseModal navigation={navigation} testID='room-actions-view-close' />; } return options; - } + }; static propTypes = { navigation: PropTypes.object, @@ -68,8 +64,9 @@ class RoomActionsView extends React.Component { transferLivechatGuestPermission: PropTypes.array, createTeamPermission: PropTypes.array, addTeamChannelPermission: PropTypes.array, - convertTeamPermission: PropTypes.array - } + convertTeamPermission: PropTypes.array, + viewCannedResponsesPermission: PropTypes.array + }; constructor(props) { super(props); @@ -78,6 +75,7 @@ class RoomActionsView extends React.Component { const member = props.route.params?.member; this.rid = props.route.params?.rid; this.t = props.route.params?.t; + this.joined = props.route.params?.joined; this.state = { room: room || { rid: this.rid, t: this.t }, membersCount: 0, @@ -93,18 +91,18 @@ class RoomActionsView extends React.Component { canToggleEncryption: false, canCreateTeam: false, canAddChannelToTeam: false, - canConvertTeam: false + canConvertTeam: false, + canViewCannedResponse: false }; if (room && room.observe && room.rid) { this.roomObservable = room.observe(); - this.subscription = this.roomObservable - .subscribe((changes) => { - if (this.mounted) { - this.setState({ room: changes }); - } else { - this.state.room = changes; - } - }); + this.subscription = this.roomObservable.subscribe(changes => { + if (this.mounted) { + this.setState({ room: changes }); + } else { + this.state.room = changes; + } + }); } } @@ -147,14 +145,23 @@ class RoomActionsView extends React.Component { const canConvertTeam = await this.canConvertTeam(); this.setState({ - canAutoTranslate, canAddUser, canInviteUser, canEdit, canToggleEncryption, canViewMembers, canCreateTeam, canAddChannelToTeam, canConvertTeam + canAutoTranslate, + canAddUser, + canInviteUser, + canEdit, + canToggleEncryption, + canViewMembers, + canCreateTeam, + canAddChannelToTeam, + canConvertTeam }); // livechat permissions if (room.t === 'l') { const canForwardGuest = await this.canForwardGuest(); const canReturnQueue = await this.canReturnQueue(); - this.setState({ canForwardGuest, canReturnQueue }); + const canViewCannedResponse = await this.canViewCannedResponse(); + this.setState({ canForwardGuest, canReturnQueue, canViewCannedResponse }); } } } @@ -167,29 +174,32 @@ class RoomActionsView extends React.Component { get isOmnichannelPreview() { const { room } = this.state; - return room.t === 'l' && room.status === 'queued'; + return room.t === 'l' && room.status === 'queued' && !this.joined; } - onPressTouchable = (item) => { + onPressTouchable = item => { const { route, event, params } = item; if (route) { - logEvent(events[`RA_GO_${ route.replace('View', '').toUpperCase() }${ params.name ? params.name.toUpperCase() : '' }`]); + logEvent(events[`RA_GO_${route.replace('View', '').toUpperCase()}${params.name ? params.name.toUpperCase() : ''}`]); const { navigation } = this.props; navigation.navigate(route, params); } if (event) { return event(); } - } + }; - canAddUser = async() => { + canAddUser = async () => { const { room, joined } = this.state; const { addUserToJoinedRoomPermission, addUserToAnyCRoomPermission, addUserToAnyPRoomPermission } = this.props; const { rid, t } = room; let canAddUser = false; const userInRoom = joined; - const permissions = await RocketChat.hasPermission([addUserToJoinedRoomPermission, addUserToAnyCRoomPermission, addUserToAnyPRoomPermission], rid); + const permissions = await RocketChat.hasPermission( + [addUserToJoinedRoomPermission, addUserToAnyCRoomPermission, addUserToAnyPRoomPermission], + rid + ); if (userInRoom && permissions[0]) { canAddUser = true; @@ -201,9 +211,9 @@ class RoomActionsView extends React.Component { canAddUser = true; } return canAddUser; - } + }; - canInviteUser = async() => { + canInviteUser = async () => { const { room } = this.state; const { createInviteLinksPermission } = this.props; const { rid } = room; @@ -211,9 +221,9 @@ class RoomActionsView extends React.Component { const canInviteUser = permissions[0]; return canInviteUser; - } + }; - canEdit = async() => { + canEdit = async () => { const { room } = this.state; const { editRoomPermission } = this.props; const { rid } = room; @@ -221,9 +231,9 @@ class RoomActionsView extends React.Component { const canEdit = permissions[0]; return canEdit; - } + }; - canCreateTeam = async() => { + canCreateTeam = async () => { const { room } = this.state; const { createTeamPermission } = this.props; const { rid } = room; @@ -231,9 +241,9 @@ class RoomActionsView extends React.Component { const canCreateTeam = permissions[0]; return canCreateTeam; - } + }; - canAddChannelToTeam = async() => { + canAddChannelToTeam = async () => { const { room } = this.state; const { addTeamChannelPermission } = this.props; const { rid } = room; @@ -241,9 +251,9 @@ class RoomActionsView extends React.Component { const canAddChannelToTeam = permissions[0]; return canAddChannelToTeam; - } + }; - canConvertTeam = async() => { + canConvertTeam = async () => { const { room } = this.state; const { convertTeamPermission } = this.props; const { rid } = room; @@ -251,9 +261,9 @@ class RoomActionsView extends React.Component { const canConvertTeam = permissions[0]; return canConvertTeam; - } + }; - canToggleEncryption = async() => { + canToggleEncryption = async () => { const { room } = this.state; const { toggleRoomE2EEncryptionPermission } = this.props; const { rid } = room; @@ -261,9 +271,9 @@ class RoomActionsView extends React.Component { const canToggleEncryption = permissions[0]; return canToggleEncryption; - } + }; - canViewMembers = async() => { + canViewMembers = async () => { const { room } = this.state; const { viewBroadcastMemberListPermission } = this.props; const { rid, t, broadcast } = room; @@ -276,26 +286,34 @@ class RoomActionsView extends React.Component { // This method is executed only in componentDidMount and returns a value // We save the state to read in render - const result = (t === 'c' || t === 'p'); + const result = t === 'c' || t === 'p'; return result; - } + }; - canForwardGuest = async() => { + canForwardGuest = async () => { const { room } = this.state; const { transferLivechatGuestPermission } = this.props; const { rid } = room; const permissions = await RocketChat.hasPermission([transferLivechatGuestPermission], rid); return permissions[0]; - } + }; - canReturnQueue = async() => { + canViewCannedResponse = async () => { + const { room } = this.state; + const { viewCannedResponsesPermission } = this.props; + const { rid } = room; + const permissions = await RocketChat.hasPermission([viewCannedResponsesPermission], rid); + return permissions[0]; + }; + + canReturnQueue = async () => { try { const { returnQueue } = await RocketChat.getRoutingConfig(); return returnQueue; } catch { // do nothing } - } + }; renderEncryptedSwitch = () => { const { room, canToggleEncryption, canEdit } = this.state; @@ -308,28 +326,27 @@ class RoomActionsView extends React.Component { hasPermission = canToggleEncryption; } return ( - <Switch - value={encrypted} - trackColor={SWITCH_TRACK_COLOR} - onValueChange={this.toggleEncrypted} - disabled={!hasPermission} - /> + <Switch value={encrypted} trackColor={SWITCH_TRACK_COLOR} onValueChange={this.toggleEncrypted} disabled={!hasPermission} /> ); - } + }; closeLivechat = () => { - const { room: { rid } } = this.state; + const { + room: { rid } + } = this.state; const { closeRoom } = this.props; closeRoom(rid); - } + }; returnLivechat = () => { - const { room: { rid } } = this.state; + const { + room: { rid } + } = this.state; showConfirmationAlert({ message: I18n.t('Would_you_like_to_return_the_inquiry'), confirmationText: I18n.t('Yes'), - onPress: async() => { + onPress: async () => { try { await RocketChat.returnLivechat(rid); } catch (e) { @@ -337,9 +354,9 @@ class RoomActionsView extends React.Component { } } }); - } + }; - updateRoomMember = async() => { + updateRoomMember = async () => { const { room } = this.state; try { @@ -354,9 +371,9 @@ class RoomActionsView extends React.Component { log(e); this.setState({ member: {} }); } - } + }; - addUser = async() => { + addUser = async () => { const { room } = this.state; const { setLoadingInvite, navigation } = this.props; const { rid } = room; @@ -369,9 +386,9 @@ class RoomActionsView extends React.Component { } finally { setLoadingInvite(false); } - } + }; - toggleBlockUser = async() => { + toggleBlockUser = async () => { logEvent(events.RA_TOGGLE_BLOCK_USER); const { room } = this.state; const { rid, blocker } = room; @@ -382,9 +399,9 @@ class RoomActionsView extends React.Component { logEvent(events.RA_TOGGLE_BLOCK_USER_F); log(e); } - } + }; - toggleEncrypted = async() => { + toggleEncrypted = async () => { logEvent(events.RA_TOGGLE_ENCRYPTED); const { room } = this.state; const { rid } = room; @@ -394,10 +411,12 @@ class RoomActionsView extends React.Component { const encrypted = !room.encrypted; try { // Instantly feedback to the user - await db.action(async() => { - await room.update(protectedFunction((r) => { - r.encrypted = encrypted; - })); + await db.action(async () => { + await room.update( + protectedFunction(r => { + r.encrypted = encrypted; + }) + ); }); try { @@ -412,16 +431,18 @@ class RoomActionsView extends React.Component { } // If something goes wrong we go back to the previous value - await db.action(async() => { - await room.update(protectedFunction((r) => { - r.encrypted = room.encrypted; - })); + await db.action(async () => { + await room.update( + protectedFunction(r => { + r.encrypted = room.encrypted; + }) + ); }); } catch (e) { logEvent(events.RA_TOGGLE_ENCRYPTED_F); log(e); } - } + }; handleShare = () => { logEvent(events.RA_SHARE); @@ -444,9 +465,9 @@ class RoomActionsView extends React.Component { confirmationText: I18n.t('Yes_action_it', { action: I18n.t('leave') }), onPress: () => leaveRoom('channel', room) }); - } + }; - convertTeamToChannel = async() => { + convertTeamToChannel = async () => { const { room } = this.state; const { navigation } = this.props; @@ -471,9 +492,9 @@ class RoomActionsView extends React.Component { } catch (e) { this.convertTeamToChannelConfirmation(); } - } + }; - handleConvertTeamToChannel = async(selected) => { + handleConvertTeamToChannel = async selected => { logEvent(events.RA_CONVERT_TEAM_TO_CHANNEL); try { const { room } = this.state; @@ -488,7 +509,7 @@ class RoomActionsView extends React.Component { logEvent(events.RA_CONVERT_TEAM_TO_CHANNEL_F); log(e); } - } + }; convertTeamToChannelConfirmation = (selected = []) => { showConfirmationAlert({ @@ -497,9 +518,9 @@ class RoomActionsView extends React.Component { confirmationText: I18n.t('Convert'), onPress: () => this.handleConvertTeamToChannel(selected) }); - } + }; - leaveTeam = async() => { + leaveTeam = async () => { const { room } = this.state; const { navigation, leaveRoom } = this.props; @@ -534,9 +555,9 @@ class RoomActionsView extends React.Component { onPress: () => leaveRoom('team', room) }); } - } + }; - handleConvertToTeam = async() => { + handleConvertToTeam = async () => { logEvent(events.RA_CONVERT_TO_TEAM); try { const { room } = this.state; @@ -550,7 +571,7 @@ class RoomActionsView extends React.Component { logEvent(events.RA_CONVERT_TO_TEAM_F); log(e); } - } + }; convertToTeam = () => { showConfirmationAlert({ @@ -559,9 +580,9 @@ class RoomActionsView extends React.Component { confirmationText: I18n.t('Convert'), onPress: () => this.handleConvertToTeam() }); - } + }; - handleMoveToTeam = async(selected) => { + handleMoveToTeam = async selected => { logEvent(events.RA_MOVE_TO_TEAM); try { const { room } = this.state; @@ -575,16 +596,14 @@ class RoomActionsView extends React.Component { log(e); showErrorAlert(I18n.t('There_was_an_error_while_action', { action: I18n.t('moving_channel_to_team') })); } - } + }; - moveToTeam = async() => { + moveToTeam = async () => { try { const { navigation } = this.props; const db = database.active; const subCollection = db.get('subscriptions'); - const teamRooms = await subCollection.query( - Q.where('team_main', true) - ); + const teamRooms = await subCollection.query(Q.where('team_main', true)); if (teamRooms.length) { const data = teamRooms.map(team => ({ @@ -602,12 +621,13 @@ class RoomActionsView extends React.Component { isRadio: true, isSearch: true, onSearch: onChangeText => this.searchTeam(onChangeText), - nextAction: selected => showConfirmationAlert({ - title: I18n.t('Confirmation'), - message: I18n.t('Move_to_Team_Warning'), - confirmationText: I18n.t('Yes_action_it', { action: I18n.t('move') }), - onPress: () => this.handleMoveToTeam(selected) - }) + nextAction: selected => + showConfirmationAlert({ + title: I18n.t('Confirmation'), + message: I18n.t('Move_to_Team_Warning'), + confirmationText: I18n.t('Yes_action_it', { action: I18n.t('move') }), + onPress: () => this.handleMoveToTeam(selected) + }) }); } }); @@ -615,9 +635,9 @@ class RoomActionsView extends React.Component { } catch (e) { log(e); } - } + }; - searchTeam = async(onChangeText) => { + searchTeam = async onChangeText => { logEvent(events.RA_SEARCH_TEAM); try { const { addTeamChannelPermission, createTeamPermission } = this.props; @@ -627,19 +647,21 @@ class RoomActionsView extends React.Component { .get('subscriptions') .query( Q.where('team_main', true), - Q.where('name', Q.like(`%${ onChangeText }%`)), + Q.where('name', Q.like(`%${onChangeText}%`)), Q.experimentalTake(QUERY_SIZE), Q.experimentalSortBy('room_updated_at', Q.desc) ); - const asyncFilter = async(teamArray) => { - const results = await Promise.all(teamArray.map(async(team) => { - const permissions = await RocketChat.hasPermission([addTeamChannelPermission, createTeamPermission], team.rid); - if (!permissions[0]) { - return false; - } - return true; - })); + const asyncFilter = async teamArray => { + const results = await Promise.all( + teamArray.map(async team => { + const permissions = await RocketChat.hasPermission([addTeamChannelPermission, createTeamPermission], team.rid); + if (!permissions[0]) { + return false; + } + return true; + }) + ); return teamArray.filter((_v, index) => results[index]); }; @@ -648,13 +670,11 @@ class RoomActionsView extends React.Component { } catch (e) { log(e); } - } + }; renderRoomInfo = () => { const { room, member } = this.state; - const { - rid, name, t, topic - } = room; + const { rid, name, t, topic } = room; const { theme, fontScale } = this.props; const avatar = RocketChat.getRoomAvatar(room); @@ -664,54 +684,61 @@ class RoomActionsView extends React.Component { <List.Section> <List.Separator /> <Touch - onPress={() => this.onPressTouchable({ - route: 'RoomInfoView', - // forward room only if room isn't joined - params: { - rid, t, room, member - } - })} + onPress={() => + this.onPressTouchable({ + route: 'RoomInfoView', + // forward room only if room isn't joined + params: { + rid, + t, + room, + member + } + }) + } style={{ backgroundColor: themes[theme].backgroundColor }} accessibilityLabel={I18n.t('Room_Info')} accessibilityTraits='button' enabled={!isGroupChat} testID='room-actions-info' - theme={theme} - > + theme={theme}> <View style={[styles.roomInfoContainer, { height: 72 * fontScale }]}> - <Avatar - text={avatar} - style={styles.avatar} - size={50 * fontScale} - type={t} - rid={rid} - > - {t === 'd' && member._id - ? ( - <View style={[sharedStyles.status, { backgroundColor: themes[theme].backgroundColor }]}> - <Status size={16} id={member._id} /> - </View> - ) : null - } + <Avatar text={avatar} style={styles.avatar} size={50 * fontScale} type={t} rid={rid}> + {t === 'd' && member._id ? ( + <View style={[sharedStyles.status, { backgroundColor: themes[theme].backgroundColor }]}> + <Status size={16} id={member._id} /> + </View> + ) : null} </Avatar> <View style={styles.roomTitleContainer}> - {room.t === 'd' - ? <Text style={[styles.roomTitle, { color: themes[theme].titleText }]} numberOfLines={1}>{room.fname}</Text> - : ( - <View style={styles.roomTitleRow}> - <RoomTypeIcon type={room.prid ? 'discussion' : room.t} teamMain={room.teamMain} status={room.visitor?.status} /> - <Text style={[styles.roomTitle, { color: themes[theme].titleText }]} numberOfLines={1}>{RocketChat.getRoomTitle(room)}</Text> - </View> - ) - } + {room.t === 'd' ? ( + <Text style={[styles.roomTitle, { color: themes[theme].titleText }]} numberOfLines={1}> + {room.fname} + </Text> + ) : ( + <View style={styles.roomTitleRow}> + <RoomTypeIcon type={room.prid ? 'discussion' : room.t} teamMain={room.teamMain} status={room.visitor?.status} /> + <Text style={[styles.roomTitle, { color: themes[theme].titleText }]} numberOfLines={1}> + {RocketChat.getRoomTitle(room)} + </Text> + </View> + )} <Markdown preview - msg={t === 'd' ? `@${ name }` : topic} + msg={t === 'd' ? `@${name}` : topic} style={[styles.roomDescription, { color: themes[theme].auxiliaryText }]} numberOfLines={1} theme={theme} /> - {room.t === 'd' && <Markdown msg={member.statusText} style={[styles.roomDescription, { color: themes[theme].auxiliaryText }]} preview theme={theme} numberOfLines={1} />} + {room.t === 'd' && ( + <Markdown + msg={member.statusText} + style={[styles.roomDescription, { color: themes[theme].auxiliaryText }]} + preview + theme={theme} + numberOfLines={1} + /> + )} </View> {isGroupChat ? null : <List.Icon name='chevron-right' style={styles.actionIndicator} />} </View> @@ -719,7 +746,7 @@ class RoomActionsView extends React.Component { <List.Separator /> </List.Section> ); - } + }; renderJitsi = () => { const { room } = this.state; @@ -753,7 +780,7 @@ class RoomActionsView extends React.Component { <List.Separator /> </List.Section> ); - } + }; renderE2EEncryption = () => { const { room } = this.state; @@ -776,7 +803,7 @@ class RoomActionsView extends React.Component { ); } return null; - } + }; renderLastSection = () => { const { room, joined } = this.state; @@ -792,10 +819,12 @@ class RoomActionsView extends React.Component { <List.Section> <List.Separator /> <List.Item - title={`${ blocker ? 'Unblock' : 'Block' }_user`} - onPress={() => this.onPressTouchable({ - event: this.toggleBlockUser - })} + title={`${blocker ? 'Unblock' : 'Block'}_user`} + onPress={() => + this.onPressTouchable({ + event: this.toggleBlockUser + }) + } testID='room-actions-block-user' left={() => <List.Icon name='ignore' color={themes[theme].dangerColor} />} showActionIndicator @@ -812,9 +841,11 @@ class RoomActionsView extends React.Component { <List.Separator /> <List.Item title='Leave' - onPress={() => this.onPressTouchable({ - event: room.teamMain ? this.leaveTeam : this.leaveChannel - })} + onPress={() => + this.onPressTouchable({ + event: room.teamMain ? this.leaveTeam : this.leaveChannel + }) + } testID='room-actions-leave-channel' left={() => <List.Icon name='logout' color={themes[theme].dangerColor} />} showActionIndicator @@ -826,7 +857,7 @@ class RoomActionsView extends React.Component { } return null; - } + }; teamChannelActions = (t, room) => { const { canEdit, canCreateTeam, canAddChannelToTeam } = this.state; @@ -835,42 +866,42 @@ class RoomActionsView extends React.Component { return ( <> - {['c', 'p'].includes(t) && canConvertToTeam - ? ( - <> - <List.Item - title='Convert_to_Team' - onPress={() => this.onPressTouchable({ + {['c', 'p'].includes(t) && canConvertToTeam ? ( + <> + <List.Item + title='Convert_to_Team' + onPress={() => + this.onPressTouchable({ event: this.convertToTeam - })} - testID='room-actions-convert-to-team' - left={() => <List.Icon name='teams' />} - showActionIndicator - /> - <List.Separator /> - </> - ) - : null} + }) + } + testID='room-actions-convert-to-team' + left={() => <List.Icon name='teams' />} + showActionIndicator + /> + <List.Separator /> + </> + ) : null} - {['c', 'p'].includes(t) && canMoveToTeam - ? ( - <> - <List.Item - title='Move_to_Team' - onPress={() => this.onPressTouchable({ + {['c', 'p'].includes(t) && canMoveToTeam ? ( + <> + <List.Item + title='Move_to_Team' + onPress={() => + this.onPressTouchable({ event: this.moveToTeam - })} - testID='room-actions-move-to-team' - left={() => <List.Icon name='channel-move-to-team' />} - showActionIndicator - /> - <List.Separator /> - </> - ) - : null} + }) + } + testID='room-actions-move-to-team' + left={() => <List.Icon name='channel-move-to-team' />} + showActionIndicator + /> + <List.Separator /> + </> + ) : null} </> ); - } + }; teamToChannelActions = (t, room) => { const { canEdit, canConvertTeam } = this.state; @@ -878,33 +909,40 @@ class RoomActionsView extends React.Component { return ( <> - {['c', 'p'].includes(t) && canConvertTeamToChannel - ? ( - <> - <List.Item - title='Convert_to_Channel' - onPress={() => this.onPressTouchable({ + {['c', 'p'].includes(t) && canConvertTeamToChannel ? ( + <> + <List.Item + title='Convert_to_Channel' + onPress={() => + this.onPressTouchable({ event: this.convertTeamToChannel - })} - testID='room-actions-convert-channel-to-team' - left={() => <List.Icon name='channel-public' />} - showActionIndicator - /> - <List.Separator /> - </> - ) - : null} + }) + } + testID='room-actions-convert-channel-to-team' + left={() => <List.Icon name='channel-public' />} + showActionIndicator + /> + <List.Separator /> + </> + ) : null} </> ); - } + }; render() { const { - room, membersCount, canViewMembers, canAddUser, canInviteUser, joined, canAutoTranslate, canForwardGuest, canReturnQueue + room, + membersCount, + canViewMembers, + canAddUser, + canInviteUser, + joined, + canAutoTranslate, + canForwardGuest, + canReturnQueue, + canViewCannedResponse } = this.state; - const { - rid, t - } = room; + const { rid, t } = room; const isGroupChat = RocketChat.isGroupChat(room); return ( @@ -917,257 +955,266 @@ class RoomActionsView extends React.Component { <List.Section> <List.Separator /> - {(['c', 'p'].includes(t) && canViewMembers) || isGroupChat - ? ( - <> - <List.Item - title='Members' - subtitle={membersCount > 0 ? `${ membersCount } ${ I18n.t('members') }` : null} - onPress={() => this.onPressTouchable({ route: 'RoomMembersView', params: { rid, room } })} - testID='room-actions-members' - left={() => <List.Icon name='team' />} - showActionIndicator - translateSubtitle={false} - /> - <List.Separator /> - </> - ) - : null} + {(['c', 'p'].includes(t) && canViewMembers) || isGroupChat ? ( + <> + <List.Item + title='Members' + subtitle={membersCount > 0 ? `${membersCount} ${I18n.t('members')}` : null} + onPress={() => this.onPressTouchable({ route: 'RoomMembersView', params: { rid, room } })} + testID='room-actions-members' + left={() => <List.Icon name='team' />} + showActionIndicator + translateSubtitle={false} + /> + <List.Separator /> + </> + ) : null} - {['c', 'p'].includes(t) && canAddUser - ? ( - <> - <List.Item - title='Add_users' - onPress={() => this.onPressTouchable({ + {['c', 'p'].includes(t) && canAddUser ? ( + <> + <List.Item + title='Add_users' + onPress={() => + this.onPressTouchable({ route: 'SelectedUsersView', params: { rid, title: I18n.t('Add_users'), nextAction: this.addUser } - })} - testID='room-actions-add-user' - left={() => <List.Icon name='add' />} - showActionIndicator - /> - <List.Separator /> - </> - ) - : null} + }) + } + testID='room-actions-add-user' + left={() => <List.Icon name='add' />} + showActionIndicator + /> + <List.Separator /> + </> + ) : null} - {['c', 'p'].includes(t) && canInviteUser - ? ( - <> - <List.Item - title='Invite_users' - onPress={() => this.onPressTouchable({ + {['c', 'p'].includes(t) && canInviteUser ? ( + <> + <List.Item + title='Invite_users' + onPress={() => + this.onPressTouchable({ route: 'InviteUsersView', params: { rid } - })} - testID='room-actions-invite-user' - left={() => <List.Icon name='user-add' />} - showActionIndicator - /> - <List.Separator /> - </> - ) - : null} + }) + } + testID='room-actions-invite-user' + left={() => <List.Icon name='user-add' />} + showActionIndicator + /> + <List.Separator /> + </> + ) : null} - {['c', 'p', 'd'].includes(t) - ? ( - <> - <List.Item - title='Files' - onPress={() => this.onPressTouchable({ + {['c', 'p', 'd'].includes(t) ? ( + <> + <List.Item + title='Files' + onPress={() => + this.onPressTouchable({ route: 'MessagesView', params: { rid, t, name: 'Files' } - })} - testID='room-actions-files' - left={() => <List.Icon name='attach' />} - showActionIndicator - /> - <List.Separator /> - </> - ) - : null} + }) + } + testID='room-actions-files' + left={() => <List.Icon name='attach' />} + showActionIndicator + /> + <List.Separator /> + </> + ) : null} - {['c', 'p', 'd'].includes(t) - ? ( - <> - <List.Item - title='Mentions' - onPress={() => this.onPressTouchable({ + {['c', 'p', 'd'].includes(t) ? ( + <> + <List.Item + title='Mentions' + onPress={() => + this.onPressTouchable({ route: 'MessagesView', params: { rid, t, name: 'Mentions' } - })} - testID='room-actions-mentioned' - left={() => <List.Icon name='mention' />} - showActionIndicator - /> - <List.Separator /> - </> - ) - : null} + }) + } + testID='room-actions-mentioned' + left={() => <List.Icon name='mention' />} + showActionIndicator + /> + <List.Separator /> + </> + ) : null} - {['c', 'p', 'd'].includes(t) - ? ( - <> - <List.Item - title='Starred' - onPress={() => this.onPressTouchable({ + {['c', 'p', 'd'].includes(t) ? ( + <> + <List.Item + title='Starred' + onPress={() => + this.onPressTouchable({ route: 'MessagesView', params: { rid, t, name: 'Starred' } - })} - testID='room-actions-starred' - left={() => <List.Icon name='star' />} - showActionIndicator - /> - <List.Separator /> - </> - ) - : null} + }) + } + testID='room-actions-starred' + left={() => <List.Icon name='star' />} + showActionIndicator + /> + <List.Separator /> + </> + ) : null} - {['c', 'p', 'd'].includes(t) - ? ( - <> - <List.Item - title='Share' - onPress={() => this.onPressTouchable({ + {['c', 'p', 'd'].includes(t) ? ( + <> + <List.Item + title='Share' + onPress={() => + this.onPressTouchable({ event: this.handleShare - })} - testID='room-actions-share' - left={() => <List.Icon name='share' />} - showActionIndicator - /> - <List.Separator /> - </> - ) - : null} + }) + } + testID='room-actions-share' + left={() => <List.Icon name='share' />} + showActionIndicator + /> + <List.Separator /> + </> + ) : null} - {['c', 'p', 'd'].includes(t) - ? ( - <> - <List.Item - title='Pinned' - onPress={() => this.onPressTouchable({ + {['c', 'p', 'd'].includes(t) ? ( + <> + <List.Item + title='Pinned' + onPress={() => + this.onPressTouchable({ route: 'MessagesView', params: { rid, t, name: 'Pinned' } - })} - testID='room-actions-pinned' - left={() => <List.Icon name='pin' />} - showActionIndicator - /> - <List.Separator /> - </> - ) - : null} + }) + } + testID='room-actions-pinned' + left={() => <List.Icon name='pin' />} + showActionIndicator + /> + <List.Separator /> + </> + ) : null} - {['c', 'p', 'd'].includes(t) && canAutoTranslate - ? ( - <> - <List.Item - title='Auto_Translate' - onPress={() => this.onPressTouchable({ + {['c', 'p', 'd'].includes(t) && canAutoTranslate ? ( + <> + <List.Item + title='Auto_Translate' + onPress={() => + this.onPressTouchable({ route: 'AutoTranslateView', params: { rid, room } - })} - testID='room-actions-auto-translate' - left={() => <List.Icon name='language' />} - showActionIndicator - /> - <List.Separator /> - </> - ) - : null} + }) + } + testID='room-actions-auto-translate' + left={() => <List.Icon name='language' />} + showActionIndicator + /> + <List.Separator /> + </> + ) : null} - {['c', 'p', 'd'].includes(t) && joined - ? ( - <> - <List.Item - title='Notifications' - onPress={() => this.onPressTouchable({ + {['c', 'p', 'd'].includes(t) && joined ? ( + <> + <List.Item + title='Notifications' + onPress={() => + this.onPressTouchable({ route: 'NotificationPrefView', params: { rid, room } - })} - testID='room-actions-notifications' - left={() => <List.Icon name='notification' />} - showActionIndicator - /> - <List.Separator /> - </> - ) - : null} + }) + } + testID='room-actions-notifications' + left={() => <List.Icon name='notification' />} + showActionIndicator + /> + <List.Separator /> + </> + ) : null} - - { this.teamChannelActions(t, room) } + {this.teamChannelActions(t, room)} {this.teamToChannelActions(t, room)} - {['l'].includes(t) && !this.isOmnichannelPreview - ? ( - <> - <List.Item - title='Close' - onPress={() => this.onPressTouchable({ - event: this.closeLivechat - })} - left={() => <List.Icon name='close' />} - showActionIndicator - /> - <List.Separator /> - </> - ) - : null} + {['l'].includes(t) && !this.isOmnichannelPreview && canViewCannedResponse ? ( + <> + <List.Item + title='Canned_Responses' + onPress={() => this.onPressTouchable({ route: 'CannedResponsesListView', params: { rid, room } })} + left={() => <List.Icon name='canned-response' />} + showActionIndicator + /> + <List.Separator /> + </> + ) : null} - {['l'].includes(t) && !this.isOmnichannelPreview && canForwardGuest - ? ( - <> - <List.Item - title='Forward' - onPress={() => this.onPressTouchable({ + {['l'].includes(t) && !this.isOmnichannelPreview ? ( + <> + <List.Item + title='Close' + onPress={() => + this.onPressTouchable({ + event: this.closeLivechat + }) + } + left={() => <List.Icon name='close' />} + showActionIndicator + /> + <List.Separator /> + </> + ) : null} + + {['l'].includes(t) && !this.isOmnichannelPreview && canForwardGuest ? ( + <> + <List.Item + title='Forward' + onPress={() => + this.onPressTouchable({ route: 'ForwardLivechatView', params: { rid } - })} - left={() => <List.Icon name='user-forward' />} - showActionIndicator - /> - <List.Separator /> - </> - ) - : null} + }) + } + left={() => <List.Icon name='user-forward' />} + showActionIndicator + /> + <List.Separator /> + </> + ) : null} - {['l'].includes(t) && !this.isOmnichannelPreview && canReturnQueue - ? ( - <> - <List.Item - title='Return' - onPress={() => this.onPressTouchable({ + {['l'].includes(t) && !this.isOmnichannelPreview && canReturnQueue ? ( + <> + <List.Item + title='Return' + onPress={() => + this.onPressTouchable({ event: this.returnLivechat - })} - left={() => <List.Icon name='undo' />} - showActionIndicator - /> - <List.Separator /> - </> - ) - : null} + }) + } + left={() => <List.Icon name='undo' />} + showActionIndicator + /> + <List.Separator /> + </> + ) : null} - {['l'].includes(t) && !this.isOmnichannelPreview - ? ( - <> - <List.Item - title='Navigation_history' - onPress={() => this.onPressTouchable({ + {['l'].includes(t) && !this.isOmnichannelPreview ? ( + <> + <List.Item + title='Navigation_history' + onPress={() => + this.onPressTouchable({ route: 'VisitorNavigationView', params: { rid } - })} - left={() => <List.Icon name='history' />} - showActionIndicator - /> - <List.Separator /> - </> - ) - : null} + }) + } + left={() => <List.Icon name='history' />} + showActionIndicator + /> + <List.Separator /> + </> + ) : null} </List.Section> {this.renderLastSection()} @@ -1194,7 +1241,8 @@ const mapStateToProps = state => ({ transferLivechatGuestPermission: state.permissions['transfer-livechat-guest'], createTeamPermission: state.permissions['create-team'], addTeamChannelPermission: state.permissions['add-team-channel'], - convertTeamPermission: state.permissions['convert-team'] + convertTeamPermission: state.permissions['convert-team'], + viewCannedResponsesPermission: state.permissions['view-canned-responses'] }); const mapDispatchToProps = dispatch => ({ diff --git a/app/views/RoomActionsView/styles.js b/app/views/RoomActionsView/styles.js index 8a1ac129d..a3bfcb2b5 100644 --- a/app/views/RoomActionsView/styles.js +++ b/app/views/RoomActionsView/styles.js @@ -1,6 +1,6 @@ -import { StyleSheet, I18nManager } from 'react-native'; -import { PADDING_HORIZONTAL } from '../../containers/List/constants'; +import { I18nManager, StyleSheet } from 'react-native'; +import { PADDING_HORIZONTAL } from '../../containers/List/constants'; import sharedStyles from '../Styles'; export default StyleSheet.create({ @@ -29,8 +29,6 @@ export default StyleSheet.create({ alignItems: 'center' }, actionIndicator: { - ...I18nManager.isRTL - ? { transform: [{ rotate: '180deg' }] } - : {} + ...(I18nManager.isRTL ? { transform: [{ rotate: '180deg' }] } : {}) } }); diff --git a/app/views/RoomInfoEditView/SwitchContainer.js b/app/views/RoomInfoEditView/SwitchContainer.js index de1aed98f..c57f092e4 100644 --- a/app/views/RoomInfoEditView/SwitchContainer.js +++ b/app/views/RoomInfoEditView/SwitchContainer.js @@ -1,40 +1,61 @@ import React from 'react'; -import { View, Text, Switch } from 'react-native'; +import { Switch, Text, View } from 'react-native'; import PropTypes from 'prop-types'; -import styles from './styles'; import { SWITCH_TRACK_COLOR, themes } from '../../constants/colors'; +import styles from './styles'; -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 }]} /> - </> -)); +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 }]} /> + </> + ) +); SwitchContainer.propTypes = { value: PropTypes.bool, diff --git a/app/views/RoomInfoEditView/index.js b/app/views/RoomInfoEditView/index.js index 8dd7e0865..177573c8f 100644 --- a/app/views/RoomInfoEditView/index.js +++ b/app/views/RoomInfoEditView/index.js @@ -1,8 +1,6 @@ import React from 'react'; import PropTypes from 'prop-types'; -import { - Text, View, ScrollView, TouchableOpacity, Keyboard, Alert -} from 'react-native'; +import { Alert, Keyboard, ScrollView, Text, TouchableOpacity, View } from 'react-native'; import { connect } from 'react-redux'; import { BLOCK_CONTEXT } from '@rocket.chat/ui-kit'; import ImagePicker from 'react-native-image-crop-picker'; @@ -15,7 +13,6 @@ import database from '../../lib/database'; import { deleteRoom as deleteRoomAction } from '../../actions/room'; import KeyboardView from '../../presentation/KeyboardView'; import sharedStyles from '../Styles'; -import styles from './styles'; import scrollPersistTaps from '../../utils/scrollPersistTaps'; import { showConfirmationAlert, showErrorAlert } from '../../utils/info'; import { LISTENER } from '../../containers/Toast'; @@ -23,9 +20,8 @@ import EventEmitter from '../../utils/events'; import RocketChat from '../../lib/rocketchat'; import RCTextInput from '../../containers/TextInput'; import Loading from '../../containers/Loading'; -import SwitchContainer from './SwitchContainer'; import random from '../../utils/random'; -import log, { logEvent, events } from '../../utils/log'; +import log, { events, logEvent } from '../../utils/log'; import I18n from '../../i18n'; import StatusBar from '../../containers/StatusBar'; import { themes } from '../../constants/colors'; @@ -35,6 +31,8 @@ import { MessageTypeValues } from '../../utils/messageTypes'; import SafeAreaView from '../../containers/SafeAreaView'; import Avatar from '../../containers/Avatar'; import { CustomIcon } from '../../lib/Icons'; +import SwitchContainer from './SwitchContainer'; +import styles from './styles'; const PERMISSION_SET_READONLY = 'set-readonly'; const PERMISSION_SET_REACT_WHEN_READONLY = 'set-react-when-readonly'; @@ -47,7 +45,7 @@ const PERMISSION_DELETE_TEAM = 'delete-team'; class RoomInfoEditView extends React.Component { static navigationOptions = () => ({ title: I18n.t('Room_Info_Edit') - }) + }); static propTypes = { navigation: PropTypes.object, @@ -97,7 +95,7 @@ class RoomInfoEditView extends React.Component { } // eslint-disable-next-line react/sort-comp - loadRoom = async() => { + loadRoom = async () => { const { route, setReadOnlyPermission, @@ -117,20 +115,23 @@ class RoomInfoEditView extends React.Component { const sub = await db.get('subscriptions').find(rid); const observable = sub.observe(); - this.querySubscription = observable.subscribe((data) => { + this.querySubscription = observable.subscribe(data => { this.room = data; this.init(this.room); }); - const result = await RocketChat.hasPermission([ - setReadOnlyPermission, - setReactWhenReadOnlyPermission, - archiveRoomPermission, - unarchiveRoomPermission, - deleteCPermission, - deletePPermission, - ...(this.room.teamMain ? [deleteTeamPermission] : []) - ], rid); + const result = await RocketChat.hasPermission( + [ + setReadOnlyPermission, + setReactWhenReadOnlyPermission, + archiveRoomPermission, + unarchiveRoomPermission, + deleteCPermission, + deletePPermission, + ...(this.room.teamMain ? [deleteTeamPermission] : []) + ], + rid + ); this.setState({ permissions: { @@ -146,12 +147,10 @@ class RoomInfoEditView extends React.Component { } catch (e) { log(e); } - } + }; - init = (room) => { - const { - description, topic, announcement, t, ro, reactWhenReadOnly, joinCodeRequired, sysMes, encrypted - } = room; + init = room => { + const { description, topic, announcement, t, ro, reactWhenReadOnly, joinCodeRequired, sysMes, encrypted } = room; // fake password just to user knows about it this.randomValue = random(15); this.setState({ @@ -170,45 +169,69 @@ class RoomInfoEditView extends React.Component { enableSysMes: sysMes && sysMes.length > 0, encrypted }); - } + }; clearErrors = () => { this.setState({ nameError: {} }); - } + }; reset = () => { logEvent(events.RI_EDIT_RESET); this.clearErrors(); this.init(this.room); - } + }; formIsChanged = () => { const { - room, name, description, topic, announcement, t, ro, reactWhenReadOnly, joinCode, systemMessages, enableSysMes, encrypted, avatar + room, + name, + description, + topic, + announcement, + t, + ro, + reactWhenReadOnly, + joinCode, + systemMessages, + enableSysMes, + encrypted, + avatar } = this.state; const { joinCodeRequired } = room; - return !(room.name === name - && room.description === description - && room.topic === topic - && room.announcement === announcement - && (joinCodeRequired ? this.randomValue : '') === joinCode - && room.t === 'p' === t - && room.ro === ro - && room.reactWhenReadOnly === reactWhenReadOnly - && dequal(room.sysMes, systemMessages) - && enableSysMes === (room.sysMes && room.sysMes.length > 0) - && room.encrypted === encrypted - && isEmpty(avatar) + return !( + room.name === name && + room.description === description && + room.topic === topic && + room.announcement === announcement && + (joinCodeRequired ? this.randomValue : '') === joinCode && + (room.t === 'p') === t && + room.ro === ro && + room.reactWhenReadOnly === reactWhenReadOnly && + dequal(room.sysMes, systemMessages) && + enableSysMes === (room.sysMes && room.sysMes.length > 0) && + room.encrypted === encrypted && + isEmpty(avatar) ); - } + }; - submit = async() => { + submit = async () => { logEvent(events.RI_EDIT_SAVE); Keyboard.dismiss(); const { - room, name, description, topic, announcement, t, ro, reactWhenReadOnly, joinCode, systemMessages, encrypted, avatar + room, + name, + description, + topic, + announcement, + t, + ro, + reactWhenReadOnly, + joinCode, + systemMessages, + encrypted, + avatar } = this.state; this.setState({ saving: true }); @@ -290,31 +313,26 @@ class RoomInfoEditView extends React.Component { EventEmitter.emit(LISTENER, { message: I18n.t('Settings_succesfully_changed') }); } }, 100); - } + }; - deleteTeam = async() => { + deleteTeam = async () => { const { room } = this.state; - const { - navigation, deleteCPermission, deletePPermission, deleteRoom - } = this.props; + const { navigation, deleteCPermission, deletePPermission, deleteRoom } = this.props; try { const db = database.active; const subCollection = db.get('subscriptions'); - const teamChannels = await subCollection.query( - Q.where('team_id', room.teamId), - Q.where('team_main', Q.notEq(true)) - ); + const teamChannels = await subCollection.query(Q.where('team_id', room.teamId), Q.where('team_main', Q.notEq(true))); const teamChannelOwner = []; for (let i = 0; i < teamChannels.length; i += 1) { const permissionType = teamChannels[i].t === 'c' ? deleteCPermission : deletePPermission; // eslint-disable-next-line no-await-in-loop - const permissions = await RocketChat.hasPermission([ - permissionType - ], teamChannels[i].rid); + const permissions = await RocketChat.hasPermission([permissionType], teamChannels[i].rid); - if (permissions[0]) { teamChannelOwner.push(teamChannels[i]); } + if (permissions[0]) { + teamChannelOwner.push(teamChannels[i]); + } } if (teamChannelOwner.length) { @@ -322,7 +340,7 @@ class RoomInfoEditView extends React.Component { title: 'Delete_Team', data: teamChannelOwner, infoText: 'Select_channels_to_delete', - nextAction: (selected) => { + nextAction: selected => { showConfirmationAlert({ message: I18n.t('You_are_deleting_the_team', { team: RocketChat.getRoomTitle(room) }), confirmationText: I18n.t('Yes_action_it', { action: I18n.t('delete') }), @@ -340,13 +358,11 @@ class RoomInfoEditView extends React.Component { } catch (e) { log(e); showErrorAlert( - e.data.error - ? I18n.t(e.data.error) - : I18n.t('There_was_an_error_while_action', { action: I18n.t('deleting_team') }), + e.data.error ? I18n.t(e.data.error) : I18n.t('There_was_an_error_while_action', { action: I18n.t('deleting_team') }), I18n.t('Cannot_delete') ); } - } + }; delete = () => { const { room } = this.state; @@ -368,13 +384,13 @@ class RoomInfoEditView extends React.Component { ], { cancelable: false } ); - } + }; toggleArchive = () => { const { room } = this.state; const { rid, archived, t } = room; - const action = I18n.t(`${ archived ? 'un' : '' }archive`); + const action = I18n.t(`${archived ? 'un' : ''}archive`); Alert.alert( I18n.t('Are_you_sure_question_mark'), I18n.t('Do_you_really_want_to_key_this_room_question_mark', { key: action }), @@ -386,7 +402,7 @@ class RoomInfoEditView extends React.Component { { text: I18n.t('Yes_action_it', { action }), style: 'destructive', - onPress: async() => { + onPress: async () => { try { logEvent(events.RI_EDIT_TOGGLE_ARCHIVE); await RocketChat.toggleArchiveRoom(rid, t, !archived); @@ -399,7 +415,7 @@ class RoomInfoEditView extends React.Component { ], { cancelable: false } ); - } + }; hasDeletePermission = () => { const { room, permissions } = this.state; @@ -413,7 +429,7 @@ class RoomInfoEditView extends React.Component { } return permissions[PERMISSION_DELETE_C]; - } + }; renderSystemMessages = () => { const { systemMessages, enableSysMes } = this.state; @@ -425,7 +441,10 @@ class RoomInfoEditView extends React.Component { return ( <MultiSelect - options={MessageTypeValues.map(m => ({ value: m.value, text: { text: I18n.t('Hide_type_messages', { type: I18n.t(m.text) }) } }))} + 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} @@ -434,9 +453,9 @@ class RoomInfoEditView extends React.Component { theme={theme} /> ); - } + }; - changeAvatar = async() => { + changeAvatar = async () => { const options = { cropping: true, compressImageQuality: 0.8, @@ -448,44 +467,59 @@ class RoomInfoEditView extends React.Component { try { const response = await ImagePicker.openPicker(options); - this.setState({ avatar: { url: response.path, data: `data:image/jpeg;base64,${ response.data }`, service: 'upload' } }); + this.setState({ avatar: { url: response.path, data: `data:image/jpeg;base64,${response.data}`, service: 'upload' } }); } catch (e) { console.log(e); } - } + }; resetAvatar = () => { this.setState({ avatar: { data: null } }); - } + }; - toggleRoomType = (value) => { + toggleRoomType = value => { logEvent(events.RI_EDIT_TOGGLE_ROOM_TYPE); this.setState(({ encrypted }) => ({ t: value, encrypted: value && encrypted })); - } + }; - toggleReadOnly = (value) => { + toggleReadOnly = value => { logEvent(events.RI_EDIT_TOGGLE_READ_ONLY); this.setState({ ro: value }); - } + }; - toggleReactions = (value) => { + toggleReactions = value => { logEvent(events.RI_EDIT_TOGGLE_REACTIONS); this.setState({ reactWhenReadOnly: value }); - } + }; - toggleHideSystemMessages = (value) => { + toggleHideSystemMessages = value => { logEvent(events.RI_EDIT_TOGGLE_SYSTEM_MSG); this.setState(({ systemMessages }) => ({ enableSysMes: value, systemMessages: value ? systemMessages : [] })); - } + }; - toggleEncrypted = (value) => { + toggleEncrypted = value => { logEvent(events.RI_EDIT_TOGGLE_ENCRYPTED); this.setState({ encrypted: value }); - } + }; render() { const { - name, nameError, description, topic, announcement, t, ro, reactWhenReadOnly, room, joinCode, saving, permissions, archived, enableSysMes, encrypted, avatar + name, + nameError, + description, + topic, + announcement, + t, + ro, + reactWhenReadOnly, + room, + joinCode, + saving, + permissions, + archived, + enableSysMes, + encrypted, + avatar } = this.state; const { serverVersion, encryptionEnabled, theme } = this.props; const { dangerColor } = themes[theme]; @@ -494,80 +528,90 @@ class RoomInfoEditView extends React.Component { <KeyboardView style={{ backgroundColor: themes[theme].backgroundColor }} contentContainerStyle={sharedStyles.container} - keyboardVerticalOffset={128} - > + keyboardVerticalOffset={128}> <StatusBar /> - <SafeAreaView - testID='room-info-edit-view' - style={{ backgroundColor: themes[theme].backgroundColor }} - > + <SafeAreaView testID='room-info-edit-view' style={{ backgroundColor: themes[theme].backgroundColor }}> <ScrollView contentContainerStyle={sharedStyles.containerScrollView} testID='room-info-edit-view-list' - {...scrollPersistTaps} - > + {...scrollPersistTaps}> <TouchableOpacity style={styles.avatarContainer} onPress={this.changeAvatar} - disabled={compareServerVersion(serverVersion, '3.6.0', methods.lowerThan)} - > + disabled={compareServerVersion(serverVersion, '3.6.0', methods.lowerThan)}> <Avatar type={room.t} text={room.name} avatar={avatar?.url} isStatic={avatar?.url} rid={isEmpty(avatar) && room.rid} - size={100} - > - {compareServerVersion(serverVersion, '3.6.0', methods.lowerThan) - ? null - : ( - <TouchableOpacity style={[styles.resetButton, { backgroundColor: themes[theme].dangerColor }]} onPress={this.resetAvatar}> - <CustomIcon name='delete' color={themes[theme].backgroundColor} size={24} /> - </TouchableOpacity> - ) - } + size={100}> + {compareServerVersion(serverVersion, '3.6.0', methods.lowerThan) ? null : ( + <TouchableOpacity + style={[styles.resetButton, { backgroundColor: themes[theme].dangerColor }]} + onPress={this.resetAvatar}> + <CustomIcon name='delete' color={themes[theme].backgroundColor} size={24} /> + </TouchableOpacity> + )} </Avatar> </TouchableOpacity> <RCTextInput - inputRef={(e) => { this.name = e; }} + inputRef={e => { + this.name = e; + }} label={I18n.t('Name')} value={name} onChangeText={value => this.setState({ name: value })} - onSubmitEditing={() => { this.description.focus(); }} + onSubmitEditing={() => { + this.description.focus(); + }} error={nameError} theme={theme} testID='room-info-edit-view-name' /> <RCTextInput - inputRef={(e) => { this.description = e; }} + inputRef={e => { + this.description = e; + }} label={I18n.t('Description')} value={description} onChangeText={value => this.setState({ description: value })} - onSubmitEditing={() => { this.topic.focus(); }} + onSubmitEditing={() => { + this.topic.focus(); + }} theme={theme} testID='room-info-edit-view-description' /> <RCTextInput - inputRef={(e) => { this.topic = e; }} + inputRef={e => { + this.topic = e; + }} label={I18n.t('Topic')} value={topic} onChangeText={value => this.setState({ topic: value })} - onSubmitEditing={() => { this.announcement.focus(); }} + onSubmitEditing={() => { + this.announcement.focus(); + }} theme={theme} testID='room-info-edit-view-topic' /> <RCTextInput - inputRef={(e) => { this.announcement = e; }} + inputRef={e => { + this.announcement = e; + }} label={I18n.t('Announcement')} value={announcement} onChangeText={value => this.setState({ announcement: value })} - onSubmitEditing={() => { this.joinCode.focus(); }} + onSubmitEditing={() => { + this.joinCode.focus(); + }} theme={theme} testID='room-info-edit-view-announcement' /> <RCTextInput - inputRef={(e) => { this.joinCode = e; }} + inputRef={e => { + this.joinCode = e; + }} label={I18n.t('Password')} value={joinCode} onChangeText={value => this.setState({ joinCode: value })} @@ -579,9 +623,15 @@ class RoomInfoEditView extends React.Component { <SwitchContainer value={t} leftLabelPrimary={I18n.t('Public')} - leftLabelSecondary={room.teamMain ? I18n.t('Everyone_can_access_this_team') : I18n.t('Everyone_can_access_this_channel')} + leftLabelSecondary={ + room.teamMain ? I18n.t('Everyone_can_access_this_team') : I18n.t('Everyone_can_access_this_channel') + } rightLabelPrimary={I18n.t('Private')} - rightLabelSecondary={room.teamMain ? I18n.t('Just_invited_people_can_access_this_team') : I18n.t('Just_invited_people_can_access_this_channel')} + rightLabelSecondary={ + room.teamMain + ? I18n.t('Just_invited_people_can_access_this_team') + : I18n.t('Just_invited_people_can_access_this_channel') + } onValueChange={this.toggleRoomType} theme={theme} testID='room-info-edit-view-t' @@ -589,7 +639,11 @@ class RoomInfoEditView extends React.Component { <SwitchContainer value={ro} leftLabelPrimary={I18n.t('Collaborative')} - leftLabelSecondary={room.teamMain ? I18n.t('All_users_in_the_team_can_write_new_messages') : I18n.t('All_users_in_the_channel_can_write_new_messages')} + leftLabelSecondary={ + room.teamMain + ? I18n.t('All_users_in_the_team_can_write_new_messages') + : I18n.t('All_users_in_the_channel_can_write_new_messages') + } rightLabelPrimary={I18n.t('Read_Only')} rightLabelSecondary={I18n.t('Only_authorized_users_can_write_new_messages')} onValueChange={this.toggleReadOnly} @@ -597,40 +651,39 @@ class RoomInfoEditView extends React.Component { theme={theme} testID='room-info-edit-view-ro' /> - {ro && !room.broadcast - ? ( - <SwitchContainer - value={reactWhenReadOnly} - leftLabelPrimary={I18n.t('No_Reactions')} - leftLabelSecondary={I18n.t('Reactions_are_disabled')} - rightLabelPrimary={I18n.t('Allow_Reactions')} - rightLabelSecondary={I18n.t('Reactions_are_enabled')} - onValueChange={this.toggleReactions} - disabled={!permissions[PERMISSION_SET_REACT_WHEN_READONLY]} - theme={theme} - testID='room-info-edit-view-react-when-ro' - /> - ) - : null - } + {ro && !room.broadcast ? ( + <SwitchContainer + value={reactWhenReadOnly} + leftLabelPrimary={I18n.t('No_Reactions')} + leftLabelSecondary={I18n.t('Reactions_are_disabled')} + rightLabelPrimary={I18n.t('Allow_Reactions')} + rightLabelSecondary={I18n.t('Reactions_are_enabled')} + onValueChange={this.toggleReactions} + disabled={!permissions[PERMISSION_SET_REACT_WHEN_READONLY]} + theme={theme} + testID='room-info-edit-view-react-when-ro' + /> + ) : null} {room.broadcast ? [ - <Text style={styles.broadcast}>{I18n.t('Broadcast_Channel')}</Text>, - <View style={[styles.divider, { borderColor: themes[theme].separatorColor }]} /> - ] - : null - } + <Text style={styles.broadcast}>{I18n.t('Broadcast_Channel')}</Text>, + <View style={[styles.divider, { borderColor: themes[theme].separatorColor }]} /> + ] + : null} {!compareServerVersion(serverVersion, '3.0.0', methods.lowerThan) ? ( <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')} + 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={this.toggleHideSystemMessages} labelContainerStyle={styles.hideSystemMessages} - leftLabelStyle={styles.systemMessagesLabel} - > + leftLabelStyle={styles.systemMessagesLabel}> {this.renderSystemMessages()} </SwitchContainer> ) : null} @@ -655,9 +708,10 @@ class RoomInfoEditView extends React.Component { ]} onPress={this.submit} disabled={!this.formIsChanged()} - testID='room-info-edit-view-submit' - > - <Text style={[styles.button, { color: themes[theme].buttonText }]} accessibilityTraits='button'>{I18n.t('SAVE')}</Text> + testID='room-info-edit-view-submit'> + <Text style={[styles.button, { color: themes[theme].buttonText }]} accessibilityTraits='button'> + {I18n.t('SAVE')} + </Text> </TouchableOpacity> <View style={{ flexDirection: 'row' }}> <TouchableOpacity @@ -669,16 +723,10 @@ class RoomInfoEditView extends React.Component { ]} onPress={this.reset} disabled={!this.formIsChanged()} - testID='room-info-edit-view-reset' - > + testID='room-info-edit-view-reset'> <Text - style={[ - styles.button, - styles.button_inverted, - { color: themes[theme].bodyText } - ]} - accessibilityTraits='button' - > + style={[styles.button, styles.button_inverted, { color: themes[theme].bodyText }]} + accessibilityTraits='button'> {I18n.t('RESET')} </Text> </TouchableOpacity> @@ -686,21 +734,16 @@ class RoomInfoEditView extends React.Component { style={[ styles.buttonInverted, styles.buttonContainer_inverted, - archived ? !permissions[PERMISSION_UNARCHIVE] && sharedStyles.opacity5 : !permissions[PERMISSION_ARCHIVE] && sharedStyles.opacity5, + archived + ? !permissions[PERMISSION_UNARCHIVE] && sharedStyles.opacity5 + : !permissions[PERMISSION_ARCHIVE] && sharedStyles.opacity5, { flex: 1, marginLeft: 10, borderColor: dangerColor } ]} onPress={this.toggleArchive} disabled={archived ? !permissions[PERMISSION_UNARCHIVE] : !permissions[PERMISSION_ARCHIVE]} - testID={archived ? 'room-info-edit-view-unarchive' : 'room-info-edit-view-archive'} - > - <Text - style={[ - styles.button, - styles.button_inverted, - { color: dangerColor } - ]} - > - { archived ? I18n.t('UNARCHIVE') : I18n.t('ARCHIVE') } + testID={archived ? 'room-info-edit-view-unarchive' : 'room-info-edit-view-archive'}> + <Text style={[styles.button, styles.button_inverted, { color: dangerColor }]}> + {archived ? I18n.t('UNARCHIVE') : I18n.t('ARCHIVE')} </Text> </TouchableOpacity> </View> @@ -715,16 +758,8 @@ class RoomInfoEditView extends React.Component { ]} onPress={room.teamMain ? this.deleteTeam : this.delete} disabled={!this.hasDeletePermission()} - testID='room-info-edit-view-delete' - > - <Text - style={[ - styles.button, - styles.button_inverted, - { color: dangerColor } - ]} - accessibilityTraits='button' - > + testID='room-info-edit-view-delete'> + <Text style={[styles.button, styles.button_inverted, { color: dangerColor }]} accessibilityTraits='button'> {I18n.t('DELETE')} </Text> </TouchableOpacity> diff --git a/app/views/RoomInfoView/Channel.js b/app/views/RoomInfoView/Channel.js index bb65128b8..ea4a584c7 100644 --- a/app/views/RoomInfoView/Channel.js +++ b/app/views/RoomInfoView/Channel.js @@ -10,19 +10,19 @@ const Channel = ({ room, theme }) => { <> <Item label={I18n.t('Description')} - content={description || `__${ I18n.t('No_label_provided', { label: 'description' }) }__`} + content={description || `__${I18n.t('No_label_provided', { label: 'description' })}__`} theme={theme} testID='room-info-view-description' /> <Item label={I18n.t('Topic')} - content={topic || `__${ I18n.t('No_label_provided', { label: 'topic' }) }__`} + content={topic || `__${I18n.t('No_label_provided', { label: 'topic' })}__`} theme={theme} testID='room-info-view-topic' /> <Item label={I18n.t('Announcement')} - content={announcement || `__${ I18n.t('No_label_provided', { label: 'announcement' }) }__`} + content={announcement || `__${I18n.t('No_label_provided', { label: 'announcement' })}__`} theme={theme} testID='room-info-view-announcement' /> diff --git a/app/views/RoomInfoView/CustomFields.js b/app/views/RoomInfoView/CustomFields.js index b58c7af18..777cfdf80 100644 --- a/app/views/RoomInfoView/CustomFields.js +++ b/app/views/RoomInfoView/CustomFields.js @@ -5,20 +5,12 @@ import Item from './Item'; const CustomFields = ({ customFields, theme }) => { if (customFields) { - return ( - Object.keys(customFields).map((title) => { - if (!customFields[title]) { - return; - } - return ( - <Item - label={title} - content={customFields[title]} - theme={theme} - /> - ); - }) - ); + return Object.keys(customFields).map(title => { + if (!customFields[title]) { + return; + } + return <Item label={title} content={customFields[title]} theme={theme} />; + }); } return null; diff --git a/app/views/RoomInfoView/Direct.js b/app/views/RoomInfoView/Direct.js index 7587bda6f..7bc962d69 100644 --- a/app/views/RoomInfoView/Direct.js +++ b/app/views/RoomInfoView/Direct.js @@ -1,27 +1,28 @@ import React from 'react'; -import { View, Text } from 'react-native'; +import { Text, View } from 'react-native'; import PropTypes from 'prop-types'; import { themes } from '../../constants/colors'; import I18n from '../../i18n'; - import Timezone from './Timezone'; import CustomFields from './CustomFields'; - import styles from './styles'; -const Roles = ({ roles, theme }) => (roles && roles.length ? ( - <View style={styles.item}> - <Text style={[styles.itemLabel, { color: themes[theme].titleText }]}>{I18n.t('Roles')}</Text> - <View style={styles.rolesContainer}> - {roles.map(role => (role ? ( - <View style={[styles.roleBadge, { backgroundColor: themes[theme].auxiliaryBackground }]} key={role}> - <Text style={styles.role}>{role}</Text> - </View> - ) : null))} +const Roles = ({ roles, theme }) => + roles && roles.length ? ( + <View style={styles.item}> + <Text style={[styles.itemLabel, { color: themes[theme].titleText }]}>{I18n.t('Roles')}</Text> + <View style={styles.rolesContainer}> + {roles.map(role => + role ? ( + <View style={[styles.roleBadge, { backgroundColor: themes[theme].auxiliaryBackground }]} key={role}> + <Text style={styles.role}>{role}</Text> + </View> + ) : null + )} + </View> </View> - </View> -) : null); + ) : null; Roles.propTypes = { roles: PropTypes.array, theme: PropTypes.string diff --git a/app/views/RoomInfoView/Item.js b/app/views/RoomInfoView/Item.js index b2503c2e3..2b8d19f9d 100644 --- a/app/views/RoomInfoView/Item.js +++ b/app/views/RoomInfoView/Item.js @@ -1,25 +1,20 @@ import React from 'react'; -import { View, Text } from 'react-native'; +import { Text, View } from 'react-native'; import PropTypes from 'prop-types'; -import styles from './styles'; import Markdown from '../../containers/markdown'; import { themes } from '../../constants/colors'; +import styles from './styles'; -const Item = ({ - label, content, theme, testID -}) => ( +const Item = ({ label, content, theme, testID }) => content ? ( <View style={styles.item} testID={testID}> - <Text accessibilityLabel={label} style={[styles.itemLabel, { color: themes[theme].titleText }]}>{label}</Text> - <Markdown - style={[styles.itemContent, { color: themes[theme].auxiliaryText }]} - msg={content} - theme={theme} - /> + <Text accessibilityLabel={label} style={[styles.itemLabel, { color: themes[theme].titleText }]}> + {label} + </Text> + <Markdown style={[styles.itemContent, { color: themes[theme].auxiliaryText }]} msg={content} theme={theme} /> </View> - ) : null -); + ) : null; Item.propTypes = { label: PropTypes.string, content: PropTypes.string, diff --git a/app/views/RoomInfoView/Livechat.js b/app/views/RoomInfoView/Livechat.js index 09e14a608..8635bdddd 100644 --- a/app/views/RoomInfoView/Livechat.js +++ b/app/views/RoomInfoView/Livechat.js @@ -1,15 +1,15 @@ -import React, { useState, useEffect } from 'react'; -import { Text, StyleSheet } from 'react-native'; +import React, { useEffect, useState } from 'react'; +import { StyleSheet, Text } from 'react-native'; import PropTypes from 'prop-types'; import RocketChat from '../../lib/rocketchat'; import { withTheme } from '../../theme'; -import CustomFields from './CustomFields'; -import Item from './Item'; -import Timezone from './Timezone'; import sharedStyles from '../Styles'; import { themes } from '../../constants/colors'; import I18n from '../../i18n'; +import CustomFields from './CustomFields'; +import Item from './Item'; +import Timezone from './Timezone'; const styles = StyleSheet.create({ title: { @@ -28,8 +28,7 @@ Title.propTypes = { const Livechat = ({ room, roomUser, theme }) => { const [department, setDepartment] = useState({}); - - const getDepartment = async(id) => { + const getDepartment = async id => { if (id) { const result = await RocketChat.getDepartmentInfo(id); if (result.success) { @@ -44,90 +43,37 @@ const Livechat = ({ room, roomUser, theme }) => { } }; - useEffect(() => { getRoom(); }, [room]); + useEffect(() => { + getRoom(); + }, [room]); return ( <> - <Title - title={I18n.t('User')} - theme={theme} - /> - <Timezone - utcOffset={roomUser.utc} - theme={theme} - /> - <Item - label={I18n.t('Username')} - content={roomUser.username} - theme={theme} - /> + <Title title={I18n.t('User')} theme={theme} /> + <Timezone utcOffset={roomUser.utc} theme={theme} /> + <Item label={I18n.t('Username')} content={roomUser.username} theme={theme} /> <Item label={I18n.t('Email')} - content={roomUser.visitorEmails?.map(email => email.address).reduce((ret, item) => `${ ret }${ item }\n`)} + content={roomUser.visitorEmails?.map(email => email.address).reduce((ret, item) => `${ret}${item}\n`)} theme={theme} /> <Item label={I18n.t('Phone')} - content={roomUser.phone?.map(phone => phone.phoneNumber).reduce((ret, item) => `${ ret }${ item }\n`)} - theme={theme} - /> - <Item - label={I18n.t('IP')} - content={roomUser.ip} - theme={theme} - /> - <Item - label={I18n.t('OS')} - content={roomUser.os} - theme={theme} - /> - <Item - label={I18n.t('Browser')} - content={roomUser.browser} - theme={theme} - /> - <CustomFields - customFields={roomUser.livechatData} - theme={theme} - /> - <Title - title={I18n.t('Conversation')} - theme={theme} - /> - <Item - label={I18n.t('Agent')} - content={room.servedBy?.username} - theme={theme} - /> - <Item - label={I18n.t('Facebook')} - content={room.facebook?.page.name} - theme={theme} - /> - <Item - label={I18n.t('SMS')} - content={room.sms && 'SMS Enabled'} - theme={theme} - /> - <Item - label={I18n.t('Topic')} - content={room.topic} - theme={theme} - /> - <Item - label={I18n.t('Tags')} - content={room.tags?.join(', ')} - theme={theme} - /> - <Item - label={I18n.t('Department')} - content={department.name} - theme={theme} - /> - <CustomFields - customFields={room.livechatData} + content={roomUser.phone?.map(phone => phone.phoneNumber).reduce((ret, item) => `${ret}${item}\n`)} theme={theme} /> + <Item label={I18n.t('IP')} content={roomUser.ip} theme={theme} /> + <Item label={I18n.t('OS')} content={roomUser.os} theme={theme} /> + <Item label={I18n.t('Browser')} content={roomUser.browser} theme={theme} /> + <CustomFields customFields={roomUser.livechatData} theme={theme} /> + <Title title={I18n.t('Conversation')} theme={theme} /> + <Item label={I18n.t('Agent')} content={room.servedBy?.username} theme={theme} /> + <Item label={I18n.t('Facebook')} content={room.facebook?.page.name} theme={theme} /> + <Item label={I18n.t('SMS')} content={room.sms && 'SMS Enabled'} theme={theme} /> + <Item label={I18n.t('Topic')} content={room.topic} theme={theme} /> + <Item label={I18n.t('Tags')} content={room.tags?.join(', ')} theme={theme} /> + <Item label={I18n.t('Department')} content={department.name} theme={theme} /> + <CustomFields customFields={room.livechatData} theme={theme} /> </> ); }; diff --git a/app/views/RoomInfoView/Timezone.js b/app/views/RoomInfoView/Timezone.js index f07433967..e40c4c23b 100644 --- a/app/views/RoomInfoView/Timezone.js +++ b/app/views/RoomInfoView/Timezone.js @@ -6,13 +6,14 @@ import moment from 'moment'; import I18n from '../../i18n'; import Item from './Item'; -const Timezone = ({ utcOffset, Message_TimeFormat, theme }) => (utcOffset ? ( - <Item - label={I18n.t('Timezone')} - content={`${ moment().utcOffset(utcOffset).format(Message_TimeFormat) } (UTC ${ utcOffset })`} - theme={theme} - /> -) : null); +const Timezone = ({ utcOffset, Message_TimeFormat, theme }) => + utcOffset ? ( + <Item + label={I18n.t('Timezone')} + content={`${moment().utcOffset(utcOffset).format(Message_TimeFormat)} (UTC ${utcOffset})`} + theme={theme} + /> + ) : null; Timezone.propTypes = { utcOffset: PropTypes.number, Message_TimeFormat: PropTypes.string, diff --git a/app/views/RoomInfoView/index.js b/app/views/RoomInfoView/index.js index 6d0918f16..a27083613 100644 --- a/app/views/RoomInfoView/index.js +++ b/app/views/RoomInfoView/index.js @@ -1,6 +1,6 @@ import React from 'react'; import PropTypes from 'prop-types'; -import { View, Text, ScrollView } from 'react-native'; +import { ScrollView, Text, View } from 'react-native'; import { BorderlessButton } from 'react-native-gesture-handler'; import { connect } from 'react-redux'; import UAParser from 'ua-parser-js'; @@ -9,42 +9,61 @@ import isEmpty from 'lodash/isEmpty'; import { CustomIcon } from '../../lib/Icons'; import Status from '../../containers/Status'; import Avatar from '../../containers/Avatar'; -import styles from './styles'; import sharedStyles from '../Styles'; import RocketChat from '../../lib/rocketchat'; import RoomTypeIcon from '../../containers/RoomTypeIcon'; import I18n from '../../i18n'; import * as HeaderButton from '../../containers/HeaderButton'; import StatusBar from '../../containers/StatusBar'; -import log, { logEvent, events } from '../../utils/log'; +import log, { events, logEvent } from '../../utils/log'; import { themes } from '../../constants/colors'; import { withTheme } from '../../theme'; import Markdown from '../../containers/markdown'; import { LISTENER } from '../../containers/Toast'; import EventEmitter from '../../utils/events'; - -import Livechat from './Livechat'; -import Channel from './Channel'; -import Direct from './Direct'; import SafeAreaView from '../../containers/SafeAreaView'; import { goRoom } from '../../utils/goRoom'; import Navigation from '../../lib/Navigation'; +import Livechat from './Livechat'; +import Channel from './Channel'; +import Direct from './Direct'; +import styles from './styles'; -const getRoomTitle = (room, type, name, username, statusText, theme) => (type === 'd' - ? ( +const getRoomTitle = (room, type, name, username, statusText, theme) => + type === 'd' ? ( <> - <Text testID='room-info-view-name' style={[styles.roomTitle, { color: themes[theme].titleText }]}>{ name }</Text> - {username && <Text testID='room-info-view-username' style={[styles.roomUsername, { color: themes[theme].auxiliaryText }]}>{`@${ username }`}</Text>} - {!!statusText && <View testID='room-info-view-custom-status'><Markdown msg={statusText} style={[styles.roomUsername, { color: themes[theme].auxiliaryText }]} preview theme={theme} /></View>} + <Text testID='room-info-view-name' style={[styles.roomTitle, { color: themes[theme].titleText }]}> + {name} + </Text> + {username && ( + <Text + testID='room-info-view-username' + style={[styles.roomUsername, { color: themes[theme].auxiliaryText }]}>{`@${username}`}</Text> + )} + {!!statusText && ( + <View testID='room-info-view-custom-status'> + <Markdown + msg={statusText} + style={[styles.roomUsername, { color: themes[theme].auxiliaryText }]} + preview + theme={theme} + /> + </View> + )} </> - ) - : ( + ) : ( <View style={styles.roomTitleRow}> - <RoomTypeIcon type={room.prid ? 'discussion' : room.t} teamMain={room.teamMain} key='room-info-type' status={room.visitor?.status} /> - <Text testID='room-info-view-name' style={[styles.roomTitle, { color: themes[theme].titleText }]} key='room-info-name'>{RocketChat.getRoomTitle(room)}</Text> + <RoomTypeIcon + type={room.prid ? 'discussion' : room.t} + teamMain={room.teamMain} + key='room-info-type' + status={room.visitor?.status} + /> + <Text testID='room-info-view-name' style={[styles.roomTitle, { color: themes[theme].titleText }]} key='room-info-name'> + {RocketChat.getRoomTitle(room)} + </Text> </View> - ) -); + ); class RoomInfoView extends React.Component { static propTypes = { @@ -58,7 +77,7 @@ class RoomInfoView extends React.Component { editOmnichannelContact: PropTypes.array, editLivechatRoomCustomfields: PropTypes.array, roles: PropTypes.array - } + }; constructor(props) { super(props); @@ -109,21 +128,21 @@ class RoomInfoView extends React.Component { title: t === 'd' ? I18n.t('User_Info') : I18n.t('Room_Info'), headerRight: showEdit ? () => ( - <HeaderButton.Container> - <HeaderButton.Item - iconName='edit' - onPress={() => { - const isLivechat = t === 'l'; - logEvent(events[`RI_GO_${ isLivechat ? 'LIVECHAT' : 'RI' }_EDIT`]); - navigation.navigate(isLivechat ? 'LivechatEditView' : 'RoomInfoEditView', { rid, room, roomUser }); - }} - testID='room-info-view-edit-button' - /> - </HeaderButton.Container> - ) + <HeaderButton.Container> + <HeaderButton.Item + iconName='edit' + onPress={() => { + const isLivechat = t === 'l'; + logEvent(events[`RI_GO_${isLivechat ? 'LIVECHAT' : 'RI'}_EDIT`]); + navigation.navigate(isLivechat ? 'LivechatEditView' : 'RoomInfoEditView', { rid, room, roomUser }); + }} + testID='room-info-view-edit-button' + /> + </HeaderButton.Container> + ) : null }); - } + }; get isDirect() { const { room } = this.state; @@ -135,12 +154,12 @@ class RoomInfoView extends React.Component { return room.t === 'l'; } - getRoleDescription = (id) => { + getRoleDescription = id => { const { roles } = this.props; return roles[id]; }; - loadVisitor = async() => { + loadVisitor = async () => { const { room } = this.state; try { const result = await RocketChat.getVisitorInfo(room?.visitor?._id); @@ -149,17 +168,17 @@ class RoomInfoView extends React.Component { if (visitor.userAgent) { const ua = new UAParser(); ua.setUA(visitor.userAgent); - visitor.os = `${ ua.getOS().name } ${ ua.getOS().version }`; - visitor.browser = `${ ua.getBrowser().name } ${ ua.getBrowser().version }`; + visitor.os = `${ua.getOS().name} ${ua.getOS().version}`; + visitor.browser = `${ua.getBrowser().name} ${ua.getBrowser().version}`; } this.setState({ roomUser: visitor }, () => this.setHeader()); } } catch (error) { // Do nothing } - } + }; - loadUser = async() => { + loadUser = async () => { const { room, roomUser } = this.state; if (isEmpty(roomUser)) { @@ -170,10 +189,12 @@ class RoomInfoView extends React.Component { const { user } = result; const { roles } = user; if (roles && roles.length) { - user.parsedRoles = await Promise.all(roles.map(async(role) => { - const description = await this.getRoleDescription(role); - return description; - })); + user.parsedRoles = await Promise.all( + roles.map(async role => { + const description = await this.getRoleDescription(role); + return description; + }) + ); } this.setState({ roomUser: user }); @@ -182,20 +203,17 @@ class RoomInfoView extends React.Component { // do nothing } } - } + }; - loadRoom = async() => { + loadRoom = async () => { const { room: roomState } = this.state; - const { - route, editRoomPermission, editOmnichannelContact, editLivechatRoomCustomfields - } = this.props; + const { route, editRoomPermission, editOmnichannelContact, editLivechatRoomCustomfields } = this.props; let room = route.params?.room; if (room && room.observe) { this.roomObservable = room.observe(); - this.subscription = this.roomObservable - .subscribe((changes) => { - this.setState({ room: changes }, () => this.setHeader()); - }); + this.subscription = this.roomObservable.subscribe(changes => { + this.setState({ room: changes }, () => this.setHeader()); + }); } else { try { const result = await RocketChat.getRoomInfo(this.rid); @@ -214,30 +232,35 @@ class RoomInfoView extends React.Component { if (permissions.some(Boolean)) { this.setState({ showEdit: true }, () => this.setHeader()); } - } + }; - createDirect = () => new Promise(async(resolve, reject) => { - const { route } = this.props; + createDirect = () => + new Promise(async (resolve, reject) => { + const { route } = this.props; - // We don't need to create a direct - const member = route.params?.member; - if (!isEmpty(member)) { - return resolve(); - } - - // TODO: Check if some direct with the user already exists on database - try { - const { roomUser: { username } } = this.state; - const result = await RocketChat.createDirectMessage(username); - if (result.success) { - const { room: { rid } } = result; - return this.setState(({ room }) => ({ room: { ...room, rid } }), resolve); + // We don't need to create a direct + const member = route.params?.member; + if (!isEmpty(member)) { + return resolve(); } - } catch { - // do nothing - } - reject(); - }) + + // TODO: Check if some direct with the user already exists on database + try { + const { + roomUser: { username } + } = this.state; + const result = await RocketChat.createDirectMessage(username); + if (result.success) { + const { + room: { rid } + } = result; + return this.setState(({ room }) => ({ room: { ...room, rid } }), resolve); + } + } catch { + // do nothing + } + reject(); + }); goRoom = () => { logEvent(events.RI_GO_ROOM_USER); @@ -269,63 +292,50 @@ class RoomInfoView extends React.Component { navigate('RoomView', params); } } - } + }; videoCall = () => { const { room } = this.state; RocketChat.callJitsi(room); - } + }; renderAvatar = (room, roomUser) => { const { theme } = this.props; return ( - <Avatar - text={room.name || roomUser.username} - style={styles.avatar} - type={this.t} - size={100} - rid={room?.rid} - > - {this.t === 'd' && roomUser._id - ? ( - <View style={[sharedStyles.status, { backgroundColor: themes[theme].auxiliaryBackground }]}> - <Status size={20} id={roomUser._id} /> - </View> - ) - : null} + <Avatar text={room.name || roomUser.username} style={styles.avatar} type={this.t} size={100} rid={room?.rid}> + {this.t === 'd' && roomUser._id ? ( + <View style={[sharedStyles.status, { backgroundColor: themes[theme].auxiliaryBackground }]}> + <Status size={20} id={roomUser._id} /> + </View> + ) : null} </Avatar> ); - } + }; renderButton = (onPress, iconName, text) => { const { theme } = this.props; - const onActionPress = async() => { + const onActionPress = async () => { try { if (this.isDirect) { await this.createDirect(); } onPress(); } catch { - EventEmitter.emit(LISTENER, { message: I18n.t('error-action-not-allowed', { action: I18n.t('Create_Direct_Messages') }) }); + EventEmitter.emit(LISTENER, { + message: I18n.t('error-action-not-allowed', { action: I18n.t('Create_Direct_Messages') }) + }); } }; return ( - <BorderlessButton - onPress={onActionPress} - style={styles.roomButton} - > - <CustomIcon - name={iconName} - size={30} - color={themes[theme].actionTintColor} - /> + <BorderlessButton onPress={onActionPress} style={styles.roomButton}> + <CustomIcon name={iconName} size={30} color={themes[theme].actionTintColor} /> <Text style={[styles.roomButtonText, { color: themes[theme].actionTintColor }]}>{text}</Text> </BorderlessButton> ); - } + }; renderButtons = () => { const { jitsiEnabled } = this.props; @@ -335,7 +345,7 @@ class RoomInfoView extends React.Component { {jitsiEnabled && this.isDirect ? this.renderButton(this.videoCall, 'camera', I18n.t('Video_call')) : null} </View> ); - } + }; renderContent = () => { const { room, roomUser } = this.state; @@ -347,7 +357,7 @@ class RoomInfoView extends React.Component { return <Livechat room={room} roomUser={roomUser} theme={theme} />; } return <Channel room={room} theme={theme} />; - } + }; render() { const { room, roomUser } = this.state; @@ -355,13 +365,12 @@ class RoomInfoView extends React.Component { return ( <ScrollView style={[styles.scroll, { backgroundColor: themes[theme].backgroundColor }]}> <StatusBar /> - <SafeAreaView - style={{ backgroundColor: themes[theme].backgroundColor }} - testID='room-info-view' - > + <SafeAreaView style={{ backgroundColor: themes[theme].backgroundColor }} testID='room-info-view'> <View style={[styles.avatarContainer, { backgroundColor: themes[theme].auxiliaryBackground }]}> {this.renderAvatar(room, roomUser)} - <View style={styles.roomTitleContainer}>{ getRoomTitle(room, this.t, roomUser?.name, roomUser?.username, roomUser?.statusText, theme) }</View> + <View style={styles.roomTitleContainer}> + {getRoomTitle(room, this.t, roomUser?.name, roomUser?.username, roomUser?.statusText, theme)} + </View> {this.renderButtons()} </View> {this.renderContent()} diff --git a/app/views/RoomMembersView/index.js b/app/views/RoomMembersView/index.js index aaa0c98f9..308946982 100644 --- a/app/views/RoomMembersView/index.js +++ b/app/views/RoomMembersView/index.js @@ -3,9 +3,8 @@ import PropTypes from 'prop-types'; import { FlatList } from 'react-native'; import { connect } from 'react-redux'; import { Q } from '@nozbe/watermelondb'; -import * as List from '../../containers/List'; -import styles from './styles'; +import * as List from '../../containers/List'; import UserItem from '../../presentation/UserItem'; import scrollPersistTaps from '../../utils/scrollPersistTaps'; import RocketChat from '../../lib/rocketchat'; @@ -27,6 +26,7 @@ import { showConfirmationAlert, showErrorAlert } from '../../utils/info'; import SafeAreaView from '../../containers/SafeAreaView'; import { goRoom } from '../../utils/goRoom'; import { CustomIcon } from '../../lib/Icons'; +import styles from './styles'; const PAGE_SIZE = 25; @@ -64,12 +64,13 @@ class RoomMembersView extends React.Component { editTeamMemberPermission: PropTypes.array, viewAllTeamChannelsPermission: PropTypes.array, viewAllTeamsPermission: PropTypes.array - } + }; constructor(props) { super(props); this.mounted = false; this.MUTE_INDEX = 0; + this.permissions = {}; const rid = props.route.params?.rid; const room = props.route.params?.room; this.state = { @@ -84,30 +85,48 @@ class RoomMembersView extends React.Component { }; if (room && room.observe) { this.roomObservable = room.observe(); - this.subscription = this.roomObservable - .subscribe((changes) => { - if (this.mounted) { - this.setState({ room: changes }); - } else { - this.state.room = changes; - } - }); + this.subscription = this.roomObservable.subscribe(changes => { + if (this.mounted) { + this.setState({ room: changes }); + } else { + this.state.room = changes; + } + }); } this.setHeader(); } async componentDidMount() { + const { room } = this.state; this.mounted = true; this.fetchMembers(); - const { room } = this.state; + if (RocketChat.isGroupChat(room)) { + return; + } + const { - muteUserPermission, setLeaderPermission, setOwnerPermission, setModeratorPermission, removeUserPermission, editTeamMemberPermission, viewAllTeamChannelsPermission, viewAllTeamsPermission + muteUserPermission, + setLeaderPermission, + setOwnerPermission, + setModeratorPermission, + removeUserPermission, + editTeamMemberPermission, + viewAllTeamChannelsPermission, + viewAllTeamsPermission } = this.props; - const result = await RocketChat.hasPermission([ - muteUserPermission, setLeaderPermission, setOwnerPermission, setModeratorPermission, removeUserPermission, ...(room.teamMain ? [editTeamMemberPermission, viewAllTeamChannelsPermission, viewAllTeamsPermission] : []) - ], room.rid); + const result = await RocketChat.hasPermission( + [ + muteUserPermission, + setLeaderPermission, + setOwnerPermission, + setModeratorPermission, + removeUserPermission, + ...(room.teamMain ? [editTeamMemberPermission, viewAllTeamChannelsPermission, viewAllTeamsPermission] : []) + ], + room.rid + ); this.permissions = { [PERMISSION_MUTE_USER]: result[0], @@ -115,11 +134,13 @@ class RoomMembersView extends React.Component { [PERMISSION_SET_OWNER]: result[2], [PERMISSION_SET_MODERATOR]: result[3], [PERMISSION_REMOVE_USER]: result[4], - ...(room.teamMain ? { - [PERMISSION_EDIT_TEAM_MEMBER]: result[5], - [PERMISSION_VIEW_ALL_TEAM_CHANNELS]: result[6], - [PERMISION_VIEW_ALL_TEAMS]: result[7] - } : {}) + ...(room.teamMain + ? { + [PERMISSION_EDIT_TEAM_MEMBER]: result[5], + [PERMISSION_VIEW_ALL_TEAM_CHANNELS]: result[6], + [PERMISION_VIEW_ALL_TEAMS]: result[7] + } + : {}) }; const hasSinglePermission = Object.values(this.permissions).some(p => !!p); @@ -146,20 +167,22 @@ class RoomMembersView extends React.Component { </HeaderButton.Container> ) }); - } + }; - onSearchChangeText = protectedFunction((text) => { + onSearchChangeText = protectedFunction(text => { const { members } = this.state; let membersFiltered = []; text = text.trim(); if (members && members.length > 0 && text) { - membersFiltered = members.filter(m => m.username.toLowerCase().match(text.toLowerCase()) || m.name.toLowerCase().match(text.toLowerCase())); + membersFiltered = members.filter( + m => m.username.toLowerCase().match(text.toLowerCase()) || m.name.toLowerCase().match(text.toLowerCase()) + ); } this.setState({ filtering: !!text, membersFiltered }); - }) + }); - navToDirectMessage = async(item) => { + navToDirectMessage = async item => { try { const db = database.active; const subsCollection = db.get('subscriptions'); @@ -176,9 +199,9 @@ class RoomMembersView extends React.Component { } catch (e) { log(e); } - } + }; - handleRemoveFromTeam = async(selectedUser) => { + handleRemoveFromTeam = async selectedUser => { try { const { navigation } = this.props; const { room } = this.state; @@ -213,9 +236,9 @@ class RoomMembersView extends React.Component { onPress: () => this.removeFromTeam(selectedUser) }); } - } + }; - removeFromTeam = async(selectedUser, selected) => { + removeFromTeam = async (selectedUser, selected) => { try { const { members, membersFiltered, room } = this.state; const { navigation } = this.props; @@ -241,23 +264,23 @@ class RoomMembersView extends React.Component { } catch (e) { log(e); showErrorAlert( - e.data.error - ? I18n.t(e.data.error) - : I18n.t('There_was_an_error_while_action', { action: I18n.t('removing_team') }), + e.data.error ? I18n.t(e.data.error) : I18n.t('There_was_an_error_while_action', { action: I18n.t('removing_team') }), I18n.t('Cannot_remove') ); } - } + }; - onPressUser = (selectedUser) => { + onPressUser = selectedUser => { const { room } = this.state; const { showActionSheet, user, theme } = this.props; - const options = [{ - icon: 'message', - title: I18n.t('Direct_message'), - onPress: () => this.navToDirectMessage(selectedUser) - }]; + const options = [ + { + icon: 'message', + title: I18n.t('Direct_message'), + onPress: () => this.navToDirectMessage(selectedUser) + } + ]; // Ignore if (selectedUser._id !== user.id) { @@ -280,7 +303,7 @@ class RoomMembersView extends React.Component { title: I18n.t(userIsMuted ? 'Unmute' : 'Mute'), onPress: () => { showConfirmationAlert({ - message: I18n.t(`The_user_${ userIsMuted ? 'will' : 'wont' }_be_able_to_type_in_roomName`, { + message: I18n.t(`The_user_${userIsMuted ? 'will' : 'wont'}_be_able_to_type_in_roomName`, { roomName: RocketChat.getRoomTitle(room) }), confirmationText: I18n.t(userIsMuted ? 'Unmute' : 'Mute'), @@ -299,7 +322,14 @@ class RoomMembersView extends React.Component { icon: 'shield-check', title: I18n.t('Owner'), onPress: () => this.handleOwner(selectedUser, !isOwner), - right: () => <CustomIcon testID={isOwner ? 'action-sheet-set-owner-checked' : 'action-sheet-set-owner-unchecked'} name={isOwner ? 'checkbox-checked' : 'checkbox-unchecked'} size={20} color={isOwner ? themes[theme].tintActive : themes[theme].auxiliaryTintColor} />, + right: () => ( + <CustomIcon + testID={isOwner ? 'action-sheet-set-owner-checked' : 'action-sheet-set-owner-unchecked'} + name={isOwner ? 'checkbox-checked' : 'checkbox-unchecked'} + size={20} + color={isOwner ? themes[theme].tintActive : themes[theme].auxiliaryTintColor} + /> + ), testID: 'action-sheet-set-owner' }); } @@ -312,7 +342,14 @@ class RoomMembersView extends React.Component { icon: 'shield-alt', title: I18n.t('Leader'), onPress: () => this.handleLeader(selectedUser, !isLeader), - right: () => <CustomIcon testID={isLeader ? 'action-sheet-set-leader-checked' : 'action-sheet-set-leader-unchecked'} name={isLeader ? 'checkbox-checked' : 'checkbox-unchecked'} size={20} color={isLeader ? themes[theme].tintActive : themes[theme].auxiliaryTintColor} />, + right: () => ( + <CustomIcon + testID={isLeader ? 'action-sheet-set-leader-checked' : 'action-sheet-set-leader-unchecked'} + name={isLeader ? 'checkbox-checked' : 'checkbox-unchecked'} + size={20} + color={isLeader ? themes[theme].tintActive : themes[theme].auxiliaryTintColor} + /> + ), testID: 'action-sheet-set-leader' }); } @@ -325,7 +362,14 @@ class RoomMembersView extends React.Component { icon: 'shield', title: I18n.t('Moderator'), onPress: () => this.handleModerator(selectedUser, !isModerator), - right: () => <CustomIcon testID={isModerator ? 'action-sheet-set-moderator-checked' : 'action-sheet-set-moderator-unchecked'} name={isModerator ? 'checkbox-checked' : 'checkbox-unchecked'} size={20} color={isModerator ? themes[theme].tintActive : themes[theme].auxiliaryTintColor} />, + right: () => ( + <CustomIcon + testID={isModerator ? 'action-sheet-set-moderator-checked' : 'action-sheet-set-moderator-unchecked'} + name={isModerator ? 'checkbox-checked' : 'checkbox-unchecked'} + size={20} + color={isModerator ? themes[theme].tintActive : themes[theme].auxiliaryTintColor} + /> + ), testID: 'action-sheet-set-moderator' }); } @@ -362,7 +406,7 @@ class RoomMembersView extends React.Component { options, hasCancel: true }); - } + }; toggleStatus = () => { try { @@ -373,9 +417,9 @@ class RoomMembersView extends React.Component { } catch (e) { log(e); } - } + }; - fetchRoomMembersRoles = async() => { + fetchRoomMembersRoles = async () => { try { const { room } = this.state; const result = await RocketChat.getRoomRoles(room.rid, room.t); @@ -385,33 +429,38 @@ class RoomMembersView extends React.Component { } catch (e) { log(e); } - } + }; - fetchMembers = async() => { - const { - rid, members, isLoading, allUsers, end - } = this.state; + fetchMembers = async () => { + const { rid, members, isLoading, allUsers, end, room, filtering } = this.state; + const { t } = room; if (isLoading || end) { return; } this.setState({ isLoading: true }); try { - const membersResult = await RocketChat.getRoomMembers(rid, allUsers, members.length, PAGE_SIZE); - const newMembers = membersResult.records; + const membersResult = await RocketChat.getRoomMembers({ + rid, + roomType: t, + type: allUsers ? 'all' : 'online', + filter: filtering, + skip: members.length, + limit: PAGE_SIZE + }); this.setState({ - members: members.concat(newMembers || []), + members: members.concat(membersResult || []), isLoading: false, - end: newMembers.length < PAGE_SIZE + end: membersResult?.length < PAGE_SIZE }); this.setHeader(); } catch (e) { log(e); this.setState({ isLoading: false }); } - } + }; - goRoom = (item) => { + goRoom = item => { const { navigation, isMasterDetail } = this.props; if (isMasterDetail) { navigation.navigate('DrawerNavigator'); @@ -419,30 +468,37 @@ class RoomMembersView extends React.Component { navigation.popToTop(); } goRoom({ item, isMasterDetail }); - } + }; - getUserDisplayName = (user) => { + getUserDisplayName = user => { const { useRealName } = this.props; return (useRealName ? user.name : user.username) || user.username; - } + }; - handleMute = async(user) => { + handleMute = async user => { const { rid } = this.state; try { await RocketChat.toggleMuteUserInRoom(rid, user?.username, !user?.muted); - EventEmitter.emit(LISTENER, { message: I18n.t('User_has_been_key', { key: user?.muted ? I18n.t('unmuted') : I18n.t('muted') }) }); + EventEmitter.emit(LISTENER, { + message: I18n.t('User_has_been_key', { key: user?.muted ? I18n.t('unmuted') : I18n.t('muted') }) + }); } catch (e) { log(e); } - } + }; - handleOwner = async(selectedUser, isOwner) => { + handleOwner = async (selectedUser, isOwner) => { try { const { room } = this.state; await RocketChat.toggleRoomOwner({ - roomId: room.rid, t: room.t, userId: selectedUser._id, isOwner + roomId: room.rid, + t: room.t, + userId: selectedUser._id, + isOwner }); - const message = isOwner ? 'User__username__is_now_a_owner_of__room_name_' : 'User__username__removed_from__room_name__owners'; + const message = isOwner + ? 'User__username__is_now_a_owner_of__room_name_' + : 'User__username__removed_from__room_name__owners'; EventEmitter.emit(LISTENER, { message: I18n.t(message, { username: this.getUserDisplayName(selectedUser), @@ -453,15 +509,20 @@ class RoomMembersView extends React.Component { log(e); } this.fetchRoomMembersRoles(); - } + }; - handleLeader = async(selectedUser, isLeader) => { + handleLeader = async (selectedUser, isLeader) => { try { const { room } = this.state; await RocketChat.toggleRoomLeader({ - roomId: room.rid, t: room.t, userId: selectedUser._id, isLeader + roomId: room.rid, + t: room.t, + userId: selectedUser._id, + isLeader }); - const message = isLeader ? 'User__username__is_now_a_leader_of__room_name_' : 'User__username__removed_from__room_name__leaders'; + const message = isLeader + ? 'User__username__is_now_a_leader_of__room_name_' + : 'User__username__removed_from__room_name__leaders'; EventEmitter.emit(LISTENER, { message: I18n.t(message, { username: this.getUserDisplayName(selectedUser), @@ -472,15 +533,20 @@ class RoomMembersView extends React.Component { log(e); } this.fetchRoomMembersRoles(); - } + }; - handleModerator = async(selectedUser, isModerator) => { + handleModerator = async (selectedUser, isModerator) => { try { const { room } = this.state; await RocketChat.toggleRoomModerator({ - roomId: room.rid, t: room.t, userId: selectedUser._id, isModerator + roomId: room.rid, + t: room.t, + userId: selectedUser._id, + isModerator }); - const message = isModerator ? 'User__username__is_now_a_moderator_of__room_name_' : 'User__username__removed_from__room_name__moderators'; + const message = isModerator + ? 'User__username__is_now_a_moderator_of__room_name_' + : 'User__username__removed_from__room_name__moderators'; EventEmitter.emit(LISTENER, { message: I18n.t(message, { username: this.getUserDisplayName(selectedUser), @@ -491,22 +557,24 @@ class RoomMembersView extends React.Component { log(e); } this.fetchRoomMembersRoles(); - } + }; - handleIgnore = async(selectedUser, ignore) => { + handleIgnore = async (selectedUser, ignore) => { try { const { room } = this.state; await RocketChat.ignoreUser({ - rid: room.rid, userId: selectedUser._id, ignore + rid: room.rid, + userId: selectedUser._id, + ignore }); const message = I18n.t(ignore ? 'User_has_been_ignored' : 'User_has_been_unignored'); EventEmitter.emit(LISTENER, { message }); } catch (e) { log(e); } - } + }; - handleRemoveUserFromRoom = async(selectedUser) => { + handleRemoveUserFromRoom = async selectedUser => { try { const { room, members, membersFiltered } = this.state; const userId = selectedUser._id; @@ -520,11 +588,9 @@ class RoomMembersView extends React.Component { } catch (e) { log(e); } - } + }; - renderSearchBar = () => ( - <SearchBox onChangeText={text => this.onSearchChangeText(text)} testID='room-members-view-search' /> - ) + renderSearchBar = () => <SearchBox onChangeText={text => this.onSearchChangeText(text)} testID='room-members-view-search' />; renderItem = ({ item }) => { const { baseUrl, user, theme } = this.props; @@ -535,17 +601,15 @@ class RoomMembersView extends React.Component { username={item.username} onPress={() => this.onPressUser(item)} baseUrl={baseUrl} - testID={`room-members-view-item-${ item.username }`} + testID={`room-members-view-item-${item.username}`} user={user} theme={theme} /> ); - } + }; render() { - const { - filtering, members, membersFiltered, isLoading - } = this.state; + const { filtering, members, membersFiltered, isLoading } = this.state; const { theme } = this.props; return ( <SafeAreaView testID='room-members-view'> diff --git a/app/views/RoomView/Banner.js b/app/views/RoomView/Banner.js index 5ee5bb9a2..860f80b73 100644 --- a/app/views/RoomView/Banner.js +++ b/app/views/RoomView/Banner.js @@ -1,69 +1,55 @@ import React, { useState } from 'react'; -import { View, Text } from 'react-native'; +import { Text, View } from 'react-native'; import PropTypes from 'prop-types'; -import { ScrollView, BorderlessButton } from 'react-native-gesture-handler'; +import { BorderlessButton, ScrollView } from 'react-native-gesture-handler'; import Modal from 'react-native-modal'; import Markdown from '../../containers/markdown'; - import { CustomIcon } from '../../lib/Icons'; import { themes } from '../../constants/colors'; import styles from './styles'; -const Banner = React.memo(({ - text, title, theme, bannerClosed, closeBanner -}) => { - const [showModal, openModal] = useState(false); +const Banner = React.memo( + ({ text, title, theme, bannerClosed, closeBanner }) => { + const [showModal, openModal] = useState(false); - const toggleModal = () => openModal(prevState => !prevState); + const toggleModal = () => openModal(prevState => !prevState); - if (text && !bannerClosed) { - return ( - <> - <BorderlessButton - style={[styles.bannerContainer, { backgroundColor: themes[theme].bannerBackground }]} - testID='room-view-banner' - onPress={toggleModal} - > - <Markdown - msg={text} - theme={theme} - numberOfLines={1} - style={[styles.bannerText]} - preview - /> - <BorderlessButton onPress={closeBanner}> - <CustomIcon - color={themes[theme].auxiliaryText} - name='close' - size={20} - /> + if (text && !bannerClosed) { + return ( + <> + <BorderlessButton + style={[styles.bannerContainer, { backgroundColor: themes[theme].bannerBackground }]} + testID='room-view-banner' + onPress={toggleModal}> + <Markdown msg={text} theme={theme} numberOfLines={1} style={[styles.bannerText]} preview /> + <BorderlessButton onPress={closeBanner}> + <CustomIcon color={themes[theme].auxiliaryText} name='close' size={20} /> + </BorderlessButton> </BorderlessButton> - </BorderlessButton> - <Modal - onBackdropPress={toggleModal} - onBackButtonPress={toggleModal} - useNativeDriver - isVisible={showModal} - animationIn='fadeIn' - animationOut='fadeOut' - > - <View style={[styles.modalView, { backgroundColor: themes[theme].bannerBackground }]}> - <Text style={[styles.bannerModalTitle, { color: themes[theme].auxiliaryText }]}>{title}</Text> - <ScrollView style={styles.modalScrollView}> - <Markdown - msg={text} - theme={theme} - /> - </ScrollView> - </View> - </Modal> - </> - ); - } + <Modal + onBackdropPress={toggleModal} + onBackButtonPress={toggleModal} + useNativeDriver + isVisible={showModal} + animationIn='fadeIn' + animationOut='fadeOut'> + <View style={[styles.modalView, { backgroundColor: themes[theme].bannerBackground }]}> + <Text style={[styles.bannerModalTitle, { color: themes[theme].auxiliaryText }]}>{title}</Text> + <ScrollView style={styles.modalScrollView}> + <Markdown msg={text} theme={theme} /> + </ScrollView> + </View> + </Modal> + </> + ); + } - return null; -}, (prevProps, nextProps) => prevProps.text === nextProps.text && prevProps.theme === nextProps.theme && prevProps.bannerClosed === nextProps.bannerClosed); + return null; + }, + (prevProps, nextProps) => + prevProps.text === nextProps.text && prevProps.theme === nextProps.theme && prevProps.bannerClosed === nextProps.bannerClosed +); Banner.propTypes = { text: PropTypes.string, diff --git a/app/views/RoomView/EmptyRoom.js b/app/views/RoomView/EmptyRoom.js index 255940528..1a9a248f2 100644 --- a/app/views/RoomView/EmptyRoom.js +++ b/app/views/RoomView/EmptyRoom.js @@ -10,16 +10,9 @@ const styles = StyleSheet.create({ } }); -const EmptyRoom = React.memo(({ - length, mounted, theme, rid -}) => { +const EmptyRoom = React.memo(({ length, mounted, theme, rid }) => { if ((length === 0 && mounted) || !rid) { - return ( - <ImageBackground - source={{ uri: `message_empty_${ theme }` }} - style={styles.image} - /> - ); + return <ImageBackground source={{ uri: `message_empty_${theme}` }} style={styles.image} />; } return null; }); diff --git a/app/views/RoomView/JoinCode.js b/app/views/RoomView/JoinCode.js index e3639818b..ceccc1ae7 100644 --- a/app/views/RoomView/JoinCode.js +++ b/app/views/RoomView/JoinCode.js @@ -1,15 +1,6 @@ -import React, { - useState, - forwardRef, - useImperativeHandle -} from 'react'; +import React, { forwardRef, useImperativeHandle, useState } from 'react'; import PropTypes from 'prop-types'; -import { - View, - Text, - StyleSheet, - InteractionManager -} from 'react-native'; +import { InteractionManager, StyleSheet, Text, View } from 'react-native'; import Modal from 'react-native-modal'; import { connect } from 'react-redux'; @@ -18,7 +9,6 @@ import Button from '../../containers/Button'; import TextInput from '../../containers/TextInput'; import RocketChat from '../../lib/rocketchat'; import sharedStyles from '../Styles'; - import { themes } from '../../constants/colors'; const styles = StyleSheet.create({ @@ -51,81 +41,76 @@ const styles = StyleSheet.create({ } }); -const JoinCode = React.memo(forwardRef(({ - rid, - t, - onJoin, - isMasterDetail, - theme -}, ref) => { - const [visible, setVisible] = useState(false); - const [error, setError] = useState(false); - const [code, setCode] = useState(''); +const JoinCode = React.memo( + forwardRef(({ rid, t, onJoin, isMasterDetail, theme }, ref) => { + const [visible, setVisible] = useState(false); + const [error, setError] = useState(false); + const [code, setCode] = useState(''); - const show = () => setVisible(true); + const show = () => setVisible(true); - const hide = () => setVisible(false); + const hide = () => setVisible(false); - const joinRoom = async() => { - try { - await RocketChat.joinRoom(rid, code, t); - onJoin(); - hide(); - } catch (e) { - setError(true); - } - }; + const joinRoom = async () => { + try { + await RocketChat.joinRoom(rid, code, t); + onJoin(); + hide(); + } catch (e) { + setError(true); + } + }; - useImperativeHandle(ref, () => ({ show })); + useImperativeHandle(ref, () => ({ show })); - return ( - <Modal - transparent - avoidKeyboard - useNativeDriver - isVisible={visible} - hideModalContentWhileAnimating - > - <View style={styles.container} testID='join-code'> - <View style={[styles.content, isMasterDetail && [sharedStyles.modalFormSheet, styles.tablet], { backgroundColor: themes[theme].backgroundColor }]}> - <Text style={[styles.title, { color: themes[theme].titleText }]}>{I18n.t('Insert_Join_Code')}</Text> - <TextInput - value={code} - theme={theme} - inputRef={e => InteractionManager.runAfterInteractions(() => e?.getNativeRef()?.focus())} - returnKeyType='send' - autoCapitalize='none' - onChangeText={setCode} - onSubmitEditing={joinRoom} - placeholder={I18n.t('Join_Code')} - secureTextEntry - error={error && { error: 'error-code-invalid', reason: I18n.t('Code_or_password_invalid') }} - testID='join-code-input' - /> - <View style={styles.buttonContainer}> - <Button - title={I18n.t('Cancel')} - type='secondary' - style={styles.button} - backgroundColor={themes[theme].chatComponentBackground} + return ( + <Modal transparent avoidKeyboard useNativeDriver isVisible={visible} hideModalContentWhileAnimating> + <View style={styles.container} testID='join-code'> + <View + style={[ + styles.content, + isMasterDetail && [sharedStyles.modalFormSheet, styles.tablet], + { backgroundColor: themes[theme].backgroundColor } + ]}> + <Text style={[styles.title, { color: themes[theme].titleText }]}>{I18n.t('Insert_Join_Code')}</Text> + <TextInput + value={code} theme={theme} - testID='join-code-cancel' - onPress={hide} - /> - <Button - title={I18n.t('Join')} - type='primary' - style={styles.button} - theme={theme} - testID='join-code-submit' - onPress={joinRoom} + inputRef={e => InteractionManager.runAfterInteractions(() => e?.getNativeRef()?.focus())} + returnKeyType='send' + autoCapitalize='none' + onChangeText={setCode} + onSubmitEditing={joinRoom} + placeholder={I18n.t('Join_Code')} + secureTextEntry + error={error && { error: 'error-code-invalid', reason: I18n.t('Code_or_password_invalid') }} + testID='join-code-input' /> + <View style={styles.buttonContainer}> + <Button + title={I18n.t('Cancel')} + type='secondary' + style={styles.button} + backgroundColor={themes[theme].chatComponentBackground} + theme={theme} + testID='join-code-cancel' + onPress={hide} + /> + <Button + title={I18n.t('Join')} + type='primary' + style={styles.button} + theme={theme} + testID='join-code-submit' + onPress={joinRoom} + /> + </View> </View> </View> - </View> - </Modal> - ); -})); + </Modal> + ); + }) +); JoinCode.propTypes = { rid: PropTypes.string, t: PropTypes.string, diff --git a/app/views/RoomView/LeftButtons.js b/app/views/RoomView/LeftButtons.js index 089c284d6..aa41b2f36 100644 --- a/app/views/RoomView/LeftButtons.js +++ b/app/views/RoomView/LeftButtons.js @@ -13,39 +13,31 @@ const styles = StyleSheet.create({ } }); -const LeftButtons = React.memo(({ - tmid, unreadsCount, navigation, baseUrl, userId, token, title, t, theme, goRoomActionsView, isMasterDetail -}) => { - if (!isMasterDetail || tmid) { - const onPress = useCallback(() => navigation.goBack()); - const label = unreadsCount > 99 ? '+99' : unreadsCount || ' '; - const labelLength = label.length ? label.length : 1; - const marginLeft = -2 * labelLength; - const fontSize = labelLength > 1 ? 14 : 17; - return ( - <HeaderBackButton - label={label} - onPress={onPress} - tintColor={themes[theme].headerTintColor} - labelStyle={{ fontSize, marginLeft }} - /> - ); - } - const onPress = useCallback(() => goRoomActionsView(), []); +const LeftButtons = React.memo( + ({ tmid, unreadsCount, navigation, baseUrl, userId, token, title, t, theme, goRoomActionsView, isMasterDetail }) => { + if (!isMasterDetail || tmid) { + const onPress = useCallback(() => navigation.goBack()); + const label = unreadsCount > 99 ? '+99' : unreadsCount || ' '; + const labelLength = label.length ? label.length : 1; + const marginLeft = -2 * labelLength; + const fontSize = labelLength > 1 ? 14 : 17; + return ( + <HeaderBackButton + label={label} + onPress={onPress} + tintColor={themes[theme].headerTintColor} + labelStyle={{ fontSize, marginLeft }} + /> + ); + } + const onPress = useCallback(() => goRoomActionsView(), []); - if (baseUrl && userId && token) { - return ( - <Avatar - text={title} - size={30} - type={t} - style={styles.avatar} - onPress={onPress} - /> - ); + if (baseUrl && userId && token) { + return <Avatar text={title} size={30} type={t} style={styles.avatar} onPress={onPress} />; + } + return null; } - return null; -}); +); LeftButtons.propTypes = { tmid: PropTypes.string, diff --git a/app/views/RoomView/List/NavBottomFAB.js b/app/views/RoomView/List/NavBottomFAB.js index 481ac79a6..e3456c122 100644 --- a/app/views/RoomView/List/NavBottomFAB.js +++ b/app/views/RoomView/List/NavBottomFAB.js @@ -1,9 +1,7 @@ import React, { useCallback, useState } from 'react'; -import { View, StyleSheet } from 'react-native'; +import { StyleSheet, View } from 'react-native'; import PropTypes from 'prop-types'; -import Animated, { - call, cond, greaterOrEq, useCode -} from 'react-native-reanimated'; +import Animated, { call, cond, greaterOrEq, useCode } from 'react-native-reanimated'; import { themes } from '../../../constants/colors'; import { CustomIcon } from '../../../lib/Icons'; @@ -38,10 +36,15 @@ const NavBottomFAB = ({ y, onPress, isThread }) => { const handleOnPress = useCallback(() => onPress()); const toggle = v => setShow(v); - useCode(() => cond(greaterOrEq(y, SCROLL_LIMIT), - call([y], () => toggle(true)), - call([y], () => toggle(false))), - [y]); + useCode( + () => + cond( + greaterOrEq(y, SCROLL_LIMIT), + call([y], () => toggle(true)), + call([y], () => toggle(false)) + ), + [y] + ); if (!show) { return null; @@ -53,11 +56,7 @@ const NavBottomFAB = ({ y, onPress, isThread }) => { } return ( <Animated.View style={[styles.container, { bottom }]} testID='nav-jump-to-bottom'> - <Touch - onPress={handleOnPress} - theme={theme} - style={[styles.button, { backgroundColor: themes[theme].backgroundColor }]} - > + <Touch onPress={handleOnPress} theme={theme} style={[styles.button, { backgroundColor: themes[theme].backgroundColor }]}> <View style={[styles.content, { borderColor: themes[theme].borderColor }]}> <CustomIcon name='chevron-down' color={themes[theme].auxiliaryTintColor} size={36} /> </View> diff --git a/app/views/RoomView/List/index.js b/app/views/RoomView/List/index.js index cbf02879b..c32490ba6 100644 --- a/app/views/RoomView/List/index.js +++ b/app/views/RoomView/List/index.js @@ -13,22 +13,23 @@ import EmptyRoom from '../EmptyRoom'; import { animateNextTransition } from '../../../utils/layoutAnimation'; import ActivityIndicator from '../../../containers/ActivityIndicator'; import { themes } from '../../../constants/colors'; +import debounce from '../../../utils/debounce'; import List from './List'; import NavBottomFAB from './NavBottomFAB'; -import debounce from '../../../utils/debounce'; const QUERY_SIZE = 50; -const onScroll = ({ y }) => event( - [ - { - nativeEvent: { - contentOffset: { y } +const onScroll = ({ y }) => + event( + [ + { + nativeEvent: { + contentOffset: { y } + } } - } - ], - { useNativeDriver: true } -); + ], + { useNativeDriver: true } + ); class ListContainer extends React.Component { static propTypes = { @@ -47,8 +48,8 @@ class ListContainer extends React.Component { constructor(props) { super(props); - console.time(`${ this.constructor.name } init`); - console.time(`${ this.constructor.name } mount`); + console.time(`${this.constructor.name} init`); + console.time(`${this.constructor.name} mount`); this.count = 0; this.mounted = false; this.animated = false; @@ -67,19 +68,17 @@ class ListContainer extends React.Component { this.viewabilityConfig = { itemVisiblePercentThreshold: 10 }; - console.timeEnd(`${ this.constructor.name } init`); + console.timeEnd(`${this.constructor.name} init`); } componentDidMount() { this.mounted = true; - console.timeEnd(`${ this.constructor.name } mount`); + console.timeEnd(`${this.constructor.name} mount`); } shouldComponentUpdate(nextProps, nextState) { const { refreshing, highlightedMessage } = this.state; - const { - hideSystemMessages, theme, tunread, ignored, loading - } = this.props; + const { hideSystemMessages, theme, tunread, ignored, loading } = this.props; if (theme !== nextProps.theme) { return true; } @@ -120,7 +119,7 @@ class ListContainer extends React.Component { this.unsubscribeFocus(); } this.clearHighlightedMessageTimeout(); - console.countReset(`${ this.constructor.name }.render calls`); + console.countReset(`${this.constructor.name}.render calls`); } clearHighlightedMessageTimeout = () => { @@ -128,9 +127,9 @@ class ListContainer extends React.Component { clearTimeout(this.highlightedMessageTimeout); this.highlightedMessageTimeout = false; } - } + }; - query = async() => { + query = async () => { this.count += QUERY_SIZE; const { rid, tmid, showMessageInMainThread } = this.props; const db = database.active; @@ -143,20 +142,13 @@ class ListContainer extends React.Component { if (tmid) { try { - this.thread = await db.collections - .get('threads') - .find(tmid); + this.thread = await db.collections.get('threads').find(tmid); } catch (e) { console.log(e); } this.messagesObservable = db.collections .get('thread_messages') - .query( - Q.where('rid', tmid), - Q.experimentalSortBy('ts', Q.desc), - Q.experimentalSkip(0), - Q.experimentalTake(this.count) - ) + .query(Q.where('rid', tmid), Q.experimentalSortBy('ts', Q.desc), Q.experimentalSkip(0), Q.experimentalTake(this.count)) .observe(); } else if (rid) { const whereClause = [ @@ -166,12 +158,7 @@ class ListContainer extends React.Component { Q.experimentalTake(this.count) ]; if (!showMessageInMainThread) { - whereClause.push( - Q.or( - Q.where('tmid', null), - Q.where('tshow', Q.eq(true)) - ) - ); + whereClause.push(Q.or(Q.where('tmid', null), Q.where('tshow', Q.eq(true)))); } this.messagesObservable = db.collections .get('messages') @@ -181,30 +168,29 @@ class ListContainer extends React.Component { if (rid) { this.unsubscribeMessages(); - this.messagesSubscription = this.messagesObservable - .subscribe((messages) => { - if (tmid && this.thread) { - messages = [...messages, this.thread]; - } - messages = messages.filter(m => !m.t || !hideSystemMessages?.includes(m.t)); + this.messagesSubscription = this.messagesObservable.subscribe(messages => { + if (tmid && this.thread) { + messages = [...messages, this.thread]; + } + messages = messages.filter(m => !m.t || !hideSystemMessages?.includes(m.t)); - if (this.mounted) { - this.setState({ messages }, () => this.update()); - } else { - this.state.messages = messages; - } - // TODO: move it away from here - this.readThreads(); - }); + if (this.mounted) { + this.setState({ messages }, () => this.update()); + } else { + this.state.messages = messages; + } + // TODO: move it away from here + this.readThreads(); + }); } - } + }; reload = () => { this.count = 0; this.query(); - } + }; - readThreads = debounce(async() => { + readThreads = debounce(async () => { const { tmid } = this.props; if (tmid) { @@ -214,28 +200,29 @@ class ListContainer extends React.Component { // Do nothing } } - }, 300) + }, 300); - onEndReached = () => this.query() + onEndReached = () => this.query(); - onRefresh = () => this.setState({ refreshing: true }, async() => { - const { messages } = this.state; - const { rid, tmid } = this.props; + onRefresh = () => + this.setState({ refreshing: true }, async () => { + const { messages } = this.state; + const { rid, tmid } = this.props; - if (messages.length) { - try { - if (tmid) { - await RocketChat.loadThreadMessages({ tmid, rid }); - } else { - await RocketChat.loadMissedMessages({ rid, lastOpen: moment().subtract(7, 'days').toDate() }); + if (messages.length) { + try { + if (tmid) { + await RocketChat.loadThreadMessages({ tmid, rid }); + } else { + await RocketChat.loadMissedMessages({ rid, lastOpen: moment().subtract(7, 'days').toDate() }); + } + } catch (e) { + log(e); } - } catch (e) { - log(e); } - } - this.setState({ refreshing: false }); - }) + this.setState({ refreshing: false }); + }); update = () => { if (this.animated) { @@ -248,7 +235,7 @@ class ListContainer extends React.Component { if (this.messagesSubscription && this.messagesSubscription.unsubscribe) { this.messagesSubscription.unsubscribe(); } - } + }; getLastMessage = () => { const { messages } = this.state; @@ -256,52 +243,53 @@ class ListContainer extends React.Component { return messages[0]; } return null; - } + }; - handleScrollToIndexFailed = (params) => { + handleScrollToIndexFailed = params => { const { listRef } = this.props; listRef.current.getNode().scrollToIndex({ index: params.highestMeasuredFrameIndex, animated: false }); - } + }; - jumpToMessage = messageId => new Promise(async(resolve) => { - this.jumping = true; - const { messages } = this.state; - const { listRef } = this.props; - const index = messages.findIndex(item => item.id === messageId); - if (index > -1) { - listRef.current.getNode().scrollToIndex({ index, viewPosition: 0.5, viewOffset: 100 }); - await new Promise(res => setTimeout(res, 300)); - if (!this.viewableItems.map(vi => vi.key).includes(messageId)) { + jumpToMessage = messageId => + new Promise(async resolve => { + this.jumping = true; + const { messages } = this.state; + const { listRef } = this.props; + const index = messages.findIndex(item => item.id === messageId); + if (index > -1) { + listRef.current.getNode().scrollToIndex({ index, viewPosition: 0.5, viewOffset: 100 }); + await new Promise(res => setTimeout(res, 300)); + if (!this.viewableItems.map(vi => vi.key).includes(messageId)) { + if (!this.jumping) { + return resolve(); + } + await setTimeout(() => resolve(this.jumpToMessage(messageId)), 300); + return; + } + this.setState({ highlightedMessage: messageId }); + this.clearHighlightedMessageTimeout(); + this.highlightedMessageTimeout = setTimeout(() => { + this.setState({ highlightedMessage: null }); + }, 10000); + await setTimeout(() => resolve(), 300); + } else { + listRef.current.getNode().scrollToIndex({ index: messages.length - 1, animated: false }); if (!this.jumping) { return resolve(); } await setTimeout(() => resolve(this.jumpToMessage(messageId)), 300); - return; } - this.setState({ highlightedMessage: messageId }); - this.clearHighlightedMessageTimeout(); - this.highlightedMessageTimeout = setTimeout(() => { - this.setState({ highlightedMessage: null }); - }, 10000); - await setTimeout(() => resolve(), 300); - } else { - listRef.current.getNode().scrollToIndex({ index: messages.length - 1, animated: false }); - if (!this.jumping) { - return resolve(); - } - await setTimeout(() => resolve(this.jumpToMessage(messageId)), 300); - } - }); + }); // this.jumping is checked in between operations to make sure we're not stuck cancelJumpToMessage = () => { this.jumping = false; - } + }; jumpToBottom = () => { const { listRef } = this.props; listRef.current.getNode().scrollToOffset({ offset: -100 }); - } + }; renderFooter = () => { const { rid, theme, loading } = this.props; @@ -309,20 +297,20 @@ class ListContainer extends React.Component { return <ActivityIndicator theme={theme} />; } return null; - } + }; renderItem = ({ item, index }) => { const { messages, highlightedMessage } = this.state; const { renderRow } = this.props; return renderRow(item, messages[index + 1], highlightedMessage); - } + }; onViewableItemsChanged = ({ viewableItems }) => { this.viewableItems = viewableItems; - } + }; render() { - console.count(`${ this.constructor.name }.render calls`); + console.count(`${this.constructor.name}.render calls`); const { rid, tmid, listRef } = this.props; const { messages, refreshing } = this.state; const { theme } = this.props; @@ -340,13 +328,9 @@ class ListContainer extends React.Component { onScrollToIndexFailed={this.handleScrollToIndexFailed} onViewableItemsChanged={this.onViewableItemsChanged} viewabilityConfig={this.viewabilityConfig} - refreshControl={( - <RefreshControl - refreshing={refreshing} - onRefresh={this.onRefresh} - tintColor={themes[theme].auxiliaryText} - /> - )} + refreshControl={ + <RefreshControl refreshing={refreshing} onRefresh={this.onRefresh} tintColor={themes[theme].auxiliaryText} /> + } /> <NavBottomFAB y={this.y} onPress={this.jumpToBottom} isThread={!!tmid} /> </> diff --git a/app/views/RoomView/LoadMore/LoadMore.stories.js b/app/views/RoomView/LoadMore/LoadMore.stories.js index 1f110a9cf..eb719646b 100644 --- a/app/views/RoomView/LoadMore/LoadMore.stories.js +++ b/app/views/RoomView/LoadMore/LoadMore.stories.js @@ -3,14 +3,16 @@ import React from 'react'; import { ScrollView } from 'react-native'; import { storiesOf } from '@storybook/react-native'; -import LoadMore from './index'; import { longText } from '../../../../storybook/utils'; import { ThemeContext } from '../../../theme'; -import { - Message, StoryProvider, MessageDecorator -} from '../../../../storybook/stories/Message'; +import { Message, MessageDecorator, StoryProvider } from '../../../../storybook/stories/Message'; import { themes } from '../../../constants/colors'; -import { MESSAGE_TYPE_LOAD_MORE, MESSAGE_TYPE_LOAD_NEXT_CHUNK, MESSAGE_TYPE_LOAD_PREVIOUS_CHUNK } from '../../../constants/messageTypeLoad'; +import { + MESSAGE_TYPE_LOAD_MORE, + MESSAGE_TYPE_LOAD_NEXT_CHUNK, + MESSAGE_TYPE_LOAD_PREVIOUS_CHUNK +} from '../../../constants/messageTypeLoad'; +import LoadMore from './index'; const stories = storiesOf('LoadMore', module); @@ -27,9 +29,7 @@ stories.add('basic', () => ( )); const ThemeStory = ({ theme }) => ( - <ThemeContext.Provider - value={{ theme }} - > + <ThemeContext.Provider value={{ theme }}> <ScrollView style={{ backgroundColor: themes[theme].backgroundColor }}> <LoadMore load={load} type={MESSAGE_TYPE_LOAD_PREVIOUS_CHUNK} /> <Message msg='Hey!' theme={theme} /> @@ -59,4 +59,3 @@ stories .addDecorator(StoryProvider) .addDecorator(MessageDecorator) .add('black theme', () => <ThemeStory theme='black' />); - diff --git a/app/views/RoomView/LoadMore/index.js b/app/views/RoomView/LoadMore/index.js index 04b922835..3c87461d5 100644 --- a/app/views/RoomView/LoadMore/index.js +++ b/app/views/RoomView/LoadMore/index.js @@ -1,5 +1,5 @@ -import React, { useEffect, useCallback, useState } from 'react'; -import { Text, StyleSheet, ActivityIndicator } from 'react-native'; +import React, { useCallback, useEffect, useState } from 'react'; +import { ActivityIndicator, StyleSheet, Text } from 'react-native'; import PropTypes from 'prop-types'; import { themes } from '../../../constants/colors'; @@ -25,7 +25,7 @@ const LoadMore = ({ load, type, runOnRender }) => { const { theme } = useTheme(); const [loading, setLoading] = useState(false); - const handleLoad = useCallback(async() => { + const handleLoad = useCallback(async () => { try { if (loading) { return; @@ -52,17 +52,12 @@ const LoadMore = ({ load, type, runOnRender }) => { } return ( - <Touch - onPress={handleLoad} - style={styles.button} - theme={theme} - enabled={!loading} - > - { - loading - ? <ActivityIndicator color={themes[theme].auxiliaryText} /> - : <Text style={[styles.text, { color: themes[theme].titleText }]}>{I18n.t(text)}</Text> - } + <Touch onPress={handleLoad} style={styles.button} theme={theme} enabled={!loading}> + {loading ? ( + <ActivityIndicator color={themes[theme].auxiliaryText} /> + ) : ( + <Text style={[styles.text, { color: themes[theme].titleText }]}>{I18n.t(text)}</Text> + )} </Touch> ); }; diff --git a/app/views/RoomView/ReactionPicker.js b/app/views/RoomView/ReactionPicker.js index 9e9c470f8..892b2e43a 100644 --- a/app/views/RoomView/ReactionPicker.js +++ b/app/views/RoomView/ReactionPicker.js @@ -5,10 +5,10 @@ import { connect } from 'react-redux'; import Modal from 'react-native-modal'; import EmojiPicker from '../../containers/EmojiPicker'; -import styles from './styles'; import { isAndroid } from '../../utils/deviceInfo'; import { themes } from '../../constants/colors'; import { withTheme } from '../../theme'; +import styles from './styles'; const margin = isAndroid ? 40 : 20; const maxSize = 400; @@ -37,52 +37,45 @@ class ReactionPicker extends React.Component { // to set reactions, we need shortname type const { onEmojiSelected, message } = this.props; onEmojiSelected(shortname || emoji, message.id); - } + }; render() { - const { - width, height, show, baseUrl, reactionClose, isMasterDetail, theme - } = this.props; + const { width, height, show, baseUrl, reactionClose, isMasterDetail, theme } = this.props; let widthStyle = width - margin; - let heightStyle = Math.min(width, height) - (margin * 2); + let heightStyle = Math.min(width, height) - margin * 2; if (isMasterDetail) { widthStyle = maxSize; heightStyle = maxSize; } - return (show - ? ( - <Modal - isVisible={show} - style={{ alignItems: 'center' }} - onBackdropPress={reactionClose} - onBackButtonPress={reactionClose} - animationIn='fadeIn' - animationOut='fadeOut' - backdropOpacity={themes[theme].backdropOpacity} - > - <View - style={[ - styles.reactionPickerContainer, - { - width: widthStyle, - height: heightStyle - } - ]} - testID='reaction-picker' - > - <EmojiPicker - // tabEmojiStyle={tabEmojiStyle} - onEmojiSelected={this.onEmojiSelected} - baseUrl={baseUrl} - /> - </View> - </Modal> - ) - : null - ); + return show ? ( + <Modal + isVisible={show} + style={{ alignItems: 'center' }} + onBackdropPress={reactionClose} + onBackButtonPress={reactionClose} + animationIn='fadeIn' + animationOut='fadeOut' + backdropOpacity={themes[theme].backdropOpacity}> + <View + style={[ + styles.reactionPickerContainer, + { + width: widthStyle, + height: heightStyle + } + ]} + testID='reaction-picker'> + <EmojiPicker + // tabEmojiStyle={tabEmojiStyle} + onEmojiSelected={this.onEmojiSelected} + baseUrl={baseUrl} + /> + </View> + </Modal> + ) : null; } } diff --git a/app/views/RoomView/RightButtons.js b/app/views/RoomView/RightButtons.js index 5b283b4ad..70e18bd68 100644 --- a/app/views/RoomView/RightButtons.js +++ b/app/views/RoomView/RightButtons.js @@ -6,7 +6,7 @@ import { dequal } from 'dequal'; import * as HeaderButton from '../../containers/HeaderButton'; import database from '../../lib/database'; import { getUserSelector } from '../../selectors/login'; -import { logEvent, events } from '../../utils/log'; +import { events, logEvent } from '../../utils/log'; import { isTeamRoom } from '../../utils/room'; class RightButtonsContainer extends Component { @@ -41,7 +41,7 @@ class RightButtonsContainer extends Component { const threadRecord = await db.get('messages').find(tmid); this.observeThread(threadRecord); } catch (e) { - console.log('Can\'t find message to observe.'); + console.log("Can't find message to observe."); } } if (rid) { @@ -50,15 +50,13 @@ class RightButtonsContainer extends Component { const subRecord = await subCollection.find(rid); this.observeSubscription(subRecord); } catch (e) { - console.log('Can\'t find subscription to observe.'); + console.log("Can't find subscription to observe."); } } } shouldComponentUpdate(nextProps, nextState) { - const { - isFollowingThread, tunread, tunreadUser, tunreadGroup - } = this.state; + const { isFollowingThread, tunread, tunreadUser, tunreadGroup } = this.state; const { teamId } = this.props; if (nextProps.teamId !== teamId) { return true; @@ -87,40 +85,36 @@ class RightButtonsContainer extends Component { } } - observeThread = (threadRecord) => { + observeThread = threadRecord => { const threadObservable = threadRecord.observe(); - this.threadSubscription = threadObservable - .subscribe(thread => this.updateThread(thread)); - } + this.threadSubscription = threadObservable.subscribe(thread => this.updateThread(thread)); + }; - updateThread = (thread) => { + updateThread = thread => { const { userId } = this.props; this.setState({ isFollowingThread: thread.replies && !!thread.replies.find(t => t === userId) }); - } + }; - observeSubscription = (subRecord) => { + observeSubscription = subRecord => { const subObservable = subRecord.observe(); - this.subSubscription = subObservable - .subscribe((sub) => { - this.updateSubscription(sub); - }); - } + this.subSubscription = subObservable.subscribe(sub => { + this.updateSubscription(sub); + }); + }; - updateSubscription = (sub) => { + updateSubscription = sub => { this.setState({ tunread: sub?.tunread, tunreadUser: sub?.tunreadUser, tunreadGroup: sub?.tunreadGroup }); - } + }; goTeamChannels = () => { logEvent(events.ROOM_GO_TEAM_CHANNELS); - const { - navigation, isMasterDetail, teamId - } = this.props; + const { navigation, isMasterDetail, teamId } = this.props; if (isMasterDetail) { navigation.navigate('ModalStackNavigator', { screen: 'TeamChannelsView', @@ -129,31 +123,27 @@ class RightButtonsContainer extends Component { } else { navigation.navigate('TeamChannelsView', { teamId }); } - } + }; goThreadsView = () => { logEvent(events.ROOM_GO_THREADS); - const { - rid, t, navigation, isMasterDetail - } = this.props; + const { rid, t, navigation, isMasterDetail } = this.props; if (isMasterDetail) { navigation.navigate('ModalStackNavigator', { screen: 'ThreadMessagesView', params: { rid, t } }); } else { navigation.navigate('ThreadMessagesView', { rid, t }); } - } + }; goSearchView = () => { logEvent(events.ROOM_GO_SEARCH); - const { - rid, t, navigation, isMasterDetail - } = this.props; + const { rid, t, navigation, isMasterDetail } = this.props; if (isMasterDetail) { navigation.navigate('ModalStackNavigator', { screen: 'SearchMessagesView', params: { rid, showCloseModal: true } }); } else { navigation.navigate('SearchMessagesView', { rid, t }); } - } + }; toggleFollowThread = () => { logEvent(events.ROOM_TOGGLE_FOLLOW_THREADS); @@ -162,15 +152,11 @@ class RightButtonsContainer extends Component { if (toggleFollowThread) { toggleFollowThread(isFollowingThread); } - } + }; render() { - const { - isFollowingThread, tunread, tunreadUser, tunreadGroup - } = this.state; - const { - t, tmid, threadsEnabled, teamId, joined - } = this.props; + const { isFollowingThread, tunread, tunreadUser, tunreadGroup } = this.state; + const { t, tmid, threadsEnabled, teamId, joined } = this.props; if (t === 'l') { return null; } @@ -188,31 +174,17 @@ class RightButtonsContainer extends Component { return ( <HeaderButton.Container> {isTeamRoom({ teamId, joined }) ? ( - <HeaderButton.Item - iconName='channel-public' - onPress={this.goTeamChannels} - testID='room-view-header-team-channels' - /> + <HeaderButton.Item iconName='channel-public' onPress={this.goTeamChannels} testID='room-view-header-team-channels' /> ) : null} {threadsEnabled ? ( <HeaderButton.Item iconName='threads' onPress={this.goThreadsView} testID='room-view-header-threads' - badge={() => ( - <HeaderButton.Badge - tunread={tunread} - tunreadUser={tunreadUser} - tunreadGroup={tunreadGroup} - /> - )} + badge={() => <HeaderButton.Badge tunread={tunread} tunreadUser={tunreadUser} tunreadGroup={tunreadGroup} />} /> ) : null} - <HeaderButton.Item - iconName='search' - onPress={this.goSearchView} - testID='room-view-search' - /> + <HeaderButton.Item iconName='search' onPress={this.goSearchView} testID='room-view-search' /> </HeaderButton.Container> ); } diff --git a/app/views/RoomView/Separator.js b/app/views/RoomView/Separator.js index c705f1fe5..b992f2560 100644 --- a/app/views/RoomView/Separator.js +++ b/app/views/RoomView/Separator.js @@ -1,5 +1,5 @@ import React from 'react'; -import { View, StyleSheet, Text } from 'react-native'; +import { StyleSheet, Text, View } from 'react-native'; import PropTypes from 'prop-types'; import moment from 'moment'; diff --git a/app/views/RoomView/UploadProgress.js b/app/views/RoomView/UploadProgress.js index ab17efc8b..9fc5595bf 100644 --- a/app/views/RoomView/UploadProgress.js +++ b/app/views/RoomView/UploadProgress.js @@ -1,7 +1,5 @@ import React, { Component } from 'react'; -import { - View, Text, StyleSheet, TouchableOpacity, ScrollView -} from 'react-native'; +import { ScrollView, StyleSheet, Text, TouchableOpacity, View } from 'react-native'; import PropTypes from 'prop-types'; import { Q } from '@nozbe/watermelondb'; @@ -64,7 +62,7 @@ class UploadProgress extends Component { token: PropTypes.string.isRequired }), baseUrl: PropTypes.string.isRequired - } + }; constructor(props) { super(props); @@ -88,37 +86,33 @@ class UploadProgress extends Component { init = () => { const { rid } = this.props; - if (!rid) { return; } + if (!rid) { + return; + } const db = database.active; - this.uploadsObservable = db.collections - .get('uploads') - .query( - Q.where('rid', rid) - ) - .observeWithColumns(['progress', 'error']); + this.uploadsObservable = db.collections.get('uploads').query(Q.where('rid', rid)).observeWithColumns(['progress', 'error']); - this.uploadsSubscription = this.uploadsObservable - .subscribe((uploads) => { - if (this.mounted) { - this.setState({ uploads }); - } else { - this.state.uploads = uploads; - } - if (!this.ranInitialUploadCheck) { - this.uploadCheck(); - } - }); - } + this.uploadsSubscription = this.uploadsObservable.subscribe(uploads => { + if (this.mounted) { + this.setState({ uploads }); + } else { + this.state.uploads = uploads; + } + if (!this.ranInitialUploadCheck) { + this.uploadCheck(); + } + }); + }; uploadCheck = () => { this.ranInitialUploadCheck = true; const { uploads } = this.state; - uploads.forEach(async(u) => { + uploads.forEach(async u => { if (!RocketChat.isUploadActive(u.path)) { try { const db = database.active; - await db.action(async() => { + await db.action(async () => { await u.update(() => { u.error = true; }); @@ -128,33 +122,33 @@ class UploadProgress extends Component { } } }); - } + }; - deleteUpload = async(item) => { + deleteUpload = async item => { try { const db = database.active; - await db.action(async() => { + await db.action(async () => { await item.destroyPermanently(); }); } catch (e) { log(e); } - } + }; - cancelUpload = async(item) => { + cancelUpload = async item => { try { await RocketChat.cancelUpload(item); } catch (e) { log(e); } - } + }; - tryAgain = async(item) => { + tryAgain = async item => { const { rid, baseUrl: server, user } = this.props; try { const db = database.active; - await db.action(async() => { + await db.action(async () => { await item.update(() => { item.error = false; }); @@ -163,30 +157,35 @@ class UploadProgress extends Component { } catch (e) { log(e); } - } + }; - renderItemContent = (item) => { + renderItemContent = item => { const { width, theme } = this.props; if (!item.error) { - return ( - [ - <View key='row' style={styles.row}> - <CustomIcon name='attach' size={20} color={themes[theme].auxiliaryText} /> - <Text style={[styles.descriptionContainer, styles.descriptionText, { color: themes[theme].auxiliaryText }]} numberOfLines={1}> - {I18n.t('Uploading')} {item.name} - </Text> - <CustomIcon name='close' size={20} color={themes[theme].auxiliaryText} onPress={() => this.cancelUpload(item)} /> - </View>, - <View key='progress' style={[styles.progress, { width: (width * item.progress) / 100, backgroundColor: themes[theme].tintColor }]} /> - ] - ); + return [ + <View key='row' style={styles.row}> + <CustomIcon name='attach' size={20} color={themes[theme].auxiliaryText} /> + <Text + style={[styles.descriptionContainer, styles.descriptionText, { color: themes[theme].auxiliaryText }]} + numberOfLines={1}> + {I18n.t('Uploading')} {item.name} + </Text> + <CustomIcon name='close' size={20} color={themes[theme].auxiliaryText} onPress={() => this.cancelUpload(item)} /> + </View>, + <View + key='progress' + style={[styles.progress, { width: (width * item.progress) / 100, backgroundColor: themes[theme].tintColor }]} + /> + ]; } return ( <View style={styles.row}> <CustomIcon name='warning' size={20} color={themes[theme].dangerColor} /> <View style={styles.descriptionContainer}> - <Text style={[styles.descriptionText, { color: themes[theme].auxiliaryText }]} numberOfLines={1}>{I18n.t('Error_uploading')} {item.name}</Text> + <Text style={[styles.descriptionText, { color: themes[theme].auxiliaryText }]} numberOfLines={1}> + {I18n.t('Error_uploading')} {item.name} + </Text> <TouchableOpacity onPress={() => this.tryAgain(item)}> <Text style={[styles.tryAgainButtonText, { color: themes[theme].tintColor }]}>{I18n.t('Try_again')}</Text> </TouchableOpacity> @@ -194,7 +193,7 @@ class UploadProgress extends Component { <CustomIcon name='close' size={20} color={themes[theme].auxiliaryText} onPress={() => this.deleteUpload(item)} /> </View> ); - } + }; // TODO: transform into stateless and update based on its own observable changes renderItem = (item, index) => { @@ -210,20 +209,15 @@ class UploadProgress extends Component { backgroundColor: themes[theme].chatComponentBackground, borderColor: themes[theme].borderColor } - ]} - > + ]}> {this.renderItemContent(item)} </View> ); - } + }; render() { const { uploads } = this.state; - return ( - <ScrollView style={styles.container}> - {uploads.map((item, i) => this.renderItem(item, i))} - </ScrollView> - ); + return <ScrollView style={styles.container}>{uploads.map((item, i) => this.renderItem(item, i))}</ScrollView>; } } diff --git a/app/views/RoomView/index.js b/app/views/RoomView/index.js index 2f1b128ed..3aa389467 100644 --- a/app/views/RoomView/index.js +++ b/app/views/RoomView/index.js @@ -1,9 +1,8 @@ import React from 'react'; import PropTypes from 'prop-types'; -import { Text, View, InteractionManager } from 'react-native'; +import { InteractionManager, Text, View } from 'react-native'; import { connect } from 'react-redux'; import parse from 'url-parse'; - import moment from 'moment'; import * as Haptics from 'expo-haptics'; import { Q } from '@nozbe/watermelondb'; @@ -11,65 +10,60 @@ import { dequal } from 'dequal'; import { withSafeAreaInsets } from 'react-native-safe-area-context'; import Touch from '../../utils/touch'; -import { - replyBroadcast as replyBroadcastAction -} from '../../actions/messages'; -import List from './List'; +import { replyBroadcast as replyBroadcastAction } from '../../actions/messages'; import database from '../../lib/database'; import RocketChat from '../../lib/rocketchat'; import Message from '../../containers/message'; import MessageActions from '../../containers/MessageActions'; import MessageErrorActions from '../../containers/MessageErrorActions'; import MessageBox from '../../containers/MessageBox'; -import ReactionPicker from './ReactionPicker'; -import UploadProgress from './UploadProgress'; -import JoinCode from './JoinCode'; -import styles from './styles'; -import log, { logEvent, events } from '../../utils/log'; +import log, { events, logEvent } from '../../utils/log'; import EventEmitter from '../../utils/events'; import I18n from '../../i18n'; import RoomHeader from '../../containers/RoomHeader'; -import LeftButtons from './LeftButtons'; -import RightButtons from './RightButtons'; import StatusBar from '../../containers/StatusBar'; -import Separator from './Separator'; import { themes } from '../../constants/colors'; import { MESSAGE_TYPE_ANY_LOAD, MESSAGE_TYPE_LOAD_MORE } from '../../constants/messageTypeLoad'; import debounce from '../../utils/debounce'; import ReactionsModal from '../../containers/ReactionsModal'; import { LISTENER } from '../../containers/Toast'; -import { - getBadgeColor, isBlocked, makeThreadName, isTeamRoom -} from '../../utils/room'; +import { getBadgeColor, isBlocked, isTeamRoom, makeThreadName } from '../../utils/room'; import { isReadOnly } from '../../utils/isReadOnly'; import { isIOS, isTablet } from '../../utils/deviceInfo'; import { showErrorAlert } from '../../utils/info'; import { withTheme } from '../../theme'; import { KEY_COMMAND, - handleCommandScroll, + handleCommandReplyLatest, handleCommandRoomActions, - handleCommandSearchMessages, - handleCommandReplyLatest + handleCommandScroll, + handleCommandSearchMessages } from '../../commands'; import { Review } from '../../utils/review'; import RoomClass from '../../lib/methods/subscriptions/room'; import { getUserSelector } from '../../selectors/login'; import { CONTAINER_TYPES } from '../../lib/methods/actions'; -import Banner from './Banner'; import Navigation from '../../lib/Navigation'; import SafeAreaView from '../../containers/SafeAreaView'; import { withDimensions } from '../../dimensions'; import { getHeaderTitlePosition } from '../../containers/Header'; import { E2E_MESSAGE_TYPE, E2E_STATUS } from '../../lib/encryption/constants'; - import { takeInquiry } from '../../ee/omnichannel/lib'; import Loading from '../../containers/Loading'; -import LoadMore from './LoadMore'; -import RoomServices from './services'; import { goRoom } from '../../utils/goRoom'; import getThreadName from '../../lib/methods/getThreadName'; import getRoomInfo from '../../lib/methods/getRoomInfo'; +import RoomServices from './services'; +import LoadMore from './LoadMore'; +import Banner from './Banner'; +import Separator from './Separator'; +import RightButtons from './RightButtons'; +import LeftButtons from './LeftButtons'; +import styles from './styles'; +import JoinCode from './JoinCode'; +import UploadProgress from './UploadProgress'; +import ReactionPicker from './ReactionPicker'; +import List from './List'; const stateAttrsUpdate = [ 'joined', @@ -85,7 +79,28 @@ const stateAttrsUpdate = [ 'member', 'showingBlockingLoader' ]; -const roomAttrsUpdate = ['f', 'ro', 'blocked', 'blocker', 'archived', 'tunread', 'muted', 'ignored', 'jitsiTimeout', 'announcement', 'sysMes', 'topic', 'name', 'fname', 'roles', 'bannerClosed', 'visitor', 'joinCodeRequired', 'teamMain', 'teamId']; +const roomAttrsUpdate = [ + 'f', + 'ro', + 'blocked', + 'blocker', + 'archived', + 'tunread', + 'muted', + 'ignored', + 'jitsiTimeout', + 'announcement', + 'sysMes', + 'topic', + 'name', + 'fname', + 'roles', + 'bannerClosed', + 'visitor', + 'joinCodeRequired', + 'teamMain', + 'teamId' +]; class RoomView extends React.Component { static propTypes = { @@ -116,8 +131,8 @@ class RoomView extends React.Component { constructor(props) { super(props); - console.time(`${ this.constructor.name } init`); - console.time(`${ this.constructor.name } mount`); + console.time(`${this.constructor.name} init`); + console.time(`${this.constructor.name} mount`); this.rid = props.route.params?.rid; this.t = props.route.params?.t; this.tmid = props.route.params?.tmid; @@ -126,7 +141,11 @@ class RoomView extends React.Component { const fname = props.route.params?.fname; const prid = props.route.params?.prid; const room = props.route.params?.room ?? { - rid: this.rid, t: this.t, name, fname, prid + rid: this.rid, + t: this.t, + name, + fname, + prid }; this.jumpToMessageId = props.route.params?.jumpToMessageId; const roomUserId = props.route.params?.roomUserId ?? RocketChat.getUidDirectMessage(room); @@ -169,7 +188,7 @@ class RoomView extends React.Component { if (this.rid && !this.tmid) { this.sub = new RoomClass(this.rid); } - console.timeEnd(`${ this.constructor.name } init`); + console.timeEnd(`${this.constructor.name} init`); } componentDidMount() { @@ -197,15 +216,13 @@ class RoomView extends React.Component { EventEmitter.addEventListener(KEY_COMMAND, this.handleCommands); } EventEmitter.addEventListener('ROOM_REMOVED', this.handleRoomRemoved); - console.timeEnd(`${ this.constructor.name } mount`); + console.timeEnd(`${this.constructor.name} mount`); } shouldComponentUpdate(nextProps, nextState) { const { state } = this; const { roomUpdate, member } = state; - const { - appState, theme, insets, route - } = this.props; + const { appState, theme, insets, route } = this.props; if (theme !== nextProps.theme) { return true; } @@ -254,10 +271,16 @@ class RoomView extends React.Component { this.setHeader(); } } - if ((roomUpdate.teamMain !== prevState.roomUpdate.teamMain) || (roomUpdate.teamId !== prevState.roomUpdate.teamId)) { + if (roomUpdate.teamMain !== prevState.roomUpdate.teamMain || roomUpdate.teamId !== prevState.roomUpdate.teamId) { this.setHeader(); } - if (((roomUpdate.fname !== prevState.roomUpdate.fname) || (roomUpdate.name !== prevState.roomUpdate.name) || (roomUpdate.teamMain !== prevState.roomUpdate.teamMain) || (roomUpdate.teamId !== prevState.roomUpdate.teamId)) && !this.tmid) { + if ( + (roomUpdate.fname !== prevState.roomUpdate.fname || + roomUpdate.name !== prevState.roomUpdate.name || + roomUpdate.teamMain !== prevState.roomUpdate.teamMain || + roomUpdate.teamId !== prevState.roomUpdate.teamId) && + !this.tmid + ) { this.setHeader(); } if (insets.left !== prevProps.insets.left || insets.right !== prevProps.insets.right) { @@ -285,8 +308,8 @@ class RoomView extends React.Component { } if (obj) { try { - await db.action(async() => { - await obj.update((r) => { + await db.action(async () => { + await obj.update(r => { r.draftMessage = text; }); }); @@ -313,7 +336,7 @@ class RoomView extends React.Component { EventEmitter.removeListener(KEY_COMMAND, this.handleCommands); } EventEmitter.removeListener('ROOM_REMOVED', this.handleRoomRemoved); - console.countReset(`${ this.constructor.name }.render calls`); + console.countReset(`${this.constructor.name}.render calls`); } get isOmnichannel() { @@ -322,12 +345,8 @@ class RoomView extends React.Component { } setHeader = () => { - const { - room, unreadsCount, roomUserId, joined - } = this.state; - const { - navigation, isMasterDetail, theme, baseUrl, user, insets, route - } = this.props; + const { room, unreadsCount, roomUserId, joined } = this.state; + const { navigation, isMasterDetail, theme, baseUrl, user, insets, route } = this.props; const { rid, tmid } = this; const prid = room?.prid; const isGroupChat = RocketChat.isGroupChat(room); @@ -394,7 +413,7 @@ class RoomView extends React.Component { visitor={visitor} isGroupChat={isGroupChat} onPress={this.goRoomActionsView} - testID={`room-view-title-${ title }`} + testID={`room-view-title-${title}`} /> ), headerRight: () => ( @@ -410,34 +429,43 @@ class RoomView extends React.Component { /> ) }); - } + }; - goRoomActionsView = (screen) => { + goRoomActionsView = screen => { logEvent(events.ROOM_GO_RA); - const { room, member } = this.state; + const { room, member, joined } = this.state; const { navigation, isMasterDetail } = this.props; if (isMasterDetail) { navigation.navigate('ModalStackNavigator', { screen: screen ?? 'RoomActionsView', params: { - rid: this.rid, t: this.t, room, member, showCloseModal: !!screen + rid: this.rid, + t: this.t, + room, + member, + showCloseModal: !!screen, + joined } }); } else { navigation.navigate('RoomActionsView', { - rid: this.rid, t: this.t, room, member + rid: this.rid, + t: this.t, + room, + member, + joined }); } - } + }; - setReadOnly = async() => { + setReadOnly = async () => { const { room } = this.state; const { user } = this.props; const readOnly = await isReadOnly(room, user); this.setState({ readOnly }); - } + }; - init = async() => { + init = async () => { try { this.setState({ loading: true }); const { room, joined } = this.state; @@ -471,9 +499,9 @@ class RoomView extends React.Component { }, 300); } } - } + }; - getRoomMember = async() => { + getRoomMember = async () => { const { room } = this.state; const { t } = room; @@ -492,9 +520,9 @@ class RoomView extends React.Component { } return {}; - } + }; - findAndObserveRoom = async(rid) => { + findAndObserveRoom = async rid => { try { const db = database.active; const subCollection = await db.get('subscriptions'); @@ -521,37 +549,36 @@ class RoomView extends React.Component { } } } - } + }; - unsubscribe = async() => { + unsubscribe = async () => { if (this.sub && this.sub.unsubscribe) { await this.sub.unsubscribe(); } delete this.sub; - } + }; - observeRoom = (room) => { + observeRoom = room => { const observable = room.observe(); - this.subSubscription = observable - .subscribe((changes) => { - const roomUpdate = roomAttrsUpdate.reduce((ret, attr) => { - ret[attr] = changes[attr]; - return ret; - }, {}); - if (this.mounted) { - this.internalSetState({ room: changes, roomUpdate }); - } else { - this.state.room = changes; - this.state.roomUpdate = roomUpdate; - } - }); - } + this.subSubscription = observable.subscribe(changes => { + const roomUpdate = roomAttrsUpdate.reduce((ret, attr) => { + ret[attr] = changes[attr]; + return ret; + }, {}); + if (this.mounted) { + this.internalSetState({ room: changes, roomUpdate }); + } else { + this.state.room = changes; + this.state.roomUpdate = roomUpdate; + } + }); + }; - errorActionsShow = (message) => { + errorActionsShow = message => { this.messageErrorActions?.showMessageErrorActions(message); - } + }; - onEditInit = (message) => { + onEditInit = message => { const newMessage = { id: message.id, subscription: { @@ -560,49 +587,51 @@ class RoomView extends React.Component { msg: message?.attachments?.[0]?.description || message.msg }; this.setState({ selectedMessage: newMessage, editing: true }); - } + }; onEditCancel = () => { this.setState({ selectedMessage: {}, editing: false }); - } + }; - onEditRequest = async(message) => { + onEditRequest = async message => { this.setState({ selectedMessage: {}, editing: false }); try { await RocketChat.editMessage(message); } catch (e) { log(e); } - } + }; onReplyInit = (message, mention) => { this.setState({ - selectedMessage: message, replying: true, replyWithMention: mention + selectedMessage: message, + replying: true, + replyWithMention: mention }); - } + }; onReplyCancel = () => { this.setState({ selectedMessage: {}, replying: false, replyWithMention: false }); - } + }; - onReactionInit = (message) => { + onReactionInit = message => { this.setState({ selectedMessage: message, reacting: true }); - } + }; onReactionClose = () => { this.setState({ selectedMessage: {}, reacting: false }); - } + }; - onMessageLongPress = (message) => { + onMessageLongPress = message => { this.messageActions?.showMessageActions(message); - } + }; - showAttachment = (attachment) => { + showAttachment = attachment => { const { navigation } = this.props; navigation.navigate('AttachmentView', { attachment }); - } + }; - onReactionPress = async(shortname, messageId) => { + onReactionPress = async (shortname, messageId) => { try { await RocketChat.setReaction(shortname, messageId); this.onReactionClose(); @@ -612,14 +641,14 @@ class RoomView extends React.Component { } }; - onReactionLongPress = (message) => { + onReactionLongPress = message => { this.setState({ selectedMessage: message, reactionsModalVisible: true }); Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light); - } + }; onCloseReactionsModal = () => { this.setState({ selectedMessage: {}, reactionsModalVisible: false }); - } + }; onEncryptedPress = () => { logEvent(events.ROOM_ENCRYPTED_PRESS); @@ -631,28 +660,31 @@ class RoomView extends React.Component { return navigation.navigate('ModalStackNavigator', screen); } navigation.navigate('E2ESaveYourPasswordStackNavigator', screen); - } + }; - onDiscussionPress = debounce((item) => { - const { navigation } = this.props; - navigation.push('RoomView', { - rid: item.drid, prid: item.rid, name: item.msg, t: 'p' - }); - }, 1000, true) + onDiscussionPress = debounce( + item => { + const { navigation } = this.props; + navigation.push('RoomView', { + rid: item.drid, + prid: item.rid, + name: item.msg, + t: 'p' + }); + }, + 1000, + true + ); // eslint-disable-next-line react/sort-comp - updateUnreadCount = async() => { + updateUnreadCount = async () => { const db = database.active; const observable = await db.collections .get('subscriptions') - .query( - Q.where('archived', false), - Q.where('open', true), - Q.where('rid', Q.notEq(this.rid)) - ) + .query(Q.where('archived', false), Q.where('open', true), Q.where('rid', Q.notEq(this.rid))) .observeWithColumns(['unread']); - this.queryUnreads = observable.subscribe((data) => { + this.queryUnreads = observable.subscribe(data => { const { unreadsCount } = this.state; const newUnreadsCount = data.filter(s => s.unread > 0).reduce((a, b) => a + (b.unread || 0), 0); if (unreadsCount !== newUnreadsCount) { @@ -661,9 +693,9 @@ class RoomView extends React.Component { }); }; - onThreadPress = debounce(item => this.navToThread(item), 1000, true) + onThreadPress = debounce(item => this.navToThread(item), 1000, true); - shouldNavigateToRoom = (message) => { + shouldNavigateToRoom = message => { if (message.tmid && message.tmid === this.tmid) { return false; } @@ -671,9 +703,9 @@ class RoomView extends React.Component { return false; } return true; - } + }; - jumpToMessageByUrl = async(messageUrl) => { + jumpToMessageByUrl = async messageUrl => { if (!messageUrl) { return; } @@ -687,9 +719,9 @@ class RoomView extends React.Component { this.setState({ showingBlockingLoader: false }); log(e); } - } + }; - jumpToMessage = async(messageId) => { + jumpToMessage = async messageId => { try { this.setState({ showingBlockingLoader: true }); const message = await RoomServices.getMessageInfo(messageId); @@ -712,10 +744,7 @@ class RoomView extends React.Component { if (message.fromServer && !message.tmid) { await RocketChat.loadSurroundingMessages({ messageId, rid: this.rid }); } - await Promise.race([ - this.list.current.jumpToMessage(message.id), - new Promise(res => setTimeout(res, 5000)) - ]); + await Promise.race([this.list.current.jumpToMessage(message.id), new Promise(res => setTimeout(res, 5000))]); this.list.current.cancelJumpToMessage(); } } catch (e) { @@ -723,32 +752,33 @@ class RoomView extends React.Component { } finally { this.setState({ showingBlockingLoader: false }); } - } + }; - replyBroadcast = (message) => { + replyBroadcast = message => { const { replyBroadcast } = this.props; replyBroadcast(message); - } + }; handleConnected = () => { this.init(); EventEmitter.removeListener('connected', this.handleConnected); - } + }; handleRoomRemoved = ({ rid }) => { const { room } = this.state; if (rid === this.rid) { Navigation.navigate('RoomsListView'); - !this.isOmnichannel && showErrorAlert(I18n.t('You_were_removed_from_channel', { channel: RocketChat.getRoomTitle(room) }), I18n.t('Oops')); + !this.isOmnichannel && + showErrorAlert(I18n.t('You_were_removed_from_channel', { channel: RocketChat.getRoomTitle(room) }), I18n.t('Oops')); } - } + }; internalSetState = (...args) => { if (!this.mounted) { return; } this.setState(...args); - } + }; sendMessage = (message, tmid, tshow) => { logEvent(events.ROOM_SEND_MESSAGE); @@ -762,14 +792,14 @@ class RoomView extends React.Component { }); }; - getCustomEmoji = (name) => { + getCustomEmoji = name => { const { customEmojis } = this.props; const emoji = customEmojis[name]; if (emoji) { return emoji; } return null; - } + }; setLastOpen = lastOpen => this.setState({ lastOpen }); @@ -777,9 +807,9 @@ class RoomView extends React.Component { this.internalSetState({ joined: true }); - } + }; - joinRoom = async() => { + joinRoom = async () => { logEvent(events.ROOM_JOIN); try { const { room } = this.state; @@ -799,28 +829,28 @@ class RoomView extends React.Component { } catch (e) { log(e); } - } + }; - getThreadName = (tmid, messageId) => getThreadName(this.rid, tmid, messageId) + getThreadName = (tmid, messageId) => getThreadName(this.rid, tmid, messageId); - toggleFollowThread = async(isFollowingThread, tmid) => { + toggleFollowThread = async (isFollowingThread, tmid) => { try { await RocketChat.toggleFollowMessage(tmid ?? this.tmid, !isFollowingThread); EventEmitter.emit(LISTENER, { message: isFollowingThread ? I18n.t('Unfollowed_thread') : I18n.t('Following_thread') }); } catch (e) { log(e); } - } + }; - getBadgeColor = (messageId) => { + getBadgeColor = messageId => { const { room } = this.state; const { theme } = this.props; return getBadgeColor({ subscription: room, theme, messageId }); - } + }; - navToRoomInfo = (navParam) => { + navToRoomInfo = navParam => { const { navigation, user, isMasterDetail } = this.props; - logEvent(events[`ROOM_GO_${ navParam.t === 'd' ? 'USER' : 'ROOM' }_INFO`]); + logEvent(events[`ROOM_GO_${navParam.t === 'd' ? 'USER' : 'ROOM'}_INFO`]); if (navParam.rid === user.id) { return; } @@ -830,9 +860,9 @@ class RoomView extends React.Component { } else { navigation.navigate('RoomInfoView', navParam); } - } + }; - navToThread = async(item) => { + navToThread = async item => { const { roomUserId } = this.state; const { navigation } = this.props; @@ -845,24 +875,36 @@ class RoomView extends React.Component { name = I18n.t('Encrypted_message'); } return navigation.push('RoomView', { - rid: this.rid, tmid: item.tmid, name, t: 'thread', roomUserId, jumpToMessageId: item.id + rid: this.rid, + tmid: item.tmid, + name, + t: 'thread', + roomUserId, + jumpToMessageId: item.id }); } if (item.tlm) { return navigation.push('RoomView', { - rid: this.rid, tmid: item.id, name: makeThreadName(item), t: 'thread', roomUserId + rid: this.rid, + tmid: item.id, + name: makeThreadName(item), + t: 'thread', + roomUserId }); } - } + }; - navToRoom = async(message) => { + navToRoom = async message => { const { navigation, isMasterDetail } = this.props; const roomInfo = await getRoomInfo(message.rid); return goRoom({ - item: roomInfo, isMasterDetail, navigationMethod: navigation.push, jumpToMessageId: message.id + item: roomInfo, + isMasterDetail, + navigationMethod: navigation.push, + jumpToMessageId: message.id }); - } + }; callJitsi = () => { const { room } = this.state; @@ -892,29 +934,28 @@ class RoomView extends React.Component { } } } - } + }; - blockAction = ({ - actionId, appId, value, blockId, rid, mid - }) => RocketChat.triggerBlockAction({ - blockId, - actionId, - value, - mid, - rid, - appId, - container: { - type: CONTAINER_TYPES.MESSAGE, - id: mid - } - }); + blockAction = ({ actionId, appId, value, blockId, rid, mid }) => + RocketChat.triggerBlockAction({ + blockId, + actionId, + value, + mid, + rid, + appId, + container: { + type: CONTAINER_TYPES.MESSAGE, + id: mid + } + }); - closeBanner = async() => { + closeBanner = async () => { const { room } = this.state; try { const db = database.active; - await db.action(async() => { - await room.update((r) => { + await db.action(async () => { + await room.update(r => { r.bannerClosed = true; }); }); @@ -923,20 +964,23 @@ class RoomView extends React.Component { } }; - isIgnored = (message) => { + isIgnored = message => { const { room } = this.state; return room?.ignored?.includes?.(message?.u?._id) ?? false; - } + }; - onLoadMoreMessages = loaderItem => RoomServices.getMoreMessages({ - rid: this.rid, tmid: this.tmid, t: this.t, loaderItem - }) + onLoadMoreMessages = loaderItem => + RoomServices.getMoreMessages({ + rid: this.rid, + tmid: this.tmid, + t: this.t, + loaderItem + }); renderItem = (item, previousItem, highlightedMessage) => { const { room, lastOpen, canAutoTranslate } = this.state; - const { - user, Message_GroupingPeriod, Message_TimeFormat, useRealName, baseUrl, Message_Read_Receipt_Enabled, theme - } = this.props; + const { user, Message_GroupingPeriod, Message_TimeFormat, useRealName, baseUrl, Message_Read_Receipt_Enabled, theme } = + this.props; let dateSeparator = null; let showUnreadSeparator = false; @@ -944,9 +988,7 @@ class RoomView extends React.Component { dateSeparator = item.ts; showUnreadSeparator = moment(item.ts).isAfter(lastOpen); } else { - showUnreadSeparator = lastOpen - && moment(item.ts).isSameOrAfter(lastOpen) - && moment(previousItem.ts).isBefore(lastOpen); + showUnreadSeparator = lastOpen && moment(item.ts).isSameOrAfter(lastOpen) && moment(previousItem.ts).isBefore(lastOpen); if (!moment(item.ts).isSame(previousItem.ts, 'day')) { dateSeparator = item.ts; } @@ -954,7 +996,13 @@ class RoomView extends React.Component { let content = null; if (MESSAGE_TYPE_ANY_LOAD.includes(item.t)) { - content = <LoadMore load={() => this.onLoadMoreMessages(item)} type={item.t} runOnRender={item.t === MESSAGE_TYPE_LOAD_MORE && !previousItem} />; + content = ( + <LoadMore + load={() => this.onLoadMoreMessages(item)} + type={item.t} + runOnRender={item.t === MESSAGE_TYPE_LOAD_MORE && !previousItem} + /> + ); } else { content = ( <Message @@ -1002,23 +1050,19 @@ class RoomView extends React.Component { return ( <> {content} - <Separator - ts={dateSeparator} - unread={showUnreadSeparator} - theme={theme} - /> + <Separator ts={dateSeparator} unread={showUnreadSeparator} theme={theme} /> </> ); } return content; - } + }; renderFooter = () => { - const { - joined, room, selectedMessage, editing, replying, replyWithMention, readOnly - } = this.state; - const { navigation, theme } = this.props; + const { joined, room, selectedMessage, editing, replying, replyWithMention, readOnly, loading } = this.state; + const { navigation, theme, route } = this.props; + + const usedCannedResponse = route?.params?.usedCannedResponse; if (!this.rid) { return null; @@ -1026,13 +1070,19 @@ class RoomView extends React.Component { if (!joined && !this.tmid) { return ( <View style={styles.joinRoomContainer} key='room-view-join' testID='room-view-join'> - <Text accessibilityLabel={I18n.t('You_are_in_preview_mode')} style={[styles.previewMode, { color: themes[theme].titleText }]}>{I18n.t('You_are_in_preview_mode')}</Text> + <Text + accessibilityLabel={I18n.t('You_are_in_preview_mode')} + style={[styles.previewMode, { color: themes[theme].titleText }]}> + {I18n.t('You_are_in_preview_mode')} + </Text> <Touch onPress={this.joinRoom} style={[styles.joinRoomButton, { backgroundColor: themes[theme].actionTintColor }]} - theme={theme} - > - <Text style={[styles.joinRoomText, { color: themes[theme].buttonText }]} testID='room-view-join-button'>{I18n.t(this.isOmnichannel ? 'Take_it' : 'Join')}</Text> + enabled={!loading} + theme={theme}> + <Text style={[styles.joinRoomText, { color: themes[theme].buttonText }]} testID='room-view-join-button'> + {I18n.t(this.isOmnichannel ? 'Take_it' : 'Join')} + </Text> </Touch> </View> ); @@ -1040,7 +1090,11 @@ class RoomView extends React.Component { if (readOnly) { return ( <View style={styles.readOnly}> - <Text style={[styles.previewMode, { color: themes[theme].titleText }]} accessibilityLabel={I18n.t('This_room_is_read_only')}>{I18n.t('This_room_is_read_only')}</Text> + <Text + style={[styles.previewMode, { color: themes[theme].titleText }]} + accessibilityLabel={I18n.t('This_room_is_read_only')}> + {I18n.t('This_room_is_read_only')} + </Text> </View> ); } @@ -1069,6 +1123,7 @@ class RoomView extends React.Component { replyCancel={this.onReplyCancel} getCustomEmoji={this.getCustomEmoji} navigation={navigation} + usedCannedResponse={usedCannedResponse} /> ); }; @@ -1079,7 +1134,7 @@ class RoomView extends React.Component { return ( <> <MessageActions - ref={ref => this.messageActions = ref} + ref={ref => (this.messageActions = ref)} tmid={this.tmid} room={room} user={user} @@ -1089,31 +1144,19 @@ class RoomView extends React.Component { onReactionPress={this.onReactionPress} isReadOnly={readOnly} /> - <MessageErrorActions - ref={ref => this.messageErrorActions = ref} - tmid={this.tmid} - /> + <MessageErrorActions ref={ref => (this.messageErrorActions = ref)} tmid={this.tmid} /> </> ); - } + }; render() { - console.count(`${ this.constructor.name }.render calls`); - const { - room, reactionsModalVisible, selectedMessage, loading, reacting, showingBlockingLoader - } = this.state; - const { - user, baseUrl, theme, navigation, Hide_System_Messages, width, height - } = this.props; - const { - rid, t, sysMes, bannerClosed, announcement - } = room; + console.count(`${this.constructor.name}.render calls`); + const { room, reactionsModalVisible, selectedMessage, loading, reacting, showingBlockingLoader } = this.state; + const { user, baseUrl, theme, navigation, Hide_System_Messages, width, height } = this.props; + const { rid, t, sysMes, bannerClosed, announcement } = room; return ( - <SafeAreaView - style={{ backgroundColor: themes[theme].backgroundColor }} - testID='room-view' - > + <SafeAreaView style={{ backgroundColor: themes[theme].backgroundColor }} testID='room-view'> <StatusBar /> <Banner rid={rid} @@ -1158,13 +1201,7 @@ class RoomView extends React.Component { onClose={this.onCloseReactionsModal} getCustomEmoji={this.getCustomEmoji} /> - <JoinCode - ref={this.joinCode} - onJoin={this.onJoin} - rid={rid} - t={t} - theme={theme} - /> + <JoinCode ref={this.joinCode} onJoin={this.onJoin} rid={rid} t={t} theme={theme} /> <Loading visible={showingBlockingLoader} /> </SafeAreaView> ); diff --git a/app/views/RoomView/services/getMessageInfo.js b/app/views/RoomView/services/getMessageInfo.js index f7f008c46..e922f4e10 100644 --- a/app/views/RoomView/services/getMessageInfo.js +++ b/app/views/RoomView/services/getMessageInfo.js @@ -2,7 +2,7 @@ import { getMessageById } from '../../../lib/database/services/Message'; import { getThreadMessageById } from '../../../lib/database/services/ThreadMessage'; import getSingleMessage from '../../../lib/methods/getSingleMessage'; -const getMessageInfo = async(messageId) => { +const getMessageInfo = async messageId => { let result; result = await getMessageById(messageId); if (result) { diff --git a/app/views/RoomView/services/getMessages.js b/app/views/RoomView/services/getMessages.js index 7e9c03de0..516f68845 100644 --- a/app/views/RoomView/services/getMessages.js +++ b/app/views/RoomView/services/getMessages.js @@ -1,6 +1,6 @@ import RocketChat from '../../../lib/rocketchat'; -const getMessages = (room) => { +const getMessages = room => { if (room.lastOpen) { return RocketChat.loadMissedMessages(room); } else { diff --git a/app/views/RoomView/services/getMoreMessages.js b/app/views/RoomView/services/getMoreMessages.js index 6d16f69c2..eaa1d3d90 100644 --- a/app/views/RoomView/services/getMoreMessages.js +++ b/app/views/RoomView/services/getMoreMessages.js @@ -1,18 +1,26 @@ -import { MESSAGE_TYPE_LOAD_MORE, MESSAGE_TYPE_LOAD_NEXT_CHUNK, MESSAGE_TYPE_LOAD_PREVIOUS_CHUNK } from '../../../constants/messageTypeLoad'; +import { + MESSAGE_TYPE_LOAD_MORE, + MESSAGE_TYPE_LOAD_NEXT_CHUNK, + MESSAGE_TYPE_LOAD_PREVIOUS_CHUNK +} from '../../../constants/messageTypeLoad'; import RocketChat from '../../../lib/rocketchat'; -const getMoreMessages = ({ - rid, t, tmid, loaderItem -}) => { +const getMoreMessages = ({ rid, t, tmid, loaderItem }) => { if ([MESSAGE_TYPE_LOAD_MORE, MESSAGE_TYPE_LOAD_PREVIOUS_CHUNK].includes(loaderItem.t)) { return RocketChat.loadMessagesForRoom({ - rid, t, latest: loaderItem.ts, loaderItem + rid, + t, + latest: loaderItem.ts, + loaderItem }); } if (loaderItem.t === MESSAGE_TYPE_LOAD_NEXT_CHUNK) { return RocketChat.loadNextMessages({ - rid, tmid, ts: loaderItem.ts, loaderItem + rid, + tmid, + ts: loaderItem.ts, + loaderItem }); } }; diff --git a/app/views/RoomsListView/Header/Header.js b/app/views/RoomsListView/Header/Header.js index 43fa4b83a..65aa36792 100644 --- a/app/views/RoomsListView/Header/Header.js +++ b/app/views/RoomsListView/Header/Header.js @@ -1,7 +1,5 @@ import React from 'react'; -import { - Text, View, TouchableOpacity, StyleSheet -} from 'react-native'; +import { StyleSheet, Text, TouchableOpacity, View } from 'react-native'; import PropTypes from 'prop-types'; import TextInput from '../../../presentation/TextInput'; @@ -9,7 +7,7 @@ import I18n from '../../../i18n'; import sharedStyles from '../../Styles'; import { themes } from '../../../constants/colors'; import { CustomIcon } from '../../../lib/Icons'; -import { isTablet, isIOS } from '../../../utils/deviceInfo'; +import { isIOS, isTablet } from '../../../utils/deviceInfo'; import { useOrientation } from '../../../dimensions'; const styles = StyleSheet.create({ @@ -33,60 +31,79 @@ const styles = StyleSheet.create({ } }); -const Header = React.memo(({ - connecting, connected, isFetching, serverName, server, showServerDropdown, showSearchHeader, theme, onSearchChangeText, onPress -}) => { - const titleColorStyle = { color: themes[theme].headerTitleColor }; - const isLight = theme === 'light'; - const { isLandscape } = useOrientation(); - const scale = isIOS && isLandscape && !isTablet ? 0.8 : 1; - const titleFontSize = 16 * scale; - const subTitleFontSize = 14 * scale; +const Header = React.memo( + ({ + connecting, + connected, + isFetching, + serverName, + server, + showServerDropdown, + showSearchHeader, + theme, + onSearchChangeText, + onPress + }) => { + const titleColorStyle = { color: themes[theme].headerTitleColor }; + const isLight = theme === 'light'; + const { isLandscape } = useOrientation(); + const scale = isIOS && isLandscape && !isTablet ? 0.8 : 1; + const titleFontSize = 16 * scale; + const subTitleFontSize = 14 * scale; - if (showSearchHeader) { + if (showSearchHeader) { + return ( + <View style={styles.container}> + <TextInput + autoFocus + style={[styles.title, isLight && titleColorStyle, { fontSize: titleFontSize }]} + placeholder='Search' + onChangeText={onSearchChangeText} + theme={theme} + testID='rooms-list-view-search-input' + /> + </View> + ); + } + let subtitle; + if (connecting) { + subtitle = I18n.t('Connecting'); + } else if (isFetching) { + subtitle = I18n.t('Updating'); + } else if (!connected) { + subtitle = I18n.t('Waiting_for_network'); + } else { + subtitle = server?.replace(/(^\w+:|^)\/\//, ''); + } return ( <View style={styles.container}> - <TextInput - autoFocus - style={[styles.title, isLight && titleColorStyle, { fontSize: titleFontSize }]} - placeholder='Search' - onChangeText={onSearchChangeText} - theme={theme} - testID='rooms-list-view-search-input' - /> + <TouchableOpacity onPress={onPress} testID='rooms-list-header-server-dropdown-button'> + <View style={styles.button}> + <Text + style={[styles.title, isFetching && styles.serverSmall, titleColorStyle, { fontSize: titleFontSize }]} + numberOfLines={1}> + {serverName} + </Text> + <CustomIcon + name='chevron-down' + color={themes[theme].headerTintColor} + style={[showServerDropdown && styles.upsideDown]} + size={18} + /> + </View> + {subtitle ? ( + <Text + testID='rooms-list-header-server-subtitle' + style={[styles.subtitle, { color: themes[theme].auxiliaryText, fontSize: subTitleFontSize }]} + numberOfLines={1}> + {subtitle} + </Text> + ) : null} + </TouchableOpacity> </View> ); } - let subtitle; - if (connecting) { - subtitle = I18n.t('Connecting'); - } else if (isFetching) { - subtitle = I18n.t('Updating'); - } else if (!connected) { - subtitle = I18n.t('Waiting_for_network'); - } else { - subtitle = server?.replace(/(^\w+:|^)\/\//, ''); - } - return ( - <View style={styles.container}> - <TouchableOpacity - onPress={onPress} - testID='rooms-list-header-server-dropdown-button' - > - <View style={styles.button}> - <Text style={[styles.title, isFetching && styles.serverSmall, titleColorStyle, { fontSize: titleFontSize }]} numberOfLines={1}>{serverName}</Text> - <CustomIcon - name='chevron-down' - color={themes[theme].headerTintColor} - style={[showServerDropdown && styles.upsideDown]} - size={18} - /> - </View> - {subtitle ? <Text testID='rooms-list-header-server-subtitle' style={[styles.subtitle, { color: themes[theme].auxiliaryText, fontSize: subTitleFontSize }]} numberOfLines={1}>{subtitle}</Text> : null} - </TouchableOpacity> - </View> - ); -}); +); Header.propTypes = { showServerDropdown: PropTypes.bool.isRequired, diff --git a/app/views/RoomsListView/Header/index.js b/app/views/RoomsListView/Header/index.js index 97244fd5b..4557b66dc 100644 --- a/app/views/RoomsListView/Header/index.js +++ b/app/views/RoomsListView/Header/index.js @@ -3,14 +3,17 @@ import PropTypes from 'prop-types'; import { connect } from 'react-redux'; import { - toggleServerDropdown, closeServerDropdown, closeSortDropdown, setSearch as setSearchAction + closeServerDropdown, + closeSortDropdown, + setSearch as setSearchAction, + toggleServerDropdown } from '../../../actions/rooms'; -import Header from './Header'; import { withTheme } from '../../../theme'; import EventEmitter from '../../../utils/events'; import { KEY_COMMAND, handleCommandOpenServerDropdown } from '../../../commands'; import { isTablet } from '../../../utils/deviceInfo'; -import { logEvent, events } from '../../../utils/log'; +import { events, logEvent } from '../../../utils/log'; +import Header from './Header'; class RoomsListHeaderView extends PureComponent { static propTypes = { @@ -27,7 +30,7 @@ class RoomsListHeaderView extends PureComponent { close: PropTypes.func, closeSort: PropTypes.func, setSearch: PropTypes.func - } + }; componentDidMount() { if (isTablet) { @@ -46,18 +49,16 @@ class RoomsListHeaderView extends PureComponent { if (handleCommandOpenServerDropdown(event)) { this.onPress(); } - } + }; - onSearchChangeText = (text) => { + onSearchChangeText = text => { const { setSearch } = this.props; setSearch(text.trim()); - } + }; onPress = () => { logEvent(events.RL_TOGGLE_SERVER_DROPDOWN); - const { - showServerDropdown, showSortDropdown, close, open, closeSort - } = this.props; + const { showServerDropdown, showSortDropdown, close, open, closeSort } = this.props; if (showServerDropdown) { close(); } else if (showSortDropdown) { @@ -68,12 +69,10 @@ class RoomsListHeaderView extends PureComponent { } else { open(); } - } + }; render() { - const { - serverName, showServerDropdown, showSearchHeader, connecting, connected, isFetching, theme, server - } = this.props; + const { serverName, showServerDropdown, showSearchHeader, connecting, connected, isFetching, theme, server } = this.props; return ( <Header diff --git a/app/views/RoomsListView/ListHeader/index.js b/app/views/RoomsListView/ListHeader/index.js index 85ffcfa9c..d7d4d2304 100644 --- a/app/views/RoomsListView/ListHeader/index.js +++ b/app/views/RoomsListView/ListHeader/index.js @@ -6,31 +6,19 @@ import I18n from '../../../i18n'; import * as List from '../../../containers/List'; import { E2E_BANNER_TYPE } from '../../../lib/encryption/constants'; import { themes } from '../../../constants/colors'; - import OmnichannelStatus from '../../../ee/omnichannel/containers/OmnichannelStatus'; -const ListHeader = React.memo(({ - searching, - sortBy, - toggleSort, - goEncryption, - goQueue, - queueSize, - inquiryEnabled, - encryptionBanner, - user, - theme -}) => { - const sortTitle = I18n.t('Sorting_by', { key: I18n.t(sortBy === 'alphabetical' ? 'name' : 'activity') }); +const ListHeader = React.memo( + ({ searching, sortBy, toggleSort, goEncryption, goQueue, queueSize, inquiryEnabled, encryptionBanner, user, theme }) => { + const sortTitle = I18n.t('Sorting_by', { key: I18n.t(sortBy === 'alphabetical' ? 'name' : 'activity') }); - if (searching) { - return null; - } + if (searching) { + return null; + } - return ( - <> - {encryptionBanner - ? ( + return ( + <> + {encryptionBanner ? ( <> <List.Item title={ @@ -47,26 +35,26 @@ const ListHeader = React.memo(({ /> <List.Separator /> </> - ) - : null} - <List.Item - title={sortTitle} - left={() => <List.Icon name='sort' />} - color={themes[theme].auxiliaryText} - onPress={toggleSort} - translateTitle={false} - /> - <List.Separator /> - <OmnichannelStatus - searching={searching} - goQueue={goQueue} - inquiryEnabled={inquiryEnabled} - queueSize={queueSize} - user={user} - /> - </> - ); -}); + ) : null} + <List.Item + title={sortTitle} + left={() => <List.Icon name='sort' />} + color={themes[theme].auxiliaryText} + onPress={toggleSort} + translateTitle={false} + /> + <List.Separator /> + <OmnichannelStatus + searching={searching} + goQueue={goQueue} + inquiryEnabled={inquiryEnabled} + queueSize={queueSize} + user={user} + /> + </> + ); + } +); ListHeader.propTypes = { searching: PropTypes.bool, diff --git a/app/views/RoomsListView/ServerDropdown.js b/app/views/RoomsListView/ServerDropdown.js index c27691aff..97b52d666 100644 --- a/app/views/RoomsListView/ServerDropdown.js +++ b/app/views/RoomsListView/ServerDropdown.js @@ -1,16 +1,14 @@ import React, { Component } from 'react'; -import { - View, Text, Animated, Easing, TouchableWithoutFeedback, TouchableOpacity, FlatList -} from 'react-native'; +import { View, Text, Animated, Easing, TouchableWithoutFeedback, TouchableOpacity, FlatList, Linking } from 'react-native'; import PropTypes from 'prop-types'; -import { connect, batch } from 'react-redux'; +import { batch, connect } from 'react-redux'; import { withSafeAreaInsets } from 'react-native-safe-area-context'; -import * as List from '../../containers/List'; +import * as List from '../../containers/List'; +import Button from '../../containers/Button'; import { toggleServerDropdown as toggleServerDropdownAction } from '../../actions/rooms'; import { selectServerRequest as selectServerRequestAction, serverInitAdd as serverInitAddAction } from '../../actions/server'; -import { appStart as appStartAction, ROOT_NEW_SERVER } from '../../actions/app'; -import styles from './styles'; +import { appStart as appStartAction, ROOT_OUTSIDE } from '../../actions/app'; import RocketChat from '../../lib/rocketchat'; import I18n from '../../i18n'; import EventEmitter from '../../utils/events'; @@ -22,10 +20,11 @@ import { KEY_COMMAND, handleCommandSelectServer } from '../../commands'; import { isTablet } from '../../utils/deviceInfo'; import { localAuthenticate } from '../../utils/localAuthentication'; import { showConfirmationAlert } from '../../utils/info'; -import { logEvent, events } from '../../utils/log'; +import log, { events, logEvent } from '../../utils/log'; import { headerHeight } from '../../containers/Header'; import { goRoom } from '../../utils/goRoom'; import UserPreferences from '../../lib/userPreferences'; +import styles from './styles'; const ROW_HEIGHT = 68; const ANIMATION_DURATION = 200; @@ -42,7 +41,7 @@ class ServerDropdown extends Component { toggleServerDropdown: PropTypes.func, selectServerRequest: PropTypes.func, initAdd: PropTypes.func - } + }; constructor(props) { super(props); @@ -52,24 +51,18 @@ class ServerDropdown extends Component { async componentDidMount() { const serversDB = database.servers; - const observable = await serversDB.collections - .get('servers') - .query() - .observeWithColumns(['name']); + const observable = await serversDB.collections.get('servers').query().observeWithColumns(['name']); - this.subscription = observable.subscribe((data) => { + this.subscription = observable.subscribe(data => { this.setState({ servers: data }); }); - Animated.timing( - this.animatedValue, - { - toValue: 1, - duration: ANIMATION_DURATION, - easing: Easing.inOut(Easing.quad), - useNativeDriver: true - } - ).start(); + Animated.timing(this.animatedValue, { + toValue: 1, + duration: ANIMATION_DURATION, + easing: Easing.inOut(Easing.quad), + useNativeDriver: true + }).start(); if (isTablet) { EventEmitter.addEventListener(KEY_COMMAND, this.handleCommands); } @@ -97,24 +90,30 @@ class ServerDropdown extends Component { close = () => { const { toggleServerDropdown } = this.props; - Animated.timing( - this.animatedValue, - { - toValue: 0, - duration: ANIMATION_DURATION, - easing: Easing.inOut(Easing.quad), - useNativeDriver: true - } - ).start(() => toggleServerDropdown()); - } + Animated.timing(this.animatedValue, { + toValue: 0, + duration: ANIMATION_DURATION, + easing: Easing.inOut(Easing.quad), + useNativeDriver: true + }).start(() => toggleServerDropdown()); + }; - navToNewServer = (previousServer) => { + createWorkspace = async () => { + logEvent(events.RL_CREATE_NEW_WORKSPACE); + try { + await Linking.openURL('https://cloud.rocket.chat/trial'); + } catch (e) { + log(e); + } + }; + + navToNewServer = previousServer => { const { appStart, initAdd } = this.props; batch(() => { - appStart({ root: ROOT_NEW_SERVER }); + appStart({ root: ROOT_OUTSIDE }); initAdd(previousServer); }); - } + }; addServer = () => { logEvent(events.RL_ADD_SERVER); @@ -123,16 +122,14 @@ class ServerDropdown extends Component { setTimeout(() => { this.navToNewServer(server); }, ANIMATION_DURATION); - } + }; - select = async(server, version) => { - const { - server: currentServer, selectServerRequest, isMasterDetail - } = this.props; + select = async (server, version) => { + const { server: currentServer, selectServerRequest, isMasterDetail } = this.props; this.close(); if (currentServer !== server) { logEvent(events.RL_CHANGE_SERVER); - const userId = await UserPreferences.getStringAsync(`${ RocketChat.TOKEN_KEY }-${ server }`); + const userId = await UserPreferences.getStringAsync(`${RocketChat.TOKEN_KEY}-${server}`); if (isMasterDetail) { goRoom({ item: {}, isMasterDetail }); } @@ -148,20 +145,21 @@ class ServerDropdown extends Component { selectServerRequest(server, version); } } - } + }; - remove = server => showConfirmationAlert({ - message: I18n.t('This_will_remove_all_data_from_this_server'), - confirmationText: I18n.t('Delete'), - onPress: async() => { - this.close(); - try { - await RocketChat.removeServer({ server }); - } catch { - // do nothing + remove = server => + showConfirmationAlert({ + message: I18n.t('This_will_remove_all_data_from_this_server'), + confirmationText: I18n.t('Delete'), + onPress: async () => { + this.close(); + try { + await RocketChat.removeServer({ server }); + } catch { + // do nothing + } } - } - }); + }); handleCommands = ({ event }) => { const { servers } = this.state; @@ -173,7 +171,7 @@ class ServerDropdown extends Component { navigation.navigate('RoomView'); } } - } + }; renderServer = ({ item }) => { const { server, theme } = this.props; @@ -182,18 +180,18 @@ class ServerDropdown extends Component { <ServerItem item={item} onPress={() => this.select(item.id, item.version)} - onLongPress={() => (item.id === server || this.remove(item.id))} + onLongPress={() => item.id === server || this.remove(item.id)} hasCheck={item.id === server} theme={theme} /> ); - } + }; render() { const { servers } = this.state; const { theme, isMasterDetail, insets } = this.props; const maxRows = 4; - const initialTop = 41 + (Math.min(servers.length, maxRows) * ROW_HEIGHT); + const initialTop = 87 + Math.min(servers.length, maxRows) * ROW_HEIGHT; const statusBarHeight = insets?.top ?? 0; const heightDestination = isMasterDetail ? headerHeight + statusBarHeight : 0; const translateY = this.animatedValue.interpolate({ @@ -207,12 +205,15 @@ class ServerDropdown extends Component { return ( <> <TouchableWithoutFeedback onPress={this.close}> - <Animated.View style={[styles.backdrop, - { - backgroundColor: themes[theme].backdropColor, - opacity: backdropOpacity, - top: heightDestination - }]} + <Animated.View + style={[ + styles.backdrop, + { + backgroundColor: themes[theme].backdropColor, + opacity: backdropOpacity, + top: heightDestination + } + ]} /> </TouchableWithoutFeedback> <Animated.View @@ -224,15 +225,8 @@ class ServerDropdown extends Component { borderColor: themes[theme].separatorColor } ]} - testID='rooms-list-header-server-dropdown' - > - <View - style={[ - styles.dropdownContainerHeader, - styles.serverHeader, - { borderColor: themes[theme].separatorColor } - ]} - > + testID='rooms-list-header-server-dropdown'> + <View style={[styles.dropdownContainerHeader, styles.serverHeader, { borderColor: themes[theme].separatorColor }]}> <Text style={[styles.serverHeaderText, { color: themes[theme].auxiliaryText }]}>{I18n.t('Server')}</Text> <TouchableOpacity onPress={this.addServer} testID='rooms-list-header-server-add'> <Text style={[styles.serverHeaderAdd, { color: themes[theme].tintColor }]}>{I18n.t('Add_Server')}</Text> @@ -246,6 +240,17 @@ class ServerDropdown extends Component { ItemSeparatorComponent={List.Separator} keyboardShouldPersistTaps='always' /> + <List.Separator /> + <Button + title={I18n.t('Create_a_new_workspace')} + type='secondary' + onPress={this.createWorkspace} + theme={theme} + testID='rooms-list-header-create-workspace-button' + style={styles.buttonCreateWorkspace} + color={themes[theme].tintColor} + styleText={[styles.serverHeaderAdd, { textAlign: 'center' }]} + /> </Animated.View> </> ); diff --git a/app/views/RoomsListView/SortDropdown/index.js b/app/views/RoomsListView/SortDropdown/index.js index 06a398a82..6a945ff67 100644 --- a/app/views/RoomsListView/SortDropdown/index.js +++ b/app/views/RoomsListView/SortDropdown/index.js @@ -1,7 +1,5 @@ import React, { PureComponent } from 'react'; -import { - Animated, Easing, TouchableWithoutFeedback -} from 'react-native'; +import { Animated, Easing, TouchableWithoutFeedback } from 'react-native'; import PropTypes from 'prop-types'; import { connect } from 'react-redux'; import { withSafeAreaInsets } from 'react-native-safe-area-context'; @@ -10,7 +8,7 @@ import styles from '../styles'; import * as List from '../../../containers/List'; import RocketChat from '../../../lib/rocketchat'; import { setPreference } from '../../../actions/sortPreferences'; -import log, { logEvent, events } from '../../../utils/log'; +import log, { events, logEvent } from '../../../utils/log'; import I18n from '../../../i18n'; import { withTheme } from '../../../theme'; import { themes } from '../../../constants/colors'; @@ -30,7 +28,7 @@ class Sort extends PureComponent { theme: PropTypes.string, insets: PropTypes.object, setSortPreference: PropTypes.func - } + }; constructor(props) { super(props); @@ -38,15 +36,12 @@ class Sort extends PureComponent { } componentDidMount() { - Animated.timing( - this.animatedValue, - { - toValue: 1, - duration: ANIMATION_DURATION, - easing: Easing.inOut(Easing.quad), - useNativeDriver: true - } - ).start(); + Animated.timing(this.animatedValue, { + toValue: 1, + duration: ANIMATION_DURATION, + easing: Easing.inOut(Easing.quad), + useNativeDriver: true + }).start(); } componentDidUpdate(prevProps) { @@ -56,7 +51,7 @@ class Sort extends PureComponent { } } - setSortPreference = (param) => { + setSortPreference = param => { const { setSortPreference } = this.props; try { @@ -66,55 +61,52 @@ class Sort extends PureComponent { logEvent(events.RL_SORT_CHANNELS_F); log(e); } - } + }; sortByName = () => { logEvent(events.RL_SORT_CHANNELS_BY_NAME); this.setSortPreference({ sortBy: 'alphabetical' }); this.close(); - } + }; sortByActivity = () => { logEvent(events.RL_SORT_CHANNELS_BY_ACTIVITY); this.setSortPreference({ sortBy: 'activity' }); this.close(); - } + }; toggleGroupByType = () => { logEvent(events.RL_GROUP_CHANNELS_BY_TYPE); const { groupByType } = this.props; this.setSortPreference({ groupByType: !groupByType }); - } + }; toggleGroupByFavorites = () => { logEvent(events.RL_GROUP_CHANNELS_BY_FAVORITE); const { showFavorites } = this.props; this.setSortPreference({ showFavorites: !showFavorites }); - } + }; toggleUnread = () => { logEvent(events.RL_GROUP_CHANNELS_BY_UNREAD); const { showUnread } = this.props; this.setSortPreference({ showUnread: !showUnread }); - } + }; close = () => { const { close } = this.props; - Animated.timing( - this.animatedValue, - { - toValue: 0, - duration: ANIMATION_DURATION, - easing: Easing.inOut(Easing.quad), - useNativeDriver: true - } - ).start(() => close()); - } + Animated.timing(this.animatedValue, { + toValue: 0, + duration: ANIMATION_DURATION, + easing: Easing.inOut(Easing.quad), + useNativeDriver: true + }).start(() => close()); + }; renderCheck = () => { const { theme } = this.props; return <List.Icon name='check' color={themes[theme].tintColor} />; - } + }; render() { const { isMasterDetail, insets } = this.props; @@ -124,9 +116,7 @@ class Sort extends PureComponent { inputRange: [0, 1], outputRange: [-326, heightDestination] }); - const { - sortBy, groupByType, showFavorites, showUnread, theme - } = this.props; + const { sortBy, groupByType, showFavorites, showUnread, theme } = this.props; const backdropOpacity = this.animatedValue.interpolate({ inputRange: [0, 1], outputRange: [0, themes[theme].backdropOpacity] @@ -135,12 +125,15 @@ class Sort extends PureComponent { return ( <> <TouchableWithoutFeedback onPress={this.close}> - <Animated.View style={[styles.backdrop, - { - backgroundColor: themes[theme].backdropColor, - opacity: backdropOpacity, - top: heightDestination - }]} + <Animated.View + style={[ + styles.backdrop, + { + backgroundColor: themes[theme].backdropColor, + opacity: backdropOpacity, + top: heightDestination + } + ]} /> </TouchableWithoutFeedback> <Animated.View @@ -151,8 +144,7 @@ class Sort extends PureComponent { backgroundColor: themes[theme].backgroundColor, borderColor: themes[theme].separatorColor } - ]} - > + ]}> <List.Item title={I18n.t('Sorting_by', { key: I18n.t(sortBy === 'alphabetical' ? 'name' : 'activity') })} left={() => <List.Icon name='sort' />} diff --git a/app/views/RoomsListView/index.js b/app/views/RoomsListView/index.js index 8b905fb25..5f4d12553 100644 --- a/app/views/RoomsListView/index.js +++ b/app/views/RoomsListView/index.js @@ -1,14 +1,7 @@ import React from 'react'; import PropTypes from 'prop-types'; -import { - View, - FlatList, - BackHandler, - Text, - Keyboard, - RefreshControl -} from 'react-native'; -import { connect } from 'react-redux'; +import { BackHandler, FlatList, Keyboard, RefreshControl, Text, View } from 'react-native'; +import { batch, connect } from 'react-redux'; import { dequal } from 'dequal'; import Orientation from 'react-native-orientation-locker'; import { Q } from '@nozbe/watermelondb'; @@ -17,39 +10,35 @@ import { withSafeAreaInsets } from 'react-native-safe-area-context'; import database from '../../lib/database'; import RocketChat from '../../lib/rocketchat'; import RoomItem, { ROW_HEIGHT } from '../../presentation/RoomItem'; -import styles from './styles'; -import log, { logEvent, events } from '../../utils/log'; +import log, { events, logEvent } from '../../utils/log'; import I18n from '../../i18n'; -import SortDropdown from './SortDropdown'; -import ServerDropdown from './ServerDropdown'; import { - toggleSortDropdown as toggleSortDropdownAction, - openSearchHeader as openSearchHeaderAction, closeSearchHeader as closeSearchHeaderAction, + closeServerDropdown as closeServerDropdownAction, + openSearchHeader as openSearchHeaderAction, roomsRequest as roomsRequestAction, - closeServerDropdown as closeServerDropdownAction + toggleSortDropdown as toggleSortDropdownAction } from '../../actions/rooms'; +import { appStart as appStartAction, ROOT_OUTSIDE } from '../../actions/app'; import debounce from '../../utils/debounce'; import { isIOS, isTablet } from '../../utils/deviceInfo'; -import RoomsListHeaderView from './Header'; import * as HeaderButton from '../../containers/HeaderButton'; import StatusBar from '../../containers/StatusBar'; import ActivityIndicator from '../../containers/ActivityIndicator'; -import ListHeader from './ListHeader'; -import { selectServerRequest as selectServerRequestAction } from '../../actions/server'; +import { selectServerRequest as selectServerRequestAction, serverInitAdd as serverInitAddAction } from '../../actions/server'; import { animateNextTransition } from '../../utils/layoutAnimation'; import { withTheme } from '../../theme'; import { themes } from '../../constants/colors'; import EventEmitter from '../../utils/events'; import { KEY_COMMAND, - handleCommandShowPreferences, + handleCommandAddNewServer, + handleCommandNextRoom, + handleCommandPreviousRoom, handleCommandSearching, handleCommandSelectRoom, - handleCommandPreviousRoom, - handleCommandNextRoom, handleCommandShowNewMessage, - handleCommandAddNewServer + handleCommandShowPreferences } from '../../commands'; import { MAX_SIDEBAR_WIDTH } from '../../constants/tablet'; import { getUserSelector } from '../../selectors/login'; @@ -57,11 +46,15 @@ import { goRoom } from '../../utils/goRoom'; import SafeAreaView from '../../containers/SafeAreaView'; import Header, { getHeaderTitlePosition } from '../../containers/Header'; import { withDimensions } from '../../dimensions'; -import { showErrorAlert, showConfirmationAlert } from '../../utils/info'; +import { showConfirmationAlert, showErrorAlert } from '../../utils/info'; import { E2E_BANNER_TYPE } from '../../lib/encryption/constants'; - import { getInquiryQueueSelector } from '../../ee/omnichannel/selectors/inquiry'; import { changeLivechatStatus, isOmnichannelStatusAvailable } from '../../ee/omnichannel/lib'; +import ListHeader from './ListHeader'; +import RoomsListHeaderView from './Header'; +import ServerDropdown from './ServerDropdown'; +import SortDropdown from './SortDropdown'; +import styles from './styles'; const INITIAL_NUM_TO_RENDER = isTablet ? 20 : 12; const CHATS_HEADER = 'Chats'; @@ -141,13 +134,14 @@ class RoomsListView extends React.Component { insets: PropTypes.object, queueSize: PropTypes.number, inquiryEnabled: PropTypes.bool, - encryptionBanner: PropTypes.string + encryptionBanner: PropTypes.string, + initAdd: PropTypes.func }; constructor(props) { super(props); - console.time(`${ this.constructor.name } init`); - console.time(`${ this.constructor.name } mount`); + console.time(`${this.constructor.name} init`); + console.time(`${this.constructor.name} mount`); this.animated = false; this.mounted = false; @@ -165,9 +159,7 @@ class RoomsListView extends React.Component { } componentDidMount() { - const { - navigation, closeServerDropdown - } = this.props; + const { navigation, closeServerDropdown } = this.props; this.mounted = true; if (isTablet) { @@ -191,13 +183,11 @@ class RoomsListView extends React.Component { this.backHandler.remove(); } }); - console.timeEnd(`${ this.constructor.name } mount`); + console.timeEnd(`${this.constructor.name} mount`); } UNSAFE_componentWillReceiveProps(nextProps) { - const { - loadingServer, searchText, server, changingServer - } = this.props; + const { loadingServer, searchText, server, changingServer } = this.props; // when the server is changed if (server !== nextProps.server && loadingServer !== nextProps.loadingServer && nextProps.loadingServer) { @@ -241,10 +231,7 @@ class RoomsListView extends React.Component { return false; } - const { - loading, - search - } = this.state; + const { loading, search } = this.state; const { rooms, width, insets } = this.props; if (nextState.loading !== loading) { return true; @@ -270,23 +257,15 @@ class RoomsListView extends React.Component { } componentDidUpdate(prevProps) { - const { - sortBy, - groupByType, - showFavorites, - showUnread, - rooms, - isMasterDetail, - insets - } = this.props; + const { sortBy, groupByType, showFavorites, showUnread, rooms, isMasterDetail, insets } = this.props; const { item } = this.state; if ( !( - prevProps.sortBy === sortBy - && prevProps.groupByType === groupByType - && prevProps.showFavorites === showFavorites - && prevProps.showUnread === showUnread + prevProps.sortBy === sortBy && + prevProps.groupByType === groupByType && + prevProps.showFavorites === showFavorites && + prevProps.showUnread === showUnread ) ) { this.getSubscriptions(); @@ -315,7 +294,7 @@ class RoomsListView extends React.Component { if (isTablet) { EventEmitter.removeListener(KEY_COMMAND, this.handleCommands); } - console.countReset(`${ this.constructor.name }.render calls`); + console.countReset(`${this.constructor.name}.render calls`); } getHeader = () => { @@ -324,54 +303,43 @@ class RoomsListView extends React.Component { const headerTitlePosition = getHeaderTitlePosition({ insets, numIconsRight: searching ? 0 : 3 }); return { headerTitleAlign: 'left', - headerLeft: () => (searching ? ( - <HeaderButton.Container left> - <HeaderButton.Item - iconName='close' - onPress={this.cancelSearch} + headerLeft: () => + searching ? ( + <HeaderButton.Container left> + <HeaderButton.Item iconName='close' onPress={this.cancelSearch} /> + </HeaderButton.Container> + ) : ( + <HeaderButton.Drawer + navigation={navigation} + testID='rooms-list-view-sidebar' + onPress={ + isMasterDetail + ? () => navigation.navigate('ModalStackNavigator', { screen: 'SettingsView' }) + : () => navigation.toggleDrawer() + } /> - </HeaderButton.Container> - ) : ( - <HeaderButton.Drawer - navigation={navigation} - testID='rooms-list-view-sidebar' - onPress={isMasterDetail - ? () => navigation.navigate('ModalStackNavigator', { screen: 'SettingsView' }) - : () => navigation.toggleDrawer()} - /> - )), + ), headerTitle: () => <RoomsListHeaderView />, headerTitleContainerStyle: { left: headerTitlePosition.left, right: headerTitlePosition.right }, - headerRight: () => (searching ? null : ( - <HeaderButton.Container> - <HeaderButton.Item - iconName='create' - onPress={this.goToNewMessage} - testID='rooms-list-view-create-channel' - /> - <HeaderButton.Item - iconName='search' - onPress={this.initSearching} - testID='rooms-list-view-search' - /> - <HeaderButton.Item - iconName='directory' - onPress={this.goDirectory} - testID='rooms-list-view-directory' - /> - </HeaderButton.Container> - )) + headerRight: () => + searching ? null : ( + <HeaderButton.Container> + <HeaderButton.Item iconName='create' onPress={this.goToNewMessage} testID='rooms-list-view-create-channel' /> + <HeaderButton.Item iconName='search' onPress={this.initSearching} testID='rooms-list-view-search' /> + <HeaderButton.Item iconName='directory' onPress={this.goDirectory} testID='rooms-list-view-directory' /> + </HeaderButton.Container> + ) }; - } + }; setHeader = () => { const { navigation } = this.props; const options = this.getHeader(); navigation.setOptions(options); - } + }; internalSetState = (...args) => { if (this.animated) { @@ -388,29 +356,20 @@ class RoomsListView extends React.Component { allData = allData.concat(data); } return allData; - } + }; - getSubscriptions = async() => { + getSubscriptions = async () => { this.unsubscribeQuery(); - const { - sortBy, - showUnread, - showFavorites, - groupByType, - user - } = this.props; + const { sortBy, showUnread, showFavorites, groupByType, user } = this.props; const db = database.active; let observable; - const defaultWhereClause = [ - Q.where('archived', false), - Q.where('open', true) - ]; + const defaultWhereClause = [Q.where('archived', false), Q.where('open', true)]; if (sortBy === 'alphabetical') { - defaultWhereClause.push(Q.experimentalSortBy(`${ this.useRealName ? 'fname' : 'name' }`, Q.asc)); + defaultWhereClause.push(Q.experimentalSortBy(`${this.useRealName ? 'fname' : 'name'}`, Q.asc)); } else { defaultWhereClause.push(Q.experimentalSortBy('room_updated_at', Q.desc)); } @@ -422,20 +381,16 @@ class RoomsListView extends React.Component { .query(...defaultWhereClause) .observeWithColumns(['alert']); - // When we're NOT grouping + // When we're NOT grouping } else { this.count += QUERY_SIZE; observable = await db.collections .get('subscriptions') - .query( - ...defaultWhereClause, - Q.experimentalSkip(0), - Q.experimentalTake(this.count) - ) + .query(...defaultWhereClause, Q.experimentalSkip(0), Q.experimentalTake(this.count)) .observe(); } - this.querySubscription = observable.subscribe((data) => { + this.querySubscription = observable.subscribe(data => { let tempChats = []; let chats = data; @@ -503,13 +458,13 @@ class RoomsListView extends React.Component { this.state.loading = false; } }); - } + }; unsubscribeQuery = () => { if (this.querySubscription && this.querySubscription.unsubscribe) { this.querySubscription.unsubscribe(); } - } + }; initSearching = () => { logEvent(events.RL_SEARCH); @@ -550,7 +505,7 @@ class RoomsListView extends React.Component { }; // eslint-disable-next-line react/sort-comp - search = debounce(async(text) => { + search = debounce(async text => { const result = await RocketChat.search({ text }); // if the search was cancelled before the promise is resolved @@ -565,15 +520,15 @@ class RoomsListView extends React.Component { this.scrollToTop(); }, 300); - getRoomTitle = item => RocketChat.getRoomTitle(item) + getRoomTitle = item => RocketChat.getRoomTitle(item); - getRoomAvatar = item => RocketChat.getRoomAvatar(item) + getRoomAvatar = item => RocketChat.getRoomAvatar(item); - isGroupChat = item => RocketChat.isGroupChat(item) + isGroupChat = item => RocketChat.isGroupChat(item); - isRead = item => RocketChat.isRead(item) + isRead = item => RocketChat.isRead(item); - getUserPresence = uid => RocketChat.getUserPresence(uid) + getUserPresence = uid => RocketChat.getUserPresence(uid); getUidDirectMessage = room => RocketChat.getUidDirectMessage(room); @@ -596,7 +551,7 @@ class RoomsListView extends React.Component { if (this.scroll?.scrollToOffset) { this.scroll.scrollToOffset({ offset: 0 }); } - } + }; toggleSort = () => { logEvent(events.RL_TOGGLE_SORT_DROPDOWN); @@ -608,17 +563,17 @@ class RoomsListView extends React.Component { }, 100); }; - toggleFav = async(rid, favorite) => { + toggleFav = async (rid, favorite) => { logEvent(favorite ? events.RL_UNFAVORITE_CHANNEL : events.RL_FAVORITE_CHANNEL); try { const db = database.active; const result = await RocketChat.toggleFavorite(rid, !favorite); if (result.success) { const subCollection = db.get('subscriptions'); - await db.action(async() => { + await db.action(async () => { try { const subRecord = await subCollection.find(rid); - await subRecord.update((sub) => { + await subRecord.update(sub => { sub.f = !favorite; }); } catch (e) { @@ -632,7 +587,7 @@ class RoomsListView extends React.Component { } }; - toggleRead = async(rid, isRead) => { + toggleRead = async (rid, isRead) => { logEvent(isRead ? events.RL_UNREAD_CHANNEL : events.RL_READ_CHANNEL); try { const db = database.active; @@ -640,10 +595,10 @@ class RoomsListView extends React.Component { if (result.success) { const subCollection = db.get('subscriptions'); - await db.action(async() => { + await db.action(async () => { try { const subRecord = await subCollection.find(rid); - await subRecord.update((sub) => { + await subRecord.update(sub => { sub.alert = isRead; sub.unread = 0; }); @@ -658,14 +613,14 @@ class RoomsListView extends React.Component { } }; - hideChannel = async(rid, type) => { + hideChannel = async (rid, type) => { logEvent(events.RL_HIDE_CHANNEL); try { const db = database.active; const result = await RocketChat.hideRoom(rid, type); if (result.success) { const subCollection = db.get('subscriptions'); - await db.action(async() => { + await db.action(async () => { try { const subRecord = await subCollection.find(rid); await subRecord.destroyPermanently(); @@ -692,16 +647,14 @@ class RoomsListView extends React.Component { goQueue = () => { logEvent(events.RL_GO_QUEUE); - const { - navigation, isMasterDetail, queueSize, inquiryEnabled, user - } = this.props; + const { navigation, isMasterDetail, queueSize, inquiryEnabled, user } = this.props; // if not-available, prompt to change to available if (!isOmnichannelStatusAvailable(user)) { showConfirmationAlert({ message: I18n.t('Omnichannel_enable_alert'), confirmationText: I18n.t('Yes'), - onPress: async() => { + onPress: async () => { try { await changeLivechatStatus(); } catch { @@ -737,9 +690,9 @@ class RoomsListView extends React.Component { this.setState({ item }); } goRoom({ item, isMasterDetail }); - } + }; - goRoomByIndex = (index) => { + goRoomByIndex = index => { const { chats } = this.state; const { isMasterDetail } = this.props; const filteredChats = chats.filter(c => !c.separator); @@ -747,7 +700,7 @@ class RoomsListView extends React.Component { if (room) { this.goRoom({ item: room, isMasterDetail }); } - } + }; findOtherRoom = (index, sign) => { const { chats } = this.state; @@ -761,11 +714,11 @@ class RoomsListView extends React.Component { } else { return otherRoom; } - } + }; // Go to previous or next room based on sign (-1 or 1) // It's used by iPad key commands - goOtherRoom = (sign) => { + goOtherRoom = sign => { const { item } = this.state; if (!item) { return; @@ -784,7 +737,7 @@ class RoomsListView extends React.Component { if (otherRoom) { this.goRoom({ item: otherRoom, isMasterDetail }); } - } + }; goToNewMessage = () => { logEvent(events.RL_GO_NEW_MSG); @@ -795,7 +748,7 @@ class RoomsListView extends React.Component { } else { navigation.navigate('NewMessageStackNavigator'); } - } + }; goEncryption = () => { logEvent(events.RL_GO_E2E_SAVE_PASSWORD); @@ -809,10 +762,10 @@ class RoomsListView extends React.Component { const screen = isSavePassword ? 'E2ESaveYourPasswordStackNavigator' : 'E2EEnterYourPasswordStackNavigator'; navigation.navigate(screen); } - } + }; handleCommands = ({ event }) => { - const { navigation, server, isMasterDetail } = this.props; + const { navigation, server, isMasterDetail, appStart, initAdd } = this.props; const { input } = event; if (handleCommandShowPreferences(event)) { navigation.navigate('SettingsView'); @@ -831,7 +784,10 @@ class RoomsListView extends React.Component { navigation.navigate('NewMessageStack'); } } else if (handleCommandAddNewServer(event)) { - navigation.navigate('NewServerView', { previousServer: server }); + batch(() => { + appStart({ root: ROOT_OUTSIDE }); + initAdd(server); + }); } }; @@ -842,22 +798,20 @@ class RoomsListView extends React.Component { return; } roomsRequest({ allData: true }); - } + }; onEndReached = () => { // Run only when we're not grouping by anything if (!this.isGrouping) { this.getSubscriptions(); } - } + }; getScrollRef = ref => (this.scroll = ref); renderListHeader = () => { const { searching } = this.state; - const { - sortBy, queueSize, inquiryEnabled, encryptionBanner, user - } = this.props; + const { sortBy, queueSize, inquiryEnabled, encryptionBanner, user } = this.props; return ( <ListHeader searching={searching} @@ -881,12 +835,8 @@ class RoomsListView extends React.Component { } const options = this.getHeader(); - return ( - <Header - {...options} - /> - ); - } + return <Header {...options} />; + }; renderItem = ({ item }) => { if (item.separator) { @@ -929,19 +879,17 @@ class RoomsListView extends React.Component { ); }; - renderSectionHeader = (header) => { + renderSectionHeader = header => { const { theme } = this.props; return ( <View style={[styles.groupTitleContainer, { backgroundColor: themes[theme].backgroundColor }]}> <Text style={[styles.groupTitle, { color: themes[theme].controlText }]}>{I18n.t(header)}</Text> </View> ); - } + }; renderScroll = () => { - const { - loading, chats, search, searching - } = this.state; + const { loading, chats, search, searching } = this.state; const { theme, refreshing } = this.props; if (loading) { @@ -961,13 +909,9 @@ class RoomsListView extends React.Component { removeClippedSubviews={isIOS} keyboardShouldPersistTaps='always' initialNumToRender={INITIAL_NUM_TO_RENDER} - refreshControl={( - <RefreshControl - refreshing={refreshing} - onRefresh={this.onRefresh} - tintColor={themes[theme].auxiliaryText} - /> - )} + refreshControl={ + <RefreshControl refreshing={refreshing} onRefresh={this.onRefresh} tintColor={themes[theme].auxiliaryText} /> + } windowSize={9} onEndReached={this.onEndReached} onEndReachedThreshold={0.5} @@ -976,17 +920,9 @@ class RoomsListView extends React.Component { }; render = () => { - console.count(`${ this.constructor.name }.render calls`); - const { - sortBy, - groupByType, - showFavorites, - showUnread, - showServerDropdown, - showSortDropdown, - theme, - navigation - } = this.props; + console.count(`${this.constructor.name}.render calls`); + const { sortBy, groupByType, showFavorites, showUnread, showServerDropdown, showSortDropdown, theme, navigation } = + this.props; return ( <SafeAreaView testID='rooms-list-view' style={{ backgroundColor: themes[theme].backgroundColor }}> @@ -1036,7 +972,9 @@ const mapDispatchToProps = dispatch => ({ closeSearchHeader: () => dispatch(closeSearchHeaderAction()), roomsRequest: params => dispatch(roomsRequestAction(params)), selectServerRequest: server => dispatch(selectServerRequestAction(server)), - closeServerDropdown: () => dispatch(closeServerDropdownAction()) + closeServerDropdown: () => dispatch(closeServerDropdownAction()), + appStart: params => dispatch(appStartAction(params)), + initAdd: previousServer => dispatch(serverInitAddAction(previousServer)) }); export default connect(mapStateToProps, mapDispatchToProps)(withDimensions(withTheme(withSafeAreaInsets(RoomsListView)))); diff --git a/app/views/RoomsListView/styles.js b/app/views/RoomsListView/styles.js index beb04c92f..8c94c5fdb 100644 --- a/app/views/RoomsListView/styles.js +++ b/app/views/RoomsListView/styles.js @@ -56,5 +56,10 @@ export default StyleSheet.create({ marginRight: 12, paddingVertical: 10, ...sharedStyles.textRegular + }, + buttonCreateWorkspace: { + height: 46, + justifyContent: 'center', + marginBottom: 0 } }); diff --git a/app/views/ScreenLockConfigView.js b/app/views/ScreenLockConfigView.js index 294a854a6..8e7de6cae 100644 --- a/app/views/ScreenLockConfigView.js +++ b/app/views/ScreenLockConfigView.js @@ -5,11 +5,11 @@ import { connect } from 'react-redux'; import I18n from '../i18n'; import { withTheme } from '../theme'; -import { themes, SWITCH_TRACK_COLOR } from '../constants/colors'; +import { SWITCH_TRACK_COLOR, themes } from '../constants/colors'; import StatusBar from '../containers/StatusBar'; import * as List from '../containers/List'; import database from '../lib/database'; -import { supportedBiometryLabel, changePasscode, checkHasPasscode } from '../utils/localAuthentication'; +import { changePasscode, checkHasPasscode, supportedBiometryLabel } from '../utils/localAuthentication'; import { DEFAULT_AUTO_LOCK } from '../constants/localAuthentication'; import SafeAreaView from '../containers/SafeAreaView'; import { events, logEvent } from '../utils/log'; @@ -26,7 +26,7 @@ class ScreenLockConfigView extends React.Component { server: PropTypes.string, Force_Screen_Lock: PropTypes.string, Force_Screen_Lock_After: PropTypes.string - } + }; constructor(props) { super(props); @@ -68,7 +68,7 @@ class ScreenLockConfigView extends React.Component { } ]; - init = async() => { + init = async () => { const { server } = this.props; const serversDB = database.servers; const serversCollection = serversDB.get('servers'); @@ -87,71 +87,77 @@ class ScreenLockConfigView extends React.Component { this.setState({ biometryLabel }); this.observe(); - } + }; /* * We should observe biometry value * because it can be changed by PasscodeChange * when the user set his first passcode - */ + */ observe = () => { this.observable = this.serverRecord?.observe()?.subscribe(({ biometry }) => { this.setState({ biometry }); }); - } + }; - save = async() => { + save = async () => { logEvent(events.SLC_SAVE_SCREEN_LOCK); const { autoLock, autoLockTime, biometry } = this.state; const serversDB = database.servers; - await serversDB.action(async() => { - await this.serverRecord?.update((record) => { + await serversDB.action(async () => { + await this.serverRecord?.update(record => { record.autoLock = autoLock; record.autoLockTime = autoLockTime === null ? DEFAULT_AUTO_LOCK : autoLockTime; record.biometry = biometry === null ? DEFAULT_BIOMETRY : biometry; }); }); - } + }; - changePasscode = async({ force }) => { + changePasscode = async ({ force }) => { logEvent(events.SLC_CHANGE_PASSCODE); await changePasscode({ force }); - } + }; toggleAutoLock = () => { logEvent(events.SLC_TOGGLE_AUTOLOCK); - this.setState(({ autoLock }) => ({ autoLock: !autoLock, autoLockTime: DEFAULT_AUTO_LOCK }), async() => { - const { autoLock } = this.state; - if (autoLock) { - try { - await checkHasPasscode({ force: false, serverRecord: this.serverRecord }); - } catch { - this.toggleAutoLock(); + this.setState( + ({ autoLock }) => ({ autoLock: !autoLock, autoLockTime: DEFAULT_AUTO_LOCK }), + async () => { + const { autoLock } = this.state; + if (autoLock) { + try { + await checkHasPasscode({ force: false, serverRecord: this.serverRecord }); + } catch { + this.toggleAutoLock(); + } } + this.save(); } - this.save(); - }); - } + ); + }; toggleBiometry = () => { logEvent(events.SLC_TOGGLE_BIOMETRY); - this.setState(({ biometry }) => ({ biometry: !biometry }), () => this.save()); - } + this.setState( + ({ biometry }) => ({ biometry: !biometry }), + () => this.save() + ); + }; - isSelected = (value) => { + isSelected = value => { const { autoLockTime } = this.state; return autoLockTime === value; - } + }; - changeAutoLockTime = (autoLockTime) => { + changeAutoLockTime = autoLockTime => { logEvent(events.SLC_CHANGE_AUTOLOCK_TIME); this.setState({ autoLockTime }, () => this.save()); - } + }; renderIcon = () => { const { theme } = this.props; return <List.Icon name='check' color={themes[theme].tintColor} />; - } + }; renderItem = ({ item }) => { const { title, value, disabled } = item; @@ -167,31 +173,20 @@ class ScreenLockConfigView extends React.Component { <List.Separator /> </> ); - } + }; renderAutoLockSwitch = () => { const { autoLock } = this.state; const { Force_Screen_Lock } = this.props; return ( - <Switch - value={autoLock} - trackColor={SWITCH_TRACK_COLOR} - onValueChange={this.toggleAutoLock} - disabled={Force_Screen_Lock} - /> + <Switch value={autoLock} trackColor={SWITCH_TRACK_COLOR} onValueChange={this.toggleAutoLock} disabled={Force_Screen_Lock} /> ); - } + }; renderBiometrySwitch = () => { const { biometry } = this.state; - return ( - <Switch - value={biometry} - trackColor={SWITCH_TRACK_COLOR} - onValueChange={this.toggleBiometry} - /> - ); - } + return <Switch value={biometry} trackColor={SWITCH_TRACK_COLOR} onValueChange={this.toggleBiometry} />; + }; renderAutoLockItems = () => { const { autoLock, autoLockTime } = this.state; @@ -201,12 +196,14 @@ class ScreenLockConfigView extends React.Component { } let items = this.defaultAutoLockOptions; if (Force_Screen_Lock && Force_Screen_Lock_After > 0) { - items = [{ - title: I18n.t('After_seconds_set_by_admin', { seconds: Force_Screen_Lock_After }), - value: Force_Screen_Lock_After, - disabled: true - }]; - // if Force_Screen_Lock is disabled and autoLockTime is a value that isn't on our defaultOptions we'll show it + items = [ + { + title: I18n.t('After_seconds_set_by_admin', { seconds: Force_Screen_Lock_After }), + value: Force_Screen_Lock_After, + disabled: true + } + ]; + // if Force_Screen_Lock is disabled and autoLockTime is a value that isn't on our defaultOptions we'll show it } else if (Force_Screen_Lock_After === autoLockTime && !items.find(item => item.value === autoLockTime)) { items.push({ title: I18n.t('After_seconds_set_by_admin', { seconds: Force_Screen_Lock_After }), @@ -219,7 +216,7 @@ class ScreenLockConfigView extends React.Component { {items.map(item => this.renderItem({ item }))} </List.Section> ); - } + }; renderBiometry = () => { const { autoLock, biometryLabel } = this.state; @@ -237,7 +234,7 @@ class ScreenLockConfigView extends React.Component { <List.Separator /> </List.Section> ); - } + }; render() { const { autoLock } = this.state; @@ -247,23 +244,13 @@ class ScreenLockConfigView extends React.Component { <List.Container> <List.Section> <List.Separator /> - <List.Item - title='Local_authentication_unlock_option' - right={() => this.renderAutoLockSwitch()} - /> - {autoLock - ? ( - <> - <List.Separator /> - <List.Item - title='Local_authentication_change_passcode' - onPress={this.changePasscode} - showActionIndicator - /> - </> - ) - : null - } + <List.Item title='Local_authentication_unlock_option' right={() => this.renderAutoLockSwitch()} /> + {autoLock ? ( + <> + <List.Separator /> + <List.Item title='Local_authentication_change_passcode' onPress={this.changePasscode} showActionIndicator /> + </> + ) : null} <List.Separator /> <List.Info info='Local_authentication_info' /> </List.Section> diff --git a/app/views/ScreenLockedView.js b/app/views/ScreenLockedView.js index 2b036779c..644e76ff0 100644 --- a/app/views/ScreenLockedView.js +++ b/app/views/ScreenLockedView.js @@ -23,7 +23,7 @@ const ScreenLockedView = ({ theme }) => { } }, [data]); - const showScreenLock = (args) => { + const showScreenLock = args => { setData(args); }; @@ -32,12 +32,12 @@ const ScreenLockedView = ({ theme }) => { Orientation.lockToPortrait(); } const listener = EventEmitter.addEventListener(LOCAL_AUTHENTICATE_EMITTER, showScreenLock); - return (() => { + return () => { if (!isTablet) { Orientation.unlockAllOrientations(); } EventEmitter.removeListener(LOCAL_AUTHENTICATE_EMITTER, listener); - }); + }; }, []); const onSubmit = () => { @@ -55,8 +55,7 @@ const ScreenLockedView = ({ theme }) => { hideModalContentWhileAnimating style={{ margin: 0 }} animationIn='fadeIn' - animationOut='fadeOut' - > + animationOut='fadeOut'> <PasscodeEnter theme={theme} hasBiometry={data?.hasBiometry} finishProcess={onSubmit} /> </Modal> ); diff --git a/app/views/SearchMessagesView/index.js b/app/views/SearchMessagesView/index.js index 09c9e6c15..f8131ec1d 100644 --- a/app/views/SearchMessagesView/index.js +++ b/app/views/SearchMessagesView/index.js @@ -1,13 +1,12 @@ import React from 'react'; import PropTypes from 'prop-types'; -import { View, FlatList, Text } from 'react-native'; +import { FlatList, Text, View } from 'react-native'; import { Q } from '@nozbe/watermelondb'; import { connect } from 'react-redux'; import { dequal } from 'dequal'; import RCTextInput from '../../containers/TextInput'; import ActivityIndicator from '../../containers/ActivityIndicator'; -import styles from './styles'; import Markdown from '../../containers/markdown'; import debounce from '../../utils/debounce'; import RocketChat from '../../lib/rocketchat'; @@ -25,6 +24,7 @@ import database from '../../lib/database'; import { sanitizeLikeString } from '../../lib/database/utils'; import getThreadName from '../../lib/methods/getThreadName'; import getRoomInfo from '../../lib/methods/getRoomInfo'; +import styles from './styles'; class SearchMessagesView extends React.Component { static navigationOptions = ({ navigation, route }) => { @@ -36,7 +36,7 @@ class SearchMessagesView extends React.Component { options.headerLeft = () => <HeaderButton.CloseModal navigation={navigation} />; } return options; - } + }; static propTypes = { navigation: PropTypes.object, @@ -46,7 +46,7 @@ class SearchMessagesView extends React.Component { customEmojis: PropTypes.object, theme: PropTypes.string, useRealName: PropTypes.bool - } + }; constructor(props) { super(props); @@ -87,7 +87,7 @@ class SearchMessagesView extends React.Component { } // Handle encrypted rooms search messages - searchMessages = async(searchText) => { + searchMessages = async searchText => { // If it's a encrypted, room we'll search only on the local stored messages if (this.encrypted) { const db = database.active; @@ -98,7 +98,7 @@ class SearchMessagesView extends React.Component { // Messages of this room Q.where('rid', this.rid), // Message content is like the search text - Q.where('msg', Q.like(`%${ likeString }%`)) + Q.where('msg', Q.like(`%${likeString}%`)) ) .fetch(); } @@ -107,9 +107,9 @@ class SearchMessagesView extends React.Component { if (result.success) { return result.messages; } - } + }; - search = debounce(async(searchText) => { + search = debounce(async searchText => { this.setState({ searchText, loading: true, messages: [] }); try { @@ -122,31 +122,31 @@ class SearchMessagesView extends React.Component { this.setState({ loading: false }); log(e); } - }, 1000) + }, 1000); - getCustomEmoji = (name) => { + getCustomEmoji = name => { const { customEmojis } = this.props; const emoji = customEmojis[name]; if (emoji) { return emoji; } return null; - } + }; - showAttachment = (attachment) => { + showAttachment = attachment => { const { navigation } = this.props; navigation.navigate('AttachmentView', { attachment }); - } + }; - navToRoomInfo = (navParam) => { + navToRoomInfo = navParam => { const { navigation, user } = this.props; if (navParam.rid === user.id) { return; } navigation.navigate('RoomInfoView', navParam); - } + }; - jumpToMessage = async({ item }) => { + jumpToMessage = async ({ item }) => { const { navigation } = this.props; let params = { rid: this.rid, @@ -166,7 +166,7 @@ class SearchMessagesView extends React.Component { } else { navigation.navigate('RoomView', params); } - } + }; renderEmpty = () => { const { theme } = this.props; @@ -175,12 +175,10 @@ class SearchMessagesView extends React.Component { <Text style={[styles.noDataFound, { color: themes[theme].titleText }]}>{I18n.t('No_results_found')}</Text> </View> ); - } + }; renderItem = ({ item }) => { - const { - user, baseUrl, theme, useRealName - } = this.props; + const { user, baseUrl, theme, useRealName } = this.props; return ( <Message item={item} @@ -198,7 +196,7 @@ class SearchMessagesView extends React.Component { jumpToMessage={() => this.jumpToMessage({ item })} /> ); - } + }; renderList = () => { const { messages, loading, searchText } = this.state; @@ -219,7 +217,7 @@ class SearchMessagesView extends React.Component { {...scrollPersistTaps} /> ); - } + }; render() { const { theme } = this.props; diff --git a/app/views/SecurityPrivacyView.js b/app/views/SecurityPrivacyView.js index 534e6bc32..5a6adcd48 100644 --- a/app/views/SecurityPrivacyView.js +++ b/app/views/SecurityPrivacyView.js @@ -10,7 +10,12 @@ import * as List from '../containers/List'; import I18n from '../i18n'; import { CRASH_REPORT_KEY, ANALYTICS_EVENTS_KEY } from '../lib/rocketchat'; import { - logEvent, events, toggleCrashErrorsReport, toggleAnalyticsEventsReport, getReportCrashErrorsValue, getReportAnalyticsEventsValue + logEvent, + events, + toggleCrashErrorsReport, + toggleAnalyticsEventsReport, + getReportCrashErrorsValue, + getReportAnalyticsEventsValue } from '../utils/log'; import SafeAreaView from '../containers/SafeAreaView'; import { isFDroidBuild } from '../constants/environment'; @@ -27,22 +32,22 @@ const SecurityPrivacyView = ({ navigation }) => { }); }, []); - const toggleCrashReport = (value) => { + const toggleCrashReport = value => { logEvent(events.SE_TOGGLE_CRASH_REPORT); AsyncStorage.setItem(CRASH_REPORT_KEY, JSON.stringify(value)); setCrashReportState(value); toggleCrashErrorsReport(value); }; - const toggleAnalyticsEvents = (value) => { + const toggleAnalyticsEvents = value => { logEvent(events.SE_TOGGLE_ANALYTICS_EVENTS); AsyncStorage.setItem(ANALYTICS_EVENTS_KEY, JSON.stringify(value)); setAnalyticsEventsState(value); toggleAnalyticsEventsReport(value); }; - const navigateToScreen = (screen) => { - logEvent(events[`SP_GO_${ screen.replace('View', '').toUpperCase() }`]); + const navigateToScreen = screen => { + logEvent(events[`SP_GO_${screen.replace('View', '').toUpperCase()}`]); navigation.navigate(screen); }; @@ -52,20 +57,17 @@ const SecurityPrivacyView = ({ navigation }) => { <List.Container testID='security-privacy-view-list'> <List.Section> <List.Separator /> - {e2eEnabled - ? ( - <> - <List.Item - title='E2E_Encryption' - showActionIndicator - onPress={() => navigateToScreen('E2EEncryptionSecurityView')} - testID='security-privacy-view-e2e-encryption' - /> - <List.Separator /> - </> - ) - : null - } + {e2eEnabled ? ( + <> + <List.Item + title='E2E_Encryption' + showActionIndicator + onPress={() => navigateToScreen('E2EEncryptionSecurityView')} + testID='security-privacy-view-e2e-encryption' + /> + <List.Separator /> + </> + ) : null} <List.Item title='Screen_lock' showActionIndicator @@ -83,11 +85,7 @@ const SecurityPrivacyView = ({ navigation }) => { title='Log_analytics_events' testID='security-privacy-view-analytics-events' right={() => ( - <Switch - value={analyticsEventsState} - trackColor={SWITCH_TRACK_COLOR} - onValueChange={toggleAnalyticsEvents} - /> + <Switch value={analyticsEventsState} trackColor={SWITCH_TRACK_COLOR} onValueChange={toggleAnalyticsEvents} /> )} /> <List.Separator /> @@ -95,11 +93,7 @@ const SecurityPrivacyView = ({ navigation }) => { title='Send_crash_report' testID='security-privacy-view-crash-report' right={() => ( - <Switch - value={crashReportState} - trackColor={SWITCH_TRACK_COLOR} - onValueChange={toggleCrashReport} - /> + <Switch value={crashReportState} trackColor={SWITCH_TRACK_COLOR} onValueChange={toggleCrashReport} /> )} /> <List.Separator /> diff --git a/app/views/SelectListView.js b/app/views/SelectListView.js index 5767df3f2..9d2f533da 100644 --- a/app/views/SelectListView.js +++ b/app/views/SelectListView.js @@ -1,14 +1,11 @@ import React from 'react'; import PropTypes from 'prop-types'; -import { - View, StyleSheet, FlatList, Text -} from 'react-native'; +import { FlatList, StyleSheet, Text, View } from 'react-native'; import { connect } from 'react-redux'; import { RadioButton } from 'react-native-ui-lib'; import log from '../utils/log'; import * as List from '../containers/List'; -import sharedStyles from './Styles'; import I18n from '../i18n'; import * as HeaderButton from '../containers/HeaderButton'; import StatusBar from '../containers/StatusBar'; @@ -18,7 +15,7 @@ import SafeAreaView from '../containers/SafeAreaView'; import { animateNextTransition } from '../utils/layoutAnimation'; import { ICON_SIZE } from '../containers/List/constants'; import SearchBox from '../containers/SearchBox'; - +import sharedStyles from './Styles'; const styles = StyleSheet.create({ buttonText: { @@ -74,7 +71,7 @@ class SelectListView extends React.Component { ); navigation.setOptions(options); - } + }; renderInfoText = () => { const { theme } = this.props; @@ -83,18 +80,22 @@ class SelectListView extends React.Component { <Text style={[styles.buttonText, { color: themes[theme].bodyText }]}>{I18n.t(this.infoText)}</Text> </View> ); - } + }; renderSearch = () => { const { theme } = this.props; return ( <View style={{ backgroundColor: themes[theme].auxiliaryBackground }}> - <SearchBox onChangeText={text => this.search(text)} testID='select-list-view-search' onCancelPress={() => this.setState({ isSearching: false })} /> + <SearchBox + onChangeText={text => this.search(text)} + testID='select-list-view-search' + onCancelPress={() => this.setState({ isSearching: false })} + /> </View> ); - } + }; - search = async(text) => { + search = async text => { try { this.setState({ isSearching: true }); const result = await this.onSearch(text); @@ -102,14 +103,14 @@ class SelectListView extends React.Component { } catch (e) { log(e); } - } + }; - isChecked = (rid) => { + isChecked = rid => { const { selected } = this.state; return selected.includes(rid); - } + }; - toggleItem = (rid) => { + toggleItem = rid => { const { selected } = this.state; animateNextTransition(); @@ -123,7 +124,7 @@ class SelectListView extends React.Component { const filterSelected = selected.filter(el => el !== rid); this.setState({ selected: filterSelected }, () => this.setHeader()); } - } + }; renderItem = ({ item }) => { const { theme } = this.props; @@ -134,8 +135,21 @@ class SelectListView extends React.Component { const icon = item.teamMain ? teamIcon : channelIcon; const checked = this.isChecked(item.rid) ? 'check' : null; - const showRadio = () => <RadioButton testID={selected ? `radio-button-selected-${ item.name }` : `radio-button-unselected-${ item.name }`} selected={selected.includes(item.rid)} color={themes[theme].actionTintColor} size={ICON_SIZE} />; - const showCheck = () => <List.Icon testID={checked ? `${ item.name }-checked` : `${ item.name }-unchecked`} name={checked} color={themes[theme].actionTintColor} />; + const showRadio = () => ( + <RadioButton + testID={selected ? `radio-button-selected-${item.name}` : `radio-button-unselected-${item.name}`} + selected={selected.includes(item.rid)} + color={themes[theme].actionTintColor} + size={ICON_SIZE} + /> + ); + const showCheck = () => ( + <List.Icon + testID={checked ? `${item.name}-checked` : `${item.name}-unchecked`} + name={checked} + color={themes[theme].actionTintColor} + /> + ); return ( <> @@ -143,7 +157,7 @@ class SelectListView extends React.Component { <List.Item title={item.name} translateTitle={false} - testID={`select-list-view-item-${ item.name }`} + testID={`select-list-view-item-${item.name}`} onPress={() => (item.alert ? this.showAlert() : this.toggleItem(item.rid))} alert={item.alert} left={() => <List.Icon name={icon} color={themes[theme].controlText} />} @@ -151,7 +165,7 @@ class SelectListView extends React.Component { /> </> ); - } + }; render() { const { data, isSearching, dataFiltered } = this.state; diff --git a/app/views/SelectServerView.js b/app/views/SelectServerView.js index b3345c820..0b43747af 100644 --- a/app/views/SelectServerView.js +++ b/app/views/SelectServerView.js @@ -18,12 +18,12 @@ const keyExtractor = item => item.id; class SelectServerView extends React.Component { static navigationOptions = () => ({ title: I18n.t('Select_Server') - }) + }); static propTypes = { server: PropTypes.string, navigation: PropTypes.object - } + }; state = { servers: [] }; @@ -34,27 +34,19 @@ class SelectServerView extends React.Component { this.setState({ servers }); } - select = async(server) => { - const { - server: currentServer, navigation - } = this.props; + select = async server => { + const { server: currentServer, navigation } = this.props; navigation.navigate('ShareListView'); if (currentServer !== server) { await RocketChat.shareExtensionInit(server); } - } + }; renderItem = ({ item }) => { const { server } = this.props; - return ( - <ServerItem - onPress={() => this.select(item.id)} - item={item} - hasCheck={item.id === server} - /> - ); - } + return <ServerItem onPress={() => this.select(item.id)} item={item} hasCheck={item.id === server} />; + }; render() { const { servers } = this.state; @@ -79,8 +71,8 @@ class SelectServerView extends React.Component { } } -const mapStateToProps = (({ share }) => ({ +const mapStateToProps = ({ share }) => ({ server: share.server.server -})); +}); export default connect(mapStateToProps)(SelectServerView); diff --git a/app/views/SelectedUsersView.js b/app/views/SelectedUsersView.js index bd6740e1f..b7b254511 100644 --- a/app/views/SelectedUsersView.js +++ b/app/views/SelectedUsersView.js @@ -1,31 +1,27 @@ import React from 'react'; import PropTypes from 'prop-types'; -import { View, FlatList } from 'react-native'; +import { FlatList, View } from 'react-native'; import { connect } from 'react-redux'; import orderBy from 'lodash/orderBy'; import { Q } from '@nozbe/watermelondb'; -import * as List from '../containers/List'; +import * as List from '../containers/List'; import database from '../lib/database'; import RocketChat from '../lib/rocketchat'; import UserItem from '../presentation/UserItem'; import Loading from '../containers/Loading'; import I18n from '../i18n'; -import log, { logEvent, events } from '../utils/log'; +import log, { events, logEvent } from '../utils/log'; import SearchBox from '../containers/SearchBox'; -import sharedStyles from './Styles'; import * as HeaderButton from '../containers/HeaderButton'; import StatusBar from '../containers/StatusBar'; import { themes } from '../constants/colors'; import { withTheme } from '../theme'; import { getUserSelector } from '../selectors/login'; -import { - reset as resetAction, - addUser as addUserAction, - removeUser as removeUserAction -} from '../actions/selectedUsers'; +import { addUser as addUserAction, removeUser as removeUserAction, reset as resetAction } from '../actions/selectedUsers'; import { showErrorAlert } from '../utils/info'; import SafeAreaView from '../containers/SafeAreaView'; +import sharedStyles from './Styles'; const ITEM_WIDTH = 250; const getItemLayout = (_, index) => ({ length: ITEM_WIDTH, offset: ITEM_WIDTH * index, index }); @@ -84,7 +80,7 @@ class SelectedUsersView extends React.Component { } // showButton can be sent as route params or updated by the component - setHeader = (showButton) => { + setHeader = showButton => { const { navigation, route } = this.props; const title = route.params?.title ?? I18n.t('Select_Users'); const buttonText = route.params?.buttonText ?? I18n.t('Next'); @@ -92,19 +88,18 @@ class SelectedUsersView extends React.Component { const nextAction = route.params?.nextAction ?? (() => {}); const options = { title, - headerRight: () => ( + headerRight: () => (!maxUsers || showButton) && ( <HeaderButton.Container> <HeaderButton.Item title={buttonText} onPress={nextAction} testID='selected-users-view-submit' /> </HeaderButton.Container> ) - ) }; navigation.setOptions(options); - } + }; // eslint-disable-next-line react/sort-comp - init = async() => { + init = async () => { try { const db = database.active; const observable = await db.collections @@ -112,40 +107,43 @@ class SelectedUsersView extends React.Component { .query(Q.where('t', 'd')) .observeWithColumns(['room_updated_at']); - this.querySubscription = observable.subscribe((data) => { + this.querySubscription = observable.subscribe(data => { const chats = orderBy(data, ['roomUpdatedAt'], ['desc']); this.setState({ chats }); }); } catch (e) { log(e); } - } + }; onSearchChangeText(text) { this.search(text); } - search = async(text) => { + search = async text => { const result = await RocketChat.search({ text, filterRooms: false }); this.setState({ search: result }); - } + }; isGroupChat = () => { const { maxUsers } = this.state; return maxUsers > 2; - } + }; - isChecked = (username) => { + isChecked = username => { const { users } = this.props; return users.findIndex(el => el.name === username) !== -1; - } + }; - toggleUser = (user) => { + toggleUser = user => { const { maxUsers } = this.state; const { - addUser, removeUser, users, user: { username } + addUser, + removeUser, + users, + user: { username } } = this.props; // Disallow removing self user from the direct message group @@ -163,7 +161,7 @@ class SelectedUsersView extends React.Component { logEvent(events.SELECTED_USERS_REMOVE_USER); removeUser(user); } - } + }; _onPressItem = (id, item = {}) => { if (item.search) { @@ -171,7 +169,7 @@ class SelectedUsersView extends React.Component { } else { this.toggleUser({ _id: item._id, name: item.name, fname: item.fname }); } - } + }; _onPressSelectedItem = item => this.toggleUser(item); @@ -183,9 +181,9 @@ class SelectedUsersView extends React.Component { {this.renderSelected()} </View> ); - } + }; - setFlatListRef = ref => this.flatlist = ref; + setFlatListRef = ref => (this.flatlist = ref); onContentSizeChange = () => this.flatlist.scrollToEnd({ animated: true }); @@ -211,7 +209,7 @@ class SelectedUsersView extends React.Component { horizontal /> ); - } + }; renderSelectedItem = ({ item }) => { const { baseUrl, user, theme } = this.props; @@ -220,14 +218,14 @@ class SelectedUsersView extends React.Component { name={item.fname} username={item.name} onPress={() => this._onPressSelectedItem(item)} - testID={`selected-user-${ item.name }`} + testID={`selected-user-${item.name}`} baseUrl={baseUrl} style={{ paddingRight: 15 }} user={user} theme={theme} /> ); - } + }; renderItem = ({ item, index }) => { const { search, chats } = this.state; @@ -250,7 +248,7 @@ class SelectedUsersView extends React.Component { name={name} username={username} onPress={() => this._onPressItem(item._id, item)} - testID={`select-users-view-item-${ item.name }`} + testID={`select-users-view-item-${item.name}`} icon={this.isChecked(username) ? 'check' : null} baseUrl={baseUrl} style={style} @@ -258,7 +256,7 @@ class SelectedUsersView extends React.Component { theme={theme} /> ); - } + }; renderList = () => { const { search, chats } = this.state; @@ -281,7 +279,7 @@ class SelectedUsersView extends React.Component { keyboardShouldPersistTaps='always' /> ); - } + }; render = () => { const { loading } = this.props; @@ -292,7 +290,7 @@ class SelectedUsersView extends React.Component { <Loading visible={loading} /> </SafeAreaView> ); - } + }; } const mapStateToProps = state => ({ diff --git a/app/views/SetUsernameView.js b/app/views/SetUsernameView.js index ccae5d036..158c6d013 100644 --- a/app/views/SetUsernameView.js +++ b/app/views/SetUsernameView.js @@ -1,8 +1,6 @@ import React from 'react'; import PropTypes from 'prop-types'; -import { - Text, ScrollView, StyleSheet -} from 'react-native'; +import { ScrollView, StyleSheet, Text } from 'react-native'; import { connect } from 'react-redux'; import Orientation from 'react-native-orientation-locker'; @@ -10,7 +8,6 @@ import { loginRequest as loginRequestAction } from '../actions/login'; import TextInput from '../containers/TextInput'; import Button from '../containers/Button'; import KeyboardView from '../presentation/KeyboardView'; -import sharedStyles from './Styles'; import scrollPersistTaps from '../utils/scrollPersistTaps'; import I18n from '../i18n'; import RocketChat from '../lib/rocketchat'; @@ -21,6 +18,7 @@ import { isTablet } from '../utils/deviceInfo'; import { getUserSelector } from '../selectors/login'; import { showErrorAlert } from '../utils/info'; import SafeAreaView from '../containers/SafeAreaView'; +import sharedStyles from './Styles'; const styles = StyleSheet.create({ loginTitle: { @@ -32,7 +30,7 @@ const styles = StyleSheet.create({ class SetUsernameView extends React.Component { static navigationOptions = ({ route }) => ({ title: route.params?.title - }) + }); static propTypes = { navigation: PropTypes.object, @@ -41,7 +39,7 @@ class SetUsernameView extends React.Component { loginRequest: PropTypes.func, token: PropTypes.string, theme: PropTypes.string - } + }; constructor(props) { super(props); @@ -78,7 +76,7 @@ class SetUsernameView extends React.Component { return false; } - submit = async() => { + submit = async () => { const { username } = this.state; const { loginRequest, token } = this.props; @@ -94,36 +92,20 @@ class SetUsernameView extends React.Component { showErrorAlert(e.message, I18n.t('Oops')); } this.setState({ saving: false }); - } + }; render() { const { username, saving } = this.state; const { theme } = this.props; return ( - <KeyboardView - style={{ backgroundColor: themes[theme].auxiliaryBackground }} - contentContainerStyle={sharedStyles.container} - > + <KeyboardView style={{ backgroundColor: themes[theme].auxiliaryBackground }} contentContainerStyle={sharedStyles.container}> <StatusBar /> <ScrollView {...scrollPersistTaps} contentContainerStyle={sharedStyles.containerScrollView}> <SafeAreaView testID='set-username-view'> - <Text - style={[ - sharedStyles.loginTitle, - sharedStyles.textBold, - styles.loginTitle, - { color: themes[theme].titleText } - ]} - > + <Text style={[sharedStyles.loginTitle, sharedStyles.textBold, styles.loginTitle, { color: themes[theme].titleText }]}> {I18n.t('Username')} </Text> - <Text - style={[ - sharedStyles.loginSubtitle, - sharedStyles.textRegular, - { color: themes[theme].titleText } - ]} - > + <Text style={[sharedStyles.loginSubtitle, sharedStyles.textRegular, { color: themes[theme].titleText }]}> {I18n.t('Set_username_subtitle')} </Text> <TextInput diff --git a/app/views/SettingsView/index.js b/app/views/SettingsView/index.js index 0e8ed7b47..4c62e19bc 100644 --- a/app/views/SettingsView/index.js +++ b/app/views/SettingsView/index.js @@ -1,7 +1,5 @@ import React from 'react'; -import { - Linking, Share, Clipboard -} from 'react-native'; +import { Clipboard, Linking, Share } from 'react-native'; import PropTypes from 'prop-types'; import { connect } from 'react-redux'; import FastImage from '@rocket.chat/react-native-fast-image'; @@ -15,20 +13,16 @@ import StatusBar from '../../containers/StatusBar'; import * as List from '../../containers/List'; import I18n from '../../i18n'; import RocketChat from '../../lib/rocketchat'; -import { - getReadableVersion, getDeviceModel, isAndroid -} from '../../utils/deviceInfo'; +import { getDeviceModel, getReadableVersion, isAndroid } from '../../utils/deviceInfo'; import openLink from '../../utils/openLink'; -import { showErrorAlert, showConfirmationAlert } from '../../utils/info'; -import { logEvent, events } from '../../utils/log'; -import { - PLAY_MARKET_LINK, FDROID_MARKET_LINK, APP_STORE_LINK, LICENSE_LINK -} from '../../constants/links'; +import { showConfirmationAlert, showErrorAlert } from '../../utils/info'; +import { events, logEvent } from '../../utils/log'; +import { APP_STORE_LINK, FDROID_MARKET_LINK, LICENSE_LINK, PLAY_MARKET_LINK } from '../../constants/links'; import { withTheme } from '../../theme'; import SidebarView from '../SidebarView'; import { LISTENER } from '../../containers/Toast'; import EventEmitter from '../../utils/events'; -import { appStart as appStartAction, ROOT_LOADING } from '../../actions/app'; +import { ROOT_LOADING, appStart as appStartAction } from '../../actions/app'; import { onReviewPress } from '../../utils/review'; import SafeAreaView from '../../containers/SafeAreaView'; import database from '../../lib/database'; @@ -37,11 +31,12 @@ import { getUserSelector } from '../../selectors/login'; class SettingsView extends React.Component { static navigationOptions = ({ navigation, isMasterDetail }) => ({ - headerLeft: () => (isMasterDetail ? ( - <HeaderButton.CloseModal navigation={navigation} testID='settings-view-close' /> - ) : ( - <HeaderButton.Drawer navigation={navigation} testID='settings-view-drawer' /> - )), + headerLeft: () => + isMasterDetail ? ( + <HeaderButton.CloseModal navigation={navigation} testID='settings-view-close' /> + ) : ( + <HeaderButton.Drawer navigation={navigation} testID='settings-view-drawer' /> + ), title: I18n.t('Settings') }); @@ -57,9 +52,9 @@ class SettingsView extends React.Component { id: PropTypes.string }), appStart: PropTypes.func - } + }; - checkCookiesAndLogout = async() => { + checkCookiesAndLogout = async () => { const { logout, user } = this.props; const db = database.servers; const usersCollection = db.get('users'); @@ -71,7 +66,7 @@ class SettingsView extends React.Component { message: I18n.t('Clear_cookies_desc'), confirmationText: I18n.t('Clear_cookies_yes'), dismissText: I18n.t('Clear_cookies_no'), - onPress: async() => { + onPress: async () => { await CookieManager.clearAll(true); logout(); }, @@ -85,7 +80,7 @@ class SettingsView extends React.Component { } catch { // Do nothing: user not found } - } + }; handleLogout = () => { logEvent(events.SE_LOG_OUT); @@ -94,16 +89,18 @@ class SettingsView extends React.Component { confirmationText: I18n.t('Logout'), onPress: this.checkCookiesAndLogout }); - } + }; handleClearCache = () => { logEvent(events.SE_CLEAR_LOCAL_SERVER_CACHE); showConfirmationAlert({ message: I18n.t('This_will_clear_all_your_offline_data'), confirmationText: I18n.t('Clear'), - onPress: async() => { + onPress: async () => { const { - server: { server }, appStart, selectServerRequest + server: { server }, + appStart, + selectServerRequest } = this.props; appStart({ root: ROOT_LOADING, text: I18n.t('Clear_cache_loading') }); await RocketChat.clearCache({ server }); @@ -113,29 +110,29 @@ class SettingsView extends React.Component { selectServerRequest(server); } }); - } + }; - navigateToScreen = (screen) => { - logEvent(events[`SE_GO_${ screen.replace('View', '').toUpperCase() }`]); + navigateToScreen = screen => { + logEvent(events[`SE_GO_${screen.replace('View', '').toUpperCase()}`]); const { navigation } = this.props; navigation.navigate(screen); - } + }; - sendEmail = async() => { + sendEmail = async () => { logEvent(events.SE_CONTACT_US); const subject = encodeURI('React Native App Support'); const email = encodeURI('support@rocket.chat'); const description = encodeURI(` - version: ${ getReadableVersion } - device: ${ getDeviceModel } + version: ${getReadableVersion} + device: ${getDeviceModel} `); try { - await Linking.openURL(`mailto:${ email }?subject=${ subject }&body=${ description }`); + await Linking.openURL(`mailto:${email}?subject=${subject}&body=${description}`); } catch (e) { logEvent(events.SE_CONTACT_US_F); showErrorAlert(I18n.t('error-email-send-failed', { message: 'support@rocket.chat' })); } - } + }; shareApp = () => { let message; @@ -148,29 +145,31 @@ class SettingsView extends React.Component { message = APP_STORE_LINK; } Share.share({ message }); - } + }; copyServerVersion = () => { - const { server: { version } } = this.props; + const { + server: { version } + } = this.props; logEvent(events.SE_COPY_SERVER_VERSION, { serverVersion: version }); this.saveToClipboard(version); - } + }; copyAppVersion = () => { logEvent(events.SE_COPY_APP_VERSION, { appVersion: getReadableVersion }); this.saveToClipboard(getReadableVersion); - } + }; - saveToClipboard = async(content) => { + saveToClipboard = async content => { await Clipboard.setString(content); EventEmitter.emit(LISTENER, { message: I18n.t('Copied_to_clipboard') }); - } + }; onPressLicense = () => { logEvent(events.SE_READ_LICENSE); const { theme } = this.props; openLink(LICENSE_LINK, theme); - } + }; render() { const { server, isMasterDetail, theme } = this.props; @@ -200,12 +199,7 @@ class SettingsView extends React.Component { <List.Section> <List.Separator /> - <List.Item - title='Contact_us' - onPress={this.sendEmail} - showActionIndicator - testID='settings-view-contact' - /> + <List.Item title='Contact_us' onPress={this.sendEmail} showActionIndicator testID='settings-view-contact' /> <List.Separator /> <List.Item title='Language' @@ -225,12 +219,7 @@ class SettingsView extends React.Component { </> ) : null} <List.Separator /> - <List.Item - title='Share_this_app' - showActionIndicator - onPress={this.shareApp} - testID='settings-view-share-app' - /> + <List.Item title='Share_this_app' showActionIndicator onPress={this.shareApp} testID='settings-view-share-app' /> <List.Separator /> <List.Item title='Default_browser' @@ -257,12 +246,7 @@ class SettingsView extends React.Component { <List.Section> <List.Separator /> - <List.Item - title='License' - onPress={this.onPressLicense} - showActionIndicator - testID='settings-view-license' - /> + <List.Item title='License' onPress={this.onPressLicense} showActionIndicator testID='settings-view-license' /> <List.Separator /> <List.Item title={I18n.t('Version_no', { version: getReadableVersion })} @@ -274,7 +258,7 @@ class SettingsView extends React.Component { <List.Item title={I18n.t('Server_version', { version: server.version })} onPress={this.copyServerVersion} - subtitle={`${ server.server.split('//')[1] }`} + subtitle={`${server.server.split('//')[1]}`} testID='settings-view-server-version' translateTitle={false} translateSubtitle={false} diff --git a/app/views/ShareListView/Header/Header.android.js b/app/views/ShareListView/Header/Header.android.js index a7fcd0351..727fa5364 100644 --- a/app/views/ShareListView/Header/Header.android.js +++ b/app/views/ShareListView/Header/Header.android.js @@ -1,5 +1,5 @@ import React from 'react'; -import { View, StyleSheet, Text } from 'react-native'; +import { StyleSheet, Text, View } from 'react-native'; import PropTypes from 'prop-types'; import TextInput from '../../../presentation/TextInput'; diff --git a/app/views/ShareListView/Header/Header.ios.js b/app/views/ShareListView/Header/Header.ios.js index afbe15663..d68bacff2 100644 --- a/app/views/ShareListView/Header/Header.ios.js +++ b/app/views/ShareListView/Header/Header.ios.js @@ -1,12 +1,11 @@ import React, { useState } from 'react'; import PropTypes from 'prop-types'; -import { Keyboard, View, StyleSheet } from 'react-native'; +import { Keyboard, StyleSheet, View } from 'react-native'; import ShareExtension from 'rn-extensions-share'; import SearchBox from '../../../containers/SearchBox'; import * as HeaderButton from '../../../containers/HeaderButton'; import { themes } from '../../../constants/colors'; - import sharedStyles from '../../Styles'; import { animateNextTransition } from '../../../utils/layoutAnimation'; @@ -17,12 +16,10 @@ const styles = StyleSheet.create({ } }); -const Header = React.memo(({ - searching, onChangeSearchText, initSearch, cancelSearch, theme -}) => { +const Header = React.memo(({ searching, onChangeSearchText, initSearch, cancelSearch, theme }) => { const [text, setText] = useState(''); - const onChangeText = (searchText) => { + const onChangeText = searchText => { onChangeSearchText(searchText); setText(searchText); }; @@ -47,18 +44,8 @@ const Header = React.memo(({ borderColor: themes[theme].separatorColor, backgroundColor: themes[theme].headerBackground } - ]} - > - { - !searching - ? ( - <HeaderButton.CancelModal - onPress={ShareExtension.close} - testID='share-extension-close' - /> - ) - : null - } + ]}> + {!searching ? <HeaderButton.CancelModal onPress={ShareExtension.close} testID='share-extension-close' /> : null} <SearchBox value={text} hasCancel={searching} diff --git a/app/views/ShareListView/Header/index.js b/app/views/ShareListView/Header/index.js index eff18acda..d66c9a804 100644 --- a/app/views/ShareListView/Header/index.js +++ b/app/views/ShareListView/Header/index.js @@ -3,10 +3,8 @@ import PropTypes from 'prop-types'; import Header from './Header'; -const ShareListHeader = React.memo(({ - searching, initSearch, cancelSearch, search, theme -}) => { - const onSearchChangeText = (text) => { +const ShareListHeader = React.memo(({ searching, initSearch, cancelSearch, search, theme }) => { + const onSearchChangeText = text => { search(text.trim()); }; diff --git a/app/views/ShareListView/index.js b/app/views/ShareListView/index.js index 38f3aff72..e0a82a50d 100644 --- a/app/views/ShareListView/index.js +++ b/app/views/ShareListView/index.js @@ -1,8 +1,6 @@ import React from 'react'; import PropTypes from 'prop-types'; -import { - View, Text, FlatList, Keyboard, BackHandler, PermissionsAndroid, ScrollView -} from 'react-native'; +import { BackHandler, FlatList, Keyboard, PermissionsAndroid, ScrollView, Text, View } from 'react-native'; import ShareExtension from 'rn-extensions-share'; import * as FileSystem from 'expo-file-system'; import { connect } from 'react-redux'; @@ -11,21 +9,21 @@ import { dequal } from 'dequal'; import { Q } from '@nozbe/watermelondb'; import database from '../../lib/database'; -import { isIOS, isAndroid } from '../../utils/deviceInfo'; +import { isAndroid, isIOS } from '../../utils/deviceInfo'; import I18n from '../../i18n'; import DirectoryItem, { ROW_HEIGHT } from '../../presentation/DirectoryItem'; import ServerItem from '../../presentation/ServerItem'; import * as HeaderButton from '../../containers/HeaderButton'; -import ShareListHeader from './Header'; import ActivityIndicator from '../../containers/ActivityIndicator'; import * as List from '../../containers/List'; -import styles from './styles'; import { themes } from '../../constants/colors'; import { animateNextTransition } from '../../utils/layoutAnimation'; import { withTheme } from '../../theme'; import SafeAreaView from '../../containers/SafeAreaView'; import RocketChat from '../../lib/rocketchat'; import { sanitizeLikeString } from '../../lib/database/utils'; +import styles from './styles'; +import ShareListHeader from './Header'; const permission = { title: I18n.t('Read_External_Permission'), @@ -42,7 +40,7 @@ class ShareListView extends React.Component { token: PropTypes.string, userId: PropTypes.string, theme: PropTypes.string - } + }; constructor(props) { super(props); @@ -60,8 +58,12 @@ class ShareListView extends React.Component { }; this.setHeader(); if (isAndroid) { - this.unsubscribeFocus = props.navigation.addListener('focus', () => BackHandler.addEventListener('hardwareBackPress', this.handleBackPress)); - this.unsubscribeBlur = props.navigation.addListener('blur', () => BackHandler.removeEventListener('hardwareBackPress', this.handleBackPress)); + this.unsubscribeFocus = props.navigation.addListener('focus', () => + BackHandler.addEventListener('hardwareBackPress', this.handleBackPress) + ); + this.unsubscribeBlur = props.navigation.addListener('blur', () => + BackHandler.removeEventListener('hardwareBackPress', this.handleBackPress) + ); } } @@ -72,7 +74,11 @@ class ShareListView extends React.Component { if (isAndroid) { await this.askForPermission(data); } - const info = await Promise.all(data.filter(item => item.type === 'media').map(file => FileSystem.getInfoAsync(this.uriToPath(file.value), { size: true }))); + const info = await Promise.all( + data + .filter(item => item.type === 'media') + .map(file => FileSystem.getInfoAsync(this.uriToPath(file.value), { size: true })) + ); const attachments = info.map(file => ({ filename: decodeURIComponent(file.uri.substring(file.uri.lastIndexOf('/') + 1)), description: '', @@ -80,7 +86,7 @@ class ShareListView extends React.Component { mime: mime.lookup(file.uri), path: file.uri })); - const text = data.filter(item => item.type === 'text').reduce((acc, item) => `${ item.value }\n${ acc }`, ''); + const text = data.filter(item => item.type === 'text').reduce((acc, item) => `${item.value}\n${acc}`, ''); this.setState({ text, attachments @@ -154,30 +160,23 @@ class ShareListView extends React.Component { } navigation.setOptions({ - headerLeft: () => (searching - ? ( + headerLeft: () => + searching ? ( <HeaderButton.Container left> <HeaderButton.Item title='cancel' iconName='close' onPress={this.cancelSearch} /> </HeaderButton.Container> - ) - : ( - <HeaderButton.CancelModal - onPress={ShareExtension.close} - testID='share-extension-close' - /> - )), + ) : ( + <HeaderButton.CancelModal onPress={ShareExtension.close} testID='share-extension-close' /> + ), headerTitle: () => <ShareListHeader searching={searching} search={this.search} theme={theme} />, - headerRight: () => ( - searching - ? null - : ( - <HeaderButton.Container> - <HeaderButton.Item iconName='search' onPress={this.initSearch} /> - </HeaderButton.Container> - ) - ) + headerRight: () => + searching ? null : ( + <HeaderButton.Container> + <HeaderButton.Item iconName='search' onPress={this.initSearch} /> + </HeaderButton.Container> + ) }); - } + }; // eslint-disable-next-line react/sort-comp internalSetState = (...args) => { @@ -186,9 +185,9 @@ class ShareListView extends React.Component { animateNextTransition(); } this.setState(...args); - } + }; - query = async(text) => { + query = async text => { const db = database.active; const defaultWhereClause = [ Q.where('archived', false), @@ -199,14 +198,12 @@ class ShareListView extends React.Component { ]; if (text) { const likeString = sanitizeLikeString(text); - defaultWhereClause.push( - Q.or( - Q.where('name', Q.like(`%${ likeString }%`)), - Q.where('fname', Q.like(`%${ likeString }%`)) - ) - ); + defaultWhereClause.push(Q.or(Q.where('name', Q.like(`%${likeString}%`)), Q.where('fname', Q.like(`%${likeString}%`)))); } - const data = await db.get('subscriptions').query(...defaultWhereClause).fetch(); + const data = await db + .get('subscriptions') + .query(...defaultWhereClause) + .fetch(); return data.map(item => ({ rid: item.rid, t: item.t, @@ -219,9 +216,9 @@ class ShareListView extends React.Component { usernames: item.usernames, topic: item.topic })); - } + }; - getSubscriptions = async(server) => { + getSubscriptions = async server => { const serversDB = database.servers; if (server) { @@ -245,7 +242,7 @@ class ShareListView extends React.Component { } }; - askForPermission = async(data) => { + askForPermission = async data => { const mediaIndex = data.findIndex(item => item.type === 'media'); if (mediaIndex !== -1) { const result = await PermissionsAndroid.request(PermissionsAndroid.PERMISSIONS.READ_EXTERNAL_STORAGE, permission); @@ -256,17 +253,17 @@ class ShareListView extends React.Component { } this.setState({ needsPermission: false }); return Promise.resolve(); - } + }; uriToPath = uri => decodeURIComponent(isIOS ? uri.replace(/^file:\/\//, '') : uri); - getRoomTitle = (item) => { + getRoomTitle = item => { const { serverInfo } = this.state; const { useRealName } = serverInfo; return ((item.prid || useRealName) && item.fname) || item.name; - } + }; - shareMessage = (room) => { + shareMessage = room => { const { attachments, text, serverInfo } = this.state; const { navigation } = this.props; @@ -277,25 +274,25 @@ class ShareListView extends React.Component { serverInfo, isShareExtension: true }); - } + }; - search = async(text) => { + search = async text => { const result = await this.query(text); this.internalSetState({ searchResults: result, searchText: text }); - } + }; initSearch = () => { const { chats } = this.state; this.setState({ searching: true, searchResults: chats }, () => this.setHeader()); - } + }; cancelSearch = () => { this.internalSetState({ searching: false, searchResults: [], searchText: '' }, () => this.setHeader()); Keyboard.dismiss(); - } + }; handleBackPress = () => { const { searching } = this.state; @@ -304,9 +301,9 @@ class ShareListView extends React.Component { return true; } return false; - } + }; - renderSectionHeader = (header) => { + renderSectionHeader = header => { const { searching } = this.state; const { theme } = this.props; if (searching) { @@ -316,21 +313,17 @@ class ShareListView extends React.Component { return ( <> <View style={[styles.headerContainer, { backgroundColor: themes[theme].auxiliaryBackground }]}> - <Text style={[styles.headerText, { color: themes[theme].titleText }]}> - {I18n.t(header)} - </Text> + <Text style={[styles.headerText, { color: themes[theme].titleText }]}>{I18n.t(header)}</Text> </View> <List.Separator /> </> ); - } + }; renderItem = ({ item }) => { const { serverInfo } = this.state; const { useRealName } = serverInfo; - const { - userId, token, server, theme - } = this.props; + const { userId, token, server, theme } = this.props; let description; switch (item.t) { case 'c': @@ -358,11 +351,11 @@ class ShareListView extends React.Component { description={description} type={item.prid ? 'discussion' : item.t} onPress={() => this.shareMessage(item)} - testID={`share-extension-item-${ item.name }`} + testID={`share-extension-item-${item.name}`} theme={theme} /> ); - } + }; renderSelectServer = () => { const { serverInfo } = this.state; @@ -370,14 +363,11 @@ class ShareListView extends React.Component { return ( <> {this.renderSectionHeader('Select_Server')} - <ServerItem - onPress={() => navigation.navigate('SelectServerView')} - item={serverInfo} - /> + <ServerItem onPress={() => navigation.navigate('SelectServerView')} item={serverInfo} /> <List.Separator /> </> ); - } + }; renderEmptyComponent = () => { const { theme } = this.props; @@ -386,7 +376,7 @@ class ShareListView extends React.Component { <Text style={[styles.title, { color: themes[theme].titleText }]}>{I18n.t('No_results_found')}</Text> </View> ); - } + }; renderHeader = () => { const { searching, serversCount } = this.state; @@ -405,12 +395,10 @@ class ShareListView extends React.Component { {this.renderSectionHeader('Chats')} </> ); - } + }; render = () => { - const { - chats, loading, searchResults, searching, searchText, needsPermission - } = this.state; + const { chats, loading, searchResults, searching, searchText, needsPermission } = this.state; const { theme } = this.props; if (loading) { @@ -422,8 +410,7 @@ class ShareListView extends React.Component { <SafeAreaView> <ScrollView style={{ backgroundColor: themes[theme].backgroundColor }} - contentContainerStyle={[styles.container, styles.centered, { backgroundColor: themes[theme].backgroundColor }]} - > + contentContainerStyle={[styles.container, styles.centered, { backgroundColor: themes[theme].backgroundColor }]}> <Text style={[styles.permissionTitle, { color: themes[theme].titleText }]}>{permission.title}</Text> <Text style={[styles.permissionMessage, { color: themes[theme].bodyText }]}>{permission.message}</Text> </ScrollView> @@ -449,13 +436,13 @@ class ShareListView extends React.Component { /> </SafeAreaView> ); - } + }; } -const mapStateToProps = (({ share }) => ({ +const mapStateToProps = ({ share }) => ({ userId: share.user && share.user.id, token: share.user && share.user.token, server: share.server.server -})); +}); export default connect(mapStateToProps)(withTheme(ShareListView)); diff --git a/app/views/ShareListView/styles.js b/app/views/ShareListView/styles.js index 5f5687dc7..2c08ee9f9 100644 --- a/app/views/ShareListView/styles.js +++ b/app/views/ShareListView/styles.js @@ -1,4 +1,5 @@ import { StyleSheet } from 'react-native'; + import { isIOS } from '../../utils/deviceInfo'; import sharedStyles from '../Styles'; diff --git a/app/views/ShareView/Header.js b/app/views/ShareView/Header.js index 85ef08784..a52fbf453 100644 --- a/app/views/ShareView/Header.js +++ b/app/views/ShareView/Header.js @@ -1,6 +1,6 @@ import React from 'react'; import PropTypes from 'prop-types'; -import { View, Text, StyleSheet } from 'react-native'; +import { StyleSheet, Text, View } from 'react-native'; import I18n from '../../i18n'; import { CustomIcon } from '../../lib/Icons'; @@ -76,16 +76,11 @@ const Header = React.memo(({ room, thread, theme }) => { <View style={styles.container}> <View style={styles.inner}> <Text numberOfLines={1} style={styles.text}> - <Text style={[styles.text, { color: textColor }]} numberOfLines={1}>{I18n.t('Sending_to')} </Text> - <CustomIcon - name={icon} - size={16} - color={textColor} - /> - <Text - style={[styles.name, { color: textColor }]} - numberOfLines={1} - > + <Text style={[styles.text, { color: textColor }]} numberOfLines={1}> + {I18n.t('Sending_to')}{' '} + </Text> + <CustomIcon name={icon} size={16} color={textColor} /> + <Text style={[styles.name, { color: textColor }]} numberOfLines={1}> {title} </Text> </Text> diff --git a/app/views/ShareView/Preview.js b/app/views/ShareView/Preview.js index 639f41c4c..d559640f4 100644 --- a/app/views/ShareView/Preview.js +++ b/app/views/ShareView/Preview.js @@ -2,7 +2,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import { Video } from 'expo-av'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; -import { ScrollView, Text, StyleSheet } from 'react-native'; +import { ScrollView, StyleSheet, Text } from 'react-native'; import prettyBytes from 'pretty-bytes'; import { CustomIcon } from '../../lib/Icons'; @@ -10,11 +10,11 @@ import { ImageViewer, types } from '../../presentation/ImageViewer'; import { themes } from '../../constants/colors'; import { useDimensions, useOrientation } from '../../dimensions'; import { getHeaderHeight } from '../../containers/Header'; -import { THUMBS_HEIGHT } from './constants'; import sharedStyles from '../Styles'; -import { allowPreview } from './utils'; import I18n from '../../i18n'; import { isAndroid } from '../../utils/deviceInfo'; +import { allowPreview } from './utils'; +import { THUMBS_HEIGHT } from './constants'; const MESSAGEBOX_HEIGHT = 56; @@ -35,32 +35,23 @@ const styles = StyleSheet.create({ } }); -const IconPreview = React.memo(({ - iconName, title, description, theme, width, height, danger -}) => ( +const IconPreview = React.memo(({ iconName, title, description, theme, width, height, danger }) => ( <ScrollView style={{ backgroundColor: themes[theme].auxiliaryBackground }} - contentContainerStyle={[styles.fileContainer, { width, height }]} - > - <CustomIcon - name={iconName} - size={56} - color={danger ? themes[theme].dangerColor : themes[theme].tintColor} - /> + contentContainerStyle={[styles.fileContainer, { width, height }]}> + <CustomIcon name={iconName} size={56} color={danger ? themes[theme].dangerColor : themes[theme].tintColor} /> <Text style={[styles.fileName, { color: themes[theme].titleText }]}>{title}</Text> {description ? <Text style={[styles.fileSize, { color: themes[theme].bodyText }]}>{description}</Text> : null} </ScrollView> )); -const Preview = React.memo(({ - item, theme, isShareExtension, length -}) => { +const Preview = React.memo(({ item, theme, isShareExtension, length }) => { const type = item?.mime; const { width, height } = useDimensions(); const { isLandscape } = useOrientation(); const insets = useSafeAreaInsets(); const headerHeight = getHeaderHeight(isLandscape); - const thumbsHeight = (length > 1) ? THUMBS_HEIGHT : 0; + const thumbsHeight = length > 1 ? THUMBS_HEIGHT : 0; const calculatedHeight = height - insets.top - insets.bottom - MESSAGEBOX_HEIGHT - thumbsHeight - headerHeight; if (item?.canUpload) { diff --git a/app/views/ShareView/Thumbs.js b/app/views/ShareView/Thumbs.js index 26bc54fb4..758433e3f 100644 --- a/app/views/ShareView/Thumbs.js +++ b/app/views/ShareView/Thumbs.js @@ -1,9 +1,7 @@ import React from 'react'; import PropTypes from 'prop-types'; -import { - FlatList, Image, View, StyleSheet -} from 'react-native'; -import { RectButton, TouchableOpacity, TouchableNativeFeedback } from 'react-native-gesture-handler'; +import { FlatList, Image, StyleSheet, View } from 'react-native'; +import { RectButton, TouchableNativeFeedback, TouchableOpacity } from 'react-native-gesture-handler'; import { BUTTON_HIT_SLOP } from '../../containers/message/utils'; import { themes } from '../../constants/colors'; @@ -70,20 +68,11 @@ const ThumbContent = React.memo(({ item, theme, isShareExtension }) => { if (type?.match(/image/)) { // Disallow preview of images too big in order to prevent memory issues on iOS share extension if (allowPreview(isShareExtension, item?.size)) { - return ( - <Image - source={{ uri: item.path }} - style={[styles.thumb, { borderColor: themes[theme].borderColor }]} - /> - ); + return <Image source={{ uri: item.path }} style={[styles.thumb, { borderColor: themes[theme].borderColor }]} />; } else { return ( <View style={[styles.thumb, { borderColor: themes[theme].borderColor }]}> - <CustomIcon - name='image' - size={30} - color={themes[theme].tintColor} - /> + <CustomIcon name='image' size={30} color={themes[theme].tintColor} /> </View> ); } @@ -93,11 +82,7 @@ const ThumbContent = React.memo(({ item, theme, isShareExtension }) => { if (isIOS) { return ( <View style={[styles.thumb, { borderColor: themes[theme].borderColor }]}> - <CustomIcon - name='camera' - size={30} - color={themes[theme].tintColor} - /> + <CustomIcon name='camera' size={30} color={themes[theme].tintColor} /> </View> ); } else { @@ -105,12 +90,7 @@ const ThumbContent = React.memo(({ item, theme, isShareExtension }) => { return ( <> <Image source={{ uri }} style={styles.thumb} /> - <CustomIcon - name='camera-filled' - size={20} - color={themes[theme].buttonText} - style={styles.videoThumbIcon} - /> + <CustomIcon name='camera-filled' size={20} color={themes[theme].buttonText} style={styles.videoThumbIcon} /> </> ); } @@ -120,46 +100,28 @@ const ThumbContent = React.memo(({ item, theme, isShareExtension }) => { return null; }); -const Thumb = ({ - item, theme, isShareExtension, onPress, onRemove -}) => ( +const Thumb = ({ item, theme, isShareExtension, onPress, onRemove }) => ( <ThumbButton style={styles.item} onPress={() => onPress(item)} activeOpacity={0.7}> <> - <ThumbContent - item={item} - theme={theme} - isShareExtension={isShareExtension} - /> + <ThumbContent item={item} theme={theme} isShareExtension={isShareExtension} /> <RectButton hitSlop={BUTTON_HIT_SLOP} style={[styles.removeButton, { backgroundColor: themes[theme].bodyText, borderColor: themes[theme].auxiliaryBackground }]} activeOpacity={1} rippleColor={themes[theme].bannerBackground} - onPress={() => onRemove(item)} - > + onPress={() => onRemove(item)}> <View style={[styles.removeView, { borderColor: themes[theme].auxiliaryBackground }]}> - <CustomIcon - name='close' - color={themes[theme].backgroundColor} - size={14} - /> + <CustomIcon name='close' color={themes[theme].backgroundColor} size={14} /> </View> </RectButton> {!item?.canUpload ? ( - <CustomIcon - name='warning' - size={20} - color={themes[theme].dangerColor} - style={styles.dangerIcon} - /> + <CustomIcon name='warning' size={20} color={themes[theme].dangerColor} style={styles.dangerIcon} /> ) : null} </> </ThumbButton> ); -const Thumbs = React.memo(({ - attachments, theme, isShareExtension, onPress, onRemove -}) => { +const Thumbs = React.memo(({ attachments, theme, isShareExtension, onPress, onRemove }) => { if (attachments?.length > 1) { return ( <FlatList diff --git a/app/views/ShareView/index.js b/app/views/ShareView/index.js index 765860036..473b518df 100644 --- a/app/views/ShareView/index.js +++ b/app/views/ShareView/index.js @@ -1,22 +1,18 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; -import { View, Text, NativeModules } from 'react-native'; +import { NativeModules, Text, View } from 'react-native'; import { connect } from 'react-redux'; import ShareExtension from 'rn-extensions-share'; import { themes } from '../../constants/colors'; import I18n from '../../i18n'; -import styles from './styles'; import Loading from '../../containers/Loading'; import * as HeaderButton from '../../containers/HeaderButton'; import { isBlocked } from '../../utils/room'; import { isReadOnly } from '../../utils/isReadOnly'; import { withTheme } from '../../theme'; -import Header from './Header'; import RocketChat from '../../lib/rocketchat'; import TextInput from '../../containers/TextInput'; -import Preview from './Preview'; -import Thumbs from './Thumbs'; import MessageBox from '../../containers/MessageBox'; import SafeAreaView from '../../containers/SafeAreaView'; import { getUserSelector } from '../../selectors/login'; @@ -24,6 +20,10 @@ import StatusBar from '../../containers/StatusBar'; import database from '../../lib/database'; import { canUploadFile } from '../../utils/media'; import { isAndroid } from '../../utils/deviceInfo'; +import Thumbs from './Thumbs'; +import Preview from './Preview'; +import Header from './Header'; +import styles from './styles'; class ShareView extends Component { constructor(props) { @@ -47,20 +47,18 @@ class ShareView extends Component { this.getServerInfo(); } - componentDidMount = async() => { + componentDidMount = async () => { const readOnly = await this.getReadOnly(); const { attachments, selected } = await this.getAttachments(); this.setState({ readOnly, attachments, selected }, () => this.setHeader()); - } + }; componentWillUnmount = () => { - console.countReset(`${ this.constructor.name }.render calls`); - } + console.countReset(`${this.constructor.name}.render calls`); + }; setHeader = () => { - const { - room, thread, readOnly, attachments - } = this.state; + const { room, thread, readOnly, attachments } = this.state; const { navigation, theme } = this.props; const options = { @@ -71,7 +69,9 @@ class ShareView extends Component { // if is share extension show default back button if (!this.isShareExtension) { - options.headerLeft = () => <HeaderButton.CloseModal navigation={navigation} buttonStyle={{ color: themes[theme].previewTintColor }} />; + options.headerLeft = () => ( + <HeaderButton.CloseModal navigation={navigation} buttonStyle={{ color: themes[theme].previewTintColor }} /> + ); } if (!attachments.length && !readOnly) { @@ -89,10 +89,10 @@ class ShareView extends Component { options.headerBackground = () => <View style={[styles.container, { backgroundColor: themes[theme].previewBackground }]} />; navigation.setOptions(options); - } + }; // fetch server info - getServerInfo = async() => { + getServerInfo = async () => { const { server } = this.props; const serversDB = database.servers; const serversCollection = serversDB.get('servers'); @@ -101,47 +101,49 @@ class ShareView extends Component { } catch (error) { // Do nothing } - } + }; - getReadOnly = async() => { + getReadOnly = async () => { const { room } = this.state; const { user } = this.props; const readOnly = await isReadOnly(room, user); return readOnly; - } + }; - getAttachments = async() => { + getAttachments = async () => { const { mediaAllowList, maxFileSize } = this.state; - const items = await Promise.all(this.files.map(async(item) => { - // Check server settings - const { success: canUpload, error } = canUploadFile(item, mediaAllowList, maxFileSize); - item.canUpload = canUpload; - item.error = error; + const items = await Promise.all( + this.files.map(async item => { + // Check server settings + const { success: canUpload, error } = canUploadFile(item, mediaAllowList, maxFileSize); + item.canUpload = canUpload; + item.error = error; - // get video thumbnails - if (isAndroid && this.files.length > 1 && item.mime?.match?.(/video/)) { - try { - const VideoThumbnails = require('expo-video-thumbnails'); - const { uri } = await VideoThumbnails.getThumbnailAsync(item.path); - item.uri = uri; - } catch { - // Do nothing + // get video thumbnails + if (isAndroid && this.files.length > 1 && item.mime?.match?.(/video/)) { + try { + const VideoThumbnails = require('expo-video-thumbnails'); + const { uri } = await VideoThumbnails.getThumbnailAsync(item.path); + item.uri = uri; + } catch { + // Do nothing + } } - } - // Set a filename, if there isn't any - if (!item.filename) { - item.filename = new Date().toISOString(); - } - return item; - })); + // Set a filename, if there isn't any + if (!item.filename) { + item.filename = new Date().toISOString(); + } + return item; + }) + ); return { attachments: items, selected: items[0] }; - } + }; - send = async() => { + send = async () => { const { loading, selected } = this.state; if (loading) { return; @@ -150,16 +152,14 @@ class ShareView extends Component { // update state await this.selectFile(selected); - const { - attachments, room, text, thread - } = this.state; + const { attachments, room, text, thread } = this.state; const { navigation, server, user } = this.props; // if it's share extension this should show loading if (this.isShareExtension) { this.setState({ loading: true }); - // if it's not share extension this can close + // if it's not share extension this can close } else { navigation.pop(); } @@ -167,34 +167,29 @@ class ShareView extends Component { try { // Send attachment if (attachments.length) { - await Promise.all(attachments.map(({ - filename: name, - mime: type, - description, - size, - path, - canUpload - }) => { - if (canUpload) { - return RocketChat.sendFileMessage( - room.rid, - { - name, - description, - size, - type, - path, - store: 'Uploads' - }, - thread?.id, - server, - { id: user.id, token: user.token } - ); - } - return Promise.resolve(); - })); + await Promise.all( + attachments.map(({ filename: name, mime: type, description, size, path, canUpload }) => { + if (canUpload) { + return RocketChat.sendFileMessage( + room.rid, + { + name, + description, + size, + type, + path, + store: 'Uploads' + }, + thread?.id, + server, + { id: user.id, token: user.token } + ); + } + return Promise.resolve(); + }) + ); - // Send text message + // Send text message } else if (text.length) { await RocketChat.sendMessage(room.rid, text, thread?.id, { id: user.id, token: user.token }); } @@ -208,11 +203,11 @@ class ShareView extends Component { } }; - selectFile = (item) => { + selectFile = item => { const { attachments, selected } = this.state; if (attachments.length > 0) { const { text } = this.messagebox.current; - const newAttachments = attachments.map((att) => { + const newAttachments = attachments.map(att => { if (att.path === selected.path) { att.description = text; } @@ -220,9 +215,9 @@ class ShareView extends Component { }); return this.setState({ attachments: newAttachments, selected: item }); } - } + }; - removeFile = (item) => { + removeFile = item => { const { selected, attachments } = this.state; let newSelected; if (item.path === selected.path) { @@ -230,7 +225,7 @@ class ShareView extends Component { // Selects the next one, if available if (attachments[selectedIndex + 1]?.path) { newSelected = attachments[selectedIndex + 1]; - // If it's the last thumb, selects the previous one + // If it's the last thumb, selects the previous one } else { newSelected = attachments[selectedIndex - 1] || {}; } @@ -238,16 +233,14 @@ class ShareView extends Component { this.setState({ attachments: attachments.filter(att => att.path !== item.path), selected: newSelected ?? selected }, () => { this.messagebox?.current?.forceUpdate?.(); }); - } + }; - onChangeText = (text) => { + onChangeText = text => { this.setState({ text }); - } + }; renderContent = () => { - const { - attachments, selected, room, text - } = this.state; + const { attachments, selected, room, text } = this.state; const { theme, navigation } = this.props; if (attachments.length) { @@ -273,8 +266,7 @@ class ShareView extends Component { navigation={navigation} isFocused={navigation.isFocused} iOSScrollBehavior={NativeModules.KeyboardTrackingViewManager?.KeyboardTrackingScrollBehaviorNone} - isActionsEnabled={false} - > + isActionsEnabled={false}> <Thumbs attachments={attachments} theme={theme} @@ -290,11 +282,7 @@ class ShareView extends Component { return ( <TextInput containerStyle={styles.inputContainer} - inputStyle={[ - styles.input, - styles.textInput, - { backgroundColor: themes[theme].focusedBackground } - ]} + inputStyle={[styles.input, styles.textInput, { backgroundColor: themes[theme].focusedBackground }]} placeholder='' onChangeText={this.onChangeText} defaultValue='' @@ -308,7 +296,7 @@ class ShareView extends Component { }; render() { - console.count(`${ this.constructor.name }.render calls`); + console.count(`${this.constructor.name}.render calls`); const { readOnly, room, loading } = this.state; const { theme } = this.props; if (readOnly || isBlocked(room)) { @@ -321,9 +309,7 @@ class ShareView extends Component { ); } return ( - <SafeAreaView - style={{ backgroundColor: themes[theme].backgroundColor }} - > + <SafeAreaView style={{ backgroundColor: themes[theme].backgroundColor }}> <StatusBar barStyle='light-content' backgroundColor={themes[theme].previewBackground} /> {this.renderContent()} <Loading visible={loading} /> diff --git a/app/views/SidebarView/SidebarItem.js b/app/views/SidebarView/SidebarItem.js index 7959438c2..4e0f9c1a1 100644 --- a/app/views/SidebarView/SidebarItem.js +++ b/app/views/SidebarView/SidebarItem.js @@ -1,33 +1,26 @@ import React from 'react'; -import { View, Text } from 'react-native'; +import { Text, View } from 'react-native'; import PropTypes from 'prop-types'; -import styles from './styles'; import Touch from '../../utils/touch'; import { themes } from '../../constants/colors'; import { withTheme } from '../../theme'; +import styles from './styles'; -const Item = React.memo(({ - left, right, text, onPress, testID, current, theme -}) => ( +const Item = React.memo(({ left, right, text, onPress, testID, current, theme }) => ( <Touch key={testID} testID={testID} onPress={onPress} theme={theme} - style={[styles.item, current && { backgroundColor: themes[theme].borderColor }]} - > - <View style={styles.itemHorizontal}> - {left} - </View> + style={[styles.item, current && { backgroundColor: themes[theme].borderColor }]}> + <View style={styles.itemHorizontal}>{left}</View> <View style={styles.itemCenter}> <Text style={[styles.itemText, { color: themes[theme].titleText }]} numberOfLines={1}> {text} </Text> </View> - <View style={styles.itemHorizontal}> - {right} - </View> + <View style={styles.itemHorizontal}>{right}</View> </Touch> )); diff --git a/app/views/SidebarView/index.js b/app/views/SidebarView/index.js index 5bb465fd0..975d8a896 100644 --- a/app/views/SidebarView/index.js +++ b/app/views/SidebarView/index.js @@ -1,23 +1,22 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; -import { - ScrollView, Text, View, TouchableWithoutFeedback -} from 'react-native'; +import { ScrollView, Text, TouchableWithoutFeedback, View } from 'react-native'; import { connect } from 'react-redux'; import { dequal } from 'dequal'; + import Avatar from '../../containers/Avatar'; import Status from '../../containers/Status/Status'; -import { logEvent, events } from '../../utils/log'; +import { events, logEvent } from '../../utils/log'; import I18n from '../../i18n'; import scrollPersistTaps from '../../utils/scrollPersistTaps'; import { CustomIcon } from '../../lib/Icons'; -import styles from './styles'; -import SidebarItem from './SidebarItem'; import { themes } from '../../constants/colors'; import { withTheme } from '../../theme'; import { getUserSelector } from '../../selectors/login'; import SafeAreaView from '../../containers/SafeAreaView'; import Navigation from '../../lib/Navigation'; +import SidebarItem from './SidebarItem'; +import styles from './styles'; const Separator = React.memo(({ theme }) => <View style={[styles.separator, { borderColor: themes[theme].separatorColor }]} />); Separator.propTypes = { @@ -40,7 +39,7 @@ class Sidebar extends Component { viewRoomAdministrationPermission: PropTypes.object, viewUserAdministrationPermission: PropTypes.object, viewPrivilegedSettingPermission: PropTypes.object - } + }; constructor(props) { super(props); @@ -52,7 +51,17 @@ class Sidebar extends Component { shouldComponentUpdate(nextProps, nextState) { const { showStatus, isAdmin } = this.state; const { - Site_Name, user, baseUrl, state, isMasterDetail, useRealName, theme, viewStatisticsPermission, viewRoomAdministrationPermission, viewUserAdministrationPermission, viewPrivilegedSettingPermission + Site_Name, + user, + baseUrl, + state, + isMasterDetail, + useRealName, + theme, + viewStatisticsPermission, + viewRoomAdministrationPermission, + viewUserAdministrationPermission, + viewPrivilegedSettingPermission } = this.props; // Drawer navigation state if (state?.index !== nextProps.state?.index) { @@ -100,33 +109,38 @@ class Sidebar extends Component { return false; } - getIsAdmin() { const { - user, viewStatisticsPermission, viewRoomAdministrationPermission, viewUserAdministrationPermission, viewPrivilegedSettingPermission + user, + viewStatisticsPermission, + viewRoomAdministrationPermission, + viewUserAdministrationPermission, + viewPrivilegedSettingPermission } = this.props; const { roles } = user; - const allPermissions = [viewStatisticsPermission, viewRoomAdministrationPermission, viewUserAdministrationPermission, viewPrivilegedSettingPermission]; + const allPermissions = [ + viewStatisticsPermission, + viewRoomAdministrationPermission, + viewUserAdministrationPermission, + viewPrivilegedSettingPermission + ]; let isAdmin = false; - if (roles) { + if (roles) { isAdmin = allPermissions.reduce((result, permission) => { if (permission) { - return ( - result || permission.some(r => roles.indexOf(r) !== -1) - ); + return result || permission.some(r => roles.indexOf(r) !== -1); } return result; - }, - false); + }, false); } return isAdmin; } - sidebarNavigate = (route) => { - logEvent(events[`SIDEBAR_GO_${ route.replace('StackNavigator', '').replace('View', '').toUpperCase() }`]); + sidebarNavigate = route => { + logEvent(events[`SIDEBAR_GO_${route.replace('StackNavigator', '').replace('View', '').toUpperCase()}`]); Navigation.navigate(route); - } + }; get currentItemKey() { const { state } = this.props; @@ -139,7 +153,7 @@ class Sidebar extends Component { return; } navigation.closeDrawer(); - } + }; renderAdmin = () => { const { theme, isMasterDetail } = this.props; @@ -159,7 +173,7 @@ class Sidebar extends Component { /> </> ); - } + }; renderNavigation = () => { const { theme } = this.props; @@ -189,7 +203,7 @@ class Sidebar extends Component { {this.renderAdmin()} </> ); - } + }; renderCustomStatus = () => { const { user, theme } = this.props; @@ -202,12 +216,10 @@ class Sidebar extends Component { testID='sidebar-custom-status' /> ); - } + }; render() { - const { - user, Site_Name, baseUrl, useRealName, allowStatusMessage, isMasterDetail, theme - } = this.props; + const { user, Site_Name, baseUrl, useRealName, allowStatusMessage, isMasterDetail, theme } = this.props; if (!user) { return null; @@ -218,29 +230,24 @@ class Sidebar extends Component { style={[ styles.container, { - backgroundColor: isMasterDetail - ? themes[theme].backgroundColor - : themes[theme].focusedBackground + backgroundColor: isMasterDetail ? themes[theme].backgroundColor : themes[theme].focusedBackground } ]} - {...scrollPersistTaps} - > + {...scrollPersistTaps}> <TouchableWithoutFeedback onPress={this.onPressUser} testID='sidebar-close-drawer'> <View style={styles.header} theme={theme}> - <Avatar - text={user.username} - style={styles.avatar} - size={30} - /> + <Avatar text={user.username} style={styles.avatar} size={30} /> <View style={styles.headerTextContainer}> <View style={styles.headerUsername}> - <Text numberOfLines={1} style={[styles.username, { color: themes[theme].titleText }]}>{useRealName ? user.name : user.username}</Text> + <Text numberOfLines={1} style={[styles.username, { color: themes[theme].titleText }]}> + {useRealName ? user.name : user.username} + </Text> </View> <Text style={[styles.currentServerText, { color: themes[theme].titleText }]} numberOfLines={1} - accessibilityLabel={`Connected to ${ baseUrl }`} - >{Site_Name} + accessibilityLabel={`Connected to ${baseUrl}`}> + {Site_Name} </Text> </View> </View> @@ -256,9 +263,7 @@ class Sidebar extends Component { <Separator theme={theme} /> </> ) : ( - <> - {this.renderAdmin()} - </> + <>{this.renderAdmin()}</> )} </ScrollView> </SafeAreaView> diff --git a/app/views/StatusView.js b/app/views/StatusView.js index 2ac358343..19c3fae2e 100644 --- a/app/views/StatusView.js +++ b/app/views/StatusView.js @@ -11,8 +11,7 @@ import EventEmitter from '../utils/events'; import { showErrorAlert } from '../utils/info'; import Loading from '../containers/Loading'; import RocketChat from '../lib/rocketchat'; -import log, { logEvent, events } from '../utils/log'; - +import log, { events, logEvent } from '../utils/log'; import { LISTENER } from '../containers/Toast'; import { withTheme } from '../theme'; import { getUserSelector } from '../selectors/login'; @@ -20,19 +19,24 @@ import * as HeaderButton from '../containers/HeaderButton'; import { setUser as setUserAction } from '../actions/login'; import SafeAreaView from '../containers/SafeAreaView'; -const STATUS = [{ - id: 'online', - name: 'Online' -}, { - id: 'busy', - name: 'Busy' -}, { - id: 'away', - name: 'Away' -}, { - id: 'offline', - name: 'Invisible' -}]; +const STATUS = [ + { + id: 'online', + name: 'Online' + }, + { + id: 'busy', + name: 'Busy' + }, + { + id: 'away', + name: 'Away' + }, + { + id: 'offline', + name: 'Invisible' + } +]; const styles = StyleSheet.create({ inputContainer: { @@ -61,7 +65,7 @@ class StatusView extends React.Component { isMasterDetail: PropTypes.bool, setUser: PropTypes.func, Accounts_AllowInvisibleStatusOption: PropTypes.bool - } + }; constructor(props) { super(props); @@ -78,17 +82,13 @@ class StatusView extends React.Component { headerLeft: isMasterDetail ? undefined : () => <HeaderButton.CancelModal onPress={this.close} />, headerRight: () => ( <HeaderButton.Container> - <HeaderButton.Item - title={I18n.t('Done')} - onPress={this.submit} - testID='status-view-submit' - /> + <HeaderButton.Item title={I18n.t('Done')} onPress={this.submit} testID='status-view-submit' /> </HeaderButton.Container> ) }); - } + }; - submit = async() => { + submit = async () => { logEvent(events.STATUS_DONE); const { statusText } = this.state; const { user } = this.props; @@ -96,14 +96,14 @@ class StatusView extends React.Component { await this.setCustomStatus(statusText); } this.close(); - } + }; close = () => { const { navigation } = this.props; navigation.goBack(); - } + }; - setCustomStatus = async(statusText) => { + setCustomStatus = async statusText => { const { user, setUser } = this.props; this.setState({ loading: true }); @@ -124,7 +124,7 @@ class StatusView extends React.Component { } this.setState({ loading: false }); - } + }; renderHeader = () => { const { statusText } = this.state; @@ -137,14 +137,7 @@ class StatusView extends React.Component { value={statusText} containerStyle={styles.inputContainer} onChangeText={text => this.setState({ statusText: text })} - left={( - <Status - testID={`status-view-current-${ user.status }`} - style={styles.inputLeft} - status={user.status} - size={24} - /> - )} + left={<Status testID={`status-view-current-${user.status}`} style={styles.inputLeft} status={user.status} size={24} />} inputStyle={styles.inputStyle} placeholder={I18n.t('What_are_you_doing_right_now')} testID='status-view-input' @@ -152,7 +145,7 @@ class StatusView extends React.Component { <List.Separator /> </> ); - } + }; renderItem = ({ item }) => { const { statusText } = this.state; @@ -161,8 +154,8 @@ class StatusView extends React.Component { return ( <List.Item title={name} - onPress={async() => { - logEvent(events[`STATUS_${ item.id.toUpperCase() }`]); + onPress={async () => { + logEvent(events[`STATUS_${item.id.toUpperCase()}`]); if (user.status !== item.id) { try { const result = await RocketChat.setUserStatus(item.id, statusText); @@ -176,11 +169,11 @@ class StatusView extends React.Component { } } }} - testID={`status-view-${ id }`} + testID={`status-view-${id}`} left={() => <Status size={24} status={item.id} />} /> ); - } + }; render() { const { loading } = this.state; diff --git a/app/views/Styles.js b/app/views/Styles.js index f457a8ed3..89b036741 100644 --- a/app/views/Styles.js +++ b/app/views/Styles.js @@ -1,4 +1,4 @@ -import { StyleSheet, Platform } from 'react-native'; +import { Platform, StyleSheet } from 'react-native'; import { MAX_SCREEN_CONTENT_WIDTH } from '../constants/tablet'; diff --git a/app/views/TeamChannelsView.js b/app/views/TeamChannelsView.js index 64822aeae..869d5e4c3 100644 --- a/app/views/TeamChannelsView.js +++ b/app/views/TeamChannelsView.js @@ -1,14 +1,14 @@ import React from 'react'; -import { Keyboard, Alert, FlatList } from 'react-native'; +import { Alert, FlatList, Keyboard } from 'react-native'; import PropTypes from 'prop-types'; import { Q } from '@nozbe/watermelondb'; import { withSafeAreaInsets } from 'react-native-safe-area-context'; import { connect } from 'react-redux'; +import { HeaderBackButton } from '@react-navigation/stack'; import StatusBar from '../containers/StatusBar'; import RoomHeader from '../containers/RoomHeader'; import { withTheme } from '../theme'; -import SearchHeader from './ThreadMessagesView/SearchHeader'; import log, { events, logEvent } from '../utils/log'; import database from '../lib/database'; import { getUserSelector } from '../selectors/login'; @@ -17,6 +17,7 @@ import * as HeaderButton from '../containers/HeaderButton'; import BackgroundContainer from '../containers/BackgroundContainer'; import SafeAreaView from '../containers/SafeAreaView'; import ActivityIndicator from '../containers/ActivityIndicator'; +import SearchHeader from '../containers/SearchHeader'; import RoomItem, { ROW_HEIGHT } from '../presentation/RoomItem'; import RocketChat from '../lib/rocketchat'; import { withDimensions } from '../dimensions'; @@ -37,7 +38,6 @@ const PERMISSION_EDIT_TEAM_CHANNEL = 'edit-team-channel'; const PERMISSION_REMOVE_TEAM_CHANNEL = 'remove-team-channel'; const PERMISSION_ADD_TEAM_CHANNEL = 'add-team-channel'; - const getItemLayout = (data, index) => ({ length: data.length, offset: ROW_HEIGHT * index, @@ -62,7 +62,7 @@ class TeamChannelsView extends React.Component { deletePPermission: PropTypes.array, showActionSheet: PropTypes.func, deleteRoom: PropTypes.func - } + }; constructor(props) { super(props); @@ -85,16 +85,14 @@ class TeamChannelsView extends React.Component { this.load(); } - loadTeam = async() => { + loadTeam = async () => { const { addTeamChannelPermission } = this.props; const { loading, data } = this.state; const db = database.active; try { const subCollection = db.get('subscriptions'); - this.teamChannels = await subCollection.query( - Q.where('team_id', Q.eq(this.teamId)) - ); + this.teamChannels = await subCollection.query(Q.where('team_id', Q.eq(this.teamId))); this.team = this.teamChannels?.find(channel => channel.teamMain); this.setHeader(); @@ -115,12 +113,10 @@ class TeamChannelsView extends React.Component { navigation.pop(); showErrorAlert(I18n.t('Team_not_found')); } - } + }; - load = debounce(async() => { - const { - loadingMore, data, search, isSearching, searchText, end - } = this.state; + load = debounce(async () => { + const { loadingMore, data, search, isSearching, searchText, end } = this.state; const length = isSearching ? search.length : data.length; if (loadingMore || end) { @@ -158,11 +154,11 @@ class TeamChannelsView extends React.Component { log(e); this.setState({ loading: false, loadingMore: false }); } - }, 300) + }, 300); setHeader = () => { const { isSearching, showCreate, data } = this.state; - const { navigation, isMasterDetail, insets } = this.props; + const { navigation, isMasterDetail, insets, theme } = this.props; const { team } = this; if (!team) { @@ -172,14 +168,11 @@ class TeamChannelsView extends React.Component { const headerTitlePosition = getHeaderTitlePosition({ insets, numIconsRight: 2 }); if (isSearching) { - return { + const options = { headerTitleAlign: 'left', headerLeft: () => ( <HeaderButton.Container left> - <HeaderButton.Item - iconName='close' - onPress={this.onCancelSearchPress} - /> + <HeaderButton.Item iconName='close' onPress={this.onCancelSearchPress} /> </HeaderButton.Container> ), headerTitle: () => <SearchHeader onSearchChangeText={this.onSearchChangeText} />, @@ -189,6 +182,7 @@ class TeamChannelsView extends React.Component { }, headerRight: () => null }; + return navigation.setOptions(options); } const options = { @@ -198,6 +192,9 @@ class TeamChannelsView extends React.Component { left: headerTitlePosition.left, right: headerTitlePosition.right }, + headerLeft: () => ( + <HeaderBackButton labelVisible={false} onPress={() => navigation.pop()} tintColor={themes[theme].headerTintColor} /> + ), headerTitle: () => ( <RoomHeader title={RocketChat.getRoomTitle(team)} @@ -215,29 +212,40 @@ class TeamChannelsView extends React.Component { options.headerRight = () => ( <HeaderButton.Container> - { showCreate - ? <HeaderButton.Item iconName='create' testID='team-channels-view-create' onPress={() => navigation.navigate('AddChannelTeamView', { teamId: this.teamId, teamChannels: data })} /> - : null} + {showCreate ? ( + <HeaderButton.Item + iconName='create' + testID='team-channels-view-create' + onPress={() => navigation.navigate('AddChannelTeamView', { teamId: this.teamId, teamChannels: data })} + /> + ) : null} <HeaderButton.Item iconName='search' testID='team-channels-view-search' onPress={this.onSearchPress} /> </HeaderButton.Container> ); navigation.setOptions(options); - } + }; onSearchPress = () => { logEvent(events.TC_SEARCH); this.setState({ isSearching: true }, () => this.setHeader()); - } + }; - onSearchChangeText = debounce((searchText) => { - this.setState({ - searchText, search: [], loading: !!searchText, loadingMore: false, end: false - }, () => { - if (searchText) { - this.load(); + onSearchChangeText = debounce(searchText => { + this.setState( + { + searchText, + search: [], + loading: !!searchText, + loadingMore: false, + end: false + }, + () => { + if (searchText) { + this.load(); + } } - }); - }, 300) + ); + }, 300); onCancelSearchPress = () => { logEvent(events.TC_CANCEL_SEARCH); @@ -246,65 +254,83 @@ class TeamChannelsView extends React.Component { return; } Keyboard.dismiss(); - this.setState({ - searchText: null, isSearching: false, search: [], loadingMore: false, end: false - }, () => { - this.setHeader(); - }); + this.setState( + { + searchText: null, + isSearching: false, + search: [], + loadingMore: false, + end: false + }, + () => { + this.setHeader(); + } + ); }; - goRoomActionsView = (screen) => { + goRoomActionsView = screen => { logEvent(events.TC_GO_ACTIONS); const { team } = this; - const { - navigation, isMasterDetail - } = this.props; + const { navigation, isMasterDetail } = this.props; if (isMasterDetail) { navigation.navigate('ModalStackNavigator', { screen: screen ?? 'RoomActionsView', params: { - rid: team.rid, t: team.t, room: team, showCloseModal: false + rid: team.rid, + t: team.t, + room: team, + showCloseModal: false } }); } else { navigation.navigate('RoomActionsView', { - rid: team.rid, t: team.t, room: team + rid: team.rid, + t: team.t, + room: team }); } - } + }; - getRoomTitle = item => RocketChat.getRoomTitle(item) + getRoomTitle = item => RocketChat.getRoomTitle(item); - getRoomAvatar = item => RocketChat.getRoomAvatar(item) + getRoomAvatar = item => RocketChat.getRoomAvatar(item); - onPressItem = debounce(async(item) => { - logEvent(events.TC_GO_ROOM); - const { navigation, isMasterDetail } = this.props; - try { - const { room } = await RocketChat.getRoomInfo(item._id); - const params = { - rid: item._id, name: RocketChat.getRoomTitle(room), joinCodeRequired: room.joinCodeRequired, t: room.t, teamId: room.teamId - }; - if (isMasterDetail) { - navigation.pop(); + onPressItem = debounce( + async item => { + logEvent(events.TC_GO_ROOM); + const { navigation, isMasterDetail } = this.props; + try { + const { room } = await RocketChat.getRoomInfo(item._id); + const params = { + rid: item._id, + name: RocketChat.getRoomTitle(room), + joinCodeRequired: room.joinCodeRequired, + t: room.t, + teamId: room.teamId + }; + if (isMasterDetail) { + navigation.pop(); + } + goRoom({ item: params, isMasterDetail, navigationMethod: navigation.push }); + } catch (e) { + if (e.data.error === 'not-allowed') { + showErrorAlert(I18n.t('error-not-allowed')); + } else { + showErrorAlert(e.data.error); + } } - goRoom({ item: params, isMasterDetail, navigationMethod: navigation.push }); - } catch (e) { - if (e.data.error === 'not-allowed') { - showErrorAlert(I18n.t('error-not-allowed')); - } else { - showErrorAlert(e.data.error); - } - } - }, 1000, true); + }, + 1000, + true + ); - toggleAutoJoin = async(item) => { + toggleAutoJoin = async item => { logEvent(events.TC_TOGGLE_AUTOJOIN); try { const { data } = this.state; const result = await RocketChat.updateTeamRoom({ roomId: item._id, isDefault: !item.teamDefault }); if (result.success) { - const newData = data.map((i) => { + const newData = data.map(i => { if (i._id === item._id) { i.teamDefault = !i.teamDefault; } @@ -316,9 +342,9 @@ class TeamChannelsView extends React.Component { logEvent(events.TC_TOGGLE_AUTOJOIN_F); log(e); } - } + }; - remove = (item) => { + remove = item => { Alert.alert( I18n.t('Confirmation'), I18n.t('Remove_Team_Room_Warning'), @@ -335,9 +361,9 @@ class TeamChannelsView extends React.Component { ], { cancelable: false } ); - } + }; - removeRoom = async(item) => { + removeRoom = async item => { logEvent(events.TC_DELETE_ROOM); try { const { data } = this.state; @@ -350,9 +376,9 @@ class TeamChannelsView extends React.Component { logEvent(events.TC_DELETE_ROOM_F); log(e); } - } + }; - delete = (item) => { + delete = item => { logEvent(events.TC_DELETE_ROOM); const { deleteRoom } = this.props; @@ -372,12 +398,17 @@ class TeamChannelsView extends React.Component { ], { cancelable: false } ); - } + }; - showChannelActions = async(item) => { + showChannelActions = async item => { logEvent(events.ROOM_SHOW_BOX_ACTIONS); const { - showActionSheet, editTeamChannelPermission, deleteCPermission, deletePPermission, theme, removeTeamChannelPermission + showActionSheet, + editTeamChannelPermission, + deleteCPermission, + deletePPermission, + theme, + removeTeamChannelPermission } = this.props; const isAutoJoinChecked = item.teamDefault; const autoJoinIcon = isAutoJoinChecked ? 'checkbox-checked' : 'checkbox-unchecked'; @@ -391,7 +422,14 @@ class TeamChannelsView extends React.Component { title: I18n.t('Auto-join'), icon: item.t === 'p' ? 'channel-private' : 'channel-public', onPress: () => this.toggleAutoJoin(item), - right: () => <CustomIcon testID={isAutoJoinChecked ? 'auto-join-checked' : 'auto-join-unchecked'} name={autoJoinIcon} size={20} color={autoJoinIconColor} />, + right: () => ( + <CustomIcon + testID={isAutoJoinChecked ? 'auto-join-checked' : 'auto-join-unchecked'} + name={autoJoinIcon} + size={20} + color={autoJoinIconColor} + /> + ), testID: 'action-sheet-auto-join' }); } @@ -422,15 +460,10 @@ class TeamChannelsView extends React.Component { return; } showActionSheet({ options }); - } + }; renderItem = ({ item }) => { - const { - StoreLastMessage, - useRealName, - theme, - width - } = this.props; + const { StoreLastMessage, useRealName, theme, width } = this.props; return ( <RoomItem item={item} @@ -456,12 +489,10 @@ class TeamChannelsView extends React.Component { return <ActivityIndicator theme={theme} />; } return null; - } + }; renderScroll = () => { - const { - loading, data, search, isSearching, searchText - } = this.state; + const { loading, data, search, isSearching, searchText } = this.state; if (loading) { return <BackgroundContainer loading />; } @@ -489,7 +520,7 @@ class TeamChannelsView extends React.Component { }; render() { - console.count(`${ this.constructor.name }.render calls`); + console.count(`${this.constructor.name}.render calls`); return ( <SafeAreaView testID='team-channels-view'> <StatusBar /> @@ -516,4 +547,7 @@ const mapDispatchToProps = dispatch => ({ deleteRoom: (rid, t) => dispatch(deleteRoomAction(rid, t)) }); -export default connect(mapStateToProps, mapDispatchToProps)(withDimensions(withSafeAreaInsets(withTheme(withActionSheet(TeamChannelsView))))); +export default connect( + mapStateToProps, + mapDispatchToProps +)(withDimensions(withSafeAreaInsets(withTheme(withActionSheet(TeamChannelsView))))); diff --git a/app/views/ThemeView.js b/app/views/ThemeView.js index 1152a920c..98c0e872d 100644 --- a/app/views/ThemeView.js +++ b/app/views/ThemeView.js @@ -26,15 +26,18 @@ const THEMES = [ label: 'Light', value: 'light', group: THEME_GROUP - }, { + }, + { label: 'Dark', value: 'dark', group: THEME_GROUP - }, { + }, + { label: 'Black', value: 'black', group: DARK_GROUP - }, { + }, + { label: 'Dark', value: 'dark', group: DARK_GROUP @@ -51,15 +54,15 @@ const darkGroup = THEMES.filter(item => item.group === DARK_GROUP); class ThemeView extends React.Component { static navigationOptions = () => ({ title: I18n.t('Theme') - }) + }); static propTypes = { theme: PropTypes.string, themePreferences: PropTypes.object, setTheme: PropTypes.func - } + }; - isSelected = (item) => { + isSelected = item => { const { themePreferences } = this.props; const { group } = item; const { darkLevel, currentTheme } = themePreferences; @@ -69,9 +72,9 @@ class ThemeView extends React.Component { if (group === DARK_GROUP) { return item.value === darkLevel; } - } + }; - onClick = (item) => { + onClick = item => { const { themePreferences } = this.props; const { darkLevel, currentTheme } = themePreferences; const { value, group } = item; @@ -85,9 +88,9 @@ class ThemeView extends React.Component { changes = { darkLevel: value }; } this.setTheme(changes); - } + }; - setTheme = async(theme) => { + setTheme = async theme => { const { setTheme, themePreferences } = this.props; const newTheme = { ...themePreferences, ...theme }; setTheme(newTheme); @@ -97,7 +100,7 @@ class ThemeView extends React.Component { renderIcon = () => { const { theme } = this.props; return <List.Icon name='check' color={themes[theme].tintColor} />; - } + }; renderItem = ({ item }) => { const { label, value } = item; @@ -106,13 +109,13 @@ class ThemeView extends React.Component { <List.Item title={label} onPress={() => this.onClick(item)} - testID={`theme-view-${ value }`} + testID={`theme-view-${value}`} right={this.isSelected(item) ? this.renderIcon : null} /> <List.Separator /> </> ); - } + }; render() { return ( @@ -121,15 +124,11 @@ class ThemeView extends React.Component { <List.Container> <List.Section title='Theme'> <List.Separator /> - { - themeGroup.map(item => this.renderItem({ item })) - } + {themeGroup.map(item => this.renderItem({ item }))} </List.Section> <List.Section title='Dark_level'> <List.Separator /> - { - darkGroup.map(item => this.renderItem({ item })) - } + {darkGroup.map(item => this.renderItem({ item }))} </List.Section> </List.Container> </SafeAreaView> diff --git a/app/views/ThreadMessagesView/Dropdown/DropdownItem.js b/app/views/ThreadMessagesView/Dropdown/DropdownItem.js index 4ebca9168..c5d69ba7c 100644 --- a/app/views/ThreadMessagesView/Dropdown/DropdownItem.js +++ b/app/views/ThreadMessagesView/Dropdown/DropdownItem.js @@ -1,6 +1,6 @@ import React from 'react'; import PropTypes from 'prop-types'; -import { View, Text, StyleSheet } from 'react-native'; +import { StyleSheet, Text, View } from 'react-native'; import { themes } from '../../../constants/colors'; import { withTheme } from '../../../theme'; @@ -23,9 +23,7 @@ const styles = StyleSheet.create({ } }); -const DropdownItem = React.memo(({ - theme, onPress, iconName, text -}) => ( +const DropdownItem = React.memo(({ theme, onPress, iconName, text }) => ( <Touch theme={theme} onPress={onPress} style={{ backgroundColor: themes[theme].backgroundColor }}> <View style={styles.container}> <Text style={[styles.text, { color: themes[theme].auxiliaryText }]}>{text}</Text> diff --git a/app/views/ThreadMessagesView/Dropdown/DropdownItemFilter.js b/app/views/ThreadMessagesView/Dropdown/DropdownItemFilter.js index cab41d226..7ff7b691f 100644 --- a/app/views/ThreadMessagesView/Dropdown/DropdownItemFilter.js +++ b/app/views/ThreadMessagesView/Dropdown/DropdownItemFilter.js @@ -1,15 +1,11 @@ import React from 'react'; import PropTypes from 'prop-types'; -import DropdownItem from './DropdownItem'; import I18n from '../../../i18n'; +import DropdownItem from './DropdownItem'; const DropdownItemFilter = ({ currentFilter, value, onPress }) => ( - <DropdownItem - text={I18n.t(value)} - iconName={currentFilter === value ? 'check' : null} - onPress={() => onPress(value)} - /> + <DropdownItem text={I18n.t(value)} iconName={currentFilter === value ? 'check' : null} onPress={() => onPress(value)} /> ); DropdownItemFilter.propTypes = { diff --git a/app/views/ThreadMessagesView/Dropdown/DropdownItemHeader.js b/app/views/ThreadMessagesView/Dropdown/DropdownItemHeader.js index 5ffbaea2c..d1ee227de 100644 --- a/app/views/ThreadMessagesView/Dropdown/DropdownItemHeader.js +++ b/app/views/ThreadMessagesView/Dropdown/DropdownItemHeader.js @@ -1,9 +1,9 @@ import React from 'react'; import PropTypes from 'prop-types'; -import DropdownItem from './DropdownItem'; import { FILTER } from '../filters'; import I18n from '../../../i18n'; +import DropdownItem from './DropdownItem'; const DropdownItemHeader = ({ currentFilter, onPress }) => { let text; diff --git a/app/views/ThreadMessagesView/Dropdown/index.js b/app/views/ThreadMessagesView/Dropdown/index.js index fdf1acba7..ac33cd89b 100644 --- a/app/views/ThreadMessagesView/Dropdown/index.js +++ b/app/views/ThreadMessagesView/Dropdown/index.js @@ -1,8 +1,6 @@ import React from 'react'; import PropTypes from 'prop-types'; -import { - Animated, Easing, TouchableWithoutFeedback -} from 'react-native'; +import { Animated, Easing, TouchableWithoutFeedback } from 'react-native'; import { withSafeAreaInsets } from 'react-native-safe-area-context'; import styles from '../styles'; @@ -24,7 +22,7 @@ class Dropdown extends React.Component { currentFilter: PropTypes.string, onClose: PropTypes.func, onFilterSelected: PropTypes.func - } + }; constructor(props) { super(props); @@ -32,34 +30,26 @@ class Dropdown extends React.Component { } componentDidMount() { - Animated.timing( - this.animatedValue, - { - toValue: 1, - duration: ANIMATION_DURATION, - easing: Easing.inOut(Easing.quad), - useNativeDriver: true - } - ).start(); + Animated.timing(this.animatedValue, { + toValue: 1, + duration: ANIMATION_DURATION, + easing: Easing.inOut(Easing.quad), + useNativeDriver: true + }).start(); } close = () => { const { onClose } = this.props; - Animated.timing( - this.animatedValue, - { - toValue: 0, - duration: ANIMATION_DURATION, - easing: Easing.inOut(Easing.quad), - useNativeDriver: true - } - ).start(() => onClose()); - } + Animated.timing(this.animatedValue, { + toValue: 0, + duration: ANIMATION_DURATION, + easing: Easing.inOut(Easing.quad), + useNativeDriver: true + }).start(() => onClose()); + }; render() { - const { - isMasterDetail, insets, theme, currentFilter, onFilterSelected - } = this.props; + const { isMasterDetail, insets, theme, currentFilter, onFilterSelected } = this.props; const statusBarHeight = insets?.top ?? 0; const heightDestination = isMasterDetail ? headerHeight + statusBarHeight : 0; const translateY = this.animatedValue.interpolate({ @@ -73,12 +63,15 @@ class Dropdown extends React.Component { return ( <> <TouchableWithoutFeedback onPress={this.close}> - <Animated.View style={[styles.backdrop, - { - backgroundColor: themes[theme].backdropColor, - opacity: backdropOpacity, - top: heightDestination - }]} + <Animated.View + style={[ + styles.backdrop, + { + backgroundColor: themes[theme].backdropColor, + opacity: backdropOpacity, + top: heightDestination + } + ]} /> </TouchableWithoutFeedback> <Animated.View @@ -89,8 +82,7 @@ class Dropdown extends React.Component { backgroundColor: themes[theme].backgroundColor, borderColor: themes[theme].separatorColor } - ]} - > + ]}> <DropdownItemHeader currentFilter={currentFilter} onPress={this.close} /> <List.Separator /> <DropdownItemFilter currentFilter={currentFilter} value={FILTER.ALL} onPress={onFilterSelected} /> diff --git a/app/views/ThreadMessagesView/Item.js b/app/views/ThreadMessagesView/Item.js index 89ae95cc6..1caef2c27 100644 --- a/app/views/ThreadMessagesView/Item.js +++ b/app/views/ThreadMessagesView/Item.js @@ -1,6 +1,6 @@ import React from 'react'; import PropTypes from 'prop-types'; -import { View, Text, StyleSheet } from 'react-native'; +import { StyleSheet, Text, View } from 'react-native'; import Touchable from 'react-native-platform-touchable'; import { withTheme } from '../../theme'; @@ -56,9 +56,7 @@ const styles = StyleSheet.create({ } }); -const Item = ({ - item, baseUrl, theme, useRealName, user, badgeColor, onPress, toggleFollowThread -}) => { +const Item = ({ item, baseUrl, theme, useRealName, user, badgeColor, onPress, toggleFollowThread }) => { const username = (useRealName && item?.u?.name) || item?.u?.username; let time; if (item?.ts) { @@ -66,7 +64,10 @@ const Item = ({ } return ( - <Touchable onPress={() => onPress(item)} testID={`thread-messages-view-${ item.msg }`} style={{ backgroundColor: themes[theme].backgroundColor }}> + <Touchable + onPress={() => onPress(item)} + testID={`thread-messages-view-${item.msg}`} + style={{ backgroundColor: themes[theme].backgroundColor }}> <View style={styles.container}> <Avatar style={styles.avatar} @@ -80,19 +81,24 @@ const Item = ({ /> <View style={styles.contentContainer}> <View style={styles.titleContainer}> - <Text style={[styles.title, { color: themes[theme].titleText }]} numberOfLines={1}>{username}</Text> + <Text style={[styles.title, { color: themes[theme].titleText }]} numberOfLines={1}> + {username} + </Text> <Text style={[styles.time, { color: themes[theme].auxiliaryText }]}>{time}</Text> </View> <View style={styles.messageContainer}> - <Markdown msg={makeThreadName(item)} baseUrl={baseUrl} username={username} theme={theme} numberOfLines={2} style={[styles.markdown]} preview /> - {badgeColor ? <View style={[styles.badge, { backgroundColor: badgeColor }]} /> : null } + <Markdown + msg={makeThreadName(item)} + baseUrl={baseUrl} + username={username} + theme={theme} + numberOfLines={2} + style={[styles.markdown]} + preview + /> + {badgeColor ? <View style={[styles.badge, { backgroundColor: badgeColor }]} /> : null} </View> - <ThreadDetails - item={item} - user={user} - toggleFollowThread={toggleFollowThread} - style={styles.threadDetails} - /> + <ThreadDetails item={item} user={user} toggleFollowThread={toggleFollowThread} style={styles.threadDetails} /> </View> </View> </Touchable> diff --git a/app/views/ThreadMessagesView/Item.stories.js b/app/views/ThreadMessagesView/Item.stories.js index be8f398ef..d829d92d0 100644 --- a/app/views/ThreadMessagesView/Item.stories.js +++ b/app/views/ThreadMessagesView/Item.stories.js @@ -5,10 +5,10 @@ import { ScrollView } from 'react-native'; import { combineReducers, createStore } from 'redux'; import { Provider } from 'react-redux'; -import Item from './Item'; import * as List from '../../containers/List'; import { themes } from '../../constants/colors'; import { ThemeContext } from '../../theme'; +import Item from './Item'; const author = { _id: 'userid', @@ -17,7 +17,8 @@ const author = { }; const baseUrl = 'https://open.rocket.chat'; const date = new Date(2020, 10, 10, 10); -const longText = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.'; +const longText = + 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.'; const defaultItem = { msg: 'Message content', tcount: 1, @@ -104,17 +105,11 @@ stories.add('content', () => ( stories.add('badge', () => ( <> - <BaseItem - badgeColor={themes.light.mentionMeColor} - /> + <BaseItem badgeColor={themes.light.mentionMeColor} /> <List.Separator /> - <BaseItem - badgeColor={themes.light.mentionGroupColor} - /> + <BaseItem badgeColor={themes.light.mentionGroupColor} /> <List.Separator /> - <BaseItem - badgeColor={themes.light.tunreadColor} - /> + <BaseItem badgeColor={themes.light.tunreadColor} /> <BaseItem item={{ msg: longText @@ -125,12 +120,8 @@ stories.add('badge', () => ( )); const ThemeStory = ({ theme }) => ( - <ThemeContext.Provider - value={{ theme }} - > - <BaseItem - badgeColor={themes[theme].mentionMeColor} - /> + <ThemeContext.Provider value={{ theme }}> + <BaseItem badgeColor={themes[theme].mentionMeColor} /> </ThemeContext.Provider> ); diff --git a/app/views/ThreadMessagesView/index.js b/app/views/ThreadMessagesView/index.js index 48cab9c99..5c6556efb 100644 --- a/app/views/ThreadMessagesView/index.js +++ b/app/views/ThreadMessagesView/index.js @@ -7,8 +7,6 @@ import { sanitizedRaw } from '@nozbe/watermelondb/RawRecord'; import { withSafeAreaInsets } from 'react-native-safe-area-context'; import { HeaderBackButton } from '@react-navigation/stack'; -import styles from './styles'; -import Item from './Item'; import ActivityIndicator from '../../containers/ActivityIndicator'; import I18n from '../../i18n'; import RocketChat from '../../lib/rocketchat'; @@ -25,16 +23,18 @@ import { getUserSelector } from '../../selectors/login'; import SafeAreaView from '../../containers/SafeAreaView'; import * as HeaderButton from '../../containers/HeaderButton'; import * as List from '../../containers/List'; -import Dropdown from './Dropdown'; -import DropdownItemHeader from './Dropdown/DropdownItemHeader'; -import { FILTER } from './filters'; import BackgroundContainer from '../../containers/BackgroundContainer'; import { isIOS } from '../../utils/deviceInfo'; import { getBadgeColor, makeThreadName } from '../../utils/room'; import { getHeaderTitlePosition } from '../../containers/Header'; -import SearchHeader from './SearchHeader'; import EventEmitter from '../../utils/events'; import { LISTENER } from '../../containers/Toast'; +import SearchHeader from '../../containers/SearchHeader'; +import { FILTER } from './filters'; +import DropdownItemHeader from './Dropdown/DropdownItemHeader'; +import Dropdown from './Dropdown'; +import Item from './Item'; +import styles from './styles'; const API_FETCH_COUNT = 50; @@ -48,7 +48,7 @@ class ThreadMessagesView extends React.Component { theme: PropTypes.string, isMasterDetail: PropTypes.bool, insets: PropTypes.object - } + }; constructor(props) { super(props); @@ -77,16 +77,14 @@ class ThreadMessagesView extends React.Component { } componentDidUpdate(prevProps) { - const { - insets - } = this.props; + const { insets } = this.props; if (insets.left !== prevProps.insets.left || insets.right !== prevProps.insets.right) { this.setHeader(); } } componentWillUnmount() { - console.countReset(`${ this.constructor.name }.render calls`); + console.countReset(`${this.constructor.name}.render calls`); if (this.subSubscription && this.subSubscription.unsubscribe) { this.subSubscription.unsubscribe(); } @@ -97,9 +95,7 @@ class ThreadMessagesView extends React.Component { getHeader = () => { const { isSearching } = this.state; - const { - navigation, isMasterDetail, insets, theme - } = this.props; + const { navigation, isMasterDetail, insets, theme } = this.props; if (isSearching) { const headerTitlePosition = getHeaderTitlePosition({ insets, numIconsRight: 1 }); @@ -107,10 +103,7 @@ class ThreadMessagesView extends React.Component { headerTitleAlign: 'left', headerLeft: () => ( <HeaderButton.Container left> - <HeaderButton.Item - iconName='close' - onPress={this.onCancelSearchPress} - /> + <HeaderButton.Item iconName='close' onPress={this.onCancelSearchPress} /> </HeaderButton.Container> ), headerTitle: () => <SearchHeader onSearchChangeText={this.onSearchChangeText} />, @@ -124,11 +117,7 @@ class ThreadMessagesView extends React.Component { const options = { headerLeft: () => ( - <HeaderBackButton - labelVisible={false} - onPress={() => navigation.pop()} - tintColor={themes[theme].headerTintColor} - /> + <HeaderBackButton labelVisible={false} onPress={() => navigation.pop()} tintColor={themes[theme].headerTintColor} /> ), headerTitleAlign: 'center', headerTitle: I18n.t('Threads'), @@ -148,33 +137,30 @@ class ThreadMessagesView extends React.Component { </HeaderButton.Container> ); return options; - } + }; setHeader = () => { const { navigation } = this.props; const options = this.getHeader(); navigation.setOptions(options); - } + }; - initSubscription = async() => { + initSubscription = async () => { try { const db = database.active; // subscription query - const subscription = await db.collections - .get('subscriptions') - .find(this.rid); + const subscription = await db.collections.get('subscriptions').find(this.rid); const observable = subscription.observe(); - this.subSubscription = observable - .subscribe((data) => { - this.setState({ subscription: data }); - }); + this.subSubscription = observable.subscribe(data => { + this.setState({ subscription: data }); + }); this.subscribeMessages(subscription); } catch (e) { log(e); } - } + }; subscribeMessages = (subscription, searchText) => { try { @@ -184,34 +170,30 @@ class ThreadMessagesView extends React.Component { this.messagesSubscription.unsubscribe(); } - const whereClause = [ - Q.where('rid', this.rid), - Q.experimentalSortBy('tlm', Q.desc) - ]; + const whereClause = [Q.where('rid', this.rid), Q.experimentalSortBy('tlm', Q.desc)]; if (searchText?.trim()) { - whereClause.push(Q.where('msg', Q.like(`%${ sanitizeLikeString(searchText.trim()) }%`))); + whereClause.push(Q.where('msg', Q.like(`%${sanitizeLikeString(searchText.trim())}%`))); } this.messagesObservable = db.collections .get('threads') .query(...whereClause) .observeWithColumns(['updated_at']); - this.messagesSubscription = this.messagesObservable - .subscribe((messages) => { - const { currentFilter } = this.state; - const displayingThreads = this.getFilteredThreads(messages, subscription, currentFilter); - if (this.mounted) { - this.setState({ messages, displayingThreads }); - } else { - this.state.messages = messages; - this.state.displayingThreads = displayingThreads; - } - }); + this.messagesSubscription = this.messagesObservable.subscribe(messages => { + const { currentFilter } = this.state; + const displayingThreads = this.getFilteredThreads(messages, subscription, currentFilter); + if (this.mounted) { + this.setState({ messages, displayingThreads }); + } else { + this.state.messages = messages; + this.state.displayingThreads = displayingThreads; + } + }); } catch (e) { log(e); } - } + }; init = () => { const { subscription } = this.state; @@ -228,9 +210,9 @@ class ThreadMessagesView extends React.Component { } catch (e) { log(e); } - } + }; - updateThreads = async({ update, remove, lastThreadSync }) => { + updateThreads = async ({ update, remove, lastThreadSync }) => { const { subscription } = this.state; // if there's no subscription, manage data on this.state.messages // note: sync will never be called without subscription @@ -252,16 +234,22 @@ class ThreadMessagesView extends React.Component { // filter threads threadsToCreate = update.filter(i1 => !allThreadsRecords.find(i2 => i1._id === i2.id)); threadsToUpdate = allThreadsRecords.filter(i1 => update.find(i2 => i1.id === i2._id)); - threadsToCreate = threadsToCreate.map(thread => threadsCollection.prepareCreate(protectedFunction((t) => { - t._raw = sanitizedRaw({ id: thread._id }, threadsCollection.schema); - t.subscription.set(subscription); - Object.assign(t, thread); - }))); - threadsToUpdate = threadsToUpdate.map((thread) => { + threadsToCreate = threadsToCreate.map(thread => + threadsCollection.prepareCreate( + protectedFunction(t => { + t._raw = sanitizedRaw({ id: thread._id }, threadsCollection.schema); + t.subscription.set(subscription); + Object.assign(t, thread); + }) + ) + ); + threadsToUpdate = threadsToUpdate.map(thread => { const newThread = update.find(t => t._id === thread.id); - return thread.prepareUpdate(protectedFunction((t) => { - Object.assign(t, newThread); - })); + return thread.prepareUpdate( + protectedFunction(t => { + Object.assign(t, newThread); + }) + ); }); } @@ -270,12 +258,12 @@ class ThreadMessagesView extends React.Component { threadsToDelete = threadsToDelete.map(t => t.prepareDestroyPermanently()); } - await db.action(async() => { + await db.action(async () => { await db.batch( ...threadsToCreate, ...threadsToUpdate, ...threadsToDelete, - subscription.prepareUpdate((s) => { + subscription.prepareUpdate(s => { s.lastThreadSync = lastThreadSync; }) ); @@ -283,13 +271,11 @@ class ThreadMessagesView extends React.Component { } catch (e) { log(e); } - } + }; // eslint-disable-next-line react/sort-comp - load = debounce(async(lastThreadSync) => { - const { - loading, end, messages, searchText - } = this.state; + load = debounce(async lastThreadSync => { + const { loading, end, messages, searchText } = this.state; if (end || loading || !this.mounted) { return; } @@ -298,7 +284,10 @@ class ThreadMessagesView extends React.Component { try { const result = await RocketChat.getThreadsList({ - rid: this.rid, count: API_FETCH_COUNT, offset: messages.length, text: searchText + rid: this.rid, + count: API_FETCH_COUNT, + offset: messages.length, + text: searchText }); if (result.success) { this.updateThreads({ update: result.threads, lastThreadSync }); @@ -311,15 +300,16 @@ class ThreadMessagesView extends React.Component { log(e); this.setState({ loading: false, end: true }); } - }, 300) + }, 300); // eslint-disable-next-line react/sort-comp - sync = async(updatedSince) => { + sync = async updatedSince => { this.setState({ loading: true }); try { const result = await RocketChat.getSyncThreadsList({ - rid: this.rid, updatedSince: updatedSince.toISOString() + rid: this.rid, + updatedSince: updatedSince.toISOString() }); if (result.success && result.threads) { const { update, remove } = result.threads; @@ -332,11 +322,11 @@ class ThreadMessagesView extends React.Component { log(e); this.setState({ loading: false }); } - } + }; onSearchPress = () => { this.setState({ isSearching: true }, () => this.setHeader()); - } + }; onCancelSearchPress = () => { this.setState({ isSearching: false, searchText: '' }, () => { @@ -344,34 +334,37 @@ class ThreadMessagesView extends React.Component { this.setHeader(); this.subscribeMessages(subscription); }); - } + }; - onSearchChangeText = debounce((searchText) => { + onSearchChangeText = debounce(searchText => { const { subscription } = this.state; this.setState({ searchText }, () => this.subscribeMessages(subscription, searchText)); - }, 300) + }, 300); + onThreadPress = debounce( + item => { + const { subscription } = this.state; + const { navigation, isMasterDetail } = this.props; + if (isMasterDetail) { + navigation.pop(); + } + navigation.push('RoomView', { + rid: item.subscription.id, + tmid: item.id, + name: makeThreadName(item), + t: 'thread', + roomUserId: RocketChat.getUidDirectMessage(subscription) + }); + }, + 1000, + true + ); - onThreadPress = debounce((item) => { - const { subscription } = this.state; - const { navigation, isMasterDetail } = this.props; - if (isMasterDetail) { - navigation.pop(); - } - navigation.push('RoomView', { - rid: item.subscription.id, - tmid: item.id, - name: makeThreadName(item), - t: 'thread', - roomUserId: RocketChat.getUidDirectMessage(subscription) - }); - }, 1000, true) - - getBadgeColor = (item) => { + getBadgeColor = item => { const { subscription } = this.state; const { theme } = this.props; return getBadgeColor({ subscription, theme, messageId: item?.id }); - } + }; // helper to query threads getFilteredThreads = (messages, subscription, currentFilter) => { @@ -383,38 +376,36 @@ class ThreadMessagesView extends React.Component { return messages?.filter(item => subscription?.tunread?.includes(item?.id)); } return messages; - } + }; // method to update state with filtered threads filterThreads = () => { const { messages, subscription } = this.state; const displayingThreads = this.getFilteredThreads(messages, subscription); this.setState({ displayingThreads }); - } + }; - showFilterDropdown = () => this.setState({ showFilterDropdown: true }) + showFilterDropdown = () => this.setState({ showFilterDropdown: true }); - closeFilterDropdown = () => this.setState({ showFilterDropdown: false }) + closeFilterDropdown = () => this.setState({ showFilterDropdown: false }); - onFilterSelected = (filter) => { + onFilterSelected = filter => { const { messages, subscription } = this.state; const displayingThreads = this.getFilteredThreads(messages, subscription, filter); this.setState({ currentFilter: filter, displayingThreads }); - } + }; - toggleFollowThread = async(isFollowingThread, tmid) => { + toggleFollowThread = async (isFollowingThread, tmid) => { try { await RocketChat.toggleFollowMessage(tmid, !isFollowingThread); EventEmitter.emit(LISTENER, { message: isFollowingThread ? I18n.t('Unfollowed_thread') : I18n.t('Following_thread') }); } catch (e) { log(e); } - } + }; renderItem = ({ item }) => { - const { - user, navigation, baseUrl, useRealName - } = this.props; + const { user, navigation, baseUrl, useRealName } = this.props; const badgeColor = this.getBadgeColor(item); return ( <Item @@ -430,7 +421,7 @@ class ThreadMessagesView extends React.Component { toggleFollowThread={this.toggleFollowThread} /> ); - } + }; renderHeader = () => { const { messages, currentFilter } = this.state; @@ -444,12 +435,10 @@ class ThreadMessagesView extends React.Component { <List.Separator /> </> ); - } + }; renderContent = () => { - const { - loading, messages, displayingThreads, currentFilter - } = this.state; + const { loading, messages, displayingThreads, currentFilter } = this.state; const { theme } = this.props; if (!messages?.length || !displayingThreads?.length) { let text; @@ -487,25 +476,19 @@ class ThreadMessagesView extends React.Component { scrollIndicatorInsets={{ right: 1 }} // https://github.com/facebook/react-native/issues/26610#issuecomment-539843444 /> ); - } + }; render() { - console.count(`${ this.constructor.name }.render calls`); + console.count(`${this.constructor.name}.render calls`); const { showFilterDropdown, currentFilter } = this.state; return ( <SafeAreaView testID='thread-messages-view'> <StatusBar /> {this.renderContent()} - {showFilterDropdown - ? ( - <Dropdown - currentFilter={currentFilter} - onFilterSelected={this.onFilterSelected} - onClose={this.closeFilterDropdown} - /> - ) - : null} + {showFilterDropdown ? ( + <Dropdown currentFilter={currentFilter} onFilterSelected={this.onFilterSelected} onClose={this.closeFilterDropdown} /> + ) : null} </SafeAreaView> ); } diff --git a/app/views/UserNotificationPreferencesView/index.js b/app/views/UserNotificationPreferencesView/index.js index e05bb534f..66b64ee5a 100644 --- a/app/views/UserNotificationPreferencesView/index.js +++ b/app/views/UserNotificationPreferencesView/index.js @@ -1,5 +1,5 @@ import React from 'react'; -import { Text, StyleSheet } from 'react-native'; +import { StyleSheet, Text } from 'react-native'; import PropTypes from 'prop-types'; import { connect } from 'react-redux'; @@ -10,10 +10,10 @@ import I18n from '../../i18n'; import RocketChat from '../../lib/rocketchat'; import { withTheme } from '../../theme'; import SafeAreaView from '../../containers/SafeAreaView'; -import { OPTIONS } from './options'; import ActivityIndicator from '../../containers/ActivityIndicator'; import { getUserSelector } from '../../selectors/login'; import sharedStyles from '../Styles'; +import { OPTIONS } from './options'; const styles = StyleSheet.create({ pickerText: { @@ -25,7 +25,7 @@ const styles = StyleSheet.create({ class UserNotificationPreferencesView extends React.Component { static navigationOptions = () => ({ title: I18n.t('Notification_Preferences') - }) + }); static propTypes = { navigation: PropTypes.object, @@ -51,17 +51,21 @@ class UserNotificationPreferencesView extends React.Component { this.setState({ preferences, loading: true }); } - findDefaultOption = (key) => { + findDefaultOption = key => { const { preferences } = this.state; const option = preferences[key] ? OPTIONS[key].find(item => item.value === preferences[key]) : OPTIONS[key][0]; return option; - } + }; - renderPickerOption = (key) => { + renderPickerOption = key => { const { theme } = this.props; const text = this.findDefaultOption(key); - return <Text style={[styles.pickerText, { color: themes[theme].actionTintColor }]}>{I18n.t(text?.label, { defaultValue: text?.label, second: text?.second })}</Text>; - } + return ( + <Text style={[styles.pickerText, { color: themes[theme].actionTintColor }]}> + {I18n.t(text?.label, { defaultValue: text?.label, second: text?.second })} + </Text> + ); + }; pickerSelection = (title, key) => { const { preferences } = this.state; @@ -70,7 +74,7 @@ class UserNotificationPreferencesView extends React.Component { const defaultOption = this.findDefaultOption(key); if (OPTIONS[key][0]?.value !== 'default') { - values = [{ label: `${ I18n.t('Default') } (${ I18n.t(defaultOption.label) })` }, ...OPTIONS[key]]; + values = [{ label: `${I18n.t('Default')} (${I18n.t(defaultOption.label)})` }, ...OPTIONS[key]]; } navigation.navigate('PickerView', { @@ -79,17 +83,19 @@ class UserNotificationPreferencesView extends React.Component { value: preferences[key], onChangeValue: value => this.onValueChangePicker(key, value ?? defaultOption.value) }); - } + }; onValueChangePicker = (key, value) => this.saveNotificationPreferences({ [key]: value.toString() }); - saveNotificationPreferences = async(params) => { + saveNotificationPreferences = async params => { const { user } = this.props; const { id } = user; const result = await RocketChat.setUserPreferences(id, params); - const { user: { settings } } = result; + const { + user: { settings } + } = result; this.setState({ preferences: settings.preferences }); - } + }; render() { const { theme } = this.props; @@ -98,47 +104,47 @@ class UserNotificationPreferencesView extends React.Component { <SafeAreaView testID='user-notification-preference-view'> <StatusBar /> <List.Container> - {loading - ? ( - <> - <List.Section title='Desktop_Notifications'> - <List.Separator /> - <List.Item - title='Alert' - testID='user-notification-preference-view-alert' - onPress={title => this.pickerSelection(title, 'desktopNotifications')} - right={() => this.renderPickerOption('desktopNotifications')} - /> - <List.Separator /> - <List.Info info='Desktop_Alert_info' /> - </List.Section> + {loading ? ( + <> + <List.Section title='Desktop_Notifications'> + <List.Separator /> + <List.Item + title='Alert' + testID='user-notification-preference-view-alert' + onPress={title => this.pickerSelection(title, 'desktopNotifications')} + right={() => this.renderPickerOption('desktopNotifications')} + /> + <List.Separator /> + <List.Info info='Desktop_Alert_info' /> + </List.Section> - <List.Section title='Push_Notifications'> - <List.Separator /> - <List.Item - title='Alert' - testID='user-notification-preference-view-push-notification' - onPress={title => this.pickerSelection(title, 'mobileNotifications')} - right={() => this.renderPickerOption('mobileNotifications')} - /> - <List.Separator /> - <List.Info info='Push_Notifications_Alert_Info' /> - </List.Section> + <List.Section title='Push_Notifications'> + <List.Separator /> + <List.Item + title='Alert' + testID='user-notification-preference-view-push-notification' + onPress={title => this.pickerSelection(title, 'mobileNotifications')} + right={() => this.renderPickerOption('mobileNotifications')} + /> + <List.Separator /> + <List.Info info='Push_Notifications_Alert_Info' /> + </List.Section> - <List.Section title='Email'> - <List.Separator /> - <List.Item - title='Alert' - testID='user-notification-preference-view-email-alert' - onPress={title => this.pickerSelection(title, 'emailNotificationMode')} - right={() => this.renderPickerOption('emailNotificationMode')} - /> - <List.Separator /> - <List.Info info='You_need_to_verifiy_your_email_address_to_get_notications' /> - </List.Section> - </> - ) : <ActivityIndicator theme={theme} /> - } + <List.Section title='Email'> + <List.Separator /> + <List.Item + title='Alert' + testID='user-notification-preference-view-email-alert' + onPress={title => this.pickerSelection(title, 'emailNotificationMode')} + right={() => this.renderPickerOption('emailNotificationMode')} + /> + <List.Separator /> + <List.Info info='You_need_to_verifiy_your_email_address_to_get_notications' /> + </List.Section> + </> + ) : ( + <ActivityIndicator theme={theme} /> + )} </List.Container> </SafeAreaView> ); diff --git a/app/views/UserNotificationPreferencesView/options.js b/app/views/UserNotificationPreferencesView/options.js index 7ed02e12e..e5c2094b7 100644 --- a/app/views/UserNotificationPreferencesView/options.js +++ b/app/views/UserNotificationPreferencesView/options.js @@ -1,19 +1,33 @@ -const commonOptions = [{ - label: 'Default', value: 'default' -}, { - label: 'All_Messages', value: 'all' -}, { - label: 'Mentions', value: 'mentions' -}, { - label: 'Nothing', value: 'nothing' -}]; +const commonOptions = [ + { + label: 'Default', + value: 'default' + }, + { + label: 'All_Messages', + value: 'all' + }, + { + label: 'Mentions', + value: 'mentions' + }, + { + label: 'Nothing', + value: 'nothing' + } +]; export const OPTIONS = { desktopNotifications: commonOptions, mobileNotifications: commonOptions, - emailNotificationMode: [{ - label: 'Email_Notification_Mode_All', value: 'mentions' - }, { - label: 'Email_Notification_Mode_Disabled', value: 'nothing' - }] + emailNotificationMode: [ + { + label: 'Email_Notification_Mode_All', + value: 'mentions' + }, + { + label: 'Email_Notification_Mode_Disabled', + value: 'nothing' + } + ] }; diff --git a/app/views/UserPreferencesView/index.js b/app/views/UserPreferencesView/index.js index bf94d31fb..573ab58f9 100644 --- a/app/views/UserPreferencesView/index.js +++ b/app/views/UserPreferencesView/index.js @@ -2,9 +2,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import I18n from '../../i18n'; -import { - logEvent, events -} from '../../utils/log'; +import { events, logEvent } from '../../utils/log'; import SafeAreaView from '../../containers/SafeAreaView'; import StatusBar from '../../containers/StatusBar'; import * as List from '../../containers/List'; @@ -16,13 +14,13 @@ class UserPreferencesView extends React.Component { static propTypes = { navigation: PropTypes.object - } + }; navigateToScreen = (screen, params) => { - logEvent(events[`SE_GO_${ screen.replace('View', '').toUpperCase() }`]); + logEvent(events[`SE_GO_${screen.replace('View', '').toUpperCase()}`]); const { navigation } = this.props; navigation.navigate(screen, params); - } + }; render() { return ( diff --git a/app/views/VisitorNavigationView.js b/app/views/VisitorNavigationView.js index 0621a6f2d..8a478d973 100644 --- a/app/views/VisitorNavigationView.js +++ b/app/views/VisitorNavigationView.js @@ -8,9 +8,9 @@ import { themes } from '../constants/colors'; import openLink from '../utils/openLink'; import I18n from '../i18n'; import debounce from '../utils/debounce'; -import sharedStyles from './Styles'; import * as List from '../containers/List'; import SafeAreaView from '../containers/SafeAreaView'; +import sharedStyles from './Styles'; const styles = StyleSheet.create({ noResult: { @@ -37,7 +37,7 @@ const VisitorNavigationView = ({ route, theme }) => { let total = 0; const [pages, setPages] = useState([]); - const getPages = async() => { + const getPages = async () => { const rid = route.params?.rid; if (rid) { try { @@ -53,7 +53,9 @@ const VisitorNavigationView = ({ route, theme }) => { } }; - useEffect(() => { getPages(); }, []); + useEffect(() => { + getPages(); + }, []); const onEndReached = debounce(() => { if (pages.length <= total) { @@ -70,7 +72,9 @@ const VisitorNavigationView = ({ route, theme }) => { ListFooterComponent={List.Separator} ListHeaderComponent={List.Separator} contentContainerStyle={List.styles.contentContainerStyleFlatList} - ListEmptyComponent={() => <Text style={[styles.noResult, { color: themes[theme].titleText }]}>{I18n.t('No_results_found')}</Text>} + ListEmptyComponent={() => ( + <Text style={[styles.noResult, { color: themes[theme].titleText }]}>{I18n.t('No_results_found')}</Text> + )} keyExtractor={item => item} onEndReached={onEndReached} onEndReachedThreshold={5} diff --git a/app/views/WithoutServersView.js b/app/views/WithoutServersView.tsx similarity index 70% rename from app/views/WithoutServersView.js rename to app/views/WithoutServersView.tsx index b4f101a69..b77719839 100644 --- a/app/views/WithoutServersView.js +++ b/app/views/WithoutServersView.tsx @@ -1,8 +1,5 @@ import React from 'react'; -import { - StyleSheet, View, Text -} from 'react-native'; -import PropTypes from 'prop-types'; +import { StyleSheet, Text, View } from 'react-native'; import ShareExtension from 'rn-extensions-share'; import * as HeaderButton from '../containers/HeaderButton'; @@ -29,27 +26,20 @@ const styles = StyleSheet.create({ } }); -class WithoutServerView extends React.Component { +class WithoutServerView extends React.Component<any, any> { static navigationOptions = () => ({ title: 'Rocket.Chat', - headerLeft: () => ( - <HeaderButton.CancelModal - onPress={ShareExtension.close} - testID='share-extension-close' - /> - ) - }) - - static propTypes = { - theme: PropTypes.string - } + headerLeft: () => <HeaderButton.CancelModal onPress={ShareExtension.close} testID='share-extension-close' /> + }); render() { const { theme } = this.props; return ( <View style={[styles.container, { backgroundColor: themes[theme].backgroundColor }]}> <Text style={[styles.title, { color: themes[theme].titleText }]}>{I18n.t('Without_Servers')}</Text> - <Text style={[styles.content, { color: themes[theme].titleText }]}>{I18n.t('You_need_to_access_at_least_one_RocketChat_server_to_share_something')}</Text> + <Text style={[styles.content, { color: themes[theme].titleText }]}> + {I18n.t('You_need_to_access_at_least_one_RocketChat_server_to_share_something')} + </Text> </View> ); } diff --git a/app/views/WorkspaceView/ServerAvatar.js b/app/views/WorkspaceView/ServerAvatar.js index 0a82b30f3..749c4b10e 100644 --- a/app/views/WorkspaceView/ServerAvatar.js +++ b/app/views/WorkspaceView/ServerAvatar.js @@ -1,5 +1,5 @@ import React from 'react'; -import { StyleSheet, View, Text } from 'react-native'; +import { StyleSheet, Text, View } from 'react-native'; import PropTypes from 'prop-types'; import { createImageProgress } from 'react-native-image-progress'; import * as Progress from 'react-native-progress'; @@ -54,7 +54,7 @@ const ServerAvatar = React.memo(({ theme, url, image }) => ( {image && ( <ImageProgress style={[styles.image, { borderColor: themes[theme].borderColor }]} - source={{ uri: `${ url }/${ image }` }} + source={{ uri: `${url}/${image}` }} resizeMode={FastImage.resizeMode.cover} indicator={Progress.Pie} indicatorProps={{ diff --git a/app/views/WorkspaceView/index.js b/app/views/WorkspaceView/index.js index 553e7135e..dbd1c69e0 100644 --- a/app/views/WorkspaceView/index.js +++ b/app/views/WorkspaceView/index.js @@ -1,21 +1,21 @@ import React from 'react'; -import { View, Text } from 'react-native'; +import { Text, View } from 'react-native'; import PropTypes from 'prop-types'; import { connect } from 'react-redux'; import I18n from '../../i18n'; import Button from '../../containers/Button'; -import styles from './styles'; import { themes } from '../../constants/colors'; import { withTheme } from '../../theme'; import FormContainer, { FormContainerInner } from '../../containers/FormContainer'; -import ServerAvatar from './ServerAvatar'; import { getShowLoginButton } from '../../selectors/login'; +import ServerAvatar from './ServerAvatar'; +import styles from './styles'; class WorkspaceView extends React.Component { static navigationOptions = () => ({ title: I18n.t('Your_workspace') - }) + }); static propTypes = { navigation: PropTypes.object, @@ -29,28 +29,29 @@ class WorkspaceView extends React.Component { showLoginButton: PropTypes.bool, Accounts_iframe_enabled: PropTypes.bool, inviteLinkToken: PropTypes.string - } + }; get showRegistrationButton() { const { registrationForm, inviteLinkToken, Accounts_iframe_enabled } = this.props; - return !Accounts_iframe_enabled && (registrationForm === 'Public' || (registrationForm === 'Secret URL' && inviteLinkToken?.length)); + return ( + !Accounts_iframe_enabled && + (registrationForm === 'Public' || (registrationForm === 'Secret URL' && inviteLinkToken?.length)) + ); } login = () => { - const { - navigation, server, Site_Name, Accounts_iframe_enabled - } = this.props; + const { navigation, server, Site_Name, Accounts_iframe_enabled } = this.props; if (Accounts_iframe_enabled) { navigation.navigate('AuthenticationWebView', { url: server, authType: 'iframe' }); return; } navigation.navigate('LoginView', { title: Site_Name }); - } + }; register = () => { const { navigation, Site_Name } = this.props; navigation.navigate('RegisterView', { title: Site_Name }); - } + }; renderRegisterDisabled = () => { const { Accounts_iframe_enabled, registrationText, theme } = this.props; @@ -59,12 +60,10 @@ class WorkspaceView extends React.Component { } return <Text style={[styles.registrationText, { color: themes[theme].auxiliaryText }]}>{registrationText}</Text>; - } + }; render() { - const { - theme, Site_Name, Site_Url, Assets_favicon_512, server, showLoginButton - } = this.props; + const { theme, Site_Name, Site_Url, Assets_favicon_512, server, showLoginButton } = this.props; return ( <FormContainer theme={theme} testID='workspace-view'> @@ -74,28 +73,21 @@ class WorkspaceView extends React.Component { <Text style={[styles.serverName, { color: themes[theme].titleText }]}>{Site_Name}</Text> <Text style={[styles.serverUrl, { color: themes[theme].auxiliaryText }]}>{Site_Url}</Text> </View> - {showLoginButton - ? ( - <Button - title={I18n.t('Login')} - type='primary' - onPress={this.login} - theme={theme} - testID='workspace-view-login' - /> - ) : null} - { - this.showRegistrationButton ? ( - <Button - title={I18n.t('Create_account')} - type='secondary' - backgroundColor={themes[theme].chatComponentBackground} - onPress={this.register} - theme={theme} - testID='workspace-view-register' - /> - ) : this.renderRegisterDisabled() - } + {showLoginButton ? ( + <Button title={I18n.t('Login')} type='primary' onPress={this.login} theme={theme} testID='workspace-view-login' /> + ) : null} + {this.showRegistrationButton ? ( + <Button + title={I18n.t('Create_account')} + type='secondary' + backgroundColor={themes[theme].chatComponentBackground} + onPress={this.register} + theme={theme} + testID='workspace-view-register' + /> + ) : ( + this.renderRegisterDisabled() + )} </FormContainerInner> </FormContainer> ); @@ -104,7 +96,6 @@ class WorkspaceView extends React.Component { const mapStateToProps = state => ({ server: state.server.server, - adding: state.server.adding, Site_Name: state.settings.Site_Name, Site_Url: state.settings.Site_Url, Assets_favicon_512: state.settings.Assets_favicon_512, diff --git a/babel.config.js b/babel.config.js index f40cb434c..708e683ff 100644 --- a/babel.config.js +++ b/babel.config.js @@ -1,8 +1,6 @@ module.exports = { presets: ['module:metro-react-native-babel-preset'], - plugins: [ - ['@babel/plugin-proposal-decorators', { legacy: true }] - ], + plugins: [['@babel/plugin-proposal-decorators', { legacy: true }]], env: { production: { plugins: ['transform-remove-console'] diff --git a/e2e/.mocharc.json b/e2e/.mocharc.json index 8e23ab48a..8c4277c78 100644 --- a/e2e/.mocharc.json +++ b/e2e/.mocharc.json @@ -1,6 +1,6 @@ { - "timeout": 300000, - "recursive": true, - "bail": true, - "file":"e2e/tests/init.js" -} \ No newline at end of file + "timeout": 300000, + "recursive": true, + "bail": true, + "file": "e2e/tests/init.js" +} diff --git a/e2e/data.js b/e2e/data.js index 4dc093cc6..979be71c7 100644 --- a/e2e/data.js +++ b/e2e/data.js @@ -8,25 +8,25 @@ const data = { alternateServer: 'https://stable.rocket.chat', users: { regular: { - username: `userone${ value }`, + username: `userone${value}`, password: '123', - email: `mobile+regular${ value }@rocket.chat` + email: `mobile+regular${value}@rocket.chat` }, alternate: { - username: `usertwo${ value }`, + username: `usertwo${value}`, password: '123', - email: `mobile+alternate${ value }@rocket.chat`, + email: `mobile+alternate${value}@rocket.chat`, totpSecret: 'NA4GOMZGHBQSK6KEFRVT62DMGJJGSYZJFZIHO3ZOGVXWCYZ6MMZQ' }, profileChanges: { - username: `userthree${ value }`, + username: `userthree${value}`, password: '123', - email: `mobile+profileChanges${ value }@rocket.chat` + email: `mobile+profileChanges${value}@rocket.chat` }, existing: { - username: `existinguser${ value }`, + username: `existinguser${value}`, password: '123', - email: `mobile+existing${ value }@rocket.chat` + email: `mobile+existing${value}@rocket.chat` } }, channels: { @@ -40,36 +40,36 @@ const data = { }, groups: { private: { - name: `detox-private-${ value }` + name: `detox-private-${value}` }, alternate: { - name: `detox-alternate-${ value }` + name: `detox-alternate-${value}` } }, teams: { private: { - name: `detox-team-${ value }` + name: `detox-team-${value}` } }, registeringUser: { - username: `newuser${ value }`, - password: `password${ value }`, - email: `mobile+registering${ value }@rocket.chat` + username: `newuser${value}`, + password: `password${value}`, + email: `mobile+registering${value}@rocket.chat` }, registeringUser2: { - username: `newusertwo${ value }`, - password: `passwordtwo${ value }`, - email: `mobile+registeringtwo${ value }@rocket.chat` + username: `newusertwo${value}`, + password: `passwordtwo${value}`, + email: `mobile+registeringtwo${value}@rocket.chat` }, registeringUser3: { - username: `newuserthree${ value }`, - password: `passwordthree${ value }`, - email: `mobile+registeringthree${ value }@rocket.chat` + username: `newuserthree${value}`, + password: `passwordthree${value}`, + email: `mobile+registeringthree${value}@rocket.chat` }, registeringUser4: { - username: `newuserfour${ value }`, - password: `passwordfour${ value }`, - email: `mobile+registeringfour${ value }@rocket.chat` + username: `newuserfour${value}`, + password: `passwordfour${value}`, + email: `mobile+registeringfour${value}@rocket.chat` }, random: value }; diff --git a/e2e/data/data.cloud.js b/e2e/data/data.cloud.js index df98fdd7a..2f4c5d8d5 100644 --- a/e2e/data/data.cloud.js +++ b/e2e/data/data.cloud.js @@ -9,25 +9,25 @@ const data = { alternateServer: 'https://stable.rocket.chat', users: { regular: { - username: `userone${ value }`, + username: `userone${value}`, password: '123', - email: `mobile+regular${ value }@rocket.chat` + email: `mobile+regular${value}@rocket.chat` }, alternate: { - username: `usertwo${ value }`, + username: `usertwo${value}`, password: '123', - email: `mobile+alternate${ value }@rocket.chat`, + email: `mobile+alternate${value}@rocket.chat`, totpSecret: 'NA4GOMZGHBQSK6KEFRVT62DMGJJGSYZJFZIHO3ZOGVXWCYZ6MMZQ' }, profileChanges: { - username: `userthree${ value }`, + username: `userthree${value}`, password: '123', - email: `mobile+profileChanges${ value }@rocket.chat` + email: `mobile+profileChanges${value}@rocket.chat` }, existing: { - username: `existinguser${ value }`, + username: `existinguser${value}`, password: '123', - email: `mobile+existing${ value }@rocket.chat` + email: `mobile+existing${value}@rocket.chat` } }, channels: { @@ -41,33 +41,33 @@ const data = { }, groups: { private: { - name: `detox-private-${ value }` + name: `detox-private-${value}` } }, teams: { private: { - name: `detox-team-${ value }` + name: `detox-team-${value}` } }, registeringUser: { - username: `newuser${ value }`, - password: `password${ value }`, - email: `mobile+registering${ value }@rocket.chat` + username: `newuser${value}`, + password: `password${value}`, + email: `mobile+registering${value}@rocket.chat` }, registeringUser2: { - username: `newusertwo${ value }`, - password: `passwordtwo${ value }`, - email: `mobile+registeringtwo${ value }@rocket.chat` + username: `newusertwo${value}`, + password: `passwordtwo${value}`, + email: `mobile+registeringtwo${value}@rocket.chat` }, registeringUser3: { - username: `newuserthree${ value }`, - password: `passwordthree${ value }`, - email: `mobile+registeringthree${ value }@rocket.chat` + username: `newuserthree${value}`, + password: `passwordthree${value}`, + email: `mobile+registeringthree${value}@rocket.chat` }, registeringUser4: { - username: `newuserfour${ value }`, - password: `passwordfour${ value }`, - email: `mobile+registeringfour${ value }@rocket.chat` + username: `newuserfour${value}`, + password: `passwordfour${value}`, + email: `mobile+registeringfour${value}@rocket.chat` }, random: value }; diff --git a/e2e/data/data.docker.js b/e2e/data/data.docker.js index 6c3ce1925..104007ce2 100644 --- a/e2e/data/data.docker.js +++ b/e2e/data/data.docker.js @@ -9,25 +9,25 @@ const data = { alternateServer: 'https://stable.rocket.chat', users: { regular: { - username: `userone${ value }`, + username: `userone${value}`, password: '123', - email: `mobile+regular${ value }@rocket.chat` + email: `mobile+regular${value}@rocket.chat` }, alternate: { - username: `usertwo${ value }`, + username: `usertwo${value}`, password: '123', - email: `mobile+alternate${ value }@rocket.chat`, + email: `mobile+alternate${value}@rocket.chat`, totpSecret: 'NA4GOMZGHBQSK6KEFRVT62DMGJJGSYZJFZIHO3ZOGVXWCYZ6MMZQ' }, profileChanges: { - username: `userthree${ value }`, + username: `userthree${value}`, password: '123', - email: `mobile+profileChanges${ value }@rocket.chat` + email: `mobile+profileChanges${value}@rocket.chat` }, existing: { - username: `existinguser${ value }`, + username: `existinguser${value}`, password: '123', - email: `mobile+existing${ value }@rocket.chat` + email: `mobile+existing${value}@rocket.chat` } }, channels: { @@ -41,36 +41,36 @@ const data = { }, groups: { private: { - name: `detox-private-${ value }` + name: `detox-private-${value}` }, alternate: { - name: `detox-alternate-${ value }` + name: `detox-alternate-${value}` } }, teams: { private: { - name: `detox-team-${ value }` + name: `detox-team-${value}` } }, registeringUser: { - username: `newuser${ value }`, - password: `password${ value }`, - email: `mobile+registering${ value }@rocket.chat` + username: `newuser${value}`, + password: `password${value}`, + email: `mobile+registering${value}@rocket.chat` }, registeringUser2: { - username: `newusertwo${ value }`, - password: `passwordtwo${ value }`, - email: `mobile+registeringtwo${ value }@rocket.chat` + username: `newusertwo${value}`, + password: `passwordtwo${value}`, + email: `mobile+registeringtwo${value}@rocket.chat` }, registeringUser3: { - username: `newuserthree${ value }`, - password: `passwordthree${ value }`, - email: `mobile+registeringthree${ value }@rocket.chat` + username: `newuserthree${value}`, + password: `passwordthree${value}`, + email: `mobile+registeringthree${value}@rocket.chat` }, registeringUser4: { - username: `newuserfour${ value }`, - password: `passwordfour${ value }`, - email: `mobile+registeringfour${ value }@rocket.chat` + username: `newuserfour${value}`, + password: `passwordfour${value}`, + email: `mobile+registeringfour${value}@rocket.chat` }, random: value }; diff --git a/e2e/helpers/app.js b/e2e/helpers/app.js index 45a67df56..6ade15c9b 100644 --- a/e2e/helpers/app.js +++ b/e2e/helpers/app.js @@ -1,86 +1,114 @@ const data = require('../data'); async function navigateToWorkspace(server = data.server) { - await waitFor(element(by.id('onboarding-view'))).toBeVisible().withTimeout(10000); - await element(by.id('join-workspace')).tap(); - await waitFor(element(by.id('new-server-view'))).toBeVisible().withTimeout(60000); - await element(by.id('new-server-view-input')).typeText(`${ server }\n`); - await waitFor(element(by.id('workspace-view'))).toBeVisible().withTimeout(60000); + await waitFor(element(by.id('new-server-view'))) + .toBeVisible() + .withTimeout(60000); + await element(by.id('new-server-view-input')).typeText(`${server}\n`); + await waitFor(element(by.id('workspace-view'))) + .toBeVisible() + .withTimeout(60000); await expect(element(by.id('workspace-view'))).toBeVisible(); } async function navigateToLogin(server) { - await waitFor(element(by.id('onboarding-view'))).toBeVisible().withTimeout(20000); await navigateToWorkspace(server); await element(by.id('workspace-view-login')).tap(); - await waitFor(element(by.id('login-view'))).toBeVisible().withTimeout(2000); + await waitFor(element(by.id('login-view'))) + .toBeVisible() + .withTimeout(2000); await expect(element(by.id('login-view'))).toBeVisible(); } async function navigateToRegister(server) { - await waitFor(element(by.id('onboarding-view'))).toBeVisible().withTimeout(20000); await navigateToWorkspace(server); await element(by.id('workspace-view-register')).tap(); - await waitFor(element(by.id('register-view'))).toBeVisible().withTimeout(2000); + await waitFor(element(by.id('register-view'))) + .toBeVisible() + .withTimeout(2000); } async function login(username, password) { - await waitFor(element(by.id('login-view'))).toBeVisible().withTimeout(2000); + await waitFor(element(by.id('login-view'))) + .toBeVisible() + .withTimeout(2000); await element(by.id('login-view-email')).replaceText(username); await element(by.id('login-view-password')).replaceText(password); await element(by.id('login-view-submit')).tap(); - await waitFor(element(by.id('rooms-list-view'))).toBeVisible().withTimeout(30000); + await waitFor(element(by.id('rooms-list-view'))) + .toBeVisible() + .withTimeout(30000); } async function logout() { await element(by.id('rooms-list-view-sidebar')).tap(); - await waitFor(element(by.id('sidebar-view'))).toBeVisible().withTimeout(2000); - await waitFor(element(by.id('sidebar-settings'))).toBeVisible().withTimeout(2000); + await waitFor(element(by.id('sidebar-view'))) + .toBeVisible() + .withTimeout(2000); + await waitFor(element(by.id('sidebar-settings'))) + .toBeVisible() + .withTimeout(2000); await element(by.id('sidebar-settings')).tap(); - await waitFor(element(by.id('settings-view'))).toBeVisible().withTimeout(2000); + await waitFor(element(by.id('settings-view'))) + .toBeVisible() + .withTimeout(2000); await element(by.type('UIScrollView')).atIndex(1).scrollTo('bottom'); await element(by.id('settings-logout')).tap(); const logoutAlertMessage = 'You will be logged out of this application.'; - await waitFor(element(by.text(logoutAlertMessage)).atIndex(0)).toExist().withTimeout(10000); + await waitFor(element(by.text(logoutAlertMessage)).atIndex(0)) + .toExist() + .withTimeout(10000); await expect(element(by.text(logoutAlertMessage)).atIndex(0)).toExist(); await element(by.text('Logout')).tap(); - await waitFor(element(by.id('onboarding-view'))).toBeVisible().withTimeout(10000); - await expect(element(by.id('onboarding-view'))).toBeVisible(); + await waitFor(element(by.id('new-server-view'))) + .toBeVisible() + .withTimeout(10000); + await expect(element(by.id('new-server-view'))).toBeVisible(); } async function mockMessage(message, isThread = false) { const input = isThread ? 'messagebox-input-thread' : 'messagebox-input'; await element(by.id(input)).tap(); - await element(by.id(input)).typeText(`${ data.random }${ message }`); + await element(by.id(input)).typeText(`${data.random}${message}`); await element(by.id('messagebox-send-message')).tap(); - await waitFor(element(by.label(`${ data.random }${ message }`))).toExist().withTimeout(60000); - await expect(element(by.label(`${ data.random }${ message }`))).toExist(); - await element(by.label(`${ data.random }${ message }`)).atIndex(0).tap(); + await waitFor(element(by.label(`${data.random}${message}`))) + .toExist() + .withTimeout(60000); + await expect(element(by.label(`${data.random}${message}`))).toExist(); + await element(by.label(`${data.random}${message}`)) + .atIndex(0) + .tap(); } async function starMessage(message) { - const messageLabel = `${ data.random }${ message }`; + const messageLabel = `${data.random}${message}`; await element(by.label(messageLabel)).atIndex(0).longPress(); await expect(element(by.id('action-sheet'))).toExist(); await expect(element(by.id('action-sheet-handle'))).toBeVisible(); await element(by.id('action-sheet-handle')).swipe('up', 'fast', 0.5); await element(by.label('Star')).atIndex(0).tap(); - await waitFor(element(by.id('action-sheet'))).not.toExist().withTimeout(5000); + await waitFor(element(by.id('action-sheet'))) + .not.toExist() + .withTimeout(5000); } async function pinMessage(message) { - const messageLabel = `${ data.random }${ message }`; + const messageLabel = `${data.random}${message}`; await waitFor(element(by.label(messageLabel)).atIndex(0)).toExist(); await element(by.label(messageLabel)).atIndex(0).longPress(); await expect(element(by.id('action-sheet'))).toExist(); await expect(element(by.id('action-sheet-handle'))).toBeVisible(); await element(by.id('action-sheet-handle')).swipe('up', 'fast', 0.5); await element(by.label('Pin')).atIndex(0).tap(); - await waitFor(element(by.id('action-sheet'))).not.toExist().withTimeout(5000); + await waitFor(element(by.id('action-sheet'))) + .not.toExist() + .withTimeout(5000); } async function dismissReviewNag() { - await waitFor(element(by.text('Are you enjoying this app?'))).toExist().withTimeout(60000); + await waitFor(element(by.text('Are you enjoying this app?'))) + .toExist() + .withTimeout(60000); await element(by.label('No').and(by.type('_UIAlertControllerActionView'))).tap(); // Tap `no` on ask for review alert } @@ -95,10 +123,14 @@ function sleep(ms) { async function searchRoom(room) { await element(by.id('rooms-list-view-search')).tap(); await expect(element(by.id('rooms-list-view-search-input'))).toExist(); - await waitFor(element(by.id('rooms-list-view-search-input'))).toExist().withTimeout(5000); + await waitFor(element(by.id('rooms-list-view-search-input'))) + .toExist() + .withTimeout(5000); await element(by.id('rooms-list-view-search-input')).typeText(room); await sleep(300); - await waitFor(element(by.id(`rooms-list-view-item-${ room }`))).toBeVisible().withTimeout(60000); + await waitFor(element(by.id(`rooms-list-view-item-${room}`))) + .toBeVisible() + .withTimeout(60000); } async function tryTapping(theElement, timeout, longtap = false) { @@ -109,7 +141,8 @@ async function tryTapping(theElement, timeout, longtap = false) { await theElement.tap(); } } catch (e) { - if (timeout <= 0) { // TODO: Maths. How closely has the timeout been honoured here? + if (timeout <= 0) { + // TODO: Maths. How closely has the timeout been honoured here? throw e; } await sleep(100); @@ -117,11 +150,15 @@ async function tryTapping(theElement, timeout, longtap = false) { } } -const checkServer = async(server) => { - const label = `Connected to ${ server }`; +const checkServer = async server => { + const label = `Connected to ${server}`; await element(by.id('rooms-list-view-sidebar')).tap(); - await waitFor(element(by.id('sidebar-view'))).toBeVisible().withTimeout(2000); - await waitFor(element(by.label(label))).toBeVisible().withTimeout(10000); + await waitFor(element(by.id('sidebar-view'))) + .toBeVisible() + .withTimeout(2000); + await waitFor(element(by.label(label))) + .toBeVisible() + .withTimeout(10000); await element(by.id('sidebar-close-drawer')).tap(); }; diff --git a/e2e/helpers/data_setup.js b/e2e/helpers/data_setup.js index fd02a20c5..4c0f2d4e0 100644 --- a/e2e/helpers/data_setup.js +++ b/e2e/helpers/data_setup.js @@ -1,4 +1,5 @@ const axios = require('axios').default; + const data = require('../data'); const TEAM_TYPE = { @@ -9,14 +10,14 @@ const TEAM_TYPE = { const { server } = data; const rocketchat = axios.create({ - baseURL: `${ server }/api/v1/`, + baseURL: `${server}/api/v1/`, headers: { 'Content-Type': 'application/json;charset=UTF-8' } }); -const login = async(username, password) => { - console.log(`Logging in as user ${ username }`); +const login = async (username, password) => { + console.log(`Logging in as user ${username}`); const response = await rocketchat.post('login', { user: username, password @@ -28,8 +29,8 @@ const login = async(username, password) => { return { authToken, userId }; }; -const createUser = async(username, password, name, email) => { - console.log(`Creating user ${ username }`); +const createUser = async (username, password, name, email) => { + console.log(`Creating user ${username}`); try { await rocketchat.post('users.create', { username, @@ -43,16 +44,17 @@ const createUser = async(username, password, name, email) => { } }; -const createChannelIfNotExists = async(channelname) => { - console.log(`Creating public channel ${ channelname }`); +const createChannelIfNotExists = async channelname => { + console.log(`Creating public channel ${channelname}`); try { const room = await rocketchat.post('channels.create', { name: channelname }); return room; } catch (createError) { - try { // Maybe it exists already? - const room = rocketchat.get(`channels.info?roomName=${ channelname }`); + try { + // Maybe it exists already? + const room = rocketchat.get(`channels.info?roomName=${channelname}`); return room; } catch (infoError) { console.log(JSON.stringify(createError)); @@ -62,16 +64,17 @@ const createChannelIfNotExists = async(channelname) => { } }; -const createTeamIfNotExists = async(teamname) => { - console.log(`Creating private team ${ teamname }`); +const createTeamIfNotExists = async teamname => { + console.log(`Creating private team ${teamname}`); try { await rocketchat.post('teams.create', { name: teamname, type: TEAM_TYPE.PRIVATE }); } catch (createError) { - try { // Maybe it exists already? - await rocketchat.get(`teams.info?teamName=${ teamname }`); + try { + // Maybe it exists already? + await rocketchat.get(`teams.info?teamName=${teamname}`); } catch (infoError) { console.log(JSON.stringify(createError)); console.log(JSON.stringify(infoError)); @@ -80,15 +83,16 @@ const createTeamIfNotExists = async(teamname) => { } }; -const createGroupIfNotExists = async(groupname) => { - console.log(`Creating private group ${ groupname }`); +const createGroupIfNotExists = async groupname => { + console.log(`Creating private group ${groupname}`); try { await rocketchat.post('groups.create', { name: groupname }); } catch (createError) { - try { // Maybe it exists already? - await rocketchat.get(`groups.info?roomName=${ groupname }`); + try { + // Maybe it exists already? + await rocketchat.get(`groups.info?roomName=${groupname}`); } catch (infoError) { console.log(JSON.stringify(createError)); console.log(JSON.stringify(infoError)); @@ -97,16 +101,13 @@ const createGroupIfNotExists = async(groupname) => { } }; -const changeChannelJoinCode = async(roomId, joinCode) => { - console.log(`Changing channel Join Code ${ roomId }`); +const changeChannelJoinCode = async (roomId, joinCode) => { + console.log(`Changing channel Join Code ${roomId}`); try { await rocketchat.post('method.call/saveRoomSettings', { message: JSON.stringify({ method: 'saveRoomSettings', - params: [ - roomId, - { joinCode } - ] + params: [roomId, { joinCode }] }) }); } catch (createError) { @@ -115,8 +116,8 @@ const changeChannelJoinCode = async(roomId, joinCode) => { } }; -const sendMessage = async(user, channel, msg) => { - console.log(`Sending message to ${ channel }`); +const sendMessage = async (user, channel, msg) => { + console.log(`Sending message to ${channel}`); try { await login(user.username, user.password); await rocketchat.post('chat.postMessage', { channel, msg }); @@ -126,7 +127,7 @@ const sendMessage = async(user, channel, msg) => { } }; -const setup = async() => { +const setup = async () => { await login(data.adminUser, data.adminPassword); for (const userKey in data.users) { @@ -139,7 +140,11 @@ const setup = async() => { for (const channelKey in data.channels) { if (Object.prototype.hasOwnProperty.call(data.channels, channelKey)) { const channel = data.channels[channelKey]; - const { data: { channel: { _id } } } = await createChannelIfNotExists(channel.name); + const { + data: { + channel: { _id } + } + } = await createChannelIfNotExists(channel.name); if (channel.joinCode) { await changeChannelJoinCode(_id, channel.joinCode); @@ -164,16 +169,20 @@ const setup = async() => { } }; -const get = (endpoint) => { - console.log(`GET /${ endpoint }`); +const get = endpoint => { + console.log(`GET /${endpoint}`); return rocketchat.get(endpoint); }; const post = (endpoint, body) => { - console.log(`POST /${ endpoint } ${ JSON.stringify(body) }`); + console.log(`POST /${endpoint} ${JSON.stringify(body)}`); return rocketchat.post(endpoint, body); }; module.exports = { - setup, sendMessage, get, post, login + setup, + sendMessage, + get, + post, + login }; diff --git a/e2e/tests/assorted/01-e2eencryption.spec.js b/e2e/tests/assorted/01-e2eencryption.spec.js index de5ccb225..1a40335b8 100644 --- a/e2e/tests/assorted/01-e2eencryption.spec.js +++ b/e2e/tests/assorted/01-e2eencryption.spec.js @@ -1,28 +1,33 @@ -const { - navigateToLogin, login, sleep, tapBack, mockMessage, searchRoom, logout -} = require('../../helpers/app'); - +const { navigateToLogin, login, sleep, tapBack, mockMessage, searchRoom, logout } = require('../../helpers/app'); const data = require('../../data'); const testuser = data.users.regular; const otheruser = data.users.alternate; -const checkServer = async(server) => { - const label = `Connected to ${ server }`; +const checkServer = async server => { + const label = `Connected to ${server}`; await element(by.id('rooms-list-view-sidebar')).tap(); - await waitFor(element(by.id('sidebar-view'))).toBeVisible().withTimeout(2000); - await waitFor(element(by.label(label))).toBeVisible().withTimeout(60000); + await waitFor(element(by.id('sidebar-view'))) + .toBeVisible() + .withTimeout(2000); + await waitFor(element(by.label(label))) + .toBeVisible() + .withTimeout(60000); await element(by.id('sidebar-close-drawer')).tap(); }; -const checkBanner = async() => { - await waitFor(element(by.id('listheader-encryption').withDescendant(by.label('Save Your Encryption Password')))).toBeVisible().withTimeout(10000); +const checkBanner = async () => { + await waitFor(element(by.id('listheader-encryption').withDescendant(by.label('Save Your Encryption Password')))) + .toBeVisible() + .withTimeout(10000); }; async function navigateToRoom(roomName) { - await searchRoom(`${ roomName }`); - await element(by.id(`rooms-list-view-item-${ roomName }`)).tap(); - await waitFor(element(by.id('room-view'))).toBeVisible().withTimeout(5000); + await searchRoom(`${roomName}`); + await element(by.id(`rooms-list-view-item-${roomName}`)).tap(); + await waitFor(element(by.id('room-view'))) + .toBeVisible() + .withTimeout(5000); } async function waitForToast() { @@ -30,21 +35,31 @@ async function waitForToast() { } async function navigateSecurityPrivacy() { - await waitFor(element(by.id('rooms-list-view'))).toBeVisible().withTimeout(2000); + await waitFor(element(by.id('rooms-list-view'))) + .toBeVisible() + .withTimeout(2000); await element(by.id('rooms-list-view-sidebar')).tap(); - await waitFor(element(by.id('sidebar-view'))).toBeVisible().withTimeout(2000); - await waitFor(element(by.id('sidebar-settings'))).toBeVisible().withTimeout(2000); + await waitFor(element(by.id('sidebar-view'))) + .toBeVisible() + .withTimeout(2000); + await waitFor(element(by.id('sidebar-settings'))) + .toBeVisible() + .withTimeout(2000); await element(by.id('sidebar-settings')).tap(); - await waitFor(element(by.id('settings-view'))).toBeVisible().withTimeout(2000); + await waitFor(element(by.id('settings-view'))) + .toBeVisible() + .withTimeout(2000); await element(by.id('settings-view-security-privacy')).tap(); - await waitFor(element(by.id('security-privacy-view'))).toBeVisible().withTimeout(2000); + await waitFor(element(by.id('security-privacy-view'))) + .toBeVisible() + .withTimeout(2000); } describe('E2E Encryption', () => { - const room = `encrypted${ data.random }`; + const room = `encrypted${data.random}`; const newPassword = 'abc'; - before(async() => { + before(async () => { await device.launchApp({ permissions: { notifications: 'YES' }, delete: true }); await navigateToLogin(); await login(testuser.username, testuser.password); @@ -52,47 +67,67 @@ describe('E2E Encryption', () => { describe('Banner', () => { describe('Render', () => { - it('should have encryption badge', async() => { + it('should have encryption badge', async () => { await checkBanner(); }); }); describe('Usage', () => { - it('should tap encryption badge and open save password modal', async() => { + it('should tap encryption badge and open save password modal', async () => { await element(by.id('listheader-encryption')).tap(); - await waitFor(element(by.id('e2e-save-password-view'))).toBeVisible().withTimeout(2000); + await waitFor(element(by.id('e2e-save-password-view'))) + .toBeVisible() + .withTimeout(2000); }); - it('should tap "How it works" and navigate', async() => { + it('should tap "How it works" and navigate', async () => { await element(by.id('e2e-save-password-view-how-it-works').and(by.label('How It Works'))).tap(); - await waitFor(element(by.id('e2e-how-it-works-view'))).toBeVisible().withTimeout(2000); + await waitFor(element(by.id('e2e-how-it-works-view'))) + .toBeVisible() + .withTimeout(2000); await tapBack(); }); - it('should tap "Save my password" and close modal', async() => { + it('should tap "Save my password" and close modal', async () => { await element(by.id('e2e-save-password-view-saved-password').and(by.label('I Saved My E2E Password'))).tap(); - await waitFor(element(by.id('rooms-list-view'))).toBeVisible().withTimeout(2000); + await waitFor(element(by.id('rooms-list-view'))) + .toBeVisible() + .withTimeout(2000); }); - it('should create encrypted room', async() => { + it('should create encrypted room', async () => { await element(by.id('rooms-list-view-create-channel')).tap(); - await waitFor(element(by.id('new-message-view'))).toBeVisible().withTimeout(2000); + await waitFor(element(by.id('new-message-view'))) + .toBeVisible() + .withTimeout(2000); await element(by.id('new-message-view-create-channel')).tap(); - await waitFor(element(by.id('select-users-view'))).toBeVisible().withTimeout(2000); + await waitFor(element(by.id('select-users-view'))) + .toBeVisible() + .withTimeout(2000); await element(by.id('select-users-view-search')).replaceText(otheruser.username); - await waitFor(element(by.id(`select-users-view-item-${ otheruser.username }`))).toBeVisible().withTimeout(60000); - await element(by.id(`select-users-view-item-${ otheruser.username }`)).tap(); - await waitFor(element(by.id(`selected-user-${ otheruser.username }`))).toBeVisible().withTimeout(5000); + await waitFor(element(by.id(`select-users-view-item-${otheruser.username}`))) + .toBeVisible() + .withTimeout(60000); + await element(by.id(`select-users-view-item-${otheruser.username}`)).tap(); + await waitFor(element(by.id(`selected-user-${otheruser.username}`))) + .toBeVisible() + .withTimeout(5000); await element(by.id('selected-users-view-submit')).tap(); - await waitFor(element(by.id('create-channel-view'))).toExist().withTimeout(5000); + await waitFor(element(by.id('create-channel-view'))) + .toExist() + .withTimeout(5000); await element(by.id('create-channel-name')).replaceText(room); await element(by.id('create-channel-encrypted')).longPress(); await element(by.id('create-channel-submit')).tap(); - await waitFor(element(by.id('room-view'))).toBeVisible().withTimeout(60000); - await waitFor(element(by.id(`room-view-title-${ room }`))).toBeVisible().withTimeout(60000); + await waitFor(element(by.id('room-view'))) + .toBeVisible() + .withTimeout(60000); + await waitFor(element(by.id(`room-view-title-${room}`))) + .toBeVisible() + .withTimeout(60000); }); - it('should send message and be able to read it', async() => { + it('should send message and be able to read it', async () => { await mockMessage('message'); await tapBack(); }); @@ -100,18 +135,28 @@ describe('E2E Encryption', () => { }); describe('Security and Privacy', () => { - it('should navigate to security privacy', async() => { - await waitFor(element(by.id('rooms-list-view'))).toBeVisible().withTimeout(2000); + it('should navigate to security privacy', async () => { + await waitFor(element(by.id('rooms-list-view'))) + .toBeVisible() + .withTimeout(2000); await element(by.id('rooms-list-view-sidebar')).tap(); - await waitFor(element(by.id('sidebar-view'))).toBeVisible().withTimeout(2000); - await waitFor(element(by.id('sidebar-settings'))).toBeVisible().withTimeout(2000); + await waitFor(element(by.id('sidebar-view'))) + .toBeVisible() + .withTimeout(2000); + await waitFor(element(by.id('sidebar-settings'))) + .toBeVisible() + .withTimeout(2000); await element(by.id('sidebar-settings')).tap(); - await waitFor(element(by.id('settings-view'))).toBeVisible().withTimeout(2000); + await waitFor(element(by.id('settings-view'))) + .toBeVisible() + .withTimeout(2000); await element(by.id('settings-view-security-privacy')).tap(); - await waitFor(element(by.id('security-privacy-view'))).toBeVisible().withTimeout(2000); + await waitFor(element(by.id('security-privacy-view'))) + .toBeVisible() + .withTimeout(2000); }); - it('render', async() => { + it('render', async () => { await expect(element(by.id('security-privacy-view-e2e-encryption'))).toExist(); await expect(element(by.id('security-privacy-view-screen-lock'))).toExist(); await expect(element(by.id('security-privacy-view-analytics-events'))).toExist(); @@ -120,14 +165,18 @@ describe('E2E Encryption', () => { }); describe('E2E Encryption Security', () => { - it('should navigate to e2e encryption security', async() => { + it('should navigate to e2e encryption security', async () => { await element(by.id('security-privacy-view-e2e-encryption')).tap(); - await waitFor(element(by.id('e2e-encryption-security-view'))).toBeVisible().withTimeout(2000); + await waitFor(element(by.id('e2e-encryption-security-view'))) + .toBeVisible() + .withTimeout(2000); }); describe('Render', () => { - it('should have items', async() => { - await waitFor(element(by.id('e2e-encryption-security-view'))).toBeVisible().withTimeout(2000); + it('should have items', async () => { + await waitFor(element(by.id('e2e-encryption-security-view'))) + .toBeVisible() + .withTimeout(2000); await expect(element(by.id('e2e-encryption-security-view-password'))).toExist(); await expect(element(by.id('e2e-encryption-security-view-change-password').and(by.label('Save Changes')))).toExist(); await expect(element(by.id('e2e-encryption-security-view-reset-key').and(by.label('Reset E2E Key')))).toExist(); @@ -135,95 +184,145 @@ describe('E2E Encryption', () => { }); describe('Change password', () => { - it('should change password', async() => { + it('should change password', async () => { await element(by.id('e2e-encryption-security-view-password')).typeText(newPassword); await element(by.id('e2e-encryption-security-view-change-password')).tap(); - await waitFor(element(by.text('Are you sure?'))).toExist().withTimeout(2000); - await expect(element(by.text('Make sure you\'ve saved it carefully somewhere else.'))).toExist(); + await waitFor(element(by.text('Are you sure?'))) + .toExist() + .withTimeout(2000); + await expect(element(by.text("Make sure you've saved it carefully somewhere else."))).toExist(); await element(by.label('Yes, change it').and(by.type('_UIAlertControllerActionView'))).tap(); await waitForToast(); }); - it('should navigate to the room and messages should remain decrypted', async() => { - await waitFor(element(by.id('e2e-encryption-security-view'))).toBeVisible().withTimeout(2000); + it('should navigate to the room and messages should remain decrypted', async () => { + await waitFor(element(by.id('e2e-encryption-security-view'))) + .toBeVisible() + .withTimeout(2000); await tapBack(); - await waitFor(element(by.id('security-privacy-view'))).toBeVisible().withTimeout(2000); + await waitFor(element(by.id('security-privacy-view'))) + .toBeVisible() + .withTimeout(2000); await tapBack(); - await waitFor(element(by.id('settings-view'))).toBeVisible().withTimeout(2000); + await waitFor(element(by.id('settings-view'))) + .toBeVisible() + .withTimeout(2000); await element(by.id('settings-view-drawer')).tap(); - await waitFor(element(by.id('sidebar-view'))).toBeVisible().withTimeout(2000); + await waitFor(element(by.id('sidebar-view'))) + .toBeVisible() + .withTimeout(2000); await element(by.id('sidebar-chats')).tap(); - await waitFor(element(by.id('rooms-list-view'))).toBeVisible().withTimeout(2000); + await waitFor(element(by.id('rooms-list-view'))) + .toBeVisible() + .withTimeout(2000); await navigateToRoom(room); - await waitFor(element(by.label(`${ data.random }message`)).atIndex(0)).toExist().withTimeout(2000); + await waitFor(element(by.label(`${data.random}message`)).atIndex(0)) + .toExist() + .withTimeout(2000); }); - it('should logout, login and messages should be encrypted', async() => { + it('should logout, login and messages should be encrypted', async () => { await tapBack(); - await waitFor(element(by.id('rooms-list-view'))).toBeVisible().withTimeout(2000); + await waitFor(element(by.id('rooms-list-view'))) + .toBeVisible() + .withTimeout(2000); await logout(); await navigateToLogin(); await login(testuser.username, testuser.password); await navigateToRoom(room); - await waitFor(element(by.label(`${ data.random }message`)).atIndex(0)).not.toExist().withTimeout(2000); + await waitFor(element(by.label(`${data.random}message`)).atIndex(0)) + .not.toExist() + .withTimeout(2000); await expect(element(by.label('Encrypted message')).atIndex(0)).toExist(); }); - it('should enter new e2e password and messages should be decrypted', async() => { + it('should enter new e2e password and messages should be decrypted', async () => { await tapBack(); - await waitFor(element(by.id('rooms-list-view'))).toBeVisible().withTimeout(2000); - await waitFor(element(by.id('listheader-encryption').withDescendant(by.label('Enter Your E2E Password')))).toBeVisible().withTimeout(2000); + await waitFor(element(by.id('rooms-list-view'))) + .toBeVisible() + .withTimeout(2000); + await waitFor(element(by.id('listheader-encryption').withDescendant(by.label('Enter Your E2E Password')))) + .toBeVisible() + .withTimeout(2000); await element(by.id('listheader-encryption').withDescendant(by.label('Enter Your E2E Password'))).tap(); - await waitFor(element(by.id('e2e-enter-your-password-view'))).toBeVisible().withTimeout(2000); + await waitFor(element(by.id('e2e-enter-your-password-view'))) + .toBeVisible() + .withTimeout(2000); await element(by.id('e2e-enter-your-password-view-password')).typeText(newPassword); await element(by.id('e2e-enter-your-password-view-confirm')).tap(); - await waitFor(element(by.id('listheader-encryption'))).not.toExist().withTimeout(10000); + await waitFor(element(by.id('listheader-encryption'))) + .not.toExist() + .withTimeout(10000); await navigateToRoom(room); - await waitFor(element(by.label(`${ data.random }message`)).atIndex(0)).toExist().withTimeout(2000); + await waitFor(element(by.label(`${data.random}message`)).atIndex(0)) + .toExist() + .withTimeout(2000); }); }); describe('Reset E2E key', () => { - it('should reset e2e key', async() => { + it('should reset e2e key', async () => { await tapBack(); - await waitFor(element(by.id('rooms-list-view'))).toBeVisible().withTimeout(2000); + await waitFor(element(by.id('rooms-list-view'))) + .toBeVisible() + .withTimeout(2000); await navigateSecurityPrivacy(); await element(by.id('security-privacy-view-e2e-encryption')).tap(); - await waitFor(element(by.id('e2e-encryption-security-view'))).toBeVisible().withTimeout(2000); + await waitFor(element(by.id('e2e-encryption-security-view'))) + .toBeVisible() + .withTimeout(2000); await element(by.id('e2e-encryption-security-view-reset-key').and(by.label('Reset E2E Key'))).tap(); - await waitFor(element(by.text('Are you sure?'))).toExist().withTimeout(2000); - await expect(element(by.text('You\'re going to be logged out.'))).toExist(); + await waitFor(element(by.text('Are you sure?'))) + .toExist() + .withTimeout(2000); + await expect(element(by.text("You're going to be logged out."))).toExist(); await element(by.label('Yes, reset it').and(by.type('UILabel'))).tap(); await sleep(2000); - await waitFor(element(by.id('workspace-view'))).toBeVisible().withTimeout(10000); - await waitFor(element(by.text('You\'ve been logged out by the server. Please log in again.'))).toExist().withTimeout(2000); + await waitFor(element(by.id('workspace-view'))) + .toBeVisible() + .withTimeout(10000); + await waitFor(element(by.text("You've been logged out by the server. Please log in again."))) + .toExist() + .withTimeout(2000); await element(by.label('OK').and(by.type('_UIAlertControllerActionView'))).tap(); await element(by.id('workspace-view-login')).tap(); - await waitFor(element(by.id('login-view'))).toBeVisible().withTimeout(2000); + await waitFor(element(by.id('login-view'))) + .toBeVisible() + .withTimeout(2000); await login(testuser.username, testuser.password); - await waitFor(element(by.id('listheader-encryption').withDescendant(by.label('Save Your Encryption Password')))).toBeVisible().withTimeout(2000); + await waitFor(element(by.id('listheader-encryption').withDescendant(by.label('Save Your Encryption Password')))) + .toBeVisible() + .withTimeout(2000); }); }); }); describe('Persist Banner', () => { - it('check save banner', async() => { + it('check save banner', async () => { await checkServer(data.server); await checkBanner(); }); - it('should add server and create new user', async() => { + it('should add server and create new user', async () => { await sleep(5000); await element(by.id('rooms-list-header-server-dropdown-button')).tap(); - await waitFor(element(by.id('rooms-list-header-server-dropdown'))).toBeVisible().withTimeout(5000); + await waitFor(element(by.id('rooms-list-header-server-dropdown'))) + .toBeVisible() + .withTimeout(5000); await element(by.id('rooms-list-header-server-add')).tap(); // TODO: refactor - await waitFor(element(by.id('new-server-view'))).toBeVisible().withTimeout(60000); - await element(by.id('new-server-view-input')).typeText(`${ data.alternateServer }\n`); - await waitFor(element(by.id('workspace-view'))).toBeVisible().withTimeout(60000); + await waitFor(element(by.id('new-server-view'))) + .toBeVisible() + .withTimeout(60000); + await element(by.id('new-server-view-input')).typeText(`${data.alternateServer}\n`); + await waitFor(element(by.id('workspace-view'))) + .toBeVisible() + .withTimeout(60000); await element(by.id('workspace-view-register')).tap(); - await waitFor(element(by.id('register-view'))).toBeVisible().withTimeout(2000); + await waitFor(element(by.id('register-view'))) + .toBeVisible() + .withTimeout(2000); // Register new user await element(by.id('register-view-name')).replaceText(data.registeringUser.username); @@ -231,26 +330,34 @@ describe('E2E Encryption', () => { await element(by.id('register-view-email')).replaceText(data.registeringUser.email); await element(by.id('register-view-password')).typeText(data.registeringUser.password); await element(by.id('register-view-submit')).tap(); - await waitFor(element(by.id('rooms-list-view'))).toBeVisible().withTimeout(60000); + await waitFor(element(by.id('rooms-list-view'))) + .toBeVisible() + .withTimeout(60000); await checkServer(data.alternateServer); }); - it('should change back', async() => { + it('should change back', async () => { await element(by.id('rooms-list-header-server-dropdown-button')).tap(); - await waitFor(element(by.id('rooms-list-header-server-dropdown'))).toBeVisible().withTimeout(5000); - await element(by.id(`rooms-list-header-server-${ data.server }`)).tap(); - await waitFor(element(by.id('rooms-list-view'))).toBeVisible().withTimeout(10000); + await waitFor(element(by.id('rooms-list-header-server-dropdown'))) + .toBeVisible() + .withTimeout(5000); + await element(by.id(`rooms-list-header-server-${data.server}`)).tap(); + await waitFor(element(by.id('rooms-list-view'))) + .toBeVisible() + .withTimeout(10000); await checkServer(data.server); await checkBanner(); }); - it('should reopen the app and have banner', async() => { + it('should reopen the app and have banner', async () => { await device.launchApp({ permissions: { notifications: 'YES' }, newInstance: true }); - await waitFor(element(by.id('rooms-list-view'))).toBeVisible().withTimeout(10000); + await waitFor(element(by.id('rooms-list-view'))) + .toBeVisible() + .withTimeout(10000); await checkBanner(); }); }); diff --git a/e2e/tests/assorted/02-broadcast.spec.js b/e2e/tests/assorted/02-broadcast.spec.js index 12350ee86..f0f2192b4 100644 --- a/e2e/tests/assorted/02-broadcast.spec.js +++ b/e2e/tests/assorted/02-broadcast.spec.js @@ -1,55 +1,77 @@ // const OTP = require('otp.js'); // const GA = OTP.googleAuthenticator; -const { - navigateToLogin, login, mockMessage, tapBack, searchRoom -} = require('../../helpers/app'); +const { navigateToLogin, login, mockMessage, tapBack, searchRoom } = require('../../helpers/app'); const data = require('../../data'); const testuser = data.users.regular; const otheruser = data.users.alternate; describe('Broadcast room', () => { - before(async() => { + before(async () => { await device.launchApp({ permissions: { notifications: 'YES' }, delete: true }); await navigateToLogin(); await login(testuser.username, testuser.password); }); - it('should create broadcast room', async() => { + it('should create broadcast room', async () => { await element(by.id('rooms-list-view-create-channel')).tap(); - await waitFor(element(by.id('new-message-view'))).toBeVisible().withTimeout(2000); + await waitFor(element(by.id('new-message-view'))) + .toBeVisible() + .withTimeout(2000); await element(by.id('new-message-view-create-channel')).tap(); - await waitFor(element(by.id('select-users-view'))).toBeVisible().withTimeout(2000); + await waitFor(element(by.id('select-users-view'))) + .toBeVisible() + .withTimeout(2000); await element(by.id('select-users-view-search')).replaceText(otheruser.username); - await waitFor(element(by.id(`select-users-view-item-${ otheruser.username }`))).toBeVisible().withTimeout(60000); - await element(by.id(`select-users-view-item-${ otheruser.username }`)).tap(); - await waitFor(element(by.id(`selected-user-${ otheruser.username }`))).toBeVisible().withTimeout(5000); + await waitFor(element(by.id(`select-users-view-item-${otheruser.username}`))) + .toBeVisible() + .withTimeout(60000); + await element(by.id(`select-users-view-item-${otheruser.username}`)).tap(); + await waitFor(element(by.id(`selected-user-${otheruser.username}`))) + .toBeVisible() + .withTimeout(5000); await element(by.id('selected-users-view-submit')).tap(); - await waitFor(element(by.id('create-channel-view'))).toExist().withTimeout(5000); - await element(by.id('create-channel-name')).replaceText(`broadcast${ data.random }`); + await waitFor(element(by.id('create-channel-view'))) + .toExist() + .withTimeout(5000); + await element(by.id('create-channel-name')).replaceText(`broadcast${data.random}`); await element(by.id('create-channel-broadcast')).longPress(); // https://github.com/facebook/react-native/issues/28032 await element(by.id('create-channel-submit')).tap(); - await waitFor(element(by.id('room-view'))).toBeVisible().withTimeout(60000); - await waitFor(element(by.id(`room-view-title-broadcast${ data.random }`))).toBeVisible().withTimeout(60000); + await waitFor(element(by.id('room-view'))) + .toBeVisible() + .withTimeout(60000); + await waitFor(element(by.id(`room-view-title-broadcast${data.random}`))) + .toBeVisible() + .withTimeout(60000); await element(by.id('room-header')).tap(); - await waitFor(element(by.id('room-actions-view'))).toBeVisible().withTimeout(5000); + await waitFor(element(by.id('room-actions-view'))) + .toBeVisible() + .withTimeout(5000); await element(by.id('room-actions-info')).tap(); - await waitFor(element(by.id('room-info-view'))).toBeVisible().withTimeout(2000); + await waitFor(element(by.id('room-info-view'))) + .toBeVisible() + .withTimeout(2000); await expect(element(by.label('Broadcast Channel').withAncestor(by.id('room-info-view-broadcast')))).toBeVisible(); await tapBack(); - await waitFor(element(by.id('room-actions-view'))).toBeVisible().withTimeout(2000); + await waitFor(element(by.id('room-actions-view'))) + .toBeVisible() + .withTimeout(2000); await tapBack(); - await waitFor(element(by.id('room-view'))).toBeVisible().withTimeout(2000); + await waitFor(element(by.id('room-view'))) + .toBeVisible() + .withTimeout(2000); }); - it('should send message', async() => { - await waitFor(element(by.id('room-view'))).toBeVisible().withTimeout(5000); + it('should send message', async () => { + await waitFor(element(by.id('room-view'))) + .toBeVisible() + .withTimeout(5000); await mockMessage('message'); await tapBack(); }); - it('should login as user without write message authorization and enter room', async() => { + it('should login as user without write message authorization and enter room', async () => { await device.launchApp({ permissions: { notifications: 'YES' }, delete: true }); await navigateToLogin(); await login(otheruser.username, otheruser.password); @@ -60,34 +82,42 @@ describe('Broadcast room', () => { // await element(by.id('two-factor-input')).replaceText(code); // await element(by.id('two-factor-send')).tap(); - await searchRoom(`broadcast${ data.random }`); - await element(by.id(`rooms-list-view-item-broadcast${ data.random }`)).tap(); - await waitFor(element(by.id('room-view'))).toBeVisible().withTimeout(5000); - await waitFor(element(by.id(`room-view-title-broadcast${ data.random }`))).toBeVisible().withTimeout(60000); + await searchRoom(`broadcast${data.random}`); + await element(by.id(`rooms-list-view-item-broadcast${data.random}`)).tap(); + await waitFor(element(by.id('room-view'))) + .toBeVisible() + .withTimeout(5000); + await waitFor(element(by.id(`room-view-title-broadcast${data.random}`))) + .toBeVisible() + .withTimeout(60000); }); - it('should not have messagebox', async() => { + it('should not have messagebox', async () => { await expect(element(by.id('messagebox'))).toBeNotVisible(); }); - it('should be read only', async() => { + it('should be read only', async () => { await expect(element(by.label('This room is read only'))).toExist(); }); - it('should have the message created earlier', async() => { - await waitFor(element(by.label(`${ data.random }message`))).toExist().withTimeout(60000); + it('should have the message created earlier', async () => { + await waitFor(element(by.label(`${data.random}message`))) + .toExist() + .withTimeout(60000); }); - it('should have reply button', async() => { + it('should have reply button', async () => { await expect(element(by.id('message-broadcast-reply'))).toBeVisible(); }); - it('should tap on reply button and navigate to direct room', async() => { + it('should tap on reply button and navigate to direct room', async () => { await element(by.id('message-broadcast-reply')).tap(); - await waitFor(element(by.id(`room-view-title-${ testuser.username }`))).toBeVisible().withTimeout(5000); + await waitFor(element(by.id(`room-view-title-${testuser.username}`))) + .toBeVisible() + .withTimeout(5000); }); - it('should reply broadcasted message', async() => { + it('should reply broadcasted message', async () => { await mockMessage('broadcastreply'); }); }); diff --git a/e2e/tests/assorted/03-profile.spec.js b/e2e/tests/assorted/03-profile.spec.js index b17485a3f..56e2aa324 100644 --- a/e2e/tests/assorted/03-profile.spec.js +++ b/e2e/tests/assorted/03-profile.spec.js @@ -14,81 +14,99 @@ async function waitForToast() { } describe('Profile screen', () => { - before(async() => { + before(async () => { await device.launchApp({ permissions: { notifications: 'YES' }, delete: true }); await navigateToLogin(); await login(profileChangeUser.username, profileChangeUser.password); await element(by.id('rooms-list-view-sidebar')).tap(); - await waitFor(element(by.id('sidebar-view'))).toBeVisible().withTimeout(2000); - await waitFor(element(by.id('sidebar-profile'))).toBeVisible().withTimeout(2000); + await waitFor(element(by.id('sidebar-view'))) + .toBeVisible() + .withTimeout(2000); + await waitFor(element(by.id('sidebar-profile'))) + .toBeVisible() + .withTimeout(2000); await element(by.id('sidebar-profile')).tap(); - await waitFor(element(by.id('profile-view'))).toBeVisible().withTimeout(2000); + await waitFor(element(by.id('profile-view'))) + .toBeVisible() + .withTimeout(2000); }); describe('Render', () => { - it('should have profile view', async() => { + it('should have profile view', async () => { await expect(element(by.id('profile-view'))).toBeVisible(); }); - it('should have avatar', async() => { + it('should have avatar', async () => { await expect(element(by.id('profile-view-avatar')).atIndex(0)).toExist(); }); - it('should have name', async() => { + it('should have name', async () => { await expect(element(by.id('profile-view-name'))).toExist(); }); - it('should have username', async() => { + it('should have username', async () => { await expect(element(by.id('profile-view-username'))).toExist(); }); - it('should have email', async() => { + it('should have email', async () => { await expect(element(by.id('profile-view-email'))).toExist(); }); - it('should have new password', async() => { + it('should have new password', async () => { await expect(element(by.id('profile-view-new-password'))).toExist(); }); - it('should have avatar url', async() => { + it('should have avatar url', async () => { await expect(element(by.id('profile-view-avatar-url'))).toExist(); }); - it('should have reset avatar button', async() => { - await waitFor(element(by.id('profile-view-reset-avatar'))).toExist().whileElement(by.id('profile-view-list')).scroll(scrollDown, 'down'); + it('should have reset avatar button', async () => { + await waitFor(element(by.id('profile-view-reset-avatar'))) + .toExist() + .whileElement(by.id('profile-view-list')) + .scroll(scrollDown, 'down'); }); - it('should have upload avatar button', async() => { - await waitFor(element(by.id('profile-view-upload-avatar'))).toExist().whileElement(by.id('profile-view-list')).scroll(scrollDown, 'down'); + it('should have upload avatar button', async () => { + await waitFor(element(by.id('profile-view-upload-avatar'))) + .toExist() + .whileElement(by.id('profile-view-list')) + .scroll(scrollDown, 'down'); }); - it('should have avatar url button', async() => { - await waitFor(element(by.id('profile-view-avatar-url-button'))).toExist().whileElement(by.id('profile-view-list')).scroll(scrollDown, 'down'); + it('should have avatar url button', async () => { + await waitFor(element(by.id('profile-view-avatar-url-button'))) + .toExist() + .whileElement(by.id('profile-view-list')) + .scroll(scrollDown, 'down'); }); - it('should have submit button', async() => { - await waitFor(element(by.id('profile-view-submit'))).toExist().whileElement(by.id('profile-view-list')).scroll(scrollDown, 'down'); + it('should have submit button', async () => { + await waitFor(element(by.id('profile-view-submit'))) + .toExist() + .whileElement(by.id('profile-view-list')) + .scroll(scrollDown, 'down'); }); }); describe('Usage', () => { - it('should change name and username', async() => { - await element(by.id('profile-view-name')).replaceText(`${ profileChangeUser.username }new`); - await element(by.id('profile-view-username')).typeText(`${ profileChangeUser.username }new`); + it('should change name and username', async () => { + await element(by.id('profile-view-name')).replaceText(`${profileChangeUser.username}new`); + await element(by.id('profile-view-username')).typeText(`${profileChangeUser.username}new`); await element(by.type('UIScrollView')).atIndex(1).swipe('up'); await element(by.id('profile-view-submit')).tap(); await waitForToast(); }); - it('should change email and password', async() => { - await element(by.id('profile-view-email')).replaceText(`mobile+profileChangesNew${ data.random }@rocket.chat`); - await element(by.id('profile-view-new-password')).replaceText(`${ profileChangeUser.password }new`); + it('should change email and password', async () => { + await element(by.id('profile-view-email')).replaceText(`mobile+profileChangesNew${data.random}@rocket.chat`); + await element(by.id('profile-view-new-password')).replaceText(`${profileChangeUser.password}new`); await element(by.id('profile-view-submit')).tap(); - await element(by.type('_UIAlertControllerTextField')).typeText(`${ profileChangeUser.password }\n`); + await element(by.type('_UIAlertControllerTextField')).typeText(`${profileChangeUser.password}\n`); await waitForToast(); }); - it('should reset avatar', async() => { + it('should reset avatar', async () => { await element(by.type('UIScrollView')).atIndex(1).swipe('up'); await element(by.id('profile-view-reset-avatar')).tap(); await waitForToast(); diff --git a/e2e/tests/assorted/04-setting.spec.js b/e2e/tests/assorted/04-setting.spec.js index edfb3fd40..a242aa8b6 100644 --- a/e2e/tests/assorted/04-setting.spec.js +++ b/e2e/tests/assorted/04-setting.spec.js @@ -1,72 +1,87 @@ const { navigateToLogin, login } = require('../../helpers/app'); - const data = require('../../data'); const testuser = data.users.regular; describe('Settings screen', () => { - before(async() => { + before(async () => { await device.launchApp({ permissions: { notifications: 'YES' }, delete: true }); await navigateToLogin(); await login(testuser.username, testuser.password); - await waitFor(element(by.id('rooms-list-view'))).toBeVisible().withTimeout(10000); + await waitFor(element(by.id('rooms-list-view'))) + .toBeVisible() + .withTimeout(10000); await element(by.id('rooms-list-view-sidebar')).tap(); - await waitFor(element(by.id('sidebar-view'))).toBeVisible().withTimeout(2000); - await waitFor(element(by.id('sidebar-settings'))).toBeVisible().withTimeout(2000); + await waitFor(element(by.id('sidebar-view'))) + .toBeVisible() + .withTimeout(2000); + await waitFor(element(by.id('sidebar-settings'))) + .toBeVisible() + .withTimeout(2000); await element(by.id('sidebar-settings')).tap(); - await waitFor(element(by.id('settings-view'))).toBeVisible().withTimeout(2000); + await waitFor(element(by.id('settings-view'))) + .toBeVisible() + .withTimeout(2000); }); describe('Render', () => { - it('should have settings view', async() => { + it('should have settings view', async () => { await expect(element(by.id('settings-view'))).toBeVisible(); }); - it('should have language', async() => { + it('should have language', async () => { await expect(element(by.id('settings-view-language'))).toExist(); }); - it('should have review app', async() => { + it('should have review app', async () => { await expect(element(by.id('settings-view-review-app'))).toExist(); }); - it('should have share app', async() => { + it('should have share app', async () => { await expect(element(by.id('settings-view-share-app'))).toExist(); }); - it('should have default browser', async() => { + it('should have default browser', async () => { await expect(element(by.id('settings-view-default-browser'))).toExist(); }); - it('should have theme', async() => { + it('should have theme', async () => { await expect(element(by.id('settings-view-theme'))).toExist(); }); - it('should have security and privacy', async() => { + it('should have security and privacy', async () => { await expect(element(by.id('settings-view-security-privacy'))).toExist(); }); - it('should have licence', async() => { + it('should have licence', async () => { await expect(element(by.id('settings-view-license'))).toExist(); }); - it('should have version no', async() => { + it('should have version no', async () => { await expect(element(by.id('settings-view-version'))).toExist(); }); - it('should have server version', async() => { + it('should have server version', async () => { await expect(element(by.id('settings-view-server-version'))).toExist(); }); }); describe('Usage', () => { - it('should tap clear cache and navigate to roomslistview', async() => { - await waitFor(element(by.id('settings-view'))).toBeVisible().withTimeout(2000); + it('should tap clear cache and navigate to roomslistview', async () => { + await waitFor(element(by.id('settings-view'))) + .toBeVisible() + .withTimeout(2000); await element(by.id('settings-view-clear-cache')).tap(); - await waitFor(element(by.text('This will clear all your offline data.'))).toExist().withTimeout(2000); + await waitFor(element(by.text('This will clear all your offline data.'))) + .toExist() + .withTimeout(2000); await element(by.label('Clear').and(by.type('_UIAlertControllerActionView'))).tap(); - await waitFor(element(by.id('rooms-list-view'))).toBeVisible().withTimeout(5000); - await waitFor(element(by.id(`rooms-list-view-item-${ data.groups.private.name }`))).toExist().withTimeout(10000); + await waitFor(element(by.id('rooms-list-view'))) + .toBeVisible() + .withTimeout(5000); + await waitFor(element(by.id(`rooms-list-view-item-${data.groups.private.name}`))) + .toExist() + .withTimeout(10000); }); }); }); diff --git a/e2e/tests/assorted/05-joinpublicroom.spec.js b/e2e/tests/assorted/05-joinpublicroom.spec.js index ed92f297e..09ca3435c 100644 --- a/e2e/tests/assorted/05-joinpublicroom.spec.js +++ b/e2e/tests/assorted/05-joinpublicroom.spec.js @@ -1,24 +1,26 @@ const data = require('../../data'); -const { - navigateToLogin, login, mockMessage, tapBack, searchRoom -} = require('../../helpers/app'); +const { navigateToLogin, login, mockMessage, tapBack, searchRoom } = require('../../helpers/app'); const testuser = data.users.regular; const room = data.channels.detoxpublic.name; async function navigateToRoom() { await searchRoom(room); - await element(by.id(`rooms-list-view-item-${ room }`)).tap(); - await waitFor(element(by.id('room-view'))).toBeVisible().withTimeout(5000); + await element(by.id(`rooms-list-view-item-${room}`)).tap(); + await waitFor(element(by.id('room-view'))) + .toBeVisible() + .withTimeout(5000); } async function navigateToRoomActions() { await element(by.id('room-header')).tap(); - await waitFor(element(by.id('room-actions-view'))).toBeVisible().withTimeout(5000); + await waitFor(element(by.id('room-actions-view'))) + .toBeVisible() + .withTimeout(5000); } describe('Join public room', () => { - before(async() => { + before(async () => { await device.launchApp({ permissions: { notifications: 'YES' }, delete: true }); await navigateToLogin(); await login(testuser.username, testuser.password); @@ -26,7 +28,7 @@ describe('Join public room', () => { }); describe('Render', () => { - it('should have room screen', async() => { + it('should have room screen', async () => { await expect(element(by.id('room-view'))).toBeVisible(); }); @@ -36,40 +38,40 @@ describe('Join public room', () => { // Render - Header describe('Header', () => { - it('should have actions button ', async() => { + it('should have actions button ', async () => { await expect(element(by.id('room-header'))).toBeVisible(); }); }); // Render - Join describe('Join', () => { - it('should have join', async() => { + it('should have join', async () => { await expect(element(by.id('room-view-join'))).toBeVisible(); }); - it('should have join text', async() => { + it('should have join text', async () => { await expect(element(by.label('You are in preview mode'))).toBeVisible(); }); - it('should have join button', async() => { + it('should have join button', async () => { await expect(element(by.id('room-view-join-button'))).toBeVisible(); }); - it('should not have messagebox', async() => { + it('should not have messagebox', async () => { await expect(element(by.id('messagebox'))).toBeNotVisible(); }); }); describe('Room Actions', () => { - before(async() => { + before(async () => { await navigateToRoomActions(); }); - it('should have room actions screen', async() => { + it('should have room actions screen', async () => { await expect(element(by.id('room-actions-view'))).toBeVisible(); }); - it('should have info', async() => { + it('should have info', async () => { await expect(element(by.id('room-actions-info'))).toBeVisible(); }); @@ -81,61 +83,67 @@ describe('Join public room', () => { // await expect(element(by.id('room-actions-video'))).toBeVisible(); // }); - it('should have members', async() => { + it('should have members', async () => { await expect(element(by.id('room-actions-members'))).toBeVisible(); }); - it('should have files', async() => { + it('should have files', async () => { await expect(element(by.id('room-actions-files'))).toBeVisible(); }); - it('should have mentions', async() => { + it('should have mentions', async () => { await expect(element(by.id('room-actions-mentioned'))).toBeVisible(); }); - it('should have starred', async() => { + it('should have starred', async () => { await expect(element(by.id('room-actions-starred'))).toBeVisible(); }); - it('should have share', async() => { + it('should have share', async () => { await expect(element(by.id('room-actions-share'))).toBeVisible(); }); - it('should have pinned', async() => { + it('should have pinned', async () => { await expect(element(by.id('room-actions-pinned'))).toBeVisible(); }); - it('should not have notifications', async() => { + it('should not have notifications', async () => { await expect(element(by.id('room-actions-notifications'))).toBeNotVisible(); }); - it('should not have leave channel', async() => { + it('should not have leave channel', async () => { await expect(element(by.id('room-actions-leave-channel'))).toBeNotVisible(); }); - after(async() => { + after(async () => { await tapBack(); - await waitFor(element(by.id('room-view'))).toBeVisible().withTimeout(2000); + await waitFor(element(by.id('room-view'))) + .toBeVisible() + .withTimeout(2000); }); }); }); describe('Usage', () => { - it('should join room', async() => { + it('should join room', async () => { await element(by.id('room-view-join-button')).tap(); await tapBack(); - await element(by.id(`rooms-list-view-item-${ room }`)).tap(); - await waitFor(element(by.id('room-view'))).toBeVisible().withTimeout(5000); - await waitFor(element(by.id('messagebox'))).toBeVisible().withTimeout(60000); + await element(by.id(`rooms-list-view-item-${room}`)).tap(); + await waitFor(element(by.id('room-view'))) + .toBeVisible() + .withTimeout(5000); + await waitFor(element(by.id('messagebox'))) + .toBeVisible() + .withTimeout(60000); await expect(element(by.id('messagebox'))).toBeVisible(); await expect(element(by.id('room-view-join'))).toBeNotVisible(); }); - it('should send message', async() => { + it('should send message', async () => { await mockMessage('message'); }); - it('should have notifications and leave channel', async() => { + it('should have notifications and leave channel', async () => { await navigateToRoomActions(); await expect(element(by.id('room-actions-view'))).toBeVisible(); await expect(element(by.id('room-actions-info'))).toBeVisible(); @@ -152,13 +160,19 @@ describe('Join public room', () => { await expect(element(by.id('room-actions-leave-channel'))).toBeVisible(); }); - it('should leave room', async() => { + it('should leave room', async () => { await element(by.id('room-actions-leave-channel')).tap(); - await waitFor(element(by.text('Yes, leave it!'))).toBeVisible().withTimeout(5000); + await waitFor(element(by.text('Yes, leave it!'))) + .toBeVisible() + .withTimeout(5000); await expect(element(by.text('Yes, leave it!'))).toBeVisible(); await element(by.text('Yes, leave it!')).tap(); - await waitFor(element(by.id('rooms-list-view'))).toBeVisible().withTimeout(10000); - await waitFor(element(by.id(`rooms-list-view-item-${ room }`))).toBeNotVisible().withTimeout(60000); + await waitFor(element(by.id('rooms-list-view'))) + .toBeVisible() + .withTimeout(10000); + await waitFor(element(by.id(`rooms-list-view-item-${room}`))) + .toBeNotVisible() + .withTimeout(60000); }); }); }); diff --git a/e2e/tests/assorted/06-status.spec.js b/e2e/tests/assorted/06-status.spec.js index 7eeb246b1..710fb4a9d 100644 --- a/e2e/tests/assorted/06-status.spec.js +++ b/e2e/tests/assorted/06-status.spec.js @@ -1,5 +1,4 @@ const { navigateToLogin, login, sleep } = require('../../helpers/app'); - const data = require('../../data'); const testuser = data.users.regular; @@ -9,21 +8,27 @@ async function waitForToast() { } describe('Status screen', () => { - before(async() => { + before(async () => { await device.launchApp({ permissions: { notifications: 'YES' }, delete: true }); await navigateToLogin(); await login(testuser.username, testuser.password); await element(by.id('rooms-list-view-sidebar')).tap(); - await waitFor(element(by.id('sidebar-view'))).toBeVisible().withTimeout(2000); - await waitFor(element(by.id('sidebar-custom-status'))).toBeVisible().withTimeout(2000); + await waitFor(element(by.id('sidebar-view'))) + .toBeVisible() + .withTimeout(2000); + await waitFor(element(by.id('sidebar-custom-status'))) + .toBeVisible() + .withTimeout(2000); await element(by.id('sidebar-custom-status')).tap(); - await waitFor(element(by.id('status-view'))).toBeVisible().withTimeout(2000); + await waitFor(element(by.id('status-view'))) + .toBeVisible() + .withTimeout(2000); }); describe('Render', () => { - it('should have status input', async() => { + it('should have status input', async () => { await expect(element(by.id('status-view-input'))).toBeVisible(); await expect(element(by.id('status-view-online'))).toExist(); await expect(element(by.id('status-view-busy'))).toExist(); @@ -33,16 +38,20 @@ describe('Status screen', () => { }); describe('Usage', () => { - it('should change status', async() => { + it('should change status', async () => { await element(by.id('status-view-busy')).tap(); - await waitFor(element(by.id('status-view-current-busy'))).toExist().withTimeout(2000); + await waitFor(element(by.id('status-view-current-busy'))) + .toExist() + .withTimeout(2000); }); - it('should change status text', async() => { + it('should change status text', async () => { await element(by.id('status-view-input')).typeText('status-text-new'); await element(by.id('status-view-submit')).tap(); await waitForToast(); - await waitFor(element(by.label('status-text-new').withAncestor(by.id('sidebar-custom-status')))).toExist().withTimeout(2000); + await waitFor(element(by.label('status-text-new').withAncestor(by.id('sidebar-custom-status')))) + .toExist() + .withTimeout(2000); }); }); }); diff --git a/e2e/tests/assorted/07-changeserver.spec.js b/e2e/tests/assorted/07-changeserver.spec.js index 4ed0d6b58..c489e4738 100644 --- a/e2e/tests/assorted/07-changeserver.spec.js +++ b/e2e/tests/assorted/07-changeserver.spec.js @@ -1,38 +1,62 @@ const data = require('../../data'); const { navigateToLogin, login, checkServer } = require('../../helpers/app'); -const reopenAndCheckServer = async(server) => { +const reopenAndCheckServer = async server => { await device.launchApp({ permissions: { notifications: 'YES' } }); - await waitFor(element(by.id('rooms-list-view'))).toBeVisible().withTimeout(6000); + await waitFor(element(by.id('rooms-list-view'))) + .toBeVisible() + .withTimeout(10000); await checkServer(server); }; describe('Change server', () => { - before(async() => { + before(async () => { await device.launchApp({ permissions: { notifications: 'YES' }, delete: true }); await navigateToLogin(); await login(data.users.regular.username, data.users.regular.password); - await waitFor(element(by.id('rooms-list-view'))).toBeVisible().withTimeout(10000); + await waitFor(element(by.id('rooms-list-view'))) + .toBeVisible() + .withTimeout(10000); }); - it('should login to server, add new server, close the app, open the app and show previous logged server', async() => { + it('should open the dropdown button, have the server add button and create workspace button', async () => { await element(by.id('rooms-list-header-server-dropdown-button')).tap(); - await waitFor(element(by.id('rooms-list-header-server-dropdown'))).toBeVisible().withTimeout(5000); - await element(by.id('rooms-list-header-server-add')).tap(); + await waitFor(element(by.id('rooms-list-header-server-dropdown'))) + .toBeVisible() + .withTimeout(5000); + await waitFor(element(by.id('rooms-list-header-server-add'))) + .toBeVisible() + .withTimeout(5000); + await waitFor(element(by.id('rooms-list-header-create-workspace-button'))) + .toBeVisible() + .withTimeout(5000); + }); - await waitFor(element(by.id('new-server-view'))).toBeVisible().withTimeout(6000); - await element(by.id('new-server-view-input')).typeText(`${ data.alternateServer }\n`); - await waitFor(element(by.id('workspace-view'))).toBeVisible().withTimeout(10000); + it('should login to server, add new server, close the app, open the app and show previous logged server', async () => { + await element(by.id('rooms-list-header-server-add')).tap(); + await waitFor(element(by.id('new-server-view'))) + .toBeVisible() + .withTimeout(6000); + await element(by.id('new-server-view-input')).typeText(`${data.alternateServer}\n`); + await waitFor(element(by.id('workspace-view'))) + .toBeVisible() + .withTimeout(10000); await reopenAndCheckServer(data.server); }); - it('should add server and create new user', async() => { + it('should add server and create new user', async () => { await element(by.id('rooms-list-header-server-dropdown-button')).tap(); - await waitFor(element(by.id('rooms-list-header-server-dropdown'))).toBeVisible().withTimeout(5000); - await element(by.id(`rooms-list-header-server-${ data.alternateServer }`)).tap(); - await waitFor(element(by.id('workspace-view'))).toBeVisible().withTimeout(60000); + await waitFor(element(by.id('rooms-list-header-server-dropdown'))) + .toBeVisible() + .withTimeout(5000); + await element(by.id(`rooms-list-header-server-${data.alternateServer}`)).tap(); + await waitFor(element(by.id('workspace-view'))) + .toBeVisible() + .withTimeout(60000); await element(by.id('workspace-view-register')).tap(); - await waitFor(element(by.id('register-view'))).toBeVisible().withTimeout(2000); + await waitFor(element(by.id('register-view'))) + .toBeVisible() + .withTimeout(2000); // Register new user await element(by.id('register-view-name')).replaceText(data.registeringUser2.username); @@ -40,26 +64,36 @@ describe('Change server', () => { await element(by.id('register-view-email')).replaceText(data.registeringUser2.email); await element(by.id('register-view-password')).replaceText(data.registeringUser2.password); await element(by.id('register-view-submit')).tap(); - await waitFor(element(by.id('rooms-list-view'))).toBeVisible().withTimeout(60000); + await waitFor(element(by.id('rooms-list-view'))) + .toBeVisible() + .withTimeout(60000); - await waitFor(element(by.id(`rooms-list-view-item-${ data.groups.private.name }`))).toBeNotVisible().withTimeout(60000); + await waitFor(element(by.id(`rooms-list-view-item-${data.groups.private.name}`))) + .toBeNotVisible() + .withTimeout(60000); await checkServer(data.alternateServer); }); - it('should reopen the app and show alternate server', async() => { + it('should reopen the app and show alternate server', async () => { await reopenAndCheckServer(data.alternateServer); }); - it('should change back to main server', async() => { + it('should change back to main server', async () => { await element(by.id('rooms-list-header-server-dropdown-button')).tap(); - await waitFor(element(by.id('rooms-list-header-server-dropdown'))).toBeVisible().withTimeout(5000); - await element(by.id(`rooms-list-header-server-${ data.server }`)).tap(); - await waitFor(element(by.id('rooms-list-view'))).toBeVisible().withTimeout(10000); - await waitFor(element(by.id(`rooms-list-view-item-${ data.groups.private.name }`))).toBeVisible().withTimeout(60000); + await waitFor(element(by.id('rooms-list-header-server-dropdown'))) + .toBeVisible() + .withTimeout(5000); + await element(by.id(`rooms-list-header-server-${data.server}`)).tap(); + await waitFor(element(by.id('rooms-list-view'))) + .toBeVisible() + .withTimeout(10000); + await waitFor(element(by.id(`rooms-list-view-item-${data.groups.private.name}`))) + .toBeVisible() + .withTimeout(60000); await checkServer(data.server); }); - it('should reopen the app and show main server', async() => { + it('should reopen the app and show main server', async () => { await reopenAndCheckServer(data.server); }); }); diff --git a/e2e/tests/assorted/08-joinprotectedroom.spec.js b/e2e/tests/assorted/08-joinprotectedroom.spec.js index f10d7bc5f..0960bf49a 100644 --- a/e2e/tests/assorted/08-joinprotectedroom.spec.js +++ b/e2e/tests/assorted/08-joinprotectedroom.spec.js @@ -1,7 +1,5 @@ const data = require('../../data'); -const { - navigateToLogin, login, mockMessage, searchRoom -} = require('../../helpers/app'); +const { navigateToLogin, login, mockMessage, searchRoom } = require('../../helpers/app'); const testuser = data.users.regular; const room = data.channels.detoxpublicprotected.name; @@ -9,17 +7,21 @@ const { joinCode } = data.channels.detoxpublicprotected; async function navigateToRoom() { await searchRoom(room); - await element(by.id(`rooms-list-view-item-${ room }`)).tap(); - await waitFor(element(by.id('room-view'))).toBeVisible().withTimeout(5000); + await element(by.id(`rooms-list-view-item-${room}`)).tap(); + await waitFor(element(by.id('room-view'))) + .toBeVisible() + .withTimeout(5000); } async function openJoinCode() { await element(by.id('room-view-join-button')).tap(); - await waitFor(element(by.id('join-code'))).toBeVisible().withTimeout(5000); + await waitFor(element(by.id('join-code'))) + .toBeVisible() + .withTimeout(5000); } describe('Join protected room', () => { - before(async() => { + before(async () => { await device.launchApp({ permissions: { notifications: 'YES' }, delete: true }); await navigateToLogin(); await login(testuser.username, testuser.password); @@ -27,26 +29,32 @@ describe('Join protected room', () => { }); describe('Usage', () => { - it('should tap join and ask for join code', async() => { + it('should tap join and ask for join code', async () => { await openJoinCode(); }); - it('should cancel join room', async() => { + it('should cancel join room', async () => { await element(by.id('join-code-cancel')).tap(); - await waitFor(element(by.id('join-code'))).toBeNotVisible().withTimeout(5000); + await waitFor(element(by.id('join-code'))) + .toBeNotVisible() + .withTimeout(5000); }); - it('should join room', async() => { + it('should join room', async () => { await openJoinCode(); await element(by.id('join-code-input')).replaceText(joinCode); await element(by.id('join-code-submit')).tap(); - await waitFor(element(by.id('join-code'))).toBeNotVisible().withTimeout(5000); - await waitFor(element(by.id('messagebox'))).toBeVisible().withTimeout(60000); + await waitFor(element(by.id('join-code'))) + .toBeNotVisible() + .withTimeout(5000); + await waitFor(element(by.id('messagebox'))) + .toBeVisible() + .withTimeout(60000); await expect(element(by.id('messagebox'))).toBeVisible(); await expect(element(by.id('room-view-join'))).toBeNotVisible(); }); - it('should send message', async() => { + it('should send message', async () => { await mockMessage('message'); }); }); diff --git a/e2e/tests/assorted/09-joinfromdirectory.spec.js b/e2e/tests/assorted/09-joinfromdirectory.spec.js index e04faabf4..e521e94ec 100644 --- a/e2e/tests/assorted/09-joinfromdirectory.spec.js +++ b/e2e/tests/assorted/09-joinfromdirectory.spec.js @@ -1,50 +1,60 @@ const data = require('../../data'); -const { - navigateToLogin, login, tapBack, sleep -} = require('../../helpers/app'); +const { navigateToLogin, login, tapBack, sleep } = require('../../helpers/app'); const testuser = data.users.regular; async function navigateToRoom(search) { await element(by.id('directory-view-search')).replaceText(search); - await waitFor(element(by.id(`directory-view-item-${ search }`))).toBeVisible().withTimeout(10000); + await waitFor(element(by.id(`directory-view-item-${search}`))) + .toBeVisible() + .withTimeout(10000); await sleep(300); // app takes some time to animate - await element(by.id(`directory-view-item-${ search }`)).tap(); - await waitFor(element(by.id('room-view'))).toExist().withTimeout(5000); - await waitFor(element(by.id(`room-view-title-${ search }`))).toExist().withTimeout(5000); + await element(by.id(`directory-view-item-${search}`)).tap(); + await waitFor(element(by.id('room-view'))) + .toExist() + .withTimeout(5000); + await waitFor(element(by.id(`room-view-title-${search}`))) + .toExist() + .withTimeout(5000); } describe('Join room from directory', () => { - before(async() => { + before(async () => { await device.launchApp({ permissions: { notifications: 'YES' }, delete: true }); await navigateToLogin(); await login(testuser.username, testuser.password); }); describe('Usage', () => { - it('should tap directory', async() => { + it('should tap directory', async () => { await element(by.id('rooms-list-view-directory')).tap(); - await waitFor(element(by.id('directory-view'))).toExist().withTimeout(2000); + await waitFor(element(by.id('directory-view'))) + .toExist() + .withTimeout(2000); }); - it('should search public channel and navigate', async() => { + it('should search public channel and navigate', async () => { await navigateToRoom(data.channels.detoxpublic.name); }); - it('should search user and navigate', async() => { + it('should search user and navigate', async () => { await tapBack(); await element(by.id('rooms-list-view-directory')).tap(); - await waitFor(element(by.id('directory-view'))).toExist().withTimeout(2000); + await waitFor(element(by.id('directory-view'))) + .toExist() + .withTimeout(2000); await element(by.id('directory-view-dropdown')).tap(); await element(by.label('Users')).tap(); await element(by.label('Search by')).tap(); await navigateToRoom(data.users.alternate.username); }); - it('should search user and navigate', async() => { + it('should search user and navigate', async () => { await tapBack(); await element(by.id('rooms-list-view-directory')).tap(); - await waitFor(element(by.id('directory-view'))).toExist().withTimeout(2000); + await waitFor(element(by.id('directory-view'))) + .toExist() + .withTimeout(2000); await element(by.id('directory-view-dropdown')).tap(); await element(by.label('Teams')).tap(); await element(by.label('Search by')).tap(); diff --git a/e2e/tests/assorted/10-deleteserver.spec.js b/e2e/tests/assorted/10-deleteserver.spec.js index d92d3c442..78366e6c5 100644 --- a/e2e/tests/assorted/10-deleteserver.spec.js +++ b/e2e/tests/assorted/10-deleteserver.spec.js @@ -1,30 +1,36 @@ const data = require('../../data'); -const { - sleep, navigateToLogin, login, checkServer -} = require('../../helpers/app'); +const { sleep, navigateToLogin, login, checkServer } = require('../../helpers/app'); describe('Delete server', () => { - before(async() => { + before(async () => { await device.launchApp({ permissions: { notifications: 'YES' }, delete: true }); await navigateToLogin(); await login(data.users.regular.username, data.users.regular.password); }); - it('should be logged in main server', async() => { + it('should be logged in main server', async () => { await checkServer(data.server); }); - it('should add server', async() => { + it('should add server', async () => { await sleep(5000); await element(by.id('rooms-list-header-server-dropdown-button')).tap(); - await waitFor(element(by.id('rooms-list-header-server-dropdown'))).toBeVisible().withTimeout(5000); + await waitFor(element(by.id('rooms-list-header-server-dropdown'))) + .toBeVisible() + .withTimeout(5000); await element(by.id('rooms-list-header-server-add')).tap(); - await waitFor(element(by.id('new-server-view'))).toBeVisible().withTimeout(10000); - await element(by.id('new-server-view-input')).typeText(`${ data.alternateServer }\n`); - await waitFor(element(by.id('workspace-view'))).toBeVisible().withTimeout(10000); + await waitFor(element(by.id('new-server-view'))) + .toBeVisible() + .withTimeout(10000); + await element(by.id('new-server-view-input')).typeText(`${data.alternateServer}\n`); + await waitFor(element(by.id('workspace-view'))) + .toBeVisible() + .withTimeout(10000); await element(by.id('workspace-view-register')).tap(); - await waitFor(element(by.id('register-view'))).toBeVisible().withTimeout(2000); + await waitFor(element(by.id('register-view'))) + .toBeVisible() + .withTimeout(2000); // Register new user await element(by.id('register-view-name')).replaceText(data.registeringUser3.username); @@ -32,18 +38,26 @@ describe('Delete server', () => { await element(by.id('register-view-email')).replaceText(data.registeringUser3.email); await element(by.id('register-view-password')).typeText(data.registeringUser3.password); await element(by.id('register-view-submit')).tap(); - await waitFor(element(by.id('rooms-list-view'))).toBeVisible().withTimeout(60000); + await waitFor(element(by.id('rooms-list-view'))) + .toBeVisible() + .withTimeout(60000); await checkServer(data.alternateServer); }); - it('should delete server', async() => { + it('should delete server', async () => { await element(by.id('rooms-list-header-server-dropdown-button')).tap(); - await waitFor(element(by.id('rooms-list-header-server-dropdown'))).toBeVisible().withTimeout(5000); - await element(by.id(`rooms-list-header-server-${ data.server }`)).longPress(1500); + await waitFor(element(by.id('rooms-list-header-server-dropdown'))) + .toBeVisible() + .withTimeout(5000); + await element(by.id(`rooms-list-header-server-${data.server}`)).longPress(1500); await element(by.label('Delete').and(by.type('_UIAlertControllerActionView'))).tap(); await element(by.id('rooms-list-header-server-dropdown-button')).tap(); - await waitFor(element(by.id('rooms-list-header-server-dropdown'))).toBeVisible().withTimeout(5000); - await waitFor(element(by.id(`rooms-list-header-server-${ data.server }`))).toBeNotVisible().withTimeout(10000); + await waitFor(element(by.id('rooms-list-header-server-dropdown'))) + .toBeVisible() + .withTimeout(5000); + await waitFor(element(by.id(`rooms-list-header-server-${data.server}`))) + .toBeNotVisible() + .withTimeout(10000); }); }); diff --git a/e2e/tests/assorted/11-deeplinking.spec.js b/e2e/tests/assorted/11-deeplinking.spec.js index 8a30be0a9..27255be3c 100644 --- a/e2e/tests/assorted/11-deeplinking.spec.js +++ b/e2e/tests/assorted/11-deeplinking.spec.js @@ -4,49 +4,61 @@ const { get, login } = require('../../helpers/data_setup'); const DEEPLINK_METHODS = { AUTH: 'auth', ROOM: 'room' }; const getDeepLink = (method, server, params) => { - const deeplink = `rocketchat://${ method }?host=${ server.replace(/^(http:\/\/|https:\/\/)/, '') }&${ params }`; - console.log(`Deeplinking to: ${ deeplink }`); + const deeplink = `rocketchat://${method}?host=${server.replace(/^(http:\/\/|https:\/\/)/, '')}&${params}`; + console.log(`Deeplinking to: ${deeplink}`); return deeplink; }; describe('Deep linking', () => { let userId; let authToken; - before(async() => { + before(async () => { const loginResult = await login(data.users.regular.username, data.users.regular.password); ({ userId, authToken } = loginResult); }); describe('Authentication', () => { - it('should run a deep link to an invalid account and raise error', async() => { + it('should run a deep link to an invalid account and raise error', async () => { await device.launchApp({ permissions: { notifications: 'YES' }, delete: true, url: getDeepLink(DEEPLINK_METHODS.AUTH, data.server, 'userId=123&token=abc'), sourceApp: 'com.apple.mobilesafari' }); - await waitFor(element(by.text('You\'ve been logged out by the server. Please log in again.'))).toExist().withTimeout(10000); // TODO: we need to improve this message + await waitFor(element(by.text("You've been logged out by the server. Please log in again."))) + .toExist() + .withTimeout(10000); // TODO: we need to improve this message }); - const authAndNavigate = async() => { + const authAndNavigate = async () => { await device.launchApp({ permissions: { notifications: 'YES' }, newInstance: true, - url: getDeepLink(DEEPLINK_METHODS.AUTH, data.server, `userId=${ userId }&token=${ authToken }&path=group/${ data.groups.private.name }`), + url: getDeepLink( + DEEPLINK_METHODS.AUTH, + data.server, + `userId=${userId}&token=${authToken}&path=group/${data.groups.private.name}` + ), sourceApp: 'com.apple.mobilesafari' }); - await waitFor(element(by.id(`room-view-title-${ data.groups.private.name }`))).toExist().withTimeout(30000); + await waitFor(element(by.id(`room-view-title-${data.groups.private.name}`))) + .toExist() + .withTimeout(30000); await tapBack(); - await waitFor(element(by.id('rooms-list-view'))).toBeVisible().withTimeout(10000); + await waitFor(element(by.id('rooms-list-view'))) + .toBeVisible() + .withTimeout(10000); await checkServer(data.server); - await waitFor(element(by.id(`rooms-list-view-item-${ data.groups.private.name }`))).toBeVisible().withTimeout(2000); + await waitFor(element(by.id(`rooms-list-view-item-${data.groups.private.name}`))) + .toBeVisible() + .withTimeout(2000); }; - it('should authenticate and navigate', async() => { + it('should authenticate and navigate', async () => { await authAndNavigate(); }); - it('should authenticate while logged in another server', async() => { + it('should authenticate while logged in another server', async () => { await device.launchApp({ permissions: { notifications: 'YES' }, delete: true }); await navigateToRegister(data.alternateServer); await element(by.id('register-view-name')).replaceText(data.registeringUser4.username); @@ -54,61 +66,75 @@ describe('Deep linking', () => { await element(by.id('register-view-email')).replaceText(data.registeringUser4.email); await element(by.id('register-view-password')).typeText(data.registeringUser4.password); await element(by.id('register-view-submit')).tap(); - await waitFor(element(by.id('rooms-list-view'))).toBeVisible().withTimeout(10000); + await waitFor(element(by.id('rooms-list-view'))) + .toBeVisible() + .withTimeout(10000); await authAndNavigate(); }); }); describe('Room', () => { describe('While logged in', () => { - it('should navigate to the room using path', async() => { + it('should navigate to the room using path', async () => { await device.launchApp({ permissions: { notifications: 'YES' }, newInstance: true, - url: getDeepLink(DEEPLINK_METHODS.ROOM, data.server, `path=group/${ data.groups.private.name }`), + url: getDeepLink(DEEPLINK_METHODS.ROOM, data.server, `path=group/${data.groups.private.name}`), sourceApp: 'com.apple.mobilesafari' }); - await waitFor(element(by.id(`room-view-title-${ data.groups.private.name }`))).toExist().withTimeout(10000); + await waitFor(element(by.id(`room-view-title-${data.groups.private.name}`))) + .toExist() + .withTimeout(10000); }); - it('should navigate to the room using rid', async() => { - const roomResult = await get(`groups.info?roomName=${ data.groups.private.name }`); + it('should navigate to the room using rid', async () => { + const roomResult = await get(`groups.info?roomName=${data.groups.private.name}`); await device.launchApp({ permissions: { notifications: 'YES' }, newInstance: true, - url: getDeepLink(DEEPLINK_METHODS.ROOM, data.server, `rid=${ roomResult.data.group._id }`), + url: getDeepLink(DEEPLINK_METHODS.ROOM, data.server, `rid=${roomResult.data.group._id}`), sourceApp: 'com.apple.mobilesafari' }); - await waitFor(element(by.id(`room-view-title-${ data.groups.private.name }`))).toExist().withTimeout(15000); + await waitFor(element(by.id(`room-view-title-${data.groups.private.name}`))) + .toExist() + .withTimeout(15000); await tapBack(); }); }); describe('Others', () => { - it('should change server', async() => { - await waitFor(element(by.id('rooms-list-view'))).toBeVisible().withTimeout(2000); + it('should change server', async () => { + await waitFor(element(by.id('rooms-list-view'))) + .toBeVisible() + .withTimeout(2000); await element(by.id('rooms-list-header-server-dropdown-button')).tap(); - await waitFor(element(by.id('rooms-list-header-server-dropdown'))).toBeVisible().withTimeout(5000); - await element(by.id(`rooms-list-header-server-${ data.alternateServer }`)).tap(); + await waitFor(element(by.id('rooms-list-header-server-dropdown'))) + .toBeVisible() + .withTimeout(5000); + await element(by.id(`rooms-list-header-server-${data.alternateServer}`)).tap(); await checkServer(data.alternateServer); await device.launchApp({ permissions: { notifications: 'YES' }, newInstance: true, - url: getDeepLink(DEEPLINK_METHODS.ROOM, data.server, `path=group/${ data.groups.private.name }`), + url: getDeepLink(DEEPLINK_METHODS.ROOM, data.server, `path=group/${data.groups.private.name}`), sourceApp: 'com.apple.mobilesafari' }); - await waitFor(element(by.id(`room-view-title-${ data.groups.private.name }`))).toExist().withTimeout(10000); + await waitFor(element(by.id(`room-view-title-${data.groups.private.name}`))) + .toExist() + .withTimeout(10000); }); - it('should add a not existing server and fallback to the previous one', async() => { + it('should add a not existing server and fallback to the previous one', async () => { await device.launchApp({ permissions: { notifications: 'YES' }, newInstance: true, url: getDeepLink(DEEPLINK_METHODS.ROOM, 'https://google.com'), sourceApp: 'com.apple.mobilesafari' }); - await waitFor(element(by.id('rooms-list-view'))).toBeVisible().withTimeout(10000); + await waitFor(element(by.id('rooms-list-view'))) + .toBeVisible() + .withTimeout(10000); await checkServer(data.server); }); }); diff --git a/e2e/tests/assorted/12-i18n.spec.js b/e2e/tests/assorted/12-i18n.spec.js index 1c4a6a9d5..b2a545bbb 100644 --- a/e2e/tests/assorted/12-i18n.spec.js +++ b/e2e/tests/assorted/12-i18n.spec.js @@ -1,25 +1,34 @@ const { navigateToLogin, login, sleep } = require('../../helpers/app'); const { post } = require('../../helpers/data_setup'); - const data = require('../../data'); const testuser = data.users.regular; const defaultLaunchArgs = { permissions: { notifications: 'YES' } }; -const navToLanguage = async() => { - await waitFor(element(by.id('rooms-list-view'))).toBeVisible().withTimeout(10000); +const navToLanguage = async () => { + await waitFor(element(by.id('rooms-list-view'))) + .toBeVisible() + .withTimeout(10000); await element(by.id('rooms-list-view-sidebar')).tap(); - await waitFor(element(by.id('sidebar-view'))).toBeVisible().withTimeout(2000); - await waitFor(element(by.id('sidebar-settings'))).toBeVisible().withTimeout(2000); + await waitFor(element(by.id('sidebar-view'))) + .toBeVisible() + .withTimeout(2000); + await waitFor(element(by.id('sidebar-settings'))) + .toBeVisible() + .withTimeout(2000); await element(by.id('sidebar-settings')).tap(); - await waitFor(element(by.id('settings-view'))).toBeVisible().withTimeout(2000); + await waitFor(element(by.id('settings-view'))) + .toBeVisible() + .withTimeout(2000); await element(by.id('settings-view-language')).tap(); - await waitFor(element(by.id('language-view'))).toBeVisible().withTimeout(10000); + await waitFor(element(by.id('language-view'))) + .toBeVisible() + .withTimeout(10000); }; describe('i18n', () => { describe('OS language', () => { - it('OS set to \'en\' and proper translate to \'en\'', async() => { + it("OS set to 'en' and proper translate to 'en'", async () => { await device.launchApp({ ...defaultLaunchArgs, languageAndLocale: { @@ -28,12 +37,13 @@ describe('i18n', () => { }, delete: true }); - await waitFor(element(by.id('onboarding-view'))).toBeVisible().withTimeout(20000); - await expect(element(by.id('join-workspace').and(by.label('Join a workspace')))).toBeVisible(); - await expect(element(by.id('create-workspace-button').and(by.label('Create a new workspace')))).toBeVisible(); + await waitFor(element(by.id('new-server-view'))) + .toBeVisible() + .withTimeout(20000); + await expect(element(by.id('new-server-view-open').and(by.label('Join our open workspace')))).toBeVisible(); }); - it('OS set to unavailable language and fallback to \'en\'', async() => { + it("OS set to unavailable language and fallback to 'en'", async () => { await device.launchApp({ ...defaultLaunchArgs, languageAndLocale: { @@ -41,9 +51,10 @@ describe('i18n', () => { locale: 'es-MX' } }); - await waitFor(element(by.id('onboarding-view'))).toBeVisible().withTimeout(20000); - await expect(element(by.id('join-workspace').and(by.label('Join a workspace')))).toBeVisible(); - await expect(element(by.id('create-workspace-button').and(by.label('Create a new workspace')))).toBeVisible(); + await waitFor(element(by.id('new-server-view'))) + .toBeVisible() + .withTimeout(20000); + await expect(element(by.id('new-server-view-open').and(by.label('Join our open workspace')))).toBeVisible(); }); /** @@ -58,49 +69,58 @@ describe('i18n', () => { // locale: "nl" // } // }); - // await waitFor(element(by.id('onboarding-view'))).toBeVisible().withTimeout(20000); - // await expect(element(by.id('join-workspace').and(by.label('Word lid van een werkruimte')))).toBeVisible(); - // await expect(element(by.id('create-workspace-button').and(by.label('Een nieuwe werkruimte aanmaken')))).toBeVisible(); // }); }); describe('Rocket.Chat language', () => { - before(async() => { + before(async () => { await device.launchApp(defaultLaunchArgs); await navigateToLogin(); await login(testuser.username, testuser.password); }); - it('should select \'en\'', async() => { + it("should select 'en'", async () => { await navToLanguage(); await element(by.id('language-view-en')).tap(); - await waitFor(element(by.id('rooms-list-view'))).toBeVisible().withTimeout(10000); + await waitFor(element(by.id('rooms-list-view'))) + .toBeVisible() + .withTimeout(10000); await element(by.id('rooms-list-view-sidebar')).tap(); - await waitFor(element(by.id('sidebar-view'))).toBeVisible().withTimeout(2000); + await waitFor(element(by.id('sidebar-view'))) + .toBeVisible() + .withTimeout(2000); await expect(element(by.id('sidebar-chats').withDescendant(by.label('Chats')))).toBeVisible(); await expect(element(by.id('sidebar-profile').withDescendant(by.label('Profile')))).toBeVisible(); await expect(element(by.id('sidebar-settings').withDescendant(by.label('Settings')))).toBeVisible(); await element(by.id('sidebar-close-drawer')).tap(); }); - it('should select \'nl\' and fallback to \'en\'', async() => { + it("should select 'nl' and fallback to 'en'", async () => { await navToLanguage(); await element(by.id('language-view-nl')).tap(); - await waitFor(element(by.id('rooms-list-view'))).toBeVisible().withTimeout(10000); + await waitFor(element(by.id('rooms-list-view'))) + .toBeVisible() + .withTimeout(10000); await element(by.id('rooms-list-view-sidebar')).tap(); - await waitFor(element(by.id('sidebar-view'))).toBeVisible().withTimeout(2000); + await waitFor(element(by.id('sidebar-view'))) + .toBeVisible() + .withTimeout(2000); await expect(element(by.id('sidebar-chats').withDescendant(by.label('Chats')))).toBeVisible(); // fallback to en await expect(element(by.id('sidebar-profile').withDescendant(by.label('Profiel')))).toBeVisible(); await expect(element(by.id('sidebar-settings').withDescendant(by.label('Instellingen')))).toBeVisible(); await element(by.id('sidebar-close-drawer')).tap(); }); - it('should set unsupported language and fallback to \'en\'', async() => { + it("should set unsupported language and fallback to 'en'", async () => { await post('users.setPreferences', { data: { language: 'eo' } }); // Set language to Esperanto await device.launchApp(defaultLaunchArgs); - await waitFor(element(by.id('rooms-list-view'))).toBeVisible().withTimeout(10000); + await waitFor(element(by.id('rooms-list-view'))) + .toBeVisible() + .withTimeout(10000); await element(by.id('rooms-list-view-sidebar')).tap(); - await waitFor(element(by.id('sidebar-view'))).toBeVisible().withTimeout(2000); + await waitFor(element(by.id('sidebar-view'))) + .toBeVisible() + .withTimeout(2000); // give the app some time to apply new language await sleep(3000); await expect(element(by.id('sidebar-chats').withDescendant(by.label('Chats')))).toBeVisible(); diff --git a/e2e/tests/init.js b/e2e/tests/init.js index b1e08a2bd..5bb81a8be 100644 --- a/e2e/tests/init.js +++ b/e2e/tests/init.js @@ -1,23 +1,24 @@ const detox = require('detox'); const adapter = require('detox/runners/mocha/adapter'); + const config = require('../../package.json').detox; const { setup } = require('../helpers/data_setup'); -before(async() => { +before(async () => { await Promise.all([setup(), detox.init(config, { launchApp: false })]); // await dataSetup() // await detox.init(config, { launchApp: false }); // await device.launchApp({ permissions: { notifications: 'YES' } }); }); -beforeEach(async function() { +beforeEach(async function () { await adapter.beforeEach(this); }); -afterEach(async function() { +afterEach(async function () { await adapter.afterEach(this); }); -after(async() => { +after(async () => { await detox.cleanup(); }); diff --git a/e2e/tests/onboarding/01-onboarding.spec.js b/e2e/tests/onboarding/01-onboarding.spec.js index a8cad8991..6edb8b14b 100644 --- a/e2e/tests/onboarding/01-onboarding.spec.js +++ b/e2e/tests/onboarding/01-onboarding.spec.js @@ -1,22 +1,20 @@ const data = require('../../data'); describe('Onboarding', () => { - before(async() => { + before(async () => { await device.launchApp({ permissions: { notifications: 'YES' }, delete: true }); - await waitFor(element(by.id('onboarding-view'))).toBeVisible().withTimeout(20000); + await waitFor(element(by.id('new-server-view'))) + .toBeVisible() + .withTimeout(20000); }); describe('Render', () => { - it('should have onboarding screen', async() => { - await expect(element(by.id('onboarding-view'))).toBeVisible(); + it('should have onboarding screen', async () => { + await expect(element(by.id('new-server-view'))).toBeVisible(); }); - it('should have "Join a workspace"', async() => { - await expect(element(by.id('join-workspace'))).toBeVisible(); - }); - - it('should have "Create a new workspace"', async() => { - await expect(element(by.id('create-workspace-button'))).toBeVisible(); + it('should have "Join our open workspace"', async () => { + await expect(element(by.id('new-server-view-open'))).toBeVisible(); }); }); @@ -25,30 +23,31 @@ describe('Onboarding', () => { // // webviews are not supported by detox: https://github.com/wix/detox/issues/136#issuecomment-306591554 // }); - it('should navigate to join a workspace', async() => { - await element(by.id('join-workspace')).tap(); - await waitFor(element(by.id('new-server-view'))).toBeVisible().withTimeout(60000); - }); - - it('should enter an invalid server and get error', async() => { + it('should enter an invalid server and get error', async () => { await element(by.id('new-server-view-input')).typeText('invalidtest\n'); const errorText = 'Oops!'; - await waitFor(element(by.text(errorText))).toBeVisible().withTimeout(60000); + await waitFor(element(by.text(errorText))) + .toBeVisible() + .withTimeout(60000); await element(by.text('OK')).tap(); }); - it('should tap on "Join our open workspace" and navigate', async() => { + it('should tap on "Join our open workspace" and navigate', async () => { await element(by.id('new-server-view-open')).tap(); - await waitFor(element(by.id('workspace-view'))).toBeVisible().withTimeout(60000); + await waitFor(element(by.id('workspace-view'))) + .toBeVisible() + .withTimeout(60000); }); - it('should enter a valid server without login services and navigate to login', async() => { + it('should enter a valid server without login services and navigate to login', async () => { await device.launchApp({ newInstance: true }); - await waitFor(element(by.id('onboarding-view'))).toBeVisible().withTimeout(2000); - await element(by.id('join-workspace')).tap(); - await waitFor(element(by.id('new-server-view'))).toBeVisible().withTimeout(60000); - await element(by.id('new-server-view-input')).typeText(`${ data.server }\n`); - await waitFor(element(by.id('workspace-view'))).toBeVisible().withTimeout(60000); + await waitFor(element(by.id('new-server-view'))) + .toBeVisible() + .withTimeout(5000); + await element(by.id('new-server-view-input')).typeText(`${data.server}\n`); + await waitFor(element(by.id('workspace-view'))) + .toBeVisible() + .withTimeout(60000); }); }); }); diff --git a/e2e/tests/onboarding/02-legal.spec.js b/e2e/tests/onboarding/02-legal.spec.js index 4ac96d985..c45744428 100644 --- a/e2e/tests/onboarding/02-legal.spec.js +++ b/e2e/tests/onboarding/02-legal.spec.js @@ -2,43 +2,51 @@ const { navigateToRegister, navigateToLogin } = require('../../helpers/app'); describe('Legal screen', () => { describe('From Login', () => { - before(async() => { + before(async () => { await device.launchApp({ permissions: { notifications: 'YES' }, delete: true }); await navigateToLogin(); }); - it('should have legal button on login', async() => { - await waitFor(element(by.id('login-view-more'))).toBeVisible().withTimeout(60000); + it('should have legal button on login', async () => { + await waitFor(element(by.id('login-view-more'))) + .toBeVisible() + .withTimeout(60000); }); - it('should navigate to legal from login', async() => { + it('should navigate to legal from login', async () => { await expect(element(by.id('login-view-more'))).toBeVisible(); await element(by.id('login-view-more')).tap(); - await waitFor(element(by.id('legal-view'))).toBeVisible().withTimeout(4000); + await waitFor(element(by.id('legal-view'))) + .toBeVisible() + .withTimeout(4000); }); }); describe('From Register', () => { - before(async() => { + before(async () => { await device.launchApp({ permissions: { notifications: 'YES' }, delete: true }); await navigateToRegister(); }); - it('should have legal button on register', async() => { - await waitFor(element(by.id('register-view-more'))).toBeVisible().withTimeout(60000); + it('should have legal button on register', async () => { + await waitFor(element(by.id('register-view-more'))) + .toBeVisible() + .withTimeout(60000); }); - it('should navigate to legal from register', async() => { + it('should navigate to legal from register', async () => { await expect(element(by.id('register-view-more'))).toBeVisible(); await element(by.id('register-view-more')).tap(); - await waitFor(element(by.id('legal-view'))).toBeVisible().withTimeout(4000); + await waitFor(element(by.id('legal-view'))) + .toBeVisible() + .withTimeout(4000); }); - it('should have terms of service button', async() => { + it('should have terms of service button', async () => { await expect(element(by.id('legal-terms-button'))).toBeVisible(); }); - it('should have privacy policy button', async() => { + it('should have privacy policy button', async () => { await expect(element(by.id('legal-privacy-button'))).toBeVisible(); }); diff --git a/e2e/tests/onboarding/03-forgotpassword.spec.js b/e2e/tests/onboarding/03-forgotpassword.spec.js index d53690d6f..691576ba9 100644 --- a/e2e/tests/onboarding/03-forgotpassword.spec.js +++ b/e2e/tests/onboarding/03-forgotpassword.spec.js @@ -2,34 +2,40 @@ const data = require('../../data'); const { navigateToLogin } = require('../../helpers/app'); describe('Forgot password screen', () => { - before(async() => { + before(async () => { await device.launchApp({ permissions: { notifications: 'YES' }, delete: true }); await navigateToLogin(); await element(by.id('login-view-forgot-password')).tap(); - await waitFor(element(by.id('forgot-password-view'))).toExist().withTimeout(2000); + await waitFor(element(by.id('forgot-password-view'))) + .toExist() + .withTimeout(2000); }); describe('Render', () => { - it('should have forgot password screen', async() => { + it('should have forgot password screen', async () => { await expect(element(by.id('forgot-password-view'))).toExist(); }); - it('should have email input', async() => { + it('should have email input', async () => { await expect(element(by.id('forgot-password-view-email'))).toBeVisible(); }); - it('should have submit button', async() => { + it('should have submit button', async () => { await expect(element(by.id('forgot-password-view-submit'))).toBeVisible(); }); }); describe('Usage', () => { - it('should reset password and navigate to login', async() => { + it('should reset password and navigate to login', async () => { await element(by.id('forgot-password-view-email')).replaceText(data.users.existing.email); await element(by.id('forgot-password-view-submit')).tap(); - await waitFor(element(by.text('OK'))).toExist().withTimeout(10000); + await waitFor(element(by.text('OK'))) + .toExist() + .withTimeout(10000); await element(by.text('OK')).tap(); - await waitFor(element(by.id('login-view'))).toBeVisible().withTimeout(60000); + await waitFor(element(by.id('login-view'))) + .toBeVisible() + .withTimeout(60000); }); }); }); diff --git a/e2e/tests/onboarding/04-createuser.spec.js b/e2e/tests/onboarding/04-createuser.spec.js index e88e9eab3..92e992ad3 100644 --- a/e2e/tests/onboarding/04-createuser.spec.js +++ b/e2e/tests/onboarding/04-createuser.spec.js @@ -2,33 +2,33 @@ const { navigateToRegister } = require('../../helpers/app'); const data = require('../../data'); describe('Create user screen', () => { - before(async() => { + before(async () => { await device.launchApp({ permissions: { notifications: 'YES' }, delete: true }); await navigateToRegister(); }); describe('Render', () => { - it('should have create user screen', async() => { + it('should have create user screen', async () => { await expect(element(by.id('register-view'))).toBeVisible(); }); - it('should have name input', async() => { + it('should have name input', async () => { await expect(element(by.id('register-view-name'))).toBeVisible(); }); - it('should have email input', async() => { + it('should have email input', async () => { await expect(element(by.id('register-view-email'))).toBeVisible(); }); - it('should have password input', async() => { + it('should have password input', async () => { await expect(element(by.id('register-view-password'))).toBeVisible(); }); - it('should have submit button', async() => { + it('should have submit button', async () => { await expect(element(by.id('register-view-submit'))).toBeVisible(); }); - it('should have legal button', async() => { + it('should have legal button', async () => { await expect(element(by.id('register-view-more'))).toBeVisible(); }); }); @@ -44,33 +44,39 @@ describe('Create user screen', () => { // await element(by.id('register-view-submit')).tap(); // }); - it('should submit email already taken and raise error', async() => { + it('should submit email already taken and raise error', async () => { await element(by.id('register-view-name')).replaceText(data.registeringUser.username); await element(by.id('register-view-username')).replaceText(data.registeringUser.username); await element(by.id('register-view-email')).replaceText(data.users.existing.email); await element(by.id('register-view-password')).replaceText(data.registeringUser.password); await element(by.id('register-view-submit')).tap(); - await waitFor(element(by.text('Email already exists. [403]')).atIndex(0)).toExist().withTimeout(10000); + await waitFor(element(by.text('Email already exists. [403]')).atIndex(0)) + .toExist() + .withTimeout(10000); await element(by.text('OK')).tap(); }); - it('should submit username already taken and raise error', async() => { + it('should submit username already taken and raise error', async () => { await element(by.id('register-view-name')).replaceText(data.registeringUser.username); await element(by.id('register-view-username')).replaceText(data.users.existing.username); await element(by.id('register-view-email')).replaceText(data.registeringUser.email); await element(by.id('register-view-password')).replaceText(data.registeringUser.password); await element(by.id('register-view-submit')).tap(); - await waitFor(element(by.text('Username is already in use')).atIndex(0)).toExist().withTimeout(10000); + await waitFor(element(by.text('Username is already in use')).atIndex(0)) + .toExist() + .withTimeout(10000); await element(by.text('OK')).tap(); }); - it('should register', async() => { + it('should register', async () => { await element(by.id('register-view-name')).replaceText(data.registeringUser.username); await element(by.id('register-view-username')).replaceText(data.registeringUser.username); await element(by.id('register-view-email')).replaceText(data.registeringUser.email); await element(by.id('register-view-password')).replaceText(data.registeringUser.password); await element(by.id('register-view-submit')).tap(); - await waitFor(element(by.id('rooms-list-view'))).toBeVisible().withTimeout(60000); + await waitFor(element(by.id('rooms-list-view'))) + .toBeVisible() + .withTimeout(60000); }); }); }); diff --git a/e2e/tests/onboarding/05-login.spec.js b/e2e/tests/onboarding/05-login.spec.js index 91cb91737..627261c17 100644 --- a/e2e/tests/onboarding/05-login.spec.js +++ b/e2e/tests/onboarding/05-login.spec.js @@ -2,66 +2,74 @@ const { navigateToLogin, tapBack } = require('../../helpers/app'); const data = require('../../data'); describe('Login screen', () => { - before(async() => { + before(async () => { await device.launchApp({ permissions: { notifications: 'YES' }, newInstance: true, delete: true }); await navigateToLogin(); }); describe('Render', () => { - it('should have login screen', async() => { + it('should have login screen', async () => { await expect(element(by.id('login-view'))).toBeVisible(); }); - it('should have email input', async() => { + it('should have email input', async () => { await expect(element(by.id('login-view-email'))).toBeVisible(); }); - it('should have password input', async() => { + it('should have password input', async () => { await expect(element(by.id('login-view-password'))).toBeVisible(); }); - it('should have submit button', async() => { + it('should have submit button', async () => { await expect(element(by.id('login-view-submit'))).toBeVisible(); }); - it('should have register button', async() => { + it('should have register button', async () => { await expect(element(by.id('login-view-register'))).toBeVisible(); }); - it('should have forgot password button', async() => { + it('should have forgot password button', async () => { await expect(element(by.id('login-view-forgot-password'))).toBeVisible(); }); - it('should have legal button', async() => { + it('should have legal button', async () => { await expect(element(by.id('login-view-more'))).toBeVisible(); }); }); describe('Usage', () => { - it('should navigate to register', async() => { + it('should navigate to register', async () => { await element(by.id('login-view-register')).tap(); - await waitFor(element(by.id('register-view'))).toBeVisible().withTimeout(2000); + await waitFor(element(by.id('register-view'))) + .toBeVisible() + .withTimeout(2000); await tapBack(); }); - it('should navigate to forgot password', async() => { + it('should navigate to forgot password', async () => { await element(by.id('login-view-forgot-password')).tap(); - await waitFor(element(by.id('forgot-password-view'))).toExist().withTimeout(2000); + await waitFor(element(by.id('forgot-password-view'))) + .toExist() + .withTimeout(2000); await tapBack(); }); - it('should insert wrong password and get error', async() => { + it('should insert wrong password and get error', async () => { await element(by.id('login-view-email')).replaceText(data.users.regular.username); await element(by.id('login-view-password')).replaceText('NotMyActualPassword'); await element(by.id('login-view-submit')).tap(); - await waitFor(element(by.text('Your credentials were rejected! Please try again.'))).toBeVisible().withTimeout(10000); + await waitFor(element(by.text('Your credentials were rejected! Please try again.'))) + .toBeVisible() + .withTimeout(10000); await element(by.text('OK')).tap(); }); - it('should login with success', async() => { + it('should login with success', async () => { await element(by.id('login-view-password')).replaceText(data.users.regular.password); await element(by.id('login-view-submit')).tap(); - await waitFor(element(by.id('rooms-list-view'))).toBeVisible().withTimeout(60000); + await waitFor(element(by.id('rooms-list-view'))) + .toBeVisible() + .withTimeout(60000); }); }); }); diff --git a/e2e/tests/onboarding/06-roomslist.spec.js b/e2e/tests/onboarding/06-roomslist.spec.js index 03e595edf..6cb2d71f8 100644 --- a/e2e/tests/onboarding/06-roomslist.spec.js +++ b/e2e/tests/onboarding/06-roomslist.spec.js @@ -1,48 +1,54 @@ -const { - login, navigateToLogin, logout, tapBack, searchRoom -} = require('../../helpers/app'); +const { login, navigateToLogin, logout, tapBack, searchRoom } = require('../../helpers/app'); const data = require('../../data'); describe('Rooms list screen', () => { - before(async() => { + before(async () => { await device.launchApp({ permissions: { notifications: 'YES' }, newInstance: true, delete: true }); await navigateToLogin(); await login(data.users.regular.username, data.users.regular.password); }); describe('Render', () => { - it('should have rooms list screen', async() => { + it('should have rooms list screen', async () => { await expect(element(by.id('rooms-list-view'))).toBeVisible(); }); - it('should have room item', async() => { + it('should have room item', async () => { await expect(element(by.id('rooms-list-view-item-general'))).toExist(); }); // Render - Header describe('Header', () => { - it('should have create channel button', async() => { + it('should have create channel button', async () => { await expect(element(by.id('rooms-list-view-create-channel'))).toBeVisible(); }); - it('should have sidebar button', async() => { + it('should have sidebar button', async () => { await expect(element(by.id('rooms-list-view-sidebar'))).toBeVisible(); }); }); }); describe('Usage', () => { - it('should search room and navigate', async() => { + it('should search room and navigate', async () => { await searchRoom('rocket.cat'); await element(by.id('rooms-list-view-item-rocket.cat')).tap(); - await waitFor(element(by.id('room-view'))).toBeVisible().withTimeout(10000); - await waitFor(element(by.id('room-view-title-rocket.cat'))).toBeVisible().withTimeout(60000); + await waitFor(element(by.id('room-view'))) + .toBeVisible() + .withTimeout(10000); + await waitFor(element(by.id('room-view-title-rocket.cat'))) + .toBeVisible() + .withTimeout(60000); await tapBack(); - await waitFor(element(by.id('rooms-list-view'))).toBeVisible().withTimeout(2000); - await waitFor(element(by.id('rooms-list-view-item-rocket.cat'))).toExist().withTimeout(60000); + await waitFor(element(by.id('rooms-list-view'))) + .toBeVisible() + .withTimeout(2000); + await waitFor(element(by.id('rooms-list-view-item-rocket.cat'))) + .toExist() + .withTimeout(60000); }); - it('should logout', async() => { + it('should logout', async () => { await logout(); }); }); diff --git a/e2e/tests/onboarding/07-server-history.spec.js b/e2e/tests/onboarding/07-server-history.spec.js index b984b9c8b..d57f1a9a7 100644 --- a/e2e/tests/onboarding/07-server-history.spec.js +++ b/e2e/tests/onboarding/07-server-history.spec.js @@ -1,43 +1,54 @@ -const { - login, navigateToLogin, logout, tapBack -} = require('../../helpers/app'); +const { login, navigateToLogin, logout, tapBack } = require('../../helpers/app'); const data = require('../../data'); describe('Server history', () => { - before(async() => { + before(async () => { await device.launchApp({ permissions: { notifications: 'YES' }, delete: true }); }); describe('Usage', () => { - it('should login, save server as history and logout', async() => { + it('should login, save server as history and logout', async () => { await navigateToLogin(); await login(data.users.regular.username, data.users.regular.password); await logout(); - await element(by.id('join-workspace')).tap(); - await waitFor(element(by.id('new-server-view'))).toBeVisible().withTimeout(60000); + await waitFor(element(by.id('new-server-view'))) + .toBeVisible() + .withTimeout(60000); }); - it('should show servers history', async() => { + it('should show servers history', async () => { await element(by.id('new-server-view-input')).tap(); - await waitFor(element(by.id(`server-history-${ data.server }`))).toBeVisible().withTimeout(2000); + await waitFor(element(by.id(`server-history-${data.server}`))) + .toBeVisible() + .withTimeout(2000); }); - it('should tap on a server history and navigate to login', async() => { - await element(by.id(`server-history-${ data.server }`)).tap(); - await waitFor(element(by.id('login-view'))).toBeVisible().withTimeout(5000); + it('should tap on a server history and navigate to login', async () => { + await element(by.id(`server-history-${data.server}`)).tap(); + await waitFor(element(by.id('login-view'))) + .toBeVisible() + .withTimeout(5000); await expect(element(by.id('login-view-email'))).toHaveText(data.users.regular.username); }); - it('should delete server from history', async() => { + it('should delete server from history', async () => { await tapBack(); - await waitFor(element(by.id('workspace-view'))).toBeVisible().withTimeout(2000); + await waitFor(element(by.id('workspace-view'))) + .toBeVisible() + .withTimeout(2000); await tapBack(); - await waitFor(element(by.id('new-server-view'))).toBeVisible().withTimeout(2000); + await waitFor(element(by.id('new-server-view'))) + .toBeVisible() + .withTimeout(2000); await element(by.id('new-server-view-input')).tap(); - await waitFor(element(by.id(`server-history-${ data.server }`))).toBeVisible().withTimeout(2000); - await element(by.id(`server-history-delete-${ data.server }`)).tap(); + await waitFor(element(by.id(`server-history-${data.server}`))) + .toBeVisible() + .withTimeout(2000); + await element(by.id(`server-history-delete-${data.server}`)).tap(); await element(by.id('new-server-view-input')).tap(); - await waitFor(element(by.id(`server-history-${ data.server }`))).toBeNotVisible().withTimeout(2000); + await waitFor(element(by.id(`server-history-${data.server}`))) + .toBeNotVisible() + .withTimeout(2000); }); }); }); diff --git a/e2e/tests/room/01-createroom.spec.js b/e2e/tests/room/01-createroom.spec.js index bc67b2c3e..a679c3a90 100644 --- a/e2e/tests/room/01-createroom.spec.js +++ b/e2e/tests/room/01-createroom.spec.js @@ -1,87 +1,117 @@ const data = require('../../data'); -const { - tapBack, navigateToLogin, login, tryTapping -} = require('../../helpers/app'); +const { tapBack, navigateToLogin, login, tryTapping } = require('../../helpers/app'); describe('Create room screen', () => { - before(async() => { + before(async () => { await device.launchApp({ permissions: { notifications: 'YES' }, delete: true }); await navigateToLogin(); await login(data.users.regular.username, data.users.regular.password); }); describe('New Message', () => { - before(async() => { + before(async () => { await element(by.id('rooms-list-view-create-channel')).tap(); }); describe('Render', () => { - it('should have new message screen', async() => { - await waitFor(element(by.id('new-message-view'))).toBeVisible().withTimeout(2000); + it('should have new message screen', async () => { + await waitFor(element(by.id('new-message-view'))) + .toBeVisible() + .withTimeout(2000); }); - it('should have search input', async() => { - await waitFor(element(by.id('new-message-view-search'))).toBeVisible().withTimeout(2000); + it('should have search input', async () => { + await waitFor(element(by.id('new-message-view-search'))) + .toBeVisible() + .withTimeout(2000); }); }); describe('Usage', () => { - it('should back to rooms list', async() => { - await waitFor(element(by.id('new-message-view-close'))).toBeVisible().withTimeout(5000); + it('should back to rooms list', async () => { + await waitFor(element(by.id('new-message-view-close'))) + .toBeVisible() + .withTimeout(5000); await element(by.id('new-message-view-close')).tap(); - await waitFor(element(by.id('rooms-list-view'))).toBeVisible().withTimeout(5000); + await waitFor(element(by.id('rooms-list-view'))) + .toBeVisible() + .withTimeout(5000); await tryTapping(element(by.id('rooms-list-view-create-channel')), 3000); // await element(by.id('rooms-list-view-create-channel')).tap(); - await waitFor(element(by.id('new-message-view'))).toExist().withTimeout(5000); + await waitFor(element(by.id('new-message-view'))) + .toExist() + .withTimeout(5000); }); - it('should search user and navigate', async() => { + it('should search user and navigate', async () => { await element(by.id('new-message-view-search')).replaceText('rocket.cat'); - await waitFor(element(by.id('new-message-view-item-rocket.cat'))).toExist().withTimeout(60000); + await waitFor(element(by.id('new-message-view-item-rocket.cat'))) + .toExist() + .withTimeout(60000); await element(by.id('new-message-view-item-rocket.cat')).tap(); - await waitFor(element(by.id('room-view'))).toExist().withTimeout(10000); - await waitFor(element(by.id('room-view-title-rocket.cat'))).toExist().withTimeout(60000); + await waitFor(element(by.id('room-view'))) + .toExist() + .withTimeout(10000); + await waitFor(element(by.id('room-view-title-rocket.cat'))) + .toExist() + .withTimeout(60000); await tapBack(); - await waitFor(element(by.id('rooms-list-view'))).toExist().withTimeout(5000); + await waitFor(element(by.id('rooms-list-view'))) + .toExist() + .withTimeout(5000); }); - it('should navigate to select users', async() => { + it('should navigate to select users', async () => { await element(by.id('rooms-list-view-create-channel')).tap(); - await waitFor(element(by.id('new-message-view'))).toExist().withTimeout(5000); + await waitFor(element(by.id('new-message-view'))) + .toExist() + .withTimeout(5000); await element(by.id('new-message-view-create-channel')).tap(); - await waitFor(element(by.id('select-users-view'))).toExist().withTimeout(5000); + await waitFor(element(by.id('select-users-view'))) + .toExist() + .withTimeout(5000); }); }); }); describe('Select Users', () => { - it('should search users', async() => { + it('should search users', async () => { await element(by.id('select-users-view-search')).replaceText('rocket.cat'); - await waitFor(element(by.id('select-users-view-item-rocket.cat'))).toBeVisible().withTimeout(10000); + await waitFor(element(by.id('select-users-view-item-rocket.cat'))) + .toBeVisible() + .withTimeout(10000); }); - it('should select/unselect user', async() => { + it('should select/unselect user', async () => { // Spotlight issues await element(by.id('select-users-view-item-rocket.cat')).tap(); - await waitFor(element(by.id('selected-user-rocket.cat'))).toBeVisible().withTimeout(10000); + await waitFor(element(by.id('selected-user-rocket.cat'))) + .toBeVisible() + .withTimeout(10000); await element(by.id('selected-user-rocket.cat')).tap(); - await waitFor(element(by.id('selected-user-rocket.cat'))).toBeNotVisible().withTimeout(10000); + await waitFor(element(by.id('selected-user-rocket.cat'))) + .toBeNotVisible() + .withTimeout(10000); // Spotlight issues await element(by.id('select-users-view-item-rocket.cat')).tap(); - await waitFor(element(by.id('selected-user-rocket.cat'))).toBeVisible().withTimeout(10000); + await waitFor(element(by.id('selected-user-rocket.cat'))) + .toBeVisible() + .withTimeout(10000); }); - it('should navigate to create channel view', async() => { + it('should navigate to create channel view', async () => { await element(by.id('selected-users-view-submit')).tap(); - await waitFor(element(by.id('create-channel-view'))).toExist().withTimeout(10000); + await waitFor(element(by.id('create-channel-view'))) + .toExist() + .withTimeout(10000); }); }); describe('Create Channel', () => { describe('Render', () => { - it('should render all fields', async() => { + it('should render all fields', async () => { await expect(element(by.id('create-channel-name'))).toBeVisible(); await expect(element(by.id('create-channel-type'))).toBeVisible(); await expect(element(by.id('create-channel-readonly'))).toBeVisible(); @@ -90,73 +120,117 @@ describe('Create room screen', () => { }); describe('Usage', () => { - it('should get invalid room', async() => { + it('should get invalid room', async () => { await element(by.id('create-channel-name')).typeText('general'); await element(by.id('create-channel-submit')).tap(); - await waitFor(element(by.text('A channel with name general exists'))).toExist().withTimeout(60000); + await waitFor(element(by.text('A channel with name general exists'))) + .toExist() + .withTimeout(60000); await expect(element(by.text('A channel with name general exists'))).toExist(); await element(by.text('OK')).tap(); }); - it('should create public room', async() => { - const room = `public${ data.random }`; + it('should create public room', async () => { + const room = `public${data.random}`; await element(by.id('create-channel-name')).replaceText(''); await element(by.id('create-channel-name')).typeText(room); await element(by.id('create-channel-type')).tap(); await element(by.id('create-channel-submit')).tap(); - await waitFor(element(by.id('room-view'))).toExist().withTimeout(6000); + await waitFor(element(by.id('room-view'))) + .toExist() + .withTimeout(6000); await expect(element(by.id('room-view'))).toExist(); - await waitFor(element(by.id(`room-view-title-${ room }`))).toExist().withTimeout(6000); - await expect(element(by.id(`room-view-title-${ room }`))).toExist(); + await waitFor(element(by.id(`room-view-title-${room}`))) + .toExist() + .withTimeout(6000); + await expect(element(by.id(`room-view-title-${room}`))).toExist(); await tapBack(); - await waitFor(element(by.id('rooms-list-view'))).toExist().withTimeout(10000); - await waitFor(element(by.id(`rooms-list-view-item-${ room }`))).toExist().withTimeout(6000); - await expect(element(by.id(`rooms-list-view-item-${ room }`))).toExist(); + await waitFor(element(by.id('rooms-list-view'))) + .toExist() + .withTimeout(10000); + await waitFor(element(by.id(`rooms-list-view-item-${room}`))) + .toExist() + .withTimeout(6000); + await expect(element(by.id(`rooms-list-view-item-${room}`))).toExist(); }); - it('should create private room', async() => { - const room = `private${ data.random }`; - await waitFor(element(by.id('rooms-list-view'))).toExist().withTimeout(5000); + it('should create private room', async () => { + const room = `private${data.random}`; + await waitFor(element(by.id('rooms-list-view'))) + .toExist() + .withTimeout(5000); await element(by.id('rooms-list-view-create-channel')).tap(); - await waitFor(element(by.id('new-message-view'))).toExist().withTimeout(5000); + await waitFor(element(by.id('new-message-view'))) + .toExist() + .withTimeout(5000); await element(by.id('new-message-view-create-channel')).tap(); - await waitFor(element(by.id('select-users-view'))).toExist().withTimeout(5000); + await waitFor(element(by.id('select-users-view'))) + .toExist() + .withTimeout(5000); await element(by.id('select-users-view-item-rocket.cat')).tap(); - await waitFor(element(by.id('selected-user-rocket.cat'))).toExist().withTimeout(5000); + await waitFor(element(by.id('selected-user-rocket.cat'))) + .toExist() + .withTimeout(5000); await element(by.id('selected-users-view-submit')).tap(); - await waitFor(element(by.id('create-channel-view'))).toExist().withTimeout(5000); + await waitFor(element(by.id('create-channel-view'))) + .toExist() + .withTimeout(5000); await element(by.id('create-channel-name')).typeText(room); await element(by.id('create-channel-submit')).tap(); - await waitFor(element(by.id('room-view'))).toExist().withTimeout(60000); + await waitFor(element(by.id('room-view'))) + .toExist() + .withTimeout(60000); await expect(element(by.id('room-view'))).toExist(); - await waitFor(element(by.id(`room-view-title-${ room }`))).toExist().withTimeout(60000); - await expect(element(by.id(`room-view-title-${ room }`))).toExist(); + await waitFor(element(by.id(`room-view-title-${room}`))) + .toExist() + .withTimeout(60000); + await expect(element(by.id(`room-view-title-${room}`))).toExist(); await tapBack(); - await waitFor(element(by.id('rooms-list-view'))).toExist().withTimeout(5000); - await waitFor(element(by.id(`rooms-list-view-item-${ room }`))).toExist().withTimeout(60000); - await expect(element(by.id(`rooms-list-view-item-${ room }`))).toExist(); + await waitFor(element(by.id('rooms-list-view'))) + .toExist() + .withTimeout(5000); + await waitFor(element(by.id(`rooms-list-view-item-${room}`))) + .toExist() + .withTimeout(60000); + await expect(element(by.id(`rooms-list-view-item-${room}`))).toExist(); }); - it('should create empty room', async() => { - const room = `empty${ data.random }`; - await waitFor(element(by.id('rooms-list-view'))).toExist().withTimeout(10000); + it('should create empty room', async () => { + const room = `empty${data.random}`; + await waitFor(element(by.id('rooms-list-view'))) + .toExist() + .withTimeout(10000); // await device.launchApp({ newInstance: true }); await element(by.id('rooms-list-view-create-channel')).tap(); - await waitFor(element(by.id('new-message-view'))).toExist().withTimeout(5000); + await waitFor(element(by.id('new-message-view'))) + .toExist() + .withTimeout(5000); await element(by.id('new-message-view-create-channel')).tap(); - await waitFor(element(by.id('select-users-view'))).toExist().withTimeout(5000); + await waitFor(element(by.id('select-users-view'))) + .toExist() + .withTimeout(5000); await element(by.id('selected-users-view-submit')).tap(); - await waitFor(element(by.id('create-channel-view'))).toExist().withTimeout(10000); + await waitFor(element(by.id('create-channel-view'))) + .toExist() + .withTimeout(10000); await element(by.id('create-channel-name')).typeText(room); await element(by.id('create-channel-submit')).tap(); - await waitFor(element(by.id('room-view'))).toExist().withTimeout(60000); + await waitFor(element(by.id('room-view'))) + .toExist() + .withTimeout(60000); await expect(element(by.id('room-view'))).toExist(); - await waitFor(element(by.id(`room-view-title-${ room }`))).toExist().withTimeout(60000); - await expect(element(by.id(`room-view-title-${ room }`))).toExist(); + await waitFor(element(by.id(`room-view-title-${room}`))) + .toExist() + .withTimeout(60000); + await expect(element(by.id(`room-view-title-${room}`))).toExist(); await tapBack(); - await waitFor(element(by.id('rooms-list-view'))).toExist().withTimeout(2000); - await waitFor(element(by.id(`rooms-list-view-item-${ room }`))).toExist().withTimeout(60000); - await expect(element(by.id(`rooms-list-view-item-${ room }`))).toExist(); + await waitFor(element(by.id('rooms-list-view'))) + .toExist() + .withTimeout(2000); + await waitFor(element(by.id(`rooms-list-view-item-${room}`))) + .toExist() + .withTimeout(60000); + await expect(element(by.id(`rooms-list-view-item-${room}`))).toExist(); }); }); }); diff --git a/e2e/tests/room/02-room.spec.js b/e2e/tests/room/02-room.spec.js index 4e1711e64..9aa62c6d4 100644 --- a/e2e/tests/room/02-room.spec.js +++ b/e2e/tests/room/02-room.spec.js @@ -1,18 +1,29 @@ const data = require('../../data'); const { - navigateToLogin, login, mockMessage, tapBack, sleep, searchRoom, starMessage, pinMessage, dismissReviewNag, tryTapping + navigateToLogin, + login, + mockMessage, + tapBack, + sleep, + searchRoom, + starMessage, + pinMessage, + dismissReviewNag, + tryTapping } = require('../../helpers/app'); async function navigateToRoom(roomName) { - await searchRoom(`${ roomName }`); - await element(by.id(`rooms-list-view-item-${ roomName }`)).tap(); - await waitFor(element(by.id('room-view'))).toBeVisible().withTimeout(5000); + await searchRoom(`${roomName}`); + await element(by.id(`rooms-list-view-item-${roomName}`)).tap(); + await waitFor(element(by.id('room-view'))) + .toBeVisible() + .withTimeout(5000); } describe('Room screen', () => { const mainRoom = data.groups.private.name; - before(async() => { + before(async () => { await device.launchApp({ permissions: { notifications: 'YES' }, delete: true }); await navigateToLogin(); await login(data.users.regular.username, data.users.regular.password); @@ -20,43 +31,45 @@ describe('Room screen', () => { }); describe('Render', () => { - it('should have room screen', async() => { + it('should have room screen', async () => { await expect(element(by.id('room-view'))).toExist(); - await waitFor(element(by.id(`room-view-title-${ mainRoom }`))).toExist().withTimeout(5000); + await waitFor(element(by.id(`room-view-title-${mainRoom}`))) + .toExist() + .withTimeout(5000); }); // Render - Header describe('Header', () => { - it('should have actions button ', async() => { + it('should have actions button ', async () => { await expect(element(by.id('room-header'))).toExist(); }); - it('should have threads button ', async() => { + it('should have threads button ', async () => { await expect(element(by.id('room-view-header-threads'))).toExist(); }); }); // Render - Messagebox describe('Messagebox', () => { - it('should have messagebox', async() => { + it('should have messagebox', async () => { await expect(element(by.id('messagebox'))).toExist(); }); - it('should have open emoji button', async() => { + it('should have open emoji button', async () => { if (device.getPlatform() === 'android') { await expect(element(by.id('messagebox-open-emoji'))).toExist(); } }); - it('should have message input', async() => { + it('should have message input', async () => { await expect(element(by.id('messagebox-input'))).toExist(); }); - it('should have audio button', async() => { + it('should have audio button', async () => { await expect(element(by.id('messagebox-send-audio'))).toExist(); }); - it('should have actions button', async() => { + it('should have actions button', async () => { await expect(element(by.id('messagebox-actions'))).toExist(); }); }); @@ -64,95 +77,116 @@ describe('Room screen', () => { describe('Usage', () => { describe('Messagebox', () => { - it('should send message', async() => { + it('should send message', async () => { await mockMessage('message'); - await expect(element(by.label(`${ data.random }message`)).atIndex(0)).toExist(); + await expect(element(by.label(`${data.random}message`)).atIndex(0)).toExist(); }); - - it('should show/hide emoji keyboard', async() => { + it('should show/hide emoji keyboard', async () => { if (device.getPlatform() === 'android') { await element(by.id('messagebox-open-emoji')).tap(); - await waitFor(element(by.id('messagebox-keyboard-emoji'))).toExist().withTimeout(10000); + await waitFor(element(by.id('messagebox-keyboard-emoji'))) + .toExist() + .withTimeout(10000); await expect(element(by.id('messagebox-close-emoji'))).toExist(); await expect(element(by.id('messagebox-open-emoji'))).toBeNotVisible(); await element(by.id('messagebox-close-emoji')).tap(); - await waitFor(element(by.id('messagebox-keyboard-emoji'))).toBeNotVisible().withTimeout(10000); + await waitFor(element(by.id('messagebox-keyboard-emoji'))) + .toBeNotVisible() + .withTimeout(10000); await expect(element(by.id('messagebox-close-emoji'))).toBeNotVisible(); await expect(element(by.id('messagebox-open-emoji'))).toExist(); } }); - it('should show/hide emoji autocomplete', async() => { + it('should show/hide emoji autocomplete', async () => { await element(by.id('messagebox-input')).tap(); await element(by.id('messagebox-input')).typeText(':joy'); - await waitFor(element(by.id('messagebox-container'))).toExist().withTimeout(10000); + await waitFor(element(by.id('messagebox-container'))) + .toExist() + .withTimeout(10000); await element(by.id('messagebox-input')).clearText(); - await waitFor(element(by.id('messagebox-container'))).toBeNotVisible().withTimeout(10000); + await waitFor(element(by.id('messagebox-container'))) + .toBeNotVisible() + .withTimeout(10000); }); - it('should show and tap on emoji autocomplete', async() => { + it('should show and tap on emoji autocomplete', async () => { await element(by.id('messagebox-input')).tap(); await element(by.id('messagebox-input')).replaceText(':'); await element(by.id('messagebox-input')).typeText('joy'); // workaround for number keyboard - await waitFor(element(by.id('messagebox-container'))).toExist().withTimeout(10000); + await waitFor(element(by.id('messagebox-container'))) + .toExist() + .withTimeout(10000); await element(by.id('mention-item-joy')).tap(); await expect(element(by.id('messagebox-input'))).toHaveText(':joy: '); await element(by.id('messagebox-input')).clearText(); }); - it('should not show emoji autocomplete on semicolon in middle of a string', async() => { + it('should not show emoji autocomplete on semicolon in middle of a string', async () => { await element(by.id('messagebox-input')).tap(); // await element(by.id('messagebox-input')).replaceText(':'); await element(by.id('messagebox-input')).typeText('name:is'); - await waitFor(element(by.id('messagebox-container'))).toNotExist().withTimeout(20000); + await waitFor(element(by.id('messagebox-container'))) + .toNotExist() + .withTimeout(20000); await element(by.id('messagebox-input')).clearText(); }); - it('should show and tap on user autocomplete and send mention', async() => { + it('should show and tap on user autocomplete and send mention', async () => { const { username } = data.users.regular; await element(by.id('messagebox-input')).tap(); - await element(by.id('messagebox-input')).typeText(`@${ username }`); - await waitFor(element(by.id('messagebox-container'))).toExist().withTimeout(4000); - await waitFor(element(by.id(`mention-item-${ username }`))).toBeVisible().withTimeout(4000); - await tryTapping(element(by.id(`mention-item-${ username }`)), 2000, true); - await expect(element(by.id('messagebox-input'))).toHaveText(`@${ username } `); + await element(by.id('messagebox-input')).typeText(`@${username}`); + await waitFor(element(by.id('messagebox-container'))) + .toExist() + .withTimeout(4000); + await waitFor(element(by.id(`mention-item-${username}`))) + .toBeVisible() + .withTimeout(4000); + await tryTapping(element(by.id(`mention-item-${username}`)), 2000, true); + await expect(element(by.id('messagebox-input'))).toHaveText(`@${username} `); await tryTapping(element(by.id('messagebox-input')), 2000); - await element(by.id('messagebox-input')).typeText(`${ data.random }mention`); + await element(by.id('messagebox-input')).typeText(`${data.random}mention`); await element(by.id('messagebox-send-message')).tap(); // await waitFor(element(by.label(`@${ data.user } ${ data.random }mention`)).atIndex(0)).toExist().withTimeout(60000); }); - it('should not show user autocomplete on @ in the middle of a string', async() => { + it('should not show user autocomplete on @ in the middle of a string', async () => { await element(by.id('messagebox-input')).tap(); await element(by.id('messagebox-input')).typeText('email@gmail'); - await waitFor(element(by.id('messagebox-container'))).toNotExist().withTimeout(4000); + await waitFor(element(by.id('messagebox-container'))) + .toNotExist() + .withTimeout(4000); await element(by.id('messagebox-input')).clearText(); }); - it('should show and tap on room autocomplete', async() => { + it('should show and tap on room autocomplete', async () => { await element(by.id('messagebox-input')).tap(); await element(by.id('messagebox-input')).typeText('#general'); // await waitFor(element(by.id('messagebox-container'))).toExist().withTimeout(4000); - await waitFor(element(by.id('mention-item-general'))).toBeVisible().withTimeout(4000); + await waitFor(element(by.id('mention-item-general'))) + .toBeVisible() + .withTimeout(4000); await tryTapping(element(by.id('mention-item-general')), 2000, true); await expect(element(by.id('messagebox-input'))).toHaveText('#general '); await element(by.id('messagebox-input')).clearText(); }); - it('should not show room autocomplete on # in middle of a string', async() => { + it('should not show room autocomplete on # in middle of a string', async () => { await element(by.id('messagebox-input')).tap(); await element(by.id('messagebox-input')).typeText('te#gen'); - await waitFor(element(by.id('messagebox-container'))).toNotExist().withTimeout(4000); + await waitFor(element(by.id('messagebox-container'))) + .toNotExist() + .withTimeout(4000); await element(by.id('messagebox-input')).clearText(); }); - it('should draft message', async() => { + it('should draft message', async () => { await element(by.id('messagebox-input')).tap(); - await element(by.id('messagebox-input')).typeText(`${ data.random }draft`); + await element(by.id('messagebox-input')).typeText(`${data.random}draft`); await tapBack(); await navigateToRoom(mainRoom); - await expect(element(by.id('messagebox-input'))).toHaveText(`${ data.random }draft`); + await expect(element(by.id('messagebox-input'))).toHaveText(`${data.random}draft`); await element(by.id('messagebox-input')).clearText(); await tapBack(); @@ -162,8 +196,10 @@ describe('Room screen', () => { }); describe('Message', () => { - it('should copy permalink', async() => { - await element(by.label(`${ data.random }message`)).atIndex(0).longPress(); + it('should copy permalink', async () => { + await element(by.label(`${data.random}message`)) + .atIndex(0) + .longPress(); await expect(element(by.id('action-sheet'))).toExist(); await expect(element(by.id('action-sheet-handle'))).toBeVisible(); await element(by.id('action-sheet-handle')).swipe('up', 'fast', 0.5); @@ -172,8 +208,10 @@ describe('Room screen', () => { // TODO: test clipboard }); - it('should copy message', async() => { - await element(by.label(`${ data.random }message`)).atIndex(0).longPress(); + it('should copy message', async () => { + await element(by.label(`${data.random}message`)) + .atIndex(0) + .longPress(); await expect(element(by.id('action-sheet'))).toExist(); await expect(element(by.id('action-sheet-handle'))).toBeVisible(); await element(by.id('action-sheet-handle')).swipe('up', 'fast', 0.5); @@ -182,115 +220,167 @@ describe('Room screen', () => { // TODO: test clipboard }); - it('should star message', async() => { + it('should star message', async () => { await starMessage('message'); await sleep(1000); // https://github.com/RocketChat/Rocket.Chat.ReactNative/issues/2324 - await element(by.label(`${ data.random }message`)).atIndex(0).longPress(); + await element(by.label(`${data.random}message`)) + .atIndex(0) + .longPress(); await expect(element(by.id('action-sheet'))).toExist(); await expect(element(by.id('action-sheet-handle'))).toBeVisible(); await element(by.id('action-sheet-handle')).swipe('up', 'slow', 0.5); - await waitFor(element(by.label('Unstar')).atIndex(0)).toExist().withTimeout(6000); + await waitFor(element(by.label('Unstar')).atIndex(0)) + .toExist() + .withTimeout(6000); await element(by.id('action-sheet-handle')).swipe('down', 'fast', 0.8); }); - it('should react to message', async() => { - await element(by.label(`${ data.random }message`)).atIndex(0).longPress(); + it('should react to message', async () => { + await element(by.label(`${data.random}message`)) + .atIndex(0) + .longPress(); await expect(element(by.id('action-sheet'))).toExist(); await expect(element(by.id('action-sheet-handle'))).toBeVisible(); await element(by.id('action-sheet-handle')).swipe('up', 'fast', 0.5); await element(by.id('add-reaction')).tap(); - await waitFor(element(by.id('reaction-picker'))).toBeVisible().withTimeout(2000); + await waitFor(element(by.id('reaction-picker'))) + .toBeVisible() + .withTimeout(2000); await element(by.id('reaction-picker-😃')).tap(); - await waitFor(element(by.id('reaction-picker-grinning'))).toExist().withTimeout(2000); + await waitFor(element(by.id('reaction-picker-grinning'))) + .toExist() + .withTimeout(2000); await element(by.id('reaction-picker-grinning')).tap(); - await waitFor(element(by.id('message-reaction-:grinning:'))).toExist().withTimeout(60000); + await waitFor(element(by.id('message-reaction-:grinning:'))) + .toExist() + .withTimeout(60000); }); - it('should react to message with frequently used emoji', async() => { - await element(by.label(`${ data.random }message`)).atIndex(0).longPress(); + it('should react to message with frequently used emoji', async () => { + await element(by.label(`${data.random}message`)) + .atIndex(0) + .longPress(); await expect(element(by.id('action-sheet'))).toExist(); await expect(element(by.id('action-sheet-handle'))).toBeVisible(); await element(by.id('action-sheet-handle')).swipe('up', 'fast', 0.5); - await waitFor(element(by.id('message-actions-emoji-+1'))).toBeVisible().withTimeout(2000); + await waitFor(element(by.id('message-actions-emoji-+1'))) + .toBeVisible() + .withTimeout(2000); await element(by.id('message-actions-emoji-+1')).tap(); - await waitFor(element(by.id('message-reaction-:+1:'))).toBeVisible().withTimeout(60000); + await waitFor(element(by.id('message-reaction-:+1:'))) + .toBeVisible() + .withTimeout(60000); }); - it('should show reaction picker on add reaction button pressed and have frequently used emoji', async() => { + it('should show reaction picker on add reaction button pressed and have frequently used emoji', async () => { await element(by.id('message-add-reaction')).tap(); - await waitFor(element(by.id('reaction-picker'))).toExist().withTimeout(2000); - await waitFor(element(by.id('reaction-picker-grinning'))).toExist().withTimeout(2000); + await waitFor(element(by.id('reaction-picker'))) + .toExist() + .withTimeout(2000); + await waitFor(element(by.id('reaction-picker-grinning'))) + .toExist() + .withTimeout(2000); await element(by.id('reaction-picker-😃')).tap(); - await waitFor(element(by.id('reaction-picker-grimacing'))).toExist().withTimeout(2000); + await waitFor(element(by.id('reaction-picker-grimacing'))) + .toExist() + .withTimeout(2000); await element(by.id('reaction-picker-grimacing')).tap(); - await waitFor(element(by.id('message-reaction-:grimacing:'))).toExist().withTimeout(60000); + await waitFor(element(by.id('message-reaction-:grimacing:'))) + .toExist() + .withTimeout(60000); }); - it('should ask for review', async() => { + it('should ask for review', async () => { await dismissReviewNag(); // TODO: Create a proper test for this elsewhere. }); - it('should remove reaction', async() => { + it('should remove reaction', async () => { await element(by.id('message-reaction-:grinning:')).tap(); - await waitFor(element(by.id('message-reaction-:grinning:'))).toBeNotVisible().withTimeout(60000); + await waitFor(element(by.id('message-reaction-:grinning:'))) + .toBeNotVisible() + .withTimeout(60000); }); - it('should edit message', async() => { + it('should edit message', async () => { await mockMessage('edit'); - await element(by.label(`${ data.random }edit`)).atIndex(0).longPress(); + await element(by.label(`${data.random}edit`)) + .atIndex(0) + .longPress(); await expect(element(by.id('action-sheet'))).toExist(); await expect(element(by.id('action-sheet-handle'))).toBeVisible(); await element(by.id('action-sheet-handle')).swipe('up', 'fast', 0.5); await element(by.label('Edit')).atIndex(0).tap(); await element(by.id('messagebox-input')).typeText('ed'); await element(by.id('messagebox-send-message')).tap(); - await waitFor(element(by.label(`${ data.random }edited (edited)`)).atIndex(0)).toExist().withTimeout(60000); + await waitFor(element(by.label(`${data.random}edited (edited)`)).atIndex(0)) + .toExist() + .withTimeout(60000); }); - it('should quote message', async() => { + it('should quote message', async () => { await mockMessage('quote'); - await element(by.label(`${ data.random }quote`)).atIndex(0).longPress(); + await element(by.label(`${data.random}quote`)) + .atIndex(0) + .longPress(); await expect(element(by.id('action-sheet'))).toExist(); await expect(element(by.id('action-sheet-handle'))).toBeVisible(); await element(by.id('action-sheet-handle')).swipe('up', 'fast', 0.5); await element(by.label('Quote')).atIndex(0).tap(); - await element(by.id('messagebox-input')).typeText(`${ data.random }quoted`); + await element(by.id('messagebox-input')).typeText(`${data.random}quoted`); await element(by.id('messagebox-send-message')).tap(); // TODO: test if quote was sent }); - it('should pin message', async() => { + it('should pin message', async () => { await mockMessage('pin'); await pinMessage('pin'); - await waitFor(element(by.label(`${ data.random }pin`)).atIndex(0)).toExist().withTimeout(5000); - await waitFor(element(by.label(`${ data.users.regular.username } Message pinned`)).atIndex(0)).toExist().withTimeout(5000); - await element(by.label(`${ data.random }pin`)).atIndex(0).longPress(); - await waitFor(element(by.id('action-sheet'))).toExist().withTimeout(1000); + await waitFor(element(by.label(`${data.random}pin`)).atIndex(0)) + .toExist() + .withTimeout(5000); + await waitFor(element(by.label(`${data.users.regular.username} Message pinned`)).atIndex(0)) + .toExist() + .withTimeout(5000); + await element(by.label(`${data.random}pin`)) + .atIndex(0) + .longPress(); + await waitFor(element(by.id('action-sheet'))) + .toExist() + .withTimeout(1000); await expect(element(by.id('action-sheet-handle'))).toBeVisible(); await element(by.id('action-sheet-handle')).swipe('up', 'fast', 0.5); - await waitFor(element(by.label('Unpin')).atIndex(0)).toExist().withTimeout(2000); + await waitFor(element(by.label('Unpin')).atIndex(0)) + .toExist() + .withTimeout(2000); await element(by.id('action-sheet-handle')).swipe('down', 'fast', 0.8); }); - it('should delete message', async() => { + it('should delete message', async () => { await mockMessage('delete'); - await waitFor(element(by.label(`${ data.random }delete`)).atIndex(0)).toBeVisible(); - await element(by.label(`${ data.random }delete`)).atIndex(0).longPress(); + await waitFor(element(by.label(`${data.random}delete`)).atIndex(0)).toBeVisible(); + await element(by.label(`${data.random}delete`)) + .atIndex(0) + .longPress(); await expect(element(by.id('action-sheet'))).toExist(); await expect(element(by.id('action-sheet-handle'))).toBeVisible(); await element(by.id('action-sheet-handle')).swipe('up', 'fast', 0.5); - await waitFor(element(by.label('Delete'))).toExist().withTimeout(1000); + await waitFor(element(by.label('Delete'))) + .toExist() + .withTimeout(1000); await element(by.label('Delete')).atIndex(0).tap(); const deleteAlertMessage = 'You will not be able to recover this message!'; - await waitFor(element(by.text(deleteAlertMessage)).atIndex(0)).toExist().withTimeout(10000); + await waitFor(element(by.text(deleteAlertMessage)).atIndex(0)) + .toExist() + .withTimeout(10000); await element(by.text('Delete')).tap(); - await waitFor(element(by.label(`${ data.random }delete`)).atIndex(0)).toNotExist().withTimeout(2000); + await waitFor(element(by.label(`${data.random}delete`)).atIndex(0)) + .toNotExist() + .withTimeout(2000); }); }); }); diff --git a/e2e/tests/room/03-roomactions.spec.js b/e2e/tests/room/03-roomactions.spec.js index 1d37bff3d..fb06d07d8 100644 --- a/e2e/tests/room/03-roomactions.spec.js +++ b/e2e/tests/room/03-roomactions.spec.js @@ -1,7 +1,5 @@ const data = require('../../data'); -const { - navigateToLogin, login, tapBack, sleep, searchRoom, mockMessage, starMessage, pinMessage -} = require('../../helpers/app'); +const { navigateToLogin, login, tapBack, sleep, searchRoom, mockMessage, starMessage, pinMessage } = require('../../helpers/app'); const { sendMessage } = require('../../helpers/data_setup'); async function navigateToRoomActions(type) { @@ -12,22 +10,32 @@ async function navigateToRoomActions(type) { room = data.groups.private.name; } await searchRoom(room); - await element(by.id(`rooms-list-view-item-${ room }`)).tap(); - await waitFor(element(by.id('room-view'))).toExist().withTimeout(2000); + await element(by.id(`rooms-list-view-item-${room}`)).tap(); + await waitFor(element(by.id('room-view'))) + .toExist() + .withTimeout(2000); await element(by.id('room-header')).tap(); - await waitFor(element(by.id('room-actions-view'))).toExist().withTimeout(5000); + await waitFor(element(by.id('room-actions-view'))) + .toExist() + .withTimeout(5000); } async function backToActions() { await tapBack(); - await waitFor(element(by.id('room-actions-view'))).toExist().withTimeout(2000); + await waitFor(element(by.id('room-actions-view'))) + .toExist() + .withTimeout(2000); } async function backToRoomsList() { await tapBack(); - await waitFor(element(by.id('room-view'))).toExist().withTimeout(2000); + await waitFor(element(by.id('room-view'))) + .toExist() + .withTimeout(2000); await tapBack(); - await waitFor(element(by.id('rooms-list-view'))).toExist().withTimeout(2000); + await waitFor(element(by.id('rooms-list-view'))) + .toExist() + .withTimeout(2000); } async function waitForToast() { @@ -35,7 +43,7 @@ async function waitForToast() { } describe('Room actions screen', () => { - before(async() => { + before(async () => { await device.launchApp({ permissions: { notifications: 'YES' }, delete: true }); await navigateToLogin(); await login(data.users.regular.username, data.users.regular.password); @@ -43,15 +51,15 @@ describe('Room actions screen', () => { describe('Render', () => { describe('Direct', () => { - before(async() => { + before(async () => { await navigateToRoomActions('d'); }); - it('should have room actions screen', async() => { + it('should have room actions screen', async () => { await expect(element(by.id('room-actions-view'))).toExist(); }); - it('should have info', async() => { + it('should have info', async () => { await expect(element(by.id('room-actions-info'))).toExist(); }); @@ -63,53 +71,53 @@ describe('Room actions screen', () => { // await expect(element(by.id('room-actions-video'))).toExist(); // }); - it('should have files', async() => { + it('should have files', async () => { await expect(element(by.id('room-actions-files'))).toExist(); }); - it('should have mentions', async() => { + it('should have mentions', async () => { await expect(element(by.id('room-actions-mentioned'))).toExist(); }); - it('should have starred', async() => { + it('should have starred', async () => { await expect(element(by.id('room-actions-starred'))).toExist(); }); - it('should have share', async() => { + it('should have share', async () => { await waitFor(element(by.id('room-actions-share'))).toExist(); await expect(element(by.id('room-actions-share'))).toExist(); }); - it('should have pinned', async() => { + it('should have pinned', async () => { await waitFor(element(by.id('room-actions-pinned'))).toExist(); await expect(element(by.id('room-actions-pinned'))).toExist(); }); - it('should have notifications', async() => { + it('should have notifications', async () => { await waitFor(element(by.id('room-actions-notifications'))).toExist(); await expect(element(by.id('room-actions-notifications'))).toExist(); }); - it('should have block user', async() => { + it('should have block user', async () => { await waitFor(element(by.id('room-actions-block-user'))).toExist(); await expect(element(by.id('room-actions-block-user'))).toExist(); }); - after(async() => { + after(async () => { await backToRoomsList(); }); }); describe('Channel/Group', () => { - before(async() => { + before(async () => { await navigateToRoomActions('c'); }); - it('should have room actions screen', async() => { + it('should have room actions screen', async () => { await expect(element(by.id('room-actions-view'))).toExist(); }); - it('should have info', async() => { + it('should have info', async () => { await expect(element(by.id('room-actions-info'))).toExist(); }); @@ -121,42 +129,42 @@ describe('Room actions screen', () => { // await expect(element(by.id('room-actions-video'))).toExist(); // }); - it('should have members', async() => { + it('should have members', async () => { await expect(element(by.id('room-actions-members'))).toExist(); }); - it('should have add user', async() => { + it('should have add user', async () => { await expect(element(by.id('room-actions-add-user'))).toExist(); }); - it('should have files', async() => { + it('should have files', async () => { await expect(element(by.id('room-actions-files'))).toExist(); }); - it('should have mentions', async() => { + it('should have mentions', async () => { await expect(element(by.id('room-actions-mentioned'))).toExist(); }); - it('should have starred', async() => { + it('should have starred', async () => { await expect(element(by.id('room-actions-starred'))).toExist(); }); - it('should have share', async() => { + it('should have share', async () => { await waitFor(element(by.id('room-actions-share'))).toExist(); await expect(element(by.id('room-actions-share'))).toExist(); }); - it('should have pinned', async() => { + it('should have pinned', async () => { await waitFor(element(by.id('room-actions-pinned'))).toExist(); await expect(element(by.id('room-actions-pinned'))).toExist(); }); - it('should have notifications', async() => { + it('should have notifications', async () => { await waitFor(element(by.id('room-actions-notifications'))).toExist(); await expect(element(by.id('room-actions-notifications'))).toExist(); }); - it('should have leave channel', async() => { + it('should have leave channel', async () => { await waitFor(element(by.id('room-actions-leave-channel'))).toExist(); await expect(element(by.id('room-actions-leave-channel'))).toExist(); }); @@ -164,7 +172,7 @@ describe('Room actions screen', () => { }); describe('Usage', () => { - describe('TDB', async() => { + describe('TDB', async () => { // TODO: test into a jitsi call // it('should NOT navigate to voice call', async() => { // await waitFor(element(by.id('room-actions-voice'))).toExist(); @@ -172,14 +180,12 @@ describe('Room actions screen', () => { // await waitFor(element(by.id('room-actions-view'))).toExist().withTimeout(2000); // await expect(element(by.id('room-actions-view'))).toExist(); // }); - // TODO: test into a jitsi call // it('should NOT navigate to video call', async() => { // await element(by.id('room-actions-video')).tap(); // await waitFor(element(by.id('room-actions-view'))).toExist().withTimeout(2000); // await expect(element(by.id('room-actions-view'))).toExist(); // }); - // TODO: test share room link // it('should NOT navigate to share room', async() => { // await waitFor(element(by.id('room-actions-share'))).toExist(); @@ -190,14 +196,16 @@ describe('Room actions screen', () => { }); describe('Common', () => { - it('should show mentioned messages', async() => { + it('should show mentioned messages', async () => { await element(by.id('room-actions-mentioned')).tap(); - await waitFor(element(by.id('mentioned-messages-view'))).toExist().withTimeout(2000); + await waitFor(element(by.id('mentioned-messages-view'))) + .toExist() + .withTimeout(2000); // await waitFor(element(by.text(` ${ data.random }mention`))).toExist().withTimeout(60000); await backToActions(); }); - it('should show starred message and unstar it', async() => { + it('should show starred message and unstar it', async () => { // Go back to room and send a message await tapBack(); await mockMessage('messageToStar'); @@ -207,24 +215,34 @@ describe('Room actions screen', () => { // Back into Room Actions await element(by.id('room-header')).tap(); - await waitFor(element(by.id('room-actions-view'))).toExist().withTimeout(5000); + await waitFor(element(by.id('room-actions-view'))) + .toExist() + .withTimeout(5000); // Go to starred messages await element(by.id('room-actions-starred')).tap(); - await waitFor(element(by.id('starred-messages-view'))).toExist().withTimeout(2000); - await waitFor(element(by.label(`${ data.random }messageToStar`).withAncestor(by.id('starred-messages-view')))).toExist().withTimeout(60000); + await waitFor(element(by.id('starred-messages-view'))) + .toExist() + .withTimeout(2000); + await waitFor(element(by.label(`${data.random}messageToStar`).withAncestor(by.id('starred-messages-view')))) + .toExist() + .withTimeout(60000); // Unstar message - await element(by.label(`${ data.random }messageToStar`)).atIndex(0).longPress(); + await element(by.label(`${data.random}messageToStar`)) + .atIndex(0) + .longPress(); await expect(element(by.id('action-sheet'))).toExist(); await expect(element(by.id('action-sheet-handle'))).toBeVisible(); await element(by.label('Unstar')).atIndex(0).tap(); - await waitFor(element(by.label(`${ data.random }messageToStar`).withAncestor(by.id('starred-messages-view')))).toBeNotVisible().withTimeout(60000); + await waitFor(element(by.label(`${data.random}messageToStar`).withAncestor(by.id('starred-messages-view')))) + .toBeNotVisible() + .withTimeout(60000); await backToActions(); }); - it('should show pinned message and unpin it', async() => { + it('should show pinned message and unpin it', async () => { // Go back to room and send a message await tapBack(); await mockMessage('messageToPin'); @@ -234,19 +252,29 @@ describe('Room actions screen', () => { // Back into Room Actions await element(by.id('room-header')).tap(); - await waitFor(element(by.id('room-actions-view'))).toExist().withTimeout(5000); + await waitFor(element(by.id('room-actions-view'))) + .toExist() + .withTimeout(5000); await element(by.id('room-actions-scrollview')).scrollTo('bottom'); await waitFor(element(by.id('room-actions-pinned'))).toExist(); await element(by.id('room-actions-pinned')).tap(); - await waitFor(element(by.id('pinned-messages-view'))).toExist().withTimeout(2000); - await waitFor(element(by.label(`${ data.random }messageToPin`).withAncestor(by.id('pinned-messages-view')))).toExist().withTimeout(6000); - await element(by.label(`${ data.random }messageToPin`).withAncestor(by.id('pinned-messages-view'))).atIndex(0).longPress(); + await waitFor(element(by.id('pinned-messages-view'))) + .toExist() + .withTimeout(2000); + await waitFor(element(by.label(`${data.random}messageToPin`).withAncestor(by.id('pinned-messages-view')))) + .toExist() + .withTimeout(6000); + await element(by.label(`${data.random}messageToPin`).withAncestor(by.id('pinned-messages-view'))) + .atIndex(0) + .longPress(); await expect(element(by.id('action-sheet'))).toExist(); await expect(element(by.id('action-sheet-handle'))).toBeVisible(); await element(by.label('Unpin')).atIndex(0).tap(); - await waitFor(element(by.label(`${ data.random }messageToPin`).withAncestor(by.id('pinned-messages-view')))).not.toExist().withTimeout(6000); + await waitFor(element(by.label(`${data.random}messageToPin`).withAncestor(by.id('pinned-messages-view')))) + .not.toExist() + .withTimeout(6000); await backToActions(); }); @@ -270,48 +298,62 @@ describe('Room actions screen', () => { }); describe('Notification', () => { - it('should navigate to notification preference view', async() => { + it('should navigate to notification preference view', async () => { await element(by.id('room-actions-scrollview')).scrollTo('bottom'); - await waitFor(element(by.id('room-actions-notifications'))).toExist().withTimeout(2000); + await waitFor(element(by.id('room-actions-notifications'))) + .toExist() + .withTimeout(2000); await element(by.id('room-actions-notifications')).tap(); - await waitFor(element(by.id('notification-preference-view'))).toExist().withTimeout(2000); + await waitFor(element(by.id('notification-preference-view'))) + .toExist() + .withTimeout(2000); }); - it('should have receive notification option', async() => { + it('should have receive notification option', async () => { await expect(element(by.id('notification-preference-view-receive-notification'))).toExist(); }); - it('should have show unread count option', async() => { + it('should have show unread count option', async () => { await expect(element(by.id('notification-preference-view-unread-count'))).toExist(); }); - it('should have notification alert option', async() => { + it('should have notification alert option', async () => { await expect(element(by.id('notification-preference-view-alert'))).toExist(); }); - it('should have push notification option', async() => { - await waitFor(element(by.id('notification-preference-view-push-notification'))).toExist().withTimeout(4000); + it('should have push notification option', async () => { + await waitFor(element(by.id('notification-preference-view-push-notification'))) + .toExist() + .withTimeout(4000); }); - it('should have notification audio option', async() => { - await waitFor(element(by.id('notification-preference-view-audio'))).toExist().withTimeout(4000); + it('should have notification audio option', async () => { + await waitFor(element(by.id('notification-preference-view-audio'))) + .toExist() + .withTimeout(4000); }); - it('should have notification sound option', async() => { + it('should have notification sound option', async () => { // Ugly hack to scroll on detox await element(by.id('room-actions-scrollview')).scrollTo('bottom'); - await waitFor(element(by.id('notification-preference-view-sound'))).toExist().withTimeout(4000); + await waitFor(element(by.id('notification-preference-view-sound'))) + .toExist() + .withTimeout(4000); }); - it('should have notification duration option', async() => { - await waitFor(element(by.id('notification-preference-view-notification-duration'))).toExist().withTimeout(4000); + it('should have notification duration option', async () => { + await waitFor(element(by.id('notification-preference-view-notification-duration'))) + .toExist() + .withTimeout(4000); }); - it('should have email alert option', async() => { - await waitFor(element(by.id('notification-preference-view-email-alert'))).toExist().withTimeout(4000); + it('should have email alert option', async () => { + await waitFor(element(by.id('notification-preference-view-email-alert'))) + .toExist() + .withTimeout(4000); }); - after(async() => { + after(async () => { await backToActions(); }); }); @@ -322,197 +364,279 @@ describe('Room actions screen', () => { const user = data.users.alternate; - it('should tap on leave channel and raise alert', async() => { + it('should tap on leave channel and raise alert', async () => { await element(by.id('room-actions-scrollview')).scrollTo('bottom'); - await waitFor(element(by.id('room-actions-leave-channel'))).toExist().withTimeout(2000); + await waitFor(element(by.id('room-actions-leave-channel'))) + .toExist() + .withTimeout(2000); await element(by.id('room-actions-leave-channel')).tap(); - await waitFor(element(by.text('Yes, leave it!'))).toExist().withTimeout(2000); + await waitFor(element(by.text('Yes, leave it!'))) + .toExist() + .withTimeout(2000); await element(by.text('Yes, leave it!')).tap(); - await waitFor(element(by.text('You are the last owner. Please set new owner before leaving the room.'))).toExist().withTimeout(8000); + await waitFor(element(by.text('You are the last owner. Please set new owner before leaving the room.'))) + .toExist() + .withTimeout(8000); await element(by.text('OK')).tap(); - await waitFor(element(by.id('room-actions-view'))).toExist().withTimeout(2000); + await waitFor(element(by.id('room-actions-view'))) + .toExist() + .withTimeout(2000); }); - it('should add users to the room', async() => { - await waitFor(element(by.id('room-actions-add-user'))).toExist().withTimeout(4000); + it('should add users to the room', async () => { + await waitFor(element(by.id('room-actions-add-user'))) + .toExist() + .withTimeout(4000); await element(by.id('room-actions-add-user')).tap(); // add rocket.cat const rocketCat = 'rocket.cat'; - await waitFor(element(by.id(`select-users-view-item-${ rocketCat }`))).toExist().withTimeout(10000); - await element(by.id(`select-users-view-item-${ rocketCat }`)).tap(); - await waitFor(element(by.id(`selected-user-${ rocketCat }`))).toExist().withTimeout(5000); + await waitFor(element(by.id(`select-users-view-item-${rocketCat}`))) + .toExist() + .withTimeout(10000); + await element(by.id(`select-users-view-item-${rocketCat}`)).tap(); + await waitFor(element(by.id(`selected-user-${rocketCat}`))) + .toExist() + .withTimeout(5000); - await waitFor(element(by.id('select-users-view-search'))).toExist().withTimeout(4000); + await waitFor(element(by.id('select-users-view-search'))) + .toExist() + .withTimeout(4000); await element(by.id('select-users-view-search')).tap(); await element(by.id('select-users-view-search')).replaceText(user.username); - await waitFor(element(by.id(`select-users-view-item-${ user.username }`))).toExist().withTimeout(10000); - await element(by.id(`select-users-view-item-${ user.username }`)).tap(); - await waitFor(element(by.id(`selected-user-${ user.username }`))).toExist().withTimeout(5000); + await waitFor(element(by.id(`select-users-view-item-${user.username}`))) + .toExist() + .withTimeout(10000); + await element(by.id(`select-users-view-item-${user.username}`)).tap(); + await waitFor(element(by.id(`selected-user-${user.username}`))) + .toExist() + .withTimeout(5000); await element(by.id('selected-users-view-submit')).tap(); await sleep(300); - await waitFor(element(by.id('room-actions-members'))).toExist().withTimeout(10000); + await waitFor(element(by.id('room-actions-members'))) + .toExist() + .withTimeout(10000); await element(by.id('room-actions-members')).tap(); await element(by.id('room-members-view-toggle-status')).tap(); - await waitFor(element(by.id(`room-members-view-item-${ user.username }`))).toExist().withTimeout(60000); + await waitFor(element(by.id(`room-members-view-item-${user.username}`))) + .toExist() + .withTimeout(60000); await backToActions(); }); describe('Room Members', () => { - before(async() => { + before(async () => { await element(by.id('room-actions-members')).tap(); - await waitFor(element(by.id('room-members-view'))).toExist().withTimeout(2000); + await waitFor(element(by.id('room-members-view'))) + .toExist() + .withTimeout(2000); }); - const openActionSheet = async(username) => { - await waitFor(element(by.id(`room-members-view-item-${ username }`))).toExist().withTimeout(5000); - await element(by.id(`room-members-view-item-${ username }`)).tap(); + const openActionSheet = async username => { + await waitFor(element(by.id(`room-members-view-item-${username}`))) + .toExist() + .withTimeout(5000); + await element(by.id(`room-members-view-item-${username}`)).tap(); await sleep(300); await expect(element(by.id('action-sheet'))).toExist(); await expect(element(by.id('action-sheet-handle'))).toBeVisible(); }; - const closeActionSheet = async() => { + const closeActionSheet = async () => { await element(by.id('action-sheet-handle')).swipe('down', 'fast', 0.6); }; - it('should show all users', async() => { + it('should show all users', async () => { await element(by.id('room-members-view-toggle-status')).tap(); - await waitFor(element(by.id(`room-members-view-item-${ user.username }`))).toExist().withTimeout(60000); + await waitFor(element(by.id(`room-members-view-item-${user.username}`))) + .toExist() + .withTimeout(60000); }); - it('should filter user', async() => { - await waitFor(element(by.id(`room-members-view-item-${ user.username }`))).toExist().withTimeout(60000); + it('should filter user', async () => { + await waitFor(element(by.id(`room-members-view-item-${user.username}`))) + .toExist() + .withTimeout(60000); await element(by.id('room-members-view-search')).replaceText('rocket'); - await waitFor(element(by.id(`room-members-view-item-${ user.username }`))).toBeNotVisible().withTimeout(60000); + await waitFor(element(by.id(`room-members-view-item-${user.username}`))) + .toBeNotVisible() + .withTimeout(60000); await element(by.id('room-members-view-search')).tap(); await element(by.id('room-members-view-search')).clearText(''); - await waitFor(element(by.id(`room-members-view-item-${ user.username }`))).toExist().withTimeout(60000); + await waitFor(element(by.id(`room-members-view-item-${user.username}`))) + .toExist() + .withTimeout(60000); }); - it('should remove user from room', async() => { + it('should remove user from room', async () => { await openActionSheet('rocket.cat'); await element(by.label('Remove from room')).atIndex(0).tap(); - await waitFor(element(by.label('Are you sure?'))).toExist().withTimeout(5000); + await waitFor(element(by.label('Are you sure?'))) + .toExist() + .withTimeout(5000); await element(by.label('Yes, remove user!').and(by.type('_UIAlertControllerActionView'))).tap(); - await waitFor(element(by.id('room-members-view-item-rocket.cat'))).toBeNotVisible().withTimeout(60000); + await waitFor(element(by.id('room-members-view-item-rocket.cat'))) + .toBeNotVisible() + .withTimeout(60000); }); - it('should clear search', async() => { + it('should clear search', async () => { await element(by.id('room-members-view-search')).tap(); await element(by.id('room-members-view-search')).clearText(''); - await waitFor(element(by.id(`room-members-view-item-${ user.username }`))).toExist().withTimeout(60000); + await waitFor(element(by.id(`room-members-view-item-${user.username}`))) + .toExist() + .withTimeout(60000); }); - it('should set/remove as owner', async() => { + it('should set/remove as owner', async () => { await openActionSheet(user.username); await element(by.id('action-sheet-set-owner')).tap(); await waitForToast(); await openActionSheet(user.username); - await waitFor(element(by.id('action-sheet-set-owner-checked'))).toBeVisible().withTimeout(6000); + await waitFor(element(by.id('action-sheet-set-owner-checked'))) + .toBeVisible() + .withTimeout(6000); await element(by.id('action-sheet-set-owner')).tap(); await waitForToast(); await openActionSheet(user.username); - await waitFor(element(by.id('action-sheet-set-owner-unchecked'))).toBeVisible().withTimeout(60000); + await waitFor(element(by.id('action-sheet-set-owner-unchecked'))) + .toBeVisible() + .withTimeout(60000); await closeActionSheet(); }); - it('should set/remove as leader', async() => { + it('should set/remove as leader', async () => { await openActionSheet(user.username); await element(by.id('action-sheet-set-leader')).tap(); await waitForToast(); await openActionSheet(user.username); - await waitFor(element(by.id('action-sheet-set-leader-checked'))).toBeVisible().withTimeout(6000); + await waitFor(element(by.id('action-sheet-set-leader-checked'))) + .toBeVisible() + .withTimeout(6000); await element(by.id('action-sheet-set-leader')).tap(); await waitForToast(); await openActionSheet(user.username); - await waitFor(element(by.id('action-sheet-set-owner-unchecked'))).toBeVisible().withTimeout(60000); + await waitFor(element(by.id('action-sheet-set-owner-unchecked'))) + .toBeVisible() + .withTimeout(60000); await closeActionSheet(); }); - it('should set/remove as moderator', async() => { + it('should set/remove as moderator', async () => { await openActionSheet(user.username); await element(by.id('action-sheet-set-moderator')).tap(); await waitForToast(); await openActionSheet(user.username); - await waitFor(element(by.id('action-sheet-set-moderator-checked'))).toBeVisible().withTimeout(6000); + await waitFor(element(by.id('action-sheet-set-moderator-checked'))) + .toBeVisible() + .withTimeout(6000); await element(by.id('action-sheet-set-moderator')).tap(); await waitForToast(); await openActionSheet(user.username); - await waitFor(element(by.id('action-sheet-set-moderator-unchecked'))).toBeVisible().withTimeout(60000); + await waitFor(element(by.id('action-sheet-set-moderator-unchecked'))) + .toBeVisible() + .withTimeout(60000); await closeActionSheet(); }); - it('should set/remove as mute', async() => { + it('should set/remove as mute', async () => { await openActionSheet(user.username); await element(by.label('Mute')).atIndex(0).tap(); - await waitFor(element(by.label('Are you sure?'))).toExist().withTimeout(5000); + await waitFor(element(by.label('Are you sure?'))) + .toExist() + .withTimeout(5000); await element(by.label('Mute').and(by.type('_UIAlertControllerActionView'))).tap(); await waitForToast(); await openActionSheet(user.username); await element(by.label('Unmute')).atIndex(0).tap(); - await waitFor(element(by.label('Are you sure?'))).toExist().withTimeout(5000); + await waitFor(element(by.label('Are you sure?'))) + .toExist() + .withTimeout(5000); await element(by.label('Unmute').and(by.type('_UIAlertControllerActionView'))).tap(); await waitForToast(); await openActionSheet(user.username); // Tests if Remove as mute worked - await waitFor(element(by.label('Mute'))).toExist().withTimeout(5000); + await waitFor(element(by.label('Mute'))) + .toExist() + .withTimeout(5000); await closeActionSheet(); }); - it('should ignore user', async() => { - const message = `${ data.random }ignoredmessagecontent`; - const channelName = `#${ data.groups.private.name }`; + it('should ignore user', async () => { + const message = `${data.random}ignoredmessagecontent`; + const channelName = `#${data.groups.private.name}`; await sendMessage(user, channelName, message); await openActionSheet(user.username); await element(by.label('Ignore')).atIndex(0).tap(); await waitForToast(); await backToActions(); await tapBack(); - await waitFor(element(by.id('room-view'))).toExist().withTimeout(60000); - await waitFor(element(by.label('Message ignored. Tap to display it.')).atIndex(0)).toExist().withTimeout(60000); + await waitFor(element(by.id('room-view'))) + .toExist() + .withTimeout(60000); + await waitFor(element(by.label('Message ignored. Tap to display it.')).atIndex(0)) + .toExist() + .withTimeout(60000); await element(by.label('Message ignored. Tap to display it.')).atIndex(0).tap(); - await waitFor(element(by.label(message)).atIndex(0)).toExist().withTimeout(60000); + await waitFor(element(by.label(message)).atIndex(0)) + .toExist() + .withTimeout(60000); await element(by.label(message)).atIndex(0).tap(); }); - it('should navigate to direct message', async() => { + it('should navigate to direct message', async () => { await element(by.id('room-header')).tap(); - await waitFor(element(by.id('room-actions-view'))).toExist().withTimeout(5000); + await waitFor(element(by.id('room-actions-view'))) + .toExist() + .withTimeout(5000); await element(by.id('room-actions-members')).tap(); - await waitFor(element(by.id('room-members-view'))).toExist().withTimeout(2000); + await waitFor(element(by.id('room-members-view'))) + .toExist() + .withTimeout(2000); await element(by.id('room-members-view-toggle-status')).tap(); - await waitFor(element(by.id(`room-members-view-item-${ user.username }`))).toExist().withTimeout(60000); + await waitFor(element(by.id(`room-members-view-item-${user.username}`))) + .toExist() + .withTimeout(60000); await openActionSheet(user.username); await element(by.label('Direct message')).atIndex(0).tap(); - await waitFor(element(by.id('room-view'))).toExist().withTimeout(60000); - await waitFor(element(by.id(`room-view-title-${ user.username }`))).toExist().withTimeout(60000); + await waitFor(element(by.id('room-view'))) + .toExist() + .withTimeout(60000); + await waitFor(element(by.id(`room-view-title-${user.username}`))) + .toExist() + .withTimeout(60000); await tapBack(); - await waitFor(element(by.id('rooms-list-view'))).toExist().withTimeout(2000); + await waitFor(element(by.id('rooms-list-view'))) + .toExist() + .withTimeout(2000); }); }); }); describe('Direct', () => { - before(async() => { + before(async () => { await navigateToRoomActions('d'); }); - it('should block/unblock user', async() => { + it('should block/unblock user', async () => { await waitFor(element(by.id('room-actions-block-user'))).toExist(); await element(by.id('room-actions-block-user')).tap(); - await waitFor(element(by.label('Unblock user'))).toExist().withTimeout(60000); + await waitFor(element(by.label('Unblock user'))) + .toExist() + .withTimeout(60000); await element(by.id('room-actions-block-user')).tap(); - await waitFor(element(by.label('Block user'))).toExist().withTimeout(60000); + await waitFor(element(by.label('Block user'))) + .toExist() + .withTimeout(60000); }); }); }); diff --git a/e2e/tests/room/04-discussion.spec.js b/e2e/tests/room/04-discussion.spec.js index afd289203..e3781262f 100644 --- a/e2e/tests/room/04-discussion.spec.js +++ b/e2e/tests/room/04-discussion.spec.js @@ -1,132 +1,172 @@ -const { - navigateToLogin, login, mockMessage, tapBack, searchRoom -} = require('../../helpers/app'); +const { navigateToLogin, login, mockMessage, tapBack, searchRoom } = require('../../helpers/app'); const data = require('../../data'); const channel = data.groups.private.name; -const navigateToRoom = async() => { +const navigateToRoom = async () => { await searchRoom(channel); - await element(by.id(`rooms-list-view-item-${ channel }`)).tap(); - await waitFor(element(by.id('room-view'))).toBeVisible().withTimeout(5000); + await element(by.id(`rooms-list-view-item-${channel}`)).tap(); + await waitFor(element(by.id('room-view'))) + .toBeVisible() + .withTimeout(5000); }; describe('Discussion', () => { - before(async() => { + before(async () => { await device.launchApp({ permissions: { notifications: 'YES' }, newInstance: true, delete: true }); await navigateToLogin(); await login(data.users.regular.username, data.users.regular.password); }); - it('should create discussion from NewMessageView', async() => { - const discussionName = `${ data.random } Discussion NewMessageView`; + it('should create discussion from NewMessageView', async () => { + const discussionName = `${data.random} Discussion NewMessageView`; await element(by.id('rooms-list-view-create-channel')).tap(); - await waitFor(element(by.id('new-message-view'))).toExist().withTimeout(2000); + await waitFor(element(by.id('new-message-view'))) + .toExist() + .withTimeout(2000); await element(by.label('Create Discussion')).atIndex(0).tap(); - await waitFor(element(by.id('create-discussion-view'))).toExist().withTimeout(60000); + await waitFor(element(by.id('create-discussion-view'))) + .toExist() + .withTimeout(60000); await expect(element(by.id('create-discussion-view'))).toExist(); await element(by.label('Select a Channel...')).tap(); - await element(by.id('multi-select-search')).replaceText(`${ channel }`); - await waitFor(element(by.id(`multi-select-item-${ channel }`))).toExist().withTimeout(10000); - await element(by.id(`multi-select-item-${ channel }`)).tap(); + await element(by.id('multi-select-search')).replaceText(`${channel}`); + await waitFor(element(by.id(`multi-select-item-${channel}`))) + .toExist() + .withTimeout(10000); + await element(by.id(`multi-select-item-${channel}`)).tap(); await element(by.id('multi-select-discussion-name')).replaceText(discussionName); - await waitFor(element(by.id('create-discussion-submit'))).toExist().withTimeout(10000); + await waitFor(element(by.id('create-discussion-submit'))) + .toExist() + .withTimeout(10000); await element(by.id('create-discussion-submit')).tap(); - await waitFor(element(by.id('room-view'))).toExist().withTimeout(10000); - await waitFor(element(by.id(`room-view-title-${ discussionName }`))).toExist().withTimeout(5000); + await waitFor(element(by.id('room-view'))) + .toExist() + .withTimeout(10000); + await waitFor(element(by.id(`room-view-title-${discussionName}`))) + .toExist() + .withTimeout(5000); await tapBack(); - await waitFor(element(by.id(`rooms-list-view-item-${ discussionName }`))).toExist().withTimeout(5000); + await waitFor(element(by.id(`rooms-list-view-item-${discussionName}`))) + .toExist() + .withTimeout(5000); }); - it('should create discussion from action button', async() => { - const discussionName = `${ data.random } Discussion Action Button`; + it('should create discussion from action button', async () => { + const discussionName = `${data.random} Discussion Action Button`; await navigateToRoom(); await element(by.id('messagebox-actions')).tap(); - await waitFor(element(by.id('action-sheet'))).toExist().withTimeout(2000); + await waitFor(element(by.id('action-sheet'))) + .toExist() + .withTimeout(2000); await element(by.label('Create Discussion')).atIndex(0).tap(); - await waitFor(element(by.id('create-discussion-view'))).toExist().withTimeout(2000); + await waitFor(element(by.id('create-discussion-view'))) + .toExist() + .withTimeout(2000); await element(by.id('multi-select-discussion-name')).replaceText(discussionName); - await waitFor(element(by.id('create-discussion-submit'))).toExist().withTimeout(10000); + await waitFor(element(by.id('create-discussion-submit'))) + .toExist() + .withTimeout(10000); await element(by.id('create-discussion-submit')).tap(); - await waitFor(element(by.id('room-view'))).toExist().withTimeout(10000); - await waitFor(element(by.id(`room-view-title-${ discussionName }`))).toExist().withTimeout(5000); + await waitFor(element(by.id('room-view'))) + .toExist() + .withTimeout(10000); + await waitFor(element(by.id(`room-view-title-${discussionName}`))) + .toExist() + .withTimeout(5000); }); describe('Create Discussion from action sheet', () => { - it('should send a message', async() => { - await waitFor(element(by.id('messagebox'))).toBeVisible().withTimeout(60000); + it('should send a message', async () => { + await waitFor(element(by.id('messagebox'))) + .toBeVisible() + .withTimeout(60000); await mockMessage('message'); }); - it('should create discussion', async() => { - const discussionName = `${ data.random }message`; + it('should create discussion', async () => { + const discussionName = `${data.random}message`; await element(by.label(discussionName)).atIndex(0).longPress(); - await waitFor(element(by.id('action-sheet'))).toExist().withTimeout(2000); + await waitFor(element(by.id('action-sheet'))) + .toExist() + .withTimeout(2000); await element(by.label('Start a Discussion')).atIndex(0).tap(); - await waitFor(element(by.id('create-discussion-view'))).toExist().withTimeout(2000); + await waitFor(element(by.id('create-discussion-view'))) + .toExist() + .withTimeout(2000); await element(by.id('create-discussion-submit')).tap(); - await waitFor(element(by.id('room-view'))).toExist().withTimeout(10000); - await waitFor(element(by.id(`room-view-title-${ discussionName }`))).toExist().withTimeout(5000); + await waitFor(element(by.id('room-view'))) + .toExist() + .withTimeout(10000); + await waitFor(element(by.id(`room-view-title-${discussionName}`))) + .toExist() + .withTimeout(5000); }); }); describe('Check RoomActionsView render', () => { - it('should navigete to RoomActionsView', async() => { - await waitFor(element(by.id('room-header'))).toBeVisible().withTimeout(5000); + it('should navigete to RoomActionsView', async () => { + await waitFor(element(by.id('room-header'))) + .toBeVisible() + .withTimeout(5000); await element(by.id('room-header')).tap(); - await waitFor(element(by.id('room-actions-view'))).toBeVisible().withTimeout(5000); + await waitFor(element(by.id('room-actions-view'))) + .toBeVisible() + .withTimeout(5000); }); - it('should have room actions screen', async() => { + it('should have room actions screen', async () => { await expect(element(by.id('room-actions-view'))).toBeVisible(); }); - it('should have info', async() => { + it('should have info', async () => { await expect(element(by.id('room-actions-info'))).toBeVisible(); }); - it('should have members', async() => { + it('should have members', async () => { await expect(element(by.id('room-actions-members'))).toBeVisible(); }); - it('should have files', async() => { + it('should have files', async () => { await expect(element(by.id('room-actions-files'))).toBeVisible(); }); - it('should have mentions', async() => { + it('should have mentions', async () => { await expect(element(by.id('room-actions-mentioned'))).toBeVisible(); }); - it('should have starred', async() => { + it('should have starred', async () => { await expect(element(by.id('room-actions-starred'))).toBeVisible(); }); - it('should have share', async() => { + it('should have share', async () => { await element(by.id('room-actions-scrollview')).swipe('up'); await expect(element(by.id('room-actions-share'))).toBeVisible(); }); - it('should have pinned', async() => { + it('should have pinned', async () => { await expect(element(by.id('room-actions-pinned'))).toBeVisible(); }); - it('should not have notifications', async() => { + it('should not have notifications', async () => { await expect(element(by.id('room-actions-notifications'))).toBeVisible(); }); - it('should not have leave channel', async() => { + it('should not have leave channel', async () => { await expect(element(by.id('room-actions-leave-channel'))).toBeVisible(); }); - it('should navigate to RoomActionView', async() => { + it('should navigate to RoomActionView', async () => { await element(by.id('room-actions-scrollview')).swipe('down'); await expect(element(by.id('room-actions-info'))).toBeVisible(); await element(by.id('room-actions-info')).tap(); - await waitFor(element(by.id('room-info-view'))).toExist().withTimeout(60000); + await waitFor(element(by.id('room-info-view'))) + .toExist() + .withTimeout(60000); await expect(element(by.id('room-info-view'))).toExist(); }); - it('should have edit button', async() => { + it('should have edit button', async () => { await expect(element(by.id('room-info-view-edit-button'))).toBeVisible(); }); }); diff --git a/e2e/tests/room/05-threads.spec.js b/e2e/tests/room/05-threads.spec.js index e6c73c129..d67889284 100644 --- a/e2e/tests/room/05-threads.spec.js +++ b/e2e/tests/room/05-threads.spec.js @@ -1,62 +1,64 @@ const data = require('../../data'); -const { - navigateToLogin, login, mockMessage, tapBack, sleep, searchRoom, dismissReviewNag -} = require('../../helpers/app'); +const { navigateToLogin, login, mockMessage, tapBack, sleep, searchRoom, dismissReviewNag } = require('../../helpers/app'); async function navigateToRoom(roomName) { await device.launchApp({ permissions: { notifications: 'YES' }, delete: true }); await navigateToLogin(); await login(data.users.regular.username, data.users.regular.password); - await searchRoom(`${ roomName }`); - await element(by.id(`rooms-list-view-item-${ roomName }`)).tap(); - await waitFor(element(by.id('room-view'))).toBeVisible().withTimeout(5000); + await searchRoom(`${roomName}`); + await element(by.id(`rooms-list-view-item-${roomName}`)).tap(); + await waitFor(element(by.id('room-view'))) + .toBeVisible() + .withTimeout(5000); } describe('Threads', () => { const mainRoom = data.groups.private.name; - before(async() => { + before(async () => { await navigateToRoom(mainRoom); }); describe('Render', () => { - it('should have room screen', async() => { + it('should have room screen', async () => { await expect(element(by.id('room-view'))).toExist(); - await waitFor(element(by.id(`room-view-title-${ mainRoom }`))).toExist().withTimeout(5000); + await waitFor(element(by.id(`room-view-title-${mainRoom}`))) + .toExist() + .withTimeout(5000); }); // Render - Header describe('Header', () => { - it('should have actions button ', async() => { + it('should have actions button ', async () => { await expect(element(by.id('room-header'))).toExist(); }); - it('should have threads button ', async() => { + it('should have threads button ', async () => { await expect(element(by.id('room-view-header-threads'))).toExist(); }); }); // Render - Messagebox describe('Messagebox', () => { - it('should have messagebox', async() => { + it('should have messagebox', async () => { await expect(element(by.id('messagebox'))).toExist(); }); - it('should have open emoji button', async() => { + it('should have open emoji button', async () => { if (device.getPlatform() === 'android') { await expect(element(by.id('messagebox-open-emoji'))).toExist(); } }); - it('should have message input', async() => { + it('should have message input', async () => { await expect(element(by.id('messagebox-input'))).toExist(); }); - it('should have audio button', async() => { + it('should have audio button', async () => { await expect(element(by.id('messagebox-send-audio'))).toExist(); }); - it('should have actions button', async() => { + it('should have actions button', async () => { await expect(element(by.id('messagebox-actions'))).toExist(); }); }); @@ -64,8 +66,8 @@ describe('Threads', () => { describe('Usage', () => { describe('Thread', () => { - const thread = `${ data.random }thread`; - it('should create thread', async() => { + const thread = `${data.random}thread`; + it('should create thread', async () => { await mockMessage('thread'); await element(by.label(thread)).atIndex(0).longPress(); await expect(element(by.id('action-sheet'))).toExist(); @@ -74,102 +76,156 @@ describe('Threads', () => { await element(by.label('Reply in Thread')).atIndex(0).tap(); await element(by.id('messagebox-input')).typeText('replied'); await element(by.id('messagebox-send-message')).tap(); - await waitFor(element(by.id(`message-thread-button-${ thread }`))).toExist().withTimeout(5000); - await expect(element(by.id(`message-thread-button-${ thread }`))).toExist(); + await waitFor(element(by.id(`message-thread-button-${thread}`))) + .toExist() + .withTimeout(5000); + await expect(element(by.id(`message-thread-button-${thread}`))).toExist(); }); - it('should navigate to thread from button', async() => { - await element(by.id(`message-thread-button-${ thread }`)).tap(); - await waitFor(element(by.id('room-view'))).toExist().withTimeout(5000); - await waitFor(element(by.id(`room-view-title-${ thread }`))).toExist().withTimeout(5000); - await expect(element(by.id(`room-view-title-${ thread }`))).toExist(); + it('should navigate to thread from button', async () => { + await element(by.id(`message-thread-button-${thread}`)).tap(); + await waitFor(element(by.id('room-view'))) + .toExist() + .withTimeout(5000); + await waitFor(element(by.id(`room-view-title-${thread}`))) + .toExist() + .withTimeout(5000); + await expect(element(by.id(`room-view-title-${thread}`))).toExist(); await tapBack(); }); - it('should toggle follow thread', async() => { - await element(by.id(`message-thread-button-${ thread }`)).tap(); - await waitFor(element(by.id('room-view'))).toExist().withTimeout(5000); - await waitFor(element(by.id(`room-view-title-${ thread }`))).toExist().withTimeout(5000); - await expect(element(by.id(`room-view-title-${ thread }`))).toExist(); + it('should toggle follow thread', async () => { + await element(by.id(`message-thread-button-${thread}`)).tap(); + await waitFor(element(by.id('room-view'))) + .toExist() + .withTimeout(5000); + await waitFor(element(by.id(`room-view-title-${thread}`))) + .toExist() + .withTimeout(5000); + await expect(element(by.id(`room-view-title-${thread}`))).toExist(); await element(by.id('room-view-header-unfollow')).tap(); - await waitFor(element(by.id('room-view-header-follow'))).toExist().withTimeout(60000); + await waitFor(element(by.id('room-view-header-follow'))) + .toExist() + .withTimeout(60000); await expect(element(by.id('room-view-header-follow'))).toExist(); await element(by.id('room-view-header-follow')).tap(); - await waitFor(element(by.id('room-view-header-unfollow'))).toExist().withTimeout(60000); + await waitFor(element(by.id('room-view-header-unfollow'))) + .toExist() + .withTimeout(60000); await expect(element(by.id('room-view-header-unfollow'))).toExist(); }); - it('should send message in thread only', async() => { + it('should send message in thread only', async () => { const messageText = 'threadonly'; await mockMessage(messageText, true); await tapBack(); - await waitFor(element(by.id('room-header').and(by.label(`${ mainRoom }`)))).toBeVisible().withTimeout(2000); - await waitFor(element(by.id('room-header').and(by.label(`${ data.random }thread`)))).toBeNotVisible().withTimeout(2000); + await waitFor(element(by.id('room-header').and(by.label(`${mainRoom}`)))) + .toBeVisible() + .withTimeout(2000); + await waitFor(element(by.id('room-header').and(by.label(`${data.random}thread`)))) + .toBeNotVisible() + .withTimeout(2000); await sleep(500); // TODO: Find a better way to wait for the animation to finish and the messagebox-input to be available and usable :( - await waitFor(element(by.label(`${ data.random }${ messageText }`)).atIndex(0)).toNotExist().withTimeout(2000); + await waitFor(element(by.label(`${data.random}${messageText}`)).atIndex(0)) + .toNotExist() + .withTimeout(2000); }); - it('should mark send to channel and show on main channel', async() => { + it('should mark send to channel and show on main channel', async () => { const messageText = 'sendToChannel'; - await element(by.id(`message-thread-button-${ thread }`)).tap(); - await waitFor(element(by.id('messagebox-input-thread'))).toExist().withTimeout(5000); + await element(by.id(`message-thread-button-${thread}`)).tap(); + await waitFor(element(by.id('messagebox-input-thread'))) + .toExist() + .withTimeout(5000); await element(by.id('messagebox-input-thread')).typeText(messageText); await element(by.id('messagebox-send-to-channel')).tap(); await element(by.id('messagebox-send-message')).tap(); await tapBack(); - await waitFor(element(by.id('room-header').and(by.label(`${ mainRoom }`)))).toBeVisible().withTimeout(2000); - await waitFor(element(by.id('room-header').and(by.label(`${ data.random }thread`)))).toBeNotVisible().withTimeout(2000); + await waitFor(element(by.id('room-header').and(by.label(`${mainRoom}`)))) + .toBeVisible() + .withTimeout(2000); + await waitFor(element(by.id('room-header').and(by.label(`${data.random}thread`)))) + .toBeNotVisible() + .withTimeout(2000); await sleep(500); // TODO: Find a better way to wait for the animation to finish and the messagebox-input to be available and usable :( - await waitFor(element(by.label(messageText)).atIndex(0)).toExist().withTimeout(2000); + await waitFor(element(by.label(messageText)).atIndex(0)) + .toExist() + .withTimeout(2000); }); - it('should navigate to thread from thread name', async() => { + it('should navigate to thread from thread name', async () => { const messageText = 'navthreadname'; await mockMessage('dummymessagebetweenthethread'); await dismissReviewNag(); // TODO: Create a proper test for this elsewhere. - await element(by.id(`message-thread-button-${ thread }`)).tap(); - await waitFor(element(by.id('messagebox-input-thread'))).toExist().withTimeout(5000); + await element(by.id(`message-thread-button-${thread}`)).tap(); + await waitFor(element(by.id('messagebox-input-thread'))) + .toExist() + .withTimeout(5000); await element(by.id('messagebox-input-thread')).typeText(messageText); await element(by.id('messagebox-send-to-channel')).tap(); await element(by.id('messagebox-send-message')).tap(); await tapBack(); - await waitFor(element(by.id('room-header').and(by.label(`${ mainRoom }`)))).toBeVisible().withTimeout(2000); - await waitFor(element(by.id('room-header').and(by.label(`${ data.random }thread`)))).toBeNotVisible().withTimeout(2000); - await waitFor(element(by.id(`message-thread-replied-on-${ thread }`))).toBeVisible().withTimeout(2000); - await element(by.id(`message-thread-replied-on-${ thread }`)).tap(); - await waitFor(element(by.id(`room-view-title-${ thread }`))).toExist().withTimeout(5000); - await expect(element(by.id(`room-view-title-${ thread }`))).toExist(); + await waitFor(element(by.id('room-header').and(by.label(`${mainRoom}`)))) + .toBeVisible() + .withTimeout(2000); + await waitFor(element(by.id('room-header').and(by.label(`${data.random}thread`)))) + .toBeNotVisible() + .withTimeout(2000); + await waitFor(element(by.id(`message-thread-replied-on-${thread}`))) + .toBeVisible() + .withTimeout(2000); + await element(by.id(`message-thread-replied-on-${thread}`)).tap(); + await waitFor(element(by.id(`room-view-title-${thread}`))) + .toExist() + .withTimeout(5000); + await expect(element(by.id(`room-view-title-${thread}`))).toExist(); await sleep(2000); await tapBack(); }); - it('should navigate to thread from threads view', async() => { - await waitFor(element(by.id('room-view-header-threads'))).toExist().withTimeout(1000); + it('should navigate to thread from threads view', async () => { + await waitFor(element(by.id('room-view-header-threads'))) + .toExist() + .withTimeout(1000); await element(by.id('room-view-header-threads')).tap(); - await waitFor(element(by.id('thread-messages-view'))).toExist().withTimeout(5000); - await element(by.id(`thread-messages-view-${ thread }`)).atIndex(0).tap(); - await waitFor(element(by.id(`room-view-title-${ thread }`))).toExist().withTimeout(5000); - await expect(element(by.id(`room-view-title-${ thread }`))).toExist(); + await waitFor(element(by.id('thread-messages-view'))) + .toExist() + .withTimeout(5000); + await element(by.id(`thread-messages-view-${thread}`)) + .atIndex(0) + .tap(); + await waitFor(element(by.id(`room-view-title-${thread}`))) + .toExist() + .withTimeout(5000); + await expect(element(by.id(`room-view-title-${thread}`))).toExist(); await tapBack(); - await waitFor(element(by.id('thread-messages-view'))).toExist().withTimeout(5000); + await waitFor(element(by.id('thread-messages-view'))) + .toExist() + .withTimeout(5000); await expect(element(by.id('thread-messages-view'))).toExist(); await tapBack(); }); - it('should draft thread message', async() => { - await element(by.id(`message-thread-button-${ thread }`)).tap(); - await waitFor(element(by.id(`room-view-title-${ thread }`))).toExist().withTimeout(5000); - await element(by.id('messagebox-input-thread')).typeText(`${ thread }draft`); + it('should draft thread message', async () => { + await element(by.id(`message-thread-button-${thread}`)).tap(); + await waitFor(element(by.id(`room-view-title-${thread}`))) + .toExist() + .withTimeout(5000); + await element(by.id('messagebox-input-thread')).typeText(`${thread}draft`); await tapBack(); - await element(by.id(`message-thread-button-${ thread }`)).tap(); - await waitFor(element(by.id(`room-view-title-${ thread }`))).toExist().withTimeout(5000); - await expect(element(by.id('messagebox-input-thread'))).toHaveText(`${ thread }draft`); + await element(by.id(`message-thread-button-${thread}`)).tap(); + await waitFor(element(by.id(`room-view-title-${thread}`))) + .toExist() + .withTimeout(5000); + await expect(element(by.id('messagebox-input-thread'))).toHaveText(`${thread}draft`); await element(by.id('messagebox-input-thread')).clearText(); await tapBack(); - await element(by.id(`message-thread-button-${ thread }`)).tap(); - await waitFor(element(by.id(`room-view-title-${ thread }`))).toExist().withTimeout(5000); + await element(by.id(`message-thread-button-${thread}`)).tap(); + await waitFor(element(by.id(`room-view-title-${thread}`))) + .toExist() + .withTimeout(5000); await expect(element(by.id('messagebox-input-thread'))).toHaveText(''); }); }); diff --git a/e2e/tests/room/06-createdmgroup.spec.js b/e2e/tests/room/06-createdmgroup.spec.js index bd2be06ab..380cb7469 100644 --- a/e2e/tests/room/06-createdmgroup.spec.js +++ b/e2e/tests/room/06-createdmgroup.spec.js @@ -1,49 +1,55 @@ const data = require('../../data'); -const { - navigateToLogin, login -} = require('../../helpers/app'); - - +const { navigateToLogin, login } = require('../../helpers/app'); describe('Group DM', () => { - before(async() => { + before(async () => { await device.launchApp({ permissions: { notifications: 'YES' }, delete: true }); await navigateToLogin(); await login(data.users.regular.username, data.users.regular.password); }); describe('Create Group DM', () => { - before(async() => { + before(async () => { await element(by.id('rooms-list-view-create-channel')).tap(); }); describe('Render', () => { - it('should have new message screen', async() => { - await waitFor(element(by.id('new-message-view'))).toBeVisible().withTimeout(2000); + it('should have new message screen', async () => { + await waitFor(element(by.id('new-message-view'))) + .toBeVisible() + .withTimeout(2000); }); - it('should have search input', async() => { - await waitFor(element(by.id('new-message-view-search'))).toBeVisible().withTimeout(2000); + it('should have search input', async () => { + await waitFor(element(by.id('new-message-view-search'))) + .toBeVisible() + .withTimeout(2000); }); }); describe('Usage', () => { - it('should navigate to create DM', async() => { + it('should navigate to create DM', async () => { await element(by.label('Create Direct Messages')).tap(); }); - it('should add users', async() => { + it('should add users', async () => { await element(by.id('select-users-view-search')).replaceText('rocket.cat'); - await waitFor(element(by.id('select-users-view-item-rocket.cat'))).toBeVisible().withTimeout(10000); + await waitFor(element(by.id('select-users-view-item-rocket.cat'))) + .toBeVisible() + .withTimeout(10000); await element(by.id('select-users-view-item-rocket.cat')).tap(); await element(by.id('select-users-view-search')).replaceText(data.users.existing.username); - await waitFor(element(by.id(`select-users-view-item-${ data.users.existing.username }`))).toBeVisible().withTimeout(10000); - await element(by.id(`select-users-view-item-${ data.users.existing.username }`)).tap(); + await waitFor(element(by.id(`select-users-view-item-${data.users.existing.username}`))) + .toBeVisible() + .withTimeout(10000); + await element(by.id(`select-users-view-item-${data.users.existing.username}`)).tap(); await element(by.id('selected-users-view-submit')).tap(); }); - it('check Group DM exist', async() => { - await waitFor(element(by.id(`room-view-title-${ data.users.existing.username }, rocket.cat`))).toExist().withTimeout(10000); + it('check Group DM exist', async () => { + await waitFor(element(by.id(`room-view-title-${data.users.existing.username}, rocket.cat`))) + .toExist() + .withTimeout(10000); }); }); }); diff --git a/e2e/tests/room/07-markasunread.spec.js b/e2e/tests/room/07-markasunread.spec.js index 82920ba84..28b70fbad 100644 --- a/e2e/tests/room/07-markasunread.spec.js +++ b/e2e/tests/room/07-markasunread.spec.js @@ -1,19 +1,19 @@ const data = require('../../data'); -const { - navigateToLogin, login, searchRoom, sleep -} = require('../../helpers/app'); +const { navigateToLogin, login, searchRoom, sleep } = require('../../helpers/app'); const { sendMessage } = require('../../helpers/data_setup'); async function navigateToRoom(user) { - await searchRoom(`${ user }`); - await element(by.id(`rooms-list-view-item-${ user }`)).tap(); - await waitFor(element(by.id('room-view'))).toBeVisible().withTimeout(5000); + await searchRoom(`${user}`); + await element(by.id(`rooms-list-view-item-${user}`)).tap(); + await waitFor(element(by.id('room-view'))) + .toBeVisible() + .withTimeout(5000); } describe('Mark as unread', () => { const user = data.users.alternate.username; - before(async() => { + before(async () => { await device.launchApp({ permissions: { notifications: 'YES' }, delete: true }); await navigateToLogin(); await login(data.users.regular.username, data.users.regular.password); @@ -22,18 +22,24 @@ describe('Mark as unread', () => { describe('Usage', () => { describe('Mark message as unread', () => { - it('should mark message as unread', async() => { - const message = `${ data.random }message-mark-as-unread`; - const channelName = `@${ data.users.regular.username }`; + it('should mark message as unread', async () => { + const message = `${data.random}message-mark-as-unread`; + const channelName = `@${data.users.regular.username}`; await sendMessage(data.users.alternate, channelName, message); - await waitFor(element(by.label(message)).atIndex(0)).toExist().withTimeout(30000); + await waitFor(element(by.label(message)).atIndex(0)) + .toExist() + .withTimeout(30000); await sleep(300); await element(by.label(message)).atIndex(0).longPress(); - await waitFor(element(by.id('action-sheet-handle'))).toBeVisible().withTimeout(3000); + await waitFor(element(by.id('action-sheet-handle'))) + .toBeVisible() + .withTimeout(3000); await element(by.id('action-sheet-handle')).swipe('up', 'fast', 0.5); await element(by.label('Mark Unread')).atIndex(0).tap(); - await waitFor(element(by.id('rooms-list-view'))).toExist().withTimeout(5000); - await expect(element(by.id(`rooms-list-view-item-${ data.users.alternate.username }`))).toExist(); + await waitFor(element(by.id('rooms-list-view'))) + .toExist() + .withTimeout(5000); + await expect(element(by.id(`rooms-list-view-item-${data.users.alternate.username}`))).toExist(); }); }); }); diff --git a/e2e/tests/room/08-roominfo.spec.js b/e2e/tests/room/08-roominfo.spec.js index 8786c3761..c3f20a1be 100644 --- a/e2e/tests/room/08-roominfo.spec.js +++ b/e2e/tests/room/08-roominfo.spec.js @@ -1,7 +1,5 @@ const data = require('../../data'); -const { - navigateToLogin, login, tapBack, sleep, searchRoom -} = require('../../helpers/app'); +const { navigateToLogin, login, tapBack, sleep, searchRoom } = require('../../helpers/app'); const privateRoomName = data.groups.private.name; @@ -13,12 +11,18 @@ async function navigateToRoomInfo(type) { room = privateRoomName; } await searchRoom(room); - await element(by.id(`rooms-list-view-item-${ room }`)).tap(); - await waitFor(element(by.id('room-view'))).toExist().withTimeout(2000); + await element(by.id(`rooms-list-view-item-${room}`)).tap(); + await waitFor(element(by.id('room-view'))) + .toExist() + .withTimeout(2000); await element(by.id('room-header')).tap(); - await waitFor(element(by.id('room-actions-view'))).toExist().withTimeout(5000); + await waitFor(element(by.id('room-actions-view'))) + .toExist() + .withTimeout(5000); await element(by.id('room-actions-info')).tap(); - await waitFor(element(by.id('room-info-view'))).toExist().withTimeout(2000); + await waitFor(element(by.id('room-info-view'))) + .toExist() + .withTimeout(2000); } async function waitForToast() { @@ -30,23 +34,23 @@ async function waitForToast() { } describe('Room info screen', () => { - before(async() => { + before(async () => { await device.launchApp({ permissions: { notifications: 'YES' }, delete: true }); await navigateToLogin(); await login(data.users.regular.username, data.users.regular.password); }); describe('Direct', () => { - before(async() => { + before(async () => { await navigateToRoomInfo('d'); }); - it('should navigate to room info', async() => { + it('should navigate to room info', async () => { await expect(element(by.id('room-info-view'))).toExist(); await expect(element(by.id('room-info-view-name'))).toExist(); }); - after(async() => { + after(async () => { await tapBack(); await tapBack(); await tapBack(); @@ -54,94 +58,98 @@ describe('Room info screen', () => { }); describe('Channel/Group', () => { - before(async() => { + before(async () => { await navigateToRoomInfo('c'); }); describe('Render', () => { - it('should have room info view', async() => { + it('should have room info view', async () => { await expect(element(by.id('room-info-view'))).toExist(); }); - it('should have name', async() => { + it('should have name', async () => { await expect(element(by.id('room-info-view-name'))).toExist(); }); - it('should have description', async() => { + it('should have description', async () => { await expect(element(by.label('Description'))).toExist(); }); - it('should have topic', async() => { + it('should have topic', async () => { await expect(element(by.label('Topic'))).toExist(); }); - it('should have announcement', async() => { + it('should have announcement', async () => { await expect(element(by.label('Announcement'))).toExist(); }); - it('should have edit button', async() => { + it('should have edit button', async () => { await expect(element(by.id('room-info-view-edit-button'))).toExist(); }); }); describe('Render Edit', () => { - before(async() => { - await waitFor(element(by.id('room-info-view-edit-button'))).toExist().withTimeout(10000); + before(async () => { + await waitFor(element(by.id('room-info-view-edit-button'))) + .toExist() + .withTimeout(10000); await element(by.id('room-info-view-edit-button')).tap(); - await waitFor(element(by.id('room-info-edit-view'))).toExist().withTimeout(2000); + await waitFor(element(by.id('room-info-edit-view'))) + .toExist() + .withTimeout(2000); }); - it('should have room info edit view', async() => { + it('should have room info edit view', async () => { await expect(element(by.id('room-info-edit-view'))).toExist(); }); - it('should have name input', async() => { + it('should have name input', async () => { await expect(element(by.id('room-info-edit-view-name'))).toExist(); }); - it('should have description input', async() => { + it('should have description input', async () => { await expect(element(by.id('room-info-edit-view-description'))).toExist(); }); - it('should have topic input', async() => { + it('should have topic input', async () => { await expect(element(by.id('room-info-edit-view-topic'))).toExist(); }); - it('should have announcement input', async() => { + it('should have announcement input', async () => { await expect(element(by.id('room-info-edit-view-announcement'))).toExist(); }); - it('should have password input', async() => { + it('should have password input', async () => { await expect(element(by.id('room-info-edit-view-password'))).toExist(); }); - it('should have type switch', async() => { + it('should have type switch', async () => { // Ugly hack to scroll on detox await element(by.id('room-info-edit-view-list')).swipe('up', 'fast', 0.8); await expect(element(by.id('room-info-edit-view-t'))).toExist(); }); - it('should have ready only switch', async() => { + it('should have ready only switch', async () => { await expect(element(by.id('room-info-edit-view-ro'))).toExist(); }); - it('should have submit button', async() => { + it('should have submit button', async () => { await expect(element(by.id('room-info-edit-view-submit'))).toExist(); }); - it('should have reset button', async() => { + it('should have reset button', async () => { await expect(element(by.id('room-info-edit-view-reset'))).toExist(); }); - it('should have archive button', async() => { + it('should have archive button', async () => { await expect(element(by.id('room-info-edit-view-archive'))).toExist(); }); - it('should have delete button', async() => { + it('should have delete button', async () => { await expect(element(by.id('room-info-edit-view-delete'))).toExist(); }); - after(async() => { + after(async () => { // Ugly hack to scroll on detox await element(by.id('room-info-edit-view-list')).swipe('down', 'fast', 0.8); }); @@ -160,25 +168,29 @@ describe('Room info screen', () => { // await element(by.type('UIScrollView')).atIndex(1).swipe('down'); // }); - it('should change room name', async() => { - await element(by.id('room-info-edit-view-name')).replaceText(`${ privateRoomName }new`); + it('should change room name', async () => { + await element(by.id('room-info-edit-view-name')).replaceText(`${privateRoomName}new`); await element(by.id('room-info-edit-view-list')).swipe('up', 'fast', 0.5); await element(by.id('room-info-edit-view-submit')).tap(); await waitForToast(); await tapBack(); - await waitFor(element(by.id('room-info-view'))).toExist().withTimeout(2000); - await expect(element(by.id('room-info-view-name'))).toHaveLabel(`${ privateRoomName }new`); + await waitFor(element(by.id('room-info-view'))) + .toExist() + .withTimeout(2000); + await expect(element(by.id('room-info-view-name'))).toHaveLabel(`${privateRoomName}new`); // change name to original await element(by.id('room-info-view-edit-button')).tap(); - await waitFor(element(by.id('room-info-edit-view'))).toExist().withTimeout(2000); - await element(by.id('room-info-edit-view-name')).replaceText(`${ privateRoomName }`); + await waitFor(element(by.id('room-info-edit-view'))) + .toExist() + .withTimeout(2000); + await element(by.id('room-info-edit-view-name')).replaceText(`${privateRoomName}`); await element(by.id('room-info-edit-view-list')).swipe('up', 'fast', 0.5); await element(by.id('room-info-edit-view-submit')).tap(); await waitForToast(); await element(by.id('room-info-edit-view-list')).swipe('down', 'fast', 0.8); }); - it('should reset form', async() => { + it('should reset form', async () => { await element(by.id('room-info-edit-view-name')).replaceText('abc'); await element(by.id('room-info-edit-view-description')).replaceText('abc'); await element(by.id('room-info-edit-view-topic')).replaceText('abc'); @@ -201,53 +213,71 @@ describe('Room info screen', () => { await element(by.id('room-info-edit-view-list')).swipe('down', 'fast', 0.8); }); - it('should change room description', async() => { + it('should change room description', async () => { await element(by.id('room-info-edit-view-description')).replaceText('new description'); await element(by.id('room-info-edit-view-list')).swipe('up', 'fast', 0.5); await element(by.id('room-info-edit-view-submit')).tap(); await waitForToast(); await tapBack(); - await waitFor(element(by.id('room-info-view'))).toExist().withTimeout(2000); + await waitFor(element(by.id('room-info-view'))) + .toExist() + .withTimeout(2000); await expect(element(by.label('new description').withAncestor(by.id('room-info-view-description')))).toExist(); }); - it('should change room topic', async() => { - await waitFor(element(by.id('room-info-view-edit-button'))).toExist().withTimeout(10000); + it('should change room topic', async () => { + await waitFor(element(by.id('room-info-view-edit-button'))) + .toExist() + .withTimeout(10000); await element(by.id('room-info-view-edit-button')).tap(); - await waitFor(element(by.id('room-info-edit-view'))).toExist().withTimeout(2000); + await waitFor(element(by.id('room-info-edit-view'))) + .toExist() + .withTimeout(2000); await element(by.id('room-info-edit-view-topic')).replaceText('new topic'); await element(by.id('room-info-edit-view-list')).swipe('up', 'fast', 0.5); await element(by.id('room-info-edit-view-submit')).tap(); await waitForToast(); await tapBack(); - await waitFor(element(by.id('room-info-view'))).toExist().withTimeout(2000); + await waitFor(element(by.id('room-info-view'))) + .toExist() + .withTimeout(2000); await expect(element(by.label('new topic').withAncestor(by.id('room-info-view-topic')))).toExist(); }); - it('should change room announcement', async() => { - await waitFor(element(by.id('room-info-view-edit-button'))).toExist().withTimeout(10000); + it('should change room announcement', async () => { + await waitFor(element(by.id('room-info-view-edit-button'))) + .toExist() + .withTimeout(10000); await element(by.id('room-info-view-edit-button')).tap(); - await waitFor(element(by.id('room-info-edit-view'))).toExist().withTimeout(2000); + await waitFor(element(by.id('room-info-edit-view'))) + .toExist() + .withTimeout(2000); await element(by.id('room-info-edit-view-announcement')).replaceText('new announcement'); await element(by.id('room-info-edit-view-list')).swipe('up', 'fast', 0.5); await element(by.id('room-info-edit-view-submit')).tap(); await waitForToast(); await tapBack(); - await waitFor(element(by.id('room-info-view'))).toExist().withTimeout(2000); + await waitFor(element(by.id('room-info-view'))) + .toExist() + .withTimeout(2000); await expect(element(by.label('new announcement').withAncestor(by.id('room-info-view-announcement')))).toExist(); }); - it('should change room password', async() => { - await waitFor(element(by.id('room-info-view-edit-button'))).toExist().withTimeout(10000); + it('should change room password', async () => { + await waitFor(element(by.id('room-info-view-edit-button'))) + .toExist() + .withTimeout(10000); await element(by.id('room-info-view-edit-button')).tap(); - await waitFor(element(by.id('room-info-edit-view'))).toExist().withTimeout(2000); + await waitFor(element(by.id('room-info-edit-view'))) + .toExist() + .withTimeout(2000); await element(by.id('room-info-edit-view-list')).swipe('up', 'fast', 0.5); await element(by.id('room-info-edit-view-password')).replaceText('password'); await element(by.id('room-info-edit-view-submit')).tap(); await waitForToast(); }); - it('should change room type', async() => { + it('should change room type', async () => { await element(by.id('room-info-edit-view-list')).swipe('up', 'fast', 0.5); await element(by.id('room-info-edit-view-t')).tap(); await element(by.id('room-info-edit-view-submit')).tap(); @@ -269,12 +299,16 @@ describe('Room info screen', () => { // // TODO: test if it's possible to react // }); - it('should archive room', async() => { + it('should archive room', async () => { await element(by.id('room-info-edit-view-list')).swipe('up', 'fast', 0.5); await element(by.id('room-info-edit-view-archive')).tap(); - await waitFor(element(by.text('Yes, archive it!'))).toExist().withTimeout(5000); + await waitFor(element(by.text('Yes, archive it!'))) + .toExist() + .withTimeout(5000); await element(by.text('Yes, archive it!')).tap(); - await waitFor(element(by.id('room-info-edit-view-unarchive'))).toExist().withTimeout(60000); + await waitFor(element(by.id('room-info-edit-view-unarchive'))) + .toExist() + .withTimeout(60000); await expect(element(by.id('room-info-edit-view-archive'))).toBeNotVisible(); // TODO: needs permission to unarchive // await element(by.id('room-info-edit-view-archive')).tap(); @@ -285,13 +319,19 @@ describe('Room info screen', () => { // await expect(element(by.text('ARCHIVE'))).toExist(); }); - it('should delete room', async() => { + it('should delete room', async () => { await element(by.id('room-info-edit-view-list')).swipe('up', 'fast', 0.5); await element(by.id('room-info-edit-view-delete')).tap(); - await waitFor(element(by.text('Yes, delete it!'))).toExist().withTimeout(5000); + await waitFor(element(by.text('Yes, delete it!'))) + .toExist() + .withTimeout(5000); await element(by.text('Yes, delete it!')).tap(); - await waitFor(element(by.id('rooms-list-view'))).toExist().withTimeout(10000); - await waitFor(element(by.id(`rooms-list-view-item-${ privateRoomName }`))).toBeNotVisible().withTimeout(60000); + await waitFor(element(by.id('rooms-list-view'))) + .toExist() + .withTimeout(10000); + await waitFor(element(by.id(`rooms-list-view-item-${privateRoomName}`))) + .toBeNotVisible() + .withTimeout(60000); }); }); }); diff --git a/e2e/tests/room/09-jumptomessage.spec.js b/e2e/tests/room/09-jumptomessage.spec.js index afaeeef5b..5bb791519 100644 --- a/e2e/tests/room/09-jumptomessage.spec.js +++ b/e2e/tests/room/09-jumptomessage.spec.js @@ -1,61 +1,91 @@ const data = require('../../data'); -const { - navigateToLogin, tapBack, login, searchRoom -} = require('../../helpers/app'); +const { navigateToLogin, tapBack, login, searchRoom } = require('../../helpers/app'); async function navigateToRoom(roomName) { - await searchRoom(`${ roomName }`); - await element(by.id(`rooms-list-view-item-${ roomName }`)).tap(); - await waitFor(element(by.id('room-view'))).toBeVisible().withTimeout(5000); + await searchRoom(`${roomName}`); + await element(by.id(`rooms-list-view-item-${roomName}`)).tap(); + await waitFor(element(by.id('room-view'))) + .toBeVisible() + .withTimeout(5000); } async function clearCache() { - await waitFor(element(by.id('room-view'))).toBeVisible().withTimeout(5000); + await waitFor(element(by.id('room-view'))) + .toBeVisible() + .withTimeout(5000); await tapBack(); - await waitFor(element(by.id('rooms-list-view'))).toBeVisible().withTimeout(10000); + await waitFor(element(by.id('rooms-list-view'))) + .toBeVisible() + .withTimeout(10000); await element(by.id('rooms-list-view-sidebar')).tap(); - await waitFor(element(by.id('sidebar-view'))).toBeVisible().withTimeout(2000); + await waitFor(element(by.id('sidebar-view'))) + .toBeVisible() + .withTimeout(2000); await element(by.id('sidebar-settings')).tap(); - await waitFor(element(by.id('settings-view'))).toBeVisible().withTimeout(2000); + await waitFor(element(by.id('settings-view'))) + .toBeVisible() + .withTimeout(2000); await element(by.id('settings-view-clear-cache')).tap(); - await waitFor(element(by.text('This will clear all your offline data.'))).toExist().withTimeout(2000); + await waitFor(element(by.text('This will clear all your offline data.'))) + .toExist() + .withTimeout(2000); await element(by.label('Clear').and(by.type('_UIAlertControllerActionView'))).tap(); - await waitFor(element(by.id('rooms-list-view'))).toBeVisible().withTimeout(5000); - await waitFor(element(by.id('rooms-list-view-item-jumping'))).toExist().withTimeout(10000); + await waitFor(element(by.id('rooms-list-view'))) + .toBeVisible() + .withTimeout(5000); + await waitFor(element(by.id('rooms-list-view-item-jumping'))) + .toExist() + .withTimeout(10000); } async function waitForLoading() { - await waitFor(element(by.id('loading'))).toBeVisible().withTimeout(5000); - await waitFor(element(by.id('loading'))).toBeNotVisible().withTimeout(10000); + await waitFor(element(by.id('loading'))) + .toBeVisible() + .withTimeout(5000); + await waitFor(element(by.id('loading'))) + .toBeNotVisible() + .withTimeout(10000); } describe('Room', () => { - before(async() => { + before(async () => { await device.launchApp({ permissions: { notifications: 'YES' }, delete: true }); await navigateToLogin(); await login(data.adminUser, data.adminPassword); }); - it('should jump to an old message and load its surroundings', async() => { + it('should jump to an old message and load its surroundings', async () => { await navigateToRoom('jumping'); - await waitFor(element(by.label('Quote first message'))).toExist().withTimeout(5000); + await waitFor(element(by.label('Quote first message'))) + .toExist() + .withTimeout(5000); await element(by.label('1')).atIndex(0).tap(); await waitForLoading(); - await waitFor(element(by.label('1')).atIndex(0)).toExist().withTimeout(10000); + await waitFor(element(by.label('1')).atIndex(0)) + .toExist() + .withTimeout(10000); await expect(element(by.label('2'))).toExist(); }); - it('should tap FAB and scroll to bottom', async() => { - await waitFor(element(by.id('nav-jump-to-bottom'))).toExist().withTimeout(5000); + it('should tap FAB and scroll to bottom', async () => { + await waitFor(element(by.id('nav-jump-to-bottom'))) + .toExist() + .withTimeout(5000); await element(by.id('nav-jump-to-bottom')).tap(); - await waitFor(element(by.label('Quote first message'))).toExist().withTimeout(5000); + await waitFor(element(by.label('Quote first message'))) + .toExist() + .withTimeout(5000); await clearCache(); }); - it('should load messages on scroll', async() => { + it('should load messages on scroll', async () => { await navigateToRoom('jumping'); - await waitFor(element(by.id('room-view-messages'))).toExist().withTimeout(5000); - await waitFor(element(by.label('300'))).toExist().withTimeout(5000); + await waitFor(element(by.id('room-view-messages'))) + .toExist() + .withTimeout(5000); + await waitFor(element(by.label('300'))) + .toExist() + .withTimeout(5000); let found = false; while (!found) { await element(by.id('room-view-messages')).atIndex(0).scroll(500, 'up'); @@ -69,12 +99,16 @@ describe('Room', () => { await clearCache(); }); - it('should search for old message and load its surroundings', async() => { + it('should search for old message and load its surroundings', async () => { await navigateToRoom('jumping'); await element(by.id('room-view-search')).tap(); - await waitFor(element(by.id('search-messages-view'))).toExist().withTimeout(5000); + await waitFor(element(by.id('search-messages-view'))) + .toExist() + .withTimeout(5000); await element(by.id('search-message-view-input')).typeText('30\n'); - await waitFor(element(by.label('30')).atIndex(0)).toExist().withTimeout(5000); + await waitFor(element(by.label('30')).atIndex(0)) + .toExist() + .withTimeout(5000); await element(by.label('30')).atIndex(0).tap(); await waitForLoading(); await expect(element(by.label('30'))).toExist(); @@ -82,28 +116,52 @@ describe('Room', () => { await expect(element(by.label('32'))).toExist(); }); - it('should load newer and older messages', async() => { + it('should load newer and older messages', async () => { await element(by.id('room-view-messages')).atIndex(0).swipe('down', 'fast', 0.8); - await waitFor(element(by.label('5'))).toExist().withTimeout(10000); - await waitFor(element(by.label('Load Older'))).toExist().withTimeout(5000); + await waitFor(element(by.label('5'))) + .toExist() + .withTimeout(10000); + await waitFor(element(by.label('Load Older'))) + .toExist() + .withTimeout(5000); await element(by.label('Load Older')).atIndex(0).tap(); - await waitFor(element(by.label('4'))).toExist().withTimeout(5000); + await waitFor(element(by.label('4'))) + .toExist() + .withTimeout(5000); await element(by.id('room-view-messages')).atIndex(0).swipe('down', 'fast', 0.5); - await waitFor(element(by.label('1'))).toExist().withTimeout(5000); + await waitFor(element(by.label('1'))) + .toExist() + .withTimeout(5000); await element(by.id('room-view-messages')).atIndex(0).swipe('up', 'fast', 0.5); - await waitFor(element(by.label('25'))).toExist().withTimeout(5000); + await waitFor(element(by.label('25'))) + .toExist() + .withTimeout(5000); await element(by.id('room-view-messages')).atIndex(0).swipe('up', 'fast', 0.5); - await waitFor(element(by.label('50'))).toExist().withTimeout(5000); + await waitFor(element(by.label('50'))) + .toExist() + .withTimeout(5000); await element(by.id('room-view-messages')).atIndex(0).swipe('up', 'slow', 0.5); - await waitFor(element(by.label('Load Newer'))).toExist().withTimeout(5000); + await waitFor(element(by.label('Load Newer'))) + .toExist() + .withTimeout(5000); await element(by.label('Load Newer')).atIndex(0).tap(); - await waitFor(element(by.label('104'))).toExist().withTimeout(5000); - await waitFor(element(by.label('Load Newer'))).toExist().withTimeout(5000); + await waitFor(element(by.label('104'))) + .toExist() + .withTimeout(5000); + await waitFor(element(by.label('Load Newer'))) + .toExist() + .withTimeout(5000); await element(by.label('Load Newer')).atIndex(0).tap(); - await waitFor(element(by.label('154'))).toExist().withTimeout(5000); - await waitFor(element(by.label('Load Newer'))).toExist().withTimeout(5000); + await waitFor(element(by.label('154'))) + .toExist() + .withTimeout(5000); + await waitFor(element(by.label('Load Newer'))) + .toExist() + .withTimeout(5000); await element(by.label('Load Newer')).atIndex(0).tap(); - await waitFor(element(by.label('Load Newer'))).toNotExist().withTimeout(5000); + await waitFor(element(by.label('Load Newer'))) + .toNotExist() + .withTimeout(5000); await expect(element(by.label('Load More'))).toNotExist(); await expect(element(by.label('201'))).toExist(); await expect(element(by.label('202'))).toExist(); @@ -111,41 +169,55 @@ describe('Room', () => { }); }); -const expectThreadMessages = async(message) => { - await waitFor(element(by.id('room-view-title-jumping-thread'))).toExist().withTimeout(5000); +const expectThreadMessages = async message => { + await waitFor(element(by.id('room-view-title-jumping-thread'))) + .toExist() + .withTimeout(5000); await expect(element(by.label(message))).toExist(); }; describe('Threads', () => { - it('should navigate to a thread from another room', async() => { + it('should navigate to a thread from another room', async () => { await navigateToRoom('jumping'); - await waitFor(element(by.label('Go to jumping-thread\'s thread')).atIndex(0)).toExist().withTimeout(5000); - await element(by.label('Go to jumping-thread\'s thread')).atIndex(0).tap(); + await waitFor(element(by.label("Go to jumping-thread's thread")).atIndex(0)) + .toExist() + .withTimeout(5000); + await element(by.label("Go to jumping-thread's thread")).atIndex(0).tap(); await waitForLoading(); - await expectThreadMessages('Go to jumping-thread\'s thread'); + await expectThreadMessages("Go to jumping-thread's thread"); await tapBack(); }); - it('should tap on thread message from main room', async() => { - await waitFor(element(by.label('thread message sent to main room')).atIndex(0)).toExist().withTimeout(5000); + it('should tap on thread message from main room', async () => { + await waitFor(element(by.label('thread message sent to main room')).atIndex(0)) + .toExist() + .withTimeout(5000); await element(by.label('thread message sent to main room')).atIndex(0).tap(); await expectThreadMessages('thread message sent to main room'); await tapBack(); }); - it('should tap on quote', async() => { - await waitFor(element(by.label('quoted'))).toExist().withTimeout(5000); + it('should tap on quote', async () => { + await waitFor(element(by.label('quoted'))) + .toExist() + .withTimeout(5000); await element(by.label('quoted')).atIndex(0).tap(); await expectThreadMessages('quoted'); await tapBack(); }); - it('should jump from search message', async() => { - await waitFor(element(by.id('room-view-title-jumping-thread'))).toExist().withTimeout(5000); + it('should jump from search message', async () => { + await waitFor(element(by.id('room-view-title-jumping-thread'))) + .toExist() + .withTimeout(5000); await element(by.id('room-view-search')).atIndex(0).tap(); - await waitFor(element(by.id('search-messages-view'))).toExist().withTimeout(5000); + await waitFor(element(by.id('search-messages-view'))) + .toExist() + .withTimeout(5000); await element(by.id('search-message-view-input')).typeText('to be searched\n'); - await waitFor(element(by.label('to be searched'))).toExist().withTimeout(5000); + await waitFor(element(by.label('to be searched'))) + .toExist() + .withTimeout(5000); await element(by.label('to be searched')).atIndex(1).tap(); await expectThreadMessages('to be searched'); }); diff --git a/e2e/tests/team/01-createteam.spec.js b/e2e/tests/team/01-createteam.spec.js index 97616c6b6..ad8e3ea03 100644 --- a/e2e/tests/team/01-createteam.spec.js +++ b/e2e/tests/team/01-createteam.spec.js @@ -1,74 +1,96 @@ const data = require('../../data'); const { navigateToLogin, login } = require('../../helpers/app'); -const teamName = `team-${ data.random }`; +const teamName = `team-${data.random}`; describe('Create team screen', () => { - before(async() => { + before(async () => { await device.launchApp({ permissions: { notifications: 'YES' }, delete: true }); await navigateToLogin(); await login(data.users.regular.username, data.users.regular.password); }); describe('New Message', () => { - before(async() => { + before(async () => { await element(by.id('rooms-list-view-create-channel')).tap(); }); - it('should have team button', async() => { - await waitFor(element(by.id('new-message-view-create-team'))).toBeVisible().withTimeout(2000); + it('should have team button', async () => { + await waitFor(element(by.id('new-message-view-create-team'))) + .toBeVisible() + .withTimeout(2000); }); - it('should navigate to select users', async() => { + it('should navigate to select users', async () => { await element(by.id('new-message-view-create-team')).tap(); - await waitFor(element(by.id('select-users-view'))).toExist().withTimeout(5000); + await waitFor(element(by.id('select-users-view'))) + .toExist() + .withTimeout(5000); }); }); describe('Select Users', () => { - it('should nav to create team', async() => { + it('should nav to create team', async () => { await element(by.id('selected-users-view-submit')).tap(); - await waitFor(element(by.id('create-channel-view'))).toExist().withTimeout(10000); + await waitFor(element(by.id('create-channel-view'))) + .toExist() + .withTimeout(10000); }); }); describe('Create Team', () => { describe('Usage', () => { - it('should get invalid team name', async() => { - await element(by.id('create-channel-name')).typeText(`${ data.teams.private.name }`); + it('should get invalid team name', async () => { + await element(by.id('create-channel-name')).typeText(`${data.teams.private.name}`); await element(by.id('create-channel-submit')).tap(); - await waitFor(element(by.text('OK'))).toBeVisible().withTimeout(5000); + await waitFor(element(by.text('OK'))) + .toBeVisible() + .withTimeout(5000); await element(by.text('OK')).tap(); }); - it('should create private team', async() => { + it('should create private team', async () => { await element(by.id('create-channel-name')).replaceText(''); await element(by.id('create-channel-name')).typeText(teamName); await element(by.id('create-channel-submit')).tap(); - await waitFor(element(by.id('room-view'))).toExist().withTimeout(20000); + await waitFor(element(by.id('room-view'))) + .toExist() + .withTimeout(20000); await expect(element(by.id('room-view'))).toExist(); - await waitFor(element(by.id(`room-view-title-${ teamName }`))).toExist().withTimeout(6000); - await expect(element(by.id(`room-view-title-${ teamName }`))).toExist(); + await waitFor(element(by.id(`room-view-title-${teamName}`))) + .toExist() + .withTimeout(6000); + await expect(element(by.id(`room-view-title-${teamName}`))).toExist(); }); }); }); describe('Delete Team', () => { - it('should navigate to room info edit view', async() => { + it('should navigate to room info edit view', async () => { await element(by.id('room-header')).tap(); - await waitFor(element(by.id('room-actions-view'))).toExist().withTimeout(5000); + await waitFor(element(by.id('room-actions-view'))) + .toExist() + .withTimeout(5000); await element(by.id('room-actions-info')).tap(); - await waitFor(element(by.id('room-info-view'))).toExist().withTimeout(2000); + await waitFor(element(by.id('room-info-view'))) + .toExist() + .withTimeout(2000); }); - it('should delete team', async() => { + it('should delete team', async () => { await element(by.id('room-info-view-edit-button')).tap(); await element(by.id('room-info-edit-view-list')).swipe('up', 'fast', 0.5); await element(by.id('room-info-edit-view-delete')).tap(); - await waitFor(element(by.text('Yes, delete it!'))).toExist().withTimeout(5000); + await waitFor(element(by.text('Yes, delete it!'))) + .toExist() + .withTimeout(5000); await element(by.text('Yes, delete it!')).tap(); - await waitFor(element(by.id('rooms-list-view'))).toExist().withTimeout(10000); - await waitFor(element(by.id(`rooms-list-view-item-${ teamName }`))).toBeNotVisible().withTimeout(60000); + await waitFor(element(by.id('rooms-list-view'))) + .toExist() + .withTimeout(10000); + await waitFor(element(by.id(`rooms-list-view-item-${teamName}`))) + .toBeNotVisible() + .withTimeout(60000); }); }); }); diff --git a/e2e/tests/team/02-team.spec.js b/e2e/tests/team/02-team.spec.js index c8451cc45..7779cb9af 100644 --- a/e2e/tests/team/02-team.spec.js +++ b/e2e/tests/team/02-team.spec.js @@ -1,31 +1,39 @@ const data = require('../../data'); -const { - navigateToLogin, login, tapBack, sleep, searchRoom -} = require('../../helpers/app'); +const { navigateToLogin, login, tapBack, sleep, searchRoom } = require('../../helpers/app'); async function navigateToRoom(roomName) { - await searchRoom(`${ roomName }`); - await element(by.id(`rooms-list-view-item-${ roomName }`)).tap(); - await waitFor(element(by.id('room-view'))).toBeVisible().withTimeout(5000); + await searchRoom(`${roomName}`); + await element(by.id(`rooms-list-view-item-${roomName}`)).tap(); + await waitFor(element(by.id('room-view'))) + .toBeVisible() + .withTimeout(5000); } async function openActionSheet(username) { - await waitFor(element(by.id(`room-members-view-item-${ username }`))).toExist().withTimeout(5000); - await element(by.id(`room-members-view-item-${ username }`)).tap(); + await waitFor(element(by.id(`room-members-view-item-${username}`))) + .toExist() + .withTimeout(5000); + await element(by.id(`room-members-view-item-${username}`)).tap(); await sleep(300); await expect(element(by.id('action-sheet'))).toExist(); await expect(element(by.id('action-sheet-handle'))).toBeVisible(); } async function navigateToRoomActions() { - await waitFor(element(by.id('room-view'))).toExist().withTimeout(2000); + await waitFor(element(by.id('room-view'))) + .toExist() + .withTimeout(2000); await element(by.id('room-header')).tap(); - await waitFor(element(by.id('room-actions-view'))).toExist().withTimeout(5000); + await waitFor(element(by.id('room-actions-view'))) + .toExist() + .withTimeout(5000); } async function backToActions() { await tapBack(); - await waitFor(element(by.id('room-actions-view'))).toExist().withTimeout(2000); + await waitFor(element(by.id('room-actions-view'))) + .toExist() + .withTimeout(2000); } async function closeActionSheet() { await element(by.id('action-sheet-handle')).swipe('down', 'fast', 0.6); @@ -38,10 +46,10 @@ async function waitForToast() { describe('Team', () => { const team = data.teams.private.name; const user = data.users.alternate; - const room = `private${ data.random }-channel-team`; + const room = `private${data.random}-channel-team`; const existingRoom = data.groups.alternate.name; - before(async() => { + before(async () => { await device.launchApp({ permissions: { notifications: 'YES' }, delete: true }); await navigateToLogin(); await login(data.users.regular.username, data.users.regular.password); @@ -50,240 +58,369 @@ describe('Team', () => { describe('Team Room', () => { describe('Team Header', () => { - it('should have actions button ', async() => { + it('should have actions button ', async () => { await expect(element(by.id('room-header'))).toExist(); }); - it('should have team channels button ', async() => { + it('should have team channels button ', async () => { await expect(element(by.id('room-view-header-team-channels'))).toExist(); }); - it('should have threads button ', async() => { + it('should have threads button ', async () => { await expect(element(by.id('room-view-header-threads'))).toExist(); }); - - it('should have threads button ', async() => { + it('should have threads button ', async () => { await expect(element(by.id('room-view-search'))).toExist(); }); }); describe('Team Header Usage', () => { - it('should navigate to team channels view', async() => { + it('should navigate to team channels view', async () => { await element(by.id('room-view-header-team-channels')).tap(); - await waitFor(element(by.id('team-channels-view'))).toExist().withTimeout(5000); + await waitFor(element(by.id('team-channels-view'))) + .toExist() + .withTimeout(5000); }); }); describe('Team Channels Header', () => { - it('should have actions button ', async() => { + it('should have actions button ', async () => { await expect(element(by.id('room-header'))).toExist(); }); - it('should have team channels button ', async() => { + it('should have team channels button ', async () => { await expect(element(by.id('team-channels-view-create'))).toExist(); }); - it('should have threads button ', async() => { + it('should have threads button ', async () => { await expect(element(by.id('team-channels-view-search'))).toExist(); }); }); describe('Team Channels Header Usage', () => { - it('should navigate to add team channels view', async() => { + it('should navigate to add team channels view', async () => { await element(by.id('team-channels-view-create')).tap(); - await waitFor(element(by.id('add-channel-team-view'))).toExist().withTimeout(5000); + await waitFor(element(by.id('add-channel-team-view'))) + .toExist() + .withTimeout(5000); }); - it('should have create new button', async() => { - await waitFor(element(by.id('add-channel-team-view-create-channel'))).toExist().withTimeout(5000); + it('should have create new button', async () => { + await waitFor(element(by.id('add-channel-team-view-create-channel'))) + .toExist() + .withTimeout(5000); }); - it('should add existing button', async() => { - await waitFor(element(by.id('add-channel-team-view-add-existing'))).toExist().withTimeout(5000); + it('should add existing button', async () => { + await waitFor(element(by.id('add-channel-team-view-add-existing'))) + .toExist() + .withTimeout(5000); }); }); describe('Channels', () => { - it('should create new channel for team', async() => { + it('should create new channel for team', async () => { await element(by.id('add-channel-team-view-create-channel')).tap(); await element(by.id('select-users-view-search')).replaceText('rocket.cat'); await element(by.id('select-users-view-item-rocket.cat')).tap(); - await waitFor(element(by.id('selected-user-rocket.cat'))).toBeVisible().withTimeout(10000); + await waitFor(element(by.id('selected-user-rocket.cat'))) + .toBeVisible() + .withTimeout(10000); await element(by.id('selected-users-view-submit')).tap(); - await waitFor(element(by.id('create-channel-view'))).toExist().withTimeout(10000); + await waitFor(element(by.id('create-channel-view'))) + .toExist() + .withTimeout(10000); await element(by.id('create-channel-name')).replaceText(''); await element(by.id('create-channel-name')).typeText(room); await element(by.id('create-channel-submit')).tap(); - await waitFor(element(by.id('room-view'))).toExist().withTimeout(20000); + await waitFor(element(by.id('room-view'))) + .toExist() + .withTimeout(20000); await expect(element(by.id('room-view'))).toExist(); await expect(element(by.id('room-view-header-team-channels'))).toExist(); await element(by.id('room-view-header-team-channels')).tap(); - await waitFor(element(by.id('team-channels-view'))).toExist().withTimeout(5000); - await waitFor(element(by.id(`rooms-list-view-item-${ room }`))).toExist().withTimeout(6000); - await expect(element(by.id(`rooms-list-view-item-${ room }`))).toExist(); - await element(by.id(`rooms-list-view-item-${ room }`)).tap(); - await waitFor(element(by.id(`room-view-title-${ room }`))).toExist().withTimeout(60000); - await expect(element(by.id(`room-view-title-${ room }`))).toExist(); + await waitFor(element(by.id('team-channels-view'))) + .toExist() + .withTimeout(5000); + await waitFor(element(by.id(`rooms-list-view-item-${room}`))) + .toExist() + .withTimeout(6000); + await expect(element(by.id(`rooms-list-view-item-${room}`))).toExist(); + await element(by.id(`rooms-list-view-item-${room}`)).tap(); + await waitFor(element(by.id(`room-view-title-${room}`))) + .toExist() + .withTimeout(60000); + await expect(element(by.id(`room-view-title-${room}`))).toExist(); await expect(element(by.id('room-view-header-team-channels'))).toExist(); await expect(element(by.id('room-view-header-threads'))).toExist(); await expect(element(by.id('room-view-search'))).toExist(); await tapBack(); }); - it('should add existing channel to team', async() => { + it('should add existing channel to team', async () => { await element(by.id('team-channels-view-create')).tap(); - await waitFor(element(by.id('add-channel-team-view'))).toExist().withTimeout(5000); + await waitFor(element(by.id('add-channel-team-view'))) + .toExist() + .withTimeout(5000); await element(by.id('add-channel-team-view-add-existing')).tap(); - await waitFor(element(by.id('add-existing-channel-view'))).toExist().withTimeout(60000); - await expect(element(by.id(`add-existing-channel-view-item-${ existingRoom }`))).toExist(); - await element(by.id(`add-existing-channel-view-item-${ existingRoom }`)).tap(); - await waitFor(element(by.id('add-existing-channel-view-submit'))).toExist().withTimeout(6000); + await waitFor(element(by.id('add-existing-channel-view'))) + .toExist() + .withTimeout(60000); + await expect(element(by.id(`add-existing-channel-view-item-${existingRoom}`))).toExist(); + await element(by.id(`add-existing-channel-view-item-${existingRoom}`)).tap(); + await waitFor(element(by.id('add-existing-channel-view-submit'))) + .toExist() + .withTimeout(6000); await element(by.id('add-existing-channel-view-submit')).tap(); - await waitFor(element(by.id('room-view'))).toExist().withTimeout(20000); + await waitFor(element(by.id('room-view'))) + .toExist() + .withTimeout(20000); await expect(element(by.id('room-view'))).toExist(); await expect(element(by.id('room-view-header-team-channels'))).toExist(); await element(by.id('room-view-header-team-channels')).tap(); - await waitFor(element(by.id(`rooms-list-view-item-${ existingRoom }`))).toExist().withTimeout(10000); + await waitFor(element(by.id(`rooms-list-view-item-${existingRoom}`))) + .toExist() + .withTimeout(10000); }); - it('should activate/deactivate auto-join to channel', async() => { - await element(by.id(`rooms-list-view-item-${ existingRoom }`)).atIndex(0).longPress(); + it('should activate/deactivate auto-join to channel', async () => { + await element(by.id(`rooms-list-view-item-${existingRoom}`)) + .atIndex(0) + .longPress(); - await waitFor(element(by.id('action-sheet-auto-join'))).toBeVisible().withTimeout(5000); - await waitFor(element(by.id('auto-join-unchecked'))).toBeVisible().withTimeout(5000); - await waitFor(element(by.id('action-sheet-remove-from-team'))).toBeVisible().withTimeout(5000); - await waitFor(element(by.id('action-sheet-delete'))).toBeVisible().withTimeout(5000); + await waitFor(element(by.id('action-sheet-auto-join'))) + .toBeVisible() + .withTimeout(5000); + await waitFor(element(by.id('auto-join-unchecked'))) + .toBeVisible() + .withTimeout(5000); + await waitFor(element(by.id('action-sheet-remove-from-team'))) + .toBeVisible() + .withTimeout(5000); + await waitFor(element(by.id('action-sheet-delete'))) + .toBeVisible() + .withTimeout(5000); await element(by.id('auto-join-unchecked')).tap(); - await waitFor(element(by.id('auto-join-tag'))).toBeVisible().withTimeout(5000); - await element(by.id(`rooms-list-view-item-${ existingRoom }`)).atIndex(0).longPress(); + await waitFor(element(by.id('auto-join-tag'))) + .toBeVisible() + .withTimeout(5000); + await element(by.id(`rooms-list-view-item-${existingRoom}`)) + .atIndex(0) + .longPress(); - await waitFor(element(by.id('auto-join-checked'))).toBeVisible().withTimeout(5000); + await waitFor(element(by.id('auto-join-checked'))) + .toBeVisible() + .withTimeout(5000); await element(by.id('auto-join-checked')).tap(); - await waitFor(element(by.id('auto-join-tag'))).toBeNotVisible().withTimeout(5000); - await waitFor(element(by.id(`rooms-list-view-item-${ existingRoom }`))).toExist().withTimeout(6000); + await waitFor(element(by.id('auto-join-tag'))) + .toBeNotVisible() + .withTimeout(5000); + await waitFor(element(by.id(`rooms-list-view-item-${existingRoom}`))) + .toExist() + .withTimeout(6000); }); }); describe('Team actions', () => { - before(async() => { + before(async () => { await tapBack(); await navigateToRoomActions(); }); - it('should add users to the team', async() => { - await waitFor(element(by.id('room-actions-add-user'))).toExist().withTimeout(10000); + it('should add users to the team', async () => { + await waitFor(element(by.id('room-actions-add-user'))) + .toExist() + .withTimeout(10000); await element(by.id('room-actions-add-user')).tap(); const rocketCat = 'rocket.cat'; await element(by.id('select-users-view-search')).replaceText('rocket.cat'); - await waitFor(element(by.id(`select-users-view-item-${ rocketCat }`))).toExist().withTimeout(10000); - await element(by.id(`select-users-view-item-${ rocketCat }`)).tap(); - await waitFor(element(by.id(`selected-user-${ rocketCat }`))).toExist().withTimeout(5000); + await waitFor(element(by.id(`select-users-view-item-${rocketCat}`))) + .toExist() + .withTimeout(10000); + await element(by.id(`select-users-view-item-${rocketCat}`)).tap(); + await waitFor(element(by.id(`selected-user-${rocketCat}`))) + .toExist() + .withTimeout(5000); - await waitFor(element(by.id('select-users-view-search'))).toExist().withTimeout(4000); + await waitFor(element(by.id('select-users-view-search'))) + .toExist() + .withTimeout(4000); await element(by.id('select-users-view-search')).tap(); await element(by.id('select-users-view-search')).replaceText(user.username); - await waitFor(element(by.id(`select-users-view-item-${ user.username }`))).toExist().withTimeout(10000); - await element(by.id(`select-users-view-item-${ user.username }`)).tap(); - await waitFor(element(by.id(`selected-user-${ user.username }`))).toExist().withTimeout(5000); + await waitFor(element(by.id(`select-users-view-item-${user.username}`))) + .toExist() + .withTimeout(10000); + await element(by.id(`select-users-view-item-${user.username}`)).tap(); + await waitFor(element(by.id(`selected-user-${user.username}`))) + .toExist() + .withTimeout(5000); await element(by.id('selected-users-view-submit')).tap(); await sleep(300); - await waitFor(element(by.id('room-actions-members'))).toExist().withTimeout(10000); + await waitFor(element(by.id('room-actions-members'))) + .toExist() + .withTimeout(10000); await element(by.id('room-actions-members')).tap(); await element(by.id('room-members-view-toggle-status')).tap(); - await waitFor(element(by.id(`room-members-view-item-${ user.username }`))).toExist().withTimeout(60000); + await waitFor(element(by.id(`room-members-view-item-${user.username}`))) + .toExist() + .withTimeout(60000); await backToActions(); }); - it('should try to leave to leave team and raise alert', async() => { + it('should try to leave to leave team and raise alert', async () => { await element(by.id('room-actions-scrollview')).scrollTo('bottom'); - await waitFor(element(by.id('room-actions-leave-channel'))).toExist().withTimeout(2000); + await waitFor(element(by.id('room-actions-leave-channel'))) + .toExist() + .withTimeout(2000); await element(by.id('room-actions-leave-channel')).tap(); - await waitFor(element(by.id('select-list-view'))).toExist().withTimeout(2000); - await waitFor(element(by.id(`select-list-view-item-${ room }`))).toExist().withTimeout(2000); - await waitFor(element(by.id(`select-list-view-item-${ existingRoom }`))).toExist().withTimeout(2000); - await element(by.id(`select-list-view-item-${ room }`)).tap(); + await waitFor(element(by.id('select-list-view'))) + .toExist() + .withTimeout(2000); + await waitFor(element(by.id(`select-list-view-item-${room}`))) + .toExist() + .withTimeout(2000); + await waitFor(element(by.id(`select-list-view-item-${existingRoom}`))) + .toExist() + .withTimeout(2000); + await element(by.id(`select-list-view-item-${room}`)).tap(); - await waitFor(element(by.label('You are the last owner of this channel. Once you leave the team, the channel will be kept inside the team but you will be managing it from outside.'))).toExist().withTimeout(2000); + await waitFor( + element( + by.label( + 'You are the last owner of this channel. Once you leave the team, the channel will be kept inside the team but you will be managing it from outside.' + ) + ) + ) + .toExist() + .withTimeout(2000); await element(by.text('OK')).tap(); - await waitFor(element(by.id('select-list-view-submit'))).toExist().withTimeout(2000); + await waitFor(element(by.id('select-list-view-submit'))) + .toExist() + .withTimeout(2000); await element(by.id('select-list-view-submit')).tap(); - await waitFor(element(by.text('Last owner cannot be removed'))).toExist().withTimeout(8000); + await waitFor(element(by.text('Last owner cannot be removed'))) + .toExist() + .withTimeout(8000); await element(by.text('OK')).tap(); await tapBack(); - await waitFor(element(by.id('room-actions-view'))).toExist().withTimeout(2000); + await waitFor(element(by.id('room-actions-view'))) + .toExist() + .withTimeout(2000); }); describe('Room Members', () => { - before(async() => { + before(async () => { await element(by.id('room-actions-members')).tap(); - await waitFor(element(by.id('room-members-view'))).toExist().withTimeout(2000); + await waitFor(element(by.id('room-members-view'))) + .toExist() + .withTimeout(2000); }); - it('should show all users', async() => { + it('should show all users', async () => { await element(by.id('room-members-view-toggle-status')).tap(); - await waitFor(element(by.id(`room-members-view-item-${ user.username }`))).toExist().withTimeout(60000); + await waitFor(element(by.id(`room-members-view-item-${user.username}`))) + .toExist() + .withTimeout(60000); }); - it('should filter user', async() => { - await waitFor(element(by.id(`room-members-view-item-${ user.username }`))).toExist().withTimeout(60000); + it('should filter user', async () => { + await waitFor(element(by.id(`room-members-view-item-${user.username}`))) + .toExist() + .withTimeout(60000); await element(by.id('room-members-view-search')).replaceText('rocket'); - await waitFor(element(by.id(`room-members-view-item-${ user.username }`))).toBeNotVisible().withTimeout(60000); + await waitFor(element(by.id(`room-members-view-item-${user.username}`))) + .toBeNotVisible() + .withTimeout(60000); await element(by.id('room-members-view-search')).tap(); await element(by.id('room-members-view-search')).clearText(''); - await waitFor(element(by.id(`room-members-view-item-${ user.username }`))).toExist().withTimeout(60000); + await waitFor(element(by.id(`room-members-view-item-${user.username}`))) + .toExist() + .withTimeout(60000); }); - it('should remove member from team', async() => { + it('should remove member from team', async () => { await openActionSheet('rocket.cat'); await element(by.id('action-sheet-remove-from-team')).tap(); - await waitFor(element(by.id('select-list-view'))).toExist().withTimeout(5000); - await waitFor(element(by.id(`select-list-view-item-${ room }`))).toExist().withTimeout(5000); - await element(by.id(`select-list-view-item-${ room }`)).tap(); - await waitFor(element(by.id(`${ room }-checked`))).toExist().withTimeout(5000); - await element(by.id(`select-list-view-item-${ room }`)).tap(); - await waitFor(element(by.id(`${ room }-unchecked`))).toExist().withTimeout(5000); + await waitFor(element(by.id('select-list-view'))) + .toExist() + .withTimeout(5000); + await waitFor(element(by.id(`select-list-view-item-${room}`))) + .toExist() + .withTimeout(5000); + await element(by.id(`select-list-view-item-${room}`)).tap(); + await waitFor(element(by.id(`${room}-checked`))) + .toExist() + .withTimeout(5000); + await element(by.id(`select-list-view-item-${room}`)).tap(); + await waitFor(element(by.id(`${room}-unchecked`))) + .toExist() + .withTimeout(5000); await element(by.id('select-list-view-submit')).tap(); - await waitFor(element(by.id('room-members-view-item-rocket.cat'))).toBeNotVisible().withTimeout(60000); + await waitFor(element(by.id('room-members-view-item-rocket.cat'))) + .toBeNotVisible() + .withTimeout(60000); }); - it('should set member as owner', async() => { + it('should set member as owner', async () => { await openActionSheet(user.username); await element(by.id('action-sheet-set-owner')).tap(); await waitForToast(); await openActionSheet(user.username); - await waitFor(element(by.id('action-sheet-set-owner-checked'))).toBeVisible().withTimeout(6000); + await waitFor(element(by.id('action-sheet-set-owner-checked'))) + .toBeVisible() + .withTimeout(6000); await closeActionSheet(); }); - it('should leave team', async() => { + it('should leave team', async () => { await tapBack(); await element(by.id('room-actions-scrollview')).scrollTo('bottom'); - await waitFor(element(by.id('room-actions-leave-channel'))).toExist().withTimeout(2000); + await waitFor(element(by.id('room-actions-leave-channel'))) + .toExist() + .withTimeout(2000); await element(by.id('room-actions-leave-channel')).tap(); - await waitFor(element(by.id('select-list-view'))).toExist().withTimeout(2000); - await waitFor(element(by.id(`select-list-view-item-${ room }`))).toExist().withTimeout(2000); - await waitFor(element(by.id(`select-list-view-item-${ existingRoom }`))).toExist().withTimeout(2000); - await element(by.id(`select-list-view-item-${ room }`)).tap(); + await waitFor(element(by.id('select-list-view'))) + .toExist() + .withTimeout(2000); + await waitFor(element(by.id(`select-list-view-item-${room}`))) + .toExist() + .withTimeout(2000); + await waitFor(element(by.id(`select-list-view-item-${existingRoom}`))) + .toExist() + .withTimeout(2000); + await element(by.id(`select-list-view-item-${room}`)).tap(); - await waitFor(element(by.label('You are the last owner of this channel. Once you leave the team, the channel will be kept inside the team but you will be managing it from outside.'))).toExist().withTimeout(2000); + await waitFor( + element( + by.label( + 'You are the last owner of this channel. Once you leave the team, the channel will be kept inside the team but you will be managing it from outside.' + ) + ) + ) + .toExist() + .withTimeout(2000); await element(by.text('OK')).tap(); - await waitFor(element(by.id('select-list-view-submit'))).toExist().withTimeout(2000); + await waitFor(element(by.id('select-list-view-submit'))) + .toExist() + .withTimeout(2000); await element(by.id('select-list-view-submit')).tap(); - await waitFor(element(by.id(`rooms-list-view-item-${ team }`))).toBeNotVisible().withTimeout(60000); + await waitFor(element(by.id(`rooms-list-view-item-${team}`))) + .toBeNotVisible() + .withTimeout(60000); }); }); }); diff --git a/e2e/tests/team/03-moveconvert.spec.js b/e2e/tests/team/03-moveconvert.spec.js index 754aafc09..5cf68bd13 100644 --- a/e2e/tests/team/03-moveconvert.spec.js +++ b/e2e/tests/team/03-moveconvert.spec.js @@ -1,115 +1,179 @@ const data = require('../../data'); -const { - navigateToLogin, login, tapBack, searchRoom, sleep -} = require('../../helpers/app'); +const { navigateToLogin, login, tapBack, searchRoom, sleep } = require('../../helpers/app'); -const toBeConverted = `to-be-converted-${ data.random }`; -const toBeMoved = `to-be-moved-${ data.random }`; +const toBeConverted = `to-be-converted-${data.random}`; +const toBeMoved = `to-be-moved-${data.random}`; -const createChannel = async(room) => { +const createChannel = async room => { await element(by.id('rooms-list-view-create-channel')).tap(); - await waitFor(element(by.id('new-message-view'))).toExist().withTimeout(5000); + await waitFor(element(by.id('new-message-view'))) + .toExist() + .withTimeout(5000); await element(by.id('new-message-view-create-channel')).tap(); - await waitFor(element(by.id('select-users-view'))).toExist().withTimeout(5000); + await waitFor(element(by.id('select-users-view'))) + .toExist() + .withTimeout(5000); await element(by.id('selected-users-view-submit')).tap(); - await waitFor(element(by.id('create-channel-view'))).toExist().withTimeout(10000); + await waitFor(element(by.id('create-channel-view'))) + .toExist() + .withTimeout(10000); await element(by.id('create-channel-name')).typeText(room); await element(by.id('create-channel-submit')).tap(); - await waitFor(element(by.id('room-view'))).toExist().withTimeout(60000); - await waitFor(element(by.id(`room-view-title-${ room }`))).toExist().withTimeout(60000); + await waitFor(element(by.id('room-view'))) + .toExist() + .withTimeout(60000); + await waitFor(element(by.id(`room-view-title-${room}`))) + .toExist() + .withTimeout(60000); await tapBack(); - await waitFor(element(by.id('rooms-list-view'))).toExist().withTimeout(2000); - await waitFor(element(by.id(`rooms-list-view-item-${ room }`))).toExist().withTimeout(60000); + await waitFor(element(by.id('rooms-list-view'))) + .toExist() + .withTimeout(2000); + await waitFor(element(by.id(`rooms-list-view-item-${room}`))) + .toExist() + .withTimeout(60000); }; async function navigateToRoom(room) { - await searchRoom(`${ room }`); - await element(by.id(`rooms-list-view-item-${ room }`)).tap(); - await waitFor(element(by.id('room-view'))).toBeVisible().withTimeout(5000); + await searchRoom(`${room}`); + await element(by.id(`rooms-list-view-item-${room}`)).tap(); + await waitFor(element(by.id('room-view'))) + .toBeVisible() + .withTimeout(5000); } async function navigateToRoomActions(room) { await navigateToRoom(room); await element(by.id('room-header')).tap(); - await waitFor(element(by.id('room-actions-view'))).toExist().withTimeout(5000); + await waitFor(element(by.id('room-actions-view'))) + .toExist() + .withTimeout(5000); } describe('Move/Convert Team', () => { - before(async() => { + before(async () => { await device.launchApp({ permissions: { notifications: 'YES' }, delete: true }); await navigateToLogin(); await login(data.users.regular.username, data.users.regular.password); }); describe('Convert', () => { - before(async() => { + before(async () => { await createChannel(toBeConverted); }); - it('should convert channel to a team', async() => { + it('should convert channel to a team', async () => { await navigateToRoomActions(toBeConverted); await element(by.id('room-actions-scrollview')).scrollTo('bottom'); - await waitFor(element(by.id('room-actions-convert-to-team'))).toExist().withTimeout(2000); + await waitFor(element(by.id('room-actions-convert-to-team'))) + .toExist() + .withTimeout(2000); await element(by.id('room-actions-convert-to-team')).tap(); - await waitFor(element(by.label('You are converting this Channel to a Team. All Members will be kept.'))).toExist().withTimeout(2000); + await waitFor(element(by.label('You are converting this Channel to a Team. All Members will be kept.'))) + .toExist() + .withTimeout(2000); await element(by.text('Convert')).tap(); - await waitFor(element(by.id('room-view'))).toExist().withTimeout(20000); - await waitFor(element(by.id(`room-view-title-${ toBeConverted }`))).toExist().withTimeout(6000); + await waitFor(element(by.id('room-view'))) + .toExist() + .withTimeout(20000); + await waitFor(element(by.id(`room-view-title-${toBeConverted}`))) + .toExist() + .withTimeout(6000); }); - after(async() => { + after(async () => { await tapBack(); - await waitFor(element(by.id('rooms-list-view'))).toExist().withTimeout(2000); + await waitFor(element(by.id('rooms-list-view'))) + .toExist() + .withTimeout(2000); }); }); describe('Move', () => { - before(async() => { + before(async () => { await createChannel(toBeMoved); }); - it('should move channel to a team', async() => { + it('should move channel to a team', async () => { await navigateToRoomActions(toBeMoved); await element(by.id('room-actions-scrollview')).scrollTo('bottom'); - await waitFor(element(by.id('room-actions-move-to-team'))).toExist().withTimeout(2000); + await waitFor(element(by.id('room-actions-move-to-team'))) + .toExist() + .withTimeout(2000); await element(by.id('room-actions-move-to-team')).tap(); - await waitFor(element(by.id('select-list-view'))).toExist().withTimeout(2000); + await waitFor(element(by.id('select-list-view'))) + .toExist() + .withTimeout(2000); await element(by.id('select-list-view-submit')).tap(); await sleep(2000); - await waitFor(element(by.id('select-list-view'))).toExist().withTimeout(2000); - await waitFor(element(by.id(`select-list-view-item-${ toBeConverted }`))).toExist().withTimeout(2000); - await element(by.id(`select-list-view-item-${ toBeConverted }`)).tap(); + await waitFor(element(by.id('select-list-view'))) + .toExist() + .withTimeout(2000); + await waitFor(element(by.id(`select-list-view-item-${toBeConverted}`))) + .toExist() + .withTimeout(2000); + await element(by.id(`select-list-view-item-${toBeConverted}`)).tap(); await element(by.id('select-list-view-submit')).atIndex(0).tap(); - await waitFor(element(by.label('After reading the previous intructions about this behavior, do you still want to move this channel to the selected team?'))).toExist().withTimeout(2000); + await waitFor( + element( + by.label( + 'After reading the previous intructions about this behavior, do you still want to move this channel to the selected team?' + ) + ) + ) + .toExist() + .withTimeout(2000); await element(by.text('Yes, move it!')).tap(); - await waitFor(element(by.id('room-view-header-team-channels'))).toExist().withTimeout(10000); + await waitFor(element(by.id('room-view-header-team-channels'))) + .toExist() + .withTimeout(10000); }); - after(async() => { + after(async () => { await tapBack(); - await waitFor(element(by.id('rooms-list-view'))).toExist().withTimeout(2000); + await waitFor(element(by.id('rooms-list-view'))) + .toExist() + .withTimeout(2000); }); }); describe('Convert Team to Channel and Delete toBeMoved channel within the Converted', () => { - it('should convert a team to a channel', async() => { + it('should convert a team to a channel', async () => { await navigateToRoomActions(toBeConverted); await element(by.id('room-actions-scrollview')).scrollTo('bottom'); - await waitFor(element(by.id('room-actions-convert-channel-to-team'))).toExist().withTimeout(2000); + await waitFor(element(by.id('room-actions-convert-channel-to-team'))) + .toExist() + .withTimeout(2000); await element(by.id('room-actions-convert-channel-to-team')).tap(); await sleep(2000); - await waitFor(element(by.id('select-list-view'))).toExist().withTimeout(2000); - await waitFor(element(by.id(`select-list-view-item-${ toBeMoved }`))).toExist().withTimeout(2000); - await element(by.id(`select-list-view-item-${ toBeMoved }`)).tap(); - await waitFor(element(by.id('select-list-view-submit'))).toExist().withTimeout(2000); + await waitFor(element(by.id('select-list-view'))) + .toExist() + .withTimeout(2000); + await waitFor(element(by.id(`select-list-view-item-${toBeMoved}`))) + .toExist() + .withTimeout(2000); + await element(by.id(`select-list-view-item-${toBeMoved}`)).tap(); + await waitFor(element(by.id('select-list-view-submit'))) + .toExist() + .withTimeout(2000); await element(by.id('select-list-view-submit')).tap(); - await waitFor(element(by.label('You are converting this Team to a Channel'))).toExist().withTimeout(2000); + await waitFor(element(by.label('You are converting this Team to a Channel'))) + .toExist() + .withTimeout(2000); await element(by.text('Convert')).tap(); - await waitFor(element(by.id('room-view'))).toExist().withTimeout(20000); - await waitFor(element(by.id(`room-view-title-${ toBeConverted }`))).toExist().withTimeout(6000); + await waitFor(element(by.id('room-view'))) + .toExist() + .withTimeout(20000); + await waitFor(element(by.id(`room-view-title-${toBeConverted}`))) + .toExist() + .withTimeout(6000); await tapBack(); - await waitFor(element(by.id('rooms-list-view'))).toExist().withTimeout(2000); - await waitFor(element(by.id(`rooms-list-view-item-${ toBeMoved }`))).toBeNotVisible().withTimeout(60000); + await waitFor(element(by.id('rooms-list-view'))) + .toExist() + .withTimeout(2000); + await waitFor(element(by.id(`rooms-list-view-item-${toBeMoved}`))) + .toBeNotVisible() + .withTimeout(60000); }); }); }); diff --git a/index.js b/index.js index bbd7a5786..49d315b97 100644 --- a/index.js +++ b/index.js @@ -1,6 +1,7 @@ import 'react-native-gesture-handler'; import 'react-native-console-time-polyfill'; import { AppRegistry } from 'react-native'; + import { name as appName, share as shareName } from './app.json'; if (__DEV__) { diff --git a/ios/RocketChatRN.xcodeproj/project.pbxproj b/ios/RocketChatRN.xcodeproj/project.pbxproj index 7284faac3..d54f2e216 100644 --- a/ios/RocketChatRN.xcodeproj/project.pbxproj +++ b/ios/RocketChatRN.xcodeproj/project.pbxproj @@ -1726,7 +1726,7 @@ INFOPLIST_FILE = NotificationService/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 11.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"; - MARKETING_VERSION = 4.19.0; + MARKETING_VERSION = 4.20.0; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = chat.rocket.reactnative.NotificationService; @@ -1763,7 +1763,7 @@ INFOPLIST_FILE = NotificationService/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 11.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"; - MARKETING_VERSION = 4.19.0; + MARKETING_VERSION = 4.20.0; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = chat.rocket.reactnative.NotificationService; PRODUCT_NAME = "$(TARGET_NAME)"; diff --git a/ios/RocketChatRN/Info.plist b/ios/RocketChatRN/Info.plist index 496e641fd..4c7ca4e19 100644 --- a/ios/RocketChatRN/Info.plist +++ b/ios/RocketChatRN/Info.plist @@ -26,7 +26,7 @@ <key>CFBundlePackageType</key> <string>APPL</string> <key>CFBundleShortVersionString</key> - <string>4.19.0</string> + <string>4.20.0</string> <key>CFBundleSignature</key> <string>????</string> <key>CFBundleURLTypes</key> @@ -103,6 +103,7 @@ </dict> <key>UIBackgroundModes</key> <array> + <string>audio</string> <string>fetch</string> <string>voip</string> </array> diff --git a/ios/ShareRocketChatRN/Info.plist b/ios/ShareRocketChatRN/Info.plist index e0b6f6042..84bc33705 100644 --- a/ios/ShareRocketChatRN/Info.plist +++ b/ios/ShareRocketChatRN/Info.plist @@ -26,7 +26,7 @@ <key>CFBundlePackageType</key> <string>XPC!</string> <key>CFBundleShortVersionString</key> - <string>4.19.0</string> + <string>4.20.0</string> <key>CFBundleVersion</key> <string>1</string> <key>KeychainGroup</key> diff --git a/jsconfig.json b/jsconfig.json index b65649bb2..7924c1d2d 100644 --- a/jsconfig.json +++ b/jsconfig.json @@ -1,9 +1,8 @@ { - "compilerOptions": { - "allowSyntheticDefaultImports": true, - "experimentalDecorators": true - }, - "exclude": [ - "node_modules" - ] + "compilerOptions": { + "jsx": "react", + "allowSyntheticDefaultImports": true, + "experimentalDecorators": true + }, + "exclude": ["node_modules"] } diff --git a/metro.config.js b/metro.config.js index 6adff212d..a4fb715eb 100644 --- a/metro.config.js +++ b/metro.config.js @@ -20,8 +20,6 @@ module.exports = { }, maxWorkers: 2, resolver: { - blocklistRE: blocklist([ - /ios\/Pods\/JitsiMeetSDK\/Frameworks\/JitsiMeet.framework\/assets\/node_modules\/react-native\/.*/ - ]) + blocklistRE: blocklist([/ios\/Pods\/JitsiMeetSDK\/Frameworks\/JitsiMeet.framework\/assets\/node_modules\/react-native\/.*/]) } }; diff --git a/package.json b/package.json index 65b63cb85..1d4a2ac1c 100644 --- a/package.json +++ b/package.json @@ -1,226 +1,242 @@ { - "name": "rocket-chat-reactnative", - "version": "4.19.0", - "private": true, - "scripts": { - "start": "react-native start", - "test": "jest", - "test-update": "jest --updateSnapshot", - "lint": "eslint .", - "ci": "npm run precommit && codecov", - "ios": "npx react-native run-ios", - "android": "npx react-native run-android --variant=experimentalPlayDebug", - "android-whitelabel": "npx react-native run-android --main-activity chat.rocket.reactnative.MainActivity --variant=experimentalPlayDebug --appId", - "log-android": "react-native log-android", - "snyk-protect": "snyk protect", - "precommit": "lint-staged", - "generate-source-maps-ios": "react-native bundle --platform ios --dev false --entry-file index.js --bundle-output ios-release.bundle --sourcemap-output ios-release.bundle.map", - "postinstall": "patch-package && jetify", - "prepare": "husky install" - }, - "lint-staged": { - "*.{js,ts,tsx}": [ - "eslint", - "jest --bail --findRelatedTests" - ] - }, - "dependencies": { - "@bugsnag/react-native": "^7.10.5", - "@codler/react-native-keyboard-aware-scroll-view": "^1.0.1", - "@nozbe/watermelondb": "0.19.0", - "@react-native-community/art": "^1.2.0", - "@react-native-community/async-storage": "1.12.1", - "@react-native-community/blur": "^3.6.0", - "@react-native-community/cameraroll": "4.0.4", - "@react-native-community/datetimepicker": "3.5.2", - "@react-native-community/hooks": "2.6.0", - "@react-native-community/masked-view": "0.1.11", - "@react-native-community/netinfo": "6.0.0", - "@react-native-community/picker": "^1.8.1", - "@react-native-community/slider": "3.0.3", - "@react-native-cookies/cookies": "6.0.8", - "@react-native-firebase/analytics": "^7.3.1", - "@react-native-firebase/app": "^8.2.0", - "@react-native-firebase/crashlytics": "^8.1.2", - "@react-navigation/drawer": "5.12.5", - "@react-navigation/native": "5.9.4", - "@react-navigation/stack": "5.14.5", - "@rocket.chat/react-native-fast-image": "^8.2.0", - "@rocket.chat/sdk": "RocketChat/Rocket.Chat.js.SDK#mobile", - "@rocket.chat/ui-kit": "0.13.0", - "bytebuffer": "^5.0.1", - "color2k": "1.2.4", - "commonmark": "git+https://github.com/RocketChat/commonmark.js.git", - "commonmark-react-renderer": "git+https://github.com/RocketChat/commonmark-react-renderer.git", - "dequal": "^2.0.2", - "ejson": "2.2.1", - "eslint-config-airbnb": "^18.1.0", - "expo-apple-authentication": "^2.2.1", - "expo-av": "8.2.1", - "expo-file-system": "9.0.1", - "expo-haptics": "8.2.1", - "expo-keep-awake": "8.2.1", - "expo-local-authentication": "9.2.0", - "expo-video-thumbnails": "5.1.0", - "expo-web-browser": "8.3.1", - "hoist-non-react-statics": "3.3.2", - "i18n-js": "3.8.0", - "js-base64": "3.6.1", - "js-sha256": "^0.9.0", - "lint-staged": "^11.1.0", - "lodash": "4.17.21", - "moment": "2.29.1", - "pretty-bytes": "5.6.0", - "prop-types": "15.7.2", - "react": "17.0.1", - "react-native": "RocketChat/react-native#0.64.2", - "react-native-animatable": "^1.3.3", - "react-native-appearance": "0.3.4", - "react-native-background-timer": "2.4.1", - "react-native-bootsplash": "3.2.4", - "react-native-config-reader": "^4.1.1", - "react-native-console-time-polyfill": "1.2.3", - "react-native-device-info": "8.1.3", - "react-native-document-picker": "5.2.0", - "react-native-easy-grid": "^0.2.2", - "react-native-easy-toast": "^1.2.0", - "react-native-gesture-handler": "^1.10.3", - "react-native-image-crop-picker": "RocketChat/react-native-image-crop-picker", - "react-native-image-progress": "^1.1.1", - "react-native-jitsi-meet": "RocketChat/react-native-jitsi-meet", - "react-native-keycommands": "2.0.3", - "react-native-localize": "2.1.1", - "react-native-mime-types": "2.3.0", - "react-native-mmkv-storage": "0.3.5", - "react-native-modal": "11.10.0", - "react-native-navigation-bar-color": "2.0.1", - "react-native-notifications": "2.1.7", - "react-native-notifier": "1.6.1", - "react-native-orientation-locker": "1.1.8", - "react-native-picker-select": "^8.0.4", - "react-native-platform-touchable": "1.1.1", - "react-native-popover-view": "4.0.1", - "react-native-progress": "4.1.2", - "react-native-prompt-android": "^1.1.0", - "react-native-reanimated": "1.9.0", - "react-native-restart": "0.0.22", - "react-native-safe-area-context": "3.2.0", - "react-native-screens": "2.9.0", - "react-native-scroll-bottom-sheet": "0.6.2", - "react-native-scrollable-tab-view": "^1.0.0", - "react-native-simple-crypto": "RocketChat/react-native-simple-crypto#0.5.0", - "react-native-slowlog": "^1.0.2", - "react-native-ui-lib": "RocketChat/react-native-ui-lib#minor-improvements", - "react-native-unimodules": "0.10.1", - "react-native-vector-icons": "8.1.0", - "react-native-webview": "10.3.2", - "react-redux": "7.2.4", - "reactotron-react-native": "5.0.0", - "redux": "4.1.0", - "redux-immutable-state-invariant": "2.1.0", - "redux-saga": "1.1.3", - "remove-markdown": "^0.3.0", - "reselect": "4.0.0", - "rn-extensions-share": "RocketChat/rn-extensions-share", - "rn-fetch-blob": "0.12.0", - "rn-root-view": "1.0.3", - "semver": "7.3.5", - "ua-parser-js": "0.7.28", - "url-parse": "1.5.1", - "use-deep-compare-effect": "1.6.1", - "xregexp": "5.0.2" - }, - "devDependencies": { - "@babel/core": "^7.12.9", - "@babel/eslint-parser": "^7.13.4", - "@babel/eslint-plugin": "^7.13.0", - "@babel/plugin-proposal-decorators": "^7.8.3", - "@babel/runtime": "^7.12.5", - "@bugsnag/source-maps": "^2.2.0", - "@storybook/addon-storyshots": "5.3.21", - "@storybook/react-native": "5.3.25", - "@types/react-native": "^0.62.7", - "axios": "0.21.1", - "babel-jest": "^27.0.6", - "babel-plugin-transform-remove-console": "^6.9.4", - "codecov": "3.8.2", - "detox": "18.17.0", - "eslint": "7.14.0", - "eslint-plugin-import": "2.22.0", - "eslint-plugin-jsx-a11y": "6.3.1", - "eslint-plugin-react": "7.20.3", - "eslint-plugin-react-native": "3.8.1", - "husky": "^6.0.0", - "identity-obj-proxy": "^3.0.0", - "jest": "^27.0.6", - "jest-cli": "^27.0.6", - "metro-react-native-babel-preset": "^0.64.0", - "mocha": "9.0.1", - "otp.js": "1.2.0", - "patch-package": "6.4.7", - "react-dom": "17.0.1", - "react-test-renderer": "17.0.1", - "reactotron-redux": "3.1.3", - "reactotron-redux-saga": "4.2.3" - }, - "jest": { - "testPathIgnorePatterns": [ - "e2e", - "node_modules" - ], - "transformIgnorePatterns": [ - "node_modules/(?!(jest-)?@?react-native|@react-native-community|@react-navigation)" - ], - "preset": "react-native", - "coverageDirectory": "./coverage/", - "collectCoverage": true, - "moduleNameMapper": { - ".+\\.(css|styl|less|sass|scss)$": "identity-obj-proxy", - ".+\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$": "<rootDir>/__mocks__/fileMock.js" - }, - "transform": { - "^.+\\.js$": "<rootDir>/node_modules/react-native/jest/preprocessor.js" - } - }, - "snyk": true, - "engines": { - "node": ">=8.x", - "npm": ">=4.x" - }, - "detox": { - "runner-config": "e2e/.mocharc.json", - "specs": "e2e/tests", - "configurations": { - "ios.sim.debug": { - "binaryPath": "ios/build/Build/Products/Debug-iphonesimulator/Rocket.Chat Experimental.app", - "build": "xcodebuild -workspace ios/RocketChatRN.xcworkspace -scheme RocketChatRN -configuration Debug -sdk iphonesimulator -derivedDataPath ios/build", - "type": "ios.simulator", - "device": { - "type": "iPhone 11 Pro" - } - }, - "ios.sim.release": { - "binaryPath": "ios/build/Build/Products/Release-iphonesimulator/Rocket.Chat Experimental.app", - "build": "xcodebuild -workspace ios/RocketChatRN.xcworkspace -scheme RocketChatRN -configuration Release -sdk iphonesimulator -derivedDataPath ios/build", - "type": "ios.simulator", - "device": { - "type": "iPhone 11 Pro" - }, - "artifacts": { - "plugins": { - "screenshot": { - "enabled": true, - "shouldTakeAutomaticSnapshots": true, - "keepOnlyFailedTestsArtifacts": true, - "takeWhen": { - "testStart": true, - "testDone": true, - "appNotReady": true - } - } - } - } - } - } - } + "name": "rocket-chat-reactnative", + "version": "4.20.0", + "private": true, + "scripts": { + "start": "react-native start", + "test": "jest", + "test-update": "jest --updateSnapshot", + "lint": "eslint . && tsc", + "prettier-lint": "prettier --write . && yarn lint", + "ci": "npm run precommit && codecov", + "ios": "npx react-native run-ios", + "android": "npx react-native run-android --variant=experimentalPlayDebug", + "android-whitelabel": "npx react-native run-android --main-activity chat.rocket.reactnative.MainActivity --variant=experimentalPlayDebug --appId", + "log-android": "react-native log-android", + "snyk-protect": "snyk protect", + "precommit": "lint-staged", + "generate-source-maps-ios": "react-native bundle --platform ios --dev false --entry-file index.js --bundle-output ios-release.bundle --sourcemap-output ios-release.bundle.map", + "postinstall": "patch-package && jetify", + "prepare": "husky install" + }, + "lint-staged": { + "*.{js,ts,tsx}": [ + "prettier --write", + "eslint", + "jest --bail --findRelatedTests" + ] + }, + "dependencies": { + "@bugsnag/react-native": "^7.10.5", + "@codler/react-native-keyboard-aware-scroll-view": "^1.0.1", + "@nozbe/watermelondb": "0.19.0", + "@react-native-community/art": "^1.2.0", + "@react-native-community/async-storage": "1.12.1", + "@react-native-community/blur": "^3.6.0", + "@react-native-community/cameraroll": "4.0.4", + "@react-native-community/datetimepicker": "3.5.2", + "@react-native-community/hooks": "2.6.0", + "@react-native-community/masked-view": "0.1.11", + "@react-native-community/netinfo": "6.0.0", + "@react-native-community/picker": "^1.8.1", + "@react-native-community/slider": "3.0.3", + "@react-native-cookies/cookies": "6.0.8", + "@react-native-firebase/analytics": "^7.3.1", + "@react-native-firebase/app": "^8.2.0", + "@react-native-firebase/crashlytics": "^8.1.2", + "@react-navigation/drawer": "5.12.5", + "@react-navigation/native": "5.9.4", + "@react-navigation/stack": "5.14.5", + "@rocket.chat/react-native-fast-image": "^8.2.0", + "@rocket.chat/sdk": "RocketChat/Rocket.Chat.js.SDK#mobile", + "@rocket.chat/ui-kit": "0.13.0", + "bytebuffer": "^5.0.1", + "color2k": "1.2.4", + "commonmark": "git+https://github.com/RocketChat/commonmark.js.git", + "commonmark-react-renderer": "git+https://github.com/RocketChat/commonmark-react-renderer.git", + "dequal": "^2.0.2", + "ejson": "2.2.1", + "eslint-config-airbnb": "^18.1.0", + "expo-apple-authentication": "^2.2.1", + "expo-av": "8.2.1", + "expo-file-system": "9.0.1", + "expo-haptics": "8.2.1", + "expo-keep-awake": "8.2.1", + "expo-local-authentication": "9.2.0", + "expo-video-thumbnails": "5.1.0", + "expo-web-browser": "8.3.1", + "hoist-non-react-statics": "3.3.2", + "i18n-js": "3.8.0", + "js-base64": "3.6.1", + "js-sha256": "^0.9.0", + "lint-staged": "^11.1.0", + "lodash": "4.17.21", + "moment": "2.29.1", + "pretty-bytes": "5.6.0", + "prop-types": "15.7.2", + "react": "17.0.1", + "react-native": "RocketChat/react-native#0.64.2", + "react-native-animatable": "^1.3.3", + "react-native-appearance": "0.3.4", + "react-native-background-timer": "2.4.1", + "react-native-bootsplash": "3.2.4", + "react-native-config-reader": "^4.1.1", + "react-native-console-time-polyfill": "1.2.3", + "react-native-device-info": "8.1.3", + "react-native-document-picker": "5.2.0", + "react-native-easy-grid": "^0.2.2", + "react-native-easy-toast": "^1.2.0", + "react-native-gesture-handler": "^1.10.3", + "react-native-image-crop-picker": "RocketChat/react-native-image-crop-picker", + "react-native-image-progress": "^1.1.1", + "react-native-jitsi-meet": "RocketChat/react-native-jitsi-meet", + "react-native-keycommands": "2.0.3", + "react-native-localize": "2.1.1", + "react-native-mime-types": "2.3.0", + "react-native-mmkv-storage": "0.3.5", + "react-native-modal": "11.10.0", + "react-native-navigation-bar-color": "2.0.1", + "react-native-notifications": "2.1.7", + "react-native-notifier": "1.6.1", + "react-native-orientation-locker": "1.1.8", + "react-native-picker-select": "^8.0.4", + "react-native-platform-touchable": "1.1.1", + "react-native-popover-view": "4.0.1", + "react-native-progress": "4.1.2", + "react-native-prompt-android": "^1.1.0", + "react-native-reanimated": "1.9.0", + "react-native-restart": "0.0.22", + "react-native-safe-area-context": "3.2.0", + "react-native-screens": "2.9.0", + "react-native-scroll-bottom-sheet": "0.6.2", + "react-native-scrollable-tab-view": "^1.0.0", + "react-native-simple-crypto": "RocketChat/react-native-simple-crypto#0.5.0", + "react-native-slowlog": "^1.0.2", + "react-native-ui-lib": "RocketChat/react-native-ui-lib#minor-improvements", + "react-native-unimodules": "0.10.1", + "react-native-vector-icons": "8.1.0", + "react-native-webview": "10.3.2", + "react-redux": "7.2.4", + "reactotron-react-native": "5.0.0", + "redux": "4.1.0", + "redux-immutable-state-invariant": "2.1.0", + "redux-saga": "1.1.3", + "remove-markdown": "^0.3.0", + "reselect": "4.0.0", + "rn-extensions-share": "RocketChat/rn-extensions-share", + "rn-fetch-blob": "0.12.0", + "rn-root-view": "1.0.3", + "semver": "7.3.5", + "ua-parser-js": "0.7.28", + "url-parse": "1.5.1", + "use-deep-compare-effect": "1.6.1", + "xregexp": "5.0.2" + }, + "devDependencies": { + "@babel/core": "^7.12.9", + "@babel/eslint-parser": "^7.14.7", + "@babel/eslint-plugin": "^7.13.0", + "@babel/plugin-proposal-decorators": "^7.8.3", + "@babel/runtime": "^7.12.5", + "@bugsnag/source-maps": "^2.2.0", + "@rocket.chat/eslint-config": "^0.4.0", + "@storybook/addon-storyshots": "5.3.21", + "@storybook/react-native": "5.3.25", + "@types/jest": "^26.0.24", + "@types/lodash": "^4.14.171", + "@types/react": "^17.0.14", + "@types/react-native": "^0.62.7", + "@types/react-native-config-reader": "^4.1.0", + "@types/react-native-platform-touchable": "^1.1.2", + "@types/react-native-scrollable-tab-view": "^0.10.2", + "@types/react-redux": "^7.1.18", + "@types/react-test-renderer": "^17.0.1", + "@typescript-eslint/eslint-plugin": "^4.28.3", + "@typescript-eslint/parser": "^4.28.5", + "axios": "0.21.1", + "babel-jest": "^27.0.6", + "babel-plugin-transform-remove-console": "^6.9.4", + "codecov": "3.8.2", + "detox": "18.17.0", + "eslint": "^7.31.0", + "eslint-config-prettier": "^8.3.0", + "eslint-plugin-import": "2.22.0", + "eslint-plugin-jsx-a11y": "6.3.1", + "eslint-plugin-react": "7.20.3", + "eslint-plugin-react-native": "3.8.1", + "husky": "^6.0.0", + "identity-obj-proxy": "^3.0.0", + "jest": "^27.0.6", + "jest-cli": "^27.0.6", + "metro-react-native-babel-preset": "^0.64.0", + "mocha": "9.0.1", + "otp.js": "1.2.0", + "patch-package": "6.4.7", + "prettier": "^2.3.2", + "react-dom": "17.0.1", + "react-test-renderer": "17.0.1", + "reactotron-redux": "3.1.3", + "reactotron-redux-saga": "4.2.3", + "typescript": "^4.3.5" + }, + "jest": { + "testPathIgnorePatterns": [ + "e2e", + "node_modules" + ], + "transformIgnorePatterns": [ + "node_modules/(?!(jest-)?@?react-native|@react-native-community|@react-navigation)" + ], + "preset": "react-native", + "coverageDirectory": "./coverage/", + "collectCoverage": true, + "moduleNameMapper": { + ".+\\.(css|styl|less|sass|scss)$": "identity-obj-proxy", + ".+\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$": "<rootDir>/__mocks__/fileMock.js" + }, + "transform": { + "^.+\\.js$": "<rootDir>/node_modules/react-native/jest/preprocessor.js" + } + }, + "snyk": true, + "engines": { + "node": ">=8.x", + "npm": ">=4.x" + }, + "detox": { + "runner-config": "e2e/.mocharc.json", + "specs": "e2e/tests", + "configurations": { + "ios.sim.debug": { + "binaryPath": "ios/build/Build/Products/Debug-iphonesimulator/Rocket.Chat Experimental.app", + "build": "xcodebuild -workspace ios/RocketChatRN.xcworkspace -scheme RocketChatRN -configuration Debug -sdk iphonesimulator -derivedDataPath ios/build", + "type": "ios.simulator", + "device": { + "type": "iPhone 11 Pro" + } + }, + "ios.sim.release": { + "binaryPath": "ios/build/Build/Products/Release-iphonesimulator/Rocket.Chat Experimental.app", + "build": "xcodebuild -workspace ios/RocketChatRN.xcworkspace -scheme RocketChatRN -configuration Release -sdk iphonesimulator -derivedDataPath ios/build", + "type": "ios.simulator", + "device": { + "type": "iPhone 11 Pro" + }, + "artifacts": { + "plugins": { + "screenshot": { + "enabled": true, + "shouldTakeAutomaticSnapshots": true, + "keepOnlyFailedTestsArtifacts": true, + "takeWhen": { + "testStart": true, + "testDone": true, + "appNotReady": true + } + } + } + } + } + } + } } diff --git a/storybook/stories/Avatar.js b/storybook/stories/Avatar.js index 9943f832e..c444b5cd0 100644 --- a/storybook/stories/Avatar.js +++ b/storybook/stories/Avatar.js @@ -19,22 +19,9 @@ const _theme = 'light'; const stories = storiesOf('Avatar', module); -stories.add('Avatar by text', () => ( - <Avatar - text='Avatar' - server={server} - size={56} - /> -)); +stories.add('Avatar by text', () => <Avatar text='Avatar' server={server} size={56} />); -stories.add('Avatar by roomId', () => ( - <Avatar - type='p' - rid='devWBbYr7inwupPqK' - server={server} - size={56} - /> -)); +stories.add('Avatar by roomId', () => <Avatar type='p' rid='devWBbYr7inwupPqK' server={server} size={56} />); stories.add('Avatar by url', () => ( <Avatar @@ -44,68 +31,23 @@ stories.add('Avatar by url', () => ( /> )); -stories.add('Avatar by path', () => ( - <Avatar - avatar='/avatar/diego.mello' - server={server} - size={56} - /> -)); +stories.add('Avatar by path', () => <Avatar avatar='/avatar/diego.mello' server={server} size={56} />); stories.add('With ETag', () => ( - <Avatar - type='d' - text='djorkaeff.alexandre' - avatarETag='5ag8KffJcZj9m5rCv' - server={server} - size={56} - /> + <Avatar type='d' text='djorkaeff.alexandre' avatarETag='5ag8KffJcZj9m5rCv' server={server} size={56} /> )); -stories.add('Without ETag', () => ( - <Avatar - type='d' - text='djorkaeff.alexandre' - server={server} - size={56} - /> -)); +stories.add('Without ETag', () => <Avatar type='d' text='djorkaeff.alexandre' server={server} size={56} />); stories.add('Emoji', () => ( - <Avatar - emoji='troll' - getCustomEmoji={() => ({ name: 'troll', extension: 'jpg' })} - server={server} - size={56} - /> + <Avatar emoji='troll' getCustomEmoji={() => ({ name: 'troll', extension: 'jpg' })} server={server} size={56} /> )); -stories.add('Direct', () => ( - <Avatar - text='diego.mello' - server={server} - type='d' - size={56} - /> -)); +stories.add('Direct', () => <Avatar text='diego.mello' server={server} type='d' size={56} />); -stories.add('Channel', () => ( - <Avatar - text='general' - server={server} - type='c' - size={56} - /> -)); +stories.add('Channel', () => <Avatar text='general' server={server} type='c' size={56} />); -stories.add('Touchable', () => ( - <Avatar - text='Avatar' - server={server} - onPress={() => console.log('Pressed!')} - size={56} - /> -)); +stories.add('Touchable', () => <Avatar text='Avatar' server={server} onPress={() => console.log('Pressed!')} size={56} />); stories.add('Static', () => ( <Avatar @@ -116,51 +58,16 @@ stories.add('Static', () => ( /> )); -stories.add('Avatar by roomId', () => ( - <Avatar - type='p' - rid='devWBbYr7inwupPqK' - server={server} - size={56} - /> -)); +stories.add('Avatar by roomId', () => <Avatar type='p' rid='devWBbYr7inwupPqK' server={server} size={56} />); -stories.add('Custom borderRadius', () => ( - <Avatar - text='Avatar' - server={server} - borderRadius={28} - size={56} - /> -)); +stories.add('Custom borderRadius', () => <Avatar text='Avatar' server={server} borderRadius={28} size={56} />); stories.add('Children', () => ( - <Avatar - text='Avatar' - server={server} - size={56} - > - <Status - size={24} - style={[sharedStyles.status, styles.status]} - theme={_theme} - /> + <Avatar text='Avatar' server={server} size={56}> + <Status size={24} style={[sharedStyles.status, styles.status]} theme={_theme} /> </Avatar> )); -stories.add('Wrong server', () => ( - <Avatar - text='Avatar' - server='https://google.com' - size={56} - /> -)); +stories.add('Wrong server', () => <Avatar text='Avatar' server='https://google.com' size={56} />); -stories.add('Custom style', () => ( - <Avatar - text='Avatar' - server={server} - size={56} - style={styles.custom} - /> -)); +stories.add('Custom style', () => <Avatar text='Avatar' server={server} size={56} style={styles.custom} />); diff --git a/storybook/stories/HeaderButtons.js b/storybook/stories/HeaderButtons.js index a0d8cd97f..12b9c34b4 100644 --- a/storybook/stories/HeaderButtons.js +++ b/storybook/stories/HeaderButtons.js @@ -10,11 +10,7 @@ import { ThemeContext } from '../../app/theme'; const stories = storiesOf('Header Buttons', module); const HeaderExample = ({ left, right }) => ( - <Header - headerLeft={left} - headerTitle={() => <View style={{ flex: 1 }} />} - headerRight={right} - /> + <Header headerLeft={left} headerTitle={() => <View style={{ flex: 1 }} />} headerRight={right} /> ); stories.add('title', () => ( @@ -94,9 +90,7 @@ stories.add('badge', () => ( )); const ThemeStory = ({ theme }) => ( - <ThemeContext.Provider - value={{ theme }} - > + <ThemeContext.Provider value={{ theme }}> <HeaderExample left={() => ( <HeaderButton.Container left> @@ -123,40 +117,12 @@ stories.add('themes', () => ( stories.add('common', () => ( <> - <HeaderExample - left={() => ( - <HeaderButton.Drawer /> - )} - /> - <HeaderExample - left={() => ( - <HeaderButton.CloseModal /> - )} - /> - <HeaderExample - left={() => ( - <HeaderButton.CancelModal /> - )} - /> - <HeaderExample - right={() => ( - <HeaderButton.More /> - )} - /> - <HeaderExample - right={() => ( - <HeaderButton.Download /> - )} - /> - <HeaderExample - right={() => ( - <HeaderButton.Preferences /> - )} - /> - <HeaderExample - right={() => ( - <HeaderButton.Legal /> - )} - /> + <HeaderExample left={() => <HeaderButton.Drawer />} /> + <HeaderExample left={() => <HeaderButton.CloseModal />} /> + <HeaderExample left={() => <HeaderButton.CancelModal />} /> + <HeaderExample right={() => <HeaderButton.More />} /> + <HeaderExample right={() => <HeaderButton.Download />} /> + <HeaderExample right={() => <HeaderButton.Preferences />} /> + <HeaderExample right={() => <HeaderButton.Legal />} /> </> )); diff --git a/storybook/stories/List.js b/storybook/stories/List.js index b445a1972..5b7ac2612 100644 --- a/storybook/stories/List.js +++ b/storybook/stories/List.js @@ -42,12 +42,11 @@ stories.add('pressable', () => ( <List.Separator /> <List.Item title='Press me' onPress={() => alert('Hi there!')} translateTitle={false} /> <List.Separator /> - <List.Item title={'I\'m disabled'} onPress={() => alert('Hi there!')} disabled translateTitle={false} /> + <List.Item title={"I'm disabled"} onPress={() => alert('Hi there!')} disabled translateTitle={false} /> <List.Separator /> </List.Container> )); - stories.add('header', () => ( <List.Container> <List.Header title='Chats' /> @@ -177,9 +176,7 @@ const ListFull = () => ( ); const ThemeStory = ({ theme }) => ( - <ThemeContext.Provider - value={{ theme }} - > + <ThemeContext.Provider value={{ theme }}> <ListFull /> </ThemeContext.Provider> ); @@ -189,9 +186,7 @@ stories.add('with dark theme', () => <ThemeStory theme='dark' />); stories.add('with black theme', () => <ThemeStory theme='black' />); const FontStory = ({ fontScale }) => ( - <DimensionsContext.Provider - value={{ fontScale }} - > + <DimensionsContext.Provider value={{ fontScale }}> <ListFull /> </DimensionsContext.Provider> ); diff --git a/storybook/stories/Markdown.js b/storybook/stories/Markdown.js index bffd5a2aa..4f4cb2955 100644 --- a/storybook/stories/Markdown.js +++ b/storybook/stories/Markdown.js @@ -6,7 +6,6 @@ import { storiesOf } from '@storybook/react-native'; import Markdown from '../../app/containers/markdown'; import { themes } from '../../app/constants/colors'; - const theme = 'light'; const styles = StyleSheet.create({ @@ -22,7 +21,8 @@ const styles = StyleSheet.create({ }); const baseUrl = 'https://open.rocket.chat'; -const longText = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.'; +const longText = + 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.'; const lineBreakText = `a b c @@ -33,7 +33,7 @@ d e`; const sequentialEmptySpacesText = 'a b c'; -const getCustomEmoji = (content) => { +const getCustomEmoji = content => { const customEmoji = { marioparty: { name: content, extension: 'gif' }, react_rocket: { name: content, extension: 'png' }, @@ -47,55 +47,24 @@ const stories = storiesOf('Markdown', module); stories.add('Text', () => ( <View style={styles.container}> <Markdown msg='This is Rocket.Chat' theme={theme} /> - <Markdown - msg={longText} - theme={theme} - /> - <Markdown - msg={lineBreakText} - theme={theme} - /> - <Markdown - msg={sequentialEmptySpacesText} - theme={theme} - /> - <Markdown - msg='Strong emphasis, aka bold, with **asterisks** or __underscores__' - theme={theme} - /> + <Markdown msg={longText} theme={theme} /> + <Markdown msg={lineBreakText} theme={theme} /> + <Markdown msg={sequentialEmptySpacesText} theme={theme} /> + <Markdown msg='Strong emphasis, aka bold, with **asterisks** or __underscores__' theme={theme} /> </View> )); stories.add('Edited', () => ( <View style={styles.container}> - <Markdown - msg='This is edited' - theme={theme} - isEdited - /> + <Markdown msg='This is edited' theme={theme} isEdited /> </View> )); stories.add('Preview', () => ( <View style={styles.container}> - <Markdown - msg={longText} - theme={theme} - numberOfLines={1} - preview - /> - <Markdown - msg={lineBreakText} - theme={theme} - numberOfLines={1} - preview - /> - <Markdown - msg={sequentialEmptySpacesText} - theme={theme} - numberOfLines={1} - preview - /> + <Markdown msg={longText} theme={theme} numberOfLines={1} preview /> + <Markdown msg={lineBreakText} theme={theme} numberOfLines={1} preview /> + <Markdown msg={sequentialEmptySpacesText} theme={theme} numberOfLines={1} preview /> <Markdown msg='@rocket.cat @name1 @all @here @unknown #general #unknown' theme={theme} @@ -110,13 +79,7 @@ stories.add('Preview', () => ( channels={[{ _id: '123', name: 'test-channel' }]} username='rocket.cat' /> - <Markdown - msg='Testing: 😃 :+1: :marioparty:' - getCustomEmoji={getCustomEmoji} - theme={theme} - numberOfLines={1} - preview - /> + <Markdown msg='Testing: 😃 :+1: :marioparty:' getCustomEmoji={getCustomEmoji} theme={theme} numberOfLines={1} preview /> </View> )); @@ -150,11 +113,7 @@ stories.add('Mentions', () => ( stories.add('Hashtag', () => ( <View style={styles.container}> - <Markdown - msg='#test-channel #unknown' - theme={theme} - channels={[{ _id: '123', name: 'test-channel' }]} - /> + <Markdown msg='#test-channel #unknown' theme={theme} channels={[{ _id: '123', name: 'test-channel' }]} /> </View> )); @@ -168,12 +127,7 @@ stories.add('Emoji', () => ( getCustomEmoji={getCustomEmoji} baseUrl={baseUrl} /> - <Markdown - msg='😃 :+1: :marioparty:' - theme={theme} - getCustomEmoji={getCustomEmoji} - baseUrl={baseUrl} - /> + <Markdown msg='😃 :+1: :marioparty:' theme={theme} getCustomEmoji={getCustomEmoji} baseUrl={baseUrl} /> </View> )); @@ -194,7 +148,6 @@ stories.add('Links', () => ( </View> )); - stories.add('Image', () => ( <View style={styles.container}> <Markdown msg='![alt text](https://play.google.com/intl/en_us/badges/images/badge_new.png)' theme={theme} /> @@ -203,39 +156,18 @@ stories.add('Image', () => ( stories.add('Headers', () => ( <View style={styles.container}> - <Markdown - msg='# Header 1' - theme={theme} - /> - <Markdown - msg='## Header 2' - theme={theme} - /> - <Markdown - msg='### Header 3' - theme={theme} - /> - <Markdown - msg='#### Header 4' - theme={theme} - /> - <Markdown - msg='##### Header 5' - theme={theme} - /> - <Markdown - msg='###### Header 6' - theme={theme} - /> + <Markdown msg='# Header 1' theme={theme} /> + <Markdown msg='## Header 2' theme={theme} /> + <Markdown msg='### Header 3' theme={theme} /> + <Markdown msg='#### Header 4' theme={theme} /> + <Markdown msg='##### Header 5' theme={theme} /> + <Markdown msg='###### Header 6' theme={theme} /> </View> )); stories.add('Code', () => ( <View style={styles.container}> - <Markdown - msg='This is `inline code`' - theme={theme} - /> + <Markdown msg='This is `inline code`' theme={theme} /> <Markdown msg='Inline `code` has `back-ticks around` it. ``` @@ -248,14 +180,8 @@ Code block stories.add('Lists', () => ( <View style={styles.container}> - <Markdown - msg={'* Open Source\n* Rocket.Chat\n - nodejs\n - ReactNative'} - theme={theme} - /> - <Markdown - msg={'1. Open Source\n2. Rocket.Chat'} - theme={theme} - /> + <Markdown msg={'* Open Source\n* Rocket.Chat\n - nodejs\n - ReactNative'} theme={theme} /> + <Markdown msg={'1. Open Source\n2. Rocket.Chat'} theme={theme} /> </View> )); diff --git a/storybook/stories/Message.js b/storybook/stories/Message.js index cc916ca1e..30db507b0 100644 --- a/storybook/stories/Message.js +++ b/storybook/stories/Message.js @@ -1,6 +1,6 @@ /* eslint-disable import/no-extraneous-dependencies */ import React from 'react'; -import { StyleSheet, ScrollView } from 'react-native'; +import { ScrollView, StyleSheet } from 'react-native'; import { Provider } from 'react-redux'; import { storiesOf } from '@storybook/react-native'; // import moment from 'moment'; @@ -9,14 +9,12 @@ import MessageComponent from '../../app/containers/message/Message'; import messagesStatus from '../../app/constants/messagesStatus'; import MessageSeparator from '../../app/views/RoomView/Separator'; import MessageContext from '../../app/containers/message/Context'; - import { themes } from '../../app/constants/colors'; import { store } from './index'; const _theme = 'light'; -const styles = StyleSheet.create({ -}); +const styles = StyleSheet.create({}); const user = { id: 'y8bd77ptZswPj3EW8', @@ -29,9 +27,10 @@ const author = { }; const baseUrl = 'https://open.rocket.chat'; const date = new Date(2017, 10, 10, 10); -const longText = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.'; +const longText = + 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.'; -const getCustomEmoji = (content) => { +const getCustomEmoji = content => { const customEmoji = { marioparty: { name: content, extension: 'gif' }, react_rocket: { name: content, extension: 'png' }, @@ -54,8 +53,7 @@ export const MessageDecorator = story => ( onDiscussionPress: () => {}, onReactionLongPress: () => {}, threadBadgeColor: themes.light.tunreadColor - }} - > + }}> {story()} </MessageContext.Provider> ); @@ -106,9 +104,7 @@ stories.add('Grouped messages', () => ( </> )); -stories.add('Without header', () => ( - <Message msg='Message' isHeader={false} /> -)); +stories.add('Without header', () => <Message msg='Message' isHeader={false} />); stories.add('With alias', () => ( <> @@ -124,54 +120,34 @@ stories.add('With alias', () => ( </> )); -stories.add('Edited', () => ( - <Message msg='Message' edited /> -)); +stories.add('Edited', () => <Message msg='Message' edited />); stories.add('Encrypted', () => ( <> - <Message - msg='Message' - type='e2e' - /> - <Message - msg='Message Encrypted without Header' - isHeader={false} - type='e2e' - /> + <Message msg='Message' type='e2e' /> + <Message msg='Message Encrypted without Header' isHeader={false} type='e2e' /> <Message msg='Message Encrypted with Reactions' - reactions={[{ - emoji: ':joy:', - usernames: [user.username] - }, { - emoji: ':marioparty:', - usernames: [user.username] - }, { - emoji: ':thinking:', - usernames: [user.username] - }]} + reactions={[ + { + emoji: ':joy:', + usernames: [user.username] + }, + { + emoji: ':marioparty:', + usernames: [user.username] + }, + { + emoji: ':thinking:', + usernames: [user.username] + } + ]} onReactionPress={() => {}} type='e2e' /> - <Message - msg='Thread reply encrypted' - tmid='1' - tmsg='Thread with emoji :) :joy:' - isThreadReply - type='e2e' - /> - <Message - msg='Temp message encrypted' - status={messagesStatus.TEMP} - isTemp - type='e2e' - /> - <Message - msg='Message Edited encrypted' - edited - type='e2e' - /> + <Message msg='Thread reply encrypted' tmid='1' tmsg='Thread with emoji :) :joy:' isThreadReply type='e2e' /> + <Message msg='Temp message encrypted' status={messagesStatus.TEMP} isTemp type='e2e' /> + <Message msg='Message Edited encrypted' edited type='e2e' /> <Message hasError msg='This message has error and is encrypted' @@ -179,19 +155,8 @@ stories.add('Encrypted', () => ( onErrorPress={() => alert('Error pressed')} type='e2e' /> - <Message - msg='Read Receipt encrypted with Header' - isReadReceiptEnabled - read - type='e2e' - /> - <Message - msg='Read Receipt encrypted without Header' - isReadReceiptEnabled - read - isHeader={false} - type='e2e' - /> + <Message msg='Read Receipt encrypted with Header' isReadReceiptEnabled read type='e2e' /> + <Message msg='Read Receipt encrypted without Header' isReadReceiptEnabled read isHeader={false} type='e2e' /> </> )); @@ -212,10 +177,7 @@ stories.add('Lists', () => ( )); stories.add('Static avatar', () => ( - <Message - msg='Message' - avatar='https://pbs.twimg.com/profile_images/1016397063649660929/14EIApTi_400x400.jpg' - /> + <Message msg='Message' avatar='https://pbs.twimg.com/profile_images/1016397063649660929/14EIApTi_400x400.jpg' /> )); stories.add('Full name', () => ( @@ -234,33 +196,47 @@ stories.add('Mentions', () => ( <> <Message msg='@rocket.cat @diego.mello @all @here #general' - mentions={[{ - username: 'rocket.cat' - }, { - username: 'diego.mello' - }, { - username: 'all' - }, { - username: 'here' - }]} - channels={[{ - name: 'general' - }]} + mentions={[ + { + username: 'rocket.cat' + }, + { + username: 'diego.mello' + }, + { + username: 'all' + }, + { + username: 'here' + } + ]} + channels={[ + { + name: 'general' + } + ]} /> <Message msg='@rocket.cat Lorem ipsum dolor @diego.mello sit amet, @all consectetur adipiscing @here elit, sed do eiusmod tempor #general incididunt ut labore et dolore magna aliqua.' - mentions={[{ - username: 'rocket.cat' - }, { - username: 'diego.mello' - }, { - username: 'all' - }, { - username: 'here' - }]} - channels={[{ - name: 'general' - }]} + mentions={[ + { + username: 'rocket.cat' + }, + { + username: 'diego.mello' + }, + { + username: 'all' + }, + { + username: 'here' + } + ]} + channels={[ + { + name: 'general' + } + ]} /> </> )); @@ -276,56 +252,68 @@ stories.add('Emojis', () => ( </> )); -stories.add('Time format', () => ( - <Message msg='Testing' timeFormat='DD MMMM YYYY' /> -)); +stories.add('Time format', () => <Message msg='Testing' timeFormat='DD MMMM YYYY' />); stories.add('Reactions', () => ( <> <Message msg='Reactions' - reactions={[{ - emoji: ':joy:', - usernames: [user.username] - }, { - emoji: ':marioparty:', - usernames: new Array(99) - }, { - emoji: ':thinking:', - usernames: new Array(999) - }, { - emoji: ':thinking:', - usernames: new Array(9999) - }]} + reactions={[ + { + emoji: ':joy:', + usernames: [user.username] + }, + { + emoji: ':marioparty:', + usernames: new Array(99) + }, + { + emoji: ':thinking:', + usernames: new Array(999) + }, + { + emoji: ':thinking:', + usernames: new Array(9999) + } + ]} onReactionPress={() => {}} /> <Message msg='Multiple Reactions' - reactions={[{ - emoji: ':marioparty:', - usernames: [user.username] - }, { - emoji: ':react_rocket:', - usernames: [user.username] - }, { - emoji: ':nyan_rocket:', - usernames: [user.username] - }, { - emoji: ':heart:', - usernames: [user.username] - }, { - emoji: ':dog:', - usernames: [user.username] - }, { - emoji: ':grinning:', - usernames: [user.username] - }, { - emoji: ':grimacing:', - usernames: [user.username] - }, { - emoji: ':grin:', - usernames: [user.username] - }]} + reactions={[ + { + emoji: ':marioparty:', + usernames: [user.username] + }, + { + emoji: ':react_rocket:', + usernames: [user.username] + }, + { + emoji: ':nyan_rocket:', + usernames: [user.username] + }, + { + emoji: ':heart:', + usernames: [user.username] + }, + { + emoji: ':dog:', + usernames: [user.username] + }, + { + emoji: ':grinning:', + usernames: [user.username] + }, + { + emoji: ':grimacing:', + usernames: [user.username] + }, + { + emoji: ':grin:', + usernames: [user.username] + } + ]} onReactionPress={() => {}} /> </> @@ -366,18 +354,22 @@ stories.add('Date and Unread separators', () => ( stories.add('With image', () => ( <> <Message - attachments={[{ - title: 'This is a title', - description: 'This is a description', - image_url: '/dummypath' - }]} + attachments={[ + { + title: 'This is a title', + description: 'This is a description', + image_url: '/dummypath' + } + ]} /> <Message - attachments={[{ - title: 'This is a title', - description: 'This is a description :nyan_rocket:', - image_url: '/dummypath' - }]} + attachments={[ + { + title: 'This is a title', + description: 'This is a description :nyan_rocket:', + image_url: '/dummypath' + } + ]} /> </> )); @@ -385,17 +377,21 @@ stories.add('With image', () => ( stories.add('With video', () => ( <> <Message - attachments={[{ - title: 'This is a title', - description: 'This is a description :nyan_rocket:', - video_url: '/dummypath' - }]} + attachments={[ + { + title: 'This is a title', + description: 'This is a description :nyan_rocket:', + video_url: '/dummypath' + } + ]} /> <Message - attachments={[{ - title: 'This is a title', - video_url: '/dummypath' - }]} + attachments={[ + { + title: 'This is a title', + video_url: '/dummypath' + } + ]} /> </> )); @@ -403,33 +399,41 @@ stories.add('With video', () => ( stories.add('With audio', () => ( <> <Message - attachments={[{ - title: 'This is a title', - description: 'This is a description :nyan_rocket:', - audio_url: '/dummypath' - }]} + attachments={[ + { + title: 'This is a title', + description: 'This is a description :nyan_rocket:', + audio_url: '/dummypath' + } + ]} /> <Message msg='First message' isHeader={false} /> <Message - attachments={[{ - title: 'This is a title', - description: 'This is a description', - audio_url: '/dummypath' - }]} + attachments={[ + { + title: 'This is a title', + description: 'This is a description', + audio_url: '/dummypath' + } + ]} isHeader={false} /> <Message - attachments={[{ - title: 'This is a title', - audio_url: '/dummypath' - }]} + attachments={[ + { + title: 'This is a title', + audio_url: '/dummypath' + } + ]} isHeader={false} /> <Message - attachments={[{ - title: 'This is a title', - audio_url: '/dummypath' - }]} + attachments={[ + { + title: 'This is a title', + audio_url: '/dummypath' + } + ]} isHeader={false} /> </> @@ -438,16 +442,20 @@ stories.add('With audio', () => ( stories.add('With file', () => ( <> <Message - attachments={[{ - text: 'File.pdf', - description: 'This is a description :nyan_rocket:' - }]} + attachments={[ + { + text: 'File.pdf', + description: 'This is a description :nyan_rocket:' + } + ]} /> <Message - attachments={[{ - text: 'File.pdf', - description: 'This is a description :nyan_rocket:' - }]} + attachments={[ + { + text: 'File.pdf', + description: 'This is a description :nyan_rocket:' + } + ]} isHeader={false} /> </> @@ -457,97 +465,56 @@ stories.add('Message with reply', () => ( <> <Message msg="I'm fine!" - attachments={[{ - author_name: 'I\'m a very long long title and I\'ll break', - ts: date, - timeFormat: 'LT', - text: 'How are you?' - }]} + attachments={[ + { + author_name: "I'm a very long long title and I'll break", + ts: date, + timeFormat: 'LT', + text: 'How are you?' + } + ]} /> <Message msg="I'm fine!" - attachments={[{ - author_name: 'rocket.cat', - ts: date, - timeFormat: 'LT', - text: 'How are you? :nyan_rocket:' - }]} + attachments={[ + { + author_name: 'rocket.cat', + ts: date, + timeFormat: 'LT', + text: 'How are you? :nyan_rocket:' + } + ]} /> </> )); stories.add('Message with read receipt', () => ( <> - <Message - msg="I'm fine!" - isReadReceiptEnabled - unread - /> - <Message - msg="I'm fine!" - isReadReceiptEnabled - unread - isHeader={false} - /> - <Message - msg="I'm fine!" - isReadReceiptEnabled - read - /> - <Message - msg="I'm fine!" - isReadReceiptEnabled - read - isHeader={false} - /> + <Message msg="I'm fine!" isReadReceiptEnabled unread /> + <Message msg="I'm fine!" isReadReceiptEnabled unread isHeader={false} /> + <Message msg="I'm fine!" isReadReceiptEnabled read /> + <Message msg="I'm fine!" isReadReceiptEnabled read isHeader={false} /> </> )); stories.add('Message with thread', () => ( <> - <Message - msg='How are you?' - tcount={1} - tlm={date} - /> - <Message - msg="I'm fine!" - tmid='1' - tmsg='How are you?' - isThreadReply - /> - <Message - msg="I'm fine!" - tmid='1' - tmsg='Thread with emoji :) :joy:' - isThreadReply - /> - <Message - msg="I'm fine!" - tmid='1' - tmsg={longText} - isThreadReply - /> - <Message - msg={longText} - tmid='1' - tmsg='How are you?' - isThreadReply - /> - <Message - msg={longText} - tmid='1' - tmsg={longText} - isThreadReply - /> + <Message msg='How are you?' tcount={1} tlm={date} /> + <Message msg="I'm fine!" tmid='1' tmsg='How are you?' isThreadReply /> + <Message msg="I'm fine!" tmid='1' tmsg='Thread with emoji :) :joy:' isThreadReply /> + <Message msg="I'm fine!" tmid='1' tmsg={longText} isThreadReply /> + <Message msg={longText} tmid='1' tmsg='How are you?' isThreadReply /> + <Message msg={longText} tmid='1' tmsg={longText} isThreadReply /> <Message tmid='1' tmsg='Thread with attachment' - attachments={[{ - title: 'This is a title', - description: 'This is a description :nyan_rocket:', - audio_url: '/file-upload/c4wcNhrbXJLBvAJtN/1535569819516.aac' - }]} + attachments={[ + { + title: 'This is a title', + description: 'This is a description :nyan_rocket:', + audio_url: '/file-upload/c4wcNhrbXJLBvAJtN/1535569819516.aac' + } + ]} isThreadReply /> </> @@ -555,27 +522,17 @@ stories.add('Message with thread', () => ( stories.add('Sequential thread messages following thread button', () => ( <> + <Message msg='How are you?' tcount={1} tlm={date} /> + <Message msg="I'm fine!" tmid='1' isThreadSequential /> + <Message msg={longText} tmid='1' isThreadSequential /> <Message - msg='How are you?' - tcount={1} - tlm={date} - /> - <Message - msg="I'm fine!" - tmid='1' - isThreadSequential - /> - <Message - msg={longText} - tmid='1' - isThreadSequential - /> - <Message - attachments={[{ - title: 'This is a title', - description: 'This is a description', - audio_url: '/file-upload/c4wcNhrbXJLBvAJtN/1535569819516.aac' - }]} + attachments={[ + { + title: 'This is a title', + description: 'This is a description', + audio_url: '/file-upload/c4wcNhrbXJLBvAJtN/1535569819516.aac' + } + ]} tmid='1' isThreadSequential /> @@ -584,28 +541,17 @@ stories.add('Sequential thread messages following thread button', () => ( stories.add('Sequential thread messages following thread reply', () => ( <> + <Message msg="I'm fine!" tmid='1' tmsg='How are you?' isThreadReply /> + <Message msg='Cool!' tmid='1' isThreadSequential /> + <Message msg={longText} tmid='1' isThreadSequential /> <Message - msg="I'm fine!" - tmid='1' - tmsg='How are you?' - isThreadReply - /> - <Message - msg='Cool!' - tmid='1' - isThreadSequential - /> - <Message - msg={longText} - tmid='1' - isThreadSequential - /> - <Message - attachments={[{ - title: 'This is a title', - description: 'This is a description', - audio_url: '/file-upload/c4wcNhrbXJLBvAJtN/1535569819516.aac' - }]} + attachments={[ + { + title: 'This is a title', + description: 'This is a description', + audio_url: '/file-upload/c4wcNhrbXJLBvAJtN/1535569819516.aac' + } + ]} tmid='1' isThreadSequential /> @@ -614,65 +560,52 @@ stories.add('Sequential thread messages following thread reply', () => ( stories.add('Discussion', () => ( <> - <Message - type='discussion-created' - drid='aisduhasidhs' - dcount={null} - dlm={null} - msg='This is a discussion' - /> - <Message - type='discussion-created' - drid='aisduhasidhs' - dcount={1} - dlm={date} - msg='This is a discussion' - /> - <Message - type='discussion-created' - drid='aisduhasidhs' - dcount={10} - dlm={date} - msg={longText} - /> - <Message - type='discussion-created' - drid='aisduhasidhs' - dcount={1000} - dlm={date} - msg='This is a discussion' - /> + <Message type='discussion-created' drid='aisduhasidhs' dcount={null} dlm={null} msg='This is a discussion' /> + <Message type='discussion-created' drid='aisduhasidhs' dcount={1} dlm={date} msg='This is a discussion' /> + <Message type='discussion-created' drid='aisduhasidhs' dcount={10} dlm={date} msg={longText} /> + <Message type='discussion-created' drid='aisduhasidhs' dcount={1000} dlm={date} msg='This is a discussion' /> </> )); stories.add('URL', () => ( <> <Message - urls={[{ - url: 'https://rocket.chat', - image: 'https://rocket.chat/images/blog/post.jpg', - title: 'Rocket.Chat - Free, Open Source, Enterprise Team Chat', - description: 'Rocket.Chat is the leading open source team chat software solution. Free, unlimited and completely customizable with on-premises and SaaS cloud hosting.' - }, { - url: 'https://google.com', - title: 'Google', - description: 'Search the world\'s information, including webpages, images, videos and more. Google has many special features to help you find exactly what you\'re looking for.' - }]} + urls={[ + { + url: 'https://rocket.chat', + image: 'https://rocket.chat/images/blog/post.jpg', + title: 'Rocket.Chat - Free, Open Source, Enterprise Team Chat', + description: + 'Rocket.Chat is the leading open source team chat software solution. Free, unlimited and completely customizable with on-premises and SaaS cloud hosting.' + }, + { + url: 'https://google.com', + title: 'Google', + description: + "Search the world's information, including webpages, images, videos and more. Google has many special features to help you find exactly what you're looking for." + } + ]} /> <Message - urls={[{ - url: 'https://google.com', - title: 'Google', - description: 'Search the world\'s information, including webpages, images, videos and more. Google has many special features to help you find exactly what you\'re looking for.' - }]} + urls={[ + { + url: 'https://google.com', + title: 'Google', + description: + "Search the world's information, including webpages, images, videos and more. Google has many special features to help you find exactly what you're looking for." + } + ]} msg='Message :nyan_rocket:' /> <Message - urls={[{ - url: 'https://google.com', - title: 'Google', - description: 'Search the world\'s information, including webpages, images, videos and more. Google has many special features to help you find exactly what you\'re looking for.' - }]} + urls={[ + { + url: 'https://google.com', + title: 'Google', + description: + "Search the world's information, including webpages, images, videos and more. Google has many special features to help you find exactly what you're looking for." + } + ]} isHeader={false} /> </> @@ -682,28 +615,36 @@ stories.add('Custom fields', () => ( <> <Message msg='Message' - attachments={[{ - author_name: 'rocket.cat', - ts: date, - timeFormat: 'LT', - text: 'Custom fields', - fields: [{ - title: 'Field 1', - value: 'Value 1' - }, { - title: 'Field 2', - value: 'Value 2' - }, { - title: 'Field 3', - value: 'Value 3' - }, { - title: 'Field 4', - value: 'Value 4' - }, { - title: 'Field 5', - value: 'Value 5' - }] - }]} + attachments={[ + { + author_name: 'rocket.cat', + ts: date, + timeFormat: 'LT', + text: 'Custom fields', + fields: [ + { + title: 'Field 1', + value: 'Value 1' + }, + { + title: 'Field 2', + value: 'Value 2' + }, + { + title: 'Field 3', + value: 'Value 3' + }, + { + title: 'Field 4', + value: 'Value 4' + }, + { + title: 'Field 5', + value: 'Value 5' + } + ] + } + ]} /> </> )); @@ -711,99 +652,119 @@ stories.add('Custom fields', () => ( stories.add('Two short custom fields with markdown', () => ( <Message msg='Message' - attachments={[{ - author_name: 'rocket.cat', - ts: date, - timeFormat: 'LT', - text: 'Custom fields', - fields: [{ - title: 'Field 1', - value: 'Value 1', - short: true - }, { - title: 'Field 2', - value: '[Value 2](https://google.com/)', - short: true - }] - }, { - author_name: 'rocket.cat', - ts: date, - timeFormat: 'LT', - text: 'Custom fields 2', - fields: [{ - title: 'Field 1', - value: 'Value 1', - short: true - }, { - title: 'Field 2', - value: '**Value 2**', - short: true - }] - }]} + attachments={[ + { + author_name: 'rocket.cat', + ts: date, + timeFormat: 'LT', + text: 'Custom fields', + fields: [ + { + title: 'Field 1', + value: 'Value 1', + short: true + }, + { + title: 'Field 2', + value: '[Value 2](https://google.com/)', + short: true + } + ] + }, + { + author_name: 'rocket.cat', + ts: date, + timeFormat: 'LT', + text: 'Custom fields 2', + fields: [ + { + title: 'Field 1', + value: 'Value 1', + short: true + }, + { + title: 'Field 2', + value: '**Value 2**', + short: true + } + ] + } + ]} /> )); stories.add('Colored attachments', () => ( <Message - attachments={[{ - color: 'red', - fields: [{ - title: 'Field 1', - value: 'Value 1', - short: true - }, { - title: 'Field 2', - value: 'Value 2', - short: true - }] - }, { - color: 'green', - fields: [{ - title: 'Field 1', - value: 'Value 1', - short: true - }, { - title: 'Field 2', - value: 'Value 2', - short: true - }] - }, { - color: 'blue', - fields: [{ - title: 'Field 1', - value: 'Value 1', - short: true - }, { - title: 'Field 2', - value: 'Value 2', - short: true - }] - }]} + attachments={[ + { + color: 'red', + fields: [ + { + title: 'Field 1', + value: 'Value 1', + short: true + }, + { + title: 'Field 2', + value: 'Value 2', + short: true + } + ] + }, + { + color: 'green', + fields: [ + { + title: 'Field 1', + value: 'Value 1', + short: true + }, + { + title: 'Field 2', + value: 'Value 2', + short: true + } + ] + }, + { + color: 'blue', + fields: [ + { + title: 'Field 1', + value: 'Value 1', + short: true + }, + { + title: 'Field 2', + value: 'Value 2', + short: true + } + ] + } + ]} /> )); -stories.add('Broadcast', () => ( - <Message msg='Broadcasted message' broadcast replyBroadcast={() => alert('broadcast!')} /> -)); +stories.add('Broadcast', () => <Message msg='Broadcasted message' broadcast replyBroadcast={() => alert('broadcast!')} />); -stories.add('Archived', () => ( - <Message msg='This message is inside an archived room' archived /> -)); +stories.add('Archived', () => <Message msg='This message is inside an archived room' archived />); stories.add('Error', () => ( <> <Message hasError msg='This message has error' status={messagesStatus.ERROR} onErrorPress={() => alert('Error pressed')} /> - <Message hasError msg='This message has error too' status={messagesStatus.ERROR} onErrorPress={() => alert('Error pressed')} isHeader={false} /> + <Message + hasError + msg='This message has error too' + status={messagesStatus.ERROR} + onErrorPress={() => alert('Error pressed')} + isHeader={false} + /> </> )); -stories.add('Temp', () => ( - <Message msg='Temp message' status={messagesStatus.TEMP} isTemp /> -)); +stories.add('Temp', () => <Message msg='Temp message' status={messagesStatus.TEMP} isTemp />); -stories.add('Editing', () => ( - <Message msg='Message being edited' editing /> -)); +stories.add('Editing', () => <Message msg='Message being edited' editing />); stories.add('System messages', () => ( <> @@ -813,30 +774,22 @@ stories.add('System messages', () => ( msg='New name' type='message_pinned' isInfo - attachments={[{ - author_name: 'rocket.cat', - ts: date, - timeFormat: 'LT', - text: 'First message' - }]} + attachments={[ + { + author_name: 'rocket.cat', + ts: date, + timeFormat: 'LT', + text: 'First message' + } + ]} /> <Message type='ul' isInfo /> <Message msg='rocket.cat' type='ru' isInfo /> <Message msg='rocket.cat' type='au' isInfo /> <Message msg='rocket.cat' type='user-muted' isInfo /> <Message msg='rocket.cat' type='user-unmuted' isInfo /> - <Message - msg='rocket.cat' - role='admin' - type='subscription-role-added' - isInfo - /> - <Message - msg='rocket.cat' - role='admin' - type='subscription-role-removed' - isInfo - /> + <Message msg='rocket.cat' role='admin' type='subscription-role-added' isInfo /> + <Message msg='rocket.cat' role='admin' type='subscription-role-removed' isInfo /> <Message msg='New name' type='r' isInfo /> <Message msg='new description' type='room_changed_description' isInfo /> <Message msg='new announcement' type='room_changed_announcement' isInfo /> @@ -847,39 +800,38 @@ stories.add('System messages', () => ( </> )); -stories.add('Ignored', () => ( - <Message isIgnored /> -)); +stories.add('Ignored', () => <Message isIgnored />); -stories.add('Custom style', () => ( - <Message msg='Message' style={[styles.normalize, { backgroundColor: '#ddd' }]} /> -)); +stories.add('Custom style', () => <Message msg='Message' style={[styles.normalize, { backgroundColor: '#ddd' }]} />); stories.add('Show a button as attachment', () => ( <Message - attachments={[{ - text: 'Test Button', - actions: [ - { - type: 'button', - text: 'Text button', - msg: 'Response message', - msg_in_chat_window: true - } - ] - }]} + attachments={[ + { + text: 'Test Button', + actions: [ + { + type: 'button', + text: 'Text button', + msg: 'Response message', + msg_in_chat_window: true + } + ] + } + ]} /> )); stories.add('Thumbnail from server', () => ( <Message msg='this is a thumbnail' - attachments={[{ - text: 'Image text', - thumb_url: 'https://images-na.ssl-images-amazon.com/images/I/71jKxPAMFbL._AC_SL1500_.jpg', - title: 'Title', - title_link: 'https://github.com/RocketChat/Rocket.Chat.ReactNative/pull/2975' - }]} + attachments={[ + { + text: 'Image text', + thumb_url: 'https://images-na.ssl-images-amazon.com/images/I/71jKxPAMFbL._AC_SL1500_.jpg', + title: 'Title', + title_link: 'https://github.com/RocketChat/Rocket.Chat.ReactNative/pull/2975' + } + ]} /> )); - diff --git a/storybook/stories/RoomItem.js b/storybook/stories/RoomItem.js index c13eaa4d2..5baeb9f95 100644 --- a/storybook/stories/RoomItem.js +++ b/storybook/stories/RoomItem.js @@ -1,6 +1,6 @@ /* eslint-disable import/no-extraneous-dependencies */ import React from 'react'; -import { ScrollView, Dimensions } from 'react-native'; +import { Dimensions, ScrollView } from 'react-native'; import { storiesOf } from '@storybook/react-native'; import { Provider } from 'react-redux'; @@ -39,21 +39,14 @@ const stories = storiesOf('Room Item', module) .addDecorator(story => <Provider store={store}>{story()}</Provider>) .addDecorator(story => <ScrollView style={{ backgroundColor: themes[_theme].backgroundColor }}>{story()}</ScrollView>); +stories.add('Basic', () => <RoomItem />); -stories.add('Basic', () => ( - <RoomItem /> -)); - -stories.add('Touch', () => ( - <RoomItem onPress={() => alert('on press')} onLongPress={() => alert('on long press')} /> -)); +stories.add('Touch', () => <RoomItem onPress={() => alert('on press')} onLongPress={() => alert('on long press')} />); stories.add('User', () => ( <> <RoomItem name='diego.mello' avatar='diego.mello' /> - <RoomItem - name={longText} - /> + <RoomItem name={longText} /> </> )); @@ -107,9 +100,7 @@ stories.add('Tag', () => ( stories.add('Last Message', () => ( <> - <RoomItem - showLastMessage - /> + <RoomItem showLastMessage /> <RoomItem showLastMessage lastMessage={{ @@ -129,27 +120,9 @@ stories.add('Last Message', () => ( }} username='diego.mello' /> - <RoomItem - showLastMessage - lastMessage={lastMessage} - /> - <RoomItem - showLastMessage - alert - unread={1} - lastMessage={lastMessage} - /> - <RoomItem - showLastMessage - alert - unread={1000} - lastMessage={lastMessage} - /> - <RoomItem - showLastMessage - alert - tunread={[1]} - lastMessage={lastMessage} - /> + <RoomItem showLastMessage lastMessage={lastMessage} /> + <RoomItem showLastMessage alert unread={1} lastMessage={lastMessage} /> + <RoomItem showLastMessage alert unread={1000} lastMessage={lastMessage} /> + <RoomItem showLastMessage alert tunread={[1]} lastMessage={lastMessage} /> </> )); diff --git a/storybook/stories/ServerItem.js b/storybook/stories/ServerItem.js index d1c53ee01..84e0668fe 100644 --- a/storybook/stories/ServerItem.js +++ b/storybook/stories/ServerItem.js @@ -19,19 +19,11 @@ const item = { iconURL: 'https://open.rocket.chat/images/logo/android-chrome-512x512.png' }; -const ServerItem = props => ( - <ServerItemComponent - item={item} - hasCheck={false} - {...props} - /> -); +const ServerItem = props => <ServerItemComponent item={item} hasCheck={false} {...props} />; stories.add('content', () => ( <> - <ServerItem - hasCheck - /> + <ServerItem hasCheck /> <ServerItem item={{ ...item, @@ -57,10 +49,7 @@ stories.add('touchable', () => ( const ThemeStory = ({ theme }) => ( <ThemeContext.Provider value={theme}> - <ServerItem - theme={theme} - hasCheck - /> + <ServerItem theme={theme} hasCheck /> </ThemeContext.Provider> ); diff --git a/storybook/stories/UiKitMessage.js b/storybook/stories/UiKitMessage.js index 4fa2865f8..a9a9aba94 100644 --- a/storybook/stories/UiKitMessage.js +++ b/storybook/stories/UiKitMessage.js @@ -1,9 +1,9 @@ /* eslint-disable import/no-extraneous-dependencies */ import React from 'react'; -import { ScrollView, StyleSheet, SafeAreaView } from 'react-native'; +import { SafeAreaView, ScrollView, StyleSheet } from 'react-native'; import { storiesOf } from '@storybook/react-native'; -import MessageContext from '../../app/containers/message/Context'; +import MessageContext from '../../app/containers/message/Context'; import { UiKitMessage } from '../../app/containers/UIKit'; import { themes } from '../../app/constants/colors'; @@ -39,407 +39,454 @@ const messageDecorator = story => ( onDiscussionPress: () => {}, onReactionLongPress: () => {}, threadBadgeColor: themes.light.tunreadColor - }} - > + }}> {story()} </MessageContext.Provider> ); const stories = storiesOf('UiKitMessage', module) .addDecorator(story => <SafeAreaView style={styles.container}>{story()}</SafeAreaView>) - .addDecorator(story => <ScrollView style={[styles.container, styles.padding]} keyboardShouldPersistTaps='always'>{story()}</ScrollView>) + .addDecorator(story => ( + <ScrollView style={[styles.container, styles.padding]} keyboardShouldPersistTaps='always'> + {story()} + </ScrollView> + )) .addDecorator(messageDecorator); -const Section = () => UiKitMessage([{ - type: 'section', - text: { - type: 'mrkdwn', - text: 'Section' - } -}]); +const Section = () => + UiKitMessage([ + { + type: 'section', + text: { + type: 'mrkdwn', + text: 'Section' + } + } + ]); stories.add('Section', () => <Section />); -const SectionMarkdownList = () => UiKitMessage([{ - type: 'section', - text: { - type: 'mrkdwn', - text: '*List*:\n1. Item' - } -}]); +const SectionMarkdownList = () => + UiKitMessage([ + { + type: 'section', + text: { + type: 'mrkdwn', + text: '*List*:\n1. Item' + } + } + ]); stories.add('Section + Markdown List', () => <SectionMarkdownList />); -const SectionOverflow = () => UiKitMessage([ - { - type: 'section', - text: { - type: 'mrkdwn', - text: 'Section + Overflow' - }, - accessory: { - type: 'overflow', - options: [ - { - text: { - type: 'plain_text', - text: 'Option 1', - emoji: true +const SectionOverflow = () => + UiKitMessage([ + { + type: 'section', + text: { + type: 'mrkdwn', + text: 'Section + Overflow' + }, + accessory: { + type: 'overflow', + options: [ + { + text: { + type: 'plain_text', + text: 'Option 1', + emoji: true + }, + value: 'value-0' }, - value: 'value-0' - }, - { - text: { - type: 'plain_text', - text: 'Option 2', - emoji: true + { + text: { + type: 'plain_text', + text: 'Option 2', + emoji: true + }, + value: 'value-1' }, - value: 'value-1' - }, - { - text: { - type: 'plain_text', - text: 'Option 3', - emoji: true + { + text: { + type: 'plain_text', + text: 'Option 3', + emoji: true + }, + value: 'value-2' }, - value: 'value-2' - }, - { - text: { - type: 'plain_text', - text: 'Option 4', - emoji: true - }, - value: 'value-3' - } - ] + { + text: { + type: 'plain_text', + text: 'Option 4', + emoji: true + }, + value: 'value-3' + } + ] + } } - } -]); + ]); stories.add('Section + Overflow', () => <SectionOverflow />); -const SectionImage = () => UiKitMessage([{ - type: 'section', - text: { - type: 'mrkdwn', - text: 'Section + Image' - }, - accessory: { - type: 'image', - imageUrl: 'https://raw.githubusercontent.com/RocketChat/Rocket.Chat.Artwork/master/Logos/icon-circle-256.png', - altText: 'plants' - } -}]); +const SectionImage = () => + UiKitMessage([ + { + type: 'section', + text: { + type: 'mrkdwn', + text: 'Section + Image' + }, + accessory: { + type: 'image', + imageUrl: 'https://raw.githubusercontent.com/RocketChat/Rocket.Chat.Artwork/master/Logos/icon-circle-256.png', + altText: 'plants' + } + } + ]); stories.add('Section + image', () => <SectionImage />); -const SectionButton = () => UiKitMessage([{ - type: 'section', - text: { - type: 'mrkdwn', - text: 'Section + button' - }, - accessory: { - type: 'button', - text: { - type: 'plain_text', - text: 'button' - } - } -}]); -stories.add('Section + button', () => <SectionButton />); - -const SectionSelect = () => UiKitMessage([{ - type: 'section', - text: { - type: 'mrkdwn', - text: 'Section + select' - }, - accessory: { - type: 'static_select', - options: [ - { - value: 1, +const SectionButton = () => + UiKitMessage([ + { + type: 'section', + text: { + type: 'mrkdwn', + text: 'Section + button' + }, + accessory: { + type: 'button', text: { type: 'plain_text', text: 'button' } - }, { - value: 2, - text: { - type: 'plain_text', - text: 'second button' - } - }] - } -}]); + } + } + ]); +stories.add('Section + button', () => <SectionButton />); + +const SectionSelect = () => + UiKitMessage([ + { + type: 'section', + text: { + type: 'mrkdwn', + text: 'Section + select' + }, + accessory: { + type: 'static_select', + options: [ + { + value: 1, + text: { + type: 'plain_text', + text: 'button' + } + }, + { + value: 2, + text: { + type: 'plain_text', + text: 'second button' + } + } + ] + } + } + ]); stories.add('Section + Select', () => <SectionSelect />); -const SectionDatePicker = () => UiKitMessage([{ - type: 'section', - text: { - type: 'mrkdwn', - text: 'Section + DatePicker' - }, - accessory: { - type: 'datepicker', - initial_date: '1990-04-28', - placeholder: { - type: 'plain_text', - text: 'Select a date', - emoji: true +const SectionDatePicker = () => + UiKitMessage([ + { + type: 'section', + text: { + type: 'mrkdwn', + text: 'Section + DatePicker' + }, + accessory: { + type: 'datepicker', + initial_date: '1990-04-28', + placeholder: { + type: 'plain_text', + text: 'Select a date', + emoji: true + } + } } - } -}]); + ]); stories.add('Section + DatePicker', () => <SectionDatePicker />); -const SectionMultiSelect = () => UiKitMessage([{ - type: 'section', - text: { - type: 'mrkdwn', - text: 'Section + select' - }, - accessory: { - type: 'multi_static_select', - options: [{ +const SectionMultiSelect = () => + UiKitMessage([ + { + type: 'section', text: { - type: 'plain_text', - text: 'button' + type: 'mrkdwn', + text: 'Section + select' }, - value: 1 - }, { - text: { - type: 'plain_text', - text: 'opt 1' - }, - value: 2 - }, { - text: { - type: 'plain_text', - text: 'opt 2' - }, - value: 3 - }, { - text: { - type: 'plain_text', - text: 'opt 3' - }, - value: 4 - }] - } -}]); + accessory: { + type: 'multi_static_select', + options: [ + { + text: { + type: 'plain_text', + text: 'button' + }, + value: 1 + }, + { + text: { + type: 'plain_text', + text: 'opt 1' + }, + value: 2 + }, + { + text: { + type: 'plain_text', + text: 'opt 2' + }, + value: 3 + }, + { + text: { + type: 'plain_text', + text: 'opt 3' + }, + value: 4 + } + ] + } + } + ]); stories.add('Section + Multi Select', () => <SectionMultiSelect />); -const Image = () => UiKitMessage([{ - type: 'image', - title: { - type: 'plain_text', - text: 'Example Image', - emoji: true - }, - imageUrl: 'https://raw.githubusercontent.com/RocketChat/Rocket.Chat.Artwork/master/Logos/icon-circle-256.png', - altText: 'Example Image' -}]); +const Image = () => + UiKitMessage([ + { + type: 'image', + title: { + type: 'plain_text', + text: 'Example Image', + emoji: true + }, + imageUrl: 'https://raw.githubusercontent.com/RocketChat/Rocket.Chat.Artwork/master/Logos/icon-circle-256.png', + altText: 'Example Image' + } + ]); stories.add('Image', () => <Image />); -const Context = () => UiKitMessage([{ - type: 'context', - elements: [{ - type: 'image', - title: { - type: 'plain_text', - text: 'Example Image', - emoji: true - }, - imageUrl: 'https://raw.githubusercontent.com/RocketChat/Rocket.Chat.Artwork/master/Logos/icon-circle-256.png', - altText: 'Example Image' - }, - { - type: 'mrkdwn', - text: 'context' - } - ] -}]); -stories.add('Context', () => <Context />); - -const ActionButton = () => UiKitMessage([{ - type: 'actions', - elements: [ +const Context = () => + UiKitMessage([ { - type: 'button', - text: { - type: 'plain_text', - emoji: true, - text: 'Approve' - }, - style: 'primary', - value: 'click_me_123' - }, - { - type: 'button', - text: { - type: 'plain_text', - emoji: true, - text: 'Deny' - }, - style: 'danger', - value: 'click_me_123' - }, - { - type: 'button', - text: { - type: 'plain_text', - emoji: true, - text: 'Deny' - }, - style: 'danger', - value: 'click_me_123' - }, - { - type: 'button', - text: { - type: 'plain_text', - emoji: true, - text: 'Deny' - }, - style: 'danger', - value: 'click_me_123' - }, - { - type: 'button', - text: { - type: 'plain_text', - emoji: true, - text: 'Deny' - }, - style: 'danger', - value: 'click_me_123' - }, - { - type: 'button', - text: { - type: 'plain_text', - emoji: true, - text: 'Deny' - }, - style: 'danger', - value: 'click_me_123' - }, - { - type: 'button', - text: { - type: 'plain_text', - emoji: true, - text: 'Deny' - }, - style: 'danger', - value: 'click_me_123' - } - ] -}]); -stories.add('Action - Buttons', () => <ActionButton />); - -const Fields = () => UiKitMessage([ - { - type: 'section', - fields: [ - { - type: 'plain_text', - text: '*this is plain_text text*', - emoji: true - }, - { - type: 'plain_text', - text: '*this is plain_text text*', - emoji: true - }, - { - type: 'plain_text', - text: '*this is plain_text text*', - emoji: true - }, - { - type: 'plain_text', - text: '*this is plain_text text*', - emoji: true - }, - { - type: 'plain_text', - text: '*this is plain_text text*', - emoji: true - } - ] - }]); -stories.add('Fields', () => <Fields />); - -const ActionSelect = () => UiKitMessage([{ - type: 'actions', - elements: [ - { - type: 'conversations_select', - placeholder: { - type: 'plain_text', - text: 'Select a conversation', - emoji: true - } - }, - { - type: 'channels_select', - placeholder: { - type: 'plain_text', - text: 'Select a channel', - emoji: true - } - }, - { - type: 'users_select', - placeholder: { - type: 'plain_text', - text: 'Select a user', - emoji: true - } - }, - { - type: 'static_select', - placeholder: { - type: 'plain_text', - text: 'Select an item', - emoji: true - }, - options: [ + type: 'context', + elements: [ { - text: { + type: 'image', + title: { type: 'plain_text', - text: 'Excellent item 1', + text: 'Example Image', emoji: true }, - value: 'value-0' + imageUrl: 'https://raw.githubusercontent.com/RocketChat/Rocket.Chat.Artwork/master/Logos/icon-circle-256.png', + altText: 'Example Image' }, { - text: { - type: 'plain_text', - text: 'Fantastic item 2', - emoji: true - }, - value: 'value-1' - }, - { - text: { - type: 'plain_text', - text: 'Nifty item 3', - emoji: true - }, - value: 'value-2' - }, - { - text: { - type: 'plain_text', - text: 'Pretty good item 4', - emoji: true - }, - value: 'value-3' + type: 'mrkdwn', + text: 'context' } ] } - ] -}]); + ]); +stories.add('Context', () => <Context />); + +const ActionButton = () => + UiKitMessage([ + { + type: 'actions', + elements: [ + { + type: 'button', + text: { + type: 'plain_text', + emoji: true, + text: 'Approve' + }, + style: 'primary', + value: 'click_me_123' + }, + { + type: 'button', + text: { + type: 'plain_text', + emoji: true, + text: 'Deny' + }, + style: 'danger', + value: 'click_me_123' + }, + { + type: 'button', + text: { + type: 'plain_text', + emoji: true, + text: 'Deny' + }, + style: 'danger', + value: 'click_me_123' + }, + { + type: 'button', + text: { + type: 'plain_text', + emoji: true, + text: 'Deny' + }, + style: 'danger', + value: 'click_me_123' + }, + { + type: 'button', + text: { + type: 'plain_text', + emoji: true, + text: 'Deny' + }, + style: 'danger', + value: 'click_me_123' + }, + { + type: 'button', + text: { + type: 'plain_text', + emoji: true, + text: 'Deny' + }, + style: 'danger', + value: 'click_me_123' + }, + { + type: 'button', + text: { + type: 'plain_text', + emoji: true, + text: 'Deny' + }, + style: 'danger', + value: 'click_me_123' + } + ] + } + ]); +stories.add('Action - Buttons', () => <ActionButton />); + +const Fields = () => + UiKitMessage([ + { + type: 'section', + fields: [ + { + type: 'plain_text', + text: '*this is plain_text text*', + emoji: true + }, + { + type: 'plain_text', + text: '*this is plain_text text*', + emoji: true + }, + { + type: 'plain_text', + text: '*this is plain_text text*', + emoji: true + }, + { + type: 'plain_text', + text: '*this is plain_text text*', + emoji: true + }, + { + type: 'plain_text', + text: '*this is plain_text text*', + emoji: true + } + ] + } + ]); +stories.add('Fields', () => <Fields />); + +const ActionSelect = () => + UiKitMessage([ + { + type: 'actions', + elements: [ + { + type: 'conversations_select', + placeholder: { + type: 'plain_text', + text: 'Select a conversation', + emoji: true + } + }, + { + type: 'channels_select', + placeholder: { + type: 'plain_text', + text: 'Select a channel', + emoji: true + } + }, + { + type: 'users_select', + placeholder: { + type: 'plain_text', + text: 'Select a user', + emoji: true + } + }, + { + type: 'static_select', + placeholder: { + type: 'plain_text', + text: 'Select an item', + emoji: true + }, + options: [ + { + text: { + type: 'plain_text', + text: 'Excellent item 1', + emoji: true + }, + value: 'value-0' + }, + { + text: { + type: 'plain_text', + text: 'Fantastic item 2', + emoji: true + }, + value: 'value-1' + }, + { + text: { + type: 'plain_text', + text: 'Nifty item 3', + emoji: true + }, + value: 'value-2' + }, + { + text: { + type: 'plain_text', + text: 'Pretty good item 4', + emoji: true + }, + value: 'value-3' + } + ] + } + ] + } + ]); stories.add('Action - Select', () => <ActionSelect />); // stories.add('Section', () => UiKitMessage([{ diff --git a/storybook/stories/UiKitModal.js b/storybook/stories/UiKitModal.js index 2ec9e1da6..6b3c22f40 100644 --- a/storybook/stories/UiKitModal.js +++ b/storybook/stories/UiKitModal.js @@ -1,9 +1,9 @@ /* eslint-disable import/no-extraneous-dependencies */ import React from 'react'; -import { ScrollView, StyleSheet, SafeAreaView } from 'react-native'; +import { SafeAreaView, ScrollView, StyleSheet } from 'react-native'; import { storiesOf } from '@storybook/react-native'; -import { UiKitModal, UiKitComponent } from '../../app/containers/UIKit'; +import { UiKitComponent, UiKitModal } from '../../app/containers/UIKit'; import { KitContext, defaultContext } from '../../app/containers/UIKit/utils'; import MessageContext from '../../app/containers/message/Context'; import { themes } from '../../app/constants/colors'; @@ -40,494 +40,523 @@ const messageDecorator = story => ( onDiscussionPress: () => {}, onReactionLongPress: () => {}, threadBadgeColor: themes.light.tunreadColor - }} - > + }}> {story()} </MessageContext.Provider> ); const stories = storiesOf('UiKitModal', module) .addDecorator(story => <SafeAreaView style={styles.container}>{story()}</SafeAreaView>) - .addDecorator(story => <ScrollView style={[styles.container, styles.padding]} keyboardShouldPersistTaps='always'>{story()}</ScrollView>) + .addDecorator(story => ( + <ScrollView style={[styles.container, styles.padding]} keyboardShouldPersistTaps='always'> + {story()} + </ScrollView> + )) .addDecorator(messageDecorator); -const ModalSectionSelects = () => UiKitModal([ - { - type: 'section', - text: { - type: 'mrkdwn', - text: '*Rocket.Chat is free, unlimited and open source* 🚀\nIf you have any doubt ask to @rocketcat' +const ModalSectionSelects = () => + UiKitModal([ + { + type: 'section', + text: { + type: 'mrkdwn', + text: '*Rocket.Chat is free, unlimited and open source* 🚀\nIf you have any doubt ask to @rocketcat' + } + }, + { + type: 'divider' + }, + { + type: 'section', + fields: [ + { + type: 'mrkdwn', + text: '*Text 1*\nDescription, Mussum Ipsum, cacilds vidis litro' + }, + { + type: 'mrkdwn', + text: '*Text 2*\nDescription, Mussum Ipsum, cacilds vidis litro' + } + ] + }, + { + type: 'section', + fields: [ + { + type: 'mrkdwn', + text: '*Text 3*\nDescription, Mussum Ipsum, cacilds vidis litro' + }, + { + type: 'mrkdwn', + text: '*Text 4*\nDescription, Mussum Ipsum, cacilds vidis litro' + } + ] } - }, - { - type: 'divider' - }, - { - type: 'section', - fields: [ - { - type: 'mrkdwn', - text: '*Text 1*\nDescription, Mussum Ipsum, cacilds vidis litro' - }, - { - type: 'mrkdwn', - text: '*Text 2*\nDescription, Mussum Ipsum, cacilds vidis litro' - } - ] - }, - { - type: 'section', - fields: [ - { - type: 'mrkdwn', - text: '*Text 3*\nDescription, Mussum Ipsum, cacilds vidis litro' - }, - { - type: 'mrkdwn', - text: '*Text 4*\nDescription, Mussum Ipsum, cacilds vidis litro' - } - ] - } -]); + ]); stories.add('Modal - Section and Selects', () => <ModalSectionSelects />); -const ModalSectionAccessories = () => UiKitModal([ - { - type: 'section', - text: { - type: 'mrkdwn', - text: '*Bruno Quadros*,\nPlease review your details for your *travel expense*.\nExpense no. *DA921*.' +const ModalSectionAccessories = () => + UiKitModal([ + { + type: 'section', + text: { + type: 'mrkdwn', + text: '*Bruno Quadros*,\nPlease review your details for your *travel expense*.\nExpense no. *DA921*.' + }, + accessory: { + type: 'image', + imageUrl: 'https://raw.githubusercontent.com/RocketChat/Rocket.Chat.Artwork/master/Logos/icon-circle-256.png' + } }, - accessory: { - type: 'image', - imageUrl: 'https://raw.githubusercontent.com/RocketChat/Rocket.Chat.Artwork/master/Logos/icon-circle-256.png' + { + type: 'divider' + }, + { + type: 'section', + text: { + type: 'mrkdwn', + text: '*Date:*\n11/02/2020' + } + }, + { + type: 'section', + text: { + type: 'mrkdwn', + text: '*Category:*\nTravel' + } + }, + { + type: 'section', + text: { + type: 'mrkdwn', + text: '*Cost:*\n$150.00 USD' + } + }, + { + type: 'section', + text: { + type: 'mrkdwn', + text: '*Notes:*\nWebSummit Conference' + } } - }, - { - type: 'divider' - }, - { - type: 'section', - text: { - type: 'mrkdwn', - text: '*Date:*\n11/02/2020' - } - }, - { - type: 'section', - text: { - type: 'mrkdwn', - text: '*Category:*\nTravel' - } - }, - { - type: 'section', - text: { - type: 'mrkdwn', - text: '*Cost:*\n$150.00 USD' - } - }, - { - type: 'section', - text: { - type: 'mrkdwn', - text: '*Notes:*\nWebSummit Conference' - } - } -]); + ]); stories.add('Modal - Section Accessories', () => <ModalSectionAccessories />); -const ModalFormInput = () => UiKitModal([ - { - type: 'input', - element: { - type: 'plain_text_input' - }, - label: { - type: 'plain_text', - text: 'Outgoing Title', - emoji: true - }, - hint: { - type: 'plain_text', - text: 'Pick something unique!', - emoji: true - } - }, - { - type: 'input', - element: { - type: 'datepicker', - initial_date: '1990-04-28', - placeholder: { +const ModalFormInput = () => + UiKitModal([ + { + type: 'input', + element: { + type: 'plain_text_input' + }, + label: { type: 'plain_text', - text: 'Select a date', + text: 'Outgoing Title', + emoji: true + }, + hint: { + type: 'plain_text', + text: 'Pick something unique!', emoji: true } }, - label: { - type: 'plain_text', - text: 'Set a date', - emoji: true - } - }, - { - type: 'input', - element: { - type: 'multi_static_select', - options: [{ - text: { + { + type: 'input', + element: { + type: 'datepicker', + initial_date: '1990-04-28', + placeholder: { type: 'plain_text', - text: 'John' - }, - value: 1 - }, { - text: { - type: 'plain_text', - text: 'Dog' - }, - value: 2 - }] + text: 'Select a date', + emoji: true + } + }, + label: { + type: 'plain_text', + text: 'Set a date', + emoji: true + } }, - label: { - type: 'plain_text', - text: 'Share with...', - emoji: true + { + type: 'input', + element: { + type: 'multi_static_select', + options: [ + { + text: { + type: 'plain_text', + text: 'John' + }, + value: 1 + }, + { + text: { + type: 'plain_text', + text: 'Dog' + }, + value: 2 + } + ] + }, + label: { + type: 'plain_text', + text: 'Share with...', + emoji: true + } } - } -]); + ]); stories.add('Modal - Form Input', () => <ModalFormInput />); -const ModalFormTextArea = () => UiKitModal([ - { - type: 'context', - elements: [{ - type: 'mrkdwn', - text: 'Task: ZOL-994' - }] - }, - { - type: 'section', - text: { - type: 'mrkdwn', - text: 'Update Spec final assets' +const ModalFormTextArea = () => + UiKitModal([ + { + type: 'context', + elements: [ + { + type: 'mrkdwn', + text: 'Task: ZOL-994' + } + ] }, - accessory: { - type: 'button', + { + type: 'section', text: { + type: 'mrkdwn', + text: 'Update Spec final assets' + }, + accessory: { + type: 'button', + text: { + type: 'plain_text', + text: 'Change' + } + } + }, + { + type: 'divider' + }, + { + type: 'input', + element: { + type: 'plain_text_input', + multiline: true + }, + placeholder: { type: 'plain_text', - text: 'Change' + text: 'Write Something', + emoji: true + }, + label: { + type: 'plain_text', + text: 'Notes', + emoji: true + }, + hint: { + type: 'plain_text', + text: 'Please take the time to compose something short', + emoji: true + }, + description: { + type: 'plain_text', + text: 'Describe your update', + emoji: true } } - }, - { - type: 'divider' - }, - { - type: 'input', - element: { - type: 'plain_text_input', - multiline: true - }, - placeholder: { - type: 'plain_text', - text: 'Write Something', - emoji: true - }, - label: { - type: 'plain_text', - text: 'Notes', - emoji: true - }, - hint: { - type: 'plain_text', - text: 'Please take the time to compose something short', - emoji: true - }, - description: { - type: 'plain_text', - text: 'Describe your update', - emoji: true - } - } -]); + ]); stories.add('Modal - Form TextArea', () => <ModalFormTextArea />); -const ModalImages = () => UiKitModal([ - { - type: 'image', - title: { - type: 'plain_text', - text: 'Example Image', - emoji: true - }, - imageUrl: 'https://raw.githubusercontent.com/RocketChat/Rocket.Chat.Artwork/master/Logos/icon-circle-256.png', - alt_text: 'Example Image' - }, - { - type: 'section', - text: { - type: 'mrkdwn', - text: 'How could be the life in Mars?' - } - }, - { - type: 'context', - elements: [ - { - type: 'image', - imageUrl: 'https://raw.githubusercontent.com/RocketChat/Rocket.Chat.Artwork/master/Logos/icon-circle-256.png' +const ModalImages = () => + UiKitModal([ + { + type: 'image', + title: { + type: 'plain_text', + text: 'Example Image', + emoji: true }, - { + imageUrl: 'https://raw.githubusercontent.com/RocketChat/Rocket.Chat.Artwork/master/Logos/icon-circle-256.png', + alt_text: 'Example Image' + }, + { + type: 'section', + text: { type: 'mrkdwn', - text: 'November 25, 2019' + text: 'How could be the life in Mars?' + } + }, + { + type: 'context', + elements: [ + { + type: 'image', + imageUrl: 'https://raw.githubusercontent.com/RocketChat/Rocket.Chat.Artwork/master/Logos/icon-circle-256.png' + }, + { + type: 'mrkdwn', + text: 'November 25, 2019' + } + ] + }, + { + type: 'section', + text: { + type: 'mrkdwn', + text: '*Next stop, Mars!*\nMussum Ipsum, cacilds vidis litro abertis. Admodum accumsan disputationi eu sit. Vide electram sadipscing et per. Diuretics paradis num copo é motivis de denguis. Mais vale um bebadis conhecidiss, que um alcoolatra anonimis. Aenean aliquam molestie leo, vitae iaculis nisl.' } - ] - }, - { - type: 'section', - text: { - type: 'mrkdwn', - text: '*Next stop, Mars!*\nMussum Ipsum, cacilds vidis litro abertis. Admodum accumsan disputationi eu sit. Vide electram sadipscing et per. Diuretics paradis num copo é motivis de denguis. Mais vale um bebadis conhecidiss, que um alcoolatra anonimis. Aenean aliquam molestie leo, vitae iaculis nisl.' } - } -]); + ]); stories.add('Modal - Images', () => <ModalImages />); -const ModalActions = () => UiKitModal([{ - type: 'input', - element: { - type: 'plain_text_input' - }, - label: { - type: 'plain_text', - text: 'Title', - emoji: true - } -}, -{ - type: 'section', - text: { - type: 'mrkdwn', - text: 'Details' - } -}, -{ - type: 'section', - accessory: { - type: 'static_select', - options: [ - { - value: 1, - text: { - type: 'plain_text', - text: 'TypeL Task' - } - }, { - value: 2, - text: { - type: 'plain_text', - text: 'second button' - } - }] - } -}, -{ - type: 'section', - accessory: { - type: 'static_select', - options: [ - { - value: 1, - text: { - type: 'plain_text', - text: 'Project: Space (winter)' - } - }, { - value: 2, - text: { - type: 'plain_text', - text: 'second button' - } - }] - } -}, -{ - type: 'section', - accessory: { - type: 'static_select', - options: [ - { - value: 1, - text: { - type: 'plain_text', - text: 'Priority (optional)' - } - }, { - value: 2, - text: { - type: 'plain_text', - text: 'second button' - } - }] - } -}, -{ - type: 'section', - accessory: { - type: 'static_select', - options: [ - { - value: 1, - text: { - type: 'plain_text', - text: 'Assinee (optional)' - } - }, { - value: 2, - text: { - type: 'plain_text', - text: 'second button' - } - }] - } -}, -{ - type: 'input', - element: { - type: 'plain_text_input', - multiline: true - }, - placeholder: { - type: 'plain_text', - text: 'Write Something', - emoji: true - }, - label: { - type: 'plain_text', - text: 'Description', - emoji: true - } -}]); +const ModalActions = () => + UiKitModal([ + { + type: 'input', + element: { + type: 'plain_text_input' + }, + label: { + type: 'plain_text', + text: 'Title', + emoji: true + } + }, + { + type: 'section', + text: { + type: 'mrkdwn', + text: 'Details' + } + }, + { + type: 'section', + accessory: { + type: 'static_select', + options: [ + { + value: 1, + text: { + type: 'plain_text', + text: 'TypeL Task' + } + }, + { + value: 2, + text: { + type: 'plain_text', + text: 'second button' + } + } + ] + } + }, + { + type: 'section', + accessory: { + type: 'static_select', + options: [ + { + value: 1, + text: { + type: 'plain_text', + text: 'Project: Space (winter)' + } + }, + { + value: 2, + text: { + type: 'plain_text', + text: 'second button' + } + } + ] + } + }, + { + type: 'section', + accessory: { + type: 'static_select', + options: [ + { + value: 1, + text: { + type: 'plain_text', + text: 'Priority (optional)' + } + }, + { + value: 2, + text: { + type: 'plain_text', + text: 'second button' + } + } + ] + } + }, + { + type: 'section', + accessory: { + type: 'static_select', + options: [ + { + value: 1, + text: { + type: 'plain_text', + text: 'Assinee (optional)' + } + }, + { + value: 2, + text: { + type: 'plain_text', + text: 'second button' + } + } + ] + } + }, + { + type: 'input', + element: { + type: 'plain_text_input', + multiline: true + }, + placeholder: { + type: 'plain_text', + text: 'Write Something', + emoji: true + }, + label: { + type: 'plain_text', + text: 'Description', + emoji: true + } + } + ]); stories.add('Modal - Actions', () => <ModalActions />); -const ModalContextsDividers = () => UiKitModal([ - { - type: 'context', - elements: [{ - type: 'mrkdwn', - text: 'Due today' - }] - }, - { - type: 'divider' - }, - { - type: 'section', - text: { - type: 'mrkdwn', - text: 'Finish interface componests (3 hours)' - }, - accessory: { - blockId: 'overflow-1', - type: 'overflow', - options: [ +const ModalContextsDividers = () => + UiKitModal([ + { + type: 'context', + elements: [ { - text: { - type: 'plain_text', - text: 'Details', - emoji: true - }, - value: 'value-0' - }, - { - text: { - type: 'plain_text', - text: 'Remove', - emoji: true - }, - value: 'value-1' + type: 'mrkdwn', + text: 'Due today' } ] - } - }, - { - type: 'section', - text: { - type: 'mrkdwn', - text: 'English Class (1 hour)' }, - accessory: { - blockId: 'overflow-2', - type: 'overflow', - options: [ - { - text: { - type: 'plain_text', - text: 'Details', - emoji: true - }, - value: 'value-0' - }, - { - text: { - type: 'plain_text', - text: 'Remove', - emoji: true - }, - value: 'value-1' - } - ] - } - }, - { - type: 'section', - text: { - type: 'mrkdwn', - text: 'Send an email to John (15min)' + { + type: 'divider' }, - accessory: { - blockId: 'overflow-3', - type: 'overflow', - options: [ - { - text: { - type: 'plain_text', - text: 'Details', - emoji: true + { + type: 'section', + text: { + type: 'mrkdwn', + text: 'Finish interface componests (3 hours)' + }, + accessory: { + blockId: 'overflow-1', + type: 'overflow', + options: [ + { + text: { + type: 'plain_text', + text: 'Details', + emoji: true + }, + value: 'value-0' }, - value: 'value-0' - }, - { - text: { - type: 'plain_text', - text: 'Remove', - emoji: true + { + text: { + type: 'plain_text', + text: 'Remove', + emoji: true + }, + value: 'value-1' + } + ] + } + }, + { + type: 'section', + text: { + type: 'mrkdwn', + text: 'English Class (1 hour)' + }, + accessory: { + blockId: 'overflow-2', + type: 'overflow', + options: [ + { + text: { + type: 'plain_text', + text: 'Details', + emoji: true + }, + value: 'value-0' }, - value: 'value-1' - } - ] + { + text: { + type: 'plain_text', + text: 'Remove', + emoji: true + }, + value: 'value-1' + } + ] + } + }, + { + type: 'section', + text: { + type: 'mrkdwn', + text: 'Send an email to John (15min)' + }, + accessory: { + blockId: 'overflow-3', + type: 'overflow', + options: [ + { + text: { + type: 'plain_text', + text: 'Details', + emoji: true + }, + value: 'value-0' + }, + { + text: { + type: 'plain_text', + text: 'Remove', + emoji: true + }, + value: 'value-1' + } + ] + } } - } -]); + ]); stories.add('Modal - Contexts and Dividers', () => <ModalContextsDividers />); const ModalInputWithError = () => ( <KitContext.Provider value={{ ...defaultContext, errors: { 'input-test': 'error test' } }}> <UiKitComponent render={UiKitModal} - blocks={[{ - type: 'input', - element: { - type: 'plain_text_input', - actionId: 'input-test' - }, - label: { - type: 'plain_text', - text: 'Label', - emoji: true + blocks={[ + { + type: 'input', + element: { + type: 'plain_text_input', + actionId: 'input-test' + }, + label: { + type: 'plain_text', + text: 'Label', + emoji: true + } } - }]} + ]} /> </KitContext.Provider> ); @@ -537,19 +566,21 @@ const ModalMultilneWithError = () => ( <KitContext.Provider value={{ ...defaultContext, errors: { 'input-test': 'error test' } }}> <UiKitComponent render={UiKitModal} - blocks={[{ - type: 'input', - element: { - type: 'plain_text_input', - multiline: true, - actionId: 'input-test' - }, - label: { - type: 'plain_text', - text: 'Label', - emoji: true + blocks={[ + { + type: 'input', + element: { + type: 'plain_text_input', + multiline: true, + actionId: 'input-test' + }, + label: { + type: 'plain_text', + text: 'Label', + emoji: true + } } - }]} + ]} /> </KitContext.Provider> ); @@ -559,24 +590,26 @@ const ModalDatePickerWithError = () => ( <KitContext.Provider value={{ ...defaultContext, errors: { 'input-test': 'error test' } }}> <UiKitComponent render={UiKitModal} - blocks={[{ - type: 'input', - element: { - type: 'datepicker', - initial_date: '1990-04-28', - actionId: 'input-test', - placeholder: { + blocks={[ + { + type: 'input', + element: { + type: 'datepicker', + initial_date: '1990-04-28', + actionId: 'input-test', + placeholder: { + type: 'plain_text', + text: 'Select a date', + emoji: true + } + }, + label: { type: 'plain_text', - text: 'Select a date', + text: 'Label', emoji: true } - }, - label: { - type: 'plain_text', - text: 'Label', - emoji: true } - }]} + ]} /> </KitContext.Provider> ); diff --git a/storybook/stories/UnreadBadge.js b/storybook/stories/UnreadBadge.js index f71c391a8..b19fa6292 100644 --- a/storybook/stories/UnreadBadge.js +++ b/storybook/stories/UnreadBadge.js @@ -11,9 +11,11 @@ const stories = storiesOf('Unread Badge', module); const StoryTester = ({ children }) => ( <View style={{ - flex: 1, flexDirection: 'row', alignItems: 'center', justifyContent: 'space-evenly' - }} - > + flex: 1, + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'space-evenly' + }}> {children} </View> ); @@ -57,9 +59,7 @@ stories.add('different mention types', () => ( )); const ThemeStory = ({ theme }) => ( - <ThemeContext.Provider - value={{ theme }} - > + <ThemeContext.Provider value={{ theme }}> <StoryTester> <UnreadBadge unread={1} /> <UnreadBadge unread={1} userMentions={1} /> diff --git a/storybook/stories/index.js b/storybook/stories/index.js index 9f9e596e7..4ff6450b0 100644 --- a/storybook/stories/index.js +++ b/storybook/stories/index.js @@ -1,5 +1,5 @@ /* eslint-disable import/no-extraneous-dependencies, import/no-unresolved, import/extensions */ -import { createStore, combineReducers } from 'redux'; +import { combineReducers, createStore } from 'redux'; import './RoomItem'; import './List'; @@ -15,6 +15,7 @@ import './Avatar'; import '../../app/containers/BackgroundContainer/index.stories.js'; import '../../app/containers/RoomHeader/RoomHeader.stories.js'; import '../../app/views/RoomView/LoadMore/LoadMore.stories'; +import '../../app/views/CannedResponsesListView/CannedResponseItem.stories'; import '../../app/containers/TextInput.stories'; // Change here to see themed storybook diff --git a/storybook/utils.js b/storybook/utils.js index 83260dd02..f3492f9f5 100644 --- a/storybook/utils.js +++ b/storybook/utils.js @@ -1 +1,2 @@ -export const longText = 'Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industrys standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries'; +export const longText = + 'Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industrys standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries'; diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 000000000..db7d37b56 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,73 @@ +{ + "compilerOptions": { + /* Visit https://aka.ms/tsconfig.json to read more about this file */ + + /* Basic Options */ + // "incremental": true, /* Enable incremental compilation */ + "target": "esnext" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', 'ES2021', or 'ESNEXT'. */, + "module": "commonjs" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */, + // "lib": [], /* Specify library files to be included in the compilation. */ + "allowJs": true /* Allow javascript files to be compiled. */, + // "checkJs": true, /* Report errors in .js files. */ + "jsx": "react-native" /* Specify JSX code generation: 'preserve', 'react-native', 'react', 'react-jsx' or 'react-jsxdev'. */, + // "declaration": true, /* Generates corresponding '.d.ts' file. */ + // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ + // "sourceMap": true, /* Generates corresponding '.map' file. */ + // "outFile": "./", /* Concatenate and emit output to single file. */ + // "outDir": "./", /* Redirect output structure to the directory. */ + // "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ + // "composite": true, /* Enable project compilation */ + // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */ + // "removeComments": true, /* Do not emit comments to output. */ + "noEmit": true /* Do not emit outputs. */, + // "importHelpers": true, /* Import emit helpers from 'tslib'. */ + // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ + // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ + + /* Strict Type-Checking Options */ + "strict": true /* Enable all strict type-checking options. */, + // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ + // "strictNullChecks": true, /* Enable strict null checks. */ + // "strictFunctionTypes": true, /* Enable strict checking of function types. */ + // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ + // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ + // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ + // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ + + /* Additional Checks */ + // "noUnusedLocals": true, /* Report errors on unused locals. */ + // "noUnusedParameters": true, /* Report errors on unused parameters. */ + // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ + // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ + // "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */ + // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an 'override' modifier. */ + // "noPropertyAccessFromIndexSignature": true, /* Require undeclared properties from index signatures to use element accesses. */ + + /* Module Resolution Options */ + "moduleResolution": "node" /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */, + "baseUrl": "app" /* Base directory to resolve non-absolute module names. */, + // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ + // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ + // "typeRoots": [], /* List of folders to include type definitions from. */ + // "types": [], /* Type declaration files to be included in compilation. */ + "allowSyntheticDefaultImports": true /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */, + "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */, + // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ + // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ + + /* Source Map Options */ + // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ + // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ + // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ + // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ + + /* Experimental Options */ + "experimentalDecorators": true /* Enables experimental support for ES7 decorators. */, + // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ + + /* Advanced Options */ + "skipLibCheck": true /* Skip type checking of declaration files. */, + "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */ + }, + "exclude": ["node_modules", "e2e/docker", "__mocks__"] +} diff --git a/yarn.lock b/yarn.lock index b5a098971..ef839c91b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,6 +2,13 @@ # yarn lockfile v1 +"@babel/code-frame@7.12.11": + version "7.12.11" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.12.11.tgz#f4ad435aa263db935b8f10f2c552d23fb716a63f" + integrity sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw== + dependencies: + "@babel/highlight" "^7.10.4" + "@babel/code-frame@7.5.5": version "7.5.5" resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.5.5.tgz#bc0782f6d69f7b7d49531219699b988f669a8f9d" @@ -126,10 +133,10 @@ semver "^5.4.1" source-map "^0.5.0" -"@babel/eslint-parser@^7.13.4": - version "7.14.7" - resolved "https://registry.yarnpkg.com/@babel/eslint-parser/-/eslint-parser-7.14.7.tgz#91be59a4f7dd60d02a3ef772d156976465596bda" - integrity sha512-6WPwZqO5priAGIwV6msJcdc9TsEPzYeYdS/Xuoap+/ihkgN6dzHp2bcAAwyWZ5bLzk0vvjDmKvRwkqNaiJ8BiQ== +"@babel/eslint-parser@^7.14.7": + version "7.15.4" + resolved "https://registry.yarnpkg.com/@babel/eslint-parser/-/eslint-parser-7.15.4.tgz#46385943726291fb3e8db99522c8099b15684387" + integrity sha512-hPMIAmGNbmQzXJIo2P43Zj9UhRmGev5f9nqdBFOWNGDGh6XKmjby79woBvg6y0Jur6yRfQBneDbUQ8ZVc1krFw== dependencies: eslint-scope "^5.1.1" eslint-visitor-keys "^2.1.0" @@ -2180,19 +2187,18 @@ resolved "https://registry.yarnpkg.com/@emotion/weak-memoize/-/weak-memoize-0.2.5.tgz#8eed982e2ee6f7f4e44c253e12962980791efd46" integrity sha512-6U71C2Wp7r5XtFtQzYrW5iKFT67OixrSxjI4MptCHzdSVlgabczzqLe0ZSgnub/5Kp4hSbpDB1tMytZY9pwxxA== -"@eslint/eslintrc@^0.2.1": - version "0.2.2" - resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-0.2.2.tgz#d01fc791e2fc33e88a29d6f3dc7e93d0cd784b76" - integrity sha512-EfB5OHNYp1F4px/LI/FEnGylop7nOqkQ1LRzCM0KccA2U8tvV8w01KBv37LbO7nW4H+YhKyo2LcJhRwjjV17QQ== +"@eslint/eslintrc@^0.4.3": + version "0.4.3" + resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-0.4.3.tgz#9e42981ef035beb3dd49add17acb96e8ff6f394c" + integrity sha512-J6KFFz5QCYUJq3pf0mjEcCJVERbzv71PUIDczuh9JkwGEzced6CO5ADLHB1rbf/+oPBtoPfMYNOpGDzCANlbXw== dependencies: ajv "^6.12.4" debug "^4.1.1" espree "^7.3.0" - globals "^12.1.0" + globals "^13.9.0" ignore "^4.0.6" import-fresh "^3.2.1" js-yaml "^3.13.1" - lodash "^4.17.19" minimatch "^3.0.4" strip-json-comments "^3.1.1" @@ -2208,6 +2214,20 @@ dependencies: "@hapi/hoek" "^9.0.0" +"@humanwhocodes/config-array@^0.5.0": + version "0.5.0" + resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.5.0.tgz#1407967d4c6eecd7388f83acf1eaf4d0c6e58ef9" + integrity sha512-FagtKFz74XrTl7y6HCzQpwDfXP0yhxe9lHLD1UZxjvZIcbyRz8zTFF/yYNfSfzU414eDwZ1SrO0Qvtyf+wFMQg== + dependencies: + "@humanwhocodes/object-schema" "^1.2.0" + debug "^4.1.1" + minimatch "^3.0.4" + +"@humanwhocodes/object-schema@^1.2.0": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-1.2.0.tgz#87de7af9c231826fdd68ac7258f77c429e0e5fcf" + integrity sha512-wdppn25U8z/2yiaT6YGquE6X8sSv7hNMWSXYSSU1jGv/yd6XqjXgTDJ8KP4NgjTXfJ3GbRjeeb8RTV7a/VpM+w== + "@hypnosphi/create-react-context@^0.3.1": version "0.3.1" resolved "https://registry.yarnpkg.com/@hypnosphi/create-react-context/-/create-react-context-0.3.1.tgz#f8bfebdc7665f5d426cba3753e0e9c7d3154d7c6" @@ -2794,11 +2814,32 @@ call-me-maybe "^1.0.1" glob-to-regexp "^0.3.0" +"@nodelib/fs.scandir@2.1.5": + version "2.1.5" + resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" + integrity sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g== + dependencies: + "@nodelib/fs.stat" "2.0.5" + run-parallel "^1.1.9" + +"@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2": + version "2.0.5" + resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz#5bd262af94e9d25bd1e71b05deed44876a222e8b" + integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== + "@nodelib/fs.stat@^1.1.2": version "1.1.3" resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-1.1.3.tgz#2b5a3ab3f918cca48a8c754c08168e3f03eba61b" integrity sha512-shAmDyaQC4H92APFoIaVDHCx5bStIocgvbwQyxPRrbUY20V1EYTbSDchWbuwlMG3V17cprZhA6+78JfB+3DTPw== +"@nodelib/fs.walk@^1.2.3": + version "1.2.8" + resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz#e95737e8bb6746ddedf69c556953494f196fe69a" + integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg== + dependencies: + "@nodelib/fs.scandir" "2.1.5" + fastq "^1.6.0" + "@nozbe/sqlite@3.31.1": version "3.31.1" resolved "https://registry.yarnpkg.com/@nozbe/sqlite/-/sqlite-3.31.1.tgz#ffd394ad7c188c6b73f89fd6e1ccb849a1b96dba" @@ -3155,14 +3196,21 @@ resolved "https://registry.yarnpkg.com/@redux-saga/types/-/types-1.1.0.tgz#0e81ce56b4883b4b2a3001ebe1ab298b84237204" integrity sha512-afmTuJrylUU/0OtqzaRkbyYFFNgCF73Bvel/sw90pvGrWIZ+vyoIJqA6eMSoA6+nb443kTmulmBtC9NerXboNg== +"@rocket.chat/eslint-config@^0.4.0": + version "0.4.0" + resolved "https://registry.yarnpkg.com/@rocket.chat/eslint-config/-/eslint-config-0.4.0.tgz#d648decd02ae739eac17a32e1630332a75318ea1" + integrity sha512-uVxoH3/TtusmXp73JtmDKlKK3EQTk+F/tnvVxfSAeyDCAS1/8HeZS+r81md3EOtRvmu1dselXg8+R1eO55QZfg== + dependencies: + eslint-plugin-import "^2.17.2" + "@rocket.chat/react-native-fast-image@^8.2.0": version "8.2.0" resolved "https://registry.yarnpkg.com/@rocket.chat/react-native-fast-image/-/react-native-fast-image-8.2.0.tgz#4f48858f95f40afcb10b39cee9b1239c150d6c51" integrity sha512-NF5KlFt642ZucP/KHnYGBNYLD6O7bcrZMKfRQlH5Y3/1xpnPX1g4wuygtiV7XArMU1FopQT+qmCUPPj8IMDTcw== "@rocket.chat/sdk@RocketChat/Rocket.Chat.js.SDK#mobile": - version "1.0.0-mobile" - resolved "https://codeload.github.com/RocketChat/Rocket.Chat.js.SDK/tar.gz/0241e2fc0c29827c51655f2d46d96e7a7720d2b6" + version "1.1.0-mobile" + resolved "https://codeload.github.com/RocketChat/Rocket.Chat.js.SDK/tar.gz/0ee2ded22b08b34ce7ab62b26e42a713dca0d1ac" dependencies: js-sha256 "^0.9.0" lru-cache "^4.1.1" @@ -3703,16 +3751,34 @@ dependencies: jest-diff "^24.3.0" +"@types/jest@^26.0.24": + version "26.0.24" + resolved "https://registry.yarnpkg.com/@types/jest/-/jest-26.0.24.tgz#943d11976b16739185913a1936e0de0c4a7d595a" + integrity sha512-E/X5Vib8BWqZNRlDxj9vYXhsDwPYbPINqKF9BsnSoon4RQ0D9moEuLD8txgyypFLH7J4+Lho9Nr/c8H0Fi+17w== + dependencies: + jest-diff "^26.0.0" + pretty-format "^26.0.0" + "@types/json-schema@^7.0.5": version "7.0.7" resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.7.tgz#98a993516c859eb0d5c4c8f098317a9ea68db9ad" integrity sha512-cxWFQVseBm6O9Gbw1IWb8r6OS4OhSt3hPZLkFApLjM8TEXROBuQGLAH2i2gZpcXdLBIrpXuTDhH7Vbm1iXmNGA== +"@types/json-schema@^7.0.7": + version "7.0.9" + resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.9.tgz#97edc9037ea0c38585320b28964dde3b39e4660d" + integrity sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ== + "@types/json5@^0.0.29": version "0.0.29" resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee" integrity sha1-7ihweulOEdK4J7y+UnC86n8+ce4= +"@types/lodash@^4.14.171": + version "4.14.172" + resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.172.tgz#aad774c28e7bfd7a67de25408e03ee5a8c3d028a" + integrity sha512-/BHF5HAx3em7/KkzVKm3LrsD6HZAXuXO1AJZQ3cRRBZj4oHZDviWPYu0aEplAqDFNHZPW6d3G7KN+ONcCCC7pw== + "@types/minimatch@*": version "3.0.3" resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d" @@ -3756,6 +3822,34 @@ "@types/history" "*" "@types/react" "*" +"@types/react-native-config-reader@^4.1.0": + version "4.1.0" + resolved "https://registry.yarnpkg.com/@types/react-native-config-reader/-/react-native-config-reader-4.1.0.tgz#33066cd0452b86b605b41bed47b38470dd85d428" + integrity sha512-meRB7e21CUmeS+so51CthLRQxZBymHZoDpq3UaDQHP9ucYbdeA7E0WFoI9vQE1h6kX3Mau3wyU2wQTZiD2674Q== + +"@types/react-native-platform-touchable@^1.1.2": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@types/react-native-platform-touchable/-/react-native-platform-touchable-1.1.2.tgz#c0060f679ffe2ff96960cfde8b60c0fe87168ebf" + integrity sha512-nRA5GuDdG1e2I3f5Ukwdlbw1d0H2GbBU+j0H16/cNelVYPz3zLoxcCHhkNdZ9Wr8LVgVcvqK7vKXG+mzepvpMw== + dependencies: + "@types/react" "*" + "@types/react-native" "*" + +"@types/react-native-scrollable-tab-view@^0.10.2": + version "0.10.2" + resolved "https://registry.yarnpkg.com/@types/react-native-scrollable-tab-view/-/react-native-scrollable-tab-view-0.10.2.tgz#a6006efcad320b9dffd23b080cfe153cebcbc1a6" + integrity sha512-N6IObGTHFKIi2/lFqMLcUICjzqA8phbNWUC6apAdfBnJSrGWkHjTctPt28qpYaQA6KqtCsmhZ19RpzszDje0pg== + dependencies: + "@types/react" "*" + "@types/react-native" "*" + +"@types/react-native@*": + version "0.65.0" + resolved "https://registry.yarnpkg.com/@types/react-native/-/react-native-0.65.0.tgz#bef9ca619f421abafae891ac0629e27cbfe63b42" + integrity sha512-GgM6d47SQM9a6iOWKsdseFtTsKZGvmbr0FEaJMdCVy2SJmgtUq5JVpr3+aqHdrJQrg93e08VxPAWmz0qUtIPOg== + dependencies: + "@types/react" "*" + "@types/react-native@^0.62.7": version "0.62.10" resolved "https://registry.yarnpkg.com/@types/react-native/-/react-native-0.62.10.tgz#82c481df21db4e7460755dc3fc7091e333a1d2bd" @@ -3773,6 +3867,16 @@ hoist-non-react-statics "^3.3.0" redux "^4.0.0" +"@types/react-redux@^7.1.18": + version "7.1.18" + resolved "https://registry.yarnpkg.com/@types/react-redux/-/react-redux-7.1.18.tgz#2bf8fd56ebaae679a90ebffe48ff73717c438e04" + integrity sha512-9iwAsPyJ9DLTRH+OFeIrm9cAbIj1i2ANL3sKQFATqnPWRbg+jEFXyZOKHiQK/N86pNRXbb4HRxAxo0SIX1XwzQ== + dependencies: + "@types/hoist-non-react-statics" "^3.3.0" + "@types/react" "*" + hoist-non-react-statics "^3.3.0" + redux "^4.0.0" + "@types/react-syntax-highlighter@11.0.4": version "11.0.4" resolved "https://registry.yarnpkg.com/@types/react-syntax-highlighter/-/react-syntax-highlighter-11.0.4.tgz#d86d17697db62f98046874f62fdb3e53a0bbc4cd" @@ -3780,6 +3884,13 @@ dependencies: "@types/react" "*" +"@types/react-test-renderer@^17.0.1": + version "17.0.1" + resolved "https://registry.yarnpkg.com/@types/react-test-renderer/-/react-test-renderer-17.0.1.tgz#3120f7d1c157fba9df0118dae20cb0297ee0e06b" + integrity sha512-3Fi2O6Zzq/f3QR9dRnlnHso9bMl7weKCviFmfF6B4LS1Uat6Hkm15k0ZAQuDz+UBq6B3+g+NM6IT2nr5QgPzCw== + dependencies: + "@types/react" "*" + "@types/react-textarea-autosize@^4.3.3": version "4.3.5" resolved "https://registry.yarnpkg.com/@types/react-textarea-autosize/-/react-textarea-autosize-4.3.5.tgz#6c4d2753fa1864c98c0b2b517f67bb1f6e4c46de" @@ -3804,6 +3915,15 @@ "@types/scheduler" "*" csstype "^3.0.2" +"@types/react@^17.0.14": + version "17.0.20" + resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.20.tgz#a4284b184d47975c71658cd69e759b6bd37c3b8c" + integrity sha512-wWZrPlihslrPpcKyCSlmIlruakxr57/buQN1RjlIeaaTWDLtJkTtRW429MoQJergvVKc4IWBpRhWw7YNh/7GVA== + dependencies: + "@types/prop-types" "*" + "@types/scheduler" "*" + csstype "^3.0.2" + "@types/scheduler@*": version "0.16.1" resolved "https://registry.yarnpkg.com/@types/scheduler/-/scheduler-0.16.1.tgz#18845205e86ff0038517aab7a18a62a6b9f71275" @@ -3888,6 +4008,75 @@ dependencies: "@types/yargs-parser" "*" +"@typescript-eslint/eslint-plugin@^4.28.3": + version "4.31.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.31.0.tgz#9c3fa6f44bad789a962426ad951b54695bd3af6b" + integrity sha512-iPKZTZNavAlOhfF4gymiSuUkgLne/nh5Oz2/mdiUmuZVD42m9PapnCnzjxuDsnpnbH3wT5s2D8bw6S39TC6GNw== + dependencies: + "@typescript-eslint/experimental-utils" "4.31.0" + "@typescript-eslint/scope-manager" "4.31.0" + debug "^4.3.1" + functional-red-black-tree "^1.0.1" + regexpp "^3.1.0" + semver "^7.3.5" + tsutils "^3.21.0" + +"@typescript-eslint/experimental-utils@4.31.0": + version "4.31.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-4.31.0.tgz#0ef1d5d86c334f983a00f310e43c1ce4c14e054d" + integrity sha512-Hld+EQiKLMppgKKkdUsLeVIeEOrwKc2G983NmznY/r5/ZtZCDvIOXnXtwqJIgYz/ymsy7n7RGvMyrzf1WaSQrw== + dependencies: + "@types/json-schema" "^7.0.7" + "@typescript-eslint/scope-manager" "4.31.0" + "@typescript-eslint/types" "4.31.0" + "@typescript-eslint/typescript-estree" "4.31.0" + eslint-scope "^5.1.1" + eslint-utils "^3.0.0" + +"@typescript-eslint/parser@^4.28.5": + version "4.31.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-4.31.0.tgz#87b7cd16b24b9170c77595d8b1363f8047121e05" + integrity sha512-oWbzvPh5amMuTmKaf1wp0ySxPt2ZXHnFQBN2Szu1O//7LmOvgaKTCIDNLK2NvzpmVd5A2M/1j/rujBqO37hj3w== + dependencies: + "@typescript-eslint/scope-manager" "4.31.0" + "@typescript-eslint/types" "4.31.0" + "@typescript-eslint/typescript-estree" "4.31.0" + debug "^4.3.1" + +"@typescript-eslint/scope-manager@4.31.0": + version "4.31.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-4.31.0.tgz#9be33aed4e9901db753803ba233b70d79a87fc3e" + integrity sha512-LJ+xtl34W76JMRLjbaQorhR0hfRAlp3Lscdiz9NeI/8i+q0hdBZ7BsiYieLoYWqy+AnRigaD3hUwPFugSzdocg== + dependencies: + "@typescript-eslint/types" "4.31.0" + "@typescript-eslint/visitor-keys" "4.31.0" + +"@typescript-eslint/types@4.31.0": + version "4.31.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-4.31.0.tgz#9a7c86fcc1620189567dc4e46cad7efa07ee8dce" + integrity sha512-9XR5q9mk7DCXgXLS7REIVs+BaAswfdHhx91XqlJklmqWpTALGjygWVIb/UnLh4NWhfwhR5wNe1yTyCInxVhLqQ== + +"@typescript-eslint/typescript-estree@4.31.0": + version "4.31.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-4.31.0.tgz#4da4cb6274a7ef3b21d53f9e7147cc76f278a078" + integrity sha512-QHl2014t3ptg+xpmOSSPn5hm4mY8D4s97ftzyk9BZ8RxYQ3j73XcwuijnJ9cMa6DO4aLXeo8XS3z1omT9LA/Eg== + dependencies: + "@typescript-eslint/types" "4.31.0" + "@typescript-eslint/visitor-keys" "4.31.0" + debug "^4.3.1" + globby "^11.0.3" + is-glob "^4.0.1" + semver "^7.3.5" + tsutils "^3.21.0" + +"@typescript-eslint/visitor-keys@4.31.0": + version "4.31.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-4.31.0.tgz#4e87b7761cb4e0e627dc2047021aa693fc76ea2b" + integrity sha512-HUcRp2a9I+P21+O21yu3ezv3GEPGjyGiXoEUQwZXjR8UxRApGeLyWH4ZIIUSalE28aG4YsV6GjtaAVB3QKOu0w== + dependencies: + "@typescript-eslint/types" "4.31.0" + eslint-visitor-keys "^2.0.0" + "@ungap/promise-all-settled@1.1.2": version "1.1.2" resolved "https://registry.yarnpkg.com/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz#aa58042711d6e3275dd37dc597e5d31e8c290a44" @@ -4215,6 +4404,16 @@ ajv@^6.12.4: json-schema-traverse "^0.4.1" uri-js "^4.2.2" +ajv@^8.0.1: + version "8.6.2" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.6.2.tgz#2fb45e0e5fcbc0813326c1c3da535d1881bb0571" + integrity sha512-9807RlWAgT564wT+DjeyU5OFMPjmzxVobvDFmNAhY+5zD6A2ly3jDp6sgnfyDtlIQ+7H97oc/DGCzzfu9rjw9w== + dependencies: + fast-deep-equal "^3.1.1" + json-schema-traverse "^1.0.0" + require-from-string "^2.0.2" + uri-js "^4.2.2" + anser@^1.4.9: version "1.4.10" resolved "https://registry.yarnpkg.com/anser/-/anser-1.4.10.tgz#befa3eddf282684bd03b63dcda3927aef8c2e35b" @@ -4450,6 +4649,17 @@ array-includes@^3.0.3, array-includes@^3.1.1: es-abstract "^1.17.0" is-string "^1.0.5" +array-includes@^3.1.3: + version "3.1.3" + resolved "https://registry.yarnpkg.com/array-includes/-/array-includes-3.1.3.tgz#c7f619b382ad2afaf5326cddfdc0afc61af7690a" + integrity sha512-gcem1KlBU7c9rB+Rq8/3PPKsK2kjqeEBa3bD5kkQo4nYlOHQCJqIJFqBXDEfwaRuYTT4E+FxA9xez7Gf/e3Q7A== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.3" + es-abstract "^1.18.0-next.2" + get-intrinsic "^1.1.1" + is-string "^1.0.5" + array-map@~0.0.0: version "0.0.0" resolved "https://registry.yarnpkg.com/array-map/-/array-map-0.0.0.tgz#88a2bab73d1cf7bcd5c1b118a003f66f665fa662" @@ -4467,6 +4677,11 @@ array-union@^1.0.1: dependencies: array-uniq "^1.0.1" +array-union@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d" + integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw== + array-uniq@^1.0.1: version "1.0.3" resolved "https://registry.yarnpkg.com/array-uniq/-/array-uniq-1.0.3.tgz#af6ac877a25cc7f74e058894753858dfdb24fdb6" @@ -4485,7 +4700,7 @@ array.prototype.flat@^1.2.1: define-properties "^1.1.3" es-abstract "^1.17.0-next.1" -array.prototype.flat@^1.2.3: +array.prototype.flat@^1.2.3, array.prototype.flat@^1.2.4: version "1.2.4" resolved "https://registry.yarnpkg.com/array.prototype.flat/-/array.prototype.flat-1.2.4.tgz#6ef638b43312bd401b4c6199fdec7e2dc9e9a123" integrity sha512-4470Xi3GAPAjZqFcljX2xzckv1qeKPizoNkiS0+O4IoPR2ZNpcjE0pkhdihlDouK+x6QOast26B4Q/O9DJnwSg== @@ -6796,6 +7011,11 @@ diff-sequences@^25.2.6: resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-25.2.6.tgz#5f467c00edd35352b7bca46d7927d60e687a76dd" integrity sha512-Hq8o7+6GaZeoFjtpgvRBUknSXNeJiCx7V9Fr94ZMljNiCr9n9L8H8aJqgWOQiDDGdyn29fRNcDdRVJ5fdyihfg== +diff-sequences@^26.6.2: + version "26.6.2" + resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-26.6.2.tgz#48ba99157de1923412eed41db6b6d4aa9ca7c0b1" + integrity sha512-Mv/TDa3nZ9sbc5soK+OoA74BsS3mL37yixCvUAQkiuA4Wz6YtwP/K47n2rv2ovzHZvoiQeA5FTQOschKkEwB0Q== + diff-sequences@^27.0.6: version "27.0.6" resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-27.0.6.tgz#3305cb2e55a033924054695cc66019fd7f8e5723" @@ -6823,6 +7043,13 @@ dir-glob@2.0.0: arrify "^1.0.1" path-type "^3.0.0" +dir-glob@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f" + integrity sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA== + dependencies: + path-type "^4.0.0" + doctrine@1.5.0: version "1.5.0" resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-1.5.0.tgz#379dce730f6166f76cefa4e6707a159b02c5a6fa" @@ -7268,6 +7495,11 @@ eslint-config-airbnb@^18.1.0: object.assign "^4.1.0" object.entries "^1.1.1" +eslint-config-prettier@^8.3.0: + version "8.3.0" + resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-8.3.0.tgz#f7471b20b6fe8a9a9254cc684454202886a2dd7a" + integrity sha512-BgZuLUSeKzvlL/VUjx/Yb787VQ26RU3gGjA3iiFvdsp/2bMfVIWUVP7tjxtjS0e+HP409cPlPvNkQloz8C91ew== + eslint-import-resolver-node@^0.3.3: version "0.3.4" resolved "https://registry.yarnpkg.com/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.4.tgz#85ffa81942c25012d8231096ddf679c03042c717" @@ -7276,6 +7508,14 @@ eslint-import-resolver-node@^0.3.3: debug "^2.6.9" resolve "^1.13.1" +eslint-import-resolver-node@^0.3.6: + version "0.3.6" + resolved "https://registry.yarnpkg.com/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.6.tgz#4048b958395da89668252001dbd9eca6b83bacbd" + integrity sha512-0En0w03NRVMn9Uiyn8YRPDKvWjxCWkslUEhGNTdGx15RvPJYQ+lbOlqrlNI2vEAs4pDYK4f/HN2TbDmk5TP0iw== + dependencies: + debug "^3.2.7" + resolve "^1.20.0" + eslint-module-utils@^2.6.0: version "2.6.1" resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.6.1.tgz#b51be1e473dd0de1c5ea638e22429c2490ea8233" @@ -7284,6 +7524,14 @@ eslint-module-utils@^2.6.0: debug "^3.2.7" pkg-dir "^2.0.0" +eslint-module-utils@^2.6.2: + version "2.6.2" + resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.6.2.tgz#94e5540dd15fe1522e8ffa3ec8db3b7fa7e7a534" + integrity sha512-QG8pcgThYOuqxupd06oYTZoNOGaUdTY1PqK+oS6ElF6vs4pBdk/aYxFVQQXzcrAqp9m7cl7lb2ubazX+g16k2Q== + dependencies: + debug "^3.2.7" + pkg-dir "^2.0.0" + eslint-plugin-import@2.22.0: version "2.22.0" resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.22.0.tgz#92f7736fe1fde3e2de77623c838dd992ff5ffb7e" @@ -7303,6 +7551,27 @@ eslint-plugin-import@2.22.0: resolve "^1.17.0" tsconfig-paths "^3.9.0" +eslint-plugin-import@^2.17.2: + version "2.24.2" + resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.24.2.tgz#2c8cd2e341f3885918ee27d18479910ade7bb4da" + integrity sha512-hNVtyhiEtZmpsabL4neEj+6M5DCLgpYyG9nzJY8lZQeQXEn5UPW1DpUdsMHMXsq98dbNm7nt1w9ZMSVpfJdi8Q== + dependencies: + array-includes "^3.1.3" + array.prototype.flat "^1.2.4" + debug "^2.6.9" + doctrine "^2.1.0" + eslint-import-resolver-node "^0.3.6" + eslint-module-utils "^2.6.2" + find-up "^2.0.0" + has "^1.0.3" + is-core-module "^2.6.0" + minimatch "^3.0.4" + object.values "^1.1.4" + pkg-up "^2.0.0" + read-pkg-up "^3.0.0" + resolve "^1.20.0" + tsconfig-paths "^3.11.0" + eslint-plugin-jsx-a11y@6.3.1: version "6.3.1" resolved "https://registry.yarnpkg.com/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.3.1.tgz#99ef7e97f567cc6a5b8dd5ab95a94a67058a2660" @@ -7377,6 +7646,13 @@ eslint-utils@^2.1.0: dependencies: eslint-visitor-keys "^1.1.0" +eslint-utils@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-3.0.0.tgz#8aebaface7345bb33559db0a1f13a1d2d48c3672" + integrity sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA== + dependencies: + eslint-visitor-keys "^2.0.0" + eslint-visitor-keys@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.1.0.tgz#e2a82cea84ff246ad6fb57f9bde5b46621459ec2" @@ -7392,29 +7668,32 @@ eslint-visitor-keys@^2.0.0, eslint-visitor-keys@^2.1.0: resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz#f65328259305927392c938ed44eb0a5c9b2bd303" integrity sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw== -eslint@7.14.0: - version "7.14.0" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-7.14.0.tgz#2d2cac1d28174c510a97b377f122a5507958e344" - integrity sha512-5YubdnPXrlrYAFCKybPuHIAH++PINe1pmKNc5wQRB9HSbqIK1ywAnntE3Wwua4giKu0bjligf1gLF6qxMGOYRA== +eslint@^7.31.0: + version "7.32.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-7.32.0.tgz#c6d328a14be3fb08c8d1d21e12c02fdb7a2a812d" + integrity sha512-VHZ8gX+EDfz+97jGcgyGCyRia/dPOd6Xh9yPv8Bl1+SoaIwD+a/vlrOmGRUyOYu7MwUhc7CxqeaDZU13S4+EpA== dependencies: - "@babel/code-frame" "^7.0.0" - "@eslint/eslintrc" "^0.2.1" + "@babel/code-frame" "7.12.11" + "@eslint/eslintrc" "^0.4.3" + "@humanwhocodes/config-array" "^0.5.0" ajv "^6.10.0" chalk "^4.0.0" cross-spawn "^7.0.2" debug "^4.0.1" doctrine "^3.0.0" enquirer "^2.3.5" + escape-string-regexp "^4.0.0" eslint-scope "^5.1.1" eslint-utils "^2.1.0" eslint-visitor-keys "^2.0.0" - espree "^7.3.0" - esquery "^1.2.0" + espree "^7.3.1" + esquery "^1.4.0" esutils "^2.0.2" - file-entry-cache "^5.0.1" + fast-deep-equal "^3.1.3" + file-entry-cache "^6.0.1" functional-red-black-tree "^1.0.1" - glob-parent "^5.0.0" - globals "^12.1.0" + glob-parent "^5.1.2" + globals "^13.6.0" ignore "^4.0.6" import-fresh "^3.0.0" imurmurhash "^0.1.4" @@ -7422,7 +7701,7 @@ eslint@7.14.0: js-yaml "^3.13.1" json-stable-stringify-without-jsonify "^1.0.1" levn "^0.4.1" - lodash "^4.17.19" + lodash.merge "^4.6.2" minimatch "^3.0.4" natural-compare "^1.4.0" optionator "^0.9.1" @@ -7431,11 +7710,11 @@ eslint@7.14.0: semver "^7.2.1" strip-ansi "^6.0.0" strip-json-comments "^3.1.0" - table "^5.2.3" + table "^6.0.9" text-table "^0.2.0" v8-compile-cache "^2.0.3" -espree@^7.3.0: +espree@^7.3.0, espree@^7.3.1: version "7.3.1" resolved "https://registry.yarnpkg.com/espree/-/espree-7.3.1.tgz#f2df330b752c6f55019f8bd89b7660039c1bbbb6" integrity sha512-v3JCNCE64umkFpmkFGqzVKsOT0tN1Zr+ueqLZfpV1Ob8e+CEgPWa+OxCoGH3tnhimMKIaBm4m/vaRpJ/krRz2g== @@ -7449,7 +7728,7 @@ esprima@^4.0.0, esprima@^4.0.1, esprima@~4.0.0: resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== -esquery@^1.2.0: +esquery@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.4.0.tgz#2148ffc38b82e8c7057dfed48425b3e61f0f24a5" integrity sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w== @@ -7790,6 +8069,11 @@ fast-deep-equal@^3.1.1: resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.1.tgz#545145077c501491e33b15ec408c294376e94ae4" integrity sha512-8UEa58QDLauDNfpbrX55Q9jrGHThw2ZMdOky5Gl1CDtVeJDPVrG4Jxx1N8jw2gkWaff5UUuX1KJd+9zGe2B+ZA== +fast-deep-equal@^3.1.3: + version "3.1.3" + resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" + integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== + fast-glob@^2.0.2: version "2.2.7" resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-2.2.7.tgz#6953857c3afa475fff92ee6015d52da70a4cd39d" @@ -7802,6 +8086,17 @@ fast-glob@^2.0.2: merge2 "^1.2.3" micromatch "^3.1.10" +fast-glob@^3.1.1: + version "3.2.7" + resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.7.tgz#fd6cb7a2d7e9aa7a7846111e85a196d6b2f766a1" + integrity sha512-rYGMRwip6lUMvYD3BTScMwT1HtAs2d71SMv66Vrxs0IekGZEjhM0pcMfjQPnknBt2zeCwQMEupiN02ZP4DiT1Q== + dependencies: + "@nodelib/fs.stat" "^2.0.2" + "@nodelib/fs.walk" "^1.2.3" + glob-parent "^5.1.2" + merge2 "^1.3.0" + micromatch "^4.0.4" + fast-json-stable-stringify@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" @@ -7812,6 +8107,13 @@ fast-levenshtein@^2.0.6, fast-levenshtein@~2.0.6: resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc= +fastq@^1.6.0: + version "1.12.0" + resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.12.0.tgz#ed7b6ab5d62393fb2cc591c853652a5c318bf794" + integrity sha512-VNX0QkHK3RsXVKr9KrlUv/FoTa0NdbYoHHl7uXHv2rzyHSlxjdNAKug2twd9luJxpcyNeAgf5iPPMutJO67Dfg== + dependencies: + reusify "^1.0.4" + fault@^1.0.2: version "1.0.4" resolved "https://registry.yarnpkg.com/fault/-/fault-1.0.4.tgz#eafcfc0a6d214fc94601e170df29954a4f842f13" @@ -7911,12 +8213,12 @@ figures@^3.0.0: dependencies: escape-string-regexp "^1.0.5" -file-entry-cache@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-5.0.1.tgz#ca0f6efa6dd3d561333fb14515065c2fafdf439c" - integrity sha512-bCg29ictuBaKUwwArK4ouCaqDgLZcysCFLmM/Yn/FDoqndh/9vNuQfXRDvTuXKLxfD/JtZQGKFT8MGcJBK644g== +file-entry-cache@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz#211b2dd9659cb0394b073e7323ac3c933d522027" + integrity sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg== dependencies: - flat-cache "^2.0.1" + flat-cache "^3.0.4" file-loader@^4.2.0: version "4.3.0" @@ -8060,24 +8362,23 @@ find-yarn-workspace-root@^2.0.0: dependencies: micromatch "^4.0.2" -flat-cache@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-2.0.1.tgz#5d296d6f04bda44a4630a301413bdbc2ec085ec0" - integrity sha512-LoQe6yDuUMDzQAEH8sgmh4Md6oZnc/7PjtwjNFSzveXqSHt6ka9fPBuso7IGf9Rz4uqnSnWiFH2B/zj24a5ReA== +flat-cache@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-3.0.4.tgz#61b0338302b2fe9f957dcc32fc2a87f1c3048b11" + integrity sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg== dependencies: - flatted "^2.0.0" - rimraf "2.6.3" - write "1.0.3" + flatted "^3.1.0" + rimraf "^3.0.2" flat@^5.0.2: version "5.0.2" resolved "https://registry.yarnpkg.com/flat/-/flat-5.0.2.tgz#8ca6fe332069ffa9d324c327198c598259ceb241" integrity sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ== -flatted@^2.0.0: - version "2.0.2" - resolved "https://registry.yarnpkg.com/flatted/-/flatted-2.0.2.tgz#4575b21e2bcee7434aa9be662f4b7b5f9c2b5138" - integrity sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA== +flatted@^3.1.0: + version "3.2.2" + resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.2.2.tgz#64bfed5cb68fe3ca78b3eb214ad97b63bedce561" + integrity sha512-JaTY/wtrcSyvXJl4IMFHPKyFur1sE9AUqc0QnhOaJ0CxHtAoIV8pYDzeEfAaNEtGkOfq4gr3LBFmdXW5mOQFnA== flow-parser@0.*: version "0.156.0" @@ -8420,20 +8721,20 @@ glob-parent@^3.1.0: is-glob "^3.1.0" path-dirname "^1.0.0" -glob-parent@^5.0.0, glob-parent@~5.1.0: - version "5.1.1" - resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.1.tgz#b6c1ef417c4e5663ea498f1c45afac6916bbc229" - integrity sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ== - dependencies: - is-glob "^4.0.1" - -glob-parent@~5.1.2: +glob-parent@^5.1.2, glob-parent@~5.1.2: version "5.1.2" resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== dependencies: is-glob "^4.0.1" +glob-parent@~5.1.0: + version "5.1.1" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.1.tgz#b6c1ef417c4e5663ea498f1c45afac6916bbc229" + integrity sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ== + dependencies: + is-glob "^4.0.1" + glob-to-regexp@^0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.3.0.tgz#8c5a1494d2066c570cc3bfe4496175acc4d502ab" @@ -8523,12 +8824,12 @@ globals@^11.1.0: resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== -globals@^12.1.0: - version "12.4.0" - resolved "https://registry.yarnpkg.com/globals/-/globals-12.4.0.tgz#a18813576a41b00a24a97e7f815918c2e19925f8" - integrity sha512-BWICuzzDvDoH54NHKCseDanAhE3CeDorgDL5MT6LMXXj2WCnd9UC2szdk4AWLfjdgNBCXLUanXYcpBBKOSWGwg== +globals@^13.6.0, globals@^13.9.0: + version "13.11.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-13.11.0.tgz#40ef678da117fe7bd2e28f1fab24951bd0255be7" + integrity sha512-08/xrJ7wQjK9kkkRoI3OFUBbLx4f+6x3SGwcPvQ0QH6goFDrOU2oyAWrmh3dJezu65buo+HBMzAMQy6rovVC3g== dependencies: - type-fest "^0.8.1" + type-fest "^0.20.2" globalthis@^1.0.0: version "1.0.1" @@ -8550,6 +8851,18 @@ globby@8.0.2: pify "^3.0.0" slash "^1.0.0" +globby@^11.0.3: + version "11.0.4" + resolved "https://registry.yarnpkg.com/globby/-/globby-11.0.4.tgz#2cbaff77c2f2a62e71e9b2813a67b97a3a3001a5" + integrity sha512-9O4MVG9ioZJ08ffbcyVYyLOJLk5JQ688pJ4eMGLpdWLHq/Wr1D9BlriLQyL0E+jbkuePVZXYFj47QM/v093wHg== + dependencies: + array-union "^2.1.0" + dir-glob "^3.0.1" + fast-glob "^3.1.1" + ignore "^5.1.4" + merge2 "^1.3.0" + slash "^3.0.0" + good-listener@^1.2.2: version "1.2.2" resolved "https://registry.yarnpkg.com/good-listener/-/good-listener-1.2.2.tgz#d53b30cdf9313dffb7dc9a0d477096aa6d145c50" @@ -8924,6 +9237,11 @@ ignore@^4.0.6: resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc" integrity sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg== +ignore@^5.1.4: + version "5.1.8" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.1.8.tgz#f150a8b50a34289b33e22f5889abd4d8016f0e57" + integrity sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw== + image-q@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/image-q/-/image-q-1.1.1.tgz#fc84099664460b90ca862d9300b6bfbbbfbf8056" @@ -9225,6 +9543,13 @@ is-core-module@^2.2.0: dependencies: has "^1.0.3" +is-core-module@^2.6.0: + version "2.6.0" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.6.0.tgz#d7553b2526fe59b92ba3e40c8df757ec8a709e19" + integrity sha512-wShG8vs60jKfPWpF2KZRaAtvt3a20OAn7+IJ6hLPECpSABLcKtFKTTI4ZtH5QcBruBHlq+WsdHWyz0BCZW7svQ== + dependencies: + has "^1.0.3" + is-data-descriptor@^0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz#0b5ee648388e2c860282e793f1856fec3f301b56" @@ -9718,6 +10043,16 @@ jest-diff@^25.2.1: jest-get-type "^25.2.6" pretty-format "^25.5.0" +jest-diff@^26.0.0: + version "26.6.2" + resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-26.6.2.tgz#1aa7468b52c3a68d7d5c5fdcdfcd5e49bd164394" + integrity sha512-6m+9Z3Gv9wN0WFVasqjCL/06+EFCMTqDEUl/b87HYK2rAPTyfz4ZIuSlPhY51PIQRWx5TaxeF1qmXKe9gfN3sA== + dependencies: + chalk "^4.0.0" + diff-sequences "^26.6.2" + jest-get-type "^26.3.0" + pretty-format "^26.6.2" + jest-diff@^27.0.6: version "27.0.6" resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-27.0.6.tgz#4a7a19ee6f04ad70e0e3388f35829394a44c7b5e" @@ -10434,6 +10769,11 @@ json-schema-traverse@^0.4.1: resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== +json-schema-traverse@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz#ae7bcb3656ab77a73ba5c49bf654f38e6b6860e2" + integrity sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug== + json-stable-stringify-without-jsonify@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" @@ -10743,6 +11083,11 @@ lodash.camelcase@^4.3.0: resolved "https://registry.yarnpkg.com/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz#b28aa6288a2b9fc651035c7711f65ab6190331a6" integrity sha1-soqmKIorn8ZRA1x3EfZathkDMaY= +lodash.clonedeep@^4.5.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz#e23f3f9c4f8fbdde872529c1071857a086e5ccef" + integrity sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8= + lodash.debounce@^4.0.8: version "4.0.8" resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af" @@ -10778,6 +11123,11 @@ lodash.memoize@^4.1.2: resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" integrity sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4= +lodash.merge@^4.6.2: + version "4.6.2" + resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" + integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== + lodash.omit@^4.5.0: version "4.5.0" resolved "https://registry.yarnpkg.com/lodash.omit/-/lodash.omit-4.5.0.tgz#6eb19ae5a1ee1dd9df0b969e66ce0b7fa30b5e60" @@ -10808,7 +11158,12 @@ lodash.throttle@^4.1.1: resolved "https://registry.yarnpkg.com/lodash.throttle/-/lodash.throttle-4.1.1.tgz#c23e91b710242ac70c37f1e1cda9274cc39bf2f4" integrity sha1-wj6RtxAkKscMN/HhzaknTMOb8vQ= -lodash@4.17.21, lodash@^4.17.11, lodash@^4.17.12, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.5, lodash@^4.7.0: +lodash.truncate@^4.4.2: + version "4.4.2" + resolved "https://registry.yarnpkg.com/lodash.truncate/-/lodash.truncate-4.4.2.tgz#5a350da0b1113b837ecfffd5812cbe58d6eae193" + integrity sha1-WjUNoLERO4N+z//VgSy+WNbq4ZM= + +lodash@4.17.21, lodash@^4.17.11, lodash@^4.17.12, lodash@^4.17.20, lodash@^4.17.5, lodash@^4.7.0: version "4.17.21" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== @@ -11043,6 +11398,11 @@ merge2@^1.2.3: resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.3.0.tgz#5b366ee83b2f1582c48f87e47cf1a9352103ca81" integrity sha512-2j4DAdlBOkiSZIsaXk4mTE3sRS02yBHAtfy127xRV3bQUFqXkjHCHLW6Scv7DwNRbIWNHH8zpnz9zMaKXIdvYw== +merge2@^1.3.0: + version "1.4.1" + resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" + integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== + methods@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" @@ -11992,7 +12352,7 @@ object.values@^1.1.0: function-bind "^1.1.1" has "^1.0.3" -object.values@^1.1.1: +object.values@^1.1.1, object.values@^1.1.4: version "1.1.4" resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.1.4.tgz#0d273762833e816b693a637d30073e7051535b30" integrity sha512-TnGo7j4XSnKQoK3MfvkzqKCi0nVe/D9I9IjwTNYdb/fxYHpjrluHVOgw0AF6jrRFGMPHdfuidR09tIDiIvnaSg== @@ -12709,6 +13069,11 @@ prepend-http@^2.0.0: resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-2.0.0.tgz#e92434bfa5ea8c19f41cdfd401d741a3c819d897" integrity sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc= +prettier@^2.3.2: + version "2.3.2" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.3.2.tgz#ef280a05ec253712e486233db5c6f23441e7342d" + integrity sha512-lnJzDfJ66zkMy58OL5/NY5zp70S7Nz6KqcKkXYzn2tMVrNxvbqaBpg7H3qHaLxCJ5lNMsGuM8+ohS7cZrthdLQ== + pretty-bytes@5.6.0: version "5.6.0" resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-5.6.0.tgz#356256f643804773c82f64723fe78c92c62beaeb" @@ -12742,7 +13107,7 @@ pretty-format@^25.2.1, pretty-format@^25.5.0: ansi-styles "^4.0.0" react-is "^16.12.0" -pretty-format@^26.5.2, pretty-format@^26.6.2: +pretty-format@^26.0.0, pretty-format@^26.5.2, pretty-format@^26.6.2: version "26.6.2" resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-26.6.2.tgz#e35c2705f14cb7fe2fe94fa078345b444120fc93" integrity sha512-7AeGuCYNGmycyQbCqd/3PWH4eOoX/OiCa0uphp57NVTeAGdJGaAliecxwBDHYQCIvrW7aDBZCYeNTP/WX69mkg== @@ -13024,6 +13389,11 @@ querystringify@^2.1.1: resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.1.1.tgz#60e5a5fd64a7f8bfa4d2ab2ed6fdf4c85bad154e" integrity sha512-w7fLxIRCRT7U8Qu53jQnJyPkYZIaR4n5151KMfcJlO/A9397Wxb1amJvROTK6TOnp7PfoAmg/qXiNHI+08jRfA== +queue-microtask@^1.2.2: + version "1.2.3" + resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" + integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== + rambdax@2.15.0: version "2.15.0" resolved "https://registry.yarnpkg.com/rambdax/-/rambdax-2.15.0.tgz#34fb481cea1a88e64a25e3a25e34a258fa18ca12" @@ -13735,6 +14105,14 @@ read-pkg-up@^2.0.0: find-up "^2.0.0" read-pkg "^2.0.0" +read-pkg-up@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-3.0.0.tgz#3ed496685dba0f8fe118d0691dc51f4a1ff96f07" + integrity sha1-PtSWaF26D4/hGNBpHcUfSh/5bwc= + dependencies: + find-up "^2.0.0" + read-pkg "^3.0.0" + read-pkg-up@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-4.0.0.tgz#1b221c6088ba7799601c808f91161c66e58f8978" @@ -14031,6 +14409,11 @@ require-directory@^2.1.1: resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" integrity sha1-jGStX9MNqxyXbiNE/+f3kqam30I= +require-from-string@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-2.0.2.tgz#89a7fdd938261267318eafe14f9c32e598c36909" + integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw== + require-main-filename@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-2.0.0.tgz#d0b329ecc7cc0f61649f62215be69af54aa8989b" @@ -14124,12 +14507,10 @@ retry@^0.12.0: resolved "https://registry.yarnpkg.com/retry/-/retry-0.12.0.tgz#1b42a6266a21f07421d1b0b54b7dc167b01c013b" integrity sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs= -rimraf@2.6.3, rimraf@~2.6.2: - version "2.6.3" - resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.3.tgz#b2d104fe0d8fb27cf9e0a1cda8262dd3833c6cab" - integrity sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA== - dependencies: - glob "^7.1.3" +reusify@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" + integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== rimraf@^2.2.8, rimraf@^2.5.4, rimraf@^2.6.3, rimraf@^2.7.1: version "2.7.1" @@ -14138,7 +14519,7 @@ rimraf@^2.2.8, rimraf@^2.5.4, rimraf@^2.6.3, rimraf@^2.7.1: dependencies: glob "^7.1.3" -rimraf@^3.0.0: +rimraf@^3.0.0, rimraf@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== @@ -14157,6 +14538,13 @@ rimraf@~2.4.0: dependencies: glob "^6.0.1" +rimraf@~2.6.2: + version "2.6.3" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.3.tgz#b2d104fe0d8fb27cf9e0a1cda8262dd3833c6cab" + integrity sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA== + dependencies: + glob "^7.1.3" + ripemd160@^2.0.0, ripemd160@^2.0.1: version "2.0.2" resolved "https://registry.yarnpkg.com/ripemd160/-/ripemd160-2.0.2.tgz#a1c1a6f624751577ba5d07914cbc92850585890c" @@ -14197,6 +14585,13 @@ run-async@^2.2.0, run-async@^2.4.0: resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.4.1.tgz#8440eccf99ea3e70bd409d49aab88e10c189a455" integrity sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ== +run-parallel@^1.1.9: + version "1.2.0" + resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee" + integrity sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA== + dependencies: + queue-microtask "^1.2.2" + run-queue@^1.0.0, run-queue@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/run-queue/-/run-queue-1.0.3.tgz#e848396f057d223f24386924618e25694161ec47" @@ -14351,7 +14746,7 @@ semver@7.0.0: resolved "https://registry.yarnpkg.com/semver/-/semver-7.0.0.tgz#5f3ca35761e47e05b206c6daff2cf814f0316b8e" integrity sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A== -semver@7.3.5, semver@^7.0.0, semver@^7.2.1, semver@^7.3.2: +semver@7.3.5, semver@^7.0.0, semver@^7.2.1, semver@^7.3.2, semver@^7.3.5: version "7.3.5" resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.5.tgz#0b621c879348d8998e4b0e4be94b3f12e6018ef7" integrity sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ== @@ -14619,7 +15014,7 @@ slash@^3.0.0: resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== -slice-ansi@^2.0.0, slice-ansi@^2.1.0: +slice-ansi@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-2.1.0.tgz#cacd7693461a637a5788d92a7dd4fba068e81636" integrity sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ== @@ -15258,15 +15653,17 @@ table-layout@^1.0.1: typical "^5.2.0" wordwrapjs "^4.0.0" -table@^5.2.3: - version "5.4.6" - resolved "https://registry.yarnpkg.com/table/-/table-5.4.6.tgz#1292d19500ce3f86053b05f0e8e7e4a3bb21079e" - integrity sha512-wmEc8m4fjnob4gt5riFRtTu/6+4rSe12TpAELNSqHMfF3IqnA+CH37USM6/YR3qRZv7e56kAEAtd6nKZaxe0Ug== +table@^6.0.9: + version "6.7.1" + resolved "https://registry.yarnpkg.com/table/-/table-6.7.1.tgz#ee05592b7143831a8c94f3cee6aae4c1ccef33e2" + integrity sha512-ZGum47Yi6KOOFDE8m223td53ath2enHcYLgOCjGr5ngu8bdIARQk6mN/wRMv4yMRcHnCSnHbCEha4sobQx5yWg== dependencies: - ajv "^6.10.2" - lodash "^4.17.14" - slice-ansi "^2.1.0" - string-width "^3.0.0" + ajv "^8.0.1" + lodash.clonedeep "^4.5.0" + lodash.truncate "^4.4.2" + slice-ansi "^4.0.0" + string-width "^4.2.0" + strip-ansi "^6.0.0" tail@^2.0.0: version "2.2.1" @@ -15595,6 +15992,16 @@ ts-pnp@^1.1.2: resolved "https://registry.yarnpkg.com/ts-pnp/-/ts-pnp-1.2.0.tgz#a500ad084b0798f1c3071af391e65912c86bca92" integrity sha512-csd+vJOb/gkzvcCHgTGSChYpy5f1/XKNsmvBGO4JXS+z1v2HobugDz4s1IeFXM3wZB44uczs+eazB5Q/ccdhQw== +tsconfig-paths@^3.11.0: + version "3.11.0" + resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-3.11.0.tgz#954c1fe973da6339c78e06b03ce2e48810b65f36" + integrity sha512-7ecdYDnIdmv639mmDwslG6KQg1Z9STTz1j7Gcz0xa+nshh/gKDAHcPxRbWOsA3SPp0tXP2leTcY9Kw+NAkfZzA== + dependencies: + "@types/json5" "^0.0.29" + json5 "^1.0.1" + minimist "^1.2.0" + strip-bom "^3.0.0" + tsconfig-paths@^3.9.0: version "3.9.0" resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-3.9.0.tgz#098547a6c4448807e8fcb8eae081064ee9a3c90b" @@ -15610,7 +16017,7 @@ tslib@^1.10.0, tslib@^1.9.0: resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.13.0.tgz#c881e13cc7015894ed914862d276436fa9a47043" integrity sha512-i/6DQjL8Xf3be4K/E6Wgpekn5Qasl1usyw++dAA35Ue5orEn65VIxOA+YvNNl9HV3qv70T7CNwjODHZrLwvd1Q== -tslib@^1.9.3: +tslib@^1.8.1, tslib@^1.9.3: version "1.14.1" resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== @@ -15620,6 +16027,13 @@ tslib@^2.0.1, tslib@^2.0.3: resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.3.0.tgz#803b8cdab3e12ba581a4ca41c8839bbb0dacb09e" integrity sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg== +tsutils@^3.21.0: + version "3.21.0" + resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.21.0.tgz#b48717d394cea6c1e096983eed58e9d61715b623" + integrity sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA== + dependencies: + tslib "^1.8.1" + tty-browserify@0.0.0: version "0.0.0" resolved "https://registry.yarnpkg.com/tty-browserify/-/tty-browserify-0.0.0.tgz#a157ba402da24e9bf957f9aa69d524eed42901a6" @@ -15718,6 +16132,11 @@ typescript-tuple@^2.2.1: dependencies: typescript-compare "^0.0.2" +typescript@^4.3.5: + version "4.4.2" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.4.2.tgz#6d618640d430e3569a1dfb44f7d7e600ced3ee86" + integrity sha512-gzP+t5W4hdy4c+68bfcv0t400HVJMMd2+H9B7gae1nQlBzCqvrXX+6GL/b3GAgyTH966pzrZ70/fRjwAtZksSQ== + typical@^2.6.0: version "2.6.1" resolved "https://registry.yarnpkg.com/typical/-/typical-2.6.1.tgz#5c080e5d661cbbe38259d2e70a3c7253e873881d" @@ -16459,13 +16878,6 @@ write-file-atomic@^3.0.0: signal-exit "^3.0.2" typedarray-to-buffer "^3.1.5" -write@1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/write/-/write-1.0.3.tgz#0800e14523b923a387e415123c865616aae0f5c3" - integrity sha512-/lg70HAjtkUgWPVZhZcm+T4hkL8Zbtp1nFNOn3lRrxnlv50SRBv7cR7RqR+GMsd3hUXy9hWBo4CHTbFTcOYwig== - dependencies: - mkdirp "^0.5.1" - ws@^1.1.0, ws@^1.1.5: version "1.1.5" resolved "https://registry.yarnpkg.com/ws/-/ws-1.1.5.tgz#cbd9e6e75e09fc5d2c90015f21f0c40875e0dd51"