mirror of
https://github.com/element-hq/element-web.git
synced 2024-12-15 05:42:31 +03:00
Merge branches 'develop' and 'kegan/hide-join-part' of github.com:matrix-org/matrix-react-sdk into kegan/hide-join-part
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> # Conflicts: # src/components/structures/UserSettings.js
This commit is contained in:
commit
c1cbdbcc4f
155 changed files with 8785 additions and 2094 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,5 +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:
|
||||||
- npm run test
|
./scripts/travis.sh
|
||||||
- ./.travis-test-riot.sh
|
|
||||||
|
|
182
CHANGELOG.md
182
CHANGELOG.md
|
@ -1,3 +1,185 @@
|
||||||
|
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)
|
||||||
|
===================================================================================================
|
||||||
|
[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v0.9.0-rc.2...v0.9.0)
|
||||||
|
|
||||||
|
* sync pt with pt_BR
|
||||||
|
[\#1009](https://github.com/matrix-org/matrix-react-sdk/pull/1009)
|
||||||
|
* Update from Weblate.
|
||||||
|
[\#1008](https://github.com/matrix-org/matrix-react-sdk/pull/1008)
|
||||||
|
* Update from Weblate.
|
||||||
|
[\#1003](https://github.com/matrix-org/matrix-react-sdk/pull/1003)
|
||||||
|
* allow hiding redactions, restoring old behaviour
|
||||||
|
[\#1004](https://github.com/matrix-org/matrix-react-sdk/pull/1004)
|
||||||
|
* Add missing translations
|
||||||
|
[\#1005](https://github.com/matrix-org/matrix-react-sdk/pull/1005)
|
||||||
|
|
||||||
|
Changes in [0.9.0-rc.2](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v0.9.0-rc.2) (2017-06-02)
|
||||||
|
=============================================================================================================
|
||||||
|
[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v0.9.0-rc.1...v0.9.0-rc.2)
|
||||||
|
|
||||||
|
* Update from Weblate.
|
||||||
|
[\#1002](https://github.com/matrix-org/matrix-react-sdk/pull/1002)
|
||||||
|
* webrtc config electron
|
||||||
|
[\#850](https://github.com/matrix-org/matrix-react-sdk/pull/850)
|
||||||
|
* enable useCompactLayout user setting an add a class when it's enabled
|
||||||
|
[\#986](https://github.com/matrix-org/matrix-react-sdk/pull/986)
|
||||||
|
* Update from Weblate.
|
||||||
|
[\#987](https://github.com/matrix-org/matrix-react-sdk/pull/987)
|
||||||
|
* Translation fixes for everything but src/components
|
||||||
|
[\#990](https://github.com/matrix-org/matrix-react-sdk/pull/990)
|
||||||
|
* Fix tests
|
||||||
|
[\#1001](https://github.com/matrix-org/matrix-react-sdk/pull/1001)
|
||||||
|
* Fix tests for PR #989
|
||||||
|
[\#999](https://github.com/matrix-org/matrix-react-sdk/pull/999)
|
||||||
|
* Revert "Revert "add labels to language picker""
|
||||||
|
[\#1000](https://github.com/matrix-org/matrix-react-sdk/pull/1000)
|
||||||
|
* maybe fixxy [Electron] external thing?
|
||||||
|
[\#997](https://github.com/matrix-org/matrix-react-sdk/pull/997)
|
||||||
|
* travisci: Don't run the riot-web tests if the react-sdk tests fail
|
||||||
|
[\#992](https://github.com/matrix-org/matrix-react-sdk/pull/992)
|
||||||
|
* Support 12hr time on DateSeparator
|
||||||
|
[\#991](https://github.com/matrix-org/matrix-react-sdk/pull/991)
|
||||||
|
* Revert "add labels to language picker"
|
||||||
|
[\#994](https://github.com/matrix-org/matrix-react-sdk/pull/994)
|
||||||
|
* Call MatrixClient.clearStores on logout
|
||||||
|
[\#983](https://github.com/matrix-org/matrix-react-sdk/pull/983)
|
||||||
|
* Matthew/room avatar event
|
||||||
|
[\#988](https://github.com/matrix-org/matrix-react-sdk/pull/988)
|
||||||
|
* add labels to language picker
|
||||||
|
[\#989](https://github.com/matrix-org/matrix-react-sdk/pull/989)
|
||||||
|
* Update from Weblate.
|
||||||
|
[\#981](https://github.com/matrix-org/matrix-react-sdk/pull/981)
|
||||||
|
|
||||||
|
Changes in [0.9.0-rc.1](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v0.9.0-rc.1) (2017-06-01)
|
||||||
|
=============================================================================================================
|
||||||
|
[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v0.8.9...v0.9.0-rc.1)
|
||||||
|
|
||||||
|
* Fix rare case where presence duration is undefined
|
||||||
|
[\#982](https://github.com/matrix-org/matrix-react-sdk/pull/982)
|
||||||
|
* add concept of platform handling loudNotifications (bings/pings/whatHaveYou)
|
||||||
|
[\#985](https://github.com/matrix-org/matrix-react-sdk/pull/985)
|
||||||
|
* Fixes to i18n code
|
||||||
|
[\#984](https://github.com/matrix-org/matrix-react-sdk/pull/984)
|
||||||
|
* Update from Weblate.
|
||||||
|
[\#978](https://github.com/matrix-org/matrix-react-sdk/pull/978)
|
||||||
|
* Add partial support for RTL languages
|
||||||
|
[\#955](https://github.com/matrix-org/matrix-react-sdk/pull/955)
|
||||||
|
* Added two strings to translate
|
||||||
|
[\#975](https://github.com/matrix-org/matrix-react-sdk/pull/975)
|
||||||
|
* Update from Weblate.
|
||||||
|
[\#976](https://github.com/matrix-org/matrix-react-sdk/pull/976)
|
||||||
|
* Update from Weblate.
|
||||||
|
[\#974](https://github.com/matrix-org/matrix-react-sdk/pull/974)
|
||||||
|
* Initial Electron Settings - for Auto Launch
|
||||||
|
[\#920](https://github.com/matrix-org/matrix-react-sdk/pull/920)
|
||||||
|
* Fix missing string in the room settings
|
||||||
|
[\#973](https://github.com/matrix-org/matrix-react-sdk/pull/973)
|
||||||
|
* fix error in i18n string
|
||||||
|
[\#972](https://github.com/matrix-org/matrix-react-sdk/pull/972)
|
||||||
|
* Update from Weblate.
|
||||||
|
[\#970](https://github.com/matrix-org/matrix-react-sdk/pull/970)
|
||||||
|
* Support 12hr time in full date
|
||||||
|
[\#971](https://github.com/matrix-org/matrix-react-sdk/pull/971)
|
||||||
|
* Add _tJsx()
|
||||||
|
[\#968](https://github.com/matrix-org/matrix-react-sdk/pull/968)
|
||||||
|
* Update from Weblate.
|
||||||
|
[\#966](https://github.com/matrix-org/matrix-react-sdk/pull/966)
|
||||||
|
* Remove space between time and AM/PM
|
||||||
|
[\#969](https://github.com/matrix-org/matrix-react-sdk/pull/969)
|
||||||
|
* Piwik Analytics
|
||||||
|
[\#948](https://github.com/matrix-org/matrix-react-sdk/pull/948)
|
||||||
|
* Update from Weblate.
|
||||||
|
[\#965](https://github.com/matrix-org/matrix-react-sdk/pull/965)
|
||||||
|
* Improve ChatInviteDialog perf by ditching fuse, using indexOf and
|
||||||
|
lastActiveTs()
|
||||||
|
[\#960](https://github.com/matrix-org/matrix-react-sdk/pull/960)
|
||||||
|
* Say "X removed the room name" instead of showing nothing
|
||||||
|
[\#958](https://github.com/matrix-org/matrix-react-sdk/pull/958)
|
||||||
|
* roomview/roomheader fixes
|
||||||
|
[\#959](https://github.com/matrix-org/matrix-react-sdk/pull/959)
|
||||||
|
* Update from Weblate.
|
||||||
|
[\#953](https://github.com/matrix-org/matrix-react-sdk/pull/953)
|
||||||
|
* fix i18n in a situation where navigator.languages=[]
|
||||||
|
[\#956](https://github.com/matrix-org/matrix-react-sdk/pull/956)
|
||||||
|
* `t_` -> `_t` fix typo
|
||||||
|
[\#957](https://github.com/matrix-org/matrix-react-sdk/pull/957)
|
||||||
|
* Change redact -> remove for clarity
|
||||||
|
[\#831](https://github.com/matrix-org/matrix-react-sdk/pull/831)
|
||||||
|
* Update from Weblate.
|
||||||
|
[\#950](https://github.com/matrix-org/matrix-react-sdk/pull/950)
|
||||||
|
* fix mis-linting - missed it in code review :(
|
||||||
|
[\#952](https://github.com/matrix-org/matrix-react-sdk/pull/952)
|
||||||
|
* i18n fixes
|
||||||
|
[\#951](https://github.com/matrix-org/matrix-react-sdk/pull/951)
|
||||||
|
* Message Forwarding
|
||||||
|
[\#812](https://github.com/matrix-org/matrix-react-sdk/pull/812)
|
||||||
|
* don't focus_composer on window focus
|
||||||
|
[\#944](https://github.com/matrix-org/matrix-react-sdk/pull/944)
|
||||||
|
* Fix vector-im/riot-web#4042
|
||||||
|
[\#947](https://github.com/matrix-org/matrix-react-sdk/pull/947)
|
||||||
|
* import _t, drop two unused imports
|
||||||
|
[\#946](https://github.com/matrix-org/matrix-react-sdk/pull/946)
|
||||||
|
* Fix punctuation in TextForEvent to be i18n'd consistently
|
||||||
|
[\#945](https://github.com/matrix-org/matrix-react-sdk/pull/945)
|
||||||
|
* actually wire up alwaysShowTimestamps
|
||||||
|
[\#940](https://github.com/matrix-org/matrix-react-sdk/pull/940)
|
||||||
|
* Update from Weblate.
|
||||||
|
[\#943](https://github.com/matrix-org/matrix-react-sdk/pull/943)
|
||||||
|
* Update from Weblate.
|
||||||
|
[\#942](https://github.com/matrix-org/matrix-react-sdk/pull/942)
|
||||||
|
* Update from Weblate.
|
||||||
|
[\#941](https://github.com/matrix-org/matrix-react-sdk/pull/941)
|
||||||
|
* Update from Weblate.
|
||||||
|
[\#938](https://github.com/matrix-org/matrix-react-sdk/pull/938)
|
||||||
|
* Fix PM being AM
|
||||||
|
[\#939](https://github.com/matrix-org/matrix-react-sdk/pull/939)
|
||||||
|
* pass call state through dispatcher, for poor electron
|
||||||
|
[\#918](https://github.com/matrix-org/matrix-react-sdk/pull/918)
|
||||||
|
* Translations!
|
||||||
|
[\#934](https://github.com/matrix-org/matrix-react-sdk/pull/934)
|
||||||
|
* Remove suffix and prefix from login input username
|
||||||
|
[\#906](https://github.com/matrix-org/matrix-react-sdk/pull/906)
|
||||||
|
* Kierangould/12hourtimestamp
|
||||||
|
[\#903](https://github.com/matrix-org/matrix-react-sdk/pull/903)
|
||||||
|
* Don't include src in the test resolve root
|
||||||
|
[\#931](https://github.com/matrix-org/matrix-react-sdk/pull/931)
|
||||||
|
* Make the linked versions open a new tab, turt2live complained :P
|
||||||
|
[\#910](https://github.com/matrix-org/matrix-react-sdk/pull/910)
|
||||||
|
* Fix lint errors in SlashCommands
|
||||||
|
[\#919](https://github.com/matrix-org/matrix-react-sdk/pull/919)
|
||||||
|
* autoFocus input box
|
||||||
|
[\#911](https://github.com/matrix-org/matrix-react-sdk/pull/911)
|
||||||
|
* Make travis test against riot-web new-guest-access
|
||||||
|
[\#917](https://github.com/matrix-org/matrix-react-sdk/pull/917)
|
||||||
|
* Add right-branch logic to travis test script
|
||||||
|
[\#916](https://github.com/matrix-org/matrix-react-sdk/pull/916)
|
||||||
|
* Group e2e keys into blocks of 4 characters
|
||||||
|
[\#914](https://github.com/matrix-org/matrix-react-sdk/pull/914)
|
||||||
|
* Factor out DeviceVerifyDialog
|
||||||
|
[\#913](https://github.com/matrix-org/matrix-react-sdk/pull/913)
|
||||||
|
* Fix 'missing page_type' error
|
||||||
|
[\#909](https://github.com/matrix-org/matrix-react-sdk/pull/909)
|
||||||
|
* code style update
|
||||||
|
[\#904](https://github.com/matrix-org/matrix-react-sdk/pull/904)
|
||||||
|
|
||||||
Changes in [0.8.9](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v0.8.9) (2017-05-22)
|
Changes in [0.8.9](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v0.8.9) (2017-05-22)
|
||||||
===================================================================================================
|
===================================================================================================
|
||||||
[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v0.8.9-rc.1...v0.8.9)
|
[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v0.8.9-rc.1...v0.8.9)
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -177,6 +177,11 @@ module.exports = function (config) {
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
devtool: 'inline-source-map',
|
devtool: 'inline-source-map',
|
||||||
|
externals: {
|
||||||
|
// Don't try to bundle electron: leave it as a commonjs dependency
|
||||||
|
// (the 'commonjs' here means it will output a 'require')
|
||||||
|
"electron": "commonjs electron",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
webpackMiddleware: {
|
webpackMiddleware: {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "matrix-react-sdk",
|
"name": "matrix-react-sdk",
|
||||||
"version": "0.8.9",
|
"version": "0.9.2",
|
||||||
"description": "SDK for matrix.org using React",
|
"description": "SDK for matrix.org using React",
|
||||||
"author": "matrix.org",
|
"author": "matrix.org",
|
||||||
"repository": {
|
"repository": {
|
||||||
|
@ -64,8 +64,9 @@
|
||||||
"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": "matrix-org/matrix-js-sdk#develop",
|
"matrix-js-sdk": "0.7.10",
|
||||||
"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",
|
||||||
|
|
|
@ -178,12 +178,12 @@ sub read_src_strings {
|
||||||
$src =~ s/"\s*\+\s*"//g;
|
$src =~ s/"\s*\+\s*"//g;
|
||||||
|
|
||||||
$file =~ s/^.*\/src/src/;
|
$file =~ s/^.*\/src/src/;
|
||||||
while ($src =~ /_t\(\s*'(.*?[^\\])'/sg) {
|
while ($src =~ /_t(?:Jsx)?\(\s*'(.*?[^\\])'/sg) {
|
||||||
my $s = $1;
|
my $s = $1;
|
||||||
$s =~ s/\\'/'/g;
|
$s =~ s/\\'/'/g;
|
||||||
push @$strings, [$s, $file];
|
push @$strings, [$s, $file];
|
||||||
}
|
}
|
||||||
while ($src =~ /_t\(\s*"(.*?[^\\])"/sg) {
|
while ($src =~ /_t(?:Jsx)?\(\s*"(.*?[^\\])"/sg) {
|
||||||
push @$strings, [$1, $file];
|
push @$strings, [$1, $file];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -48,6 +48,29 @@ Guests can't use labs features. Please register.
|
||||||
A new password must be entered.
|
A new password must be entered.
|
||||||
Resetting password will currently reset any end-to-end encryption keys on all devices, making encrypted chat history unreadable, unless you first export your room keys and re-import them afterwards. In future this will be improved.
|
Resetting password will currently reset any end-to-end encryption keys on all devices, making encrypted chat history unreadable, unless you first export your room keys and re-import them afterwards. In future this will be improved.
|
||||||
Guests cannot join this room even if explicitly invited.
|
Guests cannot join this room even if explicitly invited.
|
||||||
|
Guest users can't invite users. Please register to invite.
|
||||||
|
This room is inaccessible to guests. You may be able to join if you register.
|
||||||
|
delete the alias.
|
||||||
|
remove %(name)s from the directory.
|
||||||
|
Conference call failed.
|
||||||
|
Conference calling is in development and may not be reliable.
|
||||||
|
Guest users can't create new rooms. Please register to create room and start a chat.
|
||||||
|
Server may be unavailable, overloaded, or you hit a bug.
|
||||||
|
Server unavailable, overloaded, or something else went wrong.
|
||||||
|
You are already in a call.
|
||||||
|
You cannot place VoIP calls in this browser.
|
||||||
|
You cannot place a call with yourself.
|
||||||
|
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
|
||||||
)];
|
)];
|
||||||
}
|
}
|
||||||
|
@ -71,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']);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -71,6 +71,9 @@ export default class BasePlatform {
|
||||||
displayNotification(title: string, msg: string, avatarUrl: string, room: Object) {
|
displayNotification(title: string, msg: string, avatarUrl: string, room: Object) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
loudNotification(ev: Event, room: Object) {
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a promise that resolves to a string representing
|
* Returns a promise that resolves to a string representing
|
||||||
* the current version of the application.
|
* the current version of the application.
|
||||||
|
|
|
@ -226,7 +226,7 @@ function _onAction(payload) {
|
||||||
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||||
Modal.createDialog(ErrorDialog, {
|
Modal.createDialog(ErrorDialog, {
|
||||||
title: _t('Existing Call'),
|
title: _t('Existing Call'),
|
||||||
description: _t('You are already in a call') + '.',
|
description: _t('You are already in a call.'),
|
||||||
});
|
});
|
||||||
return; // don't allow >1 call to be placed.
|
return; // don't allow >1 call to be placed.
|
||||||
}
|
}
|
||||||
|
@ -236,7 +236,7 @@ function _onAction(payload) {
|
||||||
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||||
Modal.createDialog(ErrorDialog, {
|
Modal.createDialog(ErrorDialog, {
|
||||||
title: _t('VoIP is unsupported'),
|
title: _t('VoIP is unsupported'),
|
||||||
description: _t('You cannot place VoIP calls in this browser') + '.',
|
description: _t('You cannot place VoIP calls in this browser.'),
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -251,7 +251,7 @@ function _onAction(payload) {
|
||||||
if (members.length <= 1) {
|
if (members.length <= 1) {
|
||||||
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||||
Modal.createDialog(ErrorDialog, {
|
Modal.createDialog(ErrorDialog, {
|
||||||
description: _t('You cannot place a call with yourself') + '.',
|
description: _t('You cannot place a call with yourself.'),
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -284,7 +284,7 @@ function _onAction(payload) {
|
||||||
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||||
Modal.createDialog(ErrorDialog, {
|
Modal.createDialog(ErrorDialog, {
|
||||||
title: _t('VoIP is unsupported'),
|
title: _t('VoIP is unsupported'),
|
||||||
description: _t('You cannot place VoIP calls in this browser') + '.',
|
description: _t('You cannot place VoIP calls in this browser.'),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
else if (MatrixClientPeg.get().isRoomEncrypted(payload.room_id)) {
|
else if (MatrixClientPeg.get().isRoomEncrypted(payload.room_id)) {
|
||||||
|
@ -303,7 +303,7 @@ function _onAction(payload) {
|
||||||
var QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
|
var QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
|
||||||
Modal.createDialog(QuestionDialog, {
|
Modal.createDialog(QuestionDialog, {
|
||||||
title: _t('Warning!'),
|
title: _t('Warning!'),
|
||||||
description: _t('Conference calling is in development and may not be reliable') + '.',
|
description: _t('Conference calling is in development and may not be reliable.'),
|
||||||
onFinished: confirm=>{
|
onFinished: confirm=>{
|
||||||
if (confirm) {
|
if (confirm) {
|
||||||
ConferenceHandler.createNewMatrixCall(
|
ConferenceHandler.createNewMatrixCall(
|
||||||
|
@ -315,7 +315,7 @@ function _onAction(payload) {
|
||||||
console.error("Conference call failed: " + err);
|
console.error("Conference call failed: " + err);
|
||||||
Modal.createDialog(ErrorDialog, {
|
Modal.createDialog(ErrorDialog, {
|
||||||
title: _t('Failed to set up conference call'),
|
title: _t('Failed to set up conference call'),
|
||||||
description: _t('Conference call failed') + '. ' + ((err && err.message) ? err.message : ''),
|
description: _t('Conference call failed.') + ' ' + ((err && err.message) ? err.message : ''),
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
64
src/CallMediaHandler.js
Normal file
64
src/CallMediaHandler.js
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
/*
|
||||||
|
Copyright 2017 Michael Telatynski <7t3chguy@gmail.com>
|
||||||
|
|
||||||
|
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 UserSettingsStore from './UserSettingsStore';
|
||||||
|
import * as Matrix from 'matrix-js-sdk';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
getDevices: function() {
|
||||||
|
// Only needed for Electron atm, though should work in modern browsers
|
||||||
|
// once permission has been granted to the webapp
|
||||||
|
return navigator.mediaDevices.enumerateDevices().then(function(devices) {
|
||||||
|
const audioIn = [];
|
||||||
|
const videoIn = [];
|
||||||
|
|
||||||
|
if (devices.some((device) => !device.label)) return false;
|
||||||
|
|
||||||
|
devices.forEach((device) => {
|
||||||
|
switch (device.kind) {
|
||||||
|
case 'audioinput': audioIn.push(device); break;
|
||||||
|
case 'videoinput': videoIn.push(device); break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// console.log("Loaded WebRTC Devices", mediaDevices);
|
||||||
|
return {
|
||||||
|
audioinput: audioIn,
|
||||||
|
videoinput: videoIn,
|
||||||
|
};
|
||||||
|
}, (error) => { console.log('Unable to refresh WebRTC Devices: ', error); });
|
||||||
|
},
|
||||||
|
|
||||||
|
loadDevices: function() {
|
||||||
|
// this.getDevices().then((devices) => {
|
||||||
|
const localSettings = UserSettingsStore.getLocalSettings();
|
||||||
|
// // if deviceId is not found, automatic fallback is in spec
|
||||||
|
// // recall previously stored inputs if any
|
||||||
|
Matrix.setMatrixCallAudioInput(localSettings['webrtc_audioinput']);
|
||||||
|
Matrix.setMatrixCallVideoInput(localSettings['webrtc_videoinput']);
|
||||||
|
// });
|
||||||
|
},
|
||||||
|
|
||||||
|
setAudioInput: function(deviceId) {
|
||||||
|
UserSettingsStore.setLocalSetting('webrtc_audioinput', deviceId);
|
||||||
|
Matrix.setMatrixCallAudioInput(deviceId);
|
||||||
|
},
|
||||||
|
|
||||||
|
setVideoInput: function(deviceId) {
|
||||||
|
UserSettingsStore.setLocalSetting('webrtc_videoinput', deviceId);
|
||||||
|
Matrix.setMatrixCallVideoInput(deviceId);
|
||||||
|
},
|
||||||
|
};
|
|
@ -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;
|
||||||
|
@ -360,7 +361,24 @@ export function bodyToHtml(content, highlights, opts) {
|
||||||
'mx_EventTile_bigEmoji': emojiBody,
|
'mx_EventTile_bigEmoji': emojiBody,
|
||||||
'markdown-body': isHtml,
|
'markdown-body': isHtml,
|
||||||
});
|
});
|
||||||
return <span className={className} dangerouslySetInnerHTML={{ __html: safeBody }} />;
|
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) {
|
||||||
|
|
|
@ -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,
|
||||||
};
|
};
|
||||||
|
|
|
@ -187,6 +187,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);
|
||||||
|
@ -232,7 +240,9 @@ function _handleRestoreFailure(e) {
|
||||||
let msg = e.message;
|
let msg = e.message;
|
||||||
if (msg == "OLM.BAD_LEGACY_ACCOUNT_PICKLE") {
|
if (msg == "OLM.BAD_LEGACY_ACCOUNT_PICKLE") {
|
||||||
msg = _t(
|
msg = _t(
|
||||||
'You need to log back in to generate end-to-end encryption keys for this device and submit the public key to your homeserver. This is a once off; sorry for the inconvenience.'
|
'You need to log back in to generate end-to-end encryption keys'
|
||||||
|
+ ' for this device and submit the public key to your homeserver.'
|
||||||
|
+ ' This is a once off; sorry for the inconvenience.',
|
||||||
);
|
);
|
||||||
|
|
||||||
_clearLocalStorage();
|
_clearLocalStorage();
|
||||||
|
@ -312,6 +322,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);
|
||||||
|
|
21
src/Login.js
21
src/Login.js
|
@ -16,6 +16,7 @@ limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import Matrix from "matrix-js-sdk";
|
import Matrix from "matrix-js-sdk";
|
||||||
|
import { _t } from "./languageHandler";
|
||||||
|
|
||||||
import q from 'q';
|
import q from 'q';
|
||||||
import url from 'url';
|
import url from 'url';
|
||||||
|
@ -96,11 +97,6 @@ export default class Login {
|
||||||
guest: true
|
guest: true
|
||||||
};
|
};
|
||||||
}, (error) => {
|
}, (error) => {
|
||||||
if (error.httpStatus === 403) {
|
|
||||||
error.friendlyText = "Guest access is disabled on this Home Server.";
|
|
||||||
} else {
|
|
||||||
error.friendlyText = "Failed to register as guest: " + error.data;
|
|
||||||
}
|
|
||||||
throw error;
|
throw error;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -156,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 = (
|
|
||||||
'This Home Server does not support login using email address.'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
else if (error.httpStatus === 403) {
|
|
||||||
error.friendlyText = (
|
|
||||||
'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,
|
||||||
|
@ -185,11 +173,6 @@ export default class Login {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
|
||||||
error.friendlyText = (
|
|
||||||
'There was a problem logging in. (HTTP ' + error.httpStatus + ")"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
throw error;
|
throw error;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,7 +16,6 @@ limitations under the License.
|
||||||
|
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
import q from "q";
|
|
||||||
import Matrix from 'matrix-js-sdk';
|
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';
|
||||||
|
|
|
@ -250,6 +250,7 @@ const Notifier = {
|
||||||
this._displayPopupNotification(ev, room);
|
this._displayPopupNotification(ev, room);
|
||||||
}
|
}
|
||||||
if (actions.tweaks.sound && this.isAudioEnabled()) {
|
if (actions.tweaks.sound && this.isAudioEnabled()) {
|
||||||
|
PlatformPeg.get().loudNotification(ev, room);
|
||||||
this._playAudioNotification(ev, room);
|
this._playAudioNotification(ev, room);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -82,7 +82,7 @@ class PasswordReset {
|
||||||
err.message = _t('Failed to verify email address: make sure you clicked the link in the email');
|
err.message = _t('Failed to verify email address: make sure you clicked the link in the email');
|
||||||
}
|
}
|
||||||
else if (err.httpStatus === 404) {
|
else if (err.httpStatus === 404) {
|
||||||
err.message = _t('Your email address does not appear to be associated with a Matrix ID on this Homeserver') + '.';
|
err.message = _t('Your email address does not appear to be associated with a Matrix ID on this Homeserver.');
|
||||||
}
|
}
|
||||||
else if (err.httpStatus) {
|
else if (err.httpStatus) {
|
||||||
err.message += ` (Status ${err.httpStatus})`;
|
err.message += ` (Status ${err.httpStatus})`;
|
||||||
|
|
|
@ -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';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -23,22 +23,28 @@ class Skinner {
|
||||||
if (this.components === null) {
|
if (this.components === null) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
"Attempted to get a component before a skin has been loaded."+
|
"Attempted to get a component before a skin has been loaded."+
|
||||||
"This is probably because either:"+
|
" This is probably because either:"+
|
||||||
" a) Your app has not called sdk.loadSkin(), or"+
|
" a) Your app has not called sdk.loadSkin(), or"+
|
||||||
" b) A component has called getComponent at the root level"
|
" b) A component has called getComponent at the root level",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
var comp = this.components[name];
|
let comp = this.components[name];
|
||||||
if (comp) {
|
|
||||||
return comp;
|
|
||||||
}
|
|
||||||
// XXX: Temporarily also try 'views.' as we're currently
|
// XXX: Temporarily also try 'views.' as we're currently
|
||||||
// leaving the 'views.' off views.
|
// leaving the 'views.' off views.
|
||||||
var comp = this.components['views.'+name];
|
if (!comp) {
|
||||||
if (comp) {
|
comp = this.components['views.'+name];
|
||||||
return comp;
|
|
||||||
}
|
}
|
||||||
throw new Error("No such component: "+name);
|
|
||||||
|
if (!comp) {
|
||||||
|
throw new Error("No such component: "+name);
|
||||||
|
}
|
||||||
|
|
||||||
|
// components have to be functions.
|
||||||
|
const validType = typeof comp === 'function';
|
||||||
|
if (!validType) {
|
||||||
|
throw new Error(`Not a valid component: ${name}.`);
|
||||||
|
}
|
||||||
|
return comp;
|
||||||
}
|
}
|
||||||
|
|
||||||
load(skinObject) {
|
load(skinObject) {
|
||||||
|
|
|
@ -186,7 +186,7 @@ const commands = {
|
||||||
if (targetRoomId) { break; }
|
if (targetRoomId) { break; }
|
||||||
}
|
}
|
||||||
if (!targetRoomId) {
|
if (!targetRoomId) {
|
||||||
return reject("Unrecognised room alias: " + roomAlias);
|
return reject(_t("Unrecognised room alias:") + ' ' + roomAlias);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -303,14 +303,14 @@ const commands = {
|
||||||
|
|
||||||
const device = MatrixClientPeg.get().getStoredDevice(userId, deviceId);
|
const device = MatrixClientPeg.get().getStoredDevice(userId, deviceId);
|
||||||
if (!device) {
|
if (!device) {
|
||||||
return reject(`Unknown (user, device) pair: (${userId}, ${deviceId})`);
|
return reject(_t(`Unknown (user, device) pair:`) + ` (${userId}, ${deviceId})`);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (device.isVerified()) {
|
if (device.isVerified()) {
|
||||||
if (device.getFingerprint() === fingerprint) {
|
if (device.getFingerprint() === fingerprint) {
|
||||||
return reject(`Device already verified!`);
|
return reject(_t(`Device already verified!`));
|
||||||
} else {
|
} else {
|
||||||
return reject(`WARNING: Device already verified, but keys do NOT MATCH!`);
|
return reject(_t(`WARNING: Device already verified, but keys do NOT MATCH!`));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -322,12 +322,15 @@ const commands = {
|
||||||
// Tell the user we verified everything!
|
// Tell the user we verified everything!
|
||||||
const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
|
const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
|
||||||
Modal.createDialog(QuestionDialog, {
|
Modal.createDialog(QuestionDialog, {
|
||||||
title: "Verified key",
|
title: _t("Verified key"),
|
||||||
description: (
|
description: (
|
||||||
<div>
|
<div>
|
||||||
<p>
|
<p>
|
||||||
The signing key you provided matches the signing key you received
|
{
|
||||||
from { userId }'s device { deviceId }. Device marked as verified.
|
_t("The signing key you provided matches the signing key you received " +
|
||||||
|
"from %(userId)s's device %(deviceId)s. Device marked as verified.",
|
||||||
|
{userId: userId, deviceId: deviceId})
|
||||||
|
}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
),
|
),
|
||||||
|
@ -336,9 +339,13 @@ const commands = {
|
||||||
|
|
||||||
return success();
|
return success();
|
||||||
} else {
|
} else {
|
||||||
return reject(`WARNING: KEY VERIFICATION FAILED! The signing key for ${userId} and device
|
const fprint = device.getFingerprint();
|
||||||
${deviceId} is "${device.getFingerprint()}" which does not match the provided key
|
return reject(
|
||||||
"${fingerprint}". This could mean your communications are being intercepted!`);
|
_t('WARNING: KEY VERIFICATION FAILED! The signing key for %(userId)s and device' +
|
||||||
|
' %(deviceId)s is "%(fprint)s" which does not match the provided key' +
|
||||||
|
' "%(fingerprint)s". This could mean your communications are being intercepted!',
|
||||||
|
{deviceId: deviceId, fprint: fprint, userId: userId, fingerprint: fingerprint})
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -383,7 +390,7 @@ module.exports = {
|
||||||
if (commands[cmd]) {
|
if (commands[cmd]) {
|
||||||
return commands[cmd].run(roomId, args);
|
return commands[cmd].run(roomId, args);
|
||||||
} else {
|
} else {
|
||||||
return reject("Unrecognised command: " + input);
|
return reject(_t("Unrecognised command:") + ' ' + input);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null; // not a command
|
return null; // not a command
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -17,24 +17,26 @@ limitations under the License.
|
||||||
import q from 'q';
|
import q from 'q';
|
||||||
import MatrixClientPeg from './MatrixClientPeg';
|
import MatrixClientPeg from './MatrixClientPeg';
|
||||||
import Notifier from './Notifier';
|
import Notifier from './Notifier';
|
||||||
|
import { _t } from './languageHandler';
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* TODO: Make things use this. This is all WIP - see UserSettings.js for usage.
|
* TODO: Make things use this. This is all WIP - see UserSettings.js for usage.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/*
|
|
||||||
* TODO: Find a way to translate the names of LABS_FEATURES. In other words, guarantee that languages were already loaded before building this array.
|
|
||||||
*/
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
LABS_FEATURES: [
|
LABS_FEATURES: [
|
||||||
{
|
{
|
||||||
name: "New Composer & Autocomplete",
|
name: "-",
|
||||||
id: 'rich_text_editor',
|
id: 'rich_text_editor',
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|
||||||
|
// horrible but it works. The locality makes this somewhat more palatable.
|
||||||
|
doTranslations: function() {
|
||||||
|
this.LABS_FEATURES[0].name = _t("New Composer & Autocomplete");
|
||||||
|
},
|
||||||
|
|
||||||
loadProfileInfo: function() {
|
loadProfileInfo: function() {
|
||||||
const cli = MatrixClientPeg.get();
|
const cli = MatrixClientPeg.get();
|
||||||
return cli.getProfileInfo(cli.credentials.userId);
|
return cli.getProfileInfo(cli.credentials.userId);
|
||||||
|
|
|
@ -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'>
|
||||||
|
@ -166,11 +168,11 @@ export default React.createClass({
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className='mx_Dialog_buttons'>
|
<div className='mx_Dialog_buttons'>
|
||||||
<input className='mx_Dialog_primary' type='submit' value='Export'
|
<input className='mx_Dialog_primary' type='submit' value={_t('Export')}
|
||||||
disabled={disableForm}
|
disabled={disableForm}
|
||||||
/>
|
/>
|
||||||
<button onClick={this._onCancelClick} disabled={disableForm}>
|
<button onClick={this._onCancelClick} disabled={disableForm}>
|
||||||
Cancel
|
{_t("Cancel")}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
|
@ -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'>
|
||||||
|
@ -164,11 +166,11 @@ export default React.createClass({
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className='mx_Dialog_buttons'>
|
<div className='mx_Dialog_buttons'>
|
||||||
<input className='mx_Dialog_primary' type='submit' value='Import'
|
<input className='mx_Dialog_primary' type='submit' value={_t('Import')}
|
||||||
disabled={!this.state.enableSubmit || disableForm}
|
disabled={!this.state.enableSubmit || disableForm}
|
||||||
/>
|
/>
|
||||||
<button onClick={this._onCancelClick} disabled={disableForm}>
|
<button onClick={this._onCancelClick} disabled={disableForm}>
|
||||||
Cancel
|
{_t("Cancel")}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
|
@ -1,3 +1,20 @@
|
||||||
|
/*
|
||||||
|
Copyright 2016 Aviral Dasgupta
|
||||||
|
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 React from 'react';
|
||||||
import type {Completion, SelectionRange} from './Autocompleter';
|
import type {Completion, SelectionRange} from './Autocompleter';
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,19 @@
|
||||||
|
/*
|
||||||
|
Copyright 2016 Aviral Dasgupta
|
||||||
|
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
|
||||||
// @flow
|
// @flow
|
||||||
|
|
||||||
import type {Component} from 'react';
|
import type {Component} from 'react';
|
||||||
|
|
|
@ -1,3 +1,20 @@
|
||||||
|
/*
|
||||||
|
Copyright 2016 Aviral Dasgupta
|
||||||
|
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 React from 'react';
|
||||||
import { _t } from '../languageHandler';
|
import { _t } from '../languageHandler';
|
||||||
import AutocompleteProvider from './AutocompleteProvider';
|
import AutocompleteProvider from './AutocompleteProvider';
|
||||||
|
|
|
@ -1,5 +1,20 @@
|
||||||
|
/*
|
||||||
|
Copyright 2016 Aviral Dasgupta
|
||||||
|
|
||||||
|
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 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
|
||||||
|
|
|
@ -1,4 +1,22 @@
|
||||||
|
/*
|
||||||
|
Copyright 2016 Aviral Dasgupta
|
||||||
|
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 React from 'react';
|
||||||
|
import { _t } from '../languageHandler';
|
||||||
import AutocompleteProvider from './AutocompleteProvider';
|
import AutocompleteProvider from './AutocompleteProvider';
|
||||||
import 'whatwg-fetch';
|
import 'whatwg-fetch';
|
||||||
|
|
||||||
|
@ -75,7 +93,7 @@ export default class DuckDuckGoProvider extends AutocompleteProvider {
|
||||||
}
|
}
|
||||||
|
|
||||||
getName() {
|
getName() {
|
||||||
return '🔍 Results from DuckDuckGo';
|
return '🔍 ' + _t('Results from DuckDuckGo');
|
||||||
}
|
}
|
||||||
|
|
||||||
static getInstance(): DuckDuckGoProvider {
|
static getInstance(): DuckDuckGoProvider {
|
||||||
|
|
|
@ -1,3 +1,20 @@
|
||||||
|
/*
|
||||||
|
Copyright 2016 Aviral Dasgupta
|
||||||
|
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 React from 'react';
|
||||||
import { _t } from '../languageHandler';
|
import { _t } from '../languageHandler';
|
||||||
import AutocompleteProvider from './AutocompleteProvider';
|
import AutocompleteProvider from './AutocompleteProvider';
|
||||||
|
|
|
@ -1,3 +1,20 @@
|
||||||
|
/*
|
||||||
|
Copyright 2016 Aviral Dasgupta
|
||||||
|
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 React from 'react';
|
||||||
import { _t } from '../languageHandler';
|
import { _t } from '../languageHandler';
|
||||||
import AutocompleteProvider from './AutocompleteProvider';
|
import AutocompleteProvider from './AutocompleteProvider';
|
||||||
|
|
|
@ -1,7 +1,23 @@
|
||||||
|
/*
|
||||||
|
Copyright 2016 Aviral Dasgupta
|
||||||
|
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 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({
|
||||||
|
|
|
@ -18,11 +18,15 @@ limitations under the License.
|
||||||
import * as Matrix from 'matrix-js-sdk';
|
import * as Matrix from 'matrix-js-sdk';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
|
import UserSettingsStore from '../../UserSettingsStore';
|
||||||
import KeyCode from '../../KeyCode';
|
import KeyCode from '../../KeyCode';
|
||||||
import Notifier from '../../Notifier';
|
import Notifier from '../../Notifier';
|
||||||
import PageTypes from '../../PageTypes';
|
import PageTypes from '../../PageTypes';
|
||||||
|
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
|
||||||
|
@ -39,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.
|
||||||
|
@ -63,6 +70,13 @@ export default React.createClass({
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
|
getInitialState: function() {
|
||||||
|
return {
|
||||||
|
// use compact timeline view
|
||||||
|
useCompactLayout: UserSettingsStore.getSyncedSetting('useCompactLayout'),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
componentWillMount: function() {
|
componentWillMount: function() {
|
||||||
// stash the MatrixClient in case we log out before we are unmounted
|
// stash the MatrixClient in case we log out before we are unmounted
|
||||||
this._matrixClient = this.props.matrixClient;
|
this._matrixClient = this.props.matrixClient;
|
||||||
|
@ -71,11 +85,35 @@ export default React.createClass({
|
||||||
// RoomView.getScrollState()
|
// RoomView.getScrollState()
|
||||||
this._scrollStateMap = {};
|
this._scrollStateMap = {};
|
||||||
|
|
||||||
|
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);
|
||||||
},
|
},
|
||||||
|
|
||||||
componentWillUnmount: function() {
|
componentWillUnmount: function() {
|
||||||
document.removeEventListener('keydown', this._onKeyDown);
|
document.removeEventListener('keydown', this._onKeyDown);
|
||||||
|
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) {
|
||||||
|
@ -89,6 +127,20 @@ 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) {
|
||||||
|
if (event.getType() === "im.vector.web.settings") {
|
||||||
|
this.setState({
|
||||||
|
useCompactLayout: event.getContent().useCompactLayout,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
_onKeyDown: function(ev) {
|
_onKeyDown: function(ev) {
|
||||||
/*
|
/*
|
||||||
// Remove this for now as ctrl+alt = alt-gr so this breaks keyboards which rely on alt-gr for numbers
|
// Remove this for now as ctrl+alt = alt-gr so this breaks keyboards which rely on alt-gr for numbers
|
||||||
|
@ -159,8 +211,8 @@ export default React.createClass({
|
||||||
const RoomDirectory = sdk.getComponent('structures.RoomDirectory');
|
const RoomDirectory = sdk.getComponent('structures.RoomDirectory');
|
||||||
const HomePage = sdk.getComponent('structures.HomePage');
|
const HomePage = sdk.getComponent('structures.HomePage');
|
||||||
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 = '';
|
||||||
|
@ -169,15 +221,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}
|
||||||
|
@ -214,12 +263,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:
|
||||||
|
@ -228,16 +283,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 />;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -245,6 +299,9 @@ export default React.createClass({
|
||||||
if (topBar) {
|
if (topBar) {
|
||||||
bodyClasses += ' mx_MatrixChat_toolbarShowing';
|
bodyClasses += ' mx_MatrixChat_toolbarShowing';
|
||||||
}
|
}
|
||||||
|
if (this.state.useCompactLayout) {
|
||||||
|
bodyClasses += ' mx_MatrixChat_useCompactLayout';
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='mx_MatrixChat_wrapper'>
|
<div className='mx_MatrixChat_wrapper'>
|
||||||
|
@ -254,7 +311,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,11 +34,14 @@ 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";
|
||||||
import * as UDEHandler from '../../UnknownDeviceErrorHandler';
|
import * as UDEHandler from '../../UnknownDeviceErrorHandler';
|
||||||
import { _t } from '../../languageHandler';
|
import { _t, getCurrentLanguage } from '../../languageHandler';
|
||||||
|
|
||||||
module.exports = React.createClass({
|
module.exports = React.createClass({
|
||||||
displayName: 'MatrixChat',
|
displayName: 'MatrixChat',
|
||||||
|
@ -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);
|
||||||
|
|
||||||
|
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
|
||||||
|
@ -312,8 +310,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);
|
||||||
|
@ -322,7 +318,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':
|
||||||
|
@ -349,32 +344,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();
|
|
||||||
|
|
||||||
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;
|
||||||
|
@ -396,7 +376,7 @@ module.exports = React.createClass({
|
||||||
}, (err) => {
|
}, (err) => {
|
||||||
modal.close();
|
modal.close();
|
||||||
Modal.createDialog(ErrorDialog, {
|
Modal.createDialog(ErrorDialog, {
|
||||||
title: _t('Failed to reject invitation'),
|
title: _t('Failed to reject invitation'),
|
||||||
description: err.toString(),
|
description: err.toString(),
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -435,37 +415,36 @@ 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: (should_create, name) => {
|
|
||||||
if (should_create) {
|
|
||||||
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);
|
||||||
this.notifyNewScreen('directory');
|
this.notifyNewScreen('directory');
|
||||||
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;
|
||||||
|
@ -507,7 +486,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);
|
||||||
|
@ -530,6 +513,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,
|
||||||
|
@ -552,10 +539,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) {
|
||||||
|
@ -565,62 +562,62 @@ 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,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
// switch view to the given room
|
// switch view to the given room
|
||||||
//
|
//
|
||||||
// @param {Object} room_info Object containing data about the room to be joined
|
// @param {Object} roomInfo Object containing data about the room to be joined
|
||||||
// @param {string=} room_info.room_id ID of the room to join. One of room_id or room_alias must be given.
|
// @param {string=} roomInfo.room_id ID of the room to join. One of room_id or room_alias must be given.
|
||||||
// @param {string=} room_info.room_alias Alias of the room to join. One of room_id or room_alias must be given.
|
// @param {string=} roomInfo.room_alias Alias of the room to join. One of room_id or room_alias must be given.
|
||||||
// @param {boolean=} room_info.auto_join If true, automatically attempt to join the room if not already a member.
|
// @param {boolean=} roomInfo.auto_join If true, automatically attempt to join the room if not already a member.
|
||||||
// @param {boolean=} room_info.show_settings Makes RoomView show the room settings dialog.
|
// @param {boolean=} roomInfo.show_settings Makes RoomView show the room settings dialog.
|
||||||
// @param {string=} room_info.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 {Object=} room_info.third_party_invite Object containing data about the third party
|
// @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
|
||||||
// we received to join the room, if any.
|
// we received to join the room, if any.
|
||||||
// @param {string=} room_info.third_party_invite.inviteSignUrl 3pid invite sign URL
|
// @param {string=} roomInfo.third_party_invite.inviteSignUrl 3pid invite sign URL
|
||||||
// @param {string=} room_info.third_party_invite.invitedEmail The email address the invite was sent to
|
// @param {string=} roomInfo.third_party_invite.invitedEmail The email address the invite was sent to
|
||||||
// @param {Object=} room_info.oob_data Object of additional data about the room
|
// @param {Object=} roomInfo.oob_data Object of additional data about the room
|
||||||
// that has been passed out-of-band (eg.
|
// that has been passed out-of-band (eg.
|
||||||
// room name and avatar from an invite email)
|
// room name and avatar from an invite email)
|
||||||
_viewRoom: function(room_info) {
|
_viewRoom: function(roomInfo) {
|
||||||
this.focusComposer = true;
|
this.focusComposer = true;
|
||||||
|
|
||||||
const newState = {
|
const newState = {
|
||||||
initialEventId: room_info.event_id,
|
|
||||||
highlightedEventId: room_info.event_id,
|
|
||||||
initialEventPixelOffset: undefined,
|
|
||||||
page_type: PageTypes.RoomView,
|
page_type: PageTypes.RoomView,
|
||||||
thirdPartyInvite: room_info.third_party_invite,
|
thirdPartyInvite: roomInfo.third_party_invite,
|
||||||
roomOobData: room_info.oob_data,
|
roomOobData: roomInfo.oob_data,
|
||||||
currentRoomAlias: room_info.room_alias,
|
autoJoin: roomInfo.auto_join,
|
||||||
autoJoin: room_info.auto_join,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!room_info.room_alias) {
|
if (roomInfo.room_alias) {
|
||||||
newState.currentRoomId = room_info.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 (!room_info.event_id && this.refs.loggedInView) {
|
);
|
||||||
const scrollState = this.refs.loggedInView.getScrollStateForRoom(room_info.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,
|
||||||
|
@ -628,15 +625,15 @@ module.exports = React.createClass({
|
||||||
let waitFor = q(null);
|
let waitFor = q(null);
|
||||||
if (!this.firstSyncComplete) {
|
if (!this.firstSyncComplete) {
|
||||||
if (!this.firstSyncPromise) {
|
if (!this.firstSyncPromise) {
|
||||||
console.warn('Cannot view a room before first sync. room_id:', room_info.room_id);
|
console.warn('Cannot view a room before first sync. room_id:', roomInfo.room_id);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
waitFor = this.firstSyncPromise.promise;
|
waitFor = this.firstSyncPromise.promise;
|
||||||
}
|
}
|
||||||
|
|
||||||
waitFor.done(() => {
|
waitFor.done(() => {
|
||||||
let presentedId = room_info.room_alias || room_info.room_id;
|
let presentedId = roomInfo.room_alias || roomInfo.room_id;
|
||||||
const room = MatrixClientPeg.get().getRoom(room_info.room_id);
|
const room = MatrixClientPeg.get().getRoom(roomInfo.room_id);
|
||||||
if (room) {
|
if (room) {
|
||||||
const theAlias = Rooms.getDisplayAliasForRoom(room);
|
const theAlias = Rooms.getDisplayAliasForRoom(room);
|
||||||
if (theAlias) presentedId = theAlias;
|
if (theAlias) presentedId = theAlias;
|
||||||
|
@ -648,8 +645,8 @@ module.exports = React.createClass({
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (room_info.event_id) {
|
if (roomInfo.event_id && roomInfo.highlighted) {
|
||||||
presentedId += "/" + room_info.event_id;
|
presentedId += "/" + roomInfo.event_id;
|
||||||
}
|
}
|
||||||
this.notifyNewScreen('room/' + presentedId);
|
this.notifyNewScreen('room/' + presentedId);
|
||||||
newState.ready = true;
|
newState.ready = true;
|
||||||
|
@ -657,16 +654,135 @@ 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'),
|
||||||
description: _t("Who would you like to communicate with?"),
|
description: _t("Who would you like to communicate with?"),
|
||||||
placeholder: _t("Email, name or matrix ID"),
|
placeholder: _t("Email, name or matrix ID"),
|
||||||
button: _t("Start Chat")
|
button: _t("Start Chat"),
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
_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, {
|
||||||
|
@ -684,8 +800,12 @@ module.exports = React.createClass({
|
||||||
|
|
||||||
const roomToLeave = MatrixClientPeg.get().getRoom(roomId);
|
const roomToLeave = MatrixClientPeg.get().getRoom(roomId);
|
||||||
Modal.createDialog(QuestionDialog, {
|
Modal.createDialog(QuestionDialog, {
|
||||||
title: "Leave room",
|
title: _t("Leave room"),
|
||||||
description: <span>Are you sure you want to leave the room <i>{roomToLeave.name}</i>?</span>,
|
description: (
|
||||||
|
<span>
|
||||||
|
{_t("Are you sure you want to leave the room '%(roomName)s'?", {roomName: roomToLeave.name})}
|
||||||
|
</span>
|
||||||
|
),
|
||||||
onFinished: (shouldLeave) => {
|
onFinished: (shouldLeave) => {
|
||||||
if (shouldLeave) {
|
if (shouldLeave) {
|
||||||
const d = MatrixClientPeg.get().leave(roomId);
|
const d = MatrixClientPeg.get().leave(roomId);
|
||||||
|
@ -696,7 +816,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) => {
|
||||||
|
@ -791,12 +911,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) {
|
||||||
if (this.props.config.welcomeUserId) {
|
this._is_registered = false;
|
||||||
createRoom({dmUserId: this.props.config.welcomeUserId});
|
// 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")) {
|
||||||
|
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();
|
||||||
}
|
}
|
||||||
|
@ -818,12 +953,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'});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -837,7 +968,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,
|
||||||
});
|
});
|
||||||
|
@ -875,6 +1005,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;
|
||||||
|
@ -944,6 +1080,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',
|
||||||
|
@ -971,6 +1112,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,
|
||||||
};
|
};
|
||||||
|
@ -987,6 +1132,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);
|
||||||
|
@ -1083,6 +1234,8 @@ module.exports = React.createClass({
|
||||||
},
|
},
|
||||||
|
|
||||||
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;
|
||||||
|
@ -1126,7 +1279,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() {
|
||||||
|
@ -1139,18 +1300,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;
|
||||||
|
@ -1192,9 +1346,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}
|
||||||
|
@ -1220,8 +1375,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}
|
||||||
|
|
|
@ -91,6 +91,9 @@ module.exports = React.createClass({
|
||||||
|
|
||||||
// show timestamps always
|
// show timestamps always
|
||||||
alwaysShowTimestamps: React.PropTypes.bool,
|
alwaysShowTimestamps: React.PropTypes.bool,
|
||||||
|
|
||||||
|
// hide redacted events as per old behaviour
|
||||||
|
hideRedactions: React.PropTypes.bool,
|
||||||
},
|
},
|
||||||
|
|
||||||
componentWillMount: function() {
|
componentWillMount: function() {
|
||||||
|
@ -353,7 +356,7 @@ module.exports = React.createClass({
|
||||||
const key = "membereventlistsummary-" + (prevEvent ? mxEv.getId() : "initial");
|
const key = "membereventlistsummary-" + (prevEvent ? mxEv.getId() : "initial");
|
||||||
|
|
||||||
if (this._wantsDateSeparator(prevEvent, mxEv.getDate())) {
|
if (this._wantsDateSeparator(prevEvent, mxEv.getDate())) {
|
||||||
let dateSeparator = <li key={ts1+'~'}><DateSeparator key={ts1+'~'} ts={ts1}/></li>;
|
let dateSeparator = <li key={ts1+'~'}><DateSeparator key={ts1+'~'} ts={ts1} showTwelveHour={this.props.isTwelveHour}/></li>;
|
||||||
ret.push(dateSeparator);
|
ret.push(dateSeparator);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -495,11 +498,13 @@ module.exports = React.createClass({
|
||||||
|
|
||||||
// do we need a date separator since the last event?
|
// do we need a date separator since the last event?
|
||||||
if (this._wantsDateSeparator(prevEvent, eventDate)) {
|
if (this._wantsDateSeparator(prevEvent, eventDate)) {
|
||||||
var dateSeparator = <li key={ts1}><DateSeparator key={ts1} ts={ts1}/></li>;
|
var dateSeparator = <li key={ts1}><DateSeparator key={ts1} ts={ts1} showTwelveHour={this.props.isTwelveHour}/></li>;
|
||||||
ret.push(dateSeparator);
|
ret.push(dateSeparator);
|
||||||
continuation = false;
|
continuation = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (mxEv.isRedacted() && this.props.hideRedactions) return ret;
|
||||||
|
|
||||||
var eventId = mxEv.getId();
|
var eventId = mxEv.getId();
|
||||||
var highlight = (eventId == this.props.highlightedEventId);
|
var highlight = (eventId == this.props.highlightedEventId);
|
||||||
|
|
||||||
|
|
|
@ -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,47 @@ 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(),
|
||||||
|
};
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
this._onHaveRoom();
|
||||||
|
this.onRoom(MatrixClientPeg.get().getRoom(this.state.roomId));
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
_onHaveRoom: function() {
|
_onHaveRoom: function() {
|
||||||
|
@ -223,26 +205,29 @@ 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 = MatrixClientPeg.get().getRoom(this.state.roomId);
|
||||||
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 +237,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 +285,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 +310,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 +339,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 +509,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 +589,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 +675,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 +692,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 +700,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 +766,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 +833,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 +885,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 +1243,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 +1395,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 +1417,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 +1435,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 +1665,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 +1680,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 }
|
||||||
|
|
|
@ -131,6 +131,8 @@ var TimelinePanel = React.createClass({
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const syncedSettings = UserSettingsStore.getSyncedSettings();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
events: [],
|
events: [],
|
||||||
timelineLoading: true, // track whether our room timeline is loading
|
timelineLoading: true, // track whether our room timeline is loading
|
||||||
|
@ -175,10 +177,13 @@ var TimelinePanel = React.createClass({
|
||||||
clientSyncState: MatrixClientPeg.get().getSyncState(),
|
clientSyncState: MatrixClientPeg.get().getSyncState(),
|
||||||
|
|
||||||
// should the event tiles have twelve hour times
|
// should the event tiles have twelve hour times
|
||||||
isTwelveHour: UserSettingsStore.getSyncedSetting('showTwelveHourTimestamps'),
|
isTwelveHour: syncedSettings.showTwelveHourTimestamps,
|
||||||
|
|
||||||
// always show timestamps on event tiles?
|
// always show timestamps on event tiles?
|
||||||
alwaysShowTimestamps: UserSettingsStore.getSyncedSetting('alwaysShowTimestamps'),
|
alwaysShowTimestamps: syncedSettings.alwaysShowTimestamps,
|
||||||
|
|
||||||
|
// hide redacted events as per old behaviour
|
||||||
|
hideRedactions: syncedSettings.hideRedactions,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -897,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");
|
||||||
|
|
||||||
|
@ -915,9 +923,9 @@ 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,
|
||||||
|
@ -1113,26 +1121,27 @@ var TimelinePanel = React.createClass({
|
||||||
);
|
);
|
||||||
return (
|
return (
|
||||||
<MessagePanel ref="messagePanel"
|
<MessagePanel ref="messagePanel"
|
||||||
hidden={ this.props.hidden }
|
hidden={ this.props.hidden }
|
||||||
backPaginating={ this.state.backPaginating }
|
hideRedactions={ this.state.hideRedactions }
|
||||||
forwardPaginating={ forwardPaginating }
|
backPaginating={ this.state.backPaginating }
|
||||||
events={ this.state.events }
|
forwardPaginating={ forwardPaginating }
|
||||||
highlightedEventId={ this.props.highlightedEventId }
|
events={ this.state.events }
|
||||||
readMarkerEventId={ this.state.readMarkerEventId }
|
highlightedEventId={ this.props.highlightedEventId }
|
||||||
readMarkerVisible={ this.state.readMarkerVisible }
|
readMarkerEventId={ this.state.readMarkerEventId }
|
||||||
suppressFirstDateSeparator={ this.state.canBackPaginate }
|
readMarkerVisible={ this.state.readMarkerVisible }
|
||||||
showUrlPreview = { this.props.showUrlPreview }
|
suppressFirstDateSeparator={ this.state.canBackPaginate }
|
||||||
manageReadReceipts = { this.props.manageReadReceipts }
|
showUrlPreview = { this.props.showUrlPreview }
|
||||||
ourUserId={ MatrixClientPeg.get().credentials.userId }
|
manageReadReceipts = { this.props.manageReadReceipts }
|
||||||
stickyBottom={ stickyBottom }
|
ourUserId={ MatrixClientPeg.get().credentials.userId }
|
||||||
onScroll={ this.onMessageListScroll }
|
stickyBottom={ stickyBottom }
|
||||||
onFillRequest={ this.onMessageListFillRequest }
|
onScroll={ this.onMessageListScroll }
|
||||||
onUnfillRequest={ this.onMessageListUnfillRequest }
|
onFillRequest={ this.onMessageListFillRequest }
|
||||||
opacity={ this.props.opacity }
|
onUnfillRequest={ this.onMessageListUnfillRequest }
|
||||||
isTwelveHour={ this.state.isTwelveHour }
|
opacity={ this.props.opacity }
|
||||||
alwaysShowTimestamps={ this.state.alwaysShowTimestamps }
|
isTwelveHour={ this.state.isTwelveHour }
|
||||||
className={ this.props.className }
|
alwaysShowTimestamps={ this.state.alwaysShowTimestamps }
|
||||||
tileShape={ this.props.tileShape }
|
className={ this.props.className }
|
||||||
|
tileShape={ this.props.tileShape }
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
|
@ -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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,6 +24,7 @@ const dis = require("../../dispatcher");
|
||||||
const q = require('q');
|
const q = require('q');
|
||||||
const packageJson = require('../../../package.json');
|
const packageJson = require('../../../package.json');
|
||||||
const UserSettingsStore = require('../../UserSettingsStore');
|
const UserSettingsStore = require('../../UserSettingsStore');
|
||||||
|
const CallMediaHandler = require('../../CallMediaHandler');
|
||||||
const GeminiScrollbar = require('react-gemini-scrollbar');
|
const GeminiScrollbar = require('react-gemini-scrollbar');
|
||||||
const Email = require('../../email');
|
const Email = require('../../email');
|
||||||
const AddThreepid = require('../../AddThreepid');
|
const AddThreepid = require('../../AddThreepid');
|
||||||
|
@ -82,12 +83,20 @@ const SETTINGS_LABELS = [
|
||||||
{
|
{
|
||||||
id: 'hideJoinLeaves',
|
id: 'hideJoinLeaves',
|
||||||
label: 'Hide join/leave messages (invites/kicks/bans unaffected)',
|
label: 'Hide join/leave messages (invites/kicks/bans unaffected)',
|
||||||
}
|
},
|
||||||
/*
|
|
||||||
{
|
{
|
||||||
id: 'useCompactLayout',
|
id: 'useCompactLayout',
|
||||||
label: 'Use compact timeline layout',
|
label: 'Use compact timeline layout',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: 'hideRedactions',
|
||||||
|
label: 'Hide removed messages',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'disableMarkdown',
|
||||||
|
label: 'Disable markdown formatting',
|
||||||
|
},
|
||||||
|
/*
|
||||||
{
|
{
|
||||||
id: 'useFixedWidthFont',
|
id: 'useFixedWidthFont',
|
||||||
label: 'Use fixed width font',
|
label: 'Use fixed width font',
|
||||||
|
@ -180,6 +189,7 @@ module.exports = React.createClass({
|
||||||
email_add_pending: false,
|
email_add_pending: false,
|
||||||
vectorVersion: undefined,
|
vectorVersion: undefined,
|
||||||
rejectingInvites: false,
|
rejectingInvites: false,
|
||||||
|
mediaDevices: null,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -200,6 +210,8 @@ module.exports = React.createClass({
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this._refreshMediaDevices();
|
||||||
|
|
||||||
// Bulk rejecting invites:
|
// Bulk rejecting invites:
|
||||||
// /sync won't have had time to return when UserSettings re-renders from state changes, so getRooms()
|
// /sync won't have had time to return when UserSettings re-renders from state changes, so getRooms()
|
||||||
// will still return rooms with invites. To get around this, add a listener for
|
// will still return rooms with invites. To get around this, add a listener for
|
||||||
|
@ -261,6 +273,20 @@ module.exports = React.createClass({
|
||||||
this.setState({ electron_settings: settings });
|
this.setState({ electron_settings: settings });
|
||||||
},
|
},
|
||||||
|
|
||||||
|
_refreshMediaDevices: function() {
|
||||||
|
q().then(() => {
|
||||||
|
return CallMediaHandler.getDevices();
|
||||||
|
}).then((mediaDevices) => {
|
||||||
|
// console.log("got mediaDevices", mediaDevices, this._unmounted);
|
||||||
|
if (this._unmounted) return;
|
||||||
|
this.setState({
|
||||||
|
mediaDevices,
|
||||||
|
activeAudioInput: this._localSettings['webrtc_audioinput'],
|
||||||
|
activeVideoInput: this._localSettings['webrtc_videoinput'],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
_refreshFromServer: function() {
|
_refreshFromServer: function() {
|
||||||
const self = this;
|
const self = this;
|
||||||
q.all([
|
q.all([
|
||||||
|
@ -288,15 +314,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();
|
||||||
}
|
}
|
||||||
|
@ -371,14 +388,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) {
|
||||||
|
@ -408,7 +423,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,
|
||||||
});
|
});
|
||||||
|
@ -428,7 +446,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) {
|
||||||
|
@ -470,8 +488,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,
|
||||||
|
@ -589,7 +607,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>
|
||||||
|
@ -620,7 +638,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") }
|
||||||
|
@ -628,17 +646,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) }
|
||||||
|
@ -647,22 +672,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 }
|
||||||
|
@ -701,8 +728,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>Device ID:</label> <span><code>{deviceId}</code></span></li>
|
<li><label>{_t("Device ID:")}</label>
|
||||||
<li><label>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>
|
||||||
|
@ -714,16 +743,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) }
|
||||||
|
@ -735,7 +766,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>
|
||||||
);
|
);
|
||||||
|
@ -759,6 +790,8 @@ module.exports = React.createClass({
|
||||||
},
|
},
|
||||||
|
|
||||||
_renderAnalyticsControl: function() {
|
_renderAnalyticsControl: function() {
|
||||||
|
if (!SdkConfig.get().piwik) return <div/>;
|
||||||
|
|
||||||
return <div>
|
return <div>
|
||||||
<h3>{ _t('Analytics') }</h3>
|
<h3>{ _t('Analytics') }</h3>
|
||||||
<div className="mx_UserSettings_section">
|
<div className="mx_UserSettings_section">
|
||||||
|
@ -771,31 +804,29 @@ module.exports = React.createClass({
|
||||||
_renderLabs: function() {
|
_renderLabs: function() {
|
||||||
// default to enabled if undefined
|
// default to enabled if undefined
|
||||||
if (this.props.enableLabs === false) return null;
|
if (this.props.enableLabs === false) return null;
|
||||||
|
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>
|
||||||
|
@ -808,9 +839,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">
|
||||||
|
@ -847,9 +875,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>
|
||||||
);
|
);
|
||||||
|
@ -867,8 +896,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">
|
||||||
|
@ -876,9 +903,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>
|
||||||
|
@ -886,6 +911,115 @@ 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) {
|
||||||
|
return devices.map((device) => <span key={device.deviceId}>{device.label}</span>);
|
||||||
|
},
|
||||||
|
|
||||||
|
_setAudioInput: function(deviceId) {
|
||||||
|
this.setState({activeAudioInput: deviceId});
|
||||||
|
CallMediaHandler.setAudioInput(deviceId);
|
||||||
|
},
|
||||||
|
|
||||||
|
_setVideoInput: function(deviceId) {
|
||||||
|
this.setState({activeVideoInput: deviceId});
|
||||||
|
CallMediaHandler.setVideoInput(deviceId);
|
||||||
|
},
|
||||||
|
|
||||||
|
_requestMediaPermissions: function(event) {
|
||||||
|
const getUserMedia = (
|
||||||
|
window.navigator.getUserMedia || window.navigator.webkitGetUserMedia || window.navigator.mozGetUserMedia
|
||||||
|
);
|
||||||
|
if (getUserMedia) {
|
||||||
|
return getUserMedia.apply(window.navigator, [
|
||||||
|
{ video: true, audio: true },
|
||||||
|
this._refreshMediaDevices,
|
||||||
|
function() {
|
||||||
|
const ErrorDialog = sdk.getComponent('dialogs.ErrorDialog');
|
||||||
|
Modal.createDialog(ErrorDialog, {
|
||||||
|
title: _t('No media permissions'),
|
||||||
|
description: _t('You may need to manually permit Riot to access your microphone/webcam'),
|
||||||
|
});
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
_renderWebRtcSettings: function() {
|
||||||
|
if (this.state.mediaDevices === false) {
|
||||||
|
return <div>
|
||||||
|
<h3>{_t('VoIP')}</h3>
|
||||||
|
<div className="mx_UserSettings_section">
|
||||||
|
<p className="mx_UserSettings_link" onClick={this._requestMediaPermissions}>
|
||||||
|
{_t('Missing Media Permissions, click here to request.')}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>;
|
||||||
|
} else if (!this.state.mediaDevices) return;
|
||||||
|
|
||||||
|
const Dropdown = sdk.getComponent('elements.Dropdown');
|
||||||
|
|
||||||
|
let microphoneDropdown = <p>{_t('No Microphones detected')}</p>;
|
||||||
|
let webcamDropdown = <p>{_t('No Webcams detected')}</p>;
|
||||||
|
|
||||||
|
const defaultOption = {
|
||||||
|
deviceId: '',
|
||||||
|
label: _t('Default Device'),
|
||||||
|
};
|
||||||
|
|
||||||
|
const audioInputs = this.state.mediaDevices.audioinput.slice(0);
|
||||||
|
if (audioInputs.length > 0) {
|
||||||
|
let defaultInput = '';
|
||||||
|
if (!audioInputs.some((input) => input.deviceId === 'default')) {
|
||||||
|
audioInputs.unshift(defaultOption);
|
||||||
|
} else {
|
||||||
|
defaultInput = 'default';
|
||||||
|
}
|
||||||
|
|
||||||
|
microphoneDropdown = <div>
|
||||||
|
<h4>{_t('Microphone')}</h4>
|
||||||
|
<Dropdown
|
||||||
|
className="mx_UserSettings_webRtcDevices_dropdown"
|
||||||
|
value={this.state.activeAudioInput || defaultInput}
|
||||||
|
onOptionChange={this._setAudioInput}>
|
||||||
|
{this._mapWebRtcDevicesToSpans(audioInputs)}
|
||||||
|
</Dropdown>
|
||||||
|
</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
const videoInputs = this.state.mediaDevices.videoinput.slice(0);
|
||||||
|
if (videoInputs.length > 0) {
|
||||||
|
let defaultInput = '';
|
||||||
|
if (!videoInputs.some((input) => input.deviceId === 'default')) {
|
||||||
|
videoInputs.unshift(defaultOption);
|
||||||
|
} else {
|
||||||
|
defaultInput = 'default';
|
||||||
|
}
|
||||||
|
|
||||||
|
webcamDropdown = <div>
|
||||||
|
<h4>{_t('Camera')}</h4>
|
||||||
|
<Dropdown
|
||||||
|
className="mx_UserSettings_webRtcDevices_dropdown"
|
||||||
|
value={this.state.activeVideoInput || defaultInput}
|
||||||
|
onOptionChange={this._setVideoInput}>
|
||||||
|
{this._mapWebRtcDevicesToSpans(videoInputs)}
|
||||||
|
</Dropdown>
|
||||||
|
</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
return <div>
|
||||||
|
<h3>{_t('VoIP')}</h3>
|
||||||
|
<div className="mx_UserSettings_section">
|
||||||
|
{microphoneDropdown}
|
||||||
|
{webcamDropdown}
|
||||||
|
</div>
|
||||||
|
</div>;
|
||||||
|
},
|
||||||
|
|
||||||
_showSpoiler: function(event) {
|
_showSpoiler: function(event) {
|
||||||
const target = event.target;
|
const target = event.target;
|
||||||
target.innerHTML = target.getAttribute('data-spoiler');
|
target.innerHTML = target.getAttribute('data-spoiler');
|
||||||
|
@ -938,6 +1072,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">
|
||||||
|
@ -949,7 +1086,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>
|
||||||
);
|
);
|
||||||
|
@ -957,7 +1095,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">
|
||||||
|
@ -973,7 +1111,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>
|
||||||
);
|
);
|
||||||
|
@ -985,16 +1123,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"
|
||||||
|
@ -1003,10 +1132,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>
|
||||||
|
|
||||||
|
@ -1083,6 +1212,7 @@ module.exports = React.createClass({
|
||||||
|
|
||||||
{this._renderUserInterfaceSettings()}
|
{this._renderUserInterfaceSettings()}
|
||||||
{this._renderLabs()}
|
{this._renderLabs()}
|
||||||
|
{this._renderWebRtcSettings()}
|
||||||
{this._renderDevicesPanel()}
|
{this._renderDevicesPanel()}
|
||||||
{this._renderCryptoInfo()}
|
{this._renderCryptoInfo()}
|
||||||
{this._renderBulkOptions()}
|
{this._renderBulkOptions()}
|
||||||
|
@ -1099,7 +1229,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() }
|
||||||
|
|
|
@ -229,7 +229,7 @@ module.exports = React.createClass({
|
||||||
<div className="mx_Login_error">
|
<div className="mx_Login_error">
|
||||||
</div>
|
</div>
|
||||||
<a className="mx_Login_create" onClick={this.props.onLoginClick} href="#">
|
<a className="mx_Login_create" onClick={this.props.onLoginClick} href="#">
|
||||||
Return to login
|
{_t('Return to login screen')}
|
||||||
</a>
|
</a>
|
||||||
<a className="mx_Login_create" onClick={this.props.onRegisterClick} href="#">
|
<a className="mx_Login_create" onClick={this.props.onRegisterClick} href="#">
|
||||||
{ _t('Create an account') }
|
{ _t('Create an account') }
|
||||||
|
|
|
@ -18,8 +18,7 @@ limitations under the License.
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { _t } 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,55 +212,48 @@ 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"))
|
||||||
{
|
) {
|
||||||
const urlStart = <a href='https://www.google.com/search?&q=enable%20unsafe%20scripts'>;
|
|
||||||
const urlEnd = </a>;
|
|
||||||
errorText = <span>
|
errorText = <span>
|
||||||
{ _t('Can\'t connect to homeserver via HTTP when an HTTPS URL is in your browser bar. Either use HTTPS or %(urlStart)s enable unsafe scripts %(urlEnd)s', {urlStart: urlStart, urlEnd: urlEnd})}
|
{ _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>.",
|
||||||
|
/<a>(.*?)<\/a>/,
|
||||||
|
(sub) => { return <a href="https://www.google.com/search?&q=enable%20unsafe%20scripts">{ sub }</a>; }
|
||||||
|
)}
|
||||||
</span>;
|
</span>;
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
const urlStart = <a href={this.state.enteredHomeserverUrl}>;
|
|
||||||
const urlEnd = </a>;
|
|
||||||
errorText = <span>
|
errorText = <span>
|
||||||
{ _t('Can\'t connect to homeserver - please check your connectivity and ensure your %(urlStart)s homeserver\'s SSL certificate %(urlEnd)s is trusted', {urlStart: urlStart, urlEnd: urlEnd})}
|
{ _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>/,
|
||||||
|
(sub) => { return <a href={this.state.enteredHomeserverUrl}>{ sub }</a>; }
|
||||||
|
)}
|
||||||
</span>;
|
</span>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
||||||
|
@ -267,7 +262,7 @@ module.exports = React.createClass({
|
||||||
errMsg = _t('Passwords don\'t match.');
|
errMsg = _t('Passwords don\'t match.');
|
||||||
break;
|
break;
|
||||||
case "RegistrationForm.ERR_PASSWORD_LENGTH":
|
case "RegistrationForm.ERR_PASSWORD_LENGTH":
|
||||||
errMsg = _t('Password too short (min %(MIN_PASSWORD_LENGTH)s).', {MIN_PASSWORD_LENGTH: $MIN_PASSWORD_LENGTH})
|
errMsg = _t('Password too short (min %(MIN_PASSWORD_LENGTH)s).', {MIN_PASSWORD_LENGTH: MIN_PASSWORD_LENGTH});
|
||||||
break;
|
break;
|
||||||
case "RegistrationForm.ERR_EMAIL_INVALID":
|
case "RegistrationForm.ERR_EMAIL_INVALID":
|
||||||
errMsg = _t('This doesn\'t look like a valid email address.');
|
errMsg = _t('This doesn\'t look like a valid email address.');
|
||||||
|
@ -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,18 @@ 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';
|
||||||
|
|
||||||
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 +41,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 +55,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 +79,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,16 +100,25 @@ 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, {
|
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,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onExistingRoomSelected: (roomId) => {
|
||||||
|
dis.dispatch({
|
||||||
|
action: 'view_room',
|
||||||
|
user_id: roomId,
|
||||||
|
});
|
||||||
|
},
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
this._startChat(inviteList);
|
this._startChat(inviteList);
|
||||||
|
@ -131,15 +145,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 +176,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 +224,103 @@ 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) => {
|
||||||
|
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 +339,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 +405,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 +472,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 +508,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 +540,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">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}>
|
|
||||||
Cancel
|
|
||||||
</button>
|
|
||||||
<button onClick={this.onRegisterClicked}>
|
|
||||||
Register
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</BaseDialog>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
});
|
|
|
@ -51,7 +51,7 @@ export default React.createClass({
|
||||||
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
|
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
|
||||||
const cancelButton = this.props.hasCancelButton ? (
|
const cancelButton = this.props.hasCancelButton ? (
|
||||||
<button onClick={this.onCancel}>
|
<button onClick={this.onCancel}>
|
||||||
Cancel
|
{_t("Cancel")}
|
||||||
</button>
|
</button>
|
||||||
) : null;
|
) : null;
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -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,87 +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';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
});
|
|
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) {
|
||||||
|
@ -40,14 +39,7 @@ export default class LanguageDropdown extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillMount() {
|
componentWillMount() {
|
||||||
languageHandler.getAllLanguageKeysFromJson().then((langKeys) => {
|
languageHandler.getAllLanguagesFromJson().then((langs) => {
|
||||||
const langs = [];
|
|
||||||
langKeys.forEach((languageKey) => {
|
|
||||||
langs.push({
|
|
||||||
value: languageKey,
|
|
||||||
label: _t(languageKey)
|
|
||||||
});
|
|
||||||
});
|
|
||||||
langs.sort(function(a, b){
|
langs.sort(function(a, b){
|
||||||
if(a.label < b.label) return -1;
|
if(a.label < b.label) return -1;
|
||||||
if(a.label > b.label) return 1;
|
if(a.label > b.label) return 1;
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -18,6 +18,7 @@ limitations under the License.
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import * as Roles from '../../../Roles';
|
import * as Roles from '../../../Roles';
|
||||||
|
import { _t } from '../../../languageHandler';
|
||||||
|
|
||||||
var LEVEL_ROLE_MAP = {};
|
var LEVEL_ROLE_MAP = {};
|
||||||
var reverseRoles = {};
|
var reverseRoles = {};
|
||||||
|
@ -116,9 +117,9 @@ module.exports = React.createClass({
|
||||||
text: Roles.textualPowerLevel(level, 0),
|
text: Roles.textualPowerLevel(level, 0),
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
options.push({ value: "Custom", text: "Custom level" });
|
options.push({ value: "Custom", text: _t("Custom level") });
|
||||||
options = options.map((op) => {
|
options = options.map((op) => {
|
||||||
return <option value={op.value}>{op.text}</option>;
|
return <option value={op.value} key={op.value}>{op.text}</option>;
|
||||||
});
|
});
|
||||||
|
|
||||||
select =
|
select =
|
||||||
|
|
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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -129,8 +129,8 @@ export const PasswordAuthEntry = React.createClass({
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<p>To continue, please enter your password.</p>
|
<p>{_t("To continue, please enter your password.")}</p>
|
||||||
<p>Password:</p>
|
<p>{_t("Password:")}</p>
|
||||||
<form onSubmit={this._onSubmit}>
|
<form onSubmit={this._onSubmit}>
|
||||||
<input
|
<input
|
||||||
ref="passwordField"
|
ref="passwordField"
|
||||||
|
@ -380,7 +380,7 @@ export const MsisdnAuthEntry = React.createClass({
|
||||||
onChange={this._onTokenChange}
|
onChange={this._onTokenChange}
|
||||||
/>
|
/>
|
||||||
<br />
|
<br />
|
||||||
<input type="submit" value="Submit"
|
<input type="submit" value={_t("Submit")}
|
||||||
className={submitClasses}
|
className={submitClasses}
|
||||||
disabled={!enableSubmit}
|
disabled={!enableSubmit}
|
||||||
/>
|
/>
|
||||||
|
@ -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';
|
||||||
|
@ -150,7 +149,7 @@ class PasswordLogin extends React.Component {
|
||||||
type="text"
|
type="text"
|
||||||
name="phoneNumber"
|
name="phoneNumber"
|
||||||
onChange={this.onPhoneNumberChanged}
|
onChange={this.onPhoneNumberChanged}
|
||||||
placeholder="Mobile phone number"
|
placeholder={_t("Mobile phone number")}
|
||||||
value={this.state.phoneNumber}
|
value={this.state.phoneNumber}
|
||||||
autoFocus
|
autoFocus
|
||||||
/>
|
/>
|
||||||
|
@ -182,7 +181,7 @@ class PasswordLogin extends React.Component {
|
||||||
<div>
|
<div>
|
||||||
<form onSubmit={this.onSubmitForm}>
|
<form onSubmit={this.onSubmitForm}>
|
||||||
<div className="mx_Login_type_container">
|
<div className="mx_Login_type_container">
|
||||||
<label className="mx_Login_type_label">{ _t('I want to sign in with') }</label>
|
<label className="mx_Login_type_label">{ _t('Sign in with') }</label>
|
||||||
<Dropdown
|
<Dropdown
|
||||||
className="mx_Login_type_dropdown"
|
className="mx_Login_type_dropdown"
|
||||||
value={this.state.loginType}
|
value={this.state.loginType}
|
||||||
|
|
|
@ -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,
|
||||||
|
@ -282,7 +276,7 @@ module.exports = React.createClass({
|
||||||
const emailSection = (
|
const emailSection = (
|
||||||
<div>
|
<div>
|
||||||
<input type="text" ref="email"
|
<input type="text" ref="email"
|
||||||
autoFocus={true} placeholder="Email address (optional)"
|
autoFocus={true} placeholder={_t("Email address (optional)")}
|
||||||
defaultValue={this.props.defaultEmail}
|
defaultValue={this.props.defaultEmail}
|
||||||
className={this._classForField(FIELD_EMAIL, 'mx_Login_field')}
|
className={this._classForField(FIELD_EMAIL, 'mx_Login_field')}
|
||||||
onBlur={function() {self.validateField(FIELD_EMAIL);}}
|
onBlur={function() {self.validateField(FIELD_EMAIL);}}
|
||||||
|
@ -321,7 +315,7 @@ module.exports = React.createClass({
|
||||||
showPrefix={true}
|
showPrefix={true}
|
||||||
/>
|
/>
|
||||||
<input type="text" ref="phoneNumber"
|
<input type="text" ref="phoneNumber"
|
||||||
placeholder="Mobile phone number (optional)"
|
placeholder={_t("Mobile phone number (optional)")}
|
||||||
defaultValue={this.props.defaultPhoneNumber}
|
defaultValue={this.props.defaultPhoneNumber}
|
||||||
className={this._classForField(
|
className={this._classForField(
|
||||||
FIELD_PHONE_NUMBER,
|
FIELD_PHONE_NUMBER,
|
||||||
|
@ -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 = "User name";
|
let placeholderUserName = _t("User name");
|
||||||
if (this.props.guestUsername) {
|
|
||||||
placeholderUserName += " (default: " + this.props.guestUsername + ")";
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
|
@ -355,16 +346,13 @@ 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">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);}}
|
||||||
placeholder="Password" defaultValue={this.props.defaultPassword} />
|
placeholder={_t("Password")} defaultValue={this.props.defaultPassword} />
|
||||||
<br />
|
<br />
|
||||||
<input type="password" ref="passwordConfirm"
|
<input type="password" ref="passwordConfirm"
|
||||||
placeholder="Confirm password"
|
placeholder={_t("Confirm password")}
|
||||||
className={this._classForField(FIELD_PASSWORD_CONFIRM, 'mx_Login_field')}
|
className={this._classForField(FIELD_PASSWORD_CONFIRM, 'mx_Login_field')}
|
||||||
onBlur={function() {self.validateField(FIELD_PASSWORD_CONFIRM);}}
|
onBlur={function() {self.validateField(FIELD_PASSWORD_CONFIRM);}}
|
||||||
defaultValue={this.props.defaultPassword} />
|
defaultValue={this.props.defaultPassword} />
|
||||||
|
|
|
@ -132,7 +132,7 @@ module.exports = React.createClass({
|
||||||
var toggleButton;
|
var toggleButton;
|
||||||
if (this.props.withToggleButton) {
|
if (this.props.withToggleButton) {
|
||||||
toggleButton = (
|
toggleButton = (
|
||||||
<div style={{ textAlign: 'center' }}>
|
<div className="mx_ServerConfig_selector">
|
||||||
<input className="mx_Login_radio" id="basic" name="configVisible" type="radio"
|
<input className="mx_Login_radio" id="basic" name="configVisible" type="radio"
|
||||||
checked={!this.state.configVisible}
|
checked={!this.state.configVisible}
|
||||||
onChange={this.onServerConfigVisibleChange.bind(this, false)} />
|
onChange={this.onServerConfigVisibleChange.bind(this, false)} />
|
||||||
|
|
|
@ -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';
|
||||||
|
|
92
src/components/views/messages/RoomAvatarEvent.js
Normal file
92
src/components/views/messages/RoomAvatarEvent.js
Normal file
|
@ -0,0 +1,92 @@
|
||||||
|
/*
|
||||||
|
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 MatrixClientPeg from '../../../MatrixClientPeg';
|
||||||
|
import { ContentRepo } from 'matrix-js-sdk';
|
||||||
|
import { _t, _tJsx } from '../../../languageHandler';
|
||||||
|
import sdk from '../../../index';
|
||||||
|
import Modal from '../../../Modal';
|
||||||
|
import AccessibleButton from '../elements/AccessibleButton';
|
||||||
|
|
||||||
|
module.exports = React.createClass({
|
||||||
|
displayName: 'RoomAvatarEvent',
|
||||||
|
|
||||||
|
propTypes: {
|
||||||
|
/* the MatrixEvent to show */
|
||||||
|
mxEvent: React.PropTypes.object.isRequired,
|
||||||
|
},
|
||||||
|
|
||||||
|
onAvatarClick: function(name) {
|
||||||
|
var httpUrl = MatrixClientPeg.get().mxcUrlToHttp(this.props.mxEvent.getContent().url);
|
||||||
|
var ImageView = sdk.getComponent("elements.ImageView");
|
||||||
|
var params = {
|
||||||
|
src: httpUrl,
|
||||||
|
name: name,
|
||||||
|
};
|
||||||
|
Modal.createDialog(ImageView, params, "mx_Dialog_lightbox");
|
||||||
|
},
|
||||||
|
|
||||||
|
render: function() {
|
||||||
|
var ev = this.props.mxEvent;
|
||||||
|
var senderDisplayName = ev.sender && ev.sender.name ? ev.sender.name : ev.getSender();
|
||||||
|
var BaseAvatar = sdk.getComponent("avatars.BaseAvatar");
|
||||||
|
|
||||||
|
var room = MatrixClientPeg.get().getRoom(this.props.mxEvent.getRoomId());
|
||||||
|
var name = _t('%(senderDisplayName)s changed the avatar for %(roomName)s', {
|
||||||
|
senderDisplayName: senderDisplayName,
|
||||||
|
roomName: room ? room.name : '',
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!ev.getContent().url || ev.getContent().url.trim().length === 0) {
|
||||||
|
return (
|
||||||
|
<div className="mx_TextualEvent">
|
||||||
|
{ _t('%(senderDisplayName)s removed the room avatar.', {senderDisplayName: senderDisplayName}) }
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
var url = ContentRepo.getHttpUriForMxc(
|
||||||
|
MatrixClientPeg.get().getHomeserverUrl(),
|
||||||
|
ev.getContent().url,
|
||||||
|
14 * window.devicePixelRatio,
|
||||||
|
14 * window.devicePixelRatio,
|
||||||
|
'crop'
|
||||||
|
);
|
||||||
|
|
||||||
|
// it sucks that _tJsx doesn't support normal _t substitutions :((
|
||||||
|
return (
|
||||||
|
<div className="mx_RoomAvatarEvent">
|
||||||
|
{ _tJsx('$senderDisplayName changed the room avatar to <img/>',
|
||||||
|
[
|
||||||
|
/\$senderDisplayName/,
|
||||||
|
/<img\/>/,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
(sub) => senderDisplayName,
|
||||||
|
(sub) =>
|
||||||
|
<AccessibleButton key="avatar" className="mx_RoomAvatarEvent_avatar"
|
||||||
|
onClick={ this.onAvatarClick.bind(this, name) }>
|
||||||
|
<BaseAvatar width={14} height={14} url={ url }
|
||||||
|
name={ name } />
|
||||||
|
</AccessibleButton>,
|
||||||
|
]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
});
|
|
@ -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);
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -24,6 +24,11 @@ import sdk from '../../../index';
|
||||||
module.exports = React.createClass({
|
module.exports = React.createClass({
|
||||||
displayName: 'TextualEvent',
|
displayName: 'TextualEvent',
|
||||||
|
|
||||||
|
propTypes: {
|
||||||
|
/* the MatrixEvent to show */
|
||||||
|
mxEvent: React.PropTypes.object.isRequired,
|
||||||
|
},
|
||||||
|
|
||||||
render: function() {
|
render: function() {
|
||||||
const EmojiText = sdk.getComponent('elements.EmojiText');
|
const EmojiText = sdk.getComponent('elements.EmojiText');
|
||||||
var text = TextForEvent.textForEvent(this.props.mxEvent);
|
var text = TextForEvent.textForEvent(this.props.mxEvent);
|
||||||
|
|
|
@ -230,7 +230,7 @@ module.exports = React.createClass({
|
||||||
remote_aliases_section = (
|
remote_aliases_section = (
|
||||||
<div>
|
<div>
|
||||||
<div className="mx_RoomSettings_aliasLabel">
|
<div className="mx_RoomSettings_aliasLabel">
|
||||||
Remote addresses for this room:
|
{_t("Remote addresses for this room:")}
|
||||||
</div>
|
</div>
|
||||||
<div className="mx_RoomSettings_aliasesTable">
|
<div className="mx_RoomSettings_aliasesTable">
|
||||||
{ this.state.remoteDomains.map((domain, i) => {
|
{ this.state.remoteDomains.map((domain, i) => {
|
||||||
|
|
|
@ -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"
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,7 +20,7 @@ var MatrixClientPeg = require('../../../MatrixClientPeg');
|
||||||
var sdk = require("../../../index");
|
var sdk = require("../../../index");
|
||||||
var Modal = require("../../../Modal");
|
var Modal = require("../../../Modal");
|
||||||
var UserSettingsStore = require('../../../UserSettingsStore');
|
var UserSettingsStore = require('../../../UserSettingsStore');
|
||||||
import { _t } from '../../../languageHandler';
|
import { _t, _tJsx } from '../../../languageHandler';
|
||||||
|
|
||||||
|
|
||||||
module.exports = React.createClass({
|
module.exports = React.createClass({
|
||||||
|
@ -131,12 +131,24 @@ module.exports = React.createClass({
|
||||||
</label>;
|
</label>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let urlPreviewText = null;
|
||||||
|
if (UserSettingsStore.getUrlPreviewsDisabled()) {
|
||||||
|
urlPreviewText = (
|
||||||
|
_tJsx("You have <a>disabled</a> URL previews by default.", /<a>(.*?)<\/a>/, (sub)=><a href="#/settings">{sub}</a>)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
urlPreviewText = (
|
||||||
|
_tJsx("You have <a>enabled</a> URL previews by default.", /<a>(.*?)<\/a>/, (sub)=><a href="#/settings">{sub}</a>)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mx_RoomSettings_toggles">
|
<div className="mx_RoomSettings_toggles">
|
||||||
<h3>{_t("URL Previews")}</h3>
|
<h3>{_t("URL Previews")}</h3>
|
||||||
|
|
||||||
<label>
|
<label>
|
||||||
You have <a href="#/settings">{ UserSettingsStore.getUrlPreviewsDisabled() ? 'disabled' : 'enabled' }</a> URL previews by default.
|
{urlPreviewText}
|
||||||
</label>
|
</label>
|
||||||
{ disableRoomPreviewUrls }
|
{ disableRoomPreviewUrls }
|
||||||
<label>
|
<label>
|
||||||
|
|
|
@ -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")}
|
||||||
|
@ -95,9 +95,14 @@ module.exports = React.createClass({
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
joinText = (<span>
|
joinText = (<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>);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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")}/>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -38,6 +38,7 @@ var eventTileTypes = {
|
||||||
'm.call.answer' : 'messages.TextualEvent',
|
'm.call.answer' : 'messages.TextualEvent',
|
||||||
'm.call.hangup' : 'messages.TextualEvent',
|
'm.call.hangup' : 'messages.TextualEvent',
|
||||||
'm.room.name' : 'messages.TextualEvent',
|
'm.room.name' : 'messages.TextualEvent',
|
||||||
|
'm.room.avatar' : 'messages.RoomAvatarEvent',
|
||||||
'm.room.topic' : 'messages.TextualEvent',
|
'm.room.topic' : 'messages.TextualEvent',
|
||||||
'm.room.third_party_invite' : 'messages.TextualEvent',
|
'm.room.third_party_invite' : 'messages.TextualEvent',
|
||||||
'm.room.history_visibility' : 'messages.TextualEvent',
|
'm.room.history_visibility' : 'messages.TextualEvent',
|
||||||
|
@ -380,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(),
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
@ -481,27 +483,27 @@ module.exports = WithMatrixClient(React.createClass({
|
||||||
}
|
}
|
||||||
|
|
||||||
const editButton = (
|
const editButton = (
|
||||||
<span className="mx_EventTile_editButton" title="Options" onClick={this.onEditClicked} />
|
<span className="mx_EventTile_editButton" title={ _t("Options") } onClick={this.onEditClicked} />
|
||||||
);
|
);
|
||||||
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;
|
||||||
|
|
|
@ -16,6 +16,7 @@ limitations under the License.
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import sdk from '../../../index';
|
import sdk from '../../../index';
|
||||||
|
import { _t } from '../../../languageHandler';
|
||||||
|
|
||||||
export default class MemberDeviceInfo extends React.Component {
|
export default class MemberDeviceInfo extends React.Component {
|
||||||
render() {
|
render() {
|
||||||
|
@ -25,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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -49,7 +50,7 @@ export default class MemberDeviceInfo extends React.Component {
|
||||||
// add the deviceId as a titletext to help with debugging
|
// add the deviceId as a titletext to help with debugging
|
||||||
return (
|
return (
|
||||||
<div className="mx_MemberDeviceInfo"
|
<div className="mx_MemberDeviceInfo"
|
||||||
title={"device id: " + this.props.device.deviceId} >
|
title={_t("device id: ") + this.props.device.deviceId} >
|
||||||
<div className="mx_MemberDeviceInfo_deviceInfo">
|
<div className="mx_MemberDeviceInfo_deviceInfo">
|
||||||
<div className="mx_MemberDeviceInfo_deviceId">
|
<div className="mx_MemberDeviceInfo_deviceId">
|
||||||
{deviceName}
|
{deviceName}
|
||||||
|
|
|
@ -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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,12 +28,6 @@ var CallHandler = require("../../../CallHandler");
|
||||||
var Invite = require("../../../Invite");
|
var Invite = require("../../../Invite");
|
||||||
|
|
||||||
var INITIAL_LOAD_NUM_MEMBERS = 30;
|
var INITIAL_LOAD_NUM_MEMBERS = 30;
|
||||||
var SHARE_HISTORY_WARNING =
|
|
||||||
<span>
|
|
||||||
Newly invited users will see the history of this room. <br/>
|
|
||||||
If you'd prefer invited users not to see messages that were sent before they joined, <br/>
|
|
||||||
turn off, 'Share message history with new users' in the settings for this room.
|
|
||||||
</span>;
|
|
||||||
|
|
||||||
module.exports = React.createClass({
|
module.exports = React.createClass({
|
||||||
displayName: 'MemberList',
|
displayName: 'MemberList',
|
||||||
|
@ -355,7 +349,7 @@ module.exports = React.createClass({
|
||||||
if (invitedMemberTiles.length > 0) {
|
if (invitedMemberTiles.length > 0) {
|
||||||
invitedSection = (
|
invitedSection = (
|
||||||
<div className="mx_MemberList_invited">
|
<div className="mx_MemberList_invited">
|
||||||
<h2>Invited</h2>
|
<h2>{ _t("Invited") }</h2>
|
||||||
<div className="mx_MemberList_wrapper">
|
<div className="mx_MemberList_wrapper">
|
||||||
{invitedMemberTiles}
|
{invitedMemberTiles}
|
||||||
</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() {
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue