Merge branch 'develop' into erikj/group_server
This commit is contained in:
commit
8f9bf5f093
136 changed files with 8276 additions and 1888 deletions
185
.eslintignore.errorfiles
Normal file
185
.eslintignore.errorfiles
Normal file
|
@ -0,0 +1,185 @@
|
||||||
|
# autogenerated file: run scripts/generate-eslint-error-ignore-file to update.
|
||||||
|
|
||||||
|
src/AddThreepid.js
|
||||||
|
src/async-components/views/dialogs/EncryptedEventDialog.js
|
||||||
|
src/autocomplete/AutocompleteProvider.js
|
||||||
|
src/autocomplete/Autocompleter.js
|
||||||
|
src/autocomplete/Components.js
|
||||||
|
src/autocomplete/DuckDuckGoProvider.js
|
||||||
|
src/autocomplete/EmojiProvider.js
|
||||||
|
src/autocomplete/RoomProvider.js
|
||||||
|
src/autocomplete/UserProvider.js
|
||||||
|
src/Avatar.js
|
||||||
|
src/BasePlatform.js
|
||||||
|
src/CallHandler.js
|
||||||
|
src/component-index.js
|
||||||
|
src/components/structures/ContextualMenu.js
|
||||||
|
src/components/structures/CreateRoom.js
|
||||||
|
src/components/structures/FilePanel.js
|
||||||
|
src/components/structures/InteractiveAuth.js
|
||||||
|
src/components/structures/LoggedInView.js
|
||||||
|
src/components/structures/login/ForgotPassword.js
|
||||||
|
src/components/structures/login/Login.js
|
||||||
|
src/components/structures/login/PostRegistration.js
|
||||||
|
src/components/structures/login/Registration.js
|
||||||
|
src/components/structures/MessagePanel.js
|
||||||
|
src/components/structures/NotificationPanel.js
|
||||||
|
src/components/structures/RoomStatusBar.js
|
||||||
|
src/components/structures/RoomView.js
|
||||||
|
src/components/structures/ScrollPanel.js
|
||||||
|
src/components/structures/TimelinePanel.js
|
||||||
|
src/components/structures/UploadBar.js
|
||||||
|
src/components/views/avatars/BaseAvatar.js
|
||||||
|
src/components/views/avatars/MemberAvatar.js
|
||||||
|
src/components/views/avatars/RoomAvatar.js
|
||||||
|
src/components/views/create_room/CreateRoomButton.js
|
||||||
|
src/components/views/create_room/Presets.js
|
||||||
|
src/components/views/create_room/RoomAlias.js
|
||||||
|
src/components/views/dialogs/ChatCreateOrReuseDialog.js
|
||||||
|
src/components/views/dialogs/ChatInviteDialog.js
|
||||||
|
src/components/views/dialogs/DeactivateAccountDialog.js
|
||||||
|
src/components/views/dialogs/InteractiveAuthDialog.js
|
||||||
|
src/components/views/dialogs/SetMxIdDialog.js
|
||||||
|
src/components/views/dialogs/UnknownDeviceDialog.js
|
||||||
|
src/components/views/elements/AccessibleButton.js
|
||||||
|
src/components/views/elements/ActionButton.js
|
||||||
|
src/components/views/elements/AddressSelector.js
|
||||||
|
src/components/views/elements/AddressTile.js
|
||||||
|
src/components/views/elements/CreateRoomButton.js
|
||||||
|
src/components/views/elements/DeviceVerifyButtons.js
|
||||||
|
src/components/views/elements/DirectorySearchBox.js
|
||||||
|
src/components/views/elements/Dropdown.js
|
||||||
|
src/components/views/elements/EditableText.js
|
||||||
|
src/components/views/elements/EditableTextContainer.js
|
||||||
|
src/components/views/elements/HomeButton.js
|
||||||
|
src/components/views/elements/LanguageDropdown.js
|
||||||
|
src/components/views/elements/MemberEventListSummary.js
|
||||||
|
src/components/views/elements/PowerSelector.js
|
||||||
|
src/components/views/elements/ProgressBar.js
|
||||||
|
src/components/views/elements/RoomDirectoryButton.js
|
||||||
|
src/components/views/elements/SettingsButton.js
|
||||||
|
src/components/views/elements/StartChatButton.js
|
||||||
|
src/components/views/elements/TintableSvg.js
|
||||||
|
src/components/views/elements/TruncatedList.js
|
||||||
|
src/components/views/elements/UserSelector.js
|
||||||
|
src/components/views/login/CaptchaForm.js
|
||||||
|
src/components/views/login/CasLogin.js
|
||||||
|
src/components/views/login/CountryDropdown.js
|
||||||
|
src/components/views/login/CustomServerDialog.js
|
||||||
|
src/components/views/login/InteractiveAuthEntryComponents.js
|
||||||
|
src/components/views/login/LoginHeader.js
|
||||||
|
src/components/views/login/PasswordLogin.js
|
||||||
|
src/components/views/login/RegistrationForm.js
|
||||||
|
src/components/views/login/ServerConfig.js
|
||||||
|
src/components/views/messages/MAudioBody.js
|
||||||
|
src/components/views/messages/MessageEvent.js
|
||||||
|
src/components/views/messages/MFileBody.js
|
||||||
|
src/components/views/messages/MImageBody.js
|
||||||
|
src/components/views/messages/MVideoBody.js
|
||||||
|
src/components/views/messages/RoomAvatarEvent.js
|
||||||
|
src/components/views/messages/TextualBody.js
|
||||||
|
src/components/views/messages/TextualEvent.js
|
||||||
|
src/components/views/room_settings/AliasSettings.js
|
||||||
|
src/components/views/room_settings/ColorSettings.js
|
||||||
|
src/components/views/room_settings/UrlPreviewSettings.js
|
||||||
|
src/components/views/rooms/Autocomplete.js
|
||||||
|
src/components/views/rooms/AuxPanel.js
|
||||||
|
src/components/views/rooms/EntityTile.js
|
||||||
|
src/components/views/rooms/EventTile.js
|
||||||
|
src/components/views/rooms/LinkPreviewWidget.js
|
||||||
|
src/components/views/rooms/MemberDeviceInfo.js
|
||||||
|
src/components/views/rooms/MemberInfo.js
|
||||||
|
src/components/views/rooms/MemberList.js
|
||||||
|
src/components/views/rooms/MemberTile.js
|
||||||
|
src/components/views/rooms/MessageComposer.js
|
||||||
|
src/components/views/rooms/MessageComposerInput.js
|
||||||
|
src/components/views/rooms/MessageComposerInputOld.js
|
||||||
|
src/components/views/rooms/PresenceLabel.js
|
||||||
|
src/components/views/rooms/ReadReceiptMarker.js
|
||||||
|
src/components/views/rooms/RoomHeader.js
|
||||||
|
src/components/views/rooms/RoomList.js
|
||||||
|
src/components/views/rooms/RoomNameEditor.js
|
||||||
|
src/components/views/rooms/RoomPreviewBar.js
|
||||||
|
src/components/views/rooms/RoomSettings.js
|
||||||
|
src/components/views/rooms/RoomTile.js
|
||||||
|
src/components/views/rooms/RoomTopicEditor.js
|
||||||
|
src/components/views/rooms/SearchableEntityList.js
|
||||||
|
src/components/views/rooms/SearchResultTile.js
|
||||||
|
src/components/views/rooms/TabCompleteBar.js
|
||||||
|
src/components/views/rooms/TopUnreadMessagesBar.js
|
||||||
|
src/components/views/rooms/UserTile.js
|
||||||
|
src/components/views/settings/AddPhoneNumber.js
|
||||||
|
src/components/views/settings/ChangeAvatar.js
|
||||||
|
src/components/views/settings/ChangeDisplayName.js
|
||||||
|
src/components/views/settings/ChangePassword.js
|
||||||
|
src/components/views/settings/DevicesPanel.js
|
||||||
|
src/components/views/settings/DevicesPanelEntry.js
|
||||||
|
src/components/views/settings/EnableNotificationsButton.js
|
||||||
|
src/components/views/voip/CallView.js
|
||||||
|
src/components/views/voip/IncomingCallBox.js
|
||||||
|
src/components/views/voip/VideoFeed.js
|
||||||
|
src/components/views/voip/VideoView.js
|
||||||
|
src/ContentMessages.js
|
||||||
|
src/createRoom.js
|
||||||
|
src/DateUtils.js
|
||||||
|
src/email.js
|
||||||
|
src/Entities.js
|
||||||
|
src/extend.js
|
||||||
|
src/HtmlUtils.js
|
||||||
|
src/ImageUtils.js
|
||||||
|
src/Invite.js
|
||||||
|
src/languageHandler.js
|
||||||
|
src/linkify-matrix.js
|
||||||
|
src/Login.js
|
||||||
|
src/Markdown.js
|
||||||
|
src/MatrixClientPeg.js
|
||||||
|
src/Modal.js
|
||||||
|
src/Notifier.js
|
||||||
|
src/ObjectUtils.js
|
||||||
|
src/PasswordReset.js
|
||||||
|
src/PlatformPeg.js
|
||||||
|
src/Presence.js
|
||||||
|
src/ratelimitedfunc.js
|
||||||
|
src/Resend.js
|
||||||
|
src/RichText.js
|
||||||
|
src/Roles.js
|
||||||
|
src/RoomListSorter.js
|
||||||
|
src/RoomNotifs.js
|
||||||
|
src/Rooms.js
|
||||||
|
src/RtsClient.js
|
||||||
|
src/ScalarAuthClient.js
|
||||||
|
src/ScalarMessaging.js
|
||||||
|
src/SdkConfig.js
|
||||||
|
src/Skinner.js
|
||||||
|
src/SlashCommands.js
|
||||||
|
src/stores/LifecycleStore.js
|
||||||
|
src/TabComplete.js
|
||||||
|
src/TabCompleteEntries.js
|
||||||
|
src/TextForEvent.js
|
||||||
|
src/Tinter.js
|
||||||
|
src/UiEffects.js
|
||||||
|
src/Unread.js
|
||||||
|
src/UserActivity.js
|
||||||
|
src/utils/DecryptFile.js
|
||||||
|
src/utils/DMRoomMap.js
|
||||||
|
src/utils/FormattingUtils.js
|
||||||
|
src/utils/MultiInviter.js
|
||||||
|
src/utils/Receipt.js
|
||||||
|
src/Velociraptor.js
|
||||||
|
src/VelocityBounce.js
|
||||||
|
src/WhoIsTyping.js
|
||||||
|
src/wrappers/WithMatrixClient.js
|
||||||
|
test/all-tests.js
|
||||||
|
test/components/structures/login/Registration-test.js
|
||||||
|
test/components/structures/MessagePanel-test.js
|
||||||
|
test/components/structures/ScrollPanel-test.js
|
||||||
|
test/components/structures/TimelinePanel-test.js
|
||||||
|
test/components/stub-component.js
|
||||||
|
test/components/views/dialogs/InteractiveAuthDialog-test.js
|
||||||
|
test/components/views/elements/MemberEventListSummary-test.js
|
||||||
|
test/components/views/login/RegistrationForm-test.js
|
||||||
|
test/components/views/rooms/MessageComposerInput-test.js
|
||||||
|
test/mock-clock.js
|
||||||
|
test/skinned-sdk.js
|
||||||
|
test/stores/RoomViewStore-test.js
|
||||||
|
test/test-utils.js
|
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -12,3 +12,5 @@ npm-debug.log
|
||||||
|
|
||||||
/.idea
|
/.idea
|
||||||
/src/component-index.js
|
/src/component-index.js
|
||||||
|
|
||||||
|
.DS_Store
|
||||||
|
|
|
@ -5,6 +5,4 @@ install:
|
||||||
- npm install
|
- npm install
|
||||||
- (cd node_modules/matrix-js-sdk && npm install)
|
- (cd node_modules/matrix-js-sdk && npm install)
|
||||||
script:
|
script:
|
||||||
# don't run the riot tests unless the react-sdk tests pass, otherwise
|
./scripts/travis.sh
|
||||||
# the output is confusing.
|
|
||||||
- npm run test && ./.travis-test-riot.sh
|
|
||||||
|
|
192
CHANGELOG.md
192
CHANGELOG.md
|
@ -1,3 +1,195 @@
|
||||||
|
Changes in [0.9.4](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v0.9.4) (2017-06-14)
|
||||||
|
===================================================================================================
|
||||||
|
[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v0.9.3...v0.9.4)
|
||||||
|
|
||||||
|
* Ask for email address after setting password for the first time
|
||||||
|
[\#1090](https://github.com/matrix-org/matrix-react-sdk/pull/1090)
|
||||||
|
* DM guessing: prefer oldest joined member
|
||||||
|
[\#1087](https://github.com/matrix-org/matrix-react-sdk/pull/1087)
|
||||||
|
* More translations
|
||||||
|
|
||||||
|
Changes in [0.9.3](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v0.9.3) (2017-06-12)
|
||||||
|
===================================================================================================
|
||||||
|
[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v0.9.3-rc.2...v0.9.3)
|
||||||
|
|
||||||
|
* Add more translations & fix some existing ones
|
||||||
|
|
||||||
|
Changes in [0.9.3-rc.2](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v0.9.3-rc.2) (2017-06-09)
|
||||||
|
=============================================================================================================
|
||||||
|
[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v0.9.3-rc.1...v0.9.3-rc.2)
|
||||||
|
|
||||||
|
* Fix flux dependency
|
||||||
|
* Fix translations on conference call bar
|
||||||
|
|
||||||
|
Changes in [0.9.3-rc.1](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v0.9.3-rc.1) (2017-06-09)
|
||||||
|
=============================================================================================================
|
||||||
|
[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v0.9.2...v0.9.3-rc.1)
|
||||||
|
|
||||||
|
* When ChatCreateOrReuseDialog is cancelled by a guest, go home
|
||||||
|
[\#1069](https://github.com/matrix-org/matrix-react-sdk/pull/1069)
|
||||||
|
* Update from Weblate.
|
||||||
|
[\#1065](https://github.com/matrix-org/matrix-react-sdk/pull/1065)
|
||||||
|
* Goto /home when forgetting the last room
|
||||||
|
[\#1067](https://github.com/matrix-org/matrix-react-sdk/pull/1067)
|
||||||
|
* Default to home page when settings is closed
|
||||||
|
[\#1066](https://github.com/matrix-org/matrix-react-sdk/pull/1066)
|
||||||
|
* Update from Weblate.
|
||||||
|
[\#1063](https://github.com/matrix-org/matrix-react-sdk/pull/1063)
|
||||||
|
* When joining, use a roomAlias if we have it
|
||||||
|
[\#1062](https://github.com/matrix-org/matrix-react-sdk/pull/1062)
|
||||||
|
* Control currently viewed event via RoomViewStore
|
||||||
|
[\#1058](https://github.com/matrix-org/matrix-react-sdk/pull/1058)
|
||||||
|
* Better error messages for login
|
||||||
|
[\#1060](https://github.com/matrix-org/matrix-react-sdk/pull/1060)
|
||||||
|
* Add remaining translations
|
||||||
|
[\#1056](https://github.com/matrix-org/matrix-react-sdk/pull/1056)
|
||||||
|
* Added button that copies code to clipboard
|
||||||
|
[\#1040](https://github.com/matrix-org/matrix-react-sdk/pull/1040)
|
||||||
|
* de-lint MegolmExportEncryption + test
|
||||||
|
[\#1059](https://github.com/matrix-org/matrix-react-sdk/pull/1059)
|
||||||
|
* Better RTL support
|
||||||
|
[\#1021](https://github.com/matrix-org/matrix-react-sdk/pull/1021)
|
||||||
|
* make mels emoji capable
|
||||||
|
[\#1057](https://github.com/matrix-org/matrix-react-sdk/pull/1057)
|
||||||
|
* Make travis check for lint on files which are clean to start with
|
||||||
|
[\#1055](https://github.com/matrix-org/matrix-react-sdk/pull/1055)
|
||||||
|
* Update from Weblate.
|
||||||
|
[\#1053](https://github.com/matrix-org/matrix-react-sdk/pull/1053)
|
||||||
|
* Add some logging around switching rooms
|
||||||
|
[\#1054](https://github.com/matrix-org/matrix-react-sdk/pull/1054)
|
||||||
|
* Update from Weblate.
|
||||||
|
[\#1052](https://github.com/matrix-org/matrix-react-sdk/pull/1052)
|
||||||
|
* Use user_directory endpoint to populate ChatInviteDialog
|
||||||
|
[\#1050](https://github.com/matrix-org/matrix-react-sdk/pull/1050)
|
||||||
|
* Various Analytics changes/fixes/improvements
|
||||||
|
[\#1046](https://github.com/matrix-org/matrix-react-sdk/pull/1046)
|
||||||
|
* Use an arrow function to allow `this`
|
||||||
|
[\#1051](https://github.com/matrix-org/matrix-react-sdk/pull/1051)
|
||||||
|
* New guest access
|
||||||
|
[\#937](https://github.com/matrix-org/matrix-react-sdk/pull/937)
|
||||||
|
* Translate src/components/structures
|
||||||
|
[\#1048](https://github.com/matrix-org/matrix-react-sdk/pull/1048)
|
||||||
|
* Cancel 'join room' action if 'log in' is clicked
|
||||||
|
[\#1049](https://github.com/matrix-org/matrix-react-sdk/pull/1049)
|
||||||
|
* fix copy and paste derp and rip out unused imports
|
||||||
|
[\#1015](https://github.com/matrix-org/matrix-react-sdk/pull/1015)
|
||||||
|
* Update from Weblate.
|
||||||
|
[\#1042](https://github.com/matrix-org/matrix-react-sdk/pull/1042)
|
||||||
|
* Reset 'first sync' flag / promise on log in
|
||||||
|
[\#1041](https://github.com/matrix-org/matrix-react-sdk/pull/1041)
|
||||||
|
* Remove DM-guessing code (again)
|
||||||
|
[\#1036](https://github.com/matrix-org/matrix-react-sdk/pull/1036)
|
||||||
|
* Cancel deferred actions
|
||||||
|
[\#1039](https://github.com/matrix-org/matrix-react-sdk/pull/1039)
|
||||||
|
* Merge develop, add i18n for SetMxIdDialog
|
||||||
|
[\#1034](https://github.com/matrix-org/matrix-react-sdk/pull/1034)
|
||||||
|
* Defer an intention for creating a room
|
||||||
|
[\#1038](https://github.com/matrix-org/matrix-react-sdk/pull/1038)
|
||||||
|
* Fix 'create room' button
|
||||||
|
[\#1037](https://github.com/matrix-org/matrix-react-sdk/pull/1037)
|
||||||
|
* Always show the spinner during the first sync
|
||||||
|
[\#1033](https://github.com/matrix-org/matrix-react-sdk/pull/1033)
|
||||||
|
* Only view welcome user if we are not looking at a room
|
||||||
|
[\#1032](https://github.com/matrix-org/matrix-react-sdk/pull/1032)
|
||||||
|
* Update from Weblate.
|
||||||
|
[\#1030](https://github.com/matrix-org/matrix-react-sdk/pull/1030)
|
||||||
|
* Keep deferred actions for view_user_settings and view_create_chat
|
||||||
|
[\#1031](https://github.com/matrix-org/matrix-react-sdk/pull/1031)
|
||||||
|
* Don't do a deferred start chat if user is welcome user
|
||||||
|
[\#1029](https://github.com/matrix-org/matrix-react-sdk/pull/1029)
|
||||||
|
* Introduce state `peekLoading` to avoid collision with `roomLoading`
|
||||||
|
[\#1028](https://github.com/matrix-org/matrix-react-sdk/pull/1028)
|
||||||
|
* Update from Weblate.
|
||||||
|
[\#1016](https://github.com/matrix-org/matrix-react-sdk/pull/1016)
|
||||||
|
* Fix accepting a 3pid invite
|
||||||
|
[\#1013](https://github.com/matrix-org/matrix-react-sdk/pull/1013)
|
||||||
|
* Propagate room join errors to the UI
|
||||||
|
[\#1007](https://github.com/matrix-org/matrix-react-sdk/pull/1007)
|
||||||
|
* Implement /user/@userid:domain?action=chat
|
||||||
|
[\#1006](https://github.com/matrix-org/matrix-react-sdk/pull/1006)
|
||||||
|
* Show People/Rooms emptySubListTip even when total rooms !== 0
|
||||||
|
[\#967](https://github.com/matrix-org/matrix-react-sdk/pull/967)
|
||||||
|
* Fix to show the correct room
|
||||||
|
[\#995](https://github.com/matrix-org/matrix-react-sdk/pull/995)
|
||||||
|
* Remove cachedPassword from localStorage on_logged_out
|
||||||
|
[\#977](https://github.com/matrix-org/matrix-react-sdk/pull/977)
|
||||||
|
* Add /start to show the setMxId above HomePage
|
||||||
|
[\#964](https://github.com/matrix-org/matrix-react-sdk/pull/964)
|
||||||
|
* Allow pressing Enter to submit setMxId
|
||||||
|
[\#961](https://github.com/matrix-org/matrix-react-sdk/pull/961)
|
||||||
|
* add login link to SetMxIdDialog
|
||||||
|
[\#954](https://github.com/matrix-org/matrix-react-sdk/pull/954)
|
||||||
|
* Block user settings with view_set_mxid
|
||||||
|
[\#936](https://github.com/matrix-org/matrix-react-sdk/pull/936)
|
||||||
|
* Show "Something went wrong!" when errcode undefined
|
||||||
|
[\#935](https://github.com/matrix-org/matrix-react-sdk/pull/935)
|
||||||
|
* Reset store state when logging out
|
||||||
|
[\#930](https://github.com/matrix-org/matrix-react-sdk/pull/930)
|
||||||
|
* Set the displayname to the mxid once PWLU
|
||||||
|
[\#933](https://github.com/matrix-org/matrix-react-sdk/pull/933)
|
||||||
|
* Fix view_next_room, view_previous_room and view_indexed_room
|
||||||
|
[\#929](https://github.com/matrix-org/matrix-react-sdk/pull/929)
|
||||||
|
* Use RVS to indicate "joining" when setting a mxid
|
||||||
|
[\#928](https://github.com/matrix-org/matrix-react-sdk/pull/928)
|
||||||
|
* Don't show notif nag bar if guest
|
||||||
|
[\#932](https://github.com/matrix-org/matrix-react-sdk/pull/932)
|
||||||
|
* Show "Password" instead of "New Password"
|
||||||
|
[\#927](https://github.com/matrix-org/matrix-react-sdk/pull/927)
|
||||||
|
* Remove warm-fuzzy after setting mxid
|
||||||
|
[\#926](https://github.com/matrix-org/matrix-react-sdk/pull/926)
|
||||||
|
* Allow teamServerConfig to be missing
|
||||||
|
[\#925](https://github.com/matrix-org/matrix-react-sdk/pull/925)
|
||||||
|
* Remove GuestWarningBar
|
||||||
|
[\#923](https://github.com/matrix-org/matrix-react-sdk/pull/923)
|
||||||
|
* Make left panel better for new users (mk III)
|
||||||
|
[\#924](https://github.com/matrix-org/matrix-react-sdk/pull/924)
|
||||||
|
* Implement default welcome page and allow custom URL /w config
|
||||||
|
[\#922](https://github.com/matrix-org/matrix-react-sdk/pull/922)
|
||||||
|
* Implement a store for RoomView
|
||||||
|
[\#921](https://github.com/matrix-org/matrix-react-sdk/pull/921)
|
||||||
|
* Add prop to toggle whether new password input is autoFocused
|
||||||
|
[\#915](https://github.com/matrix-org/matrix-react-sdk/pull/915)
|
||||||
|
* Implement warm-fuzzy success dialog for SetMxIdDialog
|
||||||
|
[\#905](https://github.com/matrix-org/matrix-react-sdk/pull/905)
|
||||||
|
* Write some tests for the RTS UI
|
||||||
|
[\#893](https://github.com/matrix-org/matrix-react-sdk/pull/893)
|
||||||
|
* Make confirmation optional on ChangePassword
|
||||||
|
[\#890](https://github.com/matrix-org/matrix-react-sdk/pull/890)
|
||||||
|
* Remove "Current Password" input if mx_pass exists
|
||||||
|
[\#881](https://github.com/matrix-org/matrix-react-sdk/pull/881)
|
||||||
|
* Replace NeedToRegisterDialog /w SetMxIdDialog
|
||||||
|
[\#889](https://github.com/matrix-org/matrix-react-sdk/pull/889)
|
||||||
|
* Invite the welcome user after registration if configured
|
||||||
|
[\#882](https://github.com/matrix-org/matrix-react-sdk/pull/882)
|
||||||
|
* Prevent ROUs from creating new chats/new rooms
|
||||||
|
[\#879](https://github.com/matrix-org/matrix-react-sdk/pull/879)
|
||||||
|
* Redesign mxID chooser, add availability checking
|
||||||
|
[\#877](https://github.com/matrix-org/matrix-react-sdk/pull/877)
|
||||||
|
* Show password nag bar when user is PWLU
|
||||||
|
[\#864](https://github.com/matrix-org/matrix-react-sdk/pull/864)
|
||||||
|
* fix typo
|
||||||
|
[\#858](https://github.com/matrix-org/matrix-react-sdk/pull/858)
|
||||||
|
* Initial implementation: SetDisplayName -> SetMxIdDialog
|
||||||
|
[\#849](https://github.com/matrix-org/matrix-react-sdk/pull/849)
|
||||||
|
|
||||||
|
Changes in [0.9.2](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v0.9.2) (2017-06-06)
|
||||||
|
===================================================================================================
|
||||||
|
[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v0.9.1...v0.9.2)
|
||||||
|
|
||||||
|
* Hotfix: Allow password reset when logged in
|
||||||
|
[\#1044](https://github.com/matrix-org/matrix-react-sdk/pull/1044)
|
||||||
|
|
||||||
|
Changes in [0.9.1](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v0.9.1) (2017-06-02)
|
||||||
|
===================================================================================================
|
||||||
|
[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v0.9.0...v0.9.1)
|
||||||
|
|
||||||
|
* Update from Weblate.
|
||||||
|
[\#1012](https://github.com/matrix-org/matrix-react-sdk/pull/1012)
|
||||||
|
* typo, missing import and mis-casing
|
||||||
|
[\#1014](https://github.com/matrix-org/matrix-react-sdk/pull/1014)
|
||||||
|
* Update from Weblate.
|
||||||
|
[\#1010](https://github.com/matrix-org/matrix-react-sdk/pull/1010)
|
||||||
|
|
||||||
Changes in [0.9.0](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v0.9.0) (2017-06-02)
|
Changes in [0.9.0](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v0.9.0) (2017-06-02)
|
||||||
===================================================================================================
|
===================================================================================================
|
||||||
[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v0.9.0-rc.2...v0.9.0)
|
[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v0.9.0-rc.2...v0.9.0)
|
||||||
|
|
|
@ -21,6 +21,11 @@ npm run test
|
||||||
# run eslint
|
# run eslint
|
||||||
npm run lintall -- -f checkstyle -o eslint.xml || true
|
npm run lintall -- -f checkstyle -o eslint.xml || true
|
||||||
|
|
||||||
|
# re-run the linter, excluding any files known to have errors or warnings.
|
||||||
|
./node_modules/.bin/eslint --max-warnings 0 \
|
||||||
|
--ignore-path .eslintignore.errorfiles \
|
||||||
|
src test
|
||||||
|
|
||||||
# delete the old tarball, if it exists
|
# delete the old tarball, if it exists
|
||||||
rm -f matrix-react-sdk-*.tgz
|
rm -f matrix-react-sdk-*.tgz
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "matrix-react-sdk",
|
"name": "matrix-react-sdk",
|
||||||
"version": "0.9.0",
|
"version": "0.9.4",
|
||||||
"description": "SDK for matrix.org using React",
|
"description": "SDK for matrix.org using React",
|
||||||
"author": "matrix.org",
|
"author": "matrix.org",
|
||||||
"repository": {
|
"repository": {
|
||||||
|
@ -57,15 +57,16 @@
|
||||||
"emojione": "2.2.3",
|
"emojione": "2.2.3",
|
||||||
"file-saver": "^1.3.3",
|
"file-saver": "^1.3.3",
|
||||||
"filesize": "3.5.6",
|
"filesize": "3.5.6",
|
||||||
"flux": "^2.0.3",
|
"flux": "2.1.1",
|
||||||
"fuse.js": "^2.2.0",
|
"fuse.js": "^2.2.0",
|
||||||
"glob": "^5.0.14",
|
"glob": "^5.0.14",
|
||||||
"highlight.js": "^8.9.1",
|
"highlight.js": "^8.9.1",
|
||||||
"isomorphic-fetch": "^2.2.1",
|
"isomorphic-fetch": "^2.2.1",
|
||||||
"linkifyjs": "^2.1.3",
|
"linkifyjs": "^2.1.3",
|
||||||
"lodash": "^4.13.1",
|
"lodash": "^4.13.1",
|
||||||
"matrix-js-sdk": "0.7.10",
|
"matrix-js-sdk": "0.7.11",
|
||||||
"optimist": "^0.6.1",
|
"optimist": "^0.6.1",
|
||||||
|
"prop-types": "^15.5.8",
|
||||||
"q": "^1.4.1",
|
"q": "^1.4.1",
|
||||||
"react": "^15.4.0",
|
"react": "^15.4.0",
|
||||||
"react-addons-css-transition-group": "15.3.2",
|
"react-addons-css-transition-group": "15.3.2",
|
||||||
|
|
47
scripts/copy-i18n.py
Executable file
47
scripts/copy-i18n.py
Executable file
|
@ -0,0 +1,47 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
|
||||||
|
import json
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
|
||||||
|
if len(sys.argv) < 3:
|
||||||
|
print "Usage: %s <source> <dest>" % (sys.argv[0],)
|
||||||
|
print "eg. %s pt_BR.json pt.json" % (sys.argv[0],)
|
||||||
|
print
|
||||||
|
print "Adds any translations to <dest> that exist in <source> but not <dest>"
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
srcpath = sys.argv[1]
|
||||||
|
dstpath = sys.argv[2]
|
||||||
|
tmppath = dstpath + ".tmp"
|
||||||
|
|
||||||
|
with open(srcpath) as f:
|
||||||
|
src = json.load(f)
|
||||||
|
|
||||||
|
with open(dstpath) as f:
|
||||||
|
dst = json.load(f)
|
||||||
|
|
||||||
|
toAdd = {}
|
||||||
|
for k,v in src.iteritems():
|
||||||
|
if k not in dst:
|
||||||
|
print "Adding %s" % (k,)
|
||||||
|
toAdd[k] = v
|
||||||
|
|
||||||
|
# don't just json.dumps as we'll probably re-order all the keys (and they're
|
||||||
|
# not in any given order so we can't just sort_keys). Append them to the end.
|
||||||
|
with open(dstpath) as ifp:
|
||||||
|
with open(tmppath, 'w') as ofp:
|
||||||
|
for line in ifp:
|
||||||
|
strippedline = line.strip()
|
||||||
|
if strippedline in ('{', '}'):
|
||||||
|
ofp.write(line)
|
||||||
|
elif strippedline.endswith(','):
|
||||||
|
ofp.write(line)
|
||||||
|
else:
|
||||||
|
ofp.write(' '+strippedline+',')
|
||||||
|
toAddStr = json.dumps(toAdd, indent=4, separators=(',', ': '), ensure_ascii=False, encoding="utf8").strip("{}\n")
|
||||||
|
ofp.write("\n")
|
||||||
|
ofp.write(toAddStr.encode('utf8'))
|
||||||
|
ofp.write("\n")
|
||||||
|
|
||||||
|
os.rename(tmppath, dstpath)
|
|
@ -61,6 +61,16 @@ You are already in a call.
|
||||||
You cannot place VoIP calls in this browser.
|
You cannot place VoIP calls in this browser.
|
||||||
You cannot place a call with yourself.
|
You cannot place a call with yourself.
|
||||||
Your email address does not appear to be associated with a Matrix ID on this Homeserver.
|
Your email address does not appear to be associated with a Matrix ID on this Homeserver.
|
||||||
|
Guest users can't upload files. Please register to upload.
|
||||||
|
Some of your messages have not been sent.
|
||||||
|
This room is private or inaccessible to guests. You may be able to join if you register.
|
||||||
|
Tried to load a specific point in this room's timeline, but was unable to find it.
|
||||||
|
Tried to load a specific point in this room's timeline, but you do not have permission to view the message in question.
|
||||||
|
This action cannot be performed by a guest user. Please register to be able to do this.
|
||||||
|
Tried to load a specific point in this room's timeline, but was unable to find it.
|
||||||
|
Tried to load a specific point in this room's timeline, but you do not have permission to view the message in question.
|
||||||
|
You are trying to access %(roomName)s.
|
||||||
|
You will not be able to undo this change as you are promoting the user to have the same power level as yourself.
|
||||||
EOT
|
EOT
|
||||||
)];
|
)];
|
||||||
}
|
}
|
||||||
|
@ -84,7 +94,7 @@ if ($_ =~ m/^(\s+)"(.*?)"(: *)"(.*?)"(,?)$/) {
|
||||||
$sub = 1;
|
$sub = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($src eq $fixup && $dst !~ /\.$/) {
|
if ($ARGV !~ /(zh_Hans|zh_Hant|th)\.json$/ && $src eq $fixup && $dst !~ /\.$/) {
|
||||||
print STDERR "fixing up dst: $dst\n";
|
print STDERR "fixing up dst: $dst\n";
|
||||||
$dst .= '.';
|
$dst .= '.';
|
||||||
$sub = 1;
|
$sub = 1;
|
||||||
|
|
21
scripts/generate-eslint-error-ignore-file
Executable file
21
scripts/generate-eslint-error-ignore-file
Executable file
|
@ -0,0 +1,21 @@
|
||||||
|
#!/bin/sh
|
||||||
|
#
|
||||||
|
# generates .eslintignore.errorfiles to list the files which have errors in,
|
||||||
|
# so that they can be ignored in future automated linting.
|
||||||
|
|
||||||
|
out=.eslintignore.errorfiles
|
||||||
|
|
||||||
|
cd `dirname $0`/..
|
||||||
|
|
||||||
|
echo "generating $out"
|
||||||
|
|
||||||
|
{
|
||||||
|
cat <<EOF
|
||||||
|
# autogenerated file: run scripts/generate-eslint-error-ignore-file to update.
|
||||||
|
|
||||||
|
EOF
|
||||||
|
|
||||||
|
./node_modules/.bin/eslint --no-ignore -f json src test |
|
||||||
|
jq -r '.[] | select((.errorCount + .warningCount) > 0) | .filePath' |
|
||||||
|
sed -e 's/.*matrix-react-sdk\///';
|
||||||
|
} > "$out"
|
11
scripts/travis.sh
Executable file
11
scripts/travis.sh
Executable file
|
@ -0,0 +1,11 @@
|
||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
set -ex
|
||||||
|
|
||||||
|
npm run test
|
||||||
|
./.travis-test-riot.sh
|
||||||
|
|
||||||
|
# run the linter, but exclude any files known to have errors or warnings.
|
||||||
|
./node_modules/.bin/eslint --max-warnings 0 \
|
||||||
|
--ignore-path .eslintignore.errorfiles \
|
||||||
|
src test
|
|
@ -19,8 +19,10 @@ import MatrixClientPeg from './MatrixClientPeg';
|
||||||
import PlatformPeg from './PlatformPeg';
|
import PlatformPeg from './PlatformPeg';
|
||||||
import SdkConfig from './SdkConfig';
|
import SdkConfig from './SdkConfig';
|
||||||
|
|
||||||
function redact(str) {
|
function getRedactedUrl() {
|
||||||
return str.replace(/#\/(room|user)\/(.+)/, "#/$1/<redacted>");
|
const redactedHash = window.location.hash.replace(/#\/(room|user)\/(.+)/, "#/$1/<redacted>");
|
||||||
|
// hardcoded url to make piwik happy
|
||||||
|
return 'https://riot.im/app/' + redactedHash;
|
||||||
}
|
}
|
||||||
|
|
||||||
const customVariables = {
|
const customVariables = {
|
||||||
|
@ -28,6 +30,7 @@ const customVariables = {
|
||||||
'App Version': 2,
|
'App Version': 2,
|
||||||
'User Type': 3,
|
'User Type': 3,
|
||||||
'Chosen Language': 4,
|
'Chosen Language': 4,
|
||||||
|
'Instance': 5,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
@ -53,6 +56,7 @@ class Analytics {
|
||||||
* but this is second best, Piwik should not pull anything implicitly.
|
* but this is second best, Piwik should not pull anything implicitly.
|
||||||
*/
|
*/
|
||||||
disable() {
|
disable() {
|
||||||
|
this.trackEvent('Analytics', 'opt-out');
|
||||||
this.disabled = true;
|
this.disabled = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -84,6 +88,10 @@ class Analytics {
|
||||||
|
|
||||||
this._setVisitVariable('Chosen Language', getCurrentLanguage());
|
this._setVisitVariable('Chosen Language', getCurrentLanguage());
|
||||||
|
|
||||||
|
if (window.location.hostname === 'riot.im') {
|
||||||
|
this._setVisitVariable('Instance', window.location.pathname);
|
||||||
|
}
|
||||||
|
|
||||||
(function() {
|
(function() {
|
||||||
const g = document.createElement('script');
|
const g = document.createElement('script');
|
||||||
const s = document.getElementsByTagName('script')[0];
|
const s = document.getElementsByTagName('script')[0];
|
||||||
|
@ -108,7 +116,7 @@ class Analytics {
|
||||||
this.firstPage = false;
|
this.firstPage = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this._paq.push(['setCustomUrl', redact(window.location.href)]);
|
this._paq.push(['setCustomUrl', getRedactedUrl()]);
|
||||||
this._paq.push(['trackPageView']);
|
this._paq.push(['trackPageView']);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -51,13 +51,14 @@ limitations under the License.
|
||||||
* }
|
* }
|
||||||
*/
|
*/
|
||||||
|
|
||||||
var MatrixClientPeg = require('./MatrixClientPeg');
|
import MatrixClientPeg from './MatrixClientPeg';
|
||||||
var PlatformPeg = require("./PlatformPeg");
|
import UserSettingsStore from './UserSettingsStore';
|
||||||
var Modal = require('./Modal');
|
import PlatformPeg from './PlatformPeg';
|
||||||
var sdk = require('./index');
|
import Modal from './Modal';
|
||||||
|
import sdk from './index';
|
||||||
import { _t } from './languageHandler';
|
import { _t } from './languageHandler';
|
||||||
var Matrix = require("matrix-js-sdk");
|
import Matrix from 'matrix-js-sdk';
|
||||||
var dis = require("./dispatcher");
|
import dis from './dispatcher';
|
||||||
|
|
||||||
global.mxCalls = {
|
global.mxCalls = {
|
||||||
//room_id: MatrixCall
|
//room_id: MatrixCall
|
||||||
|
@ -257,9 +258,9 @@ function _onAction(payload) {
|
||||||
}
|
}
|
||||||
else if (members.length === 2) {
|
else if (members.length === 2) {
|
||||||
console.log("Place %s call in %s", payload.type, payload.room_id);
|
console.log("Place %s call in %s", payload.type, payload.room_id);
|
||||||
var call = Matrix.createNewMatrixCall(
|
const call = Matrix.createNewMatrixCall(MatrixClientPeg.get(), payload.room_id, {
|
||||||
MatrixClientPeg.get(), payload.room_id
|
forceTURN: UserSettingsStore.getLocalSetting('webRtcForceTURN', false),
|
||||||
);
|
});
|
||||||
placeCall(call);
|
placeCall(call);
|
||||||
}
|
}
|
||||||
else { // > 2
|
else { // > 2
|
||||||
|
|
|
@ -16,7 +16,6 @@
|
||||||
|
|
||||||
import UserSettingsStore from './UserSettingsStore';
|
import UserSettingsStore from './UserSettingsStore';
|
||||||
import * as Matrix from 'matrix-js-sdk';
|
import * as Matrix from 'matrix-js-sdk';
|
||||||
import q from 'q';
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
getDevices: function() {
|
getDevices: function() {
|
||||||
|
|
|
@ -345,6 +345,7 @@ export function bodyToHtml(content, highlights, opts) {
|
||||||
}
|
}
|
||||||
safeBody = sanitizeHtml(body, sanitizeHtmlParams);
|
safeBody = sanitizeHtml(body, sanitizeHtmlParams);
|
||||||
safeBody = unicodeToImage(safeBody);
|
safeBody = unicodeToImage(safeBody);
|
||||||
|
safeBody = addCodeCopyButton(safeBody);
|
||||||
}
|
}
|
||||||
finally {
|
finally {
|
||||||
delete sanitizeHtmlParams.textFilter;
|
delete sanitizeHtmlParams.textFilter;
|
||||||
|
@ -363,6 +364,23 @@ export function bodyToHtml(content, highlights, opts) {
|
||||||
return <span className={className} dangerouslySetInnerHTML={{ __html: safeBody }} dir="auto" />;
|
return <span className={className} dangerouslySetInnerHTML={{ __html: safeBody }} dir="auto" />;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function addCodeCopyButton(safeBody) {
|
||||||
|
// Adds 'copy' buttons to pre blocks
|
||||||
|
// Note that this only manipulates the markup to add the buttons:
|
||||||
|
// we need to add the event handlers once the nodes are in the DOM
|
||||||
|
// since we can't save functions in the markup.
|
||||||
|
// This is done in TextualBody
|
||||||
|
const el = document.createElement("div");
|
||||||
|
el.innerHTML = safeBody;
|
||||||
|
const codeBlocks = Array.from(el.getElementsByTagName("pre"));
|
||||||
|
codeBlocks.forEach(p => {
|
||||||
|
const button = document.createElement("span");
|
||||||
|
button.className = "mx_EventTile_copyButton";
|
||||||
|
p.appendChild(button);
|
||||||
|
});
|
||||||
|
return el.innerHTML;
|
||||||
|
}
|
||||||
|
|
||||||
export function emojifyText(text) {
|
export function emojifyText(text) {
|
||||||
return {
|
return {
|
||||||
__html: unicodeToImage(escape(text)),
|
__html: unicodeToImage(escape(text)),
|
||||||
|
|
|
@ -32,4 +32,5 @@ module.exports = {
|
||||||
DELETE: 46,
|
DELETE: 46,
|
||||||
KEY_D: 68,
|
KEY_D: 68,
|
||||||
KEY_E: 69,
|
KEY_E: 69,
|
||||||
|
KEY_M: 77,
|
||||||
};
|
};
|
||||||
|
|
150
src/Lifecycle.js
150
src/Lifecycle.js
|
@ -19,6 +19,7 @@ import q from 'q';
|
||||||
import Matrix from 'matrix-js-sdk';
|
import Matrix from 'matrix-js-sdk';
|
||||||
|
|
||||||
import MatrixClientPeg from './MatrixClientPeg';
|
import MatrixClientPeg from './MatrixClientPeg';
|
||||||
|
import createMatrixClient from './utils/createMatrixClient';
|
||||||
import Analytics from './Analytics';
|
import Analytics from './Analytics';
|
||||||
import Notifier from './Notifier';
|
import Notifier from './Notifier';
|
||||||
import UserActivity from './UserActivity';
|
import UserActivity from './UserActivity';
|
||||||
|
@ -34,9 +35,6 @@ import { _t } from './languageHandler';
|
||||||
* Called at startup, to attempt to build a logged-in Matrix session. It tries
|
* Called at startup, to attempt to build a logged-in Matrix session. It tries
|
||||||
* a number of things:
|
* a number of things:
|
||||||
*
|
*
|
||||||
* 0. if it looks like we are in the middle of a registration process, it does
|
|
||||||
* nothing.
|
|
||||||
*
|
|
||||||
* 1. if we have a loginToken in the (real) query params, it uses that to log
|
* 1. if we have a loginToken in the (real) query params, it uses that to log
|
||||||
* in.
|
* in.
|
||||||
*
|
*
|
||||||
|
@ -48,7 +46,7 @@ import { _t } from './languageHandler';
|
||||||
*
|
*
|
||||||
* 4. it attempts to auto-register as a guest user.
|
* 4. it attempts to auto-register as a guest user.
|
||||||
*
|
*
|
||||||
* If any of steps 1-4 are successful, it will call {setLoggedIn}, which in
|
* If any of steps 1-4 are successful, it will call {_doSetLoggedIn}, which in
|
||||||
* turn will raise on_logged_in and will_start_client events.
|
* turn will raise on_logged_in and will_start_client events.
|
||||||
*
|
*
|
||||||
* @param {object} opts
|
* @param {object} opts
|
||||||
|
@ -79,14 +77,6 @@ export function loadSession(opts) {
|
||||||
const guestIsUrl = opts.guestIsUrl;
|
const guestIsUrl = opts.guestIsUrl;
|
||||||
const defaultDeviceDisplayName = opts.defaultDeviceDisplayName;
|
const defaultDeviceDisplayName = opts.defaultDeviceDisplayName;
|
||||||
|
|
||||||
if (fragmentQueryParams.client_secret && fragmentQueryParams.sid) {
|
|
||||||
// this happens during email validation: the email contains a link to the
|
|
||||||
// IS, which in turn redirects back to vector. We let MatrixChat create a
|
|
||||||
// Registration component which completes the next stage of registration.
|
|
||||||
console.log("Not registering as guest: registration already in progress.");
|
|
||||||
return q();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!guestHsUrl) {
|
if (!guestHsUrl) {
|
||||||
console.warn("Cannot enable guest access: can't determine HS URL to use");
|
console.warn("Cannot enable guest access: can't determine HS URL to use");
|
||||||
enableGuest = false;
|
enableGuest = false;
|
||||||
|
@ -105,14 +95,13 @@ export function loadSession(opts) {
|
||||||
fragmentQueryParams.guest_access_token
|
fragmentQueryParams.guest_access_token
|
||||||
) {
|
) {
|
||||||
console.log("Using guest access credentials");
|
console.log("Using guest access credentials");
|
||||||
setLoggedIn({
|
return _doSetLoggedIn({
|
||||||
userId: fragmentQueryParams.guest_user_id,
|
userId: fragmentQueryParams.guest_user_id,
|
||||||
accessToken: fragmentQueryParams.guest_access_token,
|
accessToken: fragmentQueryParams.guest_access_token,
|
||||||
homeserverUrl: guestHsUrl,
|
homeserverUrl: guestHsUrl,
|
||||||
identityServerUrl: guestIsUrl,
|
identityServerUrl: guestIsUrl,
|
||||||
guest: true,
|
guest: true,
|
||||||
});
|
}, true);
|
||||||
return q();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return _restoreFromLocalStorage().then((success) => {
|
return _restoreFromLocalStorage().then((success) => {
|
||||||
|
@ -141,14 +130,14 @@ function _loginWithToken(queryParams, defaultDeviceDisplayName) {
|
||||||
},
|
},
|
||||||
).then(function(data) {
|
).then(function(data) {
|
||||||
console.log("Logged in with token");
|
console.log("Logged in with token");
|
||||||
setLoggedIn({
|
return _doSetLoggedIn({
|
||||||
userId: data.user_id,
|
userId: data.user_id,
|
||||||
deviceId: data.device_id,
|
deviceId: data.device_id,
|
||||||
accessToken: data.access_token,
|
accessToken: data.access_token,
|
||||||
homeserverUrl: queryParams.homeserver,
|
homeserverUrl: queryParams.homeserver,
|
||||||
identityServerUrl: queryParams.identityServer,
|
identityServerUrl: queryParams.identityServer,
|
||||||
guest: false,
|
guest: false,
|
||||||
});
|
}, true);
|
||||||
}, (err) => {
|
}, (err) => {
|
||||||
console.error("Failed to log in with login token: " + err + " " +
|
console.error("Failed to log in with login token: " + err + " " +
|
||||||
err.data);
|
err.data);
|
||||||
|
@ -172,14 +161,14 @@ function _registerAsGuest(hsUrl, isUrl, defaultDeviceDisplayName) {
|
||||||
},
|
},
|
||||||
}).then((creds) => {
|
}).then((creds) => {
|
||||||
console.log("Registered as guest: %s", creds.user_id);
|
console.log("Registered as guest: %s", creds.user_id);
|
||||||
setLoggedIn({
|
return _doSetLoggedIn({
|
||||||
userId: creds.user_id,
|
userId: creds.user_id,
|
||||||
deviceId: creds.device_id,
|
deviceId: creds.device_id,
|
||||||
accessToken: creds.access_token,
|
accessToken: creds.access_token,
|
||||||
homeserverUrl: hsUrl,
|
homeserverUrl: hsUrl,
|
||||||
identityServerUrl: isUrl,
|
identityServerUrl: isUrl,
|
||||||
guest: true,
|
guest: true,
|
||||||
});
|
}, true);
|
||||||
}, (err) => {
|
}, (err) => {
|
||||||
console.error("Failed to register as guest: " + err + " " + err.data);
|
console.error("Failed to register as guest: " + err + " " + err.data);
|
||||||
});
|
});
|
||||||
|
@ -187,6 +176,14 @@ function _registerAsGuest(hsUrl, isUrl, defaultDeviceDisplayName) {
|
||||||
|
|
||||||
// returns a promise which resolves to true if a session is found in
|
// returns a promise which resolves to true if a session is found in
|
||||||
// localstorage
|
// localstorage
|
||||||
|
//
|
||||||
|
// N.B. Lifecycle.js should not maintain any further localStorage state, we
|
||||||
|
// are moving towards using SessionStore to keep track of state related
|
||||||
|
// to the current session (which is typically backed by localStorage).
|
||||||
|
//
|
||||||
|
// The plan is to gradually move the localStorage access done here into
|
||||||
|
// SessionStore to avoid bugs where the view becomes out-of-sync with
|
||||||
|
// localStorage (e.g. teamToken, isGuest etc.)
|
||||||
function _restoreFromLocalStorage() {
|
function _restoreFromLocalStorage() {
|
||||||
if (!localStorage) {
|
if (!localStorage) {
|
||||||
return q(false);
|
return q(false);
|
||||||
|
@ -208,15 +205,14 @@ function _restoreFromLocalStorage() {
|
||||||
if (accessToken && userId && hsUrl) {
|
if (accessToken && userId && hsUrl) {
|
||||||
console.log("Restoring session for %s", userId);
|
console.log("Restoring session for %s", userId);
|
||||||
try {
|
try {
|
||||||
setLoggedIn({
|
return _doSetLoggedIn({
|
||||||
userId: userId,
|
userId: userId,
|
||||||
deviceId: deviceId,
|
deviceId: deviceId,
|
||||||
accessToken: accessToken,
|
accessToken: accessToken,
|
||||||
homeserverUrl: hsUrl,
|
homeserverUrl: hsUrl,
|
||||||
identityServerUrl: isUrl,
|
identityServerUrl: isUrl,
|
||||||
guest: isGuest,
|
guest: isGuest,
|
||||||
});
|
}, false).then(() => true);
|
||||||
return q(true);
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return _handleRestoreFailure(e);
|
return _handleRestoreFailure(e);
|
||||||
}
|
}
|
||||||
|
@ -273,13 +269,32 @@ export function initRtsClient(url) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Transitions to a logged-in state using the given credentials
|
* Transitions to a logged-in state using the given credentials.
|
||||||
|
*
|
||||||
|
* Starts the matrix client and all other react-sdk services that
|
||||||
|
* listen for events while a session is logged in.
|
||||||
|
*
|
||||||
|
* Also stops the old MatrixClient and clears old credentials/etc out of
|
||||||
|
* storage before starting the new client.
|
||||||
|
*
|
||||||
* @param {MatrixClientCreds} credentials The credentials to use
|
* @param {MatrixClientCreds} credentials The credentials to use
|
||||||
*/
|
*/
|
||||||
export function setLoggedIn(credentials) {
|
export function setLoggedIn(credentials) {
|
||||||
credentials.guest = Boolean(credentials.guest);
|
stopMatrixClient();
|
||||||
|
_doSetLoggedIn(credentials, true);
|
||||||
|
}
|
||||||
|
|
||||||
Analytics.setGuest(credentials.guest);
|
/**
|
||||||
|
* fires on_logging_in, optionally clears localstorage, persists new credentials
|
||||||
|
* to localstorage, starts the new client.
|
||||||
|
*
|
||||||
|
* @param {MatrixClientCreds} credentials
|
||||||
|
* @param {Boolean} clearStorage
|
||||||
|
*
|
||||||
|
* returns a Promise which resolves once the client has been started
|
||||||
|
*/
|
||||||
|
async function _doSetLoggedIn(credentials, clearStorage) {
|
||||||
|
credentials.guest = Boolean(credentials.guest);
|
||||||
|
|
||||||
console.log(
|
console.log(
|
||||||
"setLoggedIn: mxid:", credentials.userId,
|
"setLoggedIn: mxid:", credentials.userId,
|
||||||
|
@ -287,12 +302,19 @@ export function setLoggedIn(credentials) {
|
||||||
"guest:", credentials.guest,
|
"guest:", credentials.guest,
|
||||||
"hs:", credentials.homeserverUrl,
|
"hs:", credentials.homeserverUrl,
|
||||||
);
|
);
|
||||||
|
|
||||||
// This is dispatched to indicate that the user is still in the process of logging in
|
// This is dispatched to indicate that the user is still in the process of logging in
|
||||||
// because `teamPromise` may take some time to resolve, breaking the assumption that
|
// because `teamPromise` may take some time to resolve, breaking the assumption that
|
||||||
// `setLoggedIn` takes an "instant" to complete, and dispatch `on_logged_in` a few ms
|
// `setLoggedIn` takes an "instant" to complete, and dispatch `on_logged_in` a few ms
|
||||||
// later than MatrixChat might assume.
|
// later than MatrixChat might assume.
|
||||||
dis.dispatch({action: 'on_logging_in'});
|
dis.dispatch({action: 'on_logging_in'});
|
||||||
|
|
||||||
|
if (clearStorage) {
|
||||||
|
await _clearStorage();
|
||||||
|
}
|
||||||
|
|
||||||
|
Analytics.setGuest(credentials.guest);
|
||||||
|
|
||||||
// Resolves by default
|
// Resolves by default
|
||||||
let teamPromise = Promise.resolve(null);
|
let teamPromise = Promise.resolve(null);
|
||||||
|
|
||||||
|
@ -314,6 +336,16 @@ export function setLoggedIn(credentials) {
|
||||||
localStorage.setItem("mx_device_id", credentials.deviceId);
|
localStorage.setItem("mx_device_id", credentials.deviceId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The user registered as a PWLU (PassWord-Less User), the generated password
|
||||||
|
// is cached here such that the user can change it at a later time.
|
||||||
|
if (credentials.password) {
|
||||||
|
// Update SessionStore
|
||||||
|
dis.dispatch({
|
||||||
|
action: 'cached_password',
|
||||||
|
cachedPassword: credentials.password,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
console.log("Session persisted for %s", credentials.userId);
|
console.log("Session persisted for %s", credentials.userId);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.warn("Error using local storage: can't persist session!", e);
|
console.warn("Error using local storage: can't persist session!", e);
|
||||||
|
@ -331,13 +363,6 @@ export function setLoggedIn(credentials) {
|
||||||
console.warn("No local storage available: can't persist session!");
|
console.warn("No local storage available: can't persist session!");
|
||||||
}
|
}
|
||||||
|
|
||||||
// stop any running clients before we create a new one with these new credentials
|
|
||||||
//
|
|
||||||
// XXX: why do we have any running clients here? Maybe on sign-in after
|
|
||||||
// initial use as a guest? but what about our persistent storage? we need to
|
|
||||||
// be careful not to leak e2e data created as one user into another session.
|
|
||||||
stopMatrixClient();
|
|
||||||
|
|
||||||
MatrixClientPeg.replaceUsingCreds(credentials);
|
MatrixClientPeg.replaceUsingCreds(credentials);
|
||||||
|
|
||||||
teamPromise.then((teamToken) => {
|
teamPromise.then((teamToken) => {
|
||||||
|
@ -386,7 +411,7 @@ export function logout() {
|
||||||
* Starts the matrix client and all other react-sdk services that
|
* Starts the matrix client and all other react-sdk services that
|
||||||
* listen for events while a session is logged in.
|
* listen for events while a session is logged in.
|
||||||
*/
|
*/
|
||||||
export function startMatrixClient() {
|
function startMatrixClient() {
|
||||||
// dispatch this before starting the matrix client: it's used
|
// dispatch this before starting the matrix client: it's used
|
||||||
// to add listeners for the 'sync' event so otherwise we'd have
|
// to add listeners for the 'sync' event so otherwise we'd have
|
||||||
// a race condition (and we need to dispatch synchronously for this
|
// a race condition (and we need to dispatch synchronously for this
|
||||||
|
@ -402,46 +427,46 @@ export function startMatrixClient() {
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Stops a running client and all related services, used after
|
* Stops a running client and all related services, and clears persistent
|
||||||
* a session has been logged out / ended.
|
* storage. Used after a session has been logged out.
|
||||||
*/
|
*/
|
||||||
export function onLoggedOut() {
|
export function onLoggedOut() {
|
||||||
stopMatrixClient(true);
|
stopMatrixClient();
|
||||||
|
_clearStorage().done();
|
||||||
dis.dispatch({action: 'on_logged_out'});
|
dis.dispatch({action: 'on_logged_out'});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns {Promise} promise which resolves once the stores have been cleared
|
||||||
|
*/
|
||||||
function _clearStorage() {
|
function _clearStorage() {
|
||||||
Analytics.logout();
|
Analytics.logout();
|
||||||
|
|
||||||
const cli = MatrixClientPeg.get();
|
if (window.localStorage) {
|
||||||
if (cli) {
|
const hsUrl = window.localStorage.getItem("mx_hs_url");
|
||||||
// TODO: *really* ought to wait for the promise to complete
|
const isUrl = window.localStorage.getItem("mx_is_url");
|
||||||
cli.clearStores().done();
|
window.localStorage.clear();
|
||||||
|
|
||||||
|
// preserve our HS & IS URLs for convenience
|
||||||
|
// N.B. we cache them in hsUrl/isUrl and can't really inline them
|
||||||
|
// as getCurrentHsUrl() may call through to localStorage.
|
||||||
|
// NB. We do clear the device ID (as well as all the settings)
|
||||||
|
if (hsUrl) window.localStorage.setItem("mx_hs_url", hsUrl);
|
||||||
|
if (isUrl) window.localStorage.setItem("mx_is_url", isUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!window.localStorage) {
|
// create a temporary client to clear out the persistent stores.
|
||||||
return;
|
const cli = createMatrixClient({
|
||||||
}
|
// we'll never make any requests, so can pass a bogus HS URL
|
||||||
const hsUrl = window.localStorage.getItem("mx_hs_url");
|
baseUrl: "",
|
||||||
const isUrl = window.localStorage.getItem("mx_is_url");
|
});
|
||||||
window.localStorage.clear();
|
return cli.clearStores();
|
||||||
|
|
||||||
// preserve our HS & IS URLs for convenience
|
|
||||||
// N.B. we cache them in hsUrl/isUrl and can't really inline them
|
|
||||||
// as getCurrentHsUrl() may call through to localStorage.
|
|
||||||
// NB. We do clear the device ID (as well as all the settings)
|
|
||||||
if (hsUrl) window.localStorage.setItem("mx_hs_url", hsUrl);
|
|
||||||
if (isUrl) window.localStorage.setItem("mx_is_url", isUrl);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stop all the background processes related to the current client.
|
* Stop all the background processes related to the current client.
|
||||||
*
|
|
||||||
* Optionally clears persistent stores.
|
|
||||||
*
|
|
||||||
* @param {boolean} clearStores true to clear the persistent stores.
|
|
||||||
*/
|
*/
|
||||||
export function stopMatrixClient(clearStores) {
|
export function stopMatrixClient() {
|
||||||
Notifier.stop();
|
Notifier.stop();
|
||||||
UserActivity.stop();
|
UserActivity.stop();
|
||||||
Presence.stop();
|
Presence.stop();
|
||||||
|
@ -450,13 +475,6 @@ export function stopMatrixClient(clearStores) {
|
||||||
if (cli) {
|
if (cli) {
|
||||||
cli.stopClient();
|
cli.stopClient();
|
||||||
cli.removeAllListeners();
|
cli.removeAllListeners();
|
||||||
|
MatrixClientPeg.unset();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (clearStores) {
|
|
||||||
// note that we have to do this *after* stopping the client, but
|
|
||||||
// *before* clearing the MatrixClientPeg.
|
|
||||||
_clearStorage();
|
|
||||||
}
|
|
||||||
|
|
||||||
MatrixClientPeg.unset();
|
|
||||||
}
|
}
|
||||||
|
|
20
src/Login.js
20
src/Login.js
|
@ -97,11 +97,6 @@ export default class Login {
|
||||||
guest: true
|
guest: true
|
||||||
};
|
};
|
||||||
}, (error) => {
|
}, (error) => {
|
||||||
if (error.httpStatus === 403) {
|
|
||||||
error.friendlyText = _t("Guest access is disabled on this Home Server.");
|
|
||||||
} else {
|
|
||||||
error.friendlyText = _t("Failed to register as guest:") + ' ' + error.data;
|
|
||||||
}
|
|
||||||
throw error;
|
throw error;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -157,15 +152,7 @@ export default class Login {
|
||||||
accessToken: data.access_token
|
accessToken: data.access_token
|
||||||
});
|
});
|
||||||
}, function(error) {
|
}, function(error) {
|
||||||
if (error.httpStatus == 400 && loginParams.medium) {
|
if (error.httpStatus === 403) {
|
||||||
error.friendlyText = (
|
|
||||||
_t('This Home Server does not support login using email address.')
|
|
||||||
);
|
|
||||||
}
|
|
||||||
else if (error.httpStatus === 403) {
|
|
||||||
error.friendlyText = (
|
|
||||||
_t('Incorrect username and/or password.')
|
|
||||||
);
|
|
||||||
if (self._fallbackHsUrl) {
|
if (self._fallbackHsUrl) {
|
||||||
var fbClient = Matrix.createClient({
|
var fbClient = Matrix.createClient({
|
||||||
baseUrl: self._fallbackHsUrl,
|
baseUrl: self._fallbackHsUrl,
|
||||||
|
@ -186,11 +173,6 @@ export default class Login {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
|
||||||
error.friendlyText = (
|
|
||||||
_t("There was a problem logging in.") + ' (HTTP ' + error.httpStatus + ")"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
throw error;
|
throw error;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,13 +16,10 @@ limitations under the License.
|
||||||
|
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
import q from "q";
|
|
||||||
import Matrix from 'matrix-js-sdk';
|
|
||||||
import utils from 'matrix-js-sdk/lib/utils';
|
import utils from 'matrix-js-sdk/lib/utils';
|
||||||
import EventTimeline from 'matrix-js-sdk/lib/models/event-timeline';
|
import EventTimeline from 'matrix-js-sdk/lib/models/event-timeline';
|
||||||
import EventTimelineSet from 'matrix-js-sdk/lib/models/event-timeline-set';
|
import EventTimelineSet from 'matrix-js-sdk/lib/models/event-timeline-set';
|
||||||
|
import createMatrixClient from './utils/createMatrixClient';
|
||||||
const localStorage = window.localStorage;
|
|
||||||
|
|
||||||
interface MatrixClientCreds {
|
interface MatrixClientCreds {
|
||||||
homeserverUrl: string,
|
homeserverUrl: string,
|
||||||
|
@ -130,22 +127,7 @@ class MatrixClientPeg {
|
||||||
timelineSupport: true,
|
timelineSupport: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (localStorage) {
|
this.matrixClient = createMatrixClient(opts);
|
||||||
opts.sessionStore = new Matrix.WebStorageSessionStore(localStorage);
|
|
||||||
}
|
|
||||||
if (window.indexedDB && localStorage) {
|
|
||||||
// FIXME: bodge to remove old database. Remove this after a few weeks.
|
|
||||||
window.indexedDB.deleteDatabase("matrix-js-sdk:default");
|
|
||||||
|
|
||||||
opts.store = new Matrix.IndexedDBStore({
|
|
||||||
indexedDB: window.indexedDB,
|
|
||||||
dbName: "riot-web-sync",
|
|
||||||
localStorage: localStorage,
|
|
||||||
workerScript: this.indexedDbWorkerScript,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
this.matrixClient = Matrix.createClient(opts);
|
|
||||||
|
|
||||||
// we're going to add eventlisteners for each matrix event tile, so the
|
// we're going to add eventlisteners for each matrix event tile, so the
|
||||||
// potential number of event listeners is quite high.
|
// potential number of event listeners is quite high.
|
||||||
|
|
|
@ -64,7 +64,6 @@ const AsyncWrapper = React.createClass({
|
||||||
|
|
||||||
render: function() {
|
render: function() {
|
||||||
const {loader, ...otherProps} = this.props;
|
const {loader, ...otherProps} = this.props;
|
||||||
|
|
||||||
if (this.state.component) {
|
if (this.state.component) {
|
||||||
const Component = this.state.component;
|
const Component = this.state.component;
|
||||||
return <Component {...otherProps} />;
|
return <Component {...otherProps} />;
|
||||||
|
@ -199,4 +198,7 @@ class ModalManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default new ModalManager();
|
if (!global.singletonModalManager) {
|
||||||
|
global.singletonModalManager = new ModalManager();
|
||||||
|
}
|
||||||
|
export default global.singletonModalManager;
|
||||||
|
|
14
src/Rooms.js
14
src/Rooms.js
|
@ -15,7 +15,6 @@ limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import MatrixClientPeg from './MatrixClientPeg';
|
import MatrixClientPeg from './MatrixClientPeg';
|
||||||
import DMRoomMap from './utils/DMRoomMap';
|
|
||||||
import q from 'q';
|
import q from 'q';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -145,7 +144,18 @@ export function guessDMRoomTarget(room, me) {
|
||||||
let oldestTs;
|
let oldestTs;
|
||||||
let oldestUser;
|
let oldestUser;
|
||||||
|
|
||||||
// Pick the user who's been here longest (and isn't us)
|
// Pick the joined user who's been here longest (and isn't us),
|
||||||
|
for (const user of room.getJoinedMembers()) {
|
||||||
|
if (user.userId == me.userId) continue;
|
||||||
|
|
||||||
|
if (oldestTs === undefined || user.events.member.getTs() < oldestTs) {
|
||||||
|
oldestUser = user;
|
||||||
|
oldestTs = user.events.member.getTs();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (oldestUser) return oldestUser;
|
||||||
|
|
||||||
|
// if there are no joined members other than us, use the oldest member
|
||||||
for (const user of room.currentState.getMembers()) {
|
for (const user of room.currentState.getMembers()) {
|
||||||
if (user.userId == me.userId) continue;
|
if (user.userId == me.userId) continue;
|
||||||
|
|
||||||
|
|
|
@ -94,6 +94,22 @@ Example:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get_membership_count
|
||||||
|
--------------------
|
||||||
|
Get the number of joined users in the room.
|
||||||
|
|
||||||
|
Request:
|
||||||
|
- room_id is the room to get the count in.
|
||||||
|
Response:
|
||||||
|
78
|
||||||
|
Example:
|
||||||
|
{
|
||||||
|
action: "get_membership_count",
|
||||||
|
room_id: "!foo:bar",
|
||||||
|
response: 78
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
membership_state AND bot_options
|
membership_state AND bot_options
|
||||||
--------------------------------
|
--------------------------------
|
||||||
Get the content of the "m.room.member" or "m.room.bot.options" state event respectively.
|
Get the content of the "m.room.member" or "m.room.bot.options" state event respectively.
|
||||||
|
@ -256,6 +272,21 @@ function botOptions(event, roomId, userId) {
|
||||||
returnStateEvent(event, roomId, "m.room.bot.options", "_" + userId);
|
returnStateEvent(event, roomId, "m.room.bot.options", "_" + userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getMembershipCount(event, roomId) {
|
||||||
|
const client = MatrixClientPeg.get();
|
||||||
|
if (!client) {
|
||||||
|
sendError(event, _t('You need to be logged in.'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const room = client.getRoom(roomId);
|
||||||
|
if (!room) {
|
||||||
|
sendError(event, _t('This room is not recognised.'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const count = room.getJoinedMembers().length;
|
||||||
|
sendResponse(event, count);
|
||||||
|
}
|
||||||
|
|
||||||
function returnStateEvent(event, roomId, eventType, stateKey) {
|
function returnStateEvent(event, roomId, eventType, stateKey) {
|
||||||
const client = MatrixClientPeg.get();
|
const client = MatrixClientPeg.get();
|
||||||
if (!client) {
|
if (!client) {
|
||||||
|
@ -343,6 +374,9 @@ const onMessage = function(event) {
|
||||||
} else if (event.data.action === "set_plumbing_state") {
|
} else if (event.data.action === "set_plumbing_state") {
|
||||||
setPlumbingState(event, roomId, event.data.status);
|
setPlumbingState(event, roomId, event.data.status);
|
||||||
return;
|
return;
|
||||||
|
} else if (event.data.action === "get_membership_count") {
|
||||||
|
getMembershipCount(event, roomId);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!userId) {
|
if (!userId) {
|
||||||
|
|
|
@ -13,9 +13,8 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
See the License for the specific language governing permissions and
|
See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
import MatrixClientPeg from "./MatrixClientPeg";
|
||||||
var MatrixClientPeg = require("./MatrixClientPeg");
|
import CallHandler from "./CallHandler";
|
||||||
var CallHandler = require("./CallHandler");
|
|
||||||
import { _t } from './languageHandler';
|
import { _t } from './languageHandler';
|
||||||
import * as Roles from './Roles';
|
import * as Roles from './Roles';
|
||||||
|
|
||||||
|
@ -117,7 +116,7 @@ function textForTopicEvent(ev) {
|
||||||
|
|
||||||
function textForRoomNameEvent(ev) {
|
function textForRoomNameEvent(ev) {
|
||||||
var senderDisplayName = ev.sender && ev.sender.name ? ev.sender.name : ev.getSender();
|
var senderDisplayName = ev.sender && ev.sender.name ? ev.sender.name : ev.getSender();
|
||||||
|
|
||||||
if (!ev.getContent().name || ev.getContent().name.trim().length === 0) {
|
if (!ev.getContent().name || ev.getContent().name.trim().length === 0) {
|
||||||
return _t('%(senderDisplayName)s removed the room name.', {senderDisplayName: senderDisplayName});
|
return _t('%(senderDisplayName)s removed the room name.', {senderDisplayName: senderDisplayName});
|
||||||
}
|
}
|
||||||
|
@ -142,9 +141,21 @@ function textForCallAnswerEvent(event) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function textForCallHangupEvent(event) {
|
function textForCallHangupEvent(event) {
|
||||||
var senderName = event.sender ? event.sender.name : _t('Someone');
|
const senderName = event.sender ? event.sender.name : _t('Someone');
|
||||||
var supported = MatrixClientPeg.get().supportsVoip() ? "" : _t('(not supported by this browser)');
|
const eventContent = event.getContent();
|
||||||
return _t('%(senderName)s ended the call.', {senderName: senderName}) + ' ' + supported;
|
let reason = "";
|
||||||
|
if(!MatrixClientPeg.get().supportsVoip()) {
|
||||||
|
reason = _t('(not supported by this browser)');
|
||||||
|
} else if(eventContent.reason) {
|
||||||
|
if (eventContent.reason === "ice_failed") {
|
||||||
|
reason = _t('(could not connect media)');
|
||||||
|
} else if (eventContent.reason === "invite_timeout") {
|
||||||
|
reason = _t('(no answer)');
|
||||||
|
} else {
|
||||||
|
reason = _t('(unknown failure: %(reason)s)', {reason: eventContent.reason});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return _t('%(senderName)s ended the call.', {senderName}) + ' ' + reason;
|
||||||
}
|
}
|
||||||
|
|
||||||
function textForCallInviteEvent(event) {
|
function textForCallInviteEvent(event) {
|
||||||
|
|
|
@ -81,11 +81,13 @@ export default React.createClass({
|
||||||
FileSaver.saveAs(blob, 'riot-keys.txt');
|
FileSaver.saveAs(blob, 'riot-keys.txt');
|
||||||
this.props.onFinished(true);
|
this.props.onFinished(true);
|
||||||
}).catch((e) => {
|
}).catch((e) => {
|
||||||
|
console.error("Error exporting e2e keys:", e);
|
||||||
if (this._unmounted) {
|
if (this._unmounted) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
const msg = e.friendlyText || _t('Unknown error');
|
||||||
this.setState({
|
this.setState({
|
||||||
errStr: e.message,
|
errStr: msg,
|
||||||
phase: PHASE_EDIT,
|
phase: PHASE_EDIT,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -120,7 +122,7 @@ export default React.createClass({
|
||||||
'you have received in encrypted rooms to a local file. You ' +
|
'you have received in encrypted rooms to a local file. You ' +
|
||||||
'will then be able to import the file into another Matrix ' +
|
'will then be able to import the file into another Matrix ' +
|
||||||
'client in the future, so that client will also be able to ' +
|
'client in the future, so that client will also be able to ' +
|
||||||
'decrypt these messages.'
|
'decrypt these messages.',
|
||||||
) }
|
) }
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
|
@ -130,7 +132,7 @@ export default React.createClass({
|
||||||
'careful to keep it secure. To help with this, you should enter ' +
|
'careful to keep it secure. To help with this, you should enter ' +
|
||||||
'a passphrase below, which will be used to encrypt the exported ' +
|
'a passphrase below, which will be used to encrypt the exported ' +
|
||||||
'data. It will only be possible to import the data by using the ' +
|
'data. It will only be possible to import the data by using the ' +
|
||||||
'same passphrase.'
|
'same passphrase.',
|
||||||
) }
|
) }
|
||||||
</p>
|
</p>
|
||||||
<div className='error'>
|
<div className='error'>
|
||||||
|
|
|
@ -89,11 +89,13 @@ export default React.createClass({
|
||||||
// TODO: it would probably be nice to give some feedback about what we've imported here.
|
// TODO: it would probably be nice to give some feedback about what we've imported here.
|
||||||
this.props.onFinished(true);
|
this.props.onFinished(true);
|
||||||
}).catch((e) => {
|
}).catch((e) => {
|
||||||
|
console.error("Error importing e2e keys:", e);
|
||||||
if (this._unmounted) {
|
if (this._unmounted) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
const msg = e.friendlyText || _t('Unknown error');
|
||||||
this.setState({
|
this.setState({
|
||||||
errStr: e.message,
|
errStr: msg,
|
||||||
phase: PHASE_EDIT,
|
phase: PHASE_EDIT,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -122,13 +124,13 @@ export default React.createClass({
|
||||||
'This process allows you to import encryption keys ' +
|
'This process allows you to import encryption keys ' +
|
||||||
'that you had previously exported from another Matrix ' +
|
'that you had previously exported from another Matrix ' +
|
||||||
'client. You will then be able to decrypt any ' +
|
'client. You will then be able to decrypt any ' +
|
||||||
'messages that the other client could decrypt.'
|
'messages that the other client could decrypt.',
|
||||||
) }
|
) }
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
{ _t(
|
{ _t(
|
||||||
'The export file will be protected with a passphrase. ' +
|
'The export file will be protected with a passphrase. ' +
|
||||||
'You should enter the passphrase here, to decrypt the file.'
|
'You should enter the passphrase here, to decrypt the file.',
|
||||||
) }
|
) }
|
||||||
</p>
|
</p>
|
||||||
<div className='error'>
|
<div className='error'>
|
||||||
|
|
|
@ -15,7 +15,6 @@ limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import ReactDOM from 'react-dom';
|
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
|
||||||
/* These were earlier stateless functional components but had to be converted
|
/* These were earlier stateless functional components but had to be converted
|
||||||
|
|
|
@ -18,7 +18,6 @@ limitations under the License.
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { _t } from '../languageHandler';
|
import { _t } from '../languageHandler';
|
||||||
import AutocompleteProvider from './AutocompleteProvider';
|
import AutocompleteProvider from './AutocompleteProvider';
|
||||||
import Q from 'q';
|
|
||||||
import Fuse from 'fuse.js';
|
import Fuse from 'fuse.js';
|
||||||
import {PillCompletion} from './Components';
|
import {PillCompletion} from './Components';
|
||||||
import sdk from '../index';
|
import sdk from '../index';
|
||||||
|
|
|
@ -17,7 +17,6 @@ limitations under the License.
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import q from 'q';
|
|
||||||
import { _t } from '../../languageHandler';
|
import { _t } from '../../languageHandler';
|
||||||
import sdk from '../../index';
|
import sdk from '../../index';
|
||||||
import MatrixClientPeg from '../../MatrixClientPeg';
|
import MatrixClientPeg from '../../MatrixClientPeg';
|
||||||
|
@ -232,7 +231,7 @@ module.exports = React.createClass({
|
||||||
if (curr_phase == this.phases.ERROR) {
|
if (curr_phase == this.phases.ERROR) {
|
||||||
error_box = (
|
error_box = (
|
||||||
<div className="mx_Error">
|
<div className="mx_Error">
|
||||||
{_t('An error occured: %(error_string)s', {error_string: this.state.error_string})}
|
{_t('An error occurred: %(error_string)s', {error_string: this.state.error_string})}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -247,7 +246,7 @@ module.exports = React.createClass({
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mx_CreateRoom">
|
<div className="mx_CreateRoom">
|
||||||
<SimpleRoomHeader title="CreateRoom" collapsedRhs={ this.props.collapsedRhs }/>
|
<SimpleRoomHeader title={_t("Create Room")} collapsedRhs={ this.props.collapsedRhs }/>
|
||||||
<div className="mx_CreateRoom_body">
|
<div className="mx_CreateRoom_body">
|
||||||
<input type="text" ref="room_name" value={this.state.room_name} onChange={this.onNameChange} placeholder={_t('Name')}/> <br />
|
<input type="text" ref="room_name" value={this.state.room_name} onChange={this.onNameChange} placeholder={_t('Name')}/> <br />
|
||||||
<textarea className="mx_CreateRoom_description" ref="topic" value={this.state.topic} onChange={this.onTopicChange} placeholder={_t('Topic')}/> <br />
|
<textarea className="mx_CreateRoom_description" ref="topic" value={this.state.topic} onChange={this.onTopicChange} placeholder={_t('Topic')}/> <br />
|
||||||
|
|
|
@ -19,7 +19,7 @@ import React from 'react';
|
||||||
import Matrix from 'matrix-js-sdk';
|
import Matrix from 'matrix-js-sdk';
|
||||||
import sdk from '../../index';
|
import sdk from '../../index';
|
||||||
import MatrixClientPeg from '../../MatrixClientPeg';
|
import MatrixClientPeg from '../../MatrixClientPeg';
|
||||||
import { _t } from '../../languageHandler';
|
import { _t, _tJsx } from '../../languageHandler';
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Component which shows the filtered file using a TimelinePanel
|
* Component which shows the filtered file using a TimelinePanel
|
||||||
|
@ -91,7 +91,9 @@ var FilePanel = React.createClass({
|
||||||
render: function() {
|
render: function() {
|
||||||
if (MatrixClientPeg.get().isGuest()) {
|
if (MatrixClientPeg.get().isGuest()) {
|
||||||
return <div className="mx_FilePanel mx_RoomView_messageListWrapper">
|
return <div className="mx_FilePanel mx_RoomView_messageListWrapper">
|
||||||
<div className="mx_RoomView_empty">You must <a href="#/register">register</a> to use this functionality</div>
|
<div className="mx_RoomView_empty">
|
||||||
|
{_tJsx("You must <a>register</a> to use this functionality", /<a>(.*?)<\/a>/, (sub) => <a href="#/register" key="sub">{sub}</a>)}
|
||||||
|
</div>
|
||||||
</div>;
|
</div>;
|
||||||
} else if (this.noRoom) {
|
} else if (this.noRoom) {
|
||||||
return <div className="mx_FilePanel mx_RoomView_messageListWrapper">
|
return <div className="mx_FilePanel mx_RoomView_messageListWrapper">
|
||||||
|
|
|
@ -19,8 +19,6 @@ const InteractiveAuth = Matrix.InteractiveAuth;
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import sdk from '../../index';
|
|
||||||
|
|
||||||
import {getEntryComponentForLoginType} from '../views/login/InteractiveAuthEntryComponents';
|
import {getEntryComponentForLoginType} from '../views/login/InteractiveAuthEntryComponents';
|
||||||
|
|
||||||
export default React.createClass({
|
export default React.createClass({
|
||||||
|
|
|
@ -25,6 +25,8 @@ import PageTypes from '../../PageTypes';
|
||||||
import CallMediaHandler from '../../CallMediaHandler';
|
import CallMediaHandler from '../../CallMediaHandler';
|
||||||
import sdk from '../../index';
|
import sdk from '../../index';
|
||||||
import dis from '../../dispatcher';
|
import dis from '../../dispatcher';
|
||||||
|
import sessionStore from '../../stores/SessionStore';
|
||||||
|
import MatrixClientPeg from '../../MatrixClientPeg';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This is what our MatrixChat shows when we are logged in. The precise view is
|
* This is what our MatrixChat shows when we are logged in. The precise view is
|
||||||
|
@ -41,10 +43,13 @@ export default React.createClass({
|
||||||
propTypes: {
|
propTypes: {
|
||||||
matrixClient: React.PropTypes.instanceOf(Matrix.MatrixClient).isRequired,
|
matrixClient: React.PropTypes.instanceOf(Matrix.MatrixClient).isRequired,
|
||||||
page_type: React.PropTypes.string.isRequired,
|
page_type: React.PropTypes.string.isRequired,
|
||||||
onRoomIdResolved: React.PropTypes.func,
|
|
||||||
onRoomCreated: React.PropTypes.func,
|
onRoomCreated: React.PropTypes.func,
|
||||||
onUserSettingsClose: React.PropTypes.func,
|
onUserSettingsClose: React.PropTypes.func,
|
||||||
|
|
||||||
|
// Called with the credentials of a registered user (if they were a ROU that
|
||||||
|
// transitioned to PWLU)
|
||||||
|
onRegistered: React.PropTypes.func,
|
||||||
|
|
||||||
teamToken: React.PropTypes.string,
|
teamToken: React.PropTypes.string,
|
||||||
|
|
||||||
// and lots and lots of other stuff.
|
// and lots and lots of other stuff.
|
||||||
|
@ -83,12 +88,32 @@ export default React.createClass({
|
||||||
CallMediaHandler.loadDevices();
|
CallMediaHandler.loadDevices();
|
||||||
|
|
||||||
document.addEventListener('keydown', this._onKeyDown);
|
document.addEventListener('keydown', this._onKeyDown);
|
||||||
|
|
||||||
|
this._sessionStore = sessionStore;
|
||||||
|
this._sessionStoreToken = this._sessionStore.addListener(
|
||||||
|
this._setStateFromSessionStore,
|
||||||
|
);
|
||||||
|
this._setStateFromSessionStore();
|
||||||
|
|
||||||
this._matrixClient.on("accountData", this.onAccountData);
|
this._matrixClient.on("accountData", this.onAccountData);
|
||||||
},
|
},
|
||||||
|
|
||||||
componentWillUnmount: function() {
|
componentWillUnmount: function() {
|
||||||
document.removeEventListener('keydown', this._onKeyDown);
|
document.removeEventListener('keydown', this._onKeyDown);
|
||||||
this._matrixClient.removeListener("accountData", this.onAccountData);
|
this._matrixClient.removeListener("accountData", this.onAccountData);
|
||||||
|
if (this._sessionStoreToken) {
|
||||||
|
this._sessionStoreToken.remove();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Child components assume that the client peg will not be null, so give them some
|
||||||
|
// sort of assurance here by only allowing a re-render if the client is truthy.
|
||||||
|
//
|
||||||
|
// This is required because `LoggedInView` maintains its own state and if this state
|
||||||
|
// updates after the client peg has been made null (during logout), then it will
|
||||||
|
// attempt to re-render and the children will throw errors.
|
||||||
|
shouldComponentUpdate: function() {
|
||||||
|
return Boolean(MatrixClientPeg.get());
|
||||||
},
|
},
|
||||||
|
|
||||||
getScrollStateForRoom: function(roomId) {
|
getScrollStateForRoom: function(roomId) {
|
||||||
|
@ -102,10 +127,16 @@ export default React.createClass({
|
||||||
return this.refs.roomView.canResetTimeline();
|
return this.refs.roomView.canResetTimeline();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
_setStateFromSessionStore() {
|
||||||
|
this.setState({
|
||||||
|
userHasGeneratedPassword: Boolean(this._sessionStore.getCachedPassword()),
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
onAccountData: function(event) {
|
onAccountData: function(event) {
|
||||||
if (event.getType() === "im.vector.web.settings") {
|
if (event.getType() === "im.vector.web.settings") {
|
||||||
this.setState({
|
this.setState({
|
||||||
useCompactLayout: event.getContent().useCompactLayout
|
useCompactLayout: event.getContent().useCompactLayout,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -181,8 +212,8 @@ export default React.createClass({
|
||||||
const HomePage = sdk.getComponent('structures.HomePage');
|
const HomePage = sdk.getComponent('structures.HomePage');
|
||||||
const GroupView = sdk.getComponent('structures.GroupView');
|
const GroupView = sdk.getComponent('structures.GroupView');
|
||||||
const MatrixToolbar = sdk.getComponent('globals.MatrixToolbar');
|
const MatrixToolbar = sdk.getComponent('globals.MatrixToolbar');
|
||||||
const GuestWarningBar = sdk.getComponent('globals.GuestWarningBar');
|
|
||||||
const NewVersionBar = sdk.getComponent('globals.NewVersionBar');
|
const NewVersionBar = sdk.getComponent('globals.NewVersionBar');
|
||||||
|
const PasswordNagBar = sdk.getComponent('globals.PasswordNagBar');
|
||||||
|
|
||||||
let page_element;
|
let page_element;
|
||||||
let right_panel = '';
|
let right_panel = '';
|
||||||
|
@ -191,15 +222,12 @@ export default React.createClass({
|
||||||
case PageTypes.RoomView:
|
case PageTypes.RoomView:
|
||||||
page_element = <RoomView
|
page_element = <RoomView
|
||||||
ref='roomView'
|
ref='roomView'
|
||||||
roomAddress={this.props.currentRoomAlias || this.props.currentRoomId}
|
|
||||||
autoJoin={this.props.autoJoin}
|
autoJoin={this.props.autoJoin}
|
||||||
onRoomIdResolved={this.props.onRoomIdResolved}
|
onRegistered={this.props.onRegistered}
|
||||||
eventId={this.props.initialEventId}
|
|
||||||
thirdPartyInvite={this.props.thirdPartyInvite}
|
thirdPartyInvite={this.props.thirdPartyInvite}
|
||||||
oobData={this.props.roomOobData}
|
oobData={this.props.roomOobData}
|
||||||
highlightedEventId={this.props.highlightedEventId}
|
|
||||||
eventPixelOffset={this.props.initialEventPixelOffset}
|
eventPixelOffset={this.props.initialEventPixelOffset}
|
||||||
key={this.props.currentRoomAlias || this.props.currentRoomId}
|
key={this.props.currentRoomId || 'roomview'}
|
||||||
opacity={this.props.middleOpacity}
|
opacity={this.props.middleOpacity}
|
||||||
collapsedRhs={this.props.collapse_rhs}
|
collapsedRhs={this.props.collapse_rhs}
|
||||||
ConferenceHandler={this.props.ConferenceHandler}
|
ConferenceHandler={this.props.ConferenceHandler}
|
||||||
|
@ -236,12 +264,18 @@ export default React.createClass({
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case PageTypes.HomePage:
|
case PageTypes.HomePage:
|
||||||
|
// If team server config is present, pass the teamServerURL. props.teamToken
|
||||||
|
// must also be set for the team page to be displayed, otherwise the
|
||||||
|
// welcomePageUrl is used (which might be undefined).
|
||||||
|
const teamServerUrl = this.props.config.teamServerConfig ?
|
||||||
|
this.props.config.teamServerConfig.teamServerURL : null;
|
||||||
|
|
||||||
page_element = <HomePage
|
page_element = <HomePage
|
||||||
collapsedRhs={this.props.collapse_rhs}
|
collapsedRhs={this.props.collapse_rhs}
|
||||||
teamServerUrl={this.props.config.teamServerConfig.teamServerURL}
|
teamServerUrl={teamServerUrl}
|
||||||
teamToken={this.props.teamToken}
|
teamToken={this.props.teamToken}
|
||||||
/>
|
homePageUrl={this.props.config.welcomePageUrl}
|
||||||
if (!this.props.collapse_rhs) right_panel = <RightPanel opacity={this.props.rightOpacity}/>
|
/>;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case PageTypes.UserView:
|
case PageTypes.UserView:
|
||||||
|
@ -256,16 +290,15 @@ export default React.createClass({
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const isGuest = this.props.matrixClient.isGuest();
|
||||||
var topBar;
|
var topBar;
|
||||||
if (this.props.hasNewVersion) {
|
if (this.props.hasNewVersion) {
|
||||||
topBar = <NewVersionBar version={this.props.version} newVersion={this.props.newVersion}
|
topBar = <NewVersionBar version={this.props.version} newVersion={this.props.newVersion}
|
||||||
releaseNotes={this.props.newVersionReleaseNotes}
|
releaseNotes={this.props.newVersionReleaseNotes}
|
||||||
/>;
|
/>;
|
||||||
}
|
} else if (this.state.userHasGeneratedPassword) {
|
||||||
else if (this.props.matrixClient.isGuest()) {
|
topBar = <PasswordNagBar />;
|
||||||
topBar = <GuestWarningBar />;
|
} else if (!isGuest && Notifier.supportsDesktopNotifications() && !Notifier.isEnabled() && !Notifier.isToolbarHidden()) {
|
||||||
}
|
|
||||||
else if (Notifier.supportsDesktopNotifications() && !Notifier.isEnabled() && !Notifier.isToolbarHidden()) {
|
|
||||||
topBar = <MatrixToolbar />;
|
topBar = <MatrixToolbar />;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -285,7 +318,6 @@ export default React.createClass({
|
||||||
selectedRoom={this.props.currentRoomId}
|
selectedRoom={this.props.currentRoomId}
|
||||||
collapsed={this.props.collapse_lhs || false}
|
collapsed={this.props.collapse_lhs || false}
|
||||||
opacity={this.props.leftOpacity}
|
opacity={this.props.leftOpacity}
|
||||||
teamToken={this.props.teamToken}
|
|
||||||
/>
|
/>
|
||||||
<main className='mx_MatrixChat_middlePanel'>
|
<main className='mx_MatrixChat_middlePanel'>
|
||||||
{page_element}
|
{page_element}
|
||||||
|
|
|
@ -34,6 +34,9 @@ import sdk from '../../index';
|
||||||
import * as Rooms from '../../Rooms';
|
import * as Rooms from '../../Rooms';
|
||||||
import linkifyMatrix from "../../linkify-matrix";
|
import linkifyMatrix from "../../linkify-matrix";
|
||||||
import * as Lifecycle from '../../Lifecycle';
|
import * as Lifecycle from '../../Lifecycle';
|
||||||
|
// LifecycleStore is not used but does listen to and dispatch actions
|
||||||
|
require('../../stores/LifecycleStore');
|
||||||
|
import RoomViewStore from '../../stores/RoomViewStore';
|
||||||
import PageTypes from '../../PageTypes';
|
import PageTypes from '../../PageTypes';
|
||||||
|
|
||||||
import createRoom from "../../createRoom";
|
import createRoom from "../../createRoom";
|
||||||
|
@ -102,9 +105,6 @@ module.exports = React.createClass({
|
||||||
// What the LoggedInView would be showing if visible
|
// What the LoggedInView would be showing if visible
|
||||||
page_type: null,
|
page_type: null,
|
||||||
|
|
||||||
// If we are viewing a room by alias, this contains the alias
|
|
||||||
currentRoomAlias: null,
|
|
||||||
|
|
||||||
// The ID of the room we're viewing. This is either populated directly
|
// The ID of the room we're viewing. This is either populated directly
|
||||||
// in the case where we view a room by ID or by RoomView when it resolves
|
// in the case where we view a room by ID or by RoomView when it resolves
|
||||||
// what ID an alias points at.
|
// what ID an alias points at.
|
||||||
|
@ -128,11 +128,6 @@ module.exports = React.createClass({
|
||||||
hasNewVersion: false,
|
hasNewVersion: false,
|
||||||
newVersionReleaseNotes: null,
|
newVersionReleaseNotes: null,
|
||||||
|
|
||||||
// The username to default to when upgrading an account from a guest
|
|
||||||
upgradeUsername: null,
|
|
||||||
// The access token we had for our guest account, used when upgrading to a normal account
|
|
||||||
guestAccessToken: null,
|
|
||||||
|
|
||||||
// Parameters used in the registration dance with the IS
|
// Parameters used in the registration dance with the IS
|
||||||
register_client_secret: null,
|
register_client_secret: null,
|
||||||
register_session_id: null,
|
register_session_id: null,
|
||||||
|
@ -191,6 +186,9 @@ module.exports = React.createClass({
|
||||||
componentWillMount: function() {
|
componentWillMount: function() {
|
||||||
SdkConfig.put(this.props.config);
|
SdkConfig.put(this.props.config);
|
||||||
|
|
||||||
|
this._roomViewStoreToken = RoomViewStore.addListener(this._onRoomViewStoreUpdated);
|
||||||
|
this._onRoomViewStoreUpdated();
|
||||||
|
|
||||||
if (!UserSettingsStore.getLocalSetting('analyticsOptOut', false)) Analytics.enable();
|
if (!UserSettingsStore.getLocalSetting('analyticsOptOut', false)) Analytics.enable();
|
||||||
|
|
||||||
// Used by _viewRoom before getting state from sync
|
// Used by _viewRoom before getting state from sync
|
||||||
|
@ -274,6 +272,21 @@ module.exports = React.createClass({
|
||||||
Lifecycle.initRtsClient(this.props.config.teamServerConfig.teamServerURL);
|
Lifecycle.initRtsClient(this.props.config.teamServerConfig.teamServerURL);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// if the user has followed a login or register link, don't reanimate
|
||||||
|
// the old creds, but rather go straight to the relevant page
|
||||||
|
|
||||||
|
const firstScreen = this.state.screenAfterLogin ?
|
||||||
|
this.state.screenAfterLogin.screen : null;
|
||||||
|
|
||||||
|
if (firstScreen === 'login' ||
|
||||||
|
firstScreen === 'register' ||
|
||||||
|
firstScreen === 'forgot_password') {
|
||||||
|
this.props.onLoadCompleted();
|
||||||
|
this.setState({loading: false});
|
||||||
|
this._showScreenAfterLogin();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// the extra q() ensures that synchronous exceptions hit the same codepath as
|
// the extra q() ensures that synchronous exceptions hit the same codepath as
|
||||||
// asynchronous ones.
|
// asynchronous ones.
|
||||||
q().then(() => {
|
q().then(() => {
|
||||||
|
@ -295,11 +308,12 @@ module.exports = React.createClass({
|
||||||
},
|
},
|
||||||
|
|
||||||
componentWillUnmount: function() {
|
componentWillUnmount: function() {
|
||||||
Lifecycle.stopMatrixClient(false);
|
Lifecycle.stopMatrixClient();
|
||||||
dis.unregister(this.dispatcherRef);
|
dis.unregister(this.dispatcherRef);
|
||||||
UDEHandler.stopListening();
|
UDEHandler.stopListening();
|
||||||
window.removeEventListener("focus", this.onFocus);
|
window.removeEventListener("focus", this.onFocus);
|
||||||
window.removeEventListener('resize', this.handleResize);
|
window.removeEventListener('resize', this.handleResize);
|
||||||
|
this._roomViewStoreToken.remove();
|
||||||
},
|
},
|
||||||
|
|
||||||
componentDidUpdate: function() {
|
componentDidUpdate: function() {
|
||||||
|
@ -315,8 +329,6 @@ module.exports = React.createClass({
|
||||||
viewUserId: null,
|
viewUserId: null,
|
||||||
loggedIn: false,
|
loggedIn: false,
|
||||||
ready: false,
|
ready: false,
|
||||||
upgradeUsername: null,
|
|
||||||
guestAccessToken: null,
|
|
||||||
};
|
};
|
||||||
Object.assign(newState, state);
|
Object.assign(newState, state);
|
||||||
this.setState(newState);
|
this.setState(newState);
|
||||||
|
@ -325,7 +337,6 @@ module.exports = React.createClass({
|
||||||
onAction: function(payload) {
|
onAction: function(payload) {
|
||||||
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||||
const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
|
const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
|
||||||
const TextInputDialog = sdk.getComponent("dialogs.TextInputDialog");
|
|
||||||
|
|
||||||
switch (payload.action) {
|
switch (payload.action) {
|
||||||
case 'logout':
|
case 'logout':
|
||||||
|
@ -352,32 +363,17 @@ module.exports = React.createClass({
|
||||||
screen: 'post_registration',
|
screen: 'post_registration',
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
case 'start_upgrade_registration':
|
|
||||||
// also stash our credentials, then if we restore the session,
|
|
||||||
// we can just do it the same way whether we started upgrade
|
|
||||||
// registration or explicitly logged out
|
|
||||||
this.setStateForNewScreen({
|
|
||||||
guestCreds: MatrixClientPeg.getCredentials(),
|
|
||||||
screen: "register",
|
|
||||||
upgradeUsername: MatrixClientPeg.get().getUserIdLocalpart(),
|
|
||||||
guestAccessToken: MatrixClientPeg.get().getAccessToken(),
|
|
||||||
});
|
|
||||||
|
|
||||||
// stop the client: if we are syncing whilst the registration
|
|
||||||
// is completed in another browser, we'll be 401ed for using
|
|
||||||
// a guest access token for a non-guest account.
|
|
||||||
// It will be restarted in onReturnToGuestClick
|
|
||||||
Lifecycle.stopMatrixClient(false);
|
|
||||||
|
|
||||||
this.notifyNewScreen('register');
|
|
||||||
break;
|
|
||||||
case 'start_password_recovery':
|
case 'start_password_recovery':
|
||||||
if (this.state.loggedIn) return;
|
|
||||||
this.setStateForNewScreen({
|
this.setStateForNewScreen({
|
||||||
screen: 'forgot_password',
|
screen: 'forgot_password',
|
||||||
});
|
});
|
||||||
this.notifyNewScreen('forgot_password');
|
this.notifyNewScreen('forgot_password');
|
||||||
break;
|
break;
|
||||||
|
case 'start_chat':
|
||||||
|
createRoom({
|
||||||
|
dmUserId: payload.user_id,
|
||||||
|
});
|
||||||
|
break;
|
||||||
case 'leave_room':
|
case 'leave_room':
|
||||||
this._leaveRoom(payload.room_id);
|
this._leaveRoom(payload.room_id);
|
||||||
break;
|
break;
|
||||||
|
@ -438,24 +434,21 @@ module.exports = React.createClass({
|
||||||
this._viewIndexedRoom(payload.roomIndex);
|
this._viewIndexedRoom(payload.roomIndex);
|
||||||
break;
|
break;
|
||||||
case 'view_user_settings':
|
case 'view_user_settings':
|
||||||
|
if (MatrixClientPeg.get().isGuest()) {
|
||||||
|
dis.dispatch({
|
||||||
|
action: 'do_after_sync_prepared',
|
||||||
|
deferred_action: {
|
||||||
|
action: 'view_user_settings',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
dis.dispatch({action: 'view_set_mxid'});
|
||||||
|
break;
|
||||||
|
}
|
||||||
this._setPage(PageTypes.UserSettings);
|
this._setPage(PageTypes.UserSettings);
|
||||||
this.notifyNewScreen('settings');
|
this.notifyNewScreen('settings');
|
||||||
break;
|
break;
|
||||||
case 'view_create_room':
|
case 'view_create_room':
|
||||||
//this._setPage(PageTypes.CreateRoom);
|
this._createRoom();
|
||||||
//this.notifyNewScreen('new');
|
|
||||||
Modal.createDialog(TextInputDialog, {
|
|
||||||
title: _t('Create Room'),
|
|
||||||
description: _t('Room name (optional)'),
|
|
||||||
button: _t('Create Room'),
|
|
||||||
onFinished: (shouldCreate, name) => {
|
|
||||||
if (shouldCreate) {
|
|
||||||
const createOpts = {};
|
|
||||||
if (name) createOpts.name = name;
|
|
||||||
createRoom({createOpts}).done();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
|
||||||
break;
|
break;
|
||||||
case 'view_room_directory':
|
case 'view_room_directory':
|
||||||
this._setPage(PageTypes.RoomDirectory);
|
this._setPage(PageTypes.RoomDirectory);
|
||||||
|
@ -468,13 +461,15 @@ module.exports = React.createClass({
|
||||||
this.notifyNewScreen('group/' + groupId);
|
this.notifyNewScreen('group/' + groupId);
|
||||||
break;
|
break;
|
||||||
case 'view_home_page':
|
case 'view_home_page':
|
||||||
if (!this._teamToken) {
|
|
||||||
dis.dispatch({action: 'view_room_directory'});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this._setPage(PageTypes.HomePage);
|
this._setPage(PageTypes.HomePage);
|
||||||
this.notifyNewScreen('home');
|
this.notifyNewScreen('home');
|
||||||
break;
|
break;
|
||||||
|
case 'view_set_mxid':
|
||||||
|
this._setMxId(payload);
|
||||||
|
break;
|
||||||
|
case 'view_start_chat_or_reuse':
|
||||||
|
this._chatCreateOrReuse(payload.user_id);
|
||||||
|
break;
|
||||||
case 'view_create_chat':
|
case 'view_create_chat':
|
||||||
this._createChat();
|
this._createChat();
|
||||||
break;
|
break;
|
||||||
|
@ -516,7 +511,11 @@ module.exports = React.createClass({
|
||||||
this._onSetTheme(payload.value);
|
this._onSetTheme(payload.value);
|
||||||
break;
|
break;
|
||||||
case 'on_logging_in':
|
case 'on_logging_in':
|
||||||
this.setState({loggingIn: true});
|
// We are now logging in, so set the state to reflect that
|
||||||
|
// and also that we're not ready (we'll be marked as logged
|
||||||
|
// in once the login completes, then ready once the sync
|
||||||
|
// completes).
|
||||||
|
this.setState({loggingIn: true, ready: false});
|
||||||
break;
|
break;
|
||||||
case 'on_logged_in':
|
case 'on_logged_in':
|
||||||
this._onLoggedIn(payload.teamToken);
|
this._onLoggedIn(payload.teamToken);
|
||||||
|
@ -539,6 +538,10 @@ module.exports = React.createClass({
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
_onRoomViewStoreUpdated: function() {
|
||||||
|
this.setState({ currentRoomId: RoomViewStore.getRoomId() });
|
||||||
|
},
|
||||||
|
|
||||||
_setPage: function(pageType) {
|
_setPage: function(pageType) {
|
||||||
this.setState({
|
this.setState({
|
||||||
page_type: pageType,
|
page_type: pageType,
|
||||||
|
@ -561,10 +564,20 @@ module.exports = React.createClass({
|
||||||
this.notifyNewScreen('register');
|
this.notifyNewScreen('register');
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// TODO: Move to RoomViewStore
|
||||||
_viewNextRoom: function(roomIndexDelta) {
|
_viewNextRoom: function(roomIndexDelta) {
|
||||||
const allRooms = RoomListSorter.mostRecentActivityFirst(
|
const allRooms = RoomListSorter.mostRecentActivityFirst(
|
||||||
MatrixClientPeg.get().getRooms(),
|
MatrixClientPeg.get().getRooms(),
|
||||||
);
|
);
|
||||||
|
// If there are 0 rooms or 1 room, view the home page because otherwise
|
||||||
|
// if there are 0, we end up trying to index into an empty array, and
|
||||||
|
// if there is 1, we end up viewing the same room.
|
||||||
|
if (allRooms.length < 2) {
|
||||||
|
dis.dispatch({
|
||||||
|
action: 'view_home_page',
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
let roomIndex = -1;
|
let roomIndex = -1;
|
||||||
for (let i = 0; i < allRooms.length; ++i) {
|
for (let i = 0; i < allRooms.length; ++i) {
|
||||||
if (allRooms[i].roomId == this.state.currentRoomId) {
|
if (allRooms[i].roomId == this.state.currentRoomId) {
|
||||||
|
@ -574,15 +587,22 @@ module.exports = React.createClass({
|
||||||
}
|
}
|
||||||
roomIndex = (roomIndex + roomIndexDelta) % allRooms.length;
|
roomIndex = (roomIndex + roomIndexDelta) % allRooms.length;
|
||||||
if (roomIndex < 0) roomIndex = allRooms.length - 1;
|
if (roomIndex < 0) roomIndex = allRooms.length - 1;
|
||||||
this._viewRoom({ room_id: allRooms[roomIndex].roomId });
|
dis.dispatch({
|
||||||
|
action: 'view_room',
|
||||||
|
room_id: allRooms[roomIndex].roomId,
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// TODO: Move to RoomViewStore
|
||||||
_viewIndexedRoom: function(roomIndex) {
|
_viewIndexedRoom: function(roomIndex) {
|
||||||
const allRooms = RoomListSorter.mostRecentActivityFirst(
|
const allRooms = RoomListSorter.mostRecentActivityFirst(
|
||||||
MatrixClientPeg.get().getRooms(),
|
MatrixClientPeg.get().getRooms(),
|
||||||
);
|
);
|
||||||
if (allRooms[roomIndex]) {
|
if (allRooms[roomIndex]) {
|
||||||
this._viewRoom({ room_id: allRooms[roomIndex].roomId });
|
dis.dispatch({
|
||||||
|
action: 'view_room',
|
||||||
|
room_id: allRooms[roomIndex].roomId,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -595,6 +615,8 @@ module.exports = React.createClass({
|
||||||
// @param {boolean=} roomInfo.show_settings Makes RoomView show the room settings dialog.
|
// @param {boolean=} roomInfo.show_settings Makes RoomView show the room settings dialog.
|
||||||
// @param {string=} roomInfo.event_id ID of the event in this room to show: this will cause a switch to the
|
// @param {string=} roomInfo.event_id ID of the event in this room to show: this will cause a switch to the
|
||||||
// context of that particular event.
|
// context of that particular event.
|
||||||
|
// @param {boolean=} roomInfo.highlighted If true, add event_id to the hash of the URL
|
||||||
|
// and alter the EventTile to appear highlighted.
|
||||||
// @param {Object=} roomInfo.third_party_invite Object containing data about the third party
|
// @param {Object=} roomInfo.third_party_invite Object containing data about the third party
|
||||||
// we received to join the room, if any.
|
// we received to join the room, if any.
|
||||||
// @param {string=} roomInfo.third_party_invite.inviteSignUrl 3pid invite sign URL
|
// @param {string=} roomInfo.third_party_invite.inviteSignUrl 3pid invite sign URL
|
||||||
|
@ -606,30 +628,21 @@ module.exports = React.createClass({
|
||||||
this.focusComposer = true;
|
this.focusComposer = true;
|
||||||
|
|
||||||
const newState = {
|
const newState = {
|
||||||
initialEventId: roomInfo.event_id,
|
|
||||||
highlightedEventId: roomInfo.event_id,
|
|
||||||
initialEventPixelOffset: undefined,
|
|
||||||
page_type: PageTypes.RoomView,
|
page_type: PageTypes.RoomView,
|
||||||
thirdPartyInvite: roomInfo.third_party_invite,
|
thirdPartyInvite: roomInfo.third_party_invite,
|
||||||
roomOobData: roomInfo.oob_data,
|
roomOobData: roomInfo.oob_data,
|
||||||
currentRoomAlias: roomInfo.room_alias,
|
|
||||||
autoJoin: roomInfo.auto_join,
|
autoJoin: roomInfo.auto_join,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!roomInfo.room_alias) {
|
if (roomInfo.room_alias) {
|
||||||
newState.currentRoomId = roomInfo.room_id;
|
console.log(
|
||||||
}
|
`Switching to room alias ${roomInfo.room_alias} at event ` +
|
||||||
|
roomInfo.event_id,
|
||||||
// if we aren't given an explicit event id, look for one in the
|
);
|
||||||
// scrollStateMap.
|
} else {
|
||||||
//
|
console.log(`Switching to room id ${roomInfo.room_id} at event ` +
|
||||||
// TODO: do this in RoomView rather than here
|
roomInfo.event_id,
|
||||||
if (!roomInfo.event_id && this.refs.loggedInView) {
|
);
|
||||||
const scrollState = this.refs.loggedInView.getScrollStateForRoom(roomInfo.room_id);
|
|
||||||
if (scrollState) {
|
|
||||||
newState.initialEventId = scrollState.focussedEvent;
|
|
||||||
newState.initialEventPixelOffset = scrollState.pixelOffset;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wait for the first sync to complete so that if a room does have an alias,
|
// Wait for the first sync to complete so that if a room does have an alias,
|
||||||
|
@ -657,7 +670,7 @@ module.exports = React.createClass({
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (roomInfo.event_id) {
|
if (roomInfo.event_id && roomInfo.highlighted) {
|
||||||
presentedId += "/" + roomInfo.event_id;
|
presentedId += "/" + roomInfo.event_id;
|
||||||
}
|
}
|
||||||
this.notifyNewScreen('room/' + presentedId);
|
this.notifyNewScreen('room/' + presentedId);
|
||||||
|
@ -666,7 +679,46 @@ module.exports = React.createClass({
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
_setMxId: function(payload) {
|
||||||
|
const SetMxIdDialog = sdk.getComponent('views.dialogs.SetMxIdDialog');
|
||||||
|
const close = Modal.createDialog(SetMxIdDialog, {
|
||||||
|
homeserverUrl: MatrixClientPeg.get().getHomeserverUrl(),
|
||||||
|
onFinished: (submitted, credentials) => {
|
||||||
|
if (!submitted) {
|
||||||
|
dis.dispatch({
|
||||||
|
action: 'cancel_after_sync_prepared',
|
||||||
|
});
|
||||||
|
if (payload.go_home_on_cancel) {
|
||||||
|
dis.dispatch({
|
||||||
|
action: 'view_home_page',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.onRegistered(credentials);
|
||||||
|
},
|
||||||
|
onDifferentServerClicked: (ev) => {
|
||||||
|
dis.dispatch({action: 'start_registration'});
|
||||||
|
close();
|
||||||
|
},
|
||||||
|
onLoginClick: (ev) => {
|
||||||
|
dis.dispatch({action: 'start_login'});
|
||||||
|
close();
|
||||||
|
},
|
||||||
|
}).close;
|
||||||
|
},
|
||||||
|
|
||||||
_createChat: function() {
|
_createChat: function() {
|
||||||
|
if (MatrixClientPeg.get().isGuest()) {
|
||||||
|
dis.dispatch({
|
||||||
|
action: 'do_after_sync_prepared',
|
||||||
|
deferred_action: {
|
||||||
|
action: 'view_create_chat',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
dis.dispatch({action: 'view_set_mxid'});
|
||||||
|
return;
|
||||||
|
}
|
||||||
const ChatInviteDialog = sdk.getComponent("dialogs.ChatInviteDialog");
|
const ChatInviteDialog = sdk.getComponent("dialogs.ChatInviteDialog");
|
||||||
Modal.createDialog(ChatInviteDialog, {
|
Modal.createDialog(ChatInviteDialog, {
|
||||||
title: _t('Start a chat'),
|
title: _t('Start a chat'),
|
||||||
|
@ -676,6 +728,86 @@ module.exports = React.createClass({
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
_createRoom: function() {
|
||||||
|
if (MatrixClientPeg.get().isGuest()) {
|
||||||
|
dis.dispatch({
|
||||||
|
action: 'do_after_sync_prepared',
|
||||||
|
deferred_action: {
|
||||||
|
action: 'view_create_room',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
dis.dispatch({action: 'view_set_mxid'});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const TextInputDialog = sdk.getComponent("dialogs.TextInputDialog");
|
||||||
|
Modal.createDialog(TextInputDialog, {
|
||||||
|
title: _t('Create Room'),
|
||||||
|
description: _t('Room name (optional)'),
|
||||||
|
button: _t('Create Room'),
|
||||||
|
onFinished: (shouldCreate, name) => {
|
||||||
|
if (shouldCreate) {
|
||||||
|
const createOpts = {};
|
||||||
|
if (name) createOpts.name = name;
|
||||||
|
createRoom({createOpts}).done();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
_chatCreateOrReuse: function(userId) {
|
||||||
|
const ChatCreateOrReuseDialog = sdk.getComponent(
|
||||||
|
'views.dialogs.ChatCreateOrReuseDialog',
|
||||||
|
);
|
||||||
|
// Use a deferred action to reshow the dialog once the user has registered
|
||||||
|
if (MatrixClientPeg.get().isGuest()) {
|
||||||
|
// No point in making 2 DMs with welcome bot. This assumes view_set_mxid will
|
||||||
|
// result in a new DM with the welcome user.
|
||||||
|
if (userId !== this.props.config.welcomeUserId) {
|
||||||
|
dis.dispatch({
|
||||||
|
action: 'do_after_sync_prepared',
|
||||||
|
deferred_action: {
|
||||||
|
action: 'view_start_chat_or_reuse',
|
||||||
|
user_id: userId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
dis.dispatch({
|
||||||
|
action: 'view_set_mxid',
|
||||||
|
// If the set_mxid dialog is cancelled, view /home because if the browser
|
||||||
|
// was pointing at /user/@someone:domain?action=chat, the URL needs to be
|
||||||
|
// reset so that they can revisit /user/.. // (and trigger
|
||||||
|
// `_chatCreateOrReuse` again)
|
||||||
|
go_home_on_cancel: true,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const close = Modal.createDialog(ChatCreateOrReuseDialog, {
|
||||||
|
userId: userId,
|
||||||
|
onFinished: (success) => {
|
||||||
|
if (!success) {
|
||||||
|
// Dialog cancelled, default to home
|
||||||
|
dis.dispatch({ action: 'view_home_page' });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onNewDMClick: () => {
|
||||||
|
dis.dispatch({
|
||||||
|
action: 'start_chat',
|
||||||
|
user_id: userId,
|
||||||
|
});
|
||||||
|
// Close the dialog, indicate success (calls onFinished(true))
|
||||||
|
close(true);
|
||||||
|
},
|
||||||
|
onExistingRoomSelected: (roomId) => {
|
||||||
|
dis.dispatch({
|
||||||
|
action: 'view_room',
|
||||||
|
room_id: roomId,
|
||||||
|
});
|
||||||
|
close(true);
|
||||||
|
},
|
||||||
|
}).close;
|
||||||
|
},
|
||||||
|
|
||||||
_invite: function(roomId) {
|
_invite: function(roomId) {
|
||||||
const ChatInviteDialog = sdk.getComponent("dialogs.ChatInviteDialog");
|
const ChatInviteDialog = sdk.getComponent("dialogs.ChatInviteDialog");
|
||||||
Modal.createDialog(ChatInviteDialog, {
|
Modal.createDialog(ChatInviteDialog, {
|
||||||
|
@ -709,7 +841,7 @@ module.exports = React.createClass({
|
||||||
|
|
||||||
d.then(() => {
|
d.then(() => {
|
||||||
modal.close();
|
modal.close();
|
||||||
if (this.currentRoomId === roomId) {
|
if (this.state.currentRoomId === roomId) {
|
||||||
dis.dispatch({action: 'view_next_room'});
|
dis.dispatch({action: 'view_next_room'});
|
||||||
}
|
}
|
||||||
}, (err) => {
|
}, (err) => {
|
||||||
|
@ -732,14 +864,6 @@ module.exports = React.createClass({
|
||||||
_onLoadCompleted: function() {
|
_onLoadCompleted: function() {
|
||||||
this.props.onLoadCompleted();
|
this.props.onLoadCompleted();
|
||||||
this.setState({loading: false});
|
this.setState({loading: false});
|
||||||
|
|
||||||
// Show screens (like 'register') that need to be shown without _onLoggedIn
|
|
||||||
// being called. 'register' needs to be routed here when the email confirmation
|
|
||||||
// link is clicked on.
|
|
||||||
if (this.state.screenAfterLogin &&
|
|
||||||
['register'].indexOf(this.state.screenAfterLogin.screen) !== -1) {
|
|
||||||
this._showScreenAfterLogin();
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -804,12 +928,27 @@ module.exports = React.createClass({
|
||||||
this._teamToken = teamToken;
|
this._teamToken = teamToken;
|
||||||
dis.dispatch({action: 'view_home_page'});
|
dis.dispatch({action: 'view_home_page'});
|
||||||
} else if (this._is_registered) {
|
} else if (this._is_registered) {
|
||||||
|
this._is_registered = false;
|
||||||
|
// reset the 'have completed first sync' flag,
|
||||||
|
// since we've just logged in and will be about to sync
|
||||||
|
this.firstSyncComplete = false;
|
||||||
|
this.firstSyncPromise = q.defer();
|
||||||
|
|
||||||
|
// Set the display name = user ID localpart
|
||||||
|
MatrixClientPeg.get().setDisplayName(
|
||||||
|
MatrixClientPeg.get().getUserIdLocalpart(),
|
||||||
|
);
|
||||||
|
|
||||||
if (this.props.config.welcomeUserId && getCurrentLanguage().startsWith("en")) {
|
if (this.props.config.welcomeUserId && getCurrentLanguage().startsWith("en")) {
|
||||||
createRoom({dmUserId: this.props.config.welcomeUserId});
|
createRoom({
|
||||||
|
dmUserId: this.props.config.welcomeUserId,
|
||||||
|
// Only view the welcome user if we're NOT looking at a room
|
||||||
|
andView: !this.state.currentRoomId,
|
||||||
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// The user has just logged in after registering
|
// The user has just logged in after registering
|
||||||
dis.dispatch({action: 'view_room_directory'});
|
dis.dispatch({action: 'view_home_page'});
|
||||||
} else {
|
} else {
|
||||||
this._showScreenAfterLogin();
|
this._showScreenAfterLogin();
|
||||||
}
|
}
|
||||||
|
@ -823,6 +962,7 @@ module.exports = React.createClass({
|
||||||
this.state.screenAfterLogin.screen,
|
this.state.screenAfterLogin.screen,
|
||||||
this.state.screenAfterLogin.params,
|
this.state.screenAfterLogin.params,
|
||||||
);
|
);
|
||||||
|
// XXX: is this necessary? `showScreen` should do it for us.
|
||||||
this.notifyNewScreen(this.state.screenAfterLogin.screen);
|
this.notifyNewScreen(this.state.screenAfterLogin.screen);
|
||||||
this.setState({screenAfterLogin: null});
|
this.setState({screenAfterLogin: null});
|
||||||
} else if (localStorage && localStorage.getItem('mx_last_room_id')) {
|
} else if (localStorage && localStorage.getItem('mx_last_room_id')) {
|
||||||
|
@ -831,12 +971,8 @@ module.exports = React.createClass({
|
||||||
action: 'view_room',
|
action: 'view_room',
|
||||||
room_id: localStorage.getItem('mx_last_room_id'),
|
room_id: localStorage.getItem('mx_last_room_id'),
|
||||||
});
|
});
|
||||||
} else if (this._teamToken) {
|
|
||||||
// Team token might be set if we're a guest.
|
|
||||||
// Guests do not call _onLoggedIn with a teamToken
|
|
||||||
dis.dispatch({action: 'view_home_page'});
|
|
||||||
} else {
|
} else {
|
||||||
dis.dispatch({action: 'view_room_directory'});
|
dis.dispatch({action: 'view_home_page'});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -850,7 +986,6 @@ module.exports = React.createClass({
|
||||||
ready: false,
|
ready: false,
|
||||||
collapse_lhs: false,
|
collapse_lhs: false,
|
||||||
collapse_rhs: false,
|
collapse_rhs: false,
|
||||||
currentRoomAlias: null,
|
|
||||||
currentRoomId: null,
|
currentRoomId: null,
|
||||||
page_type: PageTypes.RoomDirectory,
|
page_type: PageTypes.RoomDirectory,
|
||||||
});
|
});
|
||||||
|
@ -888,6 +1023,12 @@ module.exports = React.createClass({
|
||||||
});
|
});
|
||||||
|
|
||||||
cli.on('sync', function(state, prevState) {
|
cli.on('sync', function(state, prevState) {
|
||||||
|
// LifecycleStore and others cannot directly subscribe to matrix client for
|
||||||
|
// events because flux only allows store state changes during flux dispatches.
|
||||||
|
// So dispatch directly from here. Ideally we'd use a SyncStateStore that
|
||||||
|
// would do this dispatch and expose the sync state itself (by listening to
|
||||||
|
// its own dispatch).
|
||||||
|
dis.dispatch({action: 'sync_state', prevState, state});
|
||||||
self.updateStatusIndicator(state, prevState);
|
self.updateStatusIndicator(state, prevState);
|
||||||
if (state === "SYNCING" && prevState === "SYNCING") {
|
if (state === "SYNCING" && prevState === "SYNCING") {
|
||||||
return;
|
return;
|
||||||
|
@ -957,6 +1098,11 @@ module.exports = React.createClass({
|
||||||
dis.dispatch({
|
dis.dispatch({
|
||||||
action: 'view_home_page',
|
action: 'view_home_page',
|
||||||
});
|
});
|
||||||
|
} else if (screen == 'start') {
|
||||||
|
this.showScreen('home');
|
||||||
|
dis.dispatch({
|
||||||
|
action: 'view_set_mxid',
|
||||||
|
});
|
||||||
} else if (screen == 'directory') {
|
} else if (screen == 'directory') {
|
||||||
dis.dispatch({
|
dis.dispatch({
|
||||||
action: 'view_room_directory',
|
action: 'view_room_directory',
|
||||||
|
@ -984,6 +1130,10 @@ module.exports = React.createClass({
|
||||||
const payload = {
|
const payload = {
|
||||||
action: 'view_room',
|
action: 'view_room',
|
||||||
event_id: eventId,
|
event_id: eventId,
|
||||||
|
// If an event ID is given in the URL hash, notify RoomViewStore to mark
|
||||||
|
// it as highlighted, which will propagate to RoomView and highlight the
|
||||||
|
// associated EventTile.
|
||||||
|
highlighted: Boolean(eventId),
|
||||||
third_party_invite: thirdPartyInvite,
|
third_party_invite: thirdPartyInvite,
|
||||||
oob_data: oobData,
|
oob_data: oobData,
|
||||||
};
|
};
|
||||||
|
@ -1000,6 +1150,12 @@ module.exports = React.createClass({
|
||||||
}
|
}
|
||||||
} else if (screen.indexOf('user/') == 0) {
|
} else if (screen.indexOf('user/') == 0) {
|
||||||
const userId = screen.substring(5);
|
const userId = screen.substring(5);
|
||||||
|
|
||||||
|
if (params.action === 'chat') {
|
||||||
|
this._chatCreateOrReuse(userId);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
this.setState({ viewUserId: userId });
|
this.setState({ viewUserId: userId });
|
||||||
this._setPage(PageTypes.UserView);
|
this._setPage(PageTypes.UserView);
|
||||||
this.notifyNewScreen('user/' + userId);
|
this.notifyNewScreen('user/' + userId);
|
||||||
|
@ -1104,12 +1260,16 @@ module.exports = React.createClass({
|
||||||
onReturnToGuestClick: function() {
|
onReturnToGuestClick: function() {
|
||||||
// reanimate our guest login
|
// reanimate our guest login
|
||||||
if (this.state.guestCreds) {
|
if (this.state.guestCreds) {
|
||||||
|
// TODO: this is probably a bit broken - we don't want to be
|
||||||
|
// clearing storage when we reanimate the guest creds.
|
||||||
Lifecycle.setLoggedIn(this.state.guestCreds);
|
Lifecycle.setLoggedIn(this.state.guestCreds);
|
||||||
this.setState({guestCreds: null});
|
this.setState({guestCreds: null});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
onRegistered: function(credentials, teamToken) {
|
onRegistered: function(credentials, teamToken) {
|
||||||
|
// XXX: These both should be in state or ideally store(s) because we risk not
|
||||||
|
// rendering the most up-to-date view of state otherwise.
|
||||||
// teamToken may not be truthy
|
// teamToken may not be truthy
|
||||||
this._teamToken = teamToken;
|
this._teamToken = teamToken;
|
||||||
this._is_registered = true;
|
this._is_registered = true;
|
||||||
|
@ -1153,7 +1313,15 @@ module.exports = React.createClass({
|
||||||
PlatformPeg.get().setNotificationCount(notifCount);
|
PlatformPeg.get().setNotificationCount(notifCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
document.title = `Riot ${state === "ERROR" ? " [offline]" : ""}${notifCount > 0 ? ` [${notifCount}]` : ""}`;
|
let title = "Riot ";
|
||||||
|
if (state === "ERROR") {
|
||||||
|
title += `[${_t("Offline")}] `;
|
||||||
|
}
|
||||||
|
if (notifCount > 0) {
|
||||||
|
title += `[${notifCount}]`;
|
||||||
|
}
|
||||||
|
|
||||||
|
document.title = title;
|
||||||
},
|
},
|
||||||
|
|
||||||
onUserSettingsClose: function() {
|
onUserSettingsClose: function() {
|
||||||
|
@ -1166,18 +1334,11 @@ module.exports = React.createClass({
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
dis.dispatch({
|
dis.dispatch({
|
||||||
action: 'view_room_directory',
|
action: 'view_home_page',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
onRoomIdResolved: function(roomId) {
|
|
||||||
// It's the RoomView's resposibility to look up room aliases, but we need the
|
|
||||||
// ID to pass into things like the Member List, so the Room View tells us when
|
|
||||||
// its done that resolution so we can display things that take a room ID.
|
|
||||||
this.setState({currentRoomId: roomId});
|
|
||||||
},
|
|
||||||
|
|
||||||
_makeRegistrationUrl: function(params) {
|
_makeRegistrationUrl: function(params) {
|
||||||
if (this.props.startingFragmentQueryParams.referrer) {
|
if (this.props.startingFragmentQueryParams.referrer) {
|
||||||
params.referrer = this.props.startingFragmentQueryParams.referrer;
|
params.referrer = this.props.startingFragmentQueryParams.referrer;
|
||||||
|
@ -1219,9 +1380,10 @@ module.exports = React.createClass({
|
||||||
const LoggedInView = sdk.getComponent('structures.LoggedInView');
|
const LoggedInView = sdk.getComponent('structures.LoggedInView');
|
||||||
return (
|
return (
|
||||||
<LoggedInView ref="loggedInView" matrixClient={MatrixClientPeg.get()}
|
<LoggedInView ref="loggedInView" matrixClient={MatrixClientPeg.get()}
|
||||||
onRoomIdResolved={this.onRoomIdResolved}
|
|
||||||
onRoomCreated={this.onRoomCreated}
|
onRoomCreated={this.onRoomCreated}
|
||||||
onUserSettingsClose={this.onUserSettingsClose}
|
onUserSettingsClose={this.onUserSettingsClose}
|
||||||
|
onRegistered={this.onRegistered}
|
||||||
|
currentRoomId={this.state.currentRoomId}
|
||||||
teamToken={this._teamToken}
|
teamToken={this._teamToken}
|
||||||
{...this.props}
|
{...this.props}
|
||||||
{...this.state}
|
{...this.state}
|
||||||
|
@ -1247,8 +1409,6 @@ module.exports = React.createClass({
|
||||||
idSid={this.state.register_id_sid}
|
idSid={this.state.register_id_sid}
|
||||||
email={this.props.startingFragmentQueryParams.email}
|
email={this.props.startingFragmentQueryParams.email}
|
||||||
referrer={this.props.startingFragmentQueryParams.referrer}
|
referrer={this.props.startingFragmentQueryParams.referrer}
|
||||||
username={this.state.upgradeUsername}
|
|
||||||
guestAccessToken={this.state.guestAccessToken}
|
|
||||||
defaultHsUrl={this.getDefaultHsUrl()}
|
defaultHsUrl={this.getDefaultHsUrl()}
|
||||||
defaultIsUrl={this.getDefaultIsUrl()}
|
defaultIsUrl={this.getDefaultIsUrl()}
|
||||||
brand={this.props.config.brand}
|
brand={this.props.config.brand}
|
||||||
|
|
|
@ -15,9 +15,8 @@ limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { _t } from '../../languageHandler';
|
import { _t, _tJsx } from '../../languageHandler';
|
||||||
import sdk from '../../index';
|
import sdk from '../../index';
|
||||||
import dis from '../../dispatcher';
|
|
||||||
import WhoIsTyping from '../../WhoIsTyping';
|
import WhoIsTyping from '../../WhoIsTyping';
|
||||||
import MatrixClientPeg from '../../MatrixClientPeg';
|
import MatrixClientPeg from '../../MatrixClientPeg';
|
||||||
import MemberAvatar from '../views/avatars/MemberAvatar';
|
import MemberAvatar from '../views/avatars/MemberAvatar';
|
||||||
|
@ -282,14 +281,13 @@ module.exports = React.createClass({
|
||||||
{ this.props.unsentMessageError }
|
{ this.props.unsentMessageError }
|
||||||
</div>
|
</div>
|
||||||
<div className="mx_RoomStatusBar_connectionLostBar_desc">
|
<div className="mx_RoomStatusBar_connectionLostBar_desc">
|
||||||
<a className="mx_RoomStatusBar_resend_link"
|
{_tJsx("<a>Resend all</a> or <a>cancel all</a> now. You can also select individual messages to resend or cancel.",
|
||||||
onClick={ this.props.onResendAllClick }>
|
[/<a>(.*?)<\/a>/, /<a>(.*?)<\/a>/],
|
||||||
{_t('Resend all')}
|
[
|
||||||
</a> {_t('or')} <a
|
(sub) => <a className="mx_RoomStatusBar_resend_link" key="resend" onClick={ this.props.onResendAllClick }>{sub}</a>,
|
||||||
className="mx_RoomStatusBar_resend_link"
|
(sub) => <a className="mx_RoomStatusBar_resend_link" key="cancel" onClick={ this.props.onCancelAllClick }>{sub}</a>,
|
||||||
onClick={ this.props.onCancelAllClick }>
|
]
|
||||||
{_t('cancel all')}
|
)}
|
||||||
</a> {_t('now. You can also select individual messages to resend or cancel.')}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -298,8 +296,8 @@ module.exports = React.createClass({
|
||||||
// unread count trumps who is typing since the unread count is only
|
// unread count trumps who is typing since the unread count is only
|
||||||
// set when you've scrolled up
|
// set when you've scrolled up
|
||||||
if (this.props.numUnreadMessages) {
|
if (this.props.numUnreadMessages) {
|
||||||
var unreadMsgs = this.props.numUnreadMessages + " new message" +
|
// MUST use var name "count" for pluralization to kick in
|
||||||
(this.props.numUnreadMessages > 1 ? "s" : "");
|
var unreadMsgs = _t("%(count)s new messages", {count: this.props.numUnreadMessages});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mx_RoomStatusBar_unreadMessagesBar"
|
<div className="mx_RoomStatusBar_unreadMessagesBar"
|
||||||
|
|
|
@ -45,6 +45,8 @@ import KeyCode from '../../KeyCode';
|
||||||
|
|
||||||
import UserProvider from '../../autocomplete/UserProvider';
|
import UserProvider from '../../autocomplete/UserProvider';
|
||||||
|
|
||||||
|
import RoomViewStore from '../../stores/RoomViewStore';
|
||||||
|
|
||||||
var DEBUG = false;
|
var DEBUG = false;
|
||||||
|
|
||||||
if (DEBUG) {
|
if (DEBUG) {
|
||||||
|
@ -59,16 +61,9 @@ module.exports = React.createClass({
|
||||||
propTypes: {
|
propTypes: {
|
||||||
ConferenceHandler: React.PropTypes.any,
|
ConferenceHandler: React.PropTypes.any,
|
||||||
|
|
||||||
// Either a room ID or room alias for the room to display.
|
// Called with the credentials of a registered user (if they were a ROU that
|
||||||
// If the room is being displayed as a result of the user clicking
|
// transitioned to PWLU)
|
||||||
// on a room alias, the alias should be supplied. Otherwise, a room
|
onRegistered: React.PropTypes.func,
|
||||||
// ID should be supplied.
|
|
||||||
roomAddress: React.PropTypes.string.isRequired,
|
|
||||||
|
|
||||||
// If a room alias is passed to roomAddress, a function can be
|
|
||||||
// provided here that will be called with the ID of the room
|
|
||||||
// once it has been resolved.
|
|
||||||
onRoomIdResolved: React.PropTypes.func,
|
|
||||||
|
|
||||||
// An object representing a third party invite to join this room
|
// An object representing a third party invite to join this room
|
||||||
// Fields:
|
// Fields:
|
||||||
|
@ -88,36 +83,8 @@ module.exports = React.createClass({
|
||||||
// * invited us tovthe room
|
// * invited us tovthe room
|
||||||
oobData: React.PropTypes.object,
|
oobData: React.PropTypes.object,
|
||||||
|
|
||||||
// id of an event to jump to. If not given, will go to the end of the
|
|
||||||
// live timeline.
|
|
||||||
eventId: React.PropTypes.string,
|
|
||||||
|
|
||||||
// where to position the event given by eventId, in pixels from the
|
|
||||||
// bottom of the viewport. If not given, will try to put the event
|
|
||||||
// 1/3 of the way down the viewport.
|
|
||||||
eventPixelOffset: React.PropTypes.number,
|
|
||||||
|
|
||||||
// ID of an event to highlight. If undefined, no event will be highlighted.
|
|
||||||
// Typically this will either be the same as 'eventId', or undefined.
|
|
||||||
highlightedEventId: React.PropTypes.string,
|
|
||||||
|
|
||||||
// is the RightPanel collapsed?
|
// is the RightPanel collapsed?
|
||||||
collapsedRhs: React.PropTypes.bool,
|
collapsedRhs: React.PropTypes.bool,
|
||||||
|
|
||||||
// a map from room id to scroll state, which will be updated on unmount.
|
|
||||||
//
|
|
||||||
// If there is no special scroll state (ie, we are following the live
|
|
||||||
// timeline), the scroll state is null. Otherwise, it is an object with
|
|
||||||
// the following properties:
|
|
||||||
//
|
|
||||||
// focussedEvent: the ID of the 'focussed' event. Typically this is
|
|
||||||
// the last event fully visible in the viewport, though if we
|
|
||||||
// have done an explicit scroll to an explicit event, it will be
|
|
||||||
// that event.
|
|
||||||
//
|
|
||||||
// pixelOffset: the number of pixels the window is scrolled down
|
|
||||||
// from the focussedEvent.
|
|
||||||
scrollStateMap: React.PropTypes.object,
|
|
||||||
},
|
},
|
||||||
|
|
||||||
getInitialState: function() {
|
getInitialState: function() {
|
||||||
|
@ -125,6 +92,14 @@ module.exports = React.createClass({
|
||||||
room: null,
|
room: null,
|
||||||
roomId: null,
|
roomId: null,
|
||||||
roomLoading: true,
|
roomLoading: true,
|
||||||
|
peekLoading: false,
|
||||||
|
|
||||||
|
// The event to be scrolled to initially
|
||||||
|
initialEventId: null,
|
||||||
|
// The offset in pixels from the event with which to scroll vertically
|
||||||
|
initialEventPixelOffset: null,
|
||||||
|
// Whether to highlight the event scrolled to
|
||||||
|
isInitialEventHighlighted: null,
|
||||||
|
|
||||||
forwardingEvent: null,
|
forwardingEvent: null,
|
||||||
editingRoomSettings: false,
|
editingRoomSettings: false,
|
||||||
|
@ -172,40 +147,63 @@ module.exports = React.createClass({
|
||||||
onClickCompletes: true,
|
onClickCompletes: true,
|
||||||
onStateChange: (isCompleting) => {
|
onStateChange: (isCompleting) => {
|
||||||
this.forceUpdate();
|
this.forceUpdate();
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
if (this.props.roomAddress[0] == '#') {
|
// Start listening for RoomViewStore updates
|
||||||
// we always look up the alias from the directory server:
|
this._roomStoreToken = RoomViewStore.addListener(this._onRoomViewStoreUpdate);
|
||||||
// we want the room that the given alias is pointing to
|
this._onRoomViewStoreUpdate(true);
|
||||||
// right now. We may have joined that alias before but there's
|
},
|
||||||
// no guarantee the alias hasn't subsequently been remapped.
|
|
||||||
MatrixClientPeg.get().getRoomIdForAlias(this.props.roomAddress).done((result) => {
|
_onRoomViewStoreUpdate: function(initial) {
|
||||||
if (this.props.onRoomIdResolved) {
|
if (this.unmounted) {
|
||||||
this.props.onRoomIdResolved(result.room_id);
|
return;
|
||||||
}
|
|
||||||
var room = MatrixClientPeg.get().getRoom(result.room_id);
|
|
||||||
this.setState({
|
|
||||||
room: room,
|
|
||||||
roomId: result.room_id,
|
|
||||||
roomLoading: !room,
|
|
||||||
unsentMessageError: this._getUnsentMessageError(room),
|
|
||||||
}, this._onHaveRoom);
|
|
||||||
}, (err) => {
|
|
||||||
this.setState({
|
|
||||||
roomLoading: false,
|
|
||||||
roomLoadError: err,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
var room = MatrixClientPeg.get().getRoom(this.props.roomAddress);
|
|
||||||
this.setState({
|
|
||||||
roomId: this.props.roomAddress,
|
|
||||||
room: room,
|
|
||||||
roomLoading: !room,
|
|
||||||
unsentMessageError: this._getUnsentMessageError(room),
|
|
||||||
}, this._onHaveRoom);
|
|
||||||
}
|
}
|
||||||
|
const newState = {
|
||||||
|
roomId: RoomViewStore.getRoomId(),
|
||||||
|
roomAlias: RoomViewStore.getRoomAlias(),
|
||||||
|
roomLoading: RoomViewStore.isRoomLoading(),
|
||||||
|
roomLoadError: RoomViewStore.getRoomLoadError(),
|
||||||
|
joining: RoomViewStore.isJoining(),
|
||||||
|
initialEventId: RoomViewStore.getInitialEventId(),
|
||||||
|
initialEventPixelOffset: RoomViewStore.getInitialEventPixelOffset(),
|
||||||
|
isInitialEventHighlighted: RoomViewStore.isInitialEventHighlighted(),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Temporary logging to diagnose https://github.com/vector-im/riot-web/issues/4307
|
||||||
|
console.log(
|
||||||
|
'RVS update:',
|
||||||
|
newState.roomId,
|
||||||
|
newState.roomAlias,
|
||||||
|
'loading?', newState.roomLoading,
|
||||||
|
'joining?', newState.joining,
|
||||||
|
);
|
||||||
|
|
||||||
|
// NB: This does assume that the roomID will not change for the lifetime of
|
||||||
|
// the RoomView instance
|
||||||
|
if (initial) {
|
||||||
|
newState.room = MatrixClientPeg.get().getRoom(newState.roomId);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear the search results when clicking a search result (which changes the
|
||||||
|
// currently scrolled to event, this.state.initialEventId).
|
||||||
|
if (this.state.initialEventId !== newState.initialEventId) {
|
||||||
|
newState.searchResults = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store the scroll state for the previous room so that we can return to this
|
||||||
|
// position when viewing this room in future.
|
||||||
|
if (this.state.roomId !== newState.roomId) {
|
||||||
|
this._updateScrollMap(this.state.roomId);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setState(newState, () => {
|
||||||
|
// At this point, this.state.roomId could be null (e.g. the alias might not
|
||||||
|
// have been resolved yet) so anything called here must handle this case.
|
||||||
|
if (initial) {
|
||||||
|
this._onHaveRoom();
|
||||||
|
}
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
_onHaveRoom: function() {
|
_onHaveRoom: function() {
|
||||||
|
@ -223,26 +221,28 @@ module.exports = React.createClass({
|
||||||
// NB. We peek if we are not in the room, although if we try to peek into
|
// NB. We peek if we are not in the room, although if we try to peek into
|
||||||
// a room in which we have a member event (ie. we've left) synapse will just
|
// a room in which we have a member event (ie. we've left) synapse will just
|
||||||
// send us the same data as we get in the sync (ie. the last events we saw).
|
// send us the same data as we get in the sync (ie. the last events we saw).
|
||||||
var user_is_in_room = null;
|
const room = this.state.room;
|
||||||
if (this.state.room) {
|
let isUserJoined = null;
|
||||||
user_is_in_room = this.state.room.hasMembershipState(
|
if (room) {
|
||||||
MatrixClientPeg.get().credentials.userId, 'join'
|
isUserJoined = room.hasMembershipState(
|
||||||
|
MatrixClientPeg.get().credentials.userId, 'join',
|
||||||
);
|
);
|
||||||
|
|
||||||
this._updateAutoComplete();
|
this._updateAutoComplete(room);
|
||||||
this.tabComplete.loadEntries(this.state.room);
|
this.tabComplete.loadEntries(room);
|
||||||
}
|
}
|
||||||
|
if (!isUserJoined && !this.state.joining && this.state.roomId) {
|
||||||
if (!user_is_in_room && this.state.roomId) {
|
|
||||||
if (this.props.autoJoin) {
|
if (this.props.autoJoin) {
|
||||||
this.onJoinButtonClicked();
|
this.onJoinButtonClicked();
|
||||||
} else if (this.state.roomId) {
|
} else if (this.state.roomId) {
|
||||||
console.log("Attempting to peek into room %s", this.state.roomId);
|
console.log("Attempting to peek into room %s", this.state.roomId);
|
||||||
|
this.setState({
|
||||||
|
peekLoading: true,
|
||||||
|
});
|
||||||
MatrixClientPeg.get().peekInRoom(this.state.roomId).then((room) => {
|
MatrixClientPeg.get().peekInRoom(this.state.roomId).then((room) => {
|
||||||
this.setState({
|
this.setState({
|
||||||
room: room,
|
room: room,
|
||||||
roomLoading: false,
|
peekLoading: false,
|
||||||
});
|
});
|
||||||
this._onRoomLoaded(room);
|
this._onRoomLoaded(room);
|
||||||
}, (err) => {
|
}, (err) => {
|
||||||
|
@ -252,16 +252,19 @@ module.exports = React.createClass({
|
||||||
if (err.errcode == "M_GUEST_ACCESS_FORBIDDEN") {
|
if (err.errcode == "M_GUEST_ACCESS_FORBIDDEN") {
|
||||||
// This is fine: the room just isn't peekable (we assume).
|
// This is fine: the room just isn't peekable (we assume).
|
||||||
this.setState({
|
this.setState({
|
||||||
roomLoading: false,
|
peekLoading: false,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
}).done();
|
}).done();
|
||||||
}
|
}
|
||||||
} else if (user_is_in_room) {
|
} else if (isUserJoined) {
|
||||||
MatrixClientPeg.get().stopPeeking();
|
MatrixClientPeg.get().stopPeeking();
|
||||||
this._onRoomLoaded(this.state.room);
|
this.setState({
|
||||||
|
unsentMessageError: this._getUnsentMessageError(room),
|
||||||
|
});
|
||||||
|
this._onRoomLoaded(room);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -297,17 +300,6 @@ module.exports = React.createClass({
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
componentWillReceiveProps: function(newProps) {
|
|
||||||
if (newProps.roomAddress != this.props.roomAddress) {
|
|
||||||
throw new Error(_t("changing room on a RoomView is not supported"));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (newProps.eventId != this.props.eventId) {
|
|
||||||
// when we change focussed event id, hide the search results.
|
|
||||||
this.setState({searchResults: null});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
shouldComponentUpdate: function(nextProps, nextState) {
|
shouldComponentUpdate: function(nextProps, nextState) {
|
||||||
return (!ObjectUtils.shallowEqual(this.props, nextProps) ||
|
return (!ObjectUtils.shallowEqual(this.props, nextProps) ||
|
||||||
!ObjectUtils.shallowEqual(this.state, nextState));
|
!ObjectUtils.shallowEqual(this.state, nextState));
|
||||||
|
@ -333,7 +325,7 @@ module.exports = React.createClass({
|
||||||
this.unmounted = true;
|
this.unmounted = true;
|
||||||
|
|
||||||
// update the scroll map before we get unmounted
|
// update the scroll map before we get unmounted
|
||||||
this._updateScrollMap();
|
this._updateScrollMap(this.state.roomId);
|
||||||
|
|
||||||
if (this.refs.roomView) {
|
if (this.refs.roomView) {
|
||||||
// disconnect the D&D event listeners from the room view. This
|
// disconnect the D&D event listeners from the room view. This
|
||||||
|
@ -362,6 +354,11 @@ module.exports = React.createClass({
|
||||||
|
|
||||||
document.removeEventListener("keydown", this.onKeyDown);
|
document.removeEventListener("keydown", this.onKeyDown);
|
||||||
|
|
||||||
|
// Remove RoomStore listener
|
||||||
|
if (this._roomStoreToken) {
|
||||||
|
this._roomStoreToken.remove();
|
||||||
|
}
|
||||||
|
|
||||||
// cancel any pending calls to the rate_limited_funcs
|
// cancel any pending calls to the rate_limited_funcs
|
||||||
this._updateRoomMembers.cancelPendingCall();
|
this._updateRoomMembers.cancelPendingCall();
|
||||||
|
|
||||||
|
@ -527,7 +524,7 @@ module.exports = React.createClass({
|
||||||
this._updatePreviewUrlVisibility(room);
|
this._updatePreviewUrlVisibility(room);
|
||||||
},
|
},
|
||||||
|
|
||||||
_warnAboutEncryption: function (room) {
|
_warnAboutEncryption: function(room) {
|
||||||
if (!MatrixClientPeg.get().isRoomEncrypted(room.roomId)) {
|
if (!MatrixClientPeg.get().isRoomEncrypted(room.roomId)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -607,21 +604,27 @@ module.exports = React.createClass({
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
onRoom: function(room) {
|
_updateScrollMap(roomId) {
|
||||||
// This event is fired when the room is 'stored' by the JS SDK, which
|
// No point updating scroll state if the room ID hasn't been resolved yet
|
||||||
// means it's now a fully-fledged room object ready to be used, so
|
if (!roomId) {
|
||||||
// set it in our state and start using it (ie. init the timeline)
|
return;
|
||||||
// This will happen if we start off viewing a room we're not joined,
|
|
||||||
// then join it whilst RoomView is looking at that room.
|
|
||||||
if (!this.state.room && room.roomId == this._joiningRoomId) {
|
|
||||||
this._joiningRoomId = undefined;
|
|
||||||
this.setState({
|
|
||||||
room: room,
|
|
||||||
joining: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
this._onRoomLoaded(room);
|
|
||||||
}
|
}
|
||||||
|
dis.dispatch({
|
||||||
|
action: 'update_scroll_state',
|
||||||
|
room_id: roomId,
|
||||||
|
scroll_state: this._getScrollState(),
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
onRoom: function(room) {
|
||||||
|
if (!room || room.roomId !== this.state.roomId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.setState({
|
||||||
|
room: room,
|
||||||
|
}, () => {
|
||||||
|
this._onRoomLoaded(room);
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
updateTint: function() {
|
updateTint: function() {
|
||||||
|
@ -687,7 +690,7 @@ module.exports = React.createClass({
|
||||||
|
|
||||||
// refresh the tab complete list
|
// refresh the tab complete list
|
||||||
this.tabComplete.loadEntries(this.state.room);
|
this.tabComplete.loadEntries(this.state.room);
|
||||||
this._updateAutoComplete();
|
this._updateAutoComplete(this.state.room);
|
||||||
|
|
||||||
// if we are now a member of the room, where we were not before, that
|
// if we are now a member of the room, where we were not before, that
|
||||||
// means we have finished joining a room we were previously peeking
|
// means we have finished joining a room we were previously peeking
|
||||||
|
@ -704,10 +707,6 @@ module.exports = React.createClass({
|
||||||
// compatability workaround, let's not bother.
|
// compatability workaround, let's not bother.
|
||||||
Rooms.setDMRoom(this.state.room.roomId, me.events.member.getSender()).done();
|
Rooms.setDMRoom(this.state.room.roomId, me.events.member.getSender()).done();
|
||||||
}
|
}
|
||||||
|
|
||||||
this.setState({
|
|
||||||
joining: false
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}, 500),
|
}, 500),
|
||||||
|
|
||||||
|
@ -716,7 +715,7 @@ module.exports = React.createClass({
|
||||||
if (!unsentMessages.length) return "";
|
if (!unsentMessages.length) return "";
|
||||||
for (const event of unsentMessages) {
|
for (const event of unsentMessages) {
|
||||||
if (!event.error || event.error.name !== "UnknownDeviceError") {
|
if (!event.error || event.error.name !== "UnknownDeviceError") {
|
||||||
return _t("Some of your messages have not been sent") + ".";
|
return _t("Some of your messages have not been sent.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return _t("Message not sent due to unknown devices being present");
|
return _t("Message not sent due to unknown devices being present");
|
||||||
|
@ -782,41 +781,62 @@ module.exports = React.createClass({
|
||||||
},
|
},
|
||||||
|
|
||||||
onJoinButtonClicked: function(ev) {
|
onJoinButtonClicked: function(ev) {
|
||||||
var self = this;
|
const cli = MatrixClientPeg.get();
|
||||||
|
|
||||||
var cli = MatrixClientPeg.get();
|
// If the user is a ROU, allow them to transition to a PWLU
|
||||||
var display_name_promise = q();
|
if (cli && cli.isGuest()) {
|
||||||
// if this is the first room we're joining, check the user has a display name
|
// Join this room once the user has registered and logged in
|
||||||
// and if they don't, prompt them to set one.
|
const signUrl = this.props.thirdPartyInvite ?
|
||||||
// NB. This unfortunately does not re-use the ChangeDisplayName component because
|
this.props.thirdPartyInvite.inviteSignUrl : undefined;
|
||||||
// it doesn't behave quite as desired here (we want an input field here rather than
|
dis.dispatch({
|
||||||
// content-editable, and we want a default).
|
action: 'do_after_sync_prepared',
|
||||||
if (cli.getRooms().filter((r) => {
|
deferred_action: {
|
||||||
return r.hasMembershipState(cli.credentials.userId, "join");
|
action: 'join_room',
|
||||||
})) {
|
opts: { inviteSignUrl: signUrl },
|
||||||
display_name_promise = cli.getProfileInfo(cli.credentials.userId).then((result) => {
|
},
|
||||||
if (!result.displayname) {
|
|
||||||
var SetDisplayNameDialog = sdk.getComponent('views.dialogs.SetDisplayNameDialog');
|
|
||||||
var dialog_defer = q.defer();
|
|
||||||
Modal.createDialog(SetDisplayNameDialog, {
|
|
||||||
currentDisplayName: result.displayname,
|
|
||||||
onFinished: (submitted, newDisplayName) => {
|
|
||||||
if (submitted) {
|
|
||||||
cli.setDisplayName(newDisplayName).done(() => {
|
|
||||||
dialog_defer.resolve();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
dialog_defer.reject();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return dialog_defer.promise;
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Don't peek whilst registering otherwise getPendingEventList complains
|
||||||
|
// Do this by indicating our intention to join
|
||||||
|
dis.dispatch({
|
||||||
|
action: 'will_join',
|
||||||
|
});
|
||||||
|
|
||||||
|
const SetMxIdDialog = sdk.getComponent('views.dialogs.SetMxIdDialog');
|
||||||
|
const close = Modal.createDialog(SetMxIdDialog, {
|
||||||
|
homeserverUrl: cli.getHomeserverUrl(),
|
||||||
|
onFinished: (submitted, credentials) => {
|
||||||
|
if (submitted) {
|
||||||
|
this.props.onRegistered(credentials);
|
||||||
|
} else {
|
||||||
|
dis.dispatch({
|
||||||
|
action: 'cancel_after_sync_prepared',
|
||||||
|
});
|
||||||
|
dis.dispatch({
|
||||||
|
action: 'cancel_join',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onDifferentServerClicked: (ev) => {
|
||||||
|
dis.dispatch({action: 'start_registration'});
|
||||||
|
close();
|
||||||
|
},
|
||||||
|
onLoginClick: (ev) => {
|
||||||
|
dis.dispatch({action: 'start_login'});
|
||||||
|
close();
|
||||||
|
},
|
||||||
|
}).close;
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
display_name_promise.then(() => {
|
q().then(() => {
|
||||||
|
const signUrl = this.props.thirdPartyInvite ?
|
||||||
|
this.props.thirdPartyInvite.inviteSignUrl : undefined;
|
||||||
|
dis.dispatch({
|
||||||
|
action: 'join_room',
|
||||||
|
opts: { inviteSignUrl: signUrl },
|
||||||
|
});
|
||||||
|
|
||||||
// if this is an invite and has the 'direct' hint set, mark it as a DM room now.
|
// if this is an invite and has the 'direct' hint set, mark it as a DM room now.
|
||||||
if (this.state.room) {
|
if (this.state.room) {
|
||||||
const me = this.state.room.getMember(MatrixClientPeg.get().credentials.userId);
|
const me = this.state.room.getMember(MatrixClientPeg.get().credentials.userId);
|
||||||
|
@ -828,72 +848,7 @@ module.exports = React.createClass({
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return q();
|
return q();
|
||||||
}).then(() => {
|
|
||||||
var sign_url = this.props.thirdPartyInvite ? this.props.thirdPartyInvite.inviteSignUrl : undefined;
|
|
||||||
return MatrixClientPeg.get().joinRoom(this.props.roomAddress,
|
|
||||||
{ inviteSignUrl: sign_url } );
|
|
||||||
}).then(function(resp) {
|
|
||||||
var roomId = resp.roomId;
|
|
||||||
|
|
||||||
// It is possible that there is no Room yet if state hasn't come down
|
|
||||||
// from /sync - joinRoom will resolve when the HTTP request to join succeeds,
|
|
||||||
// NOT when it comes down /sync. If there is no room, we'll keep the
|
|
||||||
// joining flag set until we see it.
|
|
||||||
|
|
||||||
// We'll need to initialise the timeline when joining, but due to
|
|
||||||
// the above, we can't do it here: we do it in onRoom instead,
|
|
||||||
// once we have a useable room object.
|
|
||||||
var room = MatrixClientPeg.get().getRoom(roomId);
|
|
||||||
if (!room) {
|
|
||||||
// wait for the room to turn up in onRoom.
|
|
||||||
self._joiningRoomId = roomId;
|
|
||||||
} else {
|
|
||||||
// we've got a valid room, but that might also just mean that
|
|
||||||
// it was peekable (so we had one before anyway). If we are
|
|
||||||
// not yet a member of the room, we will need to wait for that
|
|
||||||
// to happen, in onRoomStateMember.
|
|
||||||
var me = MatrixClientPeg.get().credentials.userId;
|
|
||||||
self.setState({
|
|
||||||
joining: !room.hasMembershipState(me, "join"),
|
|
||||||
room: room
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}).catch(function(error) {
|
|
||||||
self.setState({
|
|
||||||
joining: false,
|
|
||||||
joinError: error
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!error) return;
|
|
||||||
|
|
||||||
// https://matrix.org/jira/browse/SYN-659
|
|
||||||
// Need specific error message if joining a room is refused because the user is a guest and guest access is not allowed
|
|
||||||
if (
|
|
||||||
error.errcode == 'M_GUEST_ACCESS_FORBIDDEN' ||
|
|
||||||
(
|
|
||||||
error.errcode == 'M_FORBIDDEN' &&
|
|
||||||
MatrixClientPeg.get().isGuest()
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
var NeedToRegisterDialog = sdk.getComponent("dialogs.NeedToRegisterDialog");
|
|
||||||
Modal.createDialog(NeedToRegisterDialog, {
|
|
||||||
title: _t("Failed to join the room"),
|
|
||||||
description: _t("This room is private or inaccessible to guests. You may be able to join if you register") + "."
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
var msg = error.message ? error.message : JSON.stringify(error);
|
|
||||||
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
|
||||||
Modal.createDialog(ErrorDialog, {
|
|
||||||
title: _t("Failed to join room"),
|
|
||||||
description: msg,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}).done();
|
|
||||||
|
|
||||||
this.setState({
|
|
||||||
joining: true
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -945,11 +900,7 @@ module.exports = React.createClass({
|
||||||
|
|
||||||
uploadFile: function(file) {
|
uploadFile: function(file) {
|
||||||
if (MatrixClientPeg.get().isGuest()) {
|
if (MatrixClientPeg.get().isGuest()) {
|
||||||
var NeedToRegisterDialog = sdk.getComponent("dialogs.NeedToRegisterDialog");
|
dis.dispatch({action: 'view_set_mxid'});
|
||||||
Modal.createDialog(NeedToRegisterDialog, {
|
|
||||||
title: _t("Please Register"),
|
|
||||||
description: _t("Guest users can't upload files. Please register to upload") + "."
|
|
||||||
});
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1307,21 +1258,6 @@ module.exports = React.createClass({
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
// update scrollStateMap on unmount
|
|
||||||
_updateScrollMap: function() {
|
|
||||||
if (!this.state.room) {
|
|
||||||
// we were instantiated on a room alias and haven't yet joined the room.
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!this.props.scrollStateMap) return;
|
|
||||||
|
|
||||||
var roomId = this.state.room.roomId;
|
|
||||||
|
|
||||||
var state = this._getScrollState();
|
|
||||||
this.props.scrollStateMap[roomId] = state;
|
|
||||||
},
|
|
||||||
|
|
||||||
|
|
||||||
// get the current scroll position of the room, so that it can be
|
// get the current scroll position of the room, so that it can be
|
||||||
// restored when we switch back to it.
|
// restored when we switch back to it.
|
||||||
//
|
//
|
||||||
|
@ -1474,9 +1410,9 @@ module.exports = React.createClass({
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
_updateAutoComplete: function() {
|
_updateAutoComplete: function(room) {
|
||||||
const myUserId = MatrixClientPeg.get().credentials.userId;
|
const myUserId = MatrixClientPeg.get().credentials.userId;
|
||||||
const members = this.state.room.getJoinedMembers().filter(function(member) {
|
const members = room.getJoinedMembers().filter(function(member) {
|
||||||
if (member.userId !== myUserId) return true;
|
if (member.userId !== myUserId) return true;
|
||||||
});
|
});
|
||||||
UserProvider.getInstance().setUserList(members);
|
UserProvider.getInstance().setUserList(members);
|
||||||
|
@ -1496,7 +1432,7 @@ module.exports = React.createClass({
|
||||||
const TimelinePanel = sdk.getComponent("structures.TimelinePanel");
|
const TimelinePanel = sdk.getComponent("structures.TimelinePanel");
|
||||||
|
|
||||||
if (!this.state.room) {
|
if (!this.state.room) {
|
||||||
if (this.state.roomLoading) {
|
if (this.state.roomLoading || this.state.peekLoading) {
|
||||||
return (
|
return (
|
||||||
<div className="mx_RoomView">
|
<div className="mx_RoomView">
|
||||||
<Loader />
|
<Loader />
|
||||||
|
@ -1514,7 +1450,7 @@ module.exports = React.createClass({
|
||||||
|
|
||||||
// We have no room object for this room, only the ID.
|
// We have no room object for this room, only the ID.
|
||||||
// We've got to this room by following a link, possibly a third party invite.
|
// We've got to this room by following a link, possibly a third party invite.
|
||||||
var room_alias = this.props.roomAddress[0] == '#' ? this.props.roomAddress : null;
|
var room_alias = this.state.room_alias;
|
||||||
return (
|
return (
|
||||||
<div className="mx_RoomView">
|
<div className="mx_RoomView">
|
||||||
<RoomHeader ref="header"
|
<RoomHeader ref="header"
|
||||||
|
@ -1744,6 +1680,14 @@ module.exports = React.createClass({
|
||||||
hideMessagePanel = true;
|
hideMessagePanel = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const shouldHighlight = this.state.isInitialEventHighlighted;
|
||||||
|
let highlightedEventId = null;
|
||||||
|
if (this.state.forwardingEvent) {
|
||||||
|
highlightedEventId = this.state.forwardingEvent.getId();
|
||||||
|
} else if (shouldHighlight) {
|
||||||
|
highlightedEventId = this.state.initialEventId;
|
||||||
|
}
|
||||||
|
|
||||||
// console.log("ShowUrlPreview for %s is %s", this.state.room.roomId, this.state.showUrlPreview);
|
// console.log("ShowUrlPreview for %s is %s", this.state.room.roomId, this.state.showUrlPreview);
|
||||||
var messagePanel = (
|
var messagePanel = (
|
||||||
<TimelinePanel ref={this._gatherTimelinePanelRef}
|
<TimelinePanel ref={this._gatherTimelinePanelRef}
|
||||||
|
@ -1751,9 +1695,9 @@ module.exports = React.createClass({
|
||||||
manageReadReceipts={!UserSettingsStore.getSyncedSetting('hideReadReceipts', false)}
|
manageReadReceipts={!UserSettingsStore.getSyncedSetting('hideReadReceipts', false)}
|
||||||
manageReadMarkers={true}
|
manageReadMarkers={true}
|
||||||
hidden={hideMessagePanel}
|
hidden={hideMessagePanel}
|
||||||
highlightedEventId={this.state.forwardingEvent ? this.state.forwardingEvent.getId() : this.props.highlightedEventId}
|
highlightedEventId={highlightedEventId}
|
||||||
eventId={this.props.eventId}
|
eventId={this.state.initialEventId}
|
||||||
eventPixelOffset={this.props.eventPixelOffset}
|
eventPixelOffset={this.state.initialEventPixelOffset}
|
||||||
onScroll={ this.onMessageListScroll }
|
onScroll={ this.onMessageListScroll }
|
||||||
onReadMarkerUpdated={ this._updateTopUnreadMessagesBar }
|
onReadMarkerUpdated={ this._updateTopUnreadMessagesBar }
|
||||||
showUrlPreview = { this.state.showUrlPreview }
|
showUrlPreview = { this.state.showUrlPreview }
|
||||||
|
|
|
@ -352,13 +352,14 @@ module.exports = React.createClass({
|
||||||
const tile = tiles[backwards ? i : tiles.length - 1 - i];
|
const tile = tiles[backwards ? i : tiles.length - 1 - i];
|
||||||
// Subtract height of tile as if it were unpaginated
|
// Subtract height of tile as if it were unpaginated
|
||||||
excessHeight -= tile.clientHeight;
|
excessHeight -= tile.clientHeight;
|
||||||
|
//If removing the tile would lead to future pagination, break before setting scroll token
|
||||||
|
if (tile.clientHeight > excessHeight) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
// The tile may not have a scroll token, so guard it
|
// The tile may not have a scroll token, so guard it
|
||||||
if (tile.dataset.scrollTokens) {
|
if (tile.dataset.scrollTokens) {
|
||||||
markerScrollToken = tile.dataset.scrollTokens.split(',')[0];
|
markerScrollToken = tile.dataset.scrollTokens.split(',')[0];
|
||||||
}
|
}
|
||||||
if (tile.clientHeight > excessHeight) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (markerScrollToken) {
|
if (markerScrollToken) {
|
||||||
|
|
|
@ -902,6 +902,9 @@ var TimelinePanel = React.createClass({
|
||||||
|
|
||||||
var onError = (error) => {
|
var onError = (error) => {
|
||||||
this.setState({timelineLoading: false});
|
this.setState({timelineLoading: false});
|
||||||
|
console.error(
|
||||||
|
`Error loading timeline panel at ${eventId}: ${error}`,
|
||||||
|
);
|
||||||
var msg = error.message ? error.message : JSON.stringify(error);
|
var msg = error.message ? error.message : JSON.stringify(error);
|
||||||
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||||
|
|
||||||
|
@ -921,8 +924,8 @@ var TimelinePanel = React.createClass({
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
var message = (error.errcode == 'M_FORBIDDEN')
|
var message = (error.errcode == 'M_FORBIDDEN')
|
||||||
? _t("Tried to load a specific point in this room's timeline, but you do not have permission to view the message in question") + "."
|
? _t("Tried to load a specific point in this room's timeline, but you do not have permission to view the message in question.")
|
||||||
: _t("Tried to load a specific point in this room's timeline, but was unable to find it") + ".";
|
: _t("Tried to load a specific point in this room's timeline, but was unable to find it.");
|
||||||
Modal.createDialog(ErrorDialog, {
|
Modal.createDialog(ErrorDialog, {
|
||||||
title: _t("Failed to load timeline position"),
|
title: _t("Failed to load timeline position"),
|
||||||
description: message,
|
description: message,
|
||||||
|
|
|
@ -18,6 +18,7 @@ var React = require('react');
|
||||||
var ContentMessages = require('../../ContentMessages');
|
var ContentMessages = require('../../ContentMessages');
|
||||||
var dis = require('../../dispatcher');
|
var dis = require('../../dispatcher');
|
||||||
var filesize = require('filesize');
|
var filesize = require('filesize');
|
||||||
|
import { _t } from '../../languageHandler';
|
||||||
|
|
||||||
module.exports = React.createClass({displayName: 'UploadBar',
|
module.exports = React.createClass({displayName: 'UploadBar',
|
||||||
propTypes: {
|
propTypes: {
|
||||||
|
@ -81,10 +82,8 @@ module.exports = React.createClass({displayName: 'UploadBar',
|
||||||
uploadedSize = uploadedSize.replace(/ .*/, '');
|
uploadedSize = uploadedSize.replace(/ .*/, '');
|
||||||
}
|
}
|
||||||
|
|
||||||
var others;
|
// MUST use var name 'count' for pluralization to kick in
|
||||||
if (uploads.length > 1) {
|
var uploadText = _t("Uploading %(filename)s and %(count)s others", {filename: upload.fileName, count: (uploads.length - 1)});
|
||||||
others = ' and ' + (uploads.length - 1) + ' other' + (uploads.length > 2 ? 's' : '');
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mx_UploadBar">
|
<div className="mx_UploadBar">
|
||||||
|
@ -98,7 +97,7 @@ module.exports = React.createClass({displayName: 'UploadBar',
|
||||||
<div className="mx_UploadBar_uploadBytes">
|
<div className="mx_UploadBar_uploadBytes">
|
||||||
{ uploadedSize } / { totalSize }
|
{ uploadedSize } / { totalSize }
|
||||||
</div>
|
</div>
|
||||||
<div className="mx_UploadBar_uploadFilename">Uploading {upload.fileName}{others}</div>
|
<div className="mx_UploadBar_uploadFilename">{uploadText}</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -88,6 +88,10 @@ const SETTINGS_LABELS = [
|
||||||
id: 'hideRedactions',
|
id: 'hideRedactions',
|
||||||
label: 'Hide removed messages',
|
label: 'Hide removed messages',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: 'disableMarkdown',
|
||||||
|
label: 'Disable markdown formatting',
|
||||||
|
},
|
||||||
/*
|
/*
|
||||||
{
|
{
|
||||||
id: 'useFixedWidthFont',
|
id: 'useFixedWidthFont',
|
||||||
|
@ -106,6 +110,13 @@ const ANALYTICS_SETTINGS_LABELS = [
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const WEBRTC_SETTINGS_LABELS = [
|
||||||
|
{
|
||||||
|
id: 'webRtcForceTURN',
|
||||||
|
label: 'Disable Peer-to-Peer for 1:1 calls',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
// Warning: Each "label" string below must be added to i18n/strings/en_EN.json,
|
// Warning: Each "label" string below must be added to i18n/strings/en_EN.json,
|
||||||
// since they will be translated when rendered.
|
// since they will be translated when rendered.
|
||||||
const CRYPTO_SETTINGS_LABELS = [
|
const CRYPTO_SETTINGS_LABELS = [
|
||||||
|
@ -306,15 +317,6 @@ module.exports = React.createClass({
|
||||||
},
|
},
|
||||||
|
|
||||||
onAvatarPickerClick: function(ev) {
|
onAvatarPickerClick: function(ev) {
|
||||||
if (MatrixClientPeg.get().isGuest()) {
|
|
||||||
const NeedToRegisterDialog = sdk.getComponent("dialogs.NeedToRegisterDialog");
|
|
||||||
Modal.createDialog(NeedToRegisterDialog, {
|
|
||||||
title: _t("Please Register"),
|
|
||||||
description: _t("Guests can't set avatars. Please register."),
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.refs.file_label) {
|
if (this.refs.file_label) {
|
||||||
this.refs.file_label.click();
|
this.refs.file_label.click();
|
||||||
}
|
}
|
||||||
|
@ -389,14 +391,12 @@ module.exports = React.createClass({
|
||||||
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||||
Modal.createDialog(ErrorDialog, {
|
Modal.createDialog(ErrorDialog, {
|
||||||
title: _t("Success"),
|
title: _t("Success"),
|
||||||
description: _t("Your password was successfully changed. You will not receive push notifications on other devices until you log back in to them") + ".",
|
description: _t(
|
||||||
});
|
"Your password was successfully changed. You will not receive " +
|
||||||
},
|
"push notifications on other devices until you log back in to them",
|
||||||
|
) + ".",
|
||||||
onUpgradeClicked: function() {
|
|
||||||
dis.dispatch({
|
|
||||||
action: "start_upgrade_registration",
|
|
||||||
});
|
});
|
||||||
|
dis.dispatch({action: 'password_changed'});
|
||||||
},
|
},
|
||||||
|
|
||||||
onEnableNotificationsChange: function(event) {
|
onEnableNotificationsChange: function(event) {
|
||||||
|
@ -426,7 +426,10 @@ module.exports = React.createClass({
|
||||||
this._addThreepid.addEmailAddress(emailAddress, true).done(() => {
|
this._addThreepid.addEmailAddress(emailAddress, true).done(() => {
|
||||||
Modal.createDialog(QuestionDialog, {
|
Modal.createDialog(QuestionDialog, {
|
||||||
title: _t("Verification Pending"),
|
title: _t("Verification Pending"),
|
||||||
description: _t("Please check your email and click on the link it contains. Once this is done, click continue."),
|
description: _t(
|
||||||
|
"Please check your email and click on the link it contains. Once this " +
|
||||||
|
"is done, click continue.",
|
||||||
|
),
|
||||||
button: _t('Continue'),
|
button: _t('Continue'),
|
||||||
onFinished: this.onEmailDialogFinished,
|
onFinished: this.onEmailDialogFinished,
|
||||||
});
|
});
|
||||||
|
@ -446,7 +449,7 @@ module.exports = React.createClass({
|
||||||
const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
|
const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
|
||||||
Modal.createDialog(QuestionDialog, {
|
Modal.createDialog(QuestionDialog, {
|
||||||
title: _t("Remove Contact Information?"),
|
title: _t("Remove Contact Information?"),
|
||||||
description: _t("Remove %(threePid)s?", { threePid : threepid.address }),
|
description: _t("Remove %(threePid)s?", { threePid: threepid.address }),
|
||||||
button: _t('Remove'),
|
button: _t('Remove'),
|
||||||
onFinished: (submit) => {
|
onFinished: (submit) => {
|
||||||
if (submit) {
|
if (submit) {
|
||||||
|
@ -488,8 +491,8 @@ module.exports = React.createClass({
|
||||||
this.setState({email_add_pending: false});
|
this.setState({email_add_pending: false});
|
||||||
if (err.errcode == 'M_THREEPID_AUTH_FAILED') {
|
if (err.errcode == 'M_THREEPID_AUTH_FAILED') {
|
||||||
const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
|
const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
|
||||||
let message = _t("Unable to verify email address.") + " " +
|
const message = _t("Unable to verify email address.") + " " +
|
||||||
_t("Please check your email and click on the link it contains. Once this is done, click continue.");
|
_t("Please check your email and click on the link it contains. Once this is done, click continue.");
|
||||||
Modal.createDialog(QuestionDialog, {
|
Modal.createDialog(QuestionDialog, {
|
||||||
title: _t("Verification Pending"),
|
title: _t("Verification Pending"),
|
||||||
description: message,
|
description: message,
|
||||||
|
@ -607,7 +610,7 @@ module.exports = React.createClass({
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
_renderLanguageSetting: function () {
|
_renderLanguageSetting: function() {
|
||||||
const LanguageDropdown = sdk.getComponent('views.elements.LanguageDropdown');
|
const LanguageDropdown = sdk.getComponent('views.elements.LanguageDropdown');
|
||||||
return <div>
|
return <div>
|
||||||
<label htmlFor="languageSelector">{_t('Interface Language')}</label>
|
<label htmlFor="languageSelector">{_t('Interface Language')}</label>
|
||||||
|
@ -638,7 +641,7 @@ module.exports = React.createClass({
|
||||||
<input id="urlPreviewsDisabled"
|
<input id="urlPreviewsDisabled"
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
defaultChecked={ UserSettingsStore.getUrlPreviewsDisabled() }
|
defaultChecked={ UserSettingsStore.getUrlPreviewsDisabled() }
|
||||||
onChange={ (e) => UserSettingsStore.setUrlPreviewsDisabled(e.target.checked) }
|
onChange={ this._onPreviewsDisabledChanged }
|
||||||
/>
|
/>
|
||||||
<label htmlFor="urlPreviewsDisabled">
|
<label htmlFor="urlPreviewsDisabled">
|
||||||
{ _t("Disable inline URL previews by default") }
|
{ _t("Disable inline URL previews by default") }
|
||||||
|
@ -646,17 +649,24 @@ module.exports = React.createClass({
|
||||||
</div>;
|
</div>;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
_onPreviewsDisabledChanged: function(e) {
|
||||||
|
UserSettingsStore.setUrlPreviewsDisabled(e.target.checked);
|
||||||
|
},
|
||||||
|
|
||||||
_renderSyncedSetting: function(setting) {
|
_renderSyncedSetting: function(setting) {
|
||||||
|
// TODO: this ought to be a separate component so that we don't need
|
||||||
|
// to rebind the onChange each time we render
|
||||||
|
|
||||||
|
const onChange = (e) => {
|
||||||
|
UserSettingsStore.setSyncedSetting(setting.id, e.target.checked);
|
||||||
|
if (setting.fn) setting.fn(e.target.checked);
|
||||||
|
};
|
||||||
|
|
||||||
return <div className="mx_UserSettings_toggle" key={ setting.id }>
|
return <div className="mx_UserSettings_toggle" key={ setting.id }>
|
||||||
<input id={ setting.id }
|
<input id={ setting.id }
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
defaultChecked={ this._syncedSettings[setting.id] }
|
defaultChecked={ this._syncedSettings[setting.id] }
|
||||||
onChange={
|
onChange={ onChange }
|
||||||
(e) => {
|
|
||||||
UserSettingsStore.setSyncedSetting(setting.id, e.target.checked);
|
|
||||||
if (setting.fn) setting.fn(e.target.checked);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
<label htmlFor={ setting.id }>
|
<label htmlFor={ setting.id }>
|
||||||
{ _t(setting.label) }
|
{ _t(setting.label) }
|
||||||
|
@ -665,22 +675,24 @@ module.exports = React.createClass({
|
||||||
},
|
},
|
||||||
|
|
||||||
_renderThemeSelector: function(setting) {
|
_renderThemeSelector: function(setting) {
|
||||||
|
// TODO: this ought to be a separate component so that we don't need
|
||||||
|
// to rebind the onChange each time we render
|
||||||
|
const onChange = (e) => {
|
||||||
|
if (e.target.checked) {
|
||||||
|
UserSettingsStore.setSyncedSetting(setting.id, setting.value);
|
||||||
|
}
|
||||||
|
dis.dispatch({
|
||||||
|
action: 'set_theme',
|
||||||
|
value: setting.value,
|
||||||
|
});
|
||||||
|
};
|
||||||
return <div className="mx_UserSettings_toggle" key={ setting.id + "_" + setting.value }>
|
return <div className="mx_UserSettings_toggle" key={ setting.id + "_" + setting.value }>
|
||||||
<input id={ setting.id + "_" + setting.value }
|
<input id={ setting.id + "_" + setting.value }
|
||||||
type="radio"
|
type="radio"
|
||||||
name={ setting.id }
|
name={ setting.id }
|
||||||
value={ setting.value }
|
value={ setting.value }
|
||||||
defaultChecked={ this._syncedSettings[setting.id] === setting.value }
|
defaultChecked={ this._syncedSettings[setting.id] === setting.value }
|
||||||
onChange={ (e) => {
|
onChange={ onChange }
|
||||||
if (e.target.checked) {
|
|
||||||
UserSettingsStore.setSyncedSetting(setting.id, setting.value);
|
|
||||||
}
|
|
||||||
dis.dispatch({
|
|
||||||
action: 'set_theme',
|
|
||||||
value: setting.value,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
<label htmlFor={ setting.id + "_" + setting.value }>
|
<label htmlFor={ setting.id + "_" + setting.value }>
|
||||||
{ setting.label }
|
{ setting.label }
|
||||||
|
@ -719,8 +731,10 @@ module.exports = React.createClass({
|
||||||
<h3>{ _t("Cryptography") }</h3>
|
<h3>{ _t("Cryptography") }</h3>
|
||||||
<div className="mx_UserSettings_section mx_UserSettings_cryptoSection">
|
<div className="mx_UserSettings_section mx_UserSettings_cryptoSection">
|
||||||
<ul>
|
<ul>
|
||||||
<li><label>{_t("Device ID:")}</label> <span><code>{deviceId}</code></span></li>
|
<li><label>{_t("Device ID:")}</label>
|
||||||
<li><label>{_t("Device key:")}</label> <span><code><b>{identityKey}</b></code></span></li>
|
<span><code>{deviceId}</code></span></li>
|
||||||
|
<li><label>{_t("Device key:")}</label>
|
||||||
|
<span><code><b>{identityKey}</b></code></span></li>
|
||||||
</ul>
|
</ul>
|
||||||
{ importExportButtons }
|
{ importExportButtons }
|
||||||
</div>
|
</div>
|
||||||
|
@ -732,16 +746,18 @@ module.exports = React.createClass({
|
||||||
},
|
},
|
||||||
|
|
||||||
_renderLocalSetting: function(setting) {
|
_renderLocalSetting: function(setting) {
|
||||||
|
// TODO: this ought to be a separate component so that we don't need
|
||||||
|
// to rebind the onChange each time we render
|
||||||
|
const onChange = (e) => {
|
||||||
|
UserSettingsStore.setLocalSetting(setting.id, e.target.checked);
|
||||||
|
if (setting.fn) setting.fn(e.target.checked);
|
||||||
|
};
|
||||||
|
|
||||||
return <div className="mx_UserSettings_toggle" key={ setting.id }>
|
return <div className="mx_UserSettings_toggle" key={ setting.id }>
|
||||||
<input id={ setting.id }
|
<input id={ setting.id }
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
defaultChecked={ this._localSettings[setting.id] }
|
defaultChecked={ this._localSettings[setting.id] }
|
||||||
onChange={
|
onChange={ onChange }
|
||||||
(e) => {
|
|
||||||
UserSettingsStore.setLocalSetting(setting.id, e.target.checked);
|
|
||||||
if (setting.fn) setting.fn(e.target.checked);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
<label htmlFor={ setting.id }>
|
<label htmlFor={ setting.id }>
|
||||||
{ _t(setting.label) }
|
{ _t(setting.label) }
|
||||||
|
@ -753,7 +769,7 @@ module.exports = React.createClass({
|
||||||
const DevicesPanel = sdk.getComponent('settings.DevicesPanel');
|
const DevicesPanel = sdk.getComponent('settings.DevicesPanel');
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<h3>Devices</h3>
|
<h3>{_t("Devices")}</h3>
|
||||||
<DevicesPanel className="mx_UserSettings_section"/>
|
<DevicesPanel className="mx_UserSettings_section"/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -793,30 +809,27 @@ module.exports = React.createClass({
|
||||||
if (this.props.enableLabs === false) return null;
|
if (this.props.enableLabs === false) return null;
|
||||||
UserSettingsStore.doTranslations();
|
UserSettingsStore.doTranslations();
|
||||||
|
|
||||||
const features = UserSettingsStore.LABS_FEATURES.map((feature) => (
|
const features = UserSettingsStore.LABS_FEATURES.map((feature) => {
|
||||||
<div key={feature.id} className="mx_UserSettings_toggle">
|
// TODO: this ought to be a separate component so that we don't need
|
||||||
<input
|
// to rebind the onChange each time we render
|
||||||
type="checkbox"
|
const onChange = (e) => {
|
||||||
id={feature.id}
|
UserSettingsStore.setFeatureEnabled(feature.id, e.target.checked);
|
||||||
name={feature.id}
|
this.forceUpdate();
|
||||||
defaultChecked={ UserSettingsStore.isFeatureEnabled(feature.id) }
|
};
|
||||||
onChange={(e) => {
|
|
||||||
if (MatrixClientPeg.get().isGuest()) {
|
|
||||||
e.target.checked = false;
|
|
||||||
const NeedToRegisterDialog = sdk.getComponent("dialogs.NeedToRegisterDialog");
|
|
||||||
Modal.createDialog(NeedToRegisterDialog, {
|
|
||||||
title: _t("Please Register"),
|
|
||||||
description: _t("Guests can't use labs features. Please register."),
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
UserSettingsStore.setFeatureEnabled(feature.id, e.target.checked);
|
return (
|
||||||
this.forceUpdate();
|
<div key={feature.id} className="mx_UserSettings_toggle">
|
||||||
}}/>
|
<input
|
||||||
<label htmlFor={feature.id}>{feature.name}</label>
|
type="checkbox"
|
||||||
</div>
|
id={feature.id}
|
||||||
));
|
name={feature.id}
|
||||||
|
defaultChecked={ UserSettingsStore.isFeatureEnabled(feature.id) }
|
||||||
|
onChange={ onChange }
|
||||||
|
/>
|
||||||
|
<label htmlFor={feature.id}>{feature.name}</label>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
});
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<h3>{ _t("Labs") }</h3>
|
<h3>{ _t("Labs") }</h3>
|
||||||
|
@ -829,9 +842,6 @@ module.exports = React.createClass({
|
||||||
},
|
},
|
||||||
|
|
||||||
_renderDeactivateAccount: function() {
|
_renderDeactivateAccount: function() {
|
||||||
// We can't deactivate a guest account.
|
|
||||||
if (MatrixClientPeg.get().isGuest()) return null;
|
|
||||||
|
|
||||||
return <div>
|
return <div>
|
||||||
<h3>{ _t("Deactivate Account") }</h3>
|
<h3>{ _t("Deactivate Account") }</h3>
|
||||||
<div className="mx_UserSettings_section">
|
<div className="mx_UserSettings_section">
|
||||||
|
@ -868,9 +878,10 @@ module.exports = React.createClass({
|
||||||
if (!this.state.rejectingInvites) {
|
if (!this.state.rejectingInvites) {
|
||||||
// bind() the invited rooms so any new invites that may come in as this button is clicked
|
// bind() the invited rooms so any new invites that may come in as this button is clicked
|
||||||
// don't inadvertently get rejected as well.
|
// don't inadvertently get rejected as well.
|
||||||
|
const onClick = this._onRejectAllInvitesClicked.bind(this, invitedRooms);
|
||||||
reject = (
|
reject = (
|
||||||
<AccessibleButton className="mx_UserSettings_button danger"
|
<AccessibleButton className="mx_UserSettings_button danger"
|
||||||
onClick={this._onRejectAllInvitesClicked.bind(this, invitedRooms)}>
|
onClick={onClick}>
|
||||||
{_t("Reject all %(invitedRooms)s invites", {invitedRooms: invitedRooms.length})}
|
{_t("Reject all %(invitedRooms)s invites", {invitedRooms: invitedRooms.length})}
|
||||||
</AccessibleButton>
|
</AccessibleButton>
|
||||||
);
|
);
|
||||||
|
@ -888,8 +899,6 @@ module.exports = React.createClass({
|
||||||
const settings = this.state.electron_settings;
|
const settings = this.state.electron_settings;
|
||||||
if (!settings) return;
|
if (!settings) return;
|
||||||
|
|
||||||
const {ipcRenderer} = require('electron');
|
|
||||||
|
|
||||||
return <div>
|
return <div>
|
||||||
<h3>{ _t('Desktop specific') }</h3>
|
<h3>{ _t('Desktop specific') }</h3>
|
||||||
<div className="mx_UserSettings_section">
|
<div className="mx_UserSettings_section">
|
||||||
|
@ -897,9 +906,7 @@ module.exports = React.createClass({
|
||||||
<input type="checkbox"
|
<input type="checkbox"
|
||||||
name="auto-launch"
|
name="auto-launch"
|
||||||
defaultChecked={settings['auto-launch']}
|
defaultChecked={settings['auto-launch']}
|
||||||
onChange={(e) => {
|
onChange={this._onAutoLaunchChanged}
|
||||||
ipcRenderer.send('settings_set', 'auto-launch', e.target.checked);
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
<label htmlFor="auto-launch">{_t('Start automatically after system login')}</label>
|
<label htmlFor="auto-launch">{_t('Start automatically after system login')}</label>
|
||||||
</div>
|
</div>
|
||||||
|
@ -907,6 +914,11 @@ module.exports = React.createClass({
|
||||||
</div>;
|
</div>;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
_onAutoLaunchChanged: function(e) {
|
||||||
|
const {ipcRenderer} = require('electron');
|
||||||
|
ipcRenderer.send('settings_set', 'auto-launch', e.target.checked);
|
||||||
|
},
|
||||||
|
|
||||||
_mapWebRtcDevicesToSpans: function(devices) {
|
_mapWebRtcDevicesToSpans: function(devices) {
|
||||||
return devices.map((device) => <span key={device.deviceId}>{device.label}</span>);
|
return devices.map((device) => <span key={device.deviceId}>{device.label}</span>);
|
||||||
},
|
},
|
||||||
|
@ -940,16 +952,13 @@ module.exports = React.createClass({
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
_renderWebRtcSettings: function() {
|
_renderWebRtcDeviceSettings: function() {
|
||||||
if (this.state.mediaDevices === false) {
|
if (this.state.mediaDevices === false) {
|
||||||
return <div>
|
return (
|
||||||
<h3>{_t('VoIP')}</h3>
|
<p className="mx_UserSettings_link" onClick={this._requestMediaPermissions}>
|
||||||
<div className="mx_UserSettings_section">
|
{_t('Missing Media Permissions, click here to request.')}
|
||||||
<p className="mx_UserSettings_link" onClick={this._requestMediaPermissions}>
|
</p>
|
||||||
{_t('Missing Media Permissions, click here to request.')}
|
);
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>;
|
|
||||||
} else if (!this.state.mediaDevices) return;
|
} else if (!this.state.mediaDevices) return;
|
||||||
|
|
||||||
const Dropdown = sdk.getComponent('elements.Dropdown');
|
const Dropdown = sdk.getComponent('elements.Dropdown');
|
||||||
|
@ -1003,10 +1012,17 @@ module.exports = React.createClass({
|
||||||
}
|
}
|
||||||
|
|
||||||
return <div>
|
return <div>
|
||||||
<h3>{_t('VoIP')}</h3>
|
|
||||||
<div className="mx_UserSettings_section">
|
|
||||||
{microphoneDropdown}
|
{microphoneDropdown}
|
||||||
{webcamDropdown}
|
{webcamDropdown}
|
||||||
|
</div>;
|
||||||
|
},
|
||||||
|
|
||||||
|
_renderWebRtcSettings: function() {
|
||||||
|
return <div>
|
||||||
|
<h3>{_t('VoIP')}</h3>
|
||||||
|
<div className="mx_UserSettings_section">
|
||||||
|
{ WEBRTC_SETTINGS_LABELS.map(this._renderLocalSetting) }
|
||||||
|
{ this._renderWebRtcDeviceSettings() }
|
||||||
</div>
|
</div>
|
||||||
</div>;
|
</div>;
|
||||||
},
|
},
|
||||||
|
@ -1063,6 +1079,9 @@ module.exports = React.createClass({
|
||||||
|
|
||||||
const threepidsSection = this.state.threepids.map((val, pidIndex) => {
|
const threepidsSection = this.state.threepids.map((val, pidIndex) => {
|
||||||
const id = "3pid-" + val.address;
|
const id = "3pid-" + val.address;
|
||||||
|
// TODO; make a separate component to avoid having to rebind onClick
|
||||||
|
// each time we render
|
||||||
|
const onRemoveClick = (e) => this.onRemoveThreepidClicked(val);
|
||||||
return (
|
return (
|
||||||
<div className="mx_UserSettings_profileTableRow" key={pidIndex}>
|
<div className="mx_UserSettings_profileTableRow" key={pidIndex}>
|
||||||
<div className="mx_UserSettings_profileLabelCell">
|
<div className="mx_UserSettings_profileLabelCell">
|
||||||
|
@ -1074,7 +1093,8 @@ module.exports = React.createClass({
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="mx_UserSettings_threepidButton mx_filterFlipColor">
|
<div className="mx_UserSettings_threepidButton mx_filterFlipColor">
|
||||||
<img src="img/cancel-small.svg" width="14" height="14" alt={ _t("Remove") } onClick={this.onRemoveThreepidClicked.bind(this, val)} />
|
<img src="img/cancel-small.svg" width="14" height="14" alt={ _t("Remove") }
|
||||||
|
onClick={onRemoveClick} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -1082,7 +1102,7 @@ module.exports = React.createClass({
|
||||||
let addEmailSection;
|
let addEmailSection;
|
||||||
if (this.state.email_add_pending) {
|
if (this.state.email_add_pending) {
|
||||||
addEmailSection = <Loader key="_email_add_spinner" />;
|
addEmailSection = <Loader key="_email_add_spinner" />;
|
||||||
} else if (!MatrixClientPeg.get().isGuest()) {
|
} else {
|
||||||
addEmailSection = (
|
addEmailSection = (
|
||||||
<div className="mx_UserSettings_profileTableRow" key="_newEmail">
|
<div className="mx_UserSettings_profileTableRow" key="_newEmail">
|
||||||
<div className="mx_UserSettings_profileLabelCell">
|
<div className="mx_UserSettings_profileLabelCell">
|
||||||
|
@ -1098,7 +1118,7 @@ module.exports = React.createClass({
|
||||||
onValueChanged={ this._onAddEmailEditFinished } />
|
onValueChanged={ this._onAddEmailEditFinished } />
|
||||||
</div>
|
</div>
|
||||||
<div className="mx_UserSettings_threepidButton mx_filterFlipColor">
|
<div className="mx_UserSettings_threepidButton mx_filterFlipColor">
|
||||||
<img src="img/plus.svg" width="14" height="14" alt="Add" onClick={this._addEmail} />
|
<img src="img/plus.svg" width="14" height="14" alt={_t("Add")} onClick={this._addEmail} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -1110,16 +1130,7 @@ module.exports = React.createClass({
|
||||||
threepidsSection.push(addEmailSection);
|
threepidsSection.push(addEmailSection);
|
||||||
threepidsSection.push(addMsisdnSection);
|
threepidsSection.push(addMsisdnSection);
|
||||||
|
|
||||||
let accountJsx;
|
const accountJsx = (
|
||||||
|
|
||||||
if (MatrixClientPeg.get().isGuest()) {
|
|
||||||
accountJsx = (
|
|
||||||
<div className="mx_UserSettings_button" onClick={this.onUpgradeClicked}>
|
|
||||||
{ _t("Create an account") }
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
accountJsx = (
|
|
||||||
<ChangePassword
|
<ChangePassword
|
||||||
className="mx_UserSettings_accountTable"
|
className="mx_UserSettings_accountTable"
|
||||||
rowClassName="mx_UserSettings_profileTableRow"
|
rowClassName="mx_UserSettings_profileTableRow"
|
||||||
|
@ -1128,10 +1139,10 @@ module.exports = React.createClass({
|
||||||
buttonClassName="mx_UserSettings_button mx_UserSettings_changePasswordButton"
|
buttonClassName="mx_UserSettings_button mx_UserSettings_changePasswordButton"
|
||||||
onError={this.onPasswordChangeError}
|
onError={this.onPasswordChangeError}
|
||||||
onFinished={this.onPasswordChanged} />
|
onFinished={this.onPasswordChanged} />
|
||||||
);
|
);
|
||||||
}
|
|
||||||
let notificationArea;
|
let notificationArea;
|
||||||
if (!MatrixClientPeg.get().isGuest() && this.state.threepids !== undefined) {
|
if (this.state.threepids !== undefined) {
|
||||||
notificationArea = (<div>
|
notificationArea = (<div>
|
||||||
<h3>{ _t("Notifications") }</h3>
|
<h3>{ _t("Notifications") }</h3>
|
||||||
|
|
||||||
|
@ -1225,7 +1236,12 @@ module.exports = React.createClass({
|
||||||
{ _t("Logged in as:") } {this._me}
|
{ _t("Logged in as:") } {this._me}
|
||||||
</div>
|
</div>
|
||||||
<div className="mx_UserSettings_advanced">
|
<div className="mx_UserSettings_advanced">
|
||||||
{_t('Access Token:')} <span className="mx_UserSettings_advanced_spoiler" onClick={this._showSpoiler} data-spoiler={ MatrixClientPeg.get().getAccessToken() }><{ _t("click to reveal") }></span>
|
{_t('Access Token:')}
|
||||||
|
<span className="mx_UserSettings_advanced_spoiler"
|
||||||
|
onClick={this._showSpoiler}
|
||||||
|
data-spoiler={ MatrixClientPeg.get().getAccessToken() }>
|
||||||
|
<{ _t("click to reveal") }>
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="mx_UserSettings_advanced">
|
<div className="mx_UserSettings_advanced">
|
||||||
{ _t("Homeserver is") } { MatrixClientPeg.get().getHomeserverUrl() }
|
{ _t("Homeserver is") } { MatrixClientPeg.get().getHomeserverUrl() }
|
||||||
|
|
|
@ -19,7 +19,6 @@ limitations under the License.
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { _t, _tJsx } from '../../../languageHandler';
|
import { _t, _tJsx } from '../../../languageHandler';
|
||||||
import ReactDOM from 'react-dom';
|
|
||||||
import sdk from '../../../index';
|
import sdk from '../../../index';
|
||||||
import Login from '../../../Login';
|
import Login from '../../../Login';
|
||||||
|
|
||||||
|
@ -88,7 +87,27 @@ module.exports = React.createClass({
|
||||||
).then((data) => {
|
).then((data) => {
|
||||||
this.props.onLoggedIn(data);
|
this.props.onLoggedIn(data);
|
||||||
}, (error) => {
|
}, (error) => {
|
||||||
this._setStateFromError(error, true);
|
let errorText;
|
||||||
|
|
||||||
|
// Some error strings only apply for logging in
|
||||||
|
const usingEmail = username.indexOf("@") > 0;
|
||||||
|
if (error.httpStatus == 400 && usingEmail) {
|
||||||
|
errorText = _t('This Home Server does not support login using email address.');
|
||||||
|
} else if (error.httpStatus === 401 || error.httpStatus === 403) {
|
||||||
|
errorText = _t('Incorrect username and/or password.');
|
||||||
|
} else {
|
||||||
|
// other errors, not specific to doing a password login
|
||||||
|
errorText = this._errorTextFromError(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
errorText: errorText,
|
||||||
|
// 401 would be the sensible status code for 'incorrect password'
|
||||||
|
// but the login API gives a 403 https://matrix.org/jira/browse/SYN-744
|
||||||
|
// mentions this (although the bug is for UI auth which is not this)
|
||||||
|
// We treat both as an incorrect password
|
||||||
|
loginIncorrect: error.httpStatus === 401 || error.httpStatus == 403,
|
||||||
|
});
|
||||||
}).finally(() => {
|
}).finally(() => {
|
||||||
this.setState({
|
this.setState({
|
||||||
busy: false
|
busy: false
|
||||||
|
@ -111,7 +130,16 @@ module.exports = React.createClass({
|
||||||
this._loginLogic.loginAsGuest().then(function(data) {
|
this._loginLogic.loginAsGuest().then(function(data) {
|
||||||
self.props.onLoggedIn(data);
|
self.props.onLoggedIn(data);
|
||||||
}, function(error) {
|
}, function(error) {
|
||||||
self._setStateFromError(error, true);
|
let errorText;
|
||||||
|
if (error.httpStatus === 403) {
|
||||||
|
errorText = _t("Guest access is disabled on this Home Server.");
|
||||||
|
} else {
|
||||||
|
errorText = self._errorTextFromError(error);
|
||||||
|
}
|
||||||
|
self.setState({
|
||||||
|
errorText: errorText,
|
||||||
|
loginIncorrect: false,
|
||||||
|
});
|
||||||
}).finally(function() {
|
}).finally(function() {
|
||||||
self.setState({
|
self.setState({
|
||||||
busy: false
|
busy: false
|
||||||
|
@ -130,7 +158,7 @@ module.exports = React.createClass({
|
||||||
onPhoneNumberChanged: function(phoneNumber) {
|
onPhoneNumberChanged: function(phoneNumber) {
|
||||||
// Validate the phone number entered
|
// Validate the phone number entered
|
||||||
if (!PHONE_NUMBER_REGEX.test(phoneNumber)) {
|
if (!PHONE_NUMBER_REGEX.test(phoneNumber)) {
|
||||||
this.setState({ errorText: 'The phone number entered looks invalid' });
|
this.setState({ errorText: _t('The phone number entered looks invalid') });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -184,44 +212,35 @@ module.exports = React.createClass({
|
||||||
currentFlow: self._getCurrentFlowStep(),
|
currentFlow: self._getCurrentFlowStep(),
|
||||||
});
|
});
|
||||||
}, function(err) {
|
}, function(err) {
|
||||||
self._setStateFromError(err, false);
|
self.setState({
|
||||||
|
errorText: self._errorTextFromError(err),
|
||||||
|
loginIncorrect: false,
|
||||||
|
});
|
||||||
}).finally(function() {
|
}).finally(function() {
|
||||||
self.setState({
|
self.setState({
|
||||||
busy: false,
|
busy: false,
|
||||||
});
|
});
|
||||||
});
|
}).done();
|
||||||
},
|
},
|
||||||
|
|
||||||
_getCurrentFlowStep: function() {
|
_getCurrentFlowStep: function() {
|
||||||
return this._loginLogic ? this._loginLogic.getCurrentFlowStep() : null;
|
return this._loginLogic ? this._loginLogic.getCurrentFlowStep() : null;
|
||||||
},
|
},
|
||||||
|
|
||||||
_setStateFromError: function(err, isLoginAttempt) {
|
|
||||||
this.setState({
|
|
||||||
errorText: this._errorTextFromError(err),
|
|
||||||
// https://matrix.org/jira/browse/SYN-744
|
|
||||||
loginIncorrect: isLoginAttempt && (err.httpStatus == 401 || err.httpStatus == 403)
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
_errorTextFromError(err) {
|
_errorTextFromError(err) {
|
||||||
if (err.friendlyText) {
|
|
||||||
return err.friendlyText;
|
|
||||||
}
|
|
||||||
|
|
||||||
let errCode = err.errcode;
|
let errCode = err.errcode;
|
||||||
if (!errCode && err.httpStatus) {
|
if (!errCode && err.httpStatus) {
|
||||||
errCode = "HTTP " + err.httpStatus;
|
errCode = "HTTP " + err.httpStatus;
|
||||||
}
|
}
|
||||||
|
|
||||||
let errorText = "Error: Problem communicating with the given homeserver " +
|
let errorText = _t("Error: Problem communicating with the given homeserver.") +
|
||||||
(errCode ? "(" + errCode + ")" : "");
|
(errCode ? " (" + errCode + ")" : "");
|
||||||
|
|
||||||
if (err.cors === 'rejected') {
|
if (err.cors === 'rejected') {
|
||||||
if (window.location.protocol === 'https:' &&
|
if (window.location.protocol === 'https:' &&
|
||||||
(this.state.enteredHomeserverUrl.startsWith("http:") ||
|
(this.state.enteredHomeserverUrl.startsWith("http:") ||
|
||||||
!this.state.enteredHomeserverUrl.startsWith("http")))
|
!this.state.enteredHomeserverUrl.startsWith("http"))
|
||||||
{
|
) {
|
||||||
errorText = <span>
|
errorText = <span>
|
||||||
{ _tJsx("Can't connect to homeserver via HTTP when an HTTPS URL is in your browser bar. " +
|
{ _tJsx("Can't connect to homeserver via HTTP when an HTTPS URL is in your browser bar. " +
|
||||||
"Either use HTTPS or <a>enable unsafe scripts</a>.",
|
"Either use HTTPS or <a>enable unsafe scripts</a>.",
|
||||||
|
@ -229,10 +248,9 @@ module.exports = React.createClass({
|
||||||
(sub) => { return <a href="https://www.google.com/search?&q=enable%20unsafe%20scripts">{ sub }</a>; }
|
(sub) => { return <a href="https://www.google.com/search?&q=enable%20unsafe%20scripts">{ sub }</a>; }
|
||||||
)}
|
)}
|
||||||
</span>;
|
</span>;
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
errorText = <span>
|
errorText = <span>
|
||||||
{ _tJsx("Can't connect to homeserver - please check your connectivity and ensure your <a>homeserver's SSL certificate</a> is trusted.",
|
{ _tJsx("Can't connect to homeserver - please check your connectivity, ensure your <a>homeserver's SSL certificate</a> is trusted, and that a browser extension is not blocking requests.",
|
||||||
/<a>(.*?)<\/a>/,
|
/<a>(.*?)<\/a>/,
|
||||||
(sub) => { return <a href={this.state.enteredHomeserverUrl}>{ sub }</a>; }
|
(sub) => { return <a href={this.state.enteredHomeserverUrl}>{ sub }</a>; }
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -50,7 +50,7 @@ module.exports = React.createClass({
|
||||||
});
|
});
|
||||||
}, function(error) {
|
}, function(error) {
|
||||||
self.setState({
|
self.setState({
|
||||||
errorString: "Failed to fetch avatar URL",
|
errorString: _t("Failed to fetch avatar URL"),
|
||||||
busy: false
|
busy: false
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -21,11 +21,9 @@ import q from 'q';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import sdk from '../../../index';
|
import sdk from '../../../index';
|
||||||
import dis from '../../../dispatcher';
|
|
||||||
import ServerConfig from '../../views/login/ServerConfig';
|
import ServerConfig from '../../views/login/ServerConfig';
|
||||||
import MatrixClientPeg from '../../../MatrixClientPeg';
|
import MatrixClientPeg from '../../../MatrixClientPeg';
|
||||||
import RegistrationForm from '../../views/login/RegistrationForm';
|
import RegistrationForm from '../../views/login/RegistrationForm';
|
||||||
import CaptchaForm from '../../views/login/CaptchaForm';
|
|
||||||
import RtsClient from '../../../RtsClient';
|
import RtsClient from '../../../RtsClient';
|
||||||
import { _t } from '../../../languageHandler';
|
import { _t } from '../../../languageHandler';
|
||||||
|
|
||||||
|
@ -47,8 +45,6 @@ module.exports = React.createClass({
|
||||||
brand: React.PropTypes.string,
|
brand: React.PropTypes.string,
|
||||||
email: React.PropTypes.string,
|
email: React.PropTypes.string,
|
||||||
referrer: React.PropTypes.string,
|
referrer: React.PropTypes.string,
|
||||||
username: React.PropTypes.string,
|
|
||||||
guestAccessToken: React.PropTypes.string,
|
|
||||||
teamServerConfig: React.PropTypes.shape({
|
teamServerConfig: React.PropTypes.shape({
|
||||||
// Email address to request new teams
|
// Email address to request new teams
|
||||||
supportEmail: React.PropTypes.string.isRequired,
|
supportEmail: React.PropTypes.string.isRequired,
|
||||||
|
@ -99,7 +95,7 @@ module.exports = React.createClass({
|
||||||
this.props.teamServerConfig.teamServerURL &&
|
this.props.teamServerConfig.teamServerURL &&
|
||||||
!this._rtsClient
|
!this._rtsClient
|
||||||
) {
|
) {
|
||||||
this._rtsClient = new RtsClient(this.props.teamServerConfig.teamServerURL);
|
this._rtsClient = this.props.rtsClient || new RtsClient(this.props.teamServerConfig.teamServerURL);
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
teamServerBusy: true,
|
teamServerBusy: true,
|
||||||
|
@ -222,7 +218,6 @@ module.exports = React.createClass({
|
||||||
}
|
}
|
||||||
|
|
||||||
trackPromise.then((teamToken) => {
|
trackPromise.then((teamToken) => {
|
||||||
console.info('Team token promise',teamToken);
|
|
||||||
this.props.onLoggedIn({
|
this.props.onLoggedIn({
|
||||||
userId: response.user_id,
|
userId: response.user_id,
|
||||||
deviceId: response.device_id,
|
deviceId: response.device_id,
|
||||||
|
@ -298,17 +293,6 @@ module.exports = React.createClass({
|
||||||
},
|
},
|
||||||
|
|
||||||
_makeRegisterRequest: function(auth) {
|
_makeRegisterRequest: function(auth) {
|
||||||
let guestAccessToken = this.props.guestAccessToken;
|
|
||||||
|
|
||||||
if (
|
|
||||||
this.state.formVals.username !== this.props.username ||
|
|
||||||
this.state.hsUrl != this.props.defaultHsUrl
|
|
||||||
) {
|
|
||||||
// don't try to upgrade if we changed our username
|
|
||||||
// or are registering on a different HS
|
|
||||||
guestAccessToken = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Only send the bind params if we're sending username / pw params
|
// Only send the bind params if we're sending username / pw params
|
||||||
// (Since we need to send no params at all to use the ones saved in the
|
// (Since we need to send no params at all to use the ones saved in the
|
||||||
// session).
|
// session).
|
||||||
|
@ -323,7 +307,7 @@ module.exports = React.createClass({
|
||||||
undefined, // session id: included in the auth dict already
|
undefined, // session id: included in the auth dict already
|
||||||
auth,
|
auth,
|
||||||
bindThreepids,
|
bindThreepids,
|
||||||
guestAccessToken,
|
null,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -360,10 +344,6 @@ module.exports = React.createClass({
|
||||||
} else if (this.state.busy || this.state.teamServerBusy) {
|
} else if (this.state.busy || this.state.teamServerBusy) {
|
||||||
registerBody = <Spinner />;
|
registerBody = <Spinner />;
|
||||||
} else {
|
} else {
|
||||||
let guestUsername = this.props.username;
|
|
||||||
if (this.state.hsUrl != this.props.defaultHsUrl) {
|
|
||||||
guestUsername = null;
|
|
||||||
}
|
|
||||||
let errorSection;
|
let errorSection;
|
||||||
if (this.state.errorText) {
|
if (this.state.errorText) {
|
||||||
errorSection = <div className="mx_Login_error">{this.state.errorText}</div>;
|
errorSection = <div className="mx_Login_error">{this.state.errorText}</div>;
|
||||||
|
@ -377,7 +357,6 @@ module.exports = React.createClass({
|
||||||
defaultPhoneNumber={this.state.formVals.phoneNumber}
|
defaultPhoneNumber={this.state.formVals.phoneNumber}
|
||||||
defaultPassword={this.state.formVals.password}
|
defaultPassword={this.state.formVals.password}
|
||||||
teamsConfig={this.state.teamsConfig}
|
teamsConfig={this.state.teamsConfig}
|
||||||
guestUsername={guestUsername}
|
|
||||||
minPasswordLength={MIN_PASSWORD_LENGTH}
|
minPasswordLength={MIN_PASSWORD_LENGTH}
|
||||||
onError={this.onFormValidationFailed}
|
onError={this.onFormValidationFailed}
|
||||||
onRegisterClick={this.onFormSubmit}
|
onRegisterClick={this.onFormSubmit}
|
||||||
|
|
|
@ -32,6 +32,7 @@ module.exports = React.createClass({
|
||||||
urls: React.PropTypes.array, // [highest_priority, ... , lowest_priority]
|
urls: React.PropTypes.array, // [highest_priority, ... , lowest_priority]
|
||||||
width: React.PropTypes.number,
|
width: React.PropTypes.number,
|
||||||
height: React.PropTypes.number,
|
height: React.PropTypes.number,
|
||||||
|
// XXX resizeMethod not actually used.
|
||||||
resizeMethod: React.PropTypes.string,
|
resizeMethod: React.PropTypes.string,
|
||||||
defaultToInitialLetter: React.PropTypes.bool // true to add default url
|
defaultToInitialLetter: React.PropTypes.bool // true to add default url
|
||||||
},
|
},
|
||||||
|
|
|
@ -17,6 +17,7 @@ limitations under the License.
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
var React = require('react');
|
var React = require('react');
|
||||||
|
import { _t } from '../../../languageHandler';
|
||||||
|
|
||||||
var Presets = {
|
var Presets = {
|
||||||
PrivateChat: "private_chat",
|
PrivateChat: "private_chat",
|
||||||
|
@ -46,9 +47,9 @@ module.exports = React.createClass({
|
||||||
render: function() {
|
render: function() {
|
||||||
return (
|
return (
|
||||||
<select className="mx_Presets" onChange={this.onValueChanged} value={this.props.preset}>
|
<select className="mx_Presets" onChange={this.onValueChanged} value={this.props.preset}>
|
||||||
<option value={this.Presets.PrivateChat}>Private Chat</option>
|
<option value={this.Presets.PrivateChat}>{_t("Private Chat")}</option>
|
||||||
<option value={this.Presets.PublicChat}>Public Chat</option>
|
<option value={this.Presets.PublicChat}>{_t("Public Chat")}</option>
|
||||||
<option value={this.Presets.Custom}>Custom</option>
|
<option value={this.Presets.Custom}>{_t("Custom")}</option>
|
||||||
</select>
|
</select>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,6 +15,7 @@ limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
var React = require('react');
|
var React = require('react');
|
||||||
|
import { _t } from '../../../languageHandler';
|
||||||
|
|
||||||
module.exports = React.createClass({
|
module.exports = React.createClass({
|
||||||
displayName: 'RoomAlias',
|
displayName: 'RoomAlias',
|
||||||
|
@ -94,7 +95,7 @@ module.exports = React.createClass({
|
||||||
|
|
||||||
render: function() {
|
render: function() {
|
||||||
return (
|
return (
|
||||||
<input type="text" className="mx_RoomAlias" placeholder="Alias (optional)"
|
<input type="text" className="mx_RoomAlias" placeholder={_t("Alias (optional)")}
|
||||||
onChange={this.onValueChanged} onFocus={this.onFocus} onBlur={this.onBlur}
|
onChange={this.onValueChanged} onFocus={this.onFocus} onBlur={this.onBlur}
|
||||||
value={this.props.alias}/>
|
value={this.props.alias}/>
|
||||||
);
|
);
|
||||||
|
|
|
@ -16,36 +16,30 @@ limitations under the License.
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import sdk from '../../../index';
|
import sdk from '../../../index';
|
||||||
import dis from '../../../dispatcher';
|
import { _t } from '../../../languageHandler';
|
||||||
import MatrixClientPeg from '../../../MatrixClientPeg';
|
import MatrixClientPeg from '../../../MatrixClientPeg';
|
||||||
import DMRoomMap from '../../../utils/DMRoomMap';
|
import DMRoomMap from '../../../utils/DMRoomMap';
|
||||||
import AccessibleButton from '../elements/AccessibleButton';
|
import AccessibleButton from '../elements/AccessibleButton';
|
||||||
import Unread from '../../../Unread';
|
import Unread from '../../../Unread';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import createRoom from '../../../createRoom';
|
|
||||||
|
|
||||||
export default class ChatCreateOrReuseDialog extends React.Component {
|
export default class ChatCreateOrReuseDialog extends React.Component {
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.onNewDMClick = this.onNewDMClick.bind(this);
|
|
||||||
this.onRoomTileClick = this.onRoomTileClick.bind(this);
|
this.onRoomTileClick = this.onRoomTileClick.bind(this);
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
tiles: [],
|
||||||
|
profile: {
|
||||||
|
displayName: null,
|
||||||
|
avatarUrl: null,
|
||||||
|
},
|
||||||
|
profileError: null,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
onNewDMClick() {
|
componentWillMount() {
|
||||||
createRoom({dmUserId: this.props.userId});
|
|
||||||
this.props.onFinished(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
onRoomTileClick(roomId) {
|
|
||||||
dis.dispatch({
|
|
||||||
action: 'view_room',
|
|
||||||
room_id: roomId,
|
|
||||||
});
|
|
||||||
this.props.onFinished(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const client = MatrixClientPeg.get();
|
const client = MatrixClientPeg.get();
|
||||||
|
|
||||||
const dmRoomMap = new DMRoomMap(client);
|
const dmRoomMap = new DMRoomMap(client);
|
||||||
|
@ -70,40 +64,123 @@ export default class ChatCreateOrReuseDialog extends React.Component {
|
||||||
highlight={highlight}
|
highlight={highlight}
|
||||||
isInvite={me.membership == "invite"}
|
isInvite={me.membership == "invite"}
|
||||||
onClick={this.onRoomTileClick}
|
onClick={this.onRoomTileClick}
|
||||||
/>
|
/>,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const labelClasses = classNames({
|
this.setState({
|
||||||
mx_MemberInfo_createRoom_label: true,
|
tiles: tiles,
|
||||||
mx_RoomTile_name: true,
|
|
||||||
});
|
});
|
||||||
const startNewChat = <AccessibleButton
|
|
||||||
className="mx_MemberInfo_createRoom"
|
if (tiles.length === 0) {
|
||||||
onClick={this.onNewDMClick}
|
this.setState({
|
||||||
>
|
busyProfile: true,
|
||||||
<div className="mx_RoomTile_avatar">
|
});
|
||||||
<img src="img/create-big.svg" width="26" height="26" />
|
MatrixClientPeg.get().getProfileInfo(this.props.userId).done((resp) => {
|
||||||
</div>
|
const profile = {
|
||||||
<div className={labelClasses}><i>{_("Start new chat")}</i></div>
|
displayName: resp.displayname,
|
||||||
</AccessibleButton>;
|
avatarUrl: null,
|
||||||
|
};
|
||||||
|
if (resp.avatar_url) {
|
||||||
|
profile.avatarUrl = MatrixClientPeg.get().mxcUrlToHttp(
|
||||||
|
resp.avatar_url, 48, 48, "crop",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
this.setState({
|
||||||
|
busyProfile: false,
|
||||||
|
profile: profile,
|
||||||
|
});
|
||||||
|
}, (err) => {
|
||||||
|
console.error(
|
||||||
|
'Unable to get profile for user ' + this.props.userId + ':',
|
||||||
|
err,
|
||||||
|
);
|
||||||
|
this.setState({
|
||||||
|
busyProfile: false,
|
||||||
|
profileError: err,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onRoomTileClick(roomId) {
|
||||||
|
this.props.onExistingRoomSelected(roomId);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
let title = '';
|
||||||
|
let content = null;
|
||||||
|
if (this.state.tiles.length > 0) {
|
||||||
|
// Show the existing rooms with a "+" to add a new dm
|
||||||
|
title = _t('Create a new chat or reuse an existing one');
|
||||||
|
const labelClasses = classNames({
|
||||||
|
mx_MemberInfo_createRoom_label: true,
|
||||||
|
mx_RoomTile_name: true,
|
||||||
|
});
|
||||||
|
const startNewChat = <AccessibleButton
|
||||||
|
className="mx_MemberInfo_createRoom"
|
||||||
|
onClick={this.props.onNewDMClick}
|
||||||
|
>
|
||||||
|
<div className="mx_RoomTile_avatar">
|
||||||
|
<img src="img/create-big.svg" width="26" height="26" />
|
||||||
|
</div>
|
||||||
|
<div className={labelClasses}><i>{ _t("Start new chat") }</i></div>
|
||||||
|
</AccessibleButton>;
|
||||||
|
content = <div className="mx_Dialog_content">
|
||||||
|
{ _t('You already have existing direct chats with this user:') }
|
||||||
|
<div className="mx_ChatCreateOrReuseDialog_tiles">
|
||||||
|
{ this.state.tiles }
|
||||||
|
{ startNewChat }
|
||||||
|
</div>
|
||||||
|
</div>;
|
||||||
|
} else {
|
||||||
|
// Show the avatar, name and a button to confirm that a new chat is requested
|
||||||
|
const BaseAvatar = sdk.getComponent('avatars.BaseAvatar');
|
||||||
|
const Spinner = sdk.getComponent('elements.Spinner');
|
||||||
|
title = _t('Start chatting');
|
||||||
|
|
||||||
|
let profile = null;
|
||||||
|
if (this.state.busyProfile) {
|
||||||
|
profile = <Spinner />;
|
||||||
|
} else if (this.state.profileError) {
|
||||||
|
profile = <div className="error">
|
||||||
|
Unable to load profile information for { this.props.userId }
|
||||||
|
</div>;
|
||||||
|
} else {
|
||||||
|
profile = <div className="mx_ChatCreateOrReuseDialog_profile">
|
||||||
|
<BaseAvatar
|
||||||
|
name={this.state.profile.displayName || this.props.userId}
|
||||||
|
url={this.state.profile.avatarUrl}
|
||||||
|
width={48} height={48}
|
||||||
|
/>
|
||||||
|
<div className="mx_ChatCreateOrReuseDialog_profile_name">
|
||||||
|
{this.state.profile.displayName || this.props.userId}
|
||||||
|
</div>
|
||||||
|
</div>;
|
||||||
|
}
|
||||||
|
content = <div>
|
||||||
|
<div className="mx_Dialog_content">
|
||||||
|
<p>
|
||||||
|
{ _t('Click on the button below to start chatting!') }
|
||||||
|
</p>
|
||||||
|
{ profile }
|
||||||
|
</div>
|
||||||
|
<div className="mx_Dialog_buttons">
|
||||||
|
<button className="mx_Dialog_primary" onClick={this.props.onNewDMClick}>
|
||||||
|
{ _t('Start Chatting') }
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>;
|
||||||
|
}
|
||||||
|
|
||||||
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
|
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
|
||||||
return (
|
return (
|
||||||
<BaseDialog className='mx_ChatCreateOrReuseDialog'
|
<BaseDialog className='mx_ChatCreateOrReuseDialog'
|
||||||
onFinished={() => {
|
onFinished={ this.props.onFinished.bind(false) }
|
||||||
this.props.onFinished(false)
|
title={title}
|
||||||
}}
|
|
||||||
title='Create a new chat or reuse an existing one'
|
|
||||||
>
|
>
|
||||||
<div className="mx_Dialog_content">
|
{ content }
|
||||||
You already have existing direct chats with this user:
|
|
||||||
<div className="mx_ChatCreateOrReuseDialog_tiles">
|
|
||||||
{tiles}
|
|
||||||
{startNewChat}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</BaseDialog>
|
</BaseDialog>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -111,5 +188,8 @@ export default class ChatCreateOrReuseDialog extends React.Component {
|
||||||
|
|
||||||
ChatCreateOrReuseDialog.propTyps = {
|
ChatCreateOrReuseDialog.propTyps = {
|
||||||
userId: React.PropTypes.string.isRequired,
|
userId: React.PropTypes.string.isRequired,
|
||||||
|
// Called when clicking outside of the dialog
|
||||||
onFinished: React.PropTypes.func.isRequired,
|
onFinished: React.PropTypes.func.isRequired,
|
||||||
|
onNewDMClick: React.PropTypes.func.isRequired,
|
||||||
|
onExistingRoomSelected: React.PropTypes.func.isRequired,
|
||||||
};
|
};
|
||||||
|
|
|
@ -15,20 +15,19 @@ limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import classNames from 'classnames';
|
|
||||||
import { _t } from '../../../languageHandler';
|
import { _t } from '../../../languageHandler';
|
||||||
import sdk from '../../../index';
|
import sdk from '../../../index';
|
||||||
import { getAddressType, inviteMultipleToRoom } from '../../../Invite';
|
import { getAddressType, inviteMultipleToRoom } from '../../../Invite';
|
||||||
import createRoom from '../../../createRoom';
|
import createRoom from '../../../createRoom';
|
||||||
import MatrixClientPeg from '../../../MatrixClientPeg';
|
import MatrixClientPeg from '../../../MatrixClientPeg';
|
||||||
import DMRoomMap from '../../../utils/DMRoomMap';
|
import DMRoomMap from '../../../utils/DMRoomMap';
|
||||||
import rate_limited_func from '../../../ratelimitedfunc';
|
|
||||||
import dis from '../../../dispatcher';
|
|
||||||
import Modal from '../../../Modal';
|
import Modal from '../../../Modal';
|
||||||
import AccessibleButton from '../elements/AccessibleButton';
|
import AccessibleButton from '../elements/AccessibleButton';
|
||||||
import q from 'q';
|
import q from 'q';
|
||||||
|
import dis from '../../../dispatcher';
|
||||||
|
|
||||||
const TRUNCATE_QUERY_LIST = 40;
|
const TRUNCATE_QUERY_LIST = 40;
|
||||||
|
const QUERY_USER_DIRECTORY_DEBOUNCE_MS = 200;
|
||||||
|
|
||||||
module.exports = React.createClass({
|
module.exports = React.createClass({
|
||||||
displayName: "ChatInviteDialog",
|
displayName: "ChatInviteDialog",
|
||||||
|
@ -43,13 +42,13 @@ module.exports = React.createClass({
|
||||||
roomId: React.PropTypes.string,
|
roomId: React.PropTypes.string,
|
||||||
button: React.PropTypes.string,
|
button: React.PropTypes.string,
|
||||||
focus: React.PropTypes.bool,
|
focus: React.PropTypes.bool,
|
||||||
onFinished: React.PropTypes.func.isRequired
|
onFinished: React.PropTypes.func.isRequired,
|
||||||
},
|
},
|
||||||
|
|
||||||
getDefaultProps: function() {
|
getDefaultProps: function() {
|
||||||
return {
|
return {
|
||||||
value: "",
|
value: "",
|
||||||
focus: true
|
focus: true,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -57,12 +56,20 @@ module.exports = React.createClass({
|
||||||
return {
|
return {
|
||||||
error: false,
|
error: false,
|
||||||
|
|
||||||
// List of AddressTile.InviteAddressType objects represeting
|
// List of AddressTile.InviteAddressType objects representing
|
||||||
// the list of addresses we're going to invite
|
// the list of addresses we're going to invite
|
||||||
inviteList: [],
|
inviteList: [],
|
||||||
|
|
||||||
// List of AddressTile.InviteAddressType objects represeting
|
// Whether a search is ongoing
|
||||||
// the set of autocompletion results for the current search
|
busy: false,
|
||||||
|
// An error message generated during the user directory search
|
||||||
|
searchError: null,
|
||||||
|
// Whether the server supports the user_directory API
|
||||||
|
serverSupportsUserDirectory: true,
|
||||||
|
// The query being searched for
|
||||||
|
query: "",
|
||||||
|
// List of AddressTile.InviteAddressType objects representing
|
||||||
|
// the set of auto-completion results for the current search
|
||||||
// query.
|
// query.
|
||||||
queryList: [],
|
queryList: [],
|
||||||
};
|
};
|
||||||
|
@ -73,7 +80,6 @@ module.exports = React.createClass({
|
||||||
// Set the cursor at the end of the text input
|
// Set the cursor at the end of the text input
|
||||||
this.refs.textinput.value = this.props.value;
|
this.refs.textinput.value = this.props.value;
|
||||||
}
|
}
|
||||||
this._updateUserList();
|
|
||||||
},
|
},
|
||||||
|
|
||||||
onButtonClick: function() {
|
onButtonClick: function() {
|
||||||
|
@ -95,17 +101,28 @@ module.exports = React.createClass({
|
||||||
// A Direct Message room already exists for this user, so select a
|
// A Direct Message room already exists for this user, so select a
|
||||||
// room from a list that is similar to the one in MemberInfo panel
|
// room from a list that is similar to the one in MemberInfo panel
|
||||||
const ChatCreateOrReuseDialog = sdk.getComponent(
|
const ChatCreateOrReuseDialog = sdk.getComponent(
|
||||||
"views.dialogs.ChatCreateOrReuseDialog"
|
"views.dialogs.ChatCreateOrReuseDialog",
|
||||||
);
|
);
|
||||||
Modal.createDialog(ChatCreateOrReuseDialog, {
|
const close = Modal.createDialog(ChatCreateOrReuseDialog, {
|
||||||
userId: userId,
|
userId: userId,
|
||||||
onFinished: (success) => {
|
onFinished: (success) => {
|
||||||
if (success) {
|
this.props.onFinished(success);
|
||||||
this.props.onFinished(true, inviteList[0]);
|
},
|
||||||
}
|
onNewDMClick: () => {
|
||||||
// else show this ChatInviteDialog again
|
dis.dispatch({
|
||||||
}
|
action: 'start_chat',
|
||||||
});
|
user_id: userId,
|
||||||
|
});
|
||||||
|
close(true);
|
||||||
|
},
|
||||||
|
onExistingRoomSelected: (roomId) => {
|
||||||
|
dis.dispatch({
|
||||||
|
action: 'view_room',
|
||||||
|
room_id: roomId,
|
||||||
|
});
|
||||||
|
close(true);
|
||||||
|
},
|
||||||
|
}).close;
|
||||||
} else {
|
} else {
|
||||||
this._startChat(inviteList);
|
this._startChat(inviteList);
|
||||||
}
|
}
|
||||||
|
@ -131,15 +148,15 @@ module.exports = React.createClass({
|
||||||
} else if (e.keyCode === 38) { // up arrow
|
} else if (e.keyCode === 38) { // up arrow
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
this.addressSelector.moveSelectionUp();
|
if (this.addressSelector) this.addressSelector.moveSelectionUp();
|
||||||
} else if (e.keyCode === 40) { // down arrow
|
} else if (e.keyCode === 40) { // down arrow
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
this.addressSelector.moveSelectionDown();
|
if (this.addressSelector) this.addressSelector.moveSelectionDown();
|
||||||
} else if (this.state.queryList.length > 0 && (e.keyCode === 188 || e.keyCode === 13 || e.keyCode === 9)) { // comma or enter or tab
|
} else if (this.state.queryList.length > 0 && (e.keyCode === 188 || e.keyCode === 13 || e.keyCode === 9)) { // comma or enter or tab
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
this.addressSelector.chooseSelection();
|
if (this.addressSelector) this.addressSelector.chooseSelection();
|
||||||
} else if (this.refs.textinput.value.length === 0 && this.state.inviteList.length && e.keyCode === 8) { // backspace
|
} else if (this.refs.textinput.value.length === 0 && this.state.inviteList.length && e.keyCode === 8) { // backspace
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
@ -162,74 +179,36 @@ module.exports = React.createClass({
|
||||||
|
|
||||||
onQueryChanged: function(ev) {
|
onQueryChanged: function(ev) {
|
||||||
const query = ev.target.value.toLowerCase();
|
const query = ev.target.value.toLowerCase();
|
||||||
let queryList = [];
|
|
||||||
|
|
||||||
if (query.length < 2) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.queryChangedDebouncer) {
|
if (this.queryChangedDebouncer) {
|
||||||
clearTimeout(this.queryChangedDebouncer);
|
clearTimeout(this.queryChangedDebouncer);
|
||||||
}
|
}
|
||||||
this.queryChangedDebouncer = setTimeout(() => {
|
// Only do search if there is something to search
|
||||||
// Only do search if there is something to search
|
if (query.length > 0 && query != '@' && query.length >= 2) {
|
||||||
if (query.length > 0 && query != '@') {
|
this.queryChangedDebouncer = setTimeout(() => {
|
||||||
this._userList.forEach((user) => {
|
if (this.state.serverSupportsUserDirectory) {
|
||||||
if (user.userId.toLowerCase().indexOf(query) === -1 &&
|
this._doUserDirectorySearch(query);
|
||||||
user.displayName.toLowerCase().indexOf(query) === -1
|
} else {
|
||||||
) {
|
this._doLocalSearch(query);
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return objects, structure of which is defined
|
|
||||||
// by InviteAddressType
|
|
||||||
queryList.push({
|
|
||||||
addressType: 'mx',
|
|
||||||
address: user.userId,
|
|
||||||
displayName: user.displayName,
|
|
||||||
avatarMxc: user.avatarUrl,
|
|
||||||
isKnown: true,
|
|
||||||
order: user.getLastActiveTs(),
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
queryList = queryList.sort((a,b) => {
|
|
||||||
return a.order < b.order;
|
|
||||||
});
|
|
||||||
|
|
||||||
// If the query is a valid address, add an entry for that
|
|
||||||
// This is important, otherwise there's no way to invite
|
|
||||||
// a perfectly valid address if there are close matches.
|
|
||||||
const addrType = getAddressType(query);
|
|
||||||
if (addrType !== null) {
|
|
||||||
queryList.unshift({
|
|
||||||
addressType: addrType,
|
|
||||||
address: query,
|
|
||||||
isKnown: false,
|
|
||||||
});
|
|
||||||
if (this._cancelThreepidLookup) this._cancelThreepidLookup();
|
|
||||||
if (addrType == 'email') {
|
|
||||||
this._lookupThreepid(addrType, query).done();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}, QUERY_USER_DIRECTORY_DEBOUNCE_MS);
|
||||||
|
} else {
|
||||||
this.setState({
|
this.setState({
|
||||||
queryList: queryList,
|
queryList: [],
|
||||||
error: false,
|
query: "",
|
||||||
}, () => {
|
searchError: null,
|
||||||
this.addressSelector.moveSelectionTop();
|
|
||||||
});
|
});
|
||||||
}, 200);
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
onDismissed: function(index) {
|
onDismissed: function(index) {
|
||||||
var self = this;
|
var self = this;
|
||||||
return function() {
|
return () => {
|
||||||
var inviteList = self.state.inviteList.slice();
|
var inviteList = self.state.inviteList.slice();
|
||||||
inviteList.splice(index, 1);
|
inviteList.splice(index, 1);
|
||||||
self.setState({
|
self.setState({
|
||||||
inviteList: inviteList,
|
inviteList: inviteList,
|
||||||
queryList: [],
|
queryList: [],
|
||||||
|
query: "",
|
||||||
});
|
});
|
||||||
if (this._cancelThreepidLookup) this._cancelThreepidLookup();
|
if (this._cancelThreepidLookup) this._cancelThreepidLookup();
|
||||||
};
|
};
|
||||||
|
@ -248,10 +227,108 @@ module.exports = React.createClass({
|
||||||
this.setState({
|
this.setState({
|
||||||
inviteList: inviteList,
|
inviteList: inviteList,
|
||||||
queryList: [],
|
queryList: [],
|
||||||
|
query: "",
|
||||||
});
|
});
|
||||||
if (this._cancelThreepidLookup) this._cancelThreepidLookup();
|
if (this._cancelThreepidLookup) this._cancelThreepidLookup();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
_doUserDirectorySearch: function(query) {
|
||||||
|
this.setState({
|
||||||
|
busy: true,
|
||||||
|
query,
|
||||||
|
searchError: null,
|
||||||
|
});
|
||||||
|
MatrixClientPeg.get().searchUserDirectory({
|
||||||
|
term: query,
|
||||||
|
}).then((resp) => {
|
||||||
|
// The query might have changed since we sent the request, so ignore
|
||||||
|
// responses for anything other than the latest query.
|
||||||
|
if (this.state.query !== query) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this._processResults(resp.results, query);
|
||||||
|
}).catch((err) => {
|
||||||
|
console.error('Error whilst searching user directory: ', err);
|
||||||
|
this.setState({
|
||||||
|
searchError: err.errcode ? err.message : _t('Something went wrong!'),
|
||||||
|
});
|
||||||
|
if (err.errcode === 'M_UNRECOGNIZED') {
|
||||||
|
this.setState({
|
||||||
|
serverSupportsUserDirectory: false,
|
||||||
|
});
|
||||||
|
// Do a local search immediately
|
||||||
|
this._doLocalSearch(query);
|
||||||
|
}
|
||||||
|
}).done(() => {
|
||||||
|
this.setState({
|
||||||
|
busy: false,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
_doLocalSearch: function(query) {
|
||||||
|
this.setState({
|
||||||
|
query,
|
||||||
|
searchError: null,
|
||||||
|
});
|
||||||
|
const results = [];
|
||||||
|
MatrixClientPeg.get().getUsers().forEach((user) => {
|
||||||
|
if (user.userId.toLowerCase().indexOf(query) === -1 &&
|
||||||
|
user.displayName.toLowerCase().indexOf(query) === -1
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Put results in the format of the new API
|
||||||
|
results.push({
|
||||||
|
user_id: user.userId,
|
||||||
|
display_name: user.displayName,
|
||||||
|
avatar_url: user.avatarUrl,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
this._processResults(results, query);
|
||||||
|
},
|
||||||
|
|
||||||
|
_processResults: function(results, query) {
|
||||||
|
const queryList = [];
|
||||||
|
results.forEach((user) => {
|
||||||
|
if (user.user_id === MatrixClientPeg.get().credentials.userId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Return objects, structure of which is defined
|
||||||
|
// by InviteAddressType
|
||||||
|
queryList.push({
|
||||||
|
addressType: 'mx',
|
||||||
|
address: user.user_id,
|
||||||
|
displayName: user.display_name,
|
||||||
|
avatarMxc: user.avatar_url,
|
||||||
|
isKnown: true,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// If the query is a valid address, add an entry for that
|
||||||
|
// This is important, otherwise there's no way to invite
|
||||||
|
// a perfectly valid address if there are close matches.
|
||||||
|
const addrType = getAddressType(query);
|
||||||
|
if (addrType !== null) {
|
||||||
|
queryList.unshift({
|
||||||
|
addressType: addrType,
|
||||||
|
address: query,
|
||||||
|
isKnown: false,
|
||||||
|
});
|
||||||
|
if (this._cancelThreepidLookup) this._cancelThreepidLookup();
|
||||||
|
if (addrType == 'email') {
|
||||||
|
this._lookupThreepid(addrType, query).done();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.setState({
|
||||||
|
queryList,
|
||||||
|
error: false,
|
||||||
|
}, () => {
|
||||||
|
if (this.addressSelector) this.addressSelector.moveSelectionTop();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
_getDirectMessageRooms: function(addr) {
|
_getDirectMessageRooms: function(addr) {
|
||||||
const dmRoomMap = new DMRoomMap(MatrixClientPeg.get());
|
const dmRoomMap = new DMRoomMap(MatrixClientPeg.get());
|
||||||
const dmRooms = dmRoomMap.getDMRoomsForUserId(addr);
|
const dmRooms = dmRoomMap.getDMRoomsForUserId(addr);
|
||||||
|
@ -270,11 +347,7 @@ module.exports = React.createClass({
|
||||||
|
|
||||||
_startChat: function(addrs) {
|
_startChat: function(addrs) {
|
||||||
if (MatrixClientPeg.get().isGuest()) {
|
if (MatrixClientPeg.get().isGuest()) {
|
||||||
var NeedToRegisterDialog = sdk.getComponent("dialogs.NeedToRegisterDialog");
|
dis.dispatch({action: 'view_set_mxid'});
|
||||||
Modal.createDialog(NeedToRegisterDialog, {
|
|
||||||
title: _t("Please Register"),
|
|
||||||
description: _t("Guest users can't invite users. Please register."),
|
|
||||||
});
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -340,16 +413,6 @@ module.exports = React.createClass({
|
||||||
this.props.onFinished(true, addrTexts);
|
this.props.onFinished(true, addrTexts);
|
||||||
},
|
},
|
||||||
|
|
||||||
_updateUserList: function() {
|
|
||||||
// Get all the users
|
|
||||||
this._userList = MatrixClientPeg.get().getUsers();
|
|
||||||
// Remove current user
|
|
||||||
const meIx = this._userList.findIndex((u) => {
|
|
||||||
return u.userId === MatrixClientPeg.get().credentials.userId;
|
|
||||||
});
|
|
||||||
this._userList.splice(meIx, 1);
|
|
||||||
},
|
|
||||||
|
|
||||||
_isOnInviteList: function(uid) {
|
_isOnInviteList: function(uid) {
|
||||||
for (let i = 0; i < this.state.inviteList.length; i++) {
|
for (let i = 0; i < this.state.inviteList.length; i++) {
|
||||||
if (
|
if (
|
||||||
|
@ -417,6 +480,7 @@ module.exports = React.createClass({
|
||||||
this.setState({
|
this.setState({
|
||||||
inviteList: inviteList,
|
inviteList: inviteList,
|
||||||
queryList: [],
|
queryList: [],
|
||||||
|
query: "",
|
||||||
});
|
});
|
||||||
if (this._cancelThreepidLookup) this._cancelThreepidLookup();
|
if (this._cancelThreepidLookup) this._cancelThreepidLookup();
|
||||||
return inviteList;
|
return inviteList;
|
||||||
|
@ -452,7 +516,7 @@ module.exports = React.createClass({
|
||||||
displayName: res.displayname,
|
displayName: res.displayname,
|
||||||
avatarMxc: res.avatar_url,
|
avatarMxc: res.avatar_url,
|
||||||
isKnown: true,
|
isKnown: true,
|
||||||
}]
|
}],
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
@ -484,23 +548,27 @@ module.exports = React.createClass({
|
||||||
placeholder={this.props.placeholder}
|
placeholder={this.props.placeholder}
|
||||||
defaultValue={this.props.value}
|
defaultValue={this.props.value}
|
||||||
autoFocus={this.props.focus}>
|
autoFocus={this.props.focus}>
|
||||||
</textarea>
|
</textarea>,
|
||||||
);
|
);
|
||||||
|
|
||||||
var error;
|
let error;
|
||||||
var addressSelector;
|
let addressSelector;
|
||||||
if (this.state.error) {
|
if (this.state.error) {
|
||||||
error = <div className="mx_ChatInviteDialog_error">{_t("You have entered an invalid contact. Try using their Matrix ID or email address.")}</div>;
|
error = <div className="mx_ChatInviteDialog_error">{_t("You have entered an invalid contact. Try using their Matrix ID or email address.")}</div>;
|
||||||
|
} else if (this.state.searchError) {
|
||||||
|
error = <div className="mx_ChatInviteDialog_error">{this.state.searchError}</div>;
|
||||||
|
} else if (
|
||||||
|
this.state.query.length > 0 &&
|
||||||
|
this.state.queryList.length === 0 &&
|
||||||
|
!this.state.busy
|
||||||
|
) {
|
||||||
|
error = <div className="mx_ChatInviteDialog_error">{_t("No results")}</div>;
|
||||||
} else {
|
} else {
|
||||||
const addressSelectorHeader = <div className="mx_ChatInviteDialog_addressSelectHeader">
|
|
||||||
Searching known users
|
|
||||||
</div>;
|
|
||||||
addressSelector = (
|
addressSelector = (
|
||||||
<AddressSelector ref={(ref) => {this.addressSelector = ref;}}
|
<AddressSelector ref={(ref) => {this.addressSelector = ref;}}
|
||||||
addressList={ this.state.queryList }
|
addressList={ this.state.queryList }
|
||||||
onSelected={ this.onSelected }
|
onSelected={ this.onSelected }
|
||||||
truncateAt={ TRUNCATE_QUERY_LIST }
|
truncateAt={ TRUNCATE_QUERY_LIST }
|
||||||
header={ addressSelectorHeader }
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -86,7 +86,7 @@ export default class DeactivateAccountDialog extends React.Component {
|
||||||
passwordBoxClass = 'error';
|
passwordBoxClass = 'error';
|
||||||
}
|
}
|
||||||
|
|
||||||
const okLabel = this.state.busy ? <Loader /> : 'Deactivate Account';
|
const okLabel = this.state.busy ? <Loader /> : _t('Deactivate Account');
|
||||||
const okEnabled = this.state.confirmButtonEnabled && !this.state.busy;
|
const okEnabled = this.state.confirmButtonEnabled && !this.state.busy;
|
||||||
|
|
||||||
let cancelButton = null;
|
let cancelButton = null;
|
||||||
|
|
|
@ -15,8 +15,6 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import Matrix from 'matrix-js-sdk';
|
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import sdk from '../../../index';
|
import sdk from '../../../index';
|
||||||
|
@ -80,7 +78,7 @@ export default React.createClass({
|
||||||
<AccessibleButton onClick={this._onDismissClick}
|
<AccessibleButton onClick={this._onDismissClick}
|
||||||
className="mx_UserSettings_button"
|
className="mx_UserSettings_button"
|
||||||
>
|
>
|
||||||
Dismiss
|
{_t("Dismiss")}
|
||||||
</AccessibleButton>
|
</AccessibleButton>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,72 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright 2016 OpenMarket Ltd
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Usage:
|
|
||||||
* Modal.createDialog(NeedToRegisterDialog, {
|
|
||||||
* title: "some text", (default: "Registration required")
|
|
||||||
* description: "some more text",
|
|
||||||
* onFinished: someFunction,
|
|
||||||
* });
|
|
||||||
*/
|
|
||||||
|
|
||||||
import React from 'react';
|
|
||||||
import dis from '../../../dispatcher';
|
|
||||||
import sdk from '../../../index';
|
|
||||||
import { _t } from '../../../languageHandler';
|
|
||||||
|
|
||||||
module.exports = React.createClass({
|
|
||||||
displayName: 'NeedToRegisterDialog',
|
|
||||||
propTypes: {
|
|
||||||
title: React.PropTypes.string,
|
|
||||||
description: React.PropTypes.oneOfType([
|
|
||||||
React.PropTypes.element,
|
|
||||||
React.PropTypes.string,
|
|
||||||
]),
|
|
||||||
onFinished: React.PropTypes.func.isRequired,
|
|
||||||
},
|
|
||||||
|
|
||||||
onRegisterClicked: function() {
|
|
||||||
dis.dispatch({
|
|
||||||
action: "start_upgrade_registration",
|
|
||||||
});
|
|
||||||
if (this.props.onFinished) {
|
|
||||||
this.props.onFinished();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
render: function() {
|
|
||||||
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
|
|
||||||
return (
|
|
||||||
<BaseDialog className="mx_NeedToRegisterDialog"
|
|
||||||
onFinished={this.props.onFinished}
|
|
||||||
title={this.props.title || _t('Registration required')}
|
|
||||||
>
|
|
||||||
<div className="mx_Dialog_content">
|
|
||||||
{this.props.description || _t('A registered account is required for this action')}
|
|
||||||
</div>
|
|
||||||
<div className="mx_Dialog_buttons">
|
|
||||||
<button className="mx_Dialog_primary" onClick={this.props.onFinished} autoFocus={true}>
|
|
||||||
{_t("Cancel")}
|
|
||||||
</button>
|
|
||||||
<button onClick={this.onRegisterClicked}>
|
|
||||||
{_t("Register")}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</BaseDialog>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
});
|
|
|
@ -18,7 +18,7 @@ import React from 'react';
|
||||||
import sdk from '../../../index';
|
import sdk from '../../../index';
|
||||||
import SdkConfig from '../../../SdkConfig';
|
import SdkConfig from '../../../SdkConfig';
|
||||||
import Modal from '../../../Modal';
|
import Modal from '../../../Modal';
|
||||||
import { _t } from '../../../languageHandler';
|
import { _t, _tJsx } from '../../../languageHandler';
|
||||||
|
|
||||||
|
|
||||||
export default React.createClass({
|
export default React.createClass({
|
||||||
|
@ -44,8 +44,11 @@ export default React.createClass({
|
||||||
|
|
||||||
if (SdkConfig.get().bug_report_endpoint_url) {
|
if (SdkConfig.get().bug_report_endpoint_url) {
|
||||||
bugreport = (
|
bugreport = (
|
||||||
<p>Otherwise, <a onClick={this._sendBugReport} href='#'>
|
<p>
|
||||||
click here</a> to send a bug report.
|
{_tJsx(
|
||||||
|
"Otherwise, <a>click here</a> to send a bug report.",
|
||||||
|
/<a>(.*?)<\/a>/, (sub) => <a onClick={this._sendBugReport} key="bugreport" href='#'>{sub}</a>,
|
||||||
|
)}
|
||||||
</p>
|
</p>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,89 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright 2016 OpenMarket Ltd
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import React from 'react';
|
|
||||||
import sdk from '../../../index';
|
|
||||||
import MatrixClientPeg from '../../../MatrixClientPeg';
|
|
||||||
|
|
||||||
import { _t } from '../../../languageHandler';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Prompt the user to set a display name.
|
|
||||||
*
|
|
||||||
* On success, `onFinished(true, newDisplayName)` is called.
|
|
||||||
*/
|
|
||||||
export default React.createClass({
|
|
||||||
displayName: 'SetDisplayNameDialog',
|
|
||||||
propTypes: {
|
|
||||||
onFinished: React.PropTypes.func.isRequired,
|
|
||||||
currentDisplayName: React.PropTypes.string,
|
|
||||||
},
|
|
||||||
|
|
||||||
getInitialState: function() {
|
|
||||||
if (this.props.currentDisplayName) {
|
|
||||||
return { value: this.props.currentDisplayName };
|
|
||||||
}
|
|
||||||
|
|
||||||
if (MatrixClientPeg.get().isGuest()) {
|
|
||||||
return { value : "Guest " + MatrixClientPeg.get().getUserIdLocalpart() };
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return { value : MatrixClientPeg.get().getUserIdLocalpart() };
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
componentDidMount: function() {
|
|
||||||
this.refs.input_value.select();
|
|
||||||
},
|
|
||||||
|
|
||||||
onValueChange: function(ev) {
|
|
||||||
this.setState({
|
|
||||||
value: ev.target.value
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
onFormSubmit: function(ev) {
|
|
||||||
ev.preventDefault();
|
|
||||||
this.props.onFinished(true, this.state.value);
|
|
||||||
return false;
|
|
||||||
},
|
|
||||||
|
|
||||||
render: function() {
|
|
||||||
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
|
|
||||||
return (
|
|
||||||
<BaseDialog className="mx_SetDisplayNameDialog"
|
|
||||||
onFinished={this.props.onFinished}
|
|
||||||
title={_t("Set a Display Name")}
|
|
||||||
>
|
|
||||||
<div className="mx_Dialog_content">
|
|
||||||
{_t("Your display name is how you'll appear to others when you speak in rooms. " +
|
|
||||||
"What would you like it to be?")}
|
|
||||||
</div>
|
|
||||||
<form onSubmit={this.onFormSubmit}>
|
|
||||||
<div className="mx_Dialog_content">
|
|
||||||
<input type="text" ref="input_value" value={this.state.value}
|
|
||||||
autoFocus={true} onChange={this.onValueChange} size="30"
|
|
||||||
className="mx_SetDisplayNameDialog_input"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="mx_Dialog_buttons">
|
|
||||||
<input className="mx_Dialog_primary" type="submit" value="Set" />
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</BaseDialog>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
});
|
|
164
src/components/views/dialogs/SetEmailDialog.js
Normal file
164
src/components/views/dialogs/SetEmailDialog.js
Normal file
|
@ -0,0 +1,164 @@
|
||||||
|
/*
|
||||||
|
Copyright 2017 Vector Creations Ltd
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import sdk from '../../../index';
|
||||||
|
import Email from '../../../email';
|
||||||
|
import AddThreepid from '../../../AddThreepid';
|
||||||
|
import { _t } from '../../../languageHandler';
|
||||||
|
import Modal from '../../../Modal';
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prompt the user to set an email address.
|
||||||
|
*
|
||||||
|
* On success, `onFinished(true)` is called.
|
||||||
|
*/
|
||||||
|
export default React.createClass({
|
||||||
|
displayName: 'SetEmailDialog',
|
||||||
|
propTypes: {
|
||||||
|
onFinished: React.PropTypes.func.isRequired,
|
||||||
|
},
|
||||||
|
|
||||||
|
getInitialState: function() {
|
||||||
|
return {
|
||||||
|
emailAddress: null,
|
||||||
|
emailBusy: false,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
componentDidMount: function() {
|
||||||
|
},
|
||||||
|
|
||||||
|
onEmailAddressChanged: function(value) {
|
||||||
|
this.setState({
|
||||||
|
emailAddress: value,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
onSubmit: function() {
|
||||||
|
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||||
|
const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
|
||||||
|
|
||||||
|
const emailAddress = this.state.emailAddress;
|
||||||
|
if (!Email.looksValid(emailAddress)) {
|
||||||
|
Modal.createDialog(ErrorDialog, {
|
||||||
|
title: _t("Invalid Email Address"),
|
||||||
|
description: _t("This doesn't appear to be a valid email address"),
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this._addThreepid = new AddThreepid();
|
||||||
|
// we always bind emails when registering, so let's do the
|
||||||
|
// same here.
|
||||||
|
this._addThreepid.addEmailAddress(emailAddress, true).done(() => {
|
||||||
|
Modal.createDialog(QuestionDialog, {
|
||||||
|
title: _t("Verification Pending"),
|
||||||
|
description: _t(
|
||||||
|
"Please check your email and click on the link it contains. Once this " +
|
||||||
|
"is done, click continue.",
|
||||||
|
),
|
||||||
|
button: _t('Continue'),
|
||||||
|
onFinished: this.onEmailDialogFinished,
|
||||||
|
});
|
||||||
|
}, (err) => {
|
||||||
|
this.setState({emailBusy: false});
|
||||||
|
console.error("Unable to add email address " + emailAddress + " " + err);
|
||||||
|
Modal.createDialog(ErrorDialog, {
|
||||||
|
title: _t("Unable to add email address"),
|
||||||
|
description: ((err && err.message) ? err.message : _t("Operation failed")),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
this.setState({emailBusy: true});
|
||||||
|
},
|
||||||
|
|
||||||
|
onCancelled: function() {
|
||||||
|
this.props.onFinished(false);
|
||||||
|
},
|
||||||
|
|
||||||
|
onEmailDialogFinished: function(ok) {
|
||||||
|
if (ok) {
|
||||||
|
this.verifyEmailAddress();
|
||||||
|
} else {
|
||||||
|
this.setState({emailBusy: false});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
verifyEmailAddress: function() {
|
||||||
|
this._addThreepid.checkEmailLinkClicked().done(() => {
|
||||||
|
this.props.onFinished(true);
|
||||||
|
}, (err) => {
|
||||||
|
this.setState({emailBusy: false});
|
||||||
|
if (err.errcode == 'M_THREEPID_AUTH_FAILED') {
|
||||||
|
const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
|
||||||
|
const message = _t("Unable to verify email address.") + " " +
|
||||||
|
_t("Please check your email and click on the link it contains. Once this is done, click continue.");
|
||||||
|
Modal.createDialog(QuestionDialog, {
|
||||||
|
title: _t("Verification Pending"),
|
||||||
|
description: message,
|
||||||
|
button: _t('Continue'),
|
||||||
|
onFinished: this.onEmailDialogFinished,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||||
|
console.error("Unable to verify email address: " + err);
|
||||||
|
Modal.createDialog(ErrorDialog, {
|
||||||
|
title: _t("Unable to verify email address."),
|
||||||
|
description: ((err && err.message) ? err.message : _t("Operation failed")),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
render: function() {
|
||||||
|
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
|
||||||
|
const Spinner = sdk.getComponent('elements.Spinner');
|
||||||
|
const EditableText = sdk.getComponent('elements.EditableText');
|
||||||
|
|
||||||
|
const emailInput = this.state.emailBusy ? <Spinner /> : <EditableText
|
||||||
|
className="mx_SetEmailDialog_email_input"
|
||||||
|
placeholder={ _t("Email address") }
|
||||||
|
placeholderClassName="mx_SetEmailDialog_email_input_placeholder"
|
||||||
|
blurToCancel={ false }
|
||||||
|
onValueChanged={ this.onEmailAddressChanged } />;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<BaseDialog className="mx_SetEmailDialog"
|
||||||
|
onFinished={this.onCancelled}
|
||||||
|
title={this.props.title}
|
||||||
|
>
|
||||||
|
<div className="mx_Dialog_content">
|
||||||
|
<p>
|
||||||
|
{ _t('This will allow you to reset your password and receive notifications.') }
|
||||||
|
</p>
|
||||||
|
{ emailInput }
|
||||||
|
</div>
|
||||||
|
<div className="mx_Dialog_buttons">
|
||||||
|
<input className="mx_Dialog_primary"
|
||||||
|
type="submit"
|
||||||
|
value={_t("Continue")}
|
||||||
|
onClick={this.onSubmit}
|
||||||
|
/>
|
||||||
|
<input
|
||||||
|
type="submit"
|
||||||
|
value={_t("Cancel")}
|
||||||
|
onClick={this.onCancelled}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</BaseDialog>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
});
|
294
src/components/views/dialogs/SetMxIdDialog.js
Normal file
294
src/components/views/dialogs/SetMxIdDialog.js
Normal file
|
@ -0,0 +1,294 @@
|
||||||
|
/*
|
||||||
|
Copyright 2016 OpenMarket Ltd
|
||||||
|
Copyright 2017 Vector Creations Ltd
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import q from 'q';
|
||||||
|
import React from 'react';
|
||||||
|
import sdk from '../../../index';
|
||||||
|
import MatrixClientPeg from '../../../MatrixClientPeg';
|
||||||
|
import classnames from 'classnames';
|
||||||
|
import KeyCode from '../../../KeyCode';
|
||||||
|
import { _t, _tJsx } from '../../../languageHandler';
|
||||||
|
|
||||||
|
// The amount of time to wait for further changes to the input username before
|
||||||
|
// sending a request to the server
|
||||||
|
const USERNAME_CHECK_DEBOUNCE_MS = 250;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prompt the user to set a display name.
|
||||||
|
*
|
||||||
|
* On success, `onFinished(true, newDisplayName)` is called.
|
||||||
|
*/
|
||||||
|
export default React.createClass({
|
||||||
|
displayName: 'SetMxIdDialog',
|
||||||
|
propTypes: {
|
||||||
|
onFinished: React.PropTypes.func.isRequired,
|
||||||
|
// Called when the user requests to register with a different homeserver
|
||||||
|
onDifferentServerClicked: React.PropTypes.func.isRequired,
|
||||||
|
// Called if the user wants to switch to login instead
|
||||||
|
onLoginClick: React.PropTypes.func.isRequired,
|
||||||
|
},
|
||||||
|
|
||||||
|
getInitialState: function() {
|
||||||
|
return {
|
||||||
|
// The entered username
|
||||||
|
username: '',
|
||||||
|
// Indicate ongoing work on the username
|
||||||
|
usernameBusy: false,
|
||||||
|
// Indicate error with username
|
||||||
|
usernameError: '',
|
||||||
|
// Assume the homeserver supports username checking until "M_UNRECOGNIZED"
|
||||||
|
usernameCheckSupport: true,
|
||||||
|
|
||||||
|
// Whether the auth UI is currently being used
|
||||||
|
doingUIAuth: false,
|
||||||
|
// Indicate error with auth
|
||||||
|
authError: '',
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
componentDidMount: function() {
|
||||||
|
this.refs.input_value.select();
|
||||||
|
|
||||||
|
this._matrixClient = MatrixClientPeg.get();
|
||||||
|
},
|
||||||
|
|
||||||
|
onValueChange: function(ev) {
|
||||||
|
this.setState({
|
||||||
|
username: ev.target.value,
|
||||||
|
usernameBusy: true,
|
||||||
|
usernameError: '',
|
||||||
|
}, () => {
|
||||||
|
if (!this.state.username || !this.state.usernameCheckSupport) {
|
||||||
|
this.setState({
|
||||||
|
usernameBusy: false,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Debounce the username check to limit number of requests sent
|
||||||
|
if (this._usernameCheckTimeout) {
|
||||||
|
clearTimeout(this._usernameCheckTimeout);
|
||||||
|
}
|
||||||
|
this._usernameCheckTimeout = setTimeout(() => {
|
||||||
|
this._doUsernameCheck().finally(() => {
|
||||||
|
this.setState({
|
||||||
|
usernameBusy: false,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}, USERNAME_CHECK_DEBOUNCE_MS);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
onKeyUp: function(ev) {
|
||||||
|
if (ev.keyCode === KeyCode.ENTER) {
|
||||||
|
this.onSubmit();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
onSubmit: function(ev) {
|
||||||
|
this.setState({
|
||||||
|
doingUIAuth: true,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
_doUsernameCheck: function() {
|
||||||
|
// Check if username is available
|
||||||
|
return this._matrixClient.isUsernameAvailable(this.state.username).then(
|
||||||
|
(isAvailable) => {
|
||||||
|
if (isAvailable) {
|
||||||
|
this.setState({usernameError: ''});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
(err) => {
|
||||||
|
// Indicate whether the homeserver supports username checking
|
||||||
|
const newState = {
|
||||||
|
usernameCheckSupport: err.errcode !== "M_UNRECOGNIZED",
|
||||||
|
};
|
||||||
|
console.error('Error whilst checking username availability: ', err);
|
||||||
|
switch (err.errcode) {
|
||||||
|
case "M_USER_IN_USE":
|
||||||
|
newState.usernameError = _t('Username not available');
|
||||||
|
break;
|
||||||
|
case "M_INVALID_USERNAME":
|
||||||
|
newState.usernameError = _t(
|
||||||
|
'Username invalid: %(errMessage)s',
|
||||||
|
{ errMessage: err.message},
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case "M_UNRECOGNIZED":
|
||||||
|
// This homeserver doesn't support username checking, assume it's
|
||||||
|
// fine and rely on the error appearing in registration step.
|
||||||
|
newState.usernameError = '';
|
||||||
|
break;
|
||||||
|
case undefined:
|
||||||
|
newState.usernameError = _t('Something went wrong!');
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
newState.usernameError = _t(
|
||||||
|
'An error occurred: %(error_string)s',
|
||||||
|
{ error_string: err.message },
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
this.setState(newState);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
_generatePassword: function() {
|
||||||
|
return Math.random().toString(36).slice(2);
|
||||||
|
},
|
||||||
|
|
||||||
|
_makeRegisterRequest: function(auth) {
|
||||||
|
// Not upgrading - changing mxids
|
||||||
|
const guestAccessToken = null;
|
||||||
|
if (!this._generatedPassword) {
|
||||||
|
this._generatedPassword = this._generatePassword();
|
||||||
|
}
|
||||||
|
return this._matrixClient.register(
|
||||||
|
this.state.username,
|
||||||
|
this._generatedPassword,
|
||||||
|
undefined, // session id: included in the auth dict already
|
||||||
|
auth,
|
||||||
|
{},
|
||||||
|
guestAccessToken,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
_onUIAuthFinished: function(success, response) {
|
||||||
|
this.setState({
|
||||||
|
doingUIAuth: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!success) {
|
||||||
|
this.setState({ authError: response.message });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// XXX Implement RTS /register here
|
||||||
|
const teamToken = null;
|
||||||
|
|
||||||
|
this.props.onFinished(true, {
|
||||||
|
userId: response.user_id,
|
||||||
|
deviceId: response.device_id,
|
||||||
|
homeserverUrl: this._matrixClient.getHomeserverUrl(),
|
||||||
|
identityServerUrl: this._matrixClient.getIdentityServerUrl(),
|
||||||
|
accessToken: response.access_token,
|
||||||
|
password: this._generatedPassword,
|
||||||
|
teamToken: teamToken,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
render: function() {
|
||||||
|
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
|
||||||
|
const InteractiveAuth = sdk.getComponent('structures.InteractiveAuth');
|
||||||
|
const Spinner = sdk.getComponent('elements.Spinner');
|
||||||
|
|
||||||
|
let auth;
|
||||||
|
if (this.state.doingUIAuth) {
|
||||||
|
auth = <InteractiveAuth
|
||||||
|
matrixClient={this._matrixClient}
|
||||||
|
makeRequest={this._makeRegisterRequest}
|
||||||
|
onAuthFinished={this._onUIAuthFinished}
|
||||||
|
inputs={{}}
|
||||||
|
poll={true}
|
||||||
|
/>;
|
||||||
|
}
|
||||||
|
const inputClasses = classnames({
|
||||||
|
"mx_SetMxIdDialog_input": true,
|
||||||
|
"error": Boolean(this.state.usernameError),
|
||||||
|
});
|
||||||
|
|
||||||
|
let usernameIndicator = null;
|
||||||
|
let usernameBusyIndicator = null;
|
||||||
|
if (this.state.usernameBusy) {
|
||||||
|
usernameBusyIndicator = <Spinner w="24" h="24"/>;
|
||||||
|
} else {
|
||||||
|
const usernameAvailable = this.state.username &&
|
||||||
|
this.state.usernameCheckSupport && !this.state.usernameError;
|
||||||
|
const usernameIndicatorClasses = classnames({
|
||||||
|
"error": Boolean(this.state.usernameError),
|
||||||
|
"success": usernameAvailable,
|
||||||
|
});
|
||||||
|
usernameIndicator = <div className={usernameIndicatorClasses}>
|
||||||
|
{ usernameAvailable ? _t('Username available') : this.state.usernameError }
|
||||||
|
</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
let authErrorIndicator = null;
|
||||||
|
if (this.state.authError) {
|
||||||
|
authErrorIndicator = <div className="error">
|
||||||
|
{ this.state.authError }
|
||||||
|
</div>;
|
||||||
|
}
|
||||||
|
const canContinue = this.state.username &&
|
||||||
|
!this.state.usernameError &&
|
||||||
|
!this.state.usernameBusy;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<BaseDialog className="mx_SetMxIdDialog"
|
||||||
|
onFinished={this.props.onFinished}
|
||||||
|
title="To get started, please pick a username!"
|
||||||
|
>
|
||||||
|
<div className="mx_Dialog_content">
|
||||||
|
<div className="mx_SetMxIdDialog_input_group">
|
||||||
|
<input type="text" ref="input_value" value={this.state.username}
|
||||||
|
autoFocus={true}
|
||||||
|
onChange={this.onValueChange}
|
||||||
|
onKeyUp={this.onKeyUp}
|
||||||
|
size="30"
|
||||||
|
className={inputClasses}
|
||||||
|
/>
|
||||||
|
{ usernameBusyIndicator }
|
||||||
|
</div>
|
||||||
|
{ usernameIndicator }
|
||||||
|
<p>
|
||||||
|
{ _tJsx(
|
||||||
|
'This will be your account name on the <span></span> ' +
|
||||||
|
'homeserver, or you can pick a <a>different server</a>.',
|
||||||
|
[
|
||||||
|
/<span><\/span>/,
|
||||||
|
/<a>(.*?)<\/a>/,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
(sub) => <span>{this.props.homeserverUrl}</span>,
|
||||||
|
(sub) => <a href="#" onClick={this.props.onDifferentServerClicked}>{sub}</a>,
|
||||||
|
],
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
{ _tJsx(
|
||||||
|
'If you already have a Matrix account you can <a>log in</a> instead.',
|
||||||
|
/<a>(.*?)<\/a>/,
|
||||||
|
[(sub) => <a href="#" onClick={this.props.onLoginClick}>{sub}</a>],
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
{ auth }
|
||||||
|
{ authErrorIndicator }
|
||||||
|
</div>
|
||||||
|
<div className="mx_Dialog_buttons">
|
||||||
|
<input className="mx_Dialog_primary"
|
||||||
|
type="submit"
|
||||||
|
value={_t("Continue")}
|
||||||
|
onClick={this.onSubmit}
|
||||||
|
disabled={!canContinue}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</BaseDialog>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
});
|
|
@ -16,7 +16,6 @@ limitations under the License.
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import sdk from '../../../index';
|
import sdk from '../../../index';
|
||||||
import dis from '../../../dispatcher';
|
|
||||||
import MatrixClientPeg from '../../../MatrixClientPeg';
|
import MatrixClientPeg from '../../../MatrixClientPeg';
|
||||||
import GeminiScrollbar from 'react-gemini-scrollbar';
|
import GeminiScrollbar from 'react-gemini-scrollbar';
|
||||||
import Resend from '../../../Resend';
|
import Resend from '../../../Resend';
|
||||||
|
@ -146,7 +145,7 @@ export default React.createClass({
|
||||||
console.log("UnknownDeviceDialog closed by escape");
|
console.log("UnknownDeviceDialog closed by escape");
|
||||||
this.props.onFinished();
|
this.props.onFinished();
|
||||||
}}
|
}}
|
||||||
title='Room contains unknown devices'
|
title={_t('Room contains unknown devices')}
|
||||||
>
|
>
|
||||||
<GeminiScrollbar autoshow={false} className="mx_Dialog_content">
|
<GeminiScrollbar autoshow={false} className="mx_Dialog_content">
|
||||||
<h4>
|
<h4>
|
||||||
|
@ -163,7 +162,7 @@ export default React.createClass({
|
||||||
this.props.onFinished();
|
this.props.onFinished();
|
||||||
Resend.resendUnsentEvents(this.props.room);
|
Resend.resendUnsentEvents(this.props.room);
|
||||||
}}>
|
}}>
|
||||||
Send anyway
|
{_t("Send anyway")}
|
||||||
</button>
|
</button>
|
||||||
<button className="mx_Dialog_primary" autoFocus={ true }
|
<button className="mx_Dialog_primary" autoFocus={ true }
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
|
|
84
src/components/views/elements/ActionButton.js
Normal file
84
src/components/views/elements/ActionButton.js
Normal file
|
@ -0,0 +1,84 @@
|
||||||
|
/*
|
||||||
|
Copyright 2017 Vector Creations Ltd
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import AccessibleButton from './AccessibleButton';
|
||||||
|
import dis from '../../../dispatcher';
|
||||||
|
import sdk from '../../../index';
|
||||||
|
|
||||||
|
export default React.createClass({
|
||||||
|
displayName: 'RoleButton',
|
||||||
|
|
||||||
|
propTypes: {
|
||||||
|
size: PropTypes.string,
|
||||||
|
tooltip: PropTypes.bool,
|
||||||
|
action: PropTypes.string.isRequired,
|
||||||
|
mouseOverAction: PropTypes.string,
|
||||||
|
label: PropTypes.string.isRequired,
|
||||||
|
iconPath: PropTypes.string.isRequired,
|
||||||
|
},
|
||||||
|
|
||||||
|
getDefaultProps: function() {
|
||||||
|
return {
|
||||||
|
size: "25",
|
||||||
|
tooltip: false,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
getInitialState: function() {
|
||||||
|
return {
|
||||||
|
showTooltip: false,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
_onClick: function(ev) {
|
||||||
|
ev.stopPropagation();
|
||||||
|
dis.dispatch({action: this.props.action});
|
||||||
|
},
|
||||||
|
|
||||||
|
_onMouseEnter: function() {
|
||||||
|
if (this.props.tooltip) this.setState({showTooltip: true});
|
||||||
|
if (this.props.mouseOverAction) {
|
||||||
|
dis.dispatch({action: this.props.mouseOverAction});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
_onMouseLeave: function() {
|
||||||
|
this.setState({showTooltip: false});
|
||||||
|
},
|
||||||
|
|
||||||
|
render: function() {
|
||||||
|
const TintableSvg = sdk.getComponent("elements.TintableSvg");
|
||||||
|
|
||||||
|
let tooltip;
|
||||||
|
if (this.state.showTooltip) {
|
||||||
|
const RoomTooltip = sdk.getComponent("rooms.RoomTooltip");
|
||||||
|
tooltip = <RoomTooltip className="mx_RoleButton_tooltip" label={this.props.label} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AccessibleButton className="mx_RoleButton"
|
||||||
|
onClick={this._onClick}
|
||||||
|
onMouseEnter={this._onMouseEnter}
|
||||||
|
onMouseLeave={this._onMouseLeave}
|
||||||
|
>
|
||||||
|
<TintableSvg src={this.props.iconPath} width={this.props.size} height={this.props.size} />
|
||||||
|
{tooltip}
|
||||||
|
</AccessibleButton>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
|
@ -19,9 +19,7 @@ limitations under the License.
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import sdk from "../../../index";
|
import sdk from "../../../index";
|
||||||
import Invite from "../../../Invite";
|
|
||||||
import MatrixClientPeg from "../../../MatrixClientPeg";
|
import MatrixClientPeg from "../../../MatrixClientPeg";
|
||||||
import Avatar from '../../../Avatar';
|
|
||||||
import { _t } from '../../../languageHandler';
|
import { _t } from '../../../languageHandler';
|
||||||
|
|
||||||
// React PropType definition for an object describing
|
// React PropType definition for an object describing
|
||||||
|
|
40
src/components/views/elements/CreateRoomButton.js
Normal file
40
src/components/views/elements/CreateRoomButton.js
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
/*
|
||||||
|
Copyright 2017 Vector Creations Ltd
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import sdk from '../../../index';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { _t } from '../../../languageHandler';
|
||||||
|
|
||||||
|
const CreateRoomButton = function(props) {
|
||||||
|
const ActionButton = sdk.getComponent('elements.ActionButton');
|
||||||
|
return (
|
||||||
|
<ActionButton action="view_create_room"
|
||||||
|
mouseOverAction={props.callout ? "callout_create_room" : null}
|
||||||
|
label={ _t("Create new room") }
|
||||||
|
iconPath="img/icons-create-room.svg"
|
||||||
|
size={props.size}
|
||||||
|
tooltip={props.tooltip}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
CreateRoomButton.propTypes = {
|
||||||
|
size: PropTypes.string,
|
||||||
|
tooltip: PropTypes.bool,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CreateRoomButton;
|
39
src/components/views/elements/HomeButton.js
Normal file
39
src/components/views/elements/HomeButton.js
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
/*
|
||||||
|
Copyright 2017 Vector Creations Ltd
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import sdk from '../../../index';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { _t } from '../../../languageHandler';
|
||||||
|
|
||||||
|
const HomeButton = function(props) {
|
||||||
|
const ActionButton = sdk.getComponent('elements.ActionButton');
|
||||||
|
return (
|
||||||
|
<ActionButton action="view_home_page"
|
||||||
|
label={ _t("Home") }
|
||||||
|
iconPath="img/icons-home.svg"
|
||||||
|
size={props.size}
|
||||||
|
tooltip={props.tooltip}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
HomeButton.propTypes = {
|
||||||
|
size: PropTypes.string,
|
||||||
|
tooltip: PropTypes.bool,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default HomeButton;
|
|
@ -19,7 +19,6 @@ import React from 'react';
|
||||||
|
|
||||||
import sdk from '../../../index';
|
import sdk from '../../../index';
|
||||||
import UserSettingsStore from '../../../UserSettingsStore';
|
import UserSettingsStore from '../../../UserSettingsStore';
|
||||||
import { _t } from '../../../languageHandler';
|
|
||||||
import * as languageHandler from '../../../languageHandler';
|
import * as languageHandler from '../../../languageHandler';
|
||||||
|
|
||||||
function languageMatchesSearchQuery(query, language) {
|
function languageMatchesSearchQuery(query, language) {
|
||||||
|
|
|
@ -14,6 +14,7 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import sdk from '../../../index';
|
||||||
const MemberAvatar = require('../avatars/MemberAvatar.js');
|
const MemberAvatar = require('../avatars/MemberAvatar.js');
|
||||||
import { _t } from '../../../languageHandler';
|
import { _t } from '../../../languageHandler';
|
||||||
|
|
||||||
|
@ -111,9 +112,13 @@ module.exports = React.createClass({
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const EmojiText = sdk.getComponent('elements.EmojiText');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<span className="mx_TextualEvent mx_MemberEventListSummary_summary">
|
<span className="mx_TextualEvent mx_MemberEventListSummary_summary">
|
||||||
{summaries.join(", ")}
|
<EmojiText>
|
||||||
|
{summaries.join(", ")}
|
||||||
|
</EmojiText>
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
@ -222,8 +227,7 @@ module.exports = React.createClass({
|
||||||
? _t("%(severalUsers)sjoined", { severalUsers: "" })
|
? _t("%(severalUsers)sjoined", { severalUsers: "" })
|
||||||
: _t("%(oneUser)sjoined", { oneUser: "" });
|
: _t("%(oneUser)sjoined", { oneUser: "" });
|
||||||
}
|
}
|
||||||
|
break;
|
||||||
break;
|
|
||||||
case "left":
|
case "left":
|
||||||
if (repeats > 1) {
|
if (repeats > 1) {
|
||||||
res = (plural)
|
res = (plural)
|
||||||
|
@ -233,7 +237,8 @@ module.exports = React.createClass({
|
||||||
res = (plural)
|
res = (plural)
|
||||||
? _t("%(severalUsers)sleft", { severalUsers: "" })
|
? _t("%(severalUsers)sleft", { severalUsers: "" })
|
||||||
: _t("%(oneUser)sleft", { oneUser: "" });
|
: _t("%(oneUser)sleft", { oneUser: "" });
|
||||||
} break;
|
}
|
||||||
|
break;
|
||||||
case "joined_and_left":
|
case "joined_and_left":
|
||||||
if (repeats > 1) {
|
if (repeats > 1) {
|
||||||
res = (plural)
|
res = (plural)
|
||||||
|
@ -244,7 +249,7 @@ module.exports = React.createClass({
|
||||||
? _t("%(severalUsers)sjoined and left", { severalUsers: "" })
|
? _t("%(severalUsers)sjoined and left", { severalUsers: "" })
|
||||||
: _t("%(oneUser)sjoined and left", { oneUser: "" });
|
: _t("%(oneUser)sjoined and left", { oneUser: "" });
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case "left_and_joined":
|
case "left_and_joined":
|
||||||
if (repeats > 1) {
|
if (repeats > 1) {
|
||||||
res = (plural)
|
res = (plural)
|
||||||
|
@ -254,8 +259,8 @@ module.exports = React.createClass({
|
||||||
res = (plural)
|
res = (plural)
|
||||||
? _t("%(severalUsers)sleft and rejoined", { severalUsers: "" })
|
? _t("%(severalUsers)sleft and rejoined", { severalUsers: "" })
|
||||||
: _t("%(oneUser)sleft and rejoined", { oneUser: "" });
|
: _t("%(oneUser)sleft and rejoined", { oneUser: "" });
|
||||||
} break;
|
}
|
||||||
break;
|
break;
|
||||||
case "invite_reject":
|
case "invite_reject":
|
||||||
if (repeats > 1) {
|
if (repeats > 1) {
|
||||||
res = (plural)
|
res = (plural)
|
||||||
|
@ -266,7 +271,7 @@ module.exports = React.createClass({
|
||||||
? _t("%(severalUsers)srejected their invitations", { severalUsers: "" })
|
? _t("%(severalUsers)srejected their invitations", { severalUsers: "" })
|
||||||
: _t("%(oneUser)srejected their invitation", { oneUser: "" });
|
: _t("%(oneUser)srejected their invitation", { oneUser: "" });
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case "invite_withdrawal":
|
case "invite_withdrawal":
|
||||||
if (repeats > 1) {
|
if (repeats > 1) {
|
||||||
res = (plural)
|
res = (plural)
|
||||||
|
@ -277,7 +282,7 @@ module.exports = React.createClass({
|
||||||
? _t("%(severalUsers)shad their invitations withdrawn", { severalUsers: "" })
|
? _t("%(severalUsers)shad their invitations withdrawn", { severalUsers: "" })
|
||||||
: _t("%(oneUser)shad their invitation withdrawn", { oneUser: "" });
|
: _t("%(oneUser)shad their invitation withdrawn", { oneUser: "" });
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case "invited":
|
case "invited":
|
||||||
if (repeats > 1) {
|
if (repeats > 1) {
|
||||||
res = (plural)
|
res = (plural)
|
||||||
|
@ -288,7 +293,7 @@ module.exports = React.createClass({
|
||||||
? _t("were invited")
|
? _t("were invited")
|
||||||
: _t("was invited");
|
: _t("was invited");
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case "banned":
|
case "banned":
|
||||||
if (repeats > 1) {
|
if (repeats > 1) {
|
||||||
res = (plural)
|
res = (plural)
|
||||||
|
@ -299,7 +304,7 @@ module.exports = React.createClass({
|
||||||
? _t("were banned")
|
? _t("were banned")
|
||||||
: _t("was banned");
|
: _t("was banned");
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case "unbanned":
|
case "unbanned":
|
||||||
if (repeats > 1) {
|
if (repeats > 1) {
|
||||||
res = (plural)
|
res = (plural)
|
||||||
|
@ -310,7 +315,7 @@ module.exports = React.createClass({
|
||||||
? _t("were unbanned")
|
? _t("were unbanned")
|
||||||
: _t("was unbanned");
|
: _t("was unbanned");
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case "kicked":
|
case "kicked":
|
||||||
if (repeats > 1) {
|
if (repeats > 1) {
|
||||||
res = (plural)
|
res = (plural)
|
||||||
|
@ -321,7 +326,7 @@ module.exports = React.createClass({
|
||||||
? _t("were kicked")
|
? _t("were kicked")
|
||||||
: _t("was kicked");
|
: _t("was kicked");
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case "changed_name":
|
case "changed_name":
|
||||||
if (repeats > 1) {
|
if (repeats > 1) {
|
||||||
res = (plural)
|
res = (plural)
|
||||||
|
@ -332,7 +337,7 @@ module.exports = React.createClass({
|
||||||
? _t("%(severalUsers)schanged their name", { severalUsers: "" })
|
? _t("%(severalUsers)schanged their name", { severalUsers: "" })
|
||||||
: _t("%(oneUser)schanged their name", { oneUser: "" });
|
: _t("%(oneUser)schanged their name", { oneUser: "" });
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case "changed_avatar":
|
case "changed_avatar":
|
||||||
if (repeats > 1) {
|
if (repeats > 1) {
|
||||||
res = (plural)
|
res = (plural)
|
||||||
|
@ -343,7 +348,7 @@ module.exports = React.createClass({
|
||||||
? _t("%(severalUsers)schanged their avatar", { severalUsers: "" })
|
? _t("%(severalUsers)schanged their avatar", { severalUsers: "" })
|
||||||
: _t("%(oneUser)schanged their avatar", { oneUser: "" });
|
: _t("%(oneUser)schanged their avatar", { oneUser: "" });
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
return res;
|
return res;
|
||||||
|
|
40
src/components/views/elements/RoomDirectoryButton.js
Normal file
40
src/components/views/elements/RoomDirectoryButton.js
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
/*
|
||||||
|
Copyright 2017 Vector Creations Ltd
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import sdk from '../../../index';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { _t } from '../../../languageHandler';
|
||||||
|
|
||||||
|
const RoomDirectoryButton = function(props) {
|
||||||
|
const ActionButton = sdk.getComponent('elements.ActionButton');
|
||||||
|
return (
|
||||||
|
<ActionButton action="view_room_directory"
|
||||||
|
mouseOverAction={props.callout ? "callout_room_directory" : null}
|
||||||
|
label={ _t("Room directory") }
|
||||||
|
iconPath="img/icons-directory.svg"
|
||||||
|
size={props.size}
|
||||||
|
tooltip={props.tooltip}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
RoomDirectoryButton.propTypes = {
|
||||||
|
size: PropTypes.string,
|
||||||
|
tooltip: PropTypes.bool,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default RoomDirectoryButton;
|
39
src/components/views/elements/SettingsButton.js
Normal file
39
src/components/views/elements/SettingsButton.js
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
/*
|
||||||
|
Copyright 2017 Vector Creations Ltd
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import sdk from '../../../index';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { _t } from '../../../languageHandler';
|
||||||
|
|
||||||
|
const SettingsButton = function(props) {
|
||||||
|
const ActionButton = sdk.getComponent('elements.ActionButton');
|
||||||
|
return (
|
||||||
|
<ActionButton action="view_user_settings"
|
||||||
|
label={ _t("Settings") }
|
||||||
|
iconPath="img/icons-settings.svg"
|
||||||
|
size={props.size}
|
||||||
|
tooltip={props.tooltip}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
SettingsButton.propTypes = {
|
||||||
|
size: PropTypes.string,
|
||||||
|
tooltip: PropTypes.bool,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SettingsButton;
|
40
src/components/views/elements/StartChatButton.js
Normal file
40
src/components/views/elements/StartChatButton.js
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
/*
|
||||||
|
Copyright 2017 Vector Creations Ltd
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import sdk from '../../../index';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { _t } from '../../../languageHandler';
|
||||||
|
|
||||||
|
const StartChatButton = function(props) {
|
||||||
|
const ActionButton = sdk.getComponent('elements.ActionButton');
|
||||||
|
return (
|
||||||
|
<ActionButton action="view_create_chat"
|
||||||
|
mouseOverAction={props.callout ? "callout_start_chat" : null}
|
||||||
|
label={ _t("Start chat") }
|
||||||
|
iconPath="img/icons-people.svg"
|
||||||
|
size={props.size}
|
||||||
|
tooltip={props.tooltip}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
StartChatButton.propTypes = {
|
||||||
|
size: PropTypes.string,
|
||||||
|
tooltip: PropTypes.bool,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default StartChatButton;
|
|
@ -14,6 +14,7 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
var React = require('react');
|
var React = require('react');
|
||||||
|
import { _t } from '../../../languageHandler';
|
||||||
|
|
||||||
module.exports = React.createClass({
|
module.exports = React.createClass({
|
||||||
displayName: 'TruncatedList',
|
displayName: 'TruncatedList',
|
||||||
|
@ -33,7 +34,7 @@ module.exports = React.createClass({
|
||||||
truncateAt: 2,
|
truncateAt: 2,
|
||||||
createOverflowElement: function(overflowCount, totalCount) {
|
createOverflowElement: function(overflowCount, totalCount) {
|
||||||
return (
|
return (
|
||||||
<div>And {overflowCount} more...</div>
|
<div>{_t("And %(count)s more...", {count: overflowCount})}</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -440,7 +440,7 @@ export const FallbackAuthEntry = React.createClass({
|
||||||
render: function() {
|
render: function() {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<a onClick={this._onShowFallbackClick}>Start authentication</a>
|
<a onClick={this._onShowFallbackClick}>{_t("Start authentication")}</a>
|
||||||
<div className="error">
|
<div className="error">
|
||||||
{this.props.errorText}
|
{this.props.errorText}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -16,6 +16,7 @@ limitations under the License.
|
||||||
|
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
|
import { _t } from '../../../languageHandler';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
module.exports = React.createClass({
|
module.exports = React.createClass({
|
||||||
|
@ -27,5 +28,5 @@ module.exports = React.createClass({
|
||||||
<a href="https://matrix.org">{_t("powered by Matrix")}</a>
|
<a href="https://matrix.org">{_t("powered by Matrix")}</a>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -16,7 +16,6 @@ limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import ReactDOM from 'react-dom';
|
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import sdk from '../../../index';
|
import sdk from '../../../index';
|
||||||
import { _t } from '../../../languageHandler';
|
import { _t } from '../../../languageHandler';
|
||||||
|
|
|
@ -54,11 +54,6 @@ module.exports = React.createClass({
|
||||||
})).required,
|
})).required,
|
||||||
}),
|
}),
|
||||||
|
|
||||||
// A username that will be used if no username is entered.
|
|
||||||
// Specifying this param will also warn the user that entering
|
|
||||||
// a different username will cause a fresh account to be generated.
|
|
||||||
guestUsername: React.PropTypes.string,
|
|
||||||
|
|
||||||
minPasswordLength: React.PropTypes.number,
|
minPasswordLength: React.PropTypes.number,
|
||||||
onError: React.PropTypes.func,
|
onError: React.PropTypes.func,
|
||||||
onRegisterClick: React.PropTypes.func.isRequired, // onRegisterClick(Object) => ?Promise
|
onRegisterClick: React.PropTypes.func.isRequired, // onRegisterClick(Object) => ?Promise
|
||||||
|
@ -101,7 +96,7 @@ module.exports = React.createClass({
|
||||||
if (this.refs.email.value == '') {
|
if (this.refs.email.value == '') {
|
||||||
var QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
|
var QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
|
||||||
Modal.createDialog(QuestionDialog, {
|
Modal.createDialog(QuestionDialog, {
|
||||||
title: "Warning!",
|
title: _t("Warning!"),
|
||||||
description:
|
description:
|
||||||
<div>
|
<div>
|
||||||
{_t("If you don't specify an email address, you won't be able to reset your password. " +
|
{_t("If you don't specify an email address, you won't be able to reset your password. " +
|
||||||
|
@ -110,21 +105,20 @@ module.exports = React.createClass({
|
||||||
button: _t("Continue"),
|
button: _t("Continue"),
|
||||||
onFinished: function(confirmed) {
|
onFinished: function(confirmed) {
|
||||||
if (confirmed) {
|
if (confirmed) {
|
||||||
self._doSubmit();
|
self._doSubmit(ev);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
} else {
|
||||||
else {
|
self._doSubmit(ev);
|
||||||
self._doSubmit();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
_doSubmit: function() {
|
_doSubmit: function(ev) {
|
||||||
let email = this.refs.email.value.trim();
|
let email = this.refs.email.value.trim();
|
||||||
var promise = this.props.onRegisterClick({
|
var promise = this.props.onRegisterClick({
|
||||||
username: this.refs.username.value.trim() || this.props.guestUsername,
|
username: this.refs.username.value.trim(),
|
||||||
password: this.refs.password.value.trim(),
|
password: this.refs.password.value.trim(),
|
||||||
email: email,
|
email: email,
|
||||||
phoneCountry: this.state.phoneCountry,
|
phoneCountry: this.state.phoneCountry,
|
||||||
|
@ -192,7 +186,7 @@ module.exports = React.createClass({
|
||||||
break;
|
break;
|
||||||
case FIELD_USERNAME:
|
case FIELD_USERNAME:
|
||||||
// XXX: SPEC-1
|
// XXX: SPEC-1
|
||||||
var username = this.refs.username.value.trim() || this.props.guestUsername;
|
var username = this.refs.username.value.trim();
|
||||||
if (encodeURIComponent(username) != username) {
|
if (encodeURIComponent(username) != username) {
|
||||||
this.markFieldValid(
|
this.markFieldValid(
|
||||||
field_id,
|
field_id,
|
||||||
|
@ -336,13 +330,10 @@ module.exports = React.createClass({
|
||||||
);
|
);
|
||||||
|
|
||||||
const registerButton = (
|
const registerButton = (
|
||||||
<input className="mx_Login_submit" type="submit" value="Register" />
|
<input className="mx_Login_submit" type="submit" value={_t("Register")} />
|
||||||
);
|
);
|
||||||
|
|
||||||
let placeholderUserName = _t("User name");
|
let placeholderUserName = _t("User name");
|
||||||
if (this.props.guestUsername) {
|
|
||||||
placeholderUserName += " " + _t("(default: %(userName)s)", {userName: this.props.guestUsername});
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
|
@ -355,9 +346,6 @@ module.exports = React.createClass({
|
||||||
className={this._classForField(FIELD_USERNAME, 'mx_Login_field')}
|
className={this._classForField(FIELD_USERNAME, 'mx_Login_field')}
|
||||||
onBlur={function() {self.validateField(FIELD_USERNAME);}} />
|
onBlur={function() {self.validateField(FIELD_USERNAME);}} />
|
||||||
<br />
|
<br />
|
||||||
{ this.props.guestUsername ?
|
|
||||||
<div className="mx_Login_fieldLabel">{_t("Setting a user name will create a fresh account")}</div> : null
|
|
||||||
}
|
|
||||||
<input type="password" ref="password"
|
<input type="password" ref="password"
|
||||||
className={this._classForField(FIELD_PASSWORD, 'mx_Login_field')}
|
className={this._classForField(FIELD_PASSWORD, 'mx_Login_field')}
|
||||||
onBlur={function() {self.validateField(FIELD_PASSWORD);}}
|
onBlur={function() {self.validateField(FIELD_PASSWORD);}}
|
||||||
|
|
|
@ -20,7 +20,6 @@ import React from 'react';
|
||||||
import MFileBody from './MFileBody';
|
import MFileBody from './MFileBody';
|
||||||
|
|
||||||
import MatrixClientPeg from '../../../MatrixClientPeg';
|
import MatrixClientPeg from '../../../MatrixClientPeg';
|
||||||
import sdk from '../../../index';
|
|
||||||
import { decryptFile, readBlobAsDataUri } from '../../../utils/DecryptFile';
|
import { decryptFile, readBlobAsDataUri } from '../../../utils/DecryptFile';
|
||||||
import { _t } from '../../../languageHandler';
|
import { _t } from '../../../languageHandler';
|
||||||
|
|
||||||
|
|
|
@ -24,7 +24,6 @@ import { _t } from '../../../languageHandler';
|
||||||
import {decryptFile} from '../../../utils/DecryptFile';
|
import {decryptFile} from '../../../utils/DecryptFile';
|
||||||
import Tinter from '../../../Tinter';
|
import Tinter from '../../../Tinter';
|
||||||
import request from 'browser-request';
|
import request from 'browser-request';
|
||||||
import q from 'q';
|
|
||||||
import Modal from '../../../Modal';
|
import Modal from '../../../Modal';
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -19,8 +19,6 @@ limitations under the License.
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import MFileBody from './MFileBody';
|
import MFileBody from './MFileBody';
|
||||||
import MatrixClientPeg from '../../../MatrixClientPeg';
|
import MatrixClientPeg from '../../../MatrixClientPeg';
|
||||||
import Model from '../../../Modal';
|
|
||||||
import sdk from '../../../index';
|
|
||||||
import { decryptFile, readBlobAsDataUri } from '../../../utils/DecryptFile';
|
import { decryptFile, readBlobAsDataUri } from '../../../utils/DecryptFile';
|
||||||
import q from 'q';
|
import q from 'q';
|
||||||
import UserSettingsStore from '../../../UserSettingsStore';
|
import UserSettingsStore from '../../../UserSettingsStore';
|
||||||
|
|
|
@ -62,8 +62,8 @@ module.exports = React.createClass({
|
||||||
var url = ContentRepo.getHttpUriForMxc(
|
var url = ContentRepo.getHttpUriForMxc(
|
||||||
MatrixClientPeg.get().getHomeserverUrl(),
|
MatrixClientPeg.get().getHomeserverUrl(),
|
||||||
ev.getContent().url,
|
ev.getContent().url,
|
||||||
14 * window.devicePixelRatio,
|
Math.ceil(14 * window.devicePixelRatio),
|
||||||
14 * window.devicePixelRatio,
|
Math.ceil(14 * window.devicePixelRatio),
|
||||||
'crop'
|
'crop'
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -30,7 +30,7 @@ export default function SenderProfile(props) {
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<EmojiText className="mx_SenderProfile"
|
<EmojiText className="mx_SenderProfile" dir="auto"
|
||||||
onClick={props.onClick}>{`${name || ''} ${props.aux || ''}`}</EmojiText>
|
onClick={props.onClick}>{`${name || ''} ${props.aux || ''}`}</EmojiText>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -63,6 +63,19 @@ module.exports = React.createClass({
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
|
copyToClipboard: function(text) {
|
||||||
|
const textArea = document.createElement("textarea");
|
||||||
|
textArea.value = text;
|
||||||
|
document.body.appendChild(textArea);
|
||||||
|
textArea.select();
|
||||||
|
try {
|
||||||
|
const successful = document.execCommand('copy');
|
||||||
|
} catch (err) {
|
||||||
|
console.log('Unable to copy');
|
||||||
|
}
|
||||||
|
document.body.removeChild(textArea);
|
||||||
|
},
|
||||||
|
|
||||||
componentDidMount: function() {
|
componentDidMount: function() {
|
||||||
this._unmounted = false;
|
this._unmounted = false;
|
||||||
|
|
||||||
|
@ -81,6 +94,14 @@ module.exports = React.createClass({
|
||||||
}
|
}
|
||||||
}, 10);
|
}, 10);
|
||||||
}
|
}
|
||||||
|
// add event handlers to the 'copy code' buttons
|
||||||
|
const buttons = ReactDOM.findDOMNode(this).getElementsByClassName("mx_EventTile_copyButton");
|
||||||
|
for (let i = 0; i < buttons.length; i++) {
|
||||||
|
buttons[i].onclick = (e) => {
|
||||||
|
const copyCode = buttons[i].parentNode.getElementsByTagName("code")[0];
|
||||||
|
this.copyToClipboard(copyCode.textContent);
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -21,6 +21,8 @@ var Tinter = require('../../../Tinter');
|
||||||
var MatrixClientPeg = require("../../../MatrixClientPeg");
|
var MatrixClientPeg = require("../../../MatrixClientPeg");
|
||||||
var Modal = require("../../../Modal");
|
var Modal = require("../../../Modal");
|
||||||
|
|
||||||
|
import dis from '../../../dispatcher';
|
||||||
|
|
||||||
var ROOM_COLORS = [
|
var ROOM_COLORS = [
|
||||||
// magic room default values courtesy of Ribot
|
// magic room default values courtesy of Ribot
|
||||||
["#76cfa6", "#eaf5f0"],
|
["#76cfa6", "#eaf5f0"],
|
||||||
|
@ -86,11 +88,7 @@ module.exports = React.createClass({
|
||||||
}
|
}
|
||||||
).catch(function(err) {
|
).catch(function(err) {
|
||||||
if (err.errcode == 'M_GUEST_ACCESS_FORBIDDEN') {
|
if (err.errcode == 'M_GUEST_ACCESS_FORBIDDEN') {
|
||||||
var NeedToRegisterDialog = sdk.getComponent("dialogs.NeedToRegisterDialog");
|
dis.dispatch({action: 'view_set_mxid'});
|
||||||
Modal.createDialog(NeedToRegisterDialog, {
|
|
||||||
title: "Please Register",
|
|
||||||
description: "Saving room color settings is only available to registered users"
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@ import classNames from 'classnames';
|
||||||
import flatMap from 'lodash/flatMap';
|
import flatMap from 'lodash/flatMap';
|
||||||
import isEqual from 'lodash/isEqual';
|
import isEqual from 'lodash/isEqual';
|
||||||
import sdk from '../../../index';
|
import sdk from '../../../index';
|
||||||
import type {Completion, SelectionRange} from '../../../autocomplete/Autocompleter';
|
import type {Completion} from '../../../autocomplete/Autocompleter';
|
||||||
import Q from 'q';
|
import Q from 'q';
|
||||||
|
|
||||||
import {getCompletions} from '../../../autocomplete/Autocompleter';
|
import {getCompletions} from '../../../autocomplete/Autocompleter';
|
||||||
|
|
|
@ -19,7 +19,7 @@ import MatrixClientPeg from "../../../MatrixClientPeg";
|
||||||
import sdk from '../../../index';
|
import sdk from '../../../index';
|
||||||
import dis from "../../../dispatcher";
|
import dis from "../../../dispatcher";
|
||||||
import ObjectUtils from '../../../ObjectUtils';
|
import ObjectUtils from '../../../ObjectUtils';
|
||||||
import { _t } from '../../../languageHandler';
|
import { _t, _tJsx} from '../../../languageHandler';
|
||||||
|
|
||||||
|
|
||||||
module.exports = React.createClass({
|
module.exports = React.createClass({
|
||||||
|
@ -78,7 +78,7 @@ module.exports = React.createClass({
|
||||||
fileDropTarget = (
|
fileDropTarget = (
|
||||||
<div className="mx_RoomView_fileDropTarget">
|
<div className="mx_RoomView_fileDropTarget">
|
||||||
<div className="mx_RoomView_fileDropTargetLabel"
|
<div className="mx_RoomView_fileDropTargetLabel"
|
||||||
title="Drop File Here">
|
title={_t("Drop File Here")}>
|
||||||
<TintableSvg src="img/upload-big.svg" width="45" height="59"/>
|
<TintableSvg src="img/upload-big.svg" width="45" height="59"/>
|
||||||
<br/>
|
<br/>
|
||||||
{_t("Drop file here to upload")}
|
{_t("Drop file here to upload")}
|
||||||
|
@ -89,21 +89,31 @@ module.exports = React.createClass({
|
||||||
|
|
||||||
var conferenceCallNotification = null;
|
var conferenceCallNotification = null;
|
||||||
if (this.props.displayConfCallNotification) {
|
if (this.props.displayConfCallNotification) {
|
||||||
var supportedText, joinText;
|
let supportedText = '';
|
||||||
|
let joinNode;
|
||||||
if (!MatrixClientPeg.get().supportsVoip()) {
|
if (!MatrixClientPeg.get().supportsVoip()) {
|
||||||
supportedText = _t(" (unsupported)");
|
supportedText = _t(" (unsupported)");
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
joinText = (<span>
|
joinNode = (<span>
|
||||||
Join as <a onClick={(event)=>{ this.onConferenceNotificationClick(event, 'voice');}}
|
{_tJsx(
|
||||||
href="#">voice</a> or <a onClick={(event)=>{ this.onConferenceNotificationClick(event, 'video'); }}
|
"Join as <voiceText>voice</voiceText> or <videoText>video</videoText>.",
|
||||||
href="#">video</a>.
|
[/<voiceText>(.*?)<\/voiceText>/, /<videoText>(.*?)<\/videoText>/],
|
||||||
|
[
|
||||||
|
(sub) => <a onClick={(event)=>{ this.onConferenceNotificationClick(event, 'voice');}} href="#">{sub}</a>,
|
||||||
|
(sub) => <a onClick={(event)=>{ this.onConferenceNotificationClick(event, 'video');}} href="#">{sub}</a>,
|
||||||
|
]
|
||||||
|
)}
|
||||||
</span>);
|
</span>);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
// XXX: the translation here isn't great: appending ' (unsupported)' is likely to not make sense in many languages,
|
||||||
|
// but there are translations for this in the languages we do have so I'm leaving it for now.
|
||||||
conferenceCallNotification = (
|
conferenceCallNotification = (
|
||||||
<div className="mx_RoomView_ongoingConfCallNotification">
|
<div className="mx_RoomView_ongoingConfCallNotification">
|
||||||
{_t("Ongoing conference call%(supportedText)s. %(joinText)s", {supportedText: supportedText, joinText: joinText})}
|
{_t("Ongoing conference call%(supportedText)s.", {supportedText: supportedText})}
|
||||||
|
|
||||||
|
{joinNode}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,6 +21,7 @@ var React = require('react');
|
||||||
var MatrixClientPeg = require('../../../MatrixClientPeg');
|
var MatrixClientPeg = require('../../../MatrixClientPeg');
|
||||||
var sdk = require('../../../index');
|
var sdk = require('../../../index');
|
||||||
import AccessibleButton from '../elements/AccessibleButton';
|
import AccessibleButton from '../elements/AccessibleButton';
|
||||||
|
import { _t } from '../../../languageHandler';
|
||||||
|
|
||||||
|
|
||||||
var PRESENCE_CLASS = {
|
var PRESENCE_CLASS = {
|
||||||
|
@ -115,7 +116,7 @@ module.exports = React.createClass({
|
||||||
nameEl = (
|
nameEl = (
|
||||||
<div className="mx_EntityTile_details">
|
<div className="mx_EntityTile_details">
|
||||||
<img className="mx_EntityTile_chevron" src="img/member_chevron.png" width="8" height="12"/>
|
<img className="mx_EntityTile_chevron" src="img/member_chevron.png" width="8" height="12"/>
|
||||||
<EmojiText element="div" className="mx_EntityTile_name_hover">{name}</EmojiText>
|
<EmojiText element="div" className="mx_EntityTile_name_hover" dir="auto">{name}</EmojiText>
|
||||||
<PresenceLabel activeAgo={ activeAgo }
|
<PresenceLabel activeAgo={ activeAgo }
|
||||||
currentlyActive={this.props.presenceCurrentlyActive}
|
currentlyActive={this.props.presenceCurrentlyActive}
|
||||||
presenceState={this.props.presenceState} />
|
presenceState={this.props.presenceState} />
|
||||||
|
@ -124,7 +125,7 @@ module.exports = React.createClass({
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
nameEl = (
|
nameEl = (
|
||||||
<EmojiText element="div" className="mx_EntityTile_name">{name}</EmojiText>
|
<EmojiText element="div" className="mx_EntityTile_name" dir="auto">{name}</EmojiText>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -140,10 +141,10 @@ module.exports = React.createClass({
|
||||||
var power;
|
var power;
|
||||||
var powerLevel = this.props.powerLevel;
|
var powerLevel = this.props.powerLevel;
|
||||||
if (powerLevel >= 50 && powerLevel < 99) {
|
if (powerLevel >= 50 && powerLevel < 99) {
|
||||||
power = <img src="img/mod.svg" className="mx_EntityTile_power" width="16" height="17" alt="Mod"/>;
|
power = <img src="img/mod.svg" className="mx_EntityTile_power" width="16" height="17" alt={_t("Moderator")}/>;
|
||||||
}
|
}
|
||||||
if (powerLevel >= 99) {
|
if (powerLevel >= 99) {
|
||||||
power = <img src="img/admin.svg" className="mx_EntityTile_power" width="16" height="17" alt="Admin"/>;
|
power = <img src="img/admin.svg" className="mx_EntityTile_power" width="16" height="17" alt={_t("Admin")}/>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -381,6 +381,7 @@ module.exports = WithMatrixClient(React.createClass({
|
||||||
dis.dispatch({
|
dis.dispatch({
|
||||||
action: 'view_room',
|
action: 'view_room',
|
||||||
event_id: this.props.mxEvent.getId(),
|
event_id: this.props.mxEvent.getId(),
|
||||||
|
highlighted: true,
|
||||||
room_id: this.props.mxEvent.getRoomId(),
|
room_id: this.props.mxEvent.getRoomId(),
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
@ -487,22 +488,22 @@ module.exports = WithMatrixClient(React.createClass({
|
||||||
let e2e;
|
let e2e;
|
||||||
// cosmetic padlocks:
|
// cosmetic padlocks:
|
||||||
if ((e2eEnabled && this.props.eventSendStatus) || this.props.mxEvent.getType() === 'm.room.encryption') {
|
if ((e2eEnabled && this.props.eventSendStatus) || this.props.mxEvent.getType() === 'm.room.encryption') {
|
||||||
e2e = <img style={{ cursor: 'initial', marginLeft: '-1px' }} className="mx_EventTile_e2eIcon" alt="Encrypted by verified device" src="img/e2e-verified.svg" width="10" height="12" />;
|
e2e = <img style={{ cursor: 'initial', marginLeft: '-1px' }} className="mx_EventTile_e2eIcon" alt={_t("Encrypted by a verified device")} src="img/e2e-verified.svg" width="10" height="12" />;
|
||||||
}
|
}
|
||||||
// real padlocks
|
// real padlocks
|
||||||
else if (this.props.mxEvent.isEncrypted() || (e2eEnabled && this.props.eventSendStatus)) {
|
else if (this.props.mxEvent.isEncrypted() || (e2eEnabled && this.props.eventSendStatus)) {
|
||||||
if (this.props.mxEvent.getContent().msgtype === 'm.bad.encrypted') {
|
if (this.props.mxEvent.getContent().msgtype === 'm.bad.encrypted') {
|
||||||
e2e = <img onClick={ this.onCryptoClicked } className="mx_EventTile_e2eIcon" alt="Undecryptable" src="img/e2e-blocked.svg" width="12" height="12" style={{ marginLeft: "-1px" }} />;
|
e2e = <img onClick={ this.onCryptoClicked } className="mx_EventTile_e2eIcon" alt={_t("Undecryptable")} src="img/e2e-blocked.svg" width="12" height="12" style={{ marginLeft: "-1px" }} />;
|
||||||
}
|
}
|
||||||
else if (this.state.verified == true || (e2eEnabled && this.props.eventSendStatus)) {
|
else if (this.state.verified == true || (e2eEnabled && this.props.eventSendStatus)) {
|
||||||
e2e = <img onClick={ this.onCryptoClicked } className="mx_EventTile_e2eIcon" alt="Encrypted by verified device" src="img/e2e-verified.svg" width="10" height="12"/>;
|
e2e = <img onClick={ this.onCryptoClicked } className="mx_EventTile_e2eIcon" alt={_t("Encrypted by a verified device")} src="img/e2e-verified.svg" width="10" height="12"/>;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
e2e = <img onClick={ this.onCryptoClicked } className="mx_EventTile_e2eIcon" alt="Encrypted by unverified device" src="img/e2e-warning.svg" width="15" height="12" style={{ marginLeft: "-2px" }}/>;
|
e2e = <img onClick={ this.onCryptoClicked } className="mx_EventTile_e2eIcon" alt={_t("Encrypted by an unverified device")} src="img/e2e-warning.svg" width="15" height="12" style={{ marginLeft: "-2px" }}/>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (e2eEnabled) {
|
else if (e2eEnabled) {
|
||||||
e2e = <img onClick={ this.onCryptoClicked } className="mx_EventTile_e2eIcon" alt="Unencrypted message" src="img/e2e-unencrypted.svg" width="12" height="12"/>;
|
e2e = <img onClick={ this.onCryptoClicked } className="mx_EventTile_e2eIcon" alt={_t("Unencrypted message")} src="img/e2e-unencrypted.svg" width="12" height="12"/>;
|
||||||
}
|
}
|
||||||
const timestamp = this.props.mxEvent.getTs() ?
|
const timestamp = this.props.mxEvent.getTs() ?
|
||||||
<MessageTimestamp showTwelveHour={this.props.isTwelveHour} ts={this.props.mxEvent.getTs()} /> : null;
|
<MessageTimestamp showTwelveHour={this.props.isTwelveHour} ts={this.props.mxEvent.getTs()} /> : null;
|
||||||
|
|
|
@ -26,19 +26,19 @@ export default class MemberDeviceInfo extends React.Component {
|
||||||
if (this.props.device.isBlocked()) {
|
if (this.props.device.isBlocked()) {
|
||||||
indicator = (
|
indicator = (
|
||||||
<div className="mx_MemberDeviceInfo_blacklisted">
|
<div className="mx_MemberDeviceInfo_blacklisted">
|
||||||
<img src="img/e2e-blocked.svg" width="12" height="12" style={{ marginLeft: "-1px" }} alt="Blacklisted"/>
|
<img src="img/e2e-blocked.svg" width="12" height="12" style={{ marginLeft: "-1px" }} alt={_t("Blacklisted")}/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
} else if (this.props.device.isVerified()) {
|
} else if (this.props.device.isVerified()) {
|
||||||
indicator = (
|
indicator = (
|
||||||
<div className="mx_MemberDeviceInfo_verified">
|
<div className="mx_MemberDeviceInfo_verified">
|
||||||
<img src="img/e2e-verified.svg" width="10" height="12" alt="Verified"/>
|
<img src="img/e2e-verified.svg" width="10" height="12" alt={_t("Verified")}/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
indicator = (
|
indicator = (
|
||||||
<div className="mx_MemberDeviceInfo_unverified">
|
<div className="mx_MemberDeviceInfo_unverified">
|
||||||
<img src="img/e2e-warning.svg" width="15" height="12" style={{ marginLeft: "-2px" }} alt="Unverified"/>
|
<img src="img/e2e-warning.svg" width="15" height="12" style={{ marginLeft: "-2px" }} alt={_t("Unverified")}/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,6 +38,8 @@ import Unread from '../../../Unread';
|
||||||
import { findReadReceiptFromUserId } from '../../../utils/Receipt';
|
import { findReadReceiptFromUserId } from '../../../utils/Receipt';
|
||||||
import WithMatrixClient from '../../../wrappers/WithMatrixClient';
|
import WithMatrixClient from '../../../wrappers/WithMatrixClient';
|
||||||
import AccessibleButton from '../elements/AccessibleButton';
|
import AccessibleButton from '../elements/AccessibleButton';
|
||||||
|
import GeminiScrollbar from 'react-gemini-scrollbar';
|
||||||
|
|
||||||
|
|
||||||
module.exports = WithMatrixClient(React.createClass({
|
module.exports = WithMatrixClient(React.createClass({
|
||||||
displayName: 'MemberInfo',
|
displayName: 'MemberInfo',
|
||||||
|
@ -375,11 +377,7 @@ module.exports = WithMatrixClient(React.createClass({
|
||||||
console.log("Mod toggle success");
|
console.log("Mod toggle success");
|
||||||
}, function(err) {
|
}, function(err) {
|
||||||
if (err.errcode == 'M_GUEST_ACCESS_FORBIDDEN') {
|
if (err.errcode == 'M_GUEST_ACCESS_FORBIDDEN') {
|
||||||
var NeedToRegisterDialog = sdk.getComponent("dialogs.NeedToRegisterDialog");
|
dis.dispatch({action: 'view_set_mxid'});
|
||||||
Modal.createDialog(NeedToRegisterDialog, {
|
|
||||||
title: _t("Please Register"),
|
|
||||||
description: _t("This action cannot be performed by a guest user. Please register to be able to do this") + ".",
|
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
console.error("Toggle moderator error:" + err);
|
console.error("Toggle moderator error:" + err);
|
||||||
Modal.createDialog(ErrorDialog, {
|
Modal.createDialog(ErrorDialog, {
|
||||||
|
@ -436,7 +434,7 @@ module.exports = WithMatrixClient(React.createClass({
|
||||||
title: _t("Warning!"),
|
title: _t("Warning!"),
|
||||||
description:
|
description:
|
||||||
<div>
|
<div>
|
||||||
{ _t("You will not be able to undo this change as you are promoting the user to have the same power level as yourself") }.<br/>
|
{ _t("You will not be able to undo this change as you are promoting the user to have the same power level as yourself.") }<br/>
|
||||||
{ _t("Are you sure?") }
|
{ _t("Are you sure?") }
|
||||||
</div>,
|
</div>,
|
||||||
button: _t("Continue"),
|
button: _t("Continue"),
|
||||||
|
@ -705,7 +703,7 @@ module.exports = WithMatrixClient(React.createClass({
|
||||||
if (kickButton || banButton || muteButton || giveModButton) {
|
if (kickButton || banButton || muteButton || giveModButton) {
|
||||||
adminTools =
|
adminTools =
|
||||||
<div>
|
<div>
|
||||||
<h3>Admin tools</h3>
|
<h3>{_t("Admin tools")}</h3>
|
||||||
|
|
||||||
<div className="mx_MemberInfo_buttons">
|
<div className="mx_MemberInfo_buttons">
|
||||||
{muteButton}
|
{muteButton}
|
||||||
|
@ -731,34 +729,36 @@ module.exports = WithMatrixClient(React.createClass({
|
||||||
const EmojiText = sdk.getComponent('elements.EmojiText');
|
const EmojiText = sdk.getComponent('elements.EmojiText');
|
||||||
return (
|
return (
|
||||||
<div className="mx_MemberInfo">
|
<div className="mx_MemberInfo">
|
||||||
<AccessibleButton className="mx_MemberInfo_cancel" onClick={this.onCancel}> <img src="img/cancel.svg" width="18" height="18"/></AccessibleButton>
|
<GeminiScrollbar autoshow={true}>
|
||||||
<div className="mx_MemberInfo_avatar">
|
<AccessibleButton className="mx_MemberInfo_cancel" onClick={this.onCancel}> <img src="img/cancel.svg" width="18" height="18"/></AccessibleButton>
|
||||||
<MemberAvatar onClick={this.onMemberAvatarClick} member={this.props.member} width={48} height={48} />
|
<div className="mx_MemberInfo_avatar">
|
||||||
</div>
|
<MemberAvatar onClick={this.onMemberAvatarClick} member={this.props.member} width={48} height={48} />
|
||||||
|
|
||||||
<EmojiText element="h2">{memberName}</EmojiText>
|
|
||||||
|
|
||||||
<div className="mx_MemberInfo_profile">
|
|
||||||
<div className="mx_MemberInfo_profileField">
|
|
||||||
{ this.props.member.userId }
|
|
||||||
</div>
|
</div>
|
||||||
<div className="mx_MemberInfo_profileField">
|
|
||||||
{ _t("Level") }: <b><PowerSelector controlled={true} value={ parseInt(this.props.member.powerLevel) } disabled={ !this.state.can.modifyLevel } onChange={ this.onPowerChange }/></b>
|
<EmojiText element="h2">{memberName}</EmojiText>
|
||||||
|
|
||||||
|
<div className="mx_MemberInfo_profile">
|
||||||
|
<div className="mx_MemberInfo_profileField">
|
||||||
|
{ this.props.member.userId }
|
||||||
|
</div>
|
||||||
|
<div className="mx_MemberInfo_profileField">
|
||||||
|
{ _t("Level:") } <b><PowerSelector controlled={true} value={ parseInt(this.props.member.powerLevel) } disabled={ !this.state.can.modifyLevel } onChange={ this.onPowerChange }/></b>
|
||||||
|
</div>
|
||||||
|
<div className="mx_MemberInfo_profileField">
|
||||||
|
<PresenceLabel activeAgo={ presenceLastActiveAgo }
|
||||||
|
currentlyActive={ presenceCurrentlyActive }
|
||||||
|
presenceState={ presenceState } />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="mx_MemberInfo_profileField">
|
|
||||||
<PresenceLabel activeAgo={ presenceLastActiveAgo }
|
|
||||||
currentlyActive={ presenceCurrentlyActive }
|
|
||||||
presenceState={ presenceState } />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{ adminTools }
|
{ adminTools }
|
||||||
|
|
||||||
{ startChat }
|
{ startChat }
|
||||||
|
|
||||||
{ this._renderDevices() }
|
{ this._renderDevices() }
|
||||||
|
|
||||||
{ spinner }
|
{ spinner }
|
||||||
|
</GeminiScrollbar>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,6 +22,7 @@ var MatrixClientPeg = require('../../../MatrixClientPeg');
|
||||||
var sdk = require('../../../index');
|
var sdk = require('../../../index');
|
||||||
var dis = require('../../../dispatcher');
|
var dis = require('../../../dispatcher');
|
||||||
var Modal = require("../../../Modal");
|
var Modal = require("../../../Modal");
|
||||||
|
import { _t } from '../../../languageHandler';
|
||||||
|
|
||||||
module.exports = React.createClass({
|
module.exports = React.createClass({
|
||||||
displayName: 'MemberTile',
|
displayName: 'MemberTile',
|
||||||
|
@ -63,7 +64,7 @@ module.exports = React.createClass({
|
||||||
},
|
},
|
||||||
|
|
||||||
getPowerLabel: function() {
|
getPowerLabel: function() {
|
||||||
return this.props.member.userId + " (power " + this.props.member.powerLevel + ")";
|
return _t("%(userName)s (power %(powerLevelNumber)s)", {userName: this.props.member.userId, powerLevelNumber: this.props.member.powerLevel});
|
||||||
},
|
},
|
||||||
|
|
||||||
render: function() {
|
render: function() {
|
||||||
|
|
|
@ -91,11 +91,7 @@ export default class MessageComposer extends React.Component {
|
||||||
|
|
||||||
onUploadClick(ev) {
|
onUploadClick(ev) {
|
||||||
if (MatrixClientPeg.get().isGuest()) {
|
if (MatrixClientPeg.get().isGuest()) {
|
||||||
let NeedToRegisterDialog = sdk.getComponent("dialogs.NeedToRegisterDialog");
|
dis.dispatch({action: 'view_set_mxid'});
|
||||||
Modal.createDialog(NeedToRegisterDialog, {
|
|
||||||
title: _t('Please Register'),
|
|
||||||
description: _t('Guest users can\'t upload files. Please register to upload') + '.',
|
|
||||||
});
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -113,7 +109,7 @@ export default class MessageComposer extends React.Component {
|
||||||
let fileList = [];
|
let fileList = [];
|
||||||
for (let i=0; i<files.length; i++) {
|
for (let i=0; i<files.length; i++) {
|
||||||
fileList.push(<li key={i}>
|
fileList.push(<li key={i}>
|
||||||
<TintableSvg key={i} src="img/files.svg" width="16" height="16" /> {files[i].name || 'Attachment'}
|
<TintableSvg key={i} src="img/files.svg" width="16" height="16" /> {files[i].name || _t('Attachment')}
|
||||||
</li>);
|
</li>);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -291,7 +287,7 @@ export default class MessageComposer extends React.Component {
|
||||||
|
|
||||||
const formattingButton = (
|
const formattingButton = (
|
||||||
<img className="mx_MessageComposer_formatting"
|
<img className="mx_MessageComposer_formatting"
|
||||||
title="Show Text Formatting Toolbar"
|
title={_t("Show Text Formatting Toolbar")}
|
||||||
src="img/button-text-formatting.svg"
|
src="img/button-text-formatting.svg"
|
||||||
onClick={this.onToggleFormattingClicked}
|
onClick={this.onToggleFormattingClicked}
|
||||||
style={{visibility: this.state.showFormatting ||
|
style={{visibility: this.state.showFormatting ||
|
||||||
|
|
|
@ -28,12 +28,12 @@ import Q from 'q';
|
||||||
import MatrixClientPeg from '../../../MatrixClientPeg';
|
import MatrixClientPeg from '../../../MatrixClientPeg';
|
||||||
import type {MatrixClient} from 'matrix-js-sdk/lib/matrix';
|
import type {MatrixClient} from 'matrix-js-sdk/lib/matrix';
|
||||||
import SlashCommands from '../../../SlashCommands';
|
import SlashCommands from '../../../SlashCommands';
|
||||||
|
import KeyCode from '../../../KeyCode';
|
||||||
import Modal from '../../../Modal';
|
import Modal from '../../../Modal';
|
||||||
import sdk from '../../../index';
|
import sdk from '../../../index';
|
||||||
import { _t } from '../../../languageHandler';
|
import { _t } from '../../../languageHandler';
|
||||||
|
|
||||||
import dis from '../../../dispatcher';
|
import dis from '../../../dispatcher';
|
||||||
import KeyCode from '../../../KeyCode';
|
|
||||||
import UserSettingsStore from '../../../UserSettingsStore';
|
import UserSettingsStore from '../../../UserSettingsStore';
|
||||||
|
|
||||||
import * as RichText from '../../../RichText';
|
import * as RichText from '../../../RichText';
|
||||||
|
@ -45,8 +45,6 @@ import {onSendMessageFailed} from './MessageComposerInputOld';
|
||||||
|
|
||||||
const TYPING_USER_TIMEOUT = 10000, TYPING_SERVER_TIMEOUT = 30000;
|
const TYPING_USER_TIMEOUT = 10000, TYPING_SERVER_TIMEOUT = 30000;
|
||||||
|
|
||||||
const KEY_M = 77;
|
|
||||||
|
|
||||||
const ZWS_CODE = 8203;
|
const ZWS_CODE = 8203;
|
||||||
const ZWS = String.fromCharCode(ZWS_CODE); // zero width space
|
const ZWS = String.fromCharCode(ZWS_CODE); // zero width space
|
||||||
function stateToMarkdown(state) {
|
function stateToMarkdown(state) {
|
||||||
|
@ -62,7 +60,7 @@ function stateToMarkdown(state) {
|
||||||
export default class MessageComposerInput extends React.Component {
|
export default class MessageComposerInput extends React.Component {
|
||||||
static getKeyBinding(e: SyntheticKeyboardEvent): string {
|
static getKeyBinding(e: SyntheticKeyboardEvent): string {
|
||||||
// C-m => Toggles between rich text and markdown modes
|
// C-m => Toggles between rich text and markdown modes
|
||||||
if (e.keyCode === KEY_M && KeyBindingUtil.isCtrlKeyCommand(e)) {
|
if (e.keyCode === KeyCode.KEY_M && KeyBindingUtil.isCtrlKeyCommand(e)) {
|
||||||
return 'toggle-mode';
|
return 'toggle-mode';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -723,6 +721,7 @@ export default class MessageComposerInput extends React.Component {
|
||||||
title={ this.state.isRichtextEnabled ? _t("Markdown is disabled") : _t("Markdown is enabled")}
|
title={ this.state.isRichtextEnabled ? _t("Markdown is disabled") : _t("Markdown is enabled")}
|
||||||
src={`img/button-md-${!this.state.isRichtextEnabled}.png`} />
|
src={`img/button-md-${!this.state.isRichtextEnabled}.png`} />
|
||||||
<Editor ref="editor"
|
<Editor ref="editor"
|
||||||
|
dir="auto"
|
||||||
placeholder={this.props.placeholder}
|
placeholder={this.props.placeholder}
|
||||||
editorState={this.state.editorState}
|
editorState={this.state.editorState}
|
||||||
onChange={this.onEditorContentChanged}
|
onChange={this.onEditorContentChanged}
|
||||||
|
|
|
@ -29,7 +29,6 @@ var Markdown = require("../../../Markdown");
|
||||||
|
|
||||||
var TYPING_USER_TIMEOUT = 10000;
|
var TYPING_USER_TIMEOUT = 10000;
|
||||||
var TYPING_SERVER_TIMEOUT = 30000;
|
var TYPING_SERVER_TIMEOUT = 30000;
|
||||||
var MARKDOWN_ENABLED = true;
|
|
||||||
|
|
||||||
export function onSendMessageFailed(err, room) {
|
export function onSendMessageFailed(err, room) {
|
||||||
// XXX: temporary logging to try to diagnose
|
// XXX: temporary logging to try to diagnose
|
||||||
|
@ -77,7 +76,8 @@ export default React.createClass({
|
||||||
|
|
||||||
componentWillMount: function() {
|
componentWillMount: function() {
|
||||||
this.oldScrollHeight = 0;
|
this.oldScrollHeight = 0;
|
||||||
this.markdownEnabled = MARKDOWN_ENABLED;
|
this.markdownEnabled = !UserSettingsStore.getSyncedSetting('disableMarkdown', false);
|
||||||
|
|
||||||
var self = this;
|
var self = this;
|
||||||
this.sentHistory = {
|
this.sentHistory = {
|
||||||
// The list of typed messages. Index 0 is more recent
|
// The list of typed messages. Index 0 is more recent
|
||||||
|
@ -461,7 +461,7 @@ export default React.createClass({
|
||||||
render: function() {
|
render: function() {
|
||||||
return (
|
return (
|
||||||
<div className="mx_MessageComposer_input" onClick={ this.onInputClick }>
|
<div className="mx_MessageComposer_input" onClick={ this.onInputClick }>
|
||||||
<textarea autoFocus ref="textarea" rows="1" onKeyDown={this.onKeyDown} onKeyUp={this.onKeyUp} placeholder={this.props.placeholder}
|
<textarea dir="auto" autoFocus ref="textarea" rows="1" onKeyDown={this.onKeyDown} onKeyUp={this.onKeyUp} placeholder={this.props.placeholder}
|
||||||
onPaste={this._onPaste}
|
onPaste={this._onPaste}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -18,8 +18,6 @@ limitations under the License.
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import MatrixClientPeg from '../../../MatrixClientPeg';
|
|
||||||
import sdk from '../../../index';
|
|
||||||
import { _t } from '../../../languageHandler';
|
import { _t } from '../../../languageHandler';
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -23,6 +23,7 @@ var sdk = require('../../../index');
|
||||||
|
|
||||||
var Velociraptor = require('../../../Velociraptor');
|
var Velociraptor = require('../../../Velociraptor');
|
||||||
require('../../../VelocityBounce');
|
require('../../../VelocityBounce');
|
||||||
|
import { _t } from '../../../languageHandler';
|
||||||
|
|
||||||
import DateUtils from '../../../DateUtils';
|
import DateUtils from '../../../DateUtils';
|
||||||
|
|
||||||
|
@ -169,8 +170,10 @@ module.exports = React.createClass({
|
||||||
|
|
||||||
let title;
|
let title;
|
||||||
if (this.props.timestamp) {
|
if (this.props.timestamp) {
|
||||||
title = "Seen by " + this.props.member.userId + " at " +
|
title = _t(
|
||||||
DateUtils.formatDate(new Date(this.props.timestamp));
|
"Seen by %(userName)s at %(dateTime)s",
|
||||||
|
{userName: this.props.member.userId, dateTime: DateUtils.formatDate(new Date(this.props.timestamp))}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -213,7 +213,7 @@ module.exports = React.createClass({
|
||||||
// don't display the search count until the search completes and
|
// don't display the search count until the search completes and
|
||||||
// gives us a valid (possibly zero) searchCount.
|
// gives us a valid (possibly zero) searchCount.
|
||||||
if (this.props.searchInfo && this.props.searchInfo.searchCount !== undefined && this.props.searchInfo.searchCount !== null) {
|
if (this.props.searchInfo && this.props.searchInfo.searchCount !== undefined && this.props.searchInfo.searchCount !== null) {
|
||||||
searchStatus = <div className="mx_RoomHeader_searchStatus"> { _t("(~%(searchCount)s results)", { searchCount: this.props.searchInfo.searchCount }) }</div>;
|
searchStatus = <div className="mx_RoomHeader_searchStatus"> { _t("(~%(count)s results)", { count: this.props.searchInfo.searchCount }) }</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
// XXX: this is a bit inefficient - we could just compare room.name for 'Empty room'...
|
// XXX: this is a bit inefficient - we could just compare room.name for 'Empty room'...
|
||||||
|
@ -238,7 +238,7 @@ module.exports = React.createClass({
|
||||||
const emojiTextClasses = classNames('mx_RoomHeader_nametext', { mx_RoomHeader_settingsHint: settingsHint });
|
const emojiTextClasses = classNames('mx_RoomHeader_nametext', { mx_RoomHeader_settingsHint: settingsHint });
|
||||||
name =
|
name =
|
||||||
<div className="mx_RoomHeader_name" onClick={this.props.onSettingsClick}>
|
<div className="mx_RoomHeader_name" onClick={this.props.onSettingsClick}>
|
||||||
<EmojiText element="div" className={emojiTextClasses} title={roomName}>{ roomName }</EmojiText>
|
<EmojiText dir="auto" element="div" className={emojiTextClasses} title={roomName}>{ roomName }</EmojiText>
|
||||||
{ searchStatus }
|
{ searchStatus }
|
||||||
</div>;
|
</div>;
|
||||||
}
|
}
|
||||||
|
@ -255,7 +255,7 @@ module.exports = React.createClass({
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (topic) {
|
if (topic) {
|
||||||
topic_el = <div className="mx_RoomHeader_topic" ref="topic" title={ topic }>{ topic }</div>;
|
topic_el = <div className="mx_RoomHeader_topic" ref="topic" title={ topic } dir="auto">{ topic }</div>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -288,7 +288,7 @@ module.exports = React.createClass({
|
||||||
var settings_button;
|
var settings_button;
|
||||||
if (this.props.onSettingsClick) {
|
if (this.props.onSettingsClick) {
|
||||||
settings_button =
|
settings_button =
|
||||||
<AccessibleButton className="mx_RoomHeader_button" onClick={this.props.onSettingsClick} title="Settings">
|
<AccessibleButton className="mx_RoomHeader_button" onClick={this.props.onSettingsClick} title={_t("Settings")}>
|
||||||
<TintableSvg src="img/icons-settings-room.svg" width="16" height="16"/>
|
<TintableSvg src="img/icons-settings-room.svg" width="16" height="16"/>
|
||||||
</AccessibleButton>;
|
</AccessibleButton>;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
/*
|
/*
|
||||||
Copyright 2015, 2016 OpenMarket Ltd
|
Copyright 2015, 2016 OpenMarket Ltd
|
||||||
|
Copyright 2017 Vector Creations Ltd
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
|
@ -30,7 +31,14 @@ var Rooms = require('../../../Rooms');
|
||||||
import DMRoomMap from '../../../utils/DMRoomMap';
|
import DMRoomMap from '../../../utils/DMRoomMap';
|
||||||
var Receipt = require('../../../utils/Receipt');
|
var Receipt = require('../../../utils/Receipt');
|
||||||
|
|
||||||
var HIDE_CONFERENCE_CHANS = true;
|
const HIDE_CONFERENCE_CHANS = true;
|
||||||
|
|
||||||
|
const VERBS = {
|
||||||
|
'm.favourite': 'favourite',
|
||||||
|
'im.vector.fake.direct': 'tag direct chat',
|
||||||
|
'im.vector.fake.recent': 'restore',
|
||||||
|
'm.lowpriority': 'demote',
|
||||||
|
};
|
||||||
|
|
||||||
module.exports = React.createClass({
|
module.exports = React.createClass({
|
||||||
displayName: 'RoomList',
|
displayName: 'RoomList',
|
||||||
|
@ -45,6 +53,7 @@ module.exports = React.createClass({
|
||||||
getInitialState: function() {
|
getInitialState: function() {
|
||||||
return {
|
return {
|
||||||
isLoadingLeftRooms: false,
|
isLoadingLeftRooms: false,
|
||||||
|
totalRoomCount: null,
|
||||||
lists: {},
|
lists: {},
|
||||||
incomingCall: null,
|
incomingCall: null,
|
||||||
};
|
};
|
||||||
|
@ -64,8 +73,14 @@ module.exports = React.createClass({
|
||||||
cli.on("RoomMember.name", this.onRoomMemberName);
|
cli.on("RoomMember.name", this.onRoomMemberName);
|
||||||
cli.on("accountData", this.onAccountData);
|
cli.on("accountData", this.onAccountData);
|
||||||
|
|
||||||
var s = this.getRoomLists();
|
this.refreshRoomList();
|
||||||
this.setState(s);
|
|
||||||
|
// order of the sublists
|
||||||
|
//this.listOrder = [];
|
||||||
|
|
||||||
|
// loop count to stop a stack overflow if the user keeps waggling the
|
||||||
|
// mouse for >30s in a row, or if running under mocha
|
||||||
|
this._delayedRefreshRoomListLoopCount = 0
|
||||||
},
|
},
|
||||||
|
|
||||||
componentDidMount: function() {
|
componentDidMount: function() {
|
||||||
|
@ -203,31 +218,33 @@ module.exports = React.createClass({
|
||||||
}, 500),
|
}, 500),
|
||||||
|
|
||||||
refreshRoomList: function() {
|
refreshRoomList: function() {
|
||||||
// console.log("DEBUG: Refresh room list delta=%s ms",
|
// TODO: ideally we'd calculate this once at start, and then maintain
|
||||||
// (!this._lastRefreshRoomListTs ? "-" : (Date.now() - this._lastRefreshRoomListTs))
|
// any changes to it incrementally, updating the appropriate sublists
|
||||||
// );
|
// as needed.
|
||||||
|
// Alternatively we'd do something magical with Immutable.js or similar.
|
||||||
// TODO: rather than bluntly regenerating and re-sorting everything
|
const lists = this.getRoomLists();
|
||||||
// every time we see any kind of room change from the JS SDK
|
let totalRooms = 0;
|
||||||
// we could do incremental updates on our copy of the state
|
for (const l of Object.values(lists)) {
|
||||||
// based on the room which has actually changed. This would stop
|
totalRooms += l.length;
|
||||||
// us re-rendering all the sublists every time anything changes anywhere
|
}
|
||||||
// in the state of the client.
|
this.setState({
|
||||||
this.setState(this.getRoomLists());
|
lists: this.getRoomLists(),
|
||||||
|
totalRoomCount: totalRooms,
|
||||||
|
});
|
||||||
|
|
||||||
// this._lastRefreshRoomListTs = Date.now();
|
// this._lastRefreshRoomListTs = Date.now();
|
||||||
},
|
},
|
||||||
|
|
||||||
getRoomLists: function() {
|
getRoomLists: function() {
|
||||||
var self = this;
|
var self = this;
|
||||||
var s = { lists: {} };
|
const lists = {};
|
||||||
|
|
||||||
s.lists["im.vector.fake.invite"] = [];
|
lists["im.vector.fake.invite"] = [];
|
||||||
s.lists["m.favourite"] = [];
|
lists["m.favourite"] = [];
|
||||||
s.lists["im.vector.fake.recent"] = [];
|
lists["im.vector.fake.recent"] = [];
|
||||||
s.lists["im.vector.fake.direct"] = [];
|
lists["im.vector.fake.direct"] = [];
|
||||||
s.lists["m.lowpriority"] = [];
|
lists["m.lowpriority"] = [];
|
||||||
s.lists["im.vector.fake.archived"] = [];
|
lists["im.vector.fake.archived"] = [];
|
||||||
|
|
||||||
const dmRoomMap = new DMRoomMap(MatrixClientPeg.get());
|
const dmRoomMap = new DMRoomMap(MatrixClientPeg.get());
|
||||||
|
|
||||||
|
@ -241,7 +258,7 @@ module.exports = React.createClass({
|
||||||
// ", prevMembership = " + me.events.member.getPrevContent().membership);
|
// ", prevMembership = " + me.events.member.getPrevContent().membership);
|
||||||
|
|
||||||
if (me.membership == "invite") {
|
if (me.membership == "invite") {
|
||||||
s.lists["im.vector.fake.invite"].push(room);
|
lists["im.vector.fake.invite"].push(room);
|
||||||
}
|
}
|
||||||
else if (HIDE_CONFERENCE_CHANS && Rooms.isConfCallRoom(room, me, self.props.ConferenceHandler)) {
|
else if (HIDE_CONFERENCE_CHANS && Rooms.isConfCallRoom(room, me, self.props.ConferenceHandler)) {
|
||||||
// skip past this room & don't put it in any lists
|
// skip past this room & don't put it in any lists
|
||||||
|
@ -255,66 +272,44 @@ module.exports = React.createClass({
|
||||||
if (tagNames.length) {
|
if (tagNames.length) {
|
||||||
for (var i = 0; i < tagNames.length; i++) {
|
for (var i = 0; i < tagNames.length; i++) {
|
||||||
var tagName = tagNames[i];
|
var tagName = tagNames[i];
|
||||||
s.lists[tagName] = s.lists[tagName] || [];
|
lists[tagName] = lists[tagName] || [];
|
||||||
s.lists[tagNames[i]].push(room);
|
lists[tagName].push(room);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (dmRoomMap.getUserIdForRoomId(room.roomId)) {
|
else if (dmRoomMap.getUserIdForRoomId(room.roomId)) {
|
||||||
// "Direct Message" rooms (that we're still in and that aren't otherwise tagged)
|
// "Direct Message" rooms (that we're still in and that aren't otherwise tagged)
|
||||||
s.lists["im.vector.fake.direct"].push(room);
|
lists["im.vector.fake.direct"].push(room);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
s.lists["im.vector.fake.recent"].push(room);
|
lists["im.vector.fake.recent"].push(room);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (me.membership === "leave") {
|
else if (me.membership === "leave") {
|
||||||
s.lists["im.vector.fake.archived"].push(room);
|
lists["im.vector.fake.archived"].push(room);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
console.error("unrecognised membership: " + me.membership + " - this should never happen");
|
console.error("unrecognised membership: " + me.membership + " - this should never happen");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (s.lists["im.vector.fake.direct"].length == 0 &&
|
|
||||||
MatrixClientPeg.get().getAccountData('m.direct') === undefined &&
|
|
||||||
!MatrixClientPeg.get().isGuest())
|
|
||||||
{
|
|
||||||
// scan through the 'recents' list for any rooms which look like DM rooms
|
|
||||||
// and make them DM rooms
|
|
||||||
const oldRecents = s.lists["im.vector.fake.recent"];
|
|
||||||
s.lists["im.vector.fake.recent"] = [];
|
|
||||||
|
|
||||||
for (const room of oldRecents) {
|
|
||||||
const me = room.getMember(MatrixClientPeg.get().credentials.userId);
|
|
||||||
|
|
||||||
if (me && Rooms.looksLikeDirectMessageRoom(room, me)) {
|
|
||||||
s.lists["im.vector.fake.direct"].push(room);
|
|
||||||
} else {
|
|
||||||
s.lists["im.vector.fake.recent"].push(room);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// save these new guessed DM rooms into the account data
|
|
||||||
const newMDirectEvent = {};
|
|
||||||
for (const room of s.lists["im.vector.fake.direct"]) {
|
|
||||||
const me = room.getMember(MatrixClientPeg.get().credentials.userId);
|
|
||||||
const otherPerson = Rooms.getOnlyOtherMember(room, me);
|
|
||||||
if (!otherPerson) continue;
|
|
||||||
|
|
||||||
const roomList = newMDirectEvent[otherPerson.userId] || [];
|
|
||||||
roomList.push(room.roomId);
|
|
||||||
newMDirectEvent[otherPerson.userId] = roomList;
|
|
||||||
}
|
|
||||||
|
|
||||||
// if this fails, fine, we'll just do the same thing next time we get the room lists
|
|
||||||
MatrixClientPeg.get().setAccountData('m.direct', newMDirectEvent).done();
|
|
||||||
}
|
|
||||||
|
|
||||||
//console.log("calculated new roomLists; im.vector.fake.recent = " + s.lists["im.vector.fake.recent"]);
|
|
||||||
|
|
||||||
// we actually apply the sorting to this when receiving the prop in RoomSubLists.
|
// we actually apply the sorting to this when receiving the prop in RoomSubLists.
|
||||||
|
|
||||||
return s;
|
// we'll need this when we get to iterating through lists programatically - e.g. ctrl-shift-up/down
|
||||||
|
/*
|
||||||
|
this.listOrder = [
|
||||||
|
"im.vector.fake.invite",
|
||||||
|
"m.favourite",
|
||||||
|
"im.vector.fake.recent",
|
||||||
|
"im.vector.fake.direct",
|
||||||
|
Object.keys(otherTagNames).filter(tagName=>{
|
||||||
|
return (!tagName.match(/^m\.(favourite|lowpriority)$/));
|
||||||
|
}).sort(),
|
||||||
|
"m.lowpriority",
|
||||||
|
"im.vector.fake.archived"
|
||||||
|
];
|
||||||
|
*/
|
||||||
|
|
||||||
|
return lists;
|
||||||
},
|
},
|
||||||
|
|
||||||
_getScrollNode: function() {
|
_getScrollNode: function() {
|
||||||
|
@ -468,6 +463,62 @@ module.exports = React.createClass({
|
||||||
this.refs.gemscroll.forceUpdate();
|
this.refs.gemscroll.forceUpdate();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
_getEmptyContent: function(section) {
|
||||||
|
const RoomDropTarget = sdk.getComponent('rooms.RoomDropTarget');
|
||||||
|
|
||||||
|
if (this.props.collapsed) {
|
||||||
|
return <RoomDropTarget label="" />;
|
||||||
|
}
|
||||||
|
|
||||||
|
const StartChatButton = sdk.getComponent('elements.StartChatButton');
|
||||||
|
const RoomDirectoryButton = sdk.getComponent('elements.RoomDirectoryButton');
|
||||||
|
const CreateRoomButton = sdk.getComponent('elements.CreateRoomButton');
|
||||||
|
|
||||||
|
const TintableSvg = sdk.getComponent('elements.TintableSvg');
|
||||||
|
switch (section) {
|
||||||
|
case 'im.vector.fake.direct':
|
||||||
|
return <div className="mx_RoomList_emptySubListTip">
|
||||||
|
Press
|
||||||
|
<StartChatButton size="16" callout={true}/>
|
||||||
|
to start a chat with someone
|
||||||
|
</div>;
|
||||||
|
case 'im.vector.fake.recent':
|
||||||
|
return <div className="mx_RoomList_emptySubListTip">
|
||||||
|
You're not in any rooms yet! Press
|
||||||
|
<CreateRoomButton size="16" callout={true}/>
|
||||||
|
to make a room or
|
||||||
|
<RoomDirectoryButton size="16" callout={true}/>
|
||||||
|
to browse the directory
|
||||||
|
</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We don't want to display drop targets if there are no room tiles to drag'n'drop
|
||||||
|
if (this.state.totalRoomCount === 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const labelText = 'Drop here to ' + (VERBS[section] || 'tag ' + section);
|
||||||
|
|
||||||
|
return <RoomDropTarget label={labelText} />;
|
||||||
|
},
|
||||||
|
|
||||||
|
_getHeaderItems: function(section) {
|
||||||
|
const StartChatButton = sdk.getComponent('elements.StartChatButton');
|
||||||
|
const RoomDirectoryButton = sdk.getComponent('elements.RoomDirectoryButton');
|
||||||
|
const CreateRoomButton = sdk.getComponent('elements.CreateRoomButton');
|
||||||
|
switch (section) {
|
||||||
|
case 'im.vector.fake.direct':
|
||||||
|
return <span className="mx_RoomList_headerButtons">
|
||||||
|
<StartChatButton size="16" />
|
||||||
|
</span>;
|
||||||
|
case 'im.vector.fake.recent':
|
||||||
|
return <span className="mx_RoomList_headerButtons">
|
||||||
|
<RoomDirectoryButton size="16" />
|
||||||
|
<CreateRoomButton size="16" />
|
||||||
|
</span>;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
render: function() {
|
render: function() {
|
||||||
var RoomSubList = sdk.getComponent('structures.RoomSubList');
|
var RoomSubList = sdk.getComponent('structures.RoomSubList');
|
||||||
var self = this;
|
var self = this;
|
||||||
|
@ -489,7 +540,7 @@ module.exports = React.createClass({
|
||||||
<RoomSubList list={ self.state.lists['m.favourite'] }
|
<RoomSubList list={ self.state.lists['m.favourite'] }
|
||||||
label={ _t('Favourites') }
|
label={ _t('Favourites') }
|
||||||
tagName="m.favourite"
|
tagName="m.favourite"
|
||||||
verb={ _t('to favourite') }
|
emptyContent={this._getEmptyContent('m.favourite')}
|
||||||
editable={ true }
|
editable={ true }
|
||||||
order="manual"
|
order="manual"
|
||||||
selectedRoom={ self.props.selectedRoom }
|
selectedRoom={ self.props.selectedRoom }
|
||||||
|
@ -502,7 +553,8 @@ module.exports = React.createClass({
|
||||||
<RoomSubList list={ self.state.lists['im.vector.fake.direct'] }
|
<RoomSubList list={ self.state.lists['im.vector.fake.direct'] }
|
||||||
label={ _t('People') }
|
label={ _t('People') }
|
||||||
tagName="im.vector.fake.direct"
|
tagName="im.vector.fake.direct"
|
||||||
verb={ _t('to tag direct chat') }
|
emptyContent={this._getEmptyContent('im.vector.fake.direct')}
|
||||||
|
headerItems={this._getHeaderItems('im.vector.fake.direct')}
|
||||||
editable={ true }
|
editable={ true }
|
||||||
order="recent"
|
order="recent"
|
||||||
selectedRoom={ self.props.selectedRoom }
|
selectedRoom={ self.props.selectedRoom }
|
||||||
|
@ -516,7 +568,8 @@ module.exports = React.createClass({
|
||||||
<RoomSubList list={ self.state.lists['im.vector.fake.recent'] }
|
<RoomSubList list={ self.state.lists['im.vector.fake.recent'] }
|
||||||
label={ _t('Rooms') }
|
label={ _t('Rooms') }
|
||||||
editable={ true }
|
editable={ true }
|
||||||
verb={ _t('to restore') }
|
emptyContent={this._getEmptyContent('im.vector.fake.recent')}
|
||||||
|
headerItems={this._getHeaderItems('im.vector.fake.recent')}
|
||||||
order="recent"
|
order="recent"
|
||||||
selectedRoom={ self.props.selectedRoom }
|
selectedRoom={ self.props.selectedRoom }
|
||||||
incomingCall={ self.state.incomingCall }
|
incomingCall={ self.state.incomingCall }
|
||||||
|
@ -525,13 +578,13 @@ module.exports = React.createClass({
|
||||||
onHeaderClick={ self.onSubListHeaderClick }
|
onHeaderClick={ self.onSubListHeaderClick }
|
||||||
onShowMoreRooms={ self.onShowMoreRooms } />
|
onShowMoreRooms={ self.onShowMoreRooms } />
|
||||||
|
|
||||||
{ Object.keys(self.state.lists).map(function(tagName) {
|
{ Object.keys(self.state.lists).map((tagName) => {
|
||||||
if (!tagName.match(/^(m\.(favourite|lowpriority)|im\.vector\.fake\.(invite|recent|direct|archived))$/)) {
|
if (!tagName.match(/^(m\.(favourite|lowpriority)|im\.vector\.fake\.(invite|recent|direct|archived))$/)) {
|
||||||
return <RoomSubList list={ self.state.lists[tagName] }
|
return <RoomSubList list={ self.state.lists[tagName] }
|
||||||
key={ tagName }
|
key={ tagName }
|
||||||
label={ tagName }
|
label={ tagName }
|
||||||
tagName={ tagName }
|
tagName={ tagName }
|
||||||
verb={ _t('to tag as %(tagName)s', {tagName: tagName}) }
|
emptyContent={this._getEmptyContent(tagName)}
|
||||||
editable={ true }
|
editable={ true }
|
||||||
order="manual"
|
order="manual"
|
||||||
selectedRoom={ self.props.selectedRoom }
|
selectedRoom={ self.props.selectedRoom }
|
||||||
|
@ -547,7 +600,7 @@ module.exports = React.createClass({
|
||||||
<RoomSubList list={ self.state.lists['m.lowpriority'] }
|
<RoomSubList list={ self.state.lists['m.lowpriority'] }
|
||||||
label={ _t('Low priority') }
|
label={ _t('Low priority') }
|
||||||
tagName="m.lowpriority"
|
tagName="m.lowpriority"
|
||||||
verb={ _t('to demote') }
|
emptyContent={this._getEmptyContent('m.lowpriority')}
|
||||||
editable={ true }
|
editable={ true }
|
||||||
order="recent"
|
order="recent"
|
||||||
selectedRoom={ self.props.selectedRoom }
|
selectedRoom={ self.props.selectedRoom }
|
||||||
|
|
|
@ -19,6 +19,7 @@ limitations under the License.
|
||||||
var React = require('react');
|
var React = require('react');
|
||||||
var sdk = require('../../../index');
|
var sdk = require('../../../index');
|
||||||
var MatrixClientPeg = require('../../../MatrixClientPeg');
|
var MatrixClientPeg = require('../../../MatrixClientPeg');
|
||||||
|
import { _t } from '../../../languageHandler';
|
||||||
|
|
||||||
module.exports = React.createClass({
|
module.exports = React.createClass({
|
||||||
displayName: 'RoomNameEditor',
|
displayName: 'RoomNameEditor',
|
||||||
|
@ -35,8 +36,8 @@ module.exports = React.createClass({
|
||||||
|
|
||||||
this._initialName = name ? name.getContent().name : '';
|
this._initialName = name ? name.getContent().name : '';
|
||||||
|
|
||||||
this._placeholderName = "Unnamed Room";
|
this._placeholderName = _t("Unnamed Room");
|
||||||
if (defaultName && defaultName !== 'Empty room') {
|
if (defaultName && defaultName !== 'Empty room') { // default name from JS SDK, needs no translation as we don't ever show it.
|
||||||
this._placeholderName += " (" + defaultName + ")";
|
this._placeholderName += " (" + defaultName + ")";
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -55,9 +56,9 @@ module.exports = React.createClass({
|
||||||
placeholderClassName="mx_RoomHeader_placeholder"
|
placeholderClassName="mx_RoomHeader_placeholder"
|
||||||
placeholder={ this._placeholderName }
|
placeholder={ this._placeholderName }
|
||||||
blurToCancel={ false }
|
blurToCancel={ false }
|
||||||
initialValue={ this._initialName }/>
|
initialValue={ this._initialName }
|
||||||
|
dir="auto" />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -21,7 +21,7 @@ var React = require('react');
|
||||||
var sdk = require('../../../index');
|
var sdk = require('../../../index');
|
||||||
var MatrixClientPeg = require('../../../MatrixClientPeg');
|
var MatrixClientPeg = require('../../../MatrixClientPeg');
|
||||||
|
|
||||||
import { _t } from '../../../languageHandler';
|
import { _t, _tJsx } from '../../../languageHandler';
|
||||||
|
|
||||||
module.exports = React.createClass({
|
module.exports = React.createClass({
|
||||||
displayName: 'RoomPreviewBar',
|
displayName: 'RoomPreviewBar',
|
||||||
|
@ -84,7 +84,7 @@ module.exports = React.createClass({
|
||||||
},
|
},
|
||||||
|
|
||||||
_roomNameElement: function(fallback) {
|
_roomNameElement: function(fallback) {
|
||||||
fallback = fallback || 'a room';
|
fallback = fallback || _t('a room');
|
||||||
const name = this.props.room ? this.props.room.name : (this.props.room_alias || "");
|
const name = this.props.room ? this.props.room.name : (this.props.room_alias || "");
|
||||||
return name ? name : fallback;
|
return name ? name : fallback;
|
||||||
},
|
},
|
||||||
|
@ -114,8 +114,7 @@ module.exports = React.createClass({
|
||||||
if (this.props.invitedEmail) {
|
if (this.props.invitedEmail) {
|
||||||
if (this.state.threePidFetchError) {
|
if (this.state.threePidFetchError) {
|
||||||
emailMatchBlock = <div className="error">
|
emailMatchBlock = <div className="error">
|
||||||
Unable to ascertain that the address this invite was
|
{_t("Unable to ascertain that the address this invite was sent to matches one associated with your account.")}
|
||||||
sent to matches one associated with your account.
|
|
||||||
</div>;
|
</div>;
|
||||||
} else if (this.state.invitedEmailMxid != MatrixClientPeg.get().credentials.userId) {
|
} else if (this.state.invitedEmailMxid != MatrixClientPeg.get().credentials.userId) {
|
||||||
emailMatchBlock =
|
emailMatchBlock =
|
||||||
|
@ -124,28 +123,35 @@ module.exports = React.createClass({
|
||||||
<img src="img/warning.svg" width="24" height="23" title= "/!\\" alt="/!\\" />
|
<img src="img/warning.svg" width="24" height="23" title= "/!\\" alt="/!\\" />
|
||||||
</div>
|
</div>
|
||||||
<div className="mx_RoomPreviewBar_warningText">
|
<div className="mx_RoomPreviewBar_warningText">
|
||||||
This invitation was sent to <b><span className="email">{this.props.invitedEmail}</span></b>, which is not associated with this account.<br/>
|
{_t("This invitation was sent to an email address which is not associated with this account:")}
|
||||||
You may wish to login with a different account, or add this email to this account.
|
<b><span className="email">{this.props.invitedEmail}</span></b>
|
||||||
|
<br/>
|
||||||
|
{_t("You may wish to login with a different account, or add this email to this account.")}
|
||||||
</div>
|
</div>
|
||||||
</div>;
|
</div>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// TODO: find a way to respect HTML in counterpart!
|
|
||||||
joinBlock = (
|
joinBlock = (
|
||||||
<div>
|
<div>
|
||||||
<div className="mx_RoomPreviewBar_invite_text">
|
<div className="mx_RoomPreviewBar_invite_text">
|
||||||
{ _t('You have been invited to join this room by %(inviterName)s', {inviterName: this.props.inviterName}) }
|
{ _t('You have been invited to join this room by %(inviterName)s', {inviterName: this.props.inviterName}) }
|
||||||
</div>
|
</div>
|
||||||
<div className="mx_RoomPreviewBar_join_text">
|
<div className="mx_RoomPreviewBar_join_text">
|
||||||
{ _t('Would you like to') } <a onClick={ this.props.onJoinClick }>{ _t('accept') }</a> { _t('or') } <a onClick={ this.props.onRejectClick }>{ _t('decline') }</a> { _t('this invitation?') }
|
{ _tJsx(
|
||||||
|
'Would you like to <acceptText>accept</acceptText> or <declineText>decline</declineText> this invitation?',
|
||||||
|
[/<acceptText>(.*?)<\/acceptText>/, /<declineText>(.*?)<\/declineText>/],
|
||||||
|
[
|
||||||
|
(sub) => <a onClick={ this.props.onJoinClick }>{sub}</a>,
|
||||||
|
(sub) => <a onClick={ this.props.onRejectClick }>{sub}</a>
|
||||||
|
]
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
{emailMatchBlock}
|
{emailMatchBlock}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
} else if (kicked || banned) {
|
} else if (kicked || banned) {
|
||||||
const verb = kicked ? 'kicked' : 'banned';
|
const roomName = this._roomNameElement(_t('This room'));
|
||||||
const roomName = this._roomNameElement('this room');
|
|
||||||
const kickerMember = this.props.room.currentState.getMember(
|
const kickerMember = this.props.room.currentState.getMember(
|
||||||
myMember.events.member.getSender()
|
myMember.events.member.getSender()
|
||||||
);
|
);
|
||||||
|
@ -153,29 +159,39 @@ module.exports = React.createClass({
|
||||||
kickerMember.name : myMember.events.member.getSender();
|
kickerMember.name : myMember.events.member.getSender();
|
||||||
let reason;
|
let reason;
|
||||||
if (myMember.events.member.getContent().reason) {
|
if (myMember.events.member.getContent().reason) {
|
||||||
reason = <div>Reason: {myMember.events.member.getContent().reason}</div>
|
reason = <div>{_t("Reason: %(reasonText)s", {reasonText: myMember.events.member.getContent().reason})}</div>
|
||||||
}
|
}
|
||||||
let rejoinBlock;
|
let rejoinBlock;
|
||||||
if (!banned) {
|
if (!banned) {
|
||||||
rejoinBlock = <div><a onClick={ this.props.onJoinClick }><b>Rejoin</b></a></div>;
|
rejoinBlock = <div><a onClick={ this.props.onJoinClick }><b>{_t("Rejoin")}</b></a></div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let actionText;
|
||||||
|
if (kicked) {
|
||||||
|
actionText = _t("You have been kicked from %(roomName)s by %(userName)s.", {roomName: roomName, userName: kickerName});
|
||||||
|
}
|
||||||
|
else if (banned) {
|
||||||
|
actionText = _t("You have been banned from %(roomName)s by %(userName)s.", {roomName: roomName, userName: kickerName});
|
||||||
|
} // no other options possible due to the kicked || banned check above.
|
||||||
|
|
||||||
joinBlock = (
|
joinBlock = (
|
||||||
<div>
|
<div>
|
||||||
<div className="mx_RoomPreviewBar_join_text">
|
<div className="mx_RoomPreviewBar_join_text">
|
||||||
You have been {verb} from {roomName} by {kickerName}.<br />
|
{actionText}
|
||||||
|
<br />
|
||||||
{reason}
|
{reason}
|
||||||
{rejoinBlock}
|
{rejoinBlock}
|
||||||
<a onClick={ this.props.onForgetClick }><b>Forget</b></a>
|
<a onClick={ this.props.onForgetClick }><b>{_t("Forget room")}</b></a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
} else if (this.props.error) {
|
} else if (this.props.error) {
|
||||||
var name = this.props.roomAlias || "This room";
|
var name = this.props.roomAlias || _t("This room");
|
||||||
var error;
|
var error;
|
||||||
if (this.props.error.errcode == 'M_NOT_FOUND') {
|
if (this.props.error.errcode == 'M_NOT_FOUND') {
|
||||||
error = name + " does not exist";
|
error = _t("%(roomName)s does not exist.", {roomName: name});
|
||||||
} else {
|
} else {
|
||||||
error = name + " is not accessible at this time";
|
error = _t("%(roomName)s is not accessible at this time.", {roomName: name});
|
||||||
}
|
}
|
||||||
joinBlock = (
|
joinBlock = (
|
||||||
<div>
|
<div>
|
||||||
|
@ -189,8 +205,12 @@ module.exports = React.createClass({
|
||||||
joinBlock = (
|
joinBlock = (
|
||||||
<div>
|
<div>
|
||||||
<div className="mx_RoomPreviewBar_join_text">
|
<div className="mx_RoomPreviewBar_join_text">
|
||||||
{ _t('You are trying to access %(roomName)s', {roomName: name}) }.<br/>
|
{ _t('You are trying to access %(roomName)s.', {roomName: name}) }
|
||||||
<a onClick={ this.props.onJoinClick }><b>{ _t('Click here') }</b></a> { _t('to join the discussion') }!
|
<br/>
|
||||||
|
{ _tJsx("<a>Click here</a> to join the discussion!",
|
||||||
|
/<a>(.*?)<\/a>/,
|
||||||
|
(sub) => <a onClick={ this.props.onJoinClick }><b>{sub}</b></a>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -17,7 +17,7 @@ limitations under the License.
|
||||||
|
|
||||||
import q from 'q';
|
import q from 'q';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { _t } from '../../../languageHandler';
|
import { _t, _tJsx } from '../../../languageHandler';
|
||||||
import MatrixClientPeg from '../../../MatrixClientPeg';
|
import MatrixClientPeg from '../../../MatrixClientPeg';
|
||||||
import SdkConfig from '../../../SdkConfig';
|
import SdkConfig from '../../../SdkConfig';
|
||||||
import sdk from '../../../index';
|
import sdk from '../../../index';
|
||||||
|
@ -40,13 +40,14 @@ function parseIntWithDefault(val, def) {
|
||||||
const BannedUser = React.createClass({
|
const BannedUser = React.createClass({
|
||||||
propTypes: {
|
propTypes: {
|
||||||
member: React.PropTypes.object.isRequired, // js-sdk RoomMember
|
member: React.PropTypes.object.isRequired, // js-sdk RoomMember
|
||||||
|
reason: React.PropTypes.string,
|
||||||
},
|
},
|
||||||
|
|
||||||
_onUnbanClick: function() {
|
_onUnbanClick: function() {
|
||||||
const ConfirmUserActionDialog = sdk.getComponent("dialogs.ConfirmUserActionDialog");
|
const ConfirmUserActionDialog = sdk.getComponent("dialogs.ConfirmUserActionDialog");
|
||||||
Modal.createDialog(ConfirmUserActionDialog, {
|
Modal.createDialog(ConfirmUserActionDialog, {
|
||||||
member: this.props.member,
|
member: this.props.member,
|
||||||
action: 'Unban',
|
action: _t('Unban'),
|
||||||
danger: false,
|
danger: false,
|
||||||
onFinished: (proceed) => {
|
onFinished: (proceed) => {
|
||||||
if (!proceed) return;
|
if (!proceed) return;
|
||||||
|
@ -73,10 +74,11 @@ const BannedUser = React.createClass({
|
||||||
>
|
>
|
||||||
{ _t('Unban') }
|
{ _t('Unban') }
|
||||||
</AccessibleButton>
|
</AccessibleButton>
|
||||||
{this.props.member.userId}
|
<strong>{this.props.member.name}</strong> {this.props.member.userId}
|
||||||
|
{this.props.reason ? " " +_t('Reason') + ": " + this.props.reason : ""}
|
||||||
</li>
|
</li>
|
||||||
);
|
);
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
module.exports = React.createClass({
|
module.exports = React.createClass({
|
||||||
|
@ -576,28 +578,26 @@ module.exports = React.createClass({
|
||||||
{ _t('Never send encrypted messages to unverified devices in this room from this device') }.
|
{ _t('Never send encrypted messages to unverified devices in this room from this device') }.
|
||||||
</label>;
|
</label>;
|
||||||
|
|
||||||
if (!isEncrypted &&
|
if (!isEncrypted && roomState.mayClientSendStateEvent("m.room.encryption", cli)) {
|
||||||
roomState.mayClientSendStateEvent("m.room.encryption", cli)) {
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<label>
|
<label>
|
||||||
<input type="checkbox" ref="encrypt" onClick={ this.onEnableEncryptionClick }/>
|
<input type="checkbox" ref="encrypt" onClick={ this.onEnableEncryptionClick }/>
|
||||||
<img className="mx_RoomSettings_e2eIcon" src="img/e2e-unencrypted.svg" width="12" height="12" />
|
<img className="mx_RoomSettings_e2eIcon mx_filterFlipColor" src="img/e2e-unencrypted.svg" width="12" height="12" />
|
||||||
{ _t('Enable encryption') } { _t('(warning: cannot be disabled again!)') }
|
{ _t('Enable encryption') } { _t('(warning: cannot be disabled again!)') }
|
||||||
</label>
|
</label>
|
||||||
{ settings }
|
{ settings }
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<label>
|
<label>
|
||||||
{ isEncrypted
|
{ isEncrypted
|
||||||
? <img className="mx_RoomSettings_e2eIcon" src="img/e2e-verified.svg" width="10" height="12" />
|
? <img className="mx_RoomSettings_e2eIcon" src="img/e2e-verified.svg" width="10" height="12" />
|
||||||
: <img className="mx_RoomSettings_e2eIcon" src="img/e2e-unencrypted.svg" width="12" height="12" />
|
: <img className="mx_RoomSettings_e2eIcon mx_filterFlipColor" src="img/e2e-unencrypted.svg" width="12" height="12" />
|
||||||
}
|
}
|
||||||
{ isEncrypted ? "Encryption is enabled in this room" : "Encryption is not enabled in this room" }.
|
{ isEncrypted ? _t("Encryption is enabled in this room") : _t("Encryption is not enabled in this room") }.
|
||||||
</label>
|
</label>
|
||||||
{ settings }
|
{ settings }
|
||||||
</div>
|
</div>
|
||||||
|
@ -653,7 +653,7 @@ module.exports = React.createClass({
|
||||||
{Object.keys(user_levels).map(function(user, i) {
|
{Object.keys(user_levels).map(function(user, i) {
|
||||||
return (
|
return (
|
||||||
<li className="mx_RoomSettings_userLevel" key={user}>
|
<li className="mx_RoomSettings_userLevel" key={user}>
|
||||||
{ user } { _t('is a') } <PowerSelector value={ user_levels[user] } disabled={true}/>
|
{ _t("%(user)s is a", {user: user}) } <PowerSelector value={ user_levels[user] } disabled={true}/>
|
||||||
</li>
|
</li>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
@ -664,16 +664,17 @@ module.exports = React.createClass({
|
||||||
userLevelsSection = <div>{ _t('No users have specific privileges in this room') }.</div>;
|
userLevelsSection = <div>{ _t('No users have specific privileges in this room') }.</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
var banned = this.props.room.getMembersWithMembership("ban");
|
const banned = this.props.room.getMembersWithMembership("ban");
|
||||||
var bannedUsersSection;
|
let bannedUsersSection;
|
||||||
if (banned.length) {
|
if (banned.length) {
|
||||||
bannedUsersSection =
|
bannedUsersSection =
|
||||||
<div>
|
<div>
|
||||||
<h3>{ _t('Banned users') }</h3>
|
<h3>{ _t('Banned users') }</h3>
|
||||||
<ul className="mx_RoomSettings_banned">
|
<ul className="mx_RoomSettings_banned">
|
||||||
{banned.map(function(member) {
|
{banned.map(function(member) {
|
||||||
|
const banEvent = member.events.member.getContent();
|
||||||
return (
|
return (
|
||||||
<BannedUser key={member.userId} member={member} />
|
<BannedUser key={member.userId} member={member} reason={banEvent.reason} />
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</ul>
|
</ul>
|
||||||
|
@ -754,7 +755,11 @@ module.exports = React.createClass({
|
||||||
if (this.state.join_rule === "public" && aliasCount == 0) {
|
if (this.state.join_rule === "public" && aliasCount == 0) {
|
||||||
addressWarning =
|
addressWarning =
|
||||||
<div className="mx_RoomSettings_warning">
|
<div className="mx_RoomSettings_warning">
|
||||||
{ _t('To link to a room it must have') } <a href="#addresses"> { _t('an address') }</a>.
|
{ _tJsx(
|
||||||
|
'To link to a room it must have <a>an address</a>.',
|
||||||
|
/<a>(.*?)<\/a>/,
|
||||||
|
(sub) => <a href="#addresses">{sub}</a>
|
||||||
|
)}
|
||||||
</div>;
|
</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -224,13 +224,13 @@ module.exports = React.createClass({
|
||||||
if (this.props.selected) {
|
if (this.props.selected) {
|
||||||
let nameSelected = <EmojiText>{name}</EmojiText>;
|
let nameSelected = <EmojiText>{name}</EmojiText>;
|
||||||
|
|
||||||
label = <div title={ name } className={ nameClasses }>{ nameSelected }</div>;
|
label = <div title={ name } className={ nameClasses } dir="auto">{ nameSelected }</div>;
|
||||||
} else {
|
} else {
|
||||||
label = <EmojiText element="div" title={ name } className={ nameClasses }>{name}</EmojiText>;
|
label = <EmojiText element="div" title={ name } className={ nameClasses } dir="auto">{name}</EmojiText>;
|
||||||
}
|
}
|
||||||
} else if (this.state.hover) {
|
} else if (this.state.hover) {
|
||||||
var RoomTooltip = sdk.getComponent("rooms.RoomTooltip");
|
var RoomTooltip = sdk.getComponent("rooms.RoomTooltip");
|
||||||
tooltip = <RoomTooltip className="mx_RoomTile_tooltip" room={this.props.room} />;
|
tooltip = <RoomTooltip className="mx_RoomTile_tooltip" room={this.props.room} dir="auto" />;
|
||||||
}
|
}
|
||||||
|
|
||||||
//var incomingCallBox;
|
//var incomingCallBox;
|
||||||
|
|
|
@ -46,7 +46,8 @@ module.exports = React.createClass({
|
||||||
placeholderClassName="mx_RoomHeader_placeholder"
|
placeholderClassName="mx_RoomHeader_placeholder"
|
||||||
placeholder={_t("Add a topic")}
|
placeholder={_t("Add a topic")}
|
||||||
blurToCancel={ false }
|
blurToCancel={ false }
|
||||||
initialValue={ this._initialTopic }/>
|
initialValue={ this._initialTopic }
|
||||||
|
dir="auto" />
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -20,6 +20,7 @@ import React from 'react';
|
||||||
import dis from '../../../dispatcher';
|
import dis from '../../../dispatcher';
|
||||||
import AccessibleButton from '../elements/AccessibleButton';
|
import AccessibleButton from '../elements/AccessibleButton';
|
||||||
import sdk from '../../../index';
|
import sdk from '../../../index';
|
||||||
|
import { _t } from '../../../languageHandler';
|
||||||
|
|
||||||
// cancel button which is shared between room header and simple room header
|
// cancel button which is shared between room header and simple room header
|
||||||
export function CancelButton(props) {
|
export function CancelButton(props) {
|
||||||
|
@ -28,7 +29,7 @@ export function CancelButton(props) {
|
||||||
return (
|
return (
|
||||||
<AccessibleButton className='mx_RoomHeader_cancelButton' onClick={onClick}>
|
<AccessibleButton className='mx_RoomHeader_cancelButton' onClick={onClick}>
|
||||||
<img src="img/cancel.svg" className='mx_filterFlipColor'
|
<img src="img/cancel.svg" className='mx_filterFlipColor'
|
||||||
width="18" height="18" alt="Cancel"/>
|
width="18" height="18" alt={_t("Cancel")}/>
|
||||||
</AccessibleButton>
|
</AccessibleButton>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -41,7 +41,7 @@ module.exports = React.createClass({
|
||||||
</div>
|
</div>
|
||||||
<img className="mx_TopUnreadMessagesBar_close mx_filterFlipColor"
|
<img className="mx_TopUnreadMessagesBar_close mx_filterFlipColor"
|
||||||
src="img/cancel.svg" width="18" height="18"
|
src="img/cancel.svg" width="18" height="18"
|
||||||
alt="Close" title="Close"
|
alt={_t("Close")} title={_t("Close")}
|
||||||
onClick={this.props.onCloseClick} />
|
onClick={this.props.onCloseClick} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -165,7 +165,7 @@ export default WithMatrixClient(React.createClass({
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="mx_UserSettings_threepidButton mx_filterFlipColor">
|
<div className="mx_UserSettings_threepidButton mx_filterFlipColor">
|
||||||
<input type="image" value="Add" src="img/plus.svg" width="14" height="14" />
|
<input type="image" value={_t("Add")} src="img/plus.svg" width="14" height="14" />
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
);
|
);
|
||||||
|
|
|
@ -17,6 +17,7 @@ limitations under the License.
|
||||||
var React = require('react');
|
var React = require('react');
|
||||||
var MatrixClientPeg = require("../../../MatrixClientPeg");
|
var MatrixClientPeg = require("../../../MatrixClientPeg");
|
||||||
var sdk = require('../../../index');
|
var sdk = require('../../../index');
|
||||||
|
import { _t } from '../../../languageHandler';
|
||||||
|
|
||||||
module.exports = React.createClass({
|
module.exports = React.createClass({
|
||||||
displayName: 'ChangeAvatar',
|
displayName: 'ChangeAvatar',
|
||||||
|
@ -105,7 +106,7 @@ module.exports = React.createClass({
|
||||||
|
|
||||||
onError: function(error) {
|
onError: function(error) {
|
||||||
this.setState({
|
this.setState({
|
||||||
errorText: "Failed to upload profile picture!"
|
errorText: _t("Failed to upload profile picture!")
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -127,7 +128,7 @@ module.exports = React.createClass({
|
||||||
if (this.props.showUploadSection) {
|
if (this.props.showUploadSection) {
|
||||||
uploadSection = (
|
uploadSection = (
|
||||||
<div className={this.props.className}>
|
<div className={this.props.className}>
|
||||||
Upload new:
|
{_t("Upload new:")}
|
||||||
<input type="file" accept="image/*" onChange={this.onFileSelected}/>
|
<input type="file" accept="image/*" onChange={this.onFileSelected}/>
|
||||||
{this.state.errorText}
|
{this.state.errorText}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -18,6 +18,7 @@ limitations under the License.
|
||||||
var React = require('react');
|
var React = require('react');
|
||||||
var sdk = require('../../../index');
|
var sdk = require('../../../index');
|
||||||
var MatrixClientPeg = require("../../../MatrixClientPeg");
|
var MatrixClientPeg = require("../../../MatrixClientPeg");
|
||||||
|
import { _t } from '../../../languageHandler';
|
||||||
|
|
||||||
module.exports = React.createClass({
|
module.exports = React.createClass({
|
||||||
displayName: 'ChangeDisplayName',
|
displayName: 'ChangeDisplayName',
|
||||||
|
@ -52,7 +53,7 @@ module.exports = React.createClass({
|
||||||
return (
|
return (
|
||||||
<EditableTextContainer
|
<EditableTextContainer
|
||||||
getInitialValue={this._getDisplayName}
|
getInitialValue={this._getDisplayName}
|
||||||
placeholder="No display name"
|
placeholder={_t("No display name")}
|
||||||
blurToSubmit={true}
|
blurToSubmit={true}
|
||||||
onSubmit={this._changeDisplayName} />
|
onSubmit={this._changeDisplayName} />
|
||||||
);
|
);
|
||||||
|
|
|
@ -20,9 +20,13 @@ var React = require('react');
|
||||||
var MatrixClientPeg = require("../../../MatrixClientPeg");
|
var MatrixClientPeg = require("../../../MatrixClientPeg");
|
||||||
var Modal = require("../../../Modal");
|
var Modal = require("../../../Modal");
|
||||||
var sdk = require("../../../index");
|
var sdk = require("../../../index");
|
||||||
|
|
||||||
|
import q from 'q';
|
||||||
import AccessibleButton from '../elements/AccessibleButton';
|
import AccessibleButton from '../elements/AccessibleButton';
|
||||||
import { _t } from '../../../languageHandler';
|
import { _t } from '../../../languageHandler';
|
||||||
|
|
||||||
|
import sessionStore from '../../../stores/SessionStore';
|
||||||
|
|
||||||
module.exports = React.createClass({
|
module.exports = React.createClass({
|
||||||
displayName: 'ChangePassword',
|
displayName: 'ChangePassword',
|
||||||
propTypes: {
|
propTypes: {
|
||||||
|
@ -32,7 +36,10 @@ module.exports = React.createClass({
|
||||||
rowClassName: React.PropTypes.string,
|
rowClassName: React.PropTypes.string,
|
||||||
rowLabelClassName: React.PropTypes.string,
|
rowLabelClassName: React.PropTypes.string,
|
||||||
rowInputClassName: React.PropTypes.string,
|
rowInputClassName: React.PropTypes.string,
|
||||||
buttonClassName: React.PropTypes.string
|
buttonClassName: React.PropTypes.string,
|
||||||
|
confirm: React.PropTypes.bool,
|
||||||
|
// Whether to autoFocus the new password input
|
||||||
|
autoFocusNewPasswordInput: React.PropTypes.bool,
|
||||||
},
|
},
|
||||||
|
|
||||||
Phases: {
|
Phases: {
|
||||||
|
@ -48,27 +55,55 @@ module.exports = React.createClass({
|
||||||
onCheckPassword: function(oldPass, newPass, confirmPass) {
|
onCheckPassword: function(oldPass, newPass, confirmPass) {
|
||||||
if (newPass !== confirmPass) {
|
if (newPass !== confirmPass) {
|
||||||
return {
|
return {
|
||||||
error: _t("New passwords don't match") + "."
|
error: _t("New passwords don't match")
|
||||||
};
|
};
|
||||||
} else if (!newPass || newPass.length === 0) {
|
} else if (!newPass || newPass.length === 0) {
|
||||||
return {
|
return {
|
||||||
error: _t("Passwords can't be empty")
|
error: _t("Passwords can't be empty")
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
confirm: true,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
getInitialState: function() {
|
getInitialState: function() {
|
||||||
return {
|
return {
|
||||||
phase: this.Phases.Edit
|
phase: this.Phases.Edit,
|
||||||
|
cachedPassword: null,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
changePassword: function(old_password, new_password) {
|
componentWillMount: function() {
|
||||||
var cli = MatrixClientPeg.get();
|
this._sessionStore = sessionStore;
|
||||||
|
this._sessionStoreToken = this._sessionStore.addListener(
|
||||||
|
this._setStateFromSessionStore,
|
||||||
|
);
|
||||||
|
|
||||||
var QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
|
this._setStateFromSessionStore();
|
||||||
|
},
|
||||||
|
|
||||||
|
componentWillUnmount: function() {
|
||||||
|
if (this._sessionStoreToken) {
|
||||||
|
this._sessionStoreToken.remove();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
_setStateFromSessionStore: function() {
|
||||||
|
this.setState({
|
||||||
|
cachedPassword: this._sessionStore.getCachedPassword(),
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
changePassword: function(oldPassword, newPassword) {
|
||||||
|
const cli = MatrixClientPeg.get();
|
||||||
|
|
||||||
|
if (!this.props.confirm) {
|
||||||
|
this._changePassword(cli, oldPassword, newPassword);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
|
||||||
Modal.createDialog(QuestionDialog, {
|
Modal.createDialog(QuestionDialog, {
|
||||||
title: _t("Warning!"),
|
title: _t("Warning!"),
|
||||||
description:
|
description:
|
||||||
|
@ -89,31 +124,56 @@ module.exports = React.createClass({
|
||||||
],
|
],
|
||||||
onFinished: (confirmed) => {
|
onFinished: (confirmed) => {
|
||||||
if (confirmed) {
|
if (confirmed) {
|
||||||
var authDict = {
|
this._changePassword(cli, oldPassword, newPassword);
|
||||||
type: 'm.login.password',
|
|
||||||
user: cli.credentials.userId,
|
|
||||||
password: old_password
|
|
||||||
};
|
|
||||||
|
|
||||||
this.setState({
|
|
||||||
phase: this.Phases.Uploading
|
|
||||||
});
|
|
||||||
|
|
||||||
var self = this;
|
|
||||||
cli.setPassword(authDict, new_password).then(function() {
|
|
||||||
self.props.onFinished();
|
|
||||||
}, function(err) {
|
|
||||||
self.props.onError(err);
|
|
||||||
}).finally(function() {
|
|
||||||
self.setState({
|
|
||||||
phase: self.Phases.Edit
|
|
||||||
});
|
|
||||||
}).done();
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
_changePassword: function(cli, oldPassword, newPassword) {
|
||||||
|
const authDict = {
|
||||||
|
type: 'm.login.password',
|
||||||
|
user: cli.credentials.userId,
|
||||||
|
password: oldPassword,
|
||||||
|
};
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
phase: this.Phases.Uploading,
|
||||||
|
});
|
||||||
|
|
||||||
|
cli.setPassword(authDict, newPassword).then(() => {
|
||||||
|
if (this.props.shouldAskForEmail) {
|
||||||
|
return this._optionallySetEmail().then((confirmed) => {
|
||||||
|
this.props.onFinished({
|
||||||
|
didSetEmail: confirmed,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.props.onFinished();
|
||||||
|
}
|
||||||
|
}, (err) => {
|
||||||
|
this.props.onError(err);
|
||||||
|
}).finally(() => {
|
||||||
|
this.setState({
|
||||||
|
phase: this.Phases.Edit,
|
||||||
|
});
|
||||||
|
}).done();
|
||||||
|
},
|
||||||
|
|
||||||
|
_optionallySetEmail: function() {
|
||||||
|
const deferred = q.defer();
|
||||||
|
// Ask for an email otherwise the user has no way to reset their password
|
||||||
|
const SetEmailDialog = sdk.getComponent("dialogs.SetEmailDialog");
|
||||||
|
Modal.createDialog(SetEmailDialog, {
|
||||||
|
title: _t('Do you want to set an email address?'),
|
||||||
|
onFinished: (confirmed) => {
|
||||||
|
// ignore confirmed, setting an email is optional
|
||||||
|
deferred.resolve(confirmed);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return deferred.promise;
|
||||||
|
},
|
||||||
|
|
||||||
_onExportE2eKeysClicked: function() {
|
_onExportE2eKeysClicked: function() {
|
||||||
Modal.createDialogAsync(
|
Modal.createDialogAsync(
|
||||||
(cb) => {
|
(cb) => {
|
||||||
|
@ -124,47 +184,53 @@ module.exports = React.createClass({
|
||||||
matrixClient: MatrixClientPeg.get(),
|
matrixClient: MatrixClientPeg.get(),
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
onClickChange: function() {
|
onClickChange: function() {
|
||||||
var old_password = this.refs.old_input.value;
|
const oldPassword = this.state.cachedPassword || this.refs.old_input.value;
|
||||||
var new_password = this.refs.new_input.value;
|
const newPassword = this.refs.new_input.value;
|
||||||
var confirm_password = this.refs.confirm_input.value;
|
const confirmPassword = this.refs.confirm_input.value;
|
||||||
var err = this.props.onCheckPassword(
|
const err = this.props.onCheckPassword(
|
||||||
old_password, new_password, confirm_password
|
oldPassword, newPassword, confirmPassword,
|
||||||
);
|
);
|
||||||
if (err) {
|
if (err) {
|
||||||
this.props.onError(err);
|
this.props.onError(err);
|
||||||
}
|
} else {
|
||||||
else {
|
this.changePassword(oldPassword, newPassword);
|
||||||
this.changePassword(old_password, new_password);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
render: function() {
|
render: function() {
|
||||||
var rowClassName = this.props.rowClassName;
|
const rowClassName = this.props.rowClassName;
|
||||||
var rowLabelClassName = this.props.rowLabelClassName;
|
const rowLabelClassName = this.props.rowLabelClassName;
|
||||||
var rowInputClassName = this.props.rowInputClassName;
|
const rowInputClassName = this.props.rowInputClassName;
|
||||||
var buttonClassName = this.props.buttonClassName;
|
const buttonClassName = this.props.buttonClassName;
|
||||||
|
|
||||||
|
let currentPassword = null;
|
||||||
|
if (!this.state.cachedPassword) {
|
||||||
|
currentPassword = <div className={rowClassName}>
|
||||||
|
<div className={rowLabelClassName}>
|
||||||
|
<label htmlFor="passwordold">Current password</label>
|
||||||
|
</div>
|
||||||
|
<div className={rowInputClassName}>
|
||||||
|
<input id="passwordold" type="password" ref="old_input" />
|
||||||
|
</div>
|
||||||
|
</div>;
|
||||||
|
}
|
||||||
|
|
||||||
switch (this.state.phase) {
|
switch (this.state.phase) {
|
||||||
case this.Phases.Edit:
|
case this.Phases.Edit:
|
||||||
|
const passwordLabel = this.state.cachedPassword ?
|
||||||
|
_t('Password') : _t('New Password');
|
||||||
return (
|
return (
|
||||||
<div className={this.props.className}>
|
<form className={this.props.className} onSubmit={this.onClickChange}>
|
||||||
|
{ currentPassword }
|
||||||
<div className={rowClassName}>
|
<div className={rowClassName}>
|
||||||
<div className={rowLabelClassName}>
|
<div className={rowLabelClassName}>
|
||||||
<label htmlFor="passwordold">{ _t('Current password') }</label>
|
<label htmlFor="password1">{ passwordLabel }</label>
|
||||||
</div>
|
</div>
|
||||||
<div className={rowInputClassName}>
|
<div className={rowInputClassName}>
|
||||||
<input id="passwordold" type="password" ref="old_input" />
|
<input id="password1" type="password" ref="new_input" autoFocus={this.props.autoFocusNewPasswordInput} />
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className={rowClassName}>
|
|
||||||
<div className={rowLabelClassName}>
|
|
||||||
<label htmlFor="password1">{ _t('New password') }</label>
|
|
||||||
</div>
|
|
||||||
<div className={rowInputClassName}>
|
|
||||||
<input id="password1" type="password" ref="new_input" />
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className={rowClassName}>
|
<div className={rowClassName}>
|
||||||
|
@ -176,10 +242,11 @@ module.exports = React.createClass({
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<AccessibleButton className={buttonClassName}
|
<AccessibleButton className={buttonClassName}
|
||||||
onClick={this.onClickChange}>
|
onClick={this.onClickChange}
|
||||||
|
element="button">
|
||||||
{ _t('Change Password') }
|
{ _t('Change Password') }
|
||||||
</AccessibleButton>
|
</AccessibleButton>
|
||||||
</div>
|
</form>
|
||||||
);
|
);
|
||||||
case this.Phases.Uploading:
|
case this.Phases.Uploading:
|
||||||
var Loader = sdk.getComponent("elements.Spinner");
|
var Loader = sdk.getComponent("elements.Spinner");
|
||||||
|
|
|
@ -19,6 +19,7 @@ import classNames from 'classnames';
|
||||||
|
|
||||||
import sdk from '../../../index';
|
import sdk from '../../../index';
|
||||||
import MatrixClientPeg from '../../../MatrixClientPeg';
|
import MatrixClientPeg from '../../../MatrixClientPeg';
|
||||||
|
import { _t } from '../../../languageHandler';
|
||||||
|
|
||||||
|
|
||||||
export default class DevicesPanel extends React.Component {
|
export default class DevicesPanel extends React.Component {
|
||||||
|
@ -54,10 +55,10 @@ export default class DevicesPanel extends React.Component {
|
||||||
var errtxt;
|
var errtxt;
|
||||||
if (error.httpStatus == 404) {
|
if (error.httpStatus == 404) {
|
||||||
// 404 probably means the HS doesn't yet support the API.
|
// 404 probably means the HS doesn't yet support the API.
|
||||||
errtxt = "Your home server does not support device management.";
|
errtxt = _t("Your home server does not support device management.");
|
||||||
} else {
|
} else {
|
||||||
console.error("Error loading devices:", error);
|
console.error("Error loading devices:", error);
|
||||||
errtxt = "Unable to load device list.";
|
errtxt = _t("Unable to load device list");
|
||||||
}
|
}
|
||||||
this.setState({deviceLoadError: errtxt});
|
this.setState({deviceLoadError: errtxt});
|
||||||
}
|
}
|
||||||
|
@ -127,9 +128,9 @@ export default class DevicesPanel extends React.Component {
|
||||||
return (
|
return (
|
||||||
<div className={classes}>
|
<div className={classes}>
|
||||||
<div className="mx_DevicesPanel_header">
|
<div className="mx_DevicesPanel_header">
|
||||||
<div className="mx_DevicesPanel_deviceId">ID</div>
|
<div className="mx_DevicesPanel_deviceId">{_t("Device ID")}</div>
|
||||||
<div className="mx_DevicesPanel_deviceName">Name</div>
|
<div className="mx_DevicesPanel_deviceName">{_t("Device Name")}</div>
|
||||||
<div className="mx_DevicesPanel_deviceLastSeen">Last seen</div>
|
<div className="mx_DevicesPanel_deviceLastSeen">{_t("Last seen")}</div>
|
||||||
<div className="mx_DevicesPanel_deviceButtons"></div>
|
<div className="mx_DevicesPanel_deviceButtons"></div>
|
||||||
</div>
|
</div>
|
||||||
{devices.map(this._renderDevice)}
|
{devices.map(this._renderDevice)}
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue