mirror of
https://github.com/element-hq/element-web
synced 2024-11-28 04:21:57 +03:00
Merge branches 'develop' and 't3chguy/e2eedefault' of github.com:matrix-org/matrix-react-sdk into t3chguy/e2eedefault
Conflicts: src/components/views/dialogs/InviteDialog.js
This commit is contained in:
commit
8848a2ea64
305 changed files with 9256 additions and 4200 deletions
|
@ -6,7 +6,6 @@ src/components/structures/RoomView.js
|
||||||
src/components/structures/ScrollPanel.js
|
src/components/structures/ScrollPanel.js
|
||||||
src/components/structures/SearchBox.js
|
src/components/structures/SearchBox.js
|
||||||
src/components/structures/UploadBar.js
|
src/components/structures/UploadBar.js
|
||||||
src/components/views/avatars/BaseAvatar.js
|
|
||||||
src/components/views/avatars/MemberAvatar.js
|
src/components/views/avatars/MemberAvatar.js
|
||||||
src/components/views/create_room/RoomAlias.js
|
src/components/views/create_room/RoomAlias.js
|
||||||
src/components/views/dialogs/SetPasswordDialog.js
|
src/components/views/dialogs/SetPasswordDialog.js
|
||||||
|
@ -15,9 +14,7 @@ src/components/views/elements/AddressSelector.js
|
||||||
src/components/views/elements/DirectorySearchBox.js
|
src/components/views/elements/DirectorySearchBox.js
|
||||||
src/components/views/elements/MemberEventListSummary.js
|
src/components/views/elements/MemberEventListSummary.js
|
||||||
src/components/views/elements/UserSelector.js
|
src/components/views/elements/UserSelector.js
|
||||||
src/components/views/globals/MatrixToolbar.js
|
|
||||||
src/components/views/globals/NewVersionBar.js
|
src/components/views/globals/NewVersionBar.js
|
||||||
src/components/views/globals/UpdateCheckBar.js
|
|
||||||
src/components/views/messages/MFileBody.js
|
src/components/views/messages/MFileBody.js
|
||||||
src/components/views/messages/TextualBody.js
|
src/components/views/messages/TextualBody.js
|
||||||
src/components/views/room_settings/ColorSettings.js
|
src/components/views/room_settings/ColorSettings.js
|
||||||
|
|
281
CHANGELOG.md
281
CHANGELOG.md
|
@ -1,3 +1,284 @@
|
||||||
|
Changes in [2.6.1](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v2.6.1) (2020-05-22)
|
||||||
|
===================================================================================================
|
||||||
|
[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v2.6.0...v2.6.1)
|
||||||
|
|
||||||
|
* Fix key backup restore with SSSS
|
||||||
|
[\#4617](https://github.com/matrix-org/matrix-react-sdk/pull/4617)
|
||||||
|
* Remove SSSS key upgrade check from rageshake
|
||||||
|
[\#4616](https://github.com/matrix-org/matrix-react-sdk/pull/4616)
|
||||||
|
|
||||||
|
Changes in [2.6.0](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v2.6.0) (2020-05-19)
|
||||||
|
===================================================================================================
|
||||||
|
[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v2.6.0-rc.1...v2.6.0)
|
||||||
|
|
||||||
|
* Upgrade to JS SDK 6.1.0
|
||||||
|
* Revert "ImageView make clicking off it easier"
|
||||||
|
[\#4602](https://github.com/matrix-org/matrix-react-sdk/pull/4602)
|
||||||
|
* Remove debugging that causes email addresses to load forever (to release)
|
||||||
|
[\#4598](https://github.com/matrix-org/matrix-react-sdk/pull/4598)
|
||||||
|
|
||||||
|
Changes in [2.6.0-rc.1](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v2.6.0-rc.1) (2020-05-14)
|
||||||
|
=============================================================================================================
|
||||||
|
[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v2.5.0...v2.6.0-rc.1)
|
||||||
|
|
||||||
|
* Upgrade to JS SDK 6.1.0-rc.1
|
||||||
|
* Update from Weblate
|
||||||
|
[\#4596](https://github.com/matrix-org/matrix-react-sdk/pull/4596)
|
||||||
|
* Fix message edits dialog being wrong and sometimes crashing
|
||||||
|
[\#4595](https://github.com/matrix-org/matrix-react-sdk/pull/4595)
|
||||||
|
* Acquire a new session before enacting deactivation
|
||||||
|
[\#4584](https://github.com/matrix-org/matrix-react-sdk/pull/4584)
|
||||||
|
* Remove UI for upgrading 4S to symmetric encryption
|
||||||
|
[\#4581](https://github.com/matrix-org/matrix-react-sdk/pull/4581)
|
||||||
|
* Add copy to SSO prompts during cross-signing setup
|
||||||
|
[\#4555](https://github.com/matrix-org/matrix-react-sdk/pull/4555)
|
||||||
|
* Re-fix OpenID requests from widgets
|
||||||
|
[\#4592](https://github.com/matrix-org/matrix-react-sdk/pull/4592)
|
||||||
|
* Fix persistent widgets on desktop / http
|
||||||
|
[\#4591](https://github.com/matrix-org/matrix-react-sdk/pull/4591)
|
||||||
|
* Updated link and added:Yarn two is not yet used.
|
||||||
|
[\#4589](https://github.com/matrix-org/matrix-react-sdk/pull/4589)
|
||||||
|
* Fix topic dialog not supporting escape as it didn't have a "Close"
|
||||||
|
[\#4578](https://github.com/matrix-org/matrix-react-sdk/pull/4578)
|
||||||
|
* Default to public room when creating room from room directory
|
||||||
|
[\#4579](https://github.com/matrix-org/matrix-react-sdk/pull/4579)
|
||||||
|
* Replace png flags and add Kosovo to country code dropdown
|
||||||
|
[\#4576](https://github.com/matrix-org/matrix-react-sdk/pull/4576)
|
||||||
|
* Rename `trash (custom).svg` as electron doesn't like paths with spaces
|
||||||
|
[\#4583](https://github.com/matrix-org/matrix-react-sdk/pull/4583)
|
||||||
|
* Fix sign in / up links on previewed rooms
|
||||||
|
[\#4582](https://github.com/matrix-org/matrix-react-sdk/pull/4582)
|
||||||
|
* Avoid soft crash if unknown device in verification
|
||||||
|
[\#4580](https://github.com/matrix-org/matrix-react-sdk/pull/4580)
|
||||||
|
* Add slash commands /query and /msg to match IRC
|
||||||
|
[\#4568](https://github.com/matrix-org/matrix-react-sdk/pull/4568)
|
||||||
|
* Send cross-signing debug booleans over rageshake
|
||||||
|
[\#4570](https://github.com/matrix-org/matrix-react-sdk/pull/4570)
|
||||||
|
* Prompt user to specify an alternate server if theirs has registration off
|
||||||
|
[\#4575](https://github.com/matrix-org/matrix-react-sdk/pull/4575)
|
||||||
|
* Don't try and redact redactions for "Remove recent messages"
|
||||||
|
[\#4573](https://github.com/matrix-org/matrix-react-sdk/pull/4573)
|
||||||
|
* View Source should target the replacing event rather than the root one
|
||||||
|
[\#4571](https://github.com/matrix-org/matrix-react-sdk/pull/4571)
|
||||||
|
* Fix passphrase reset in key backup restore dialog
|
||||||
|
[\#4569](https://github.com/matrix-org/matrix-react-sdk/pull/4569)
|
||||||
|
* Ensure key backup gets dealt with correctly during secret storage reset
|
||||||
|
[\#4556](https://github.com/matrix-org/matrix-react-sdk/pull/4556)
|
||||||
|
* Fix crash for broken invites
|
||||||
|
[\#4565](https://github.com/matrix-org/matrix-react-sdk/pull/4565)
|
||||||
|
* Fix rageshake with no matrix client
|
||||||
|
[\#4572](https://github.com/matrix-org/matrix-react-sdk/pull/4572)
|
||||||
|
* Update from Weblate
|
||||||
|
[\#4567](https://github.com/matrix-org/matrix-react-sdk/pull/4567)
|
||||||
|
* Bring back UnknownBody for UISIs
|
||||||
|
[\#4564](https://github.com/matrix-org/matrix-react-sdk/pull/4564)
|
||||||
|
* clear tag panel selection if the community selected is left
|
||||||
|
[\#4559](https://github.com/matrix-org/matrix-react-sdk/pull/4559)
|
||||||
|
* Close ImageView when redacting
|
||||||
|
[\#4560](https://github.com/matrix-org/matrix-react-sdk/pull/4560)
|
||||||
|
* Redesign redactions
|
||||||
|
[\#4484](https://github.com/matrix-org/matrix-react-sdk/pull/4484)
|
||||||
|
* Don't try to reload profile information when closing the user panel
|
||||||
|
[\#4547](https://github.com/matrix-org/matrix-react-sdk/pull/4547)
|
||||||
|
* Fix right panel hiding when viewing room member
|
||||||
|
[\#4558](https://github.com/matrix-org/matrix-react-sdk/pull/4558)
|
||||||
|
* Don't erase password confirm on registration error
|
||||||
|
[\#4540](https://github.com/matrix-org/matrix-react-sdk/pull/4540)
|
||||||
|
* Add a loading state for email addresses/phone numbers in settings
|
||||||
|
[\#4557](https://github.com/matrix-org/matrix-react-sdk/pull/4557)
|
||||||
|
* set the meta tag for theme-color to the same theme css background
|
||||||
|
[\#4554](https://github.com/matrix-org/matrix-react-sdk/pull/4554)
|
||||||
|
* Update Invite Dialog copy to include email addresses
|
||||||
|
[\#4497](https://github.com/matrix-org/matrix-react-sdk/pull/4497)
|
||||||
|
* Fix slider toggle regression.
|
||||||
|
[\#4546](https://github.com/matrix-org/matrix-react-sdk/pull/4546)
|
||||||
|
* Fix a crash where a name could unexpectedly be an empty list
|
||||||
|
[\#4552](https://github.com/matrix-org/matrix-react-sdk/pull/4552)
|
||||||
|
* Solves communities can be dragged from context menu
|
||||||
|
[\#4492](https://github.com/matrix-org/matrix-react-sdk/pull/4492)
|
||||||
|
* Remove prefixes for composer avatar urls
|
||||||
|
[\#4553](https://github.com/matrix-org/matrix-react-sdk/pull/4553)
|
||||||
|
* Fix reply RR spacing getting doubled
|
||||||
|
[\#4541](https://github.com/matrix-org/matrix-react-sdk/pull/4541)
|
||||||
|
* Differentiate copy for own untrusted device dialog
|
||||||
|
[\#4549](https://github.com/matrix-org/matrix-react-sdk/pull/4549)
|
||||||
|
* EventIndex: Reduce the logging the event index is producing.
|
||||||
|
[\#4548](https://github.com/matrix-org/matrix-react-sdk/pull/4548)
|
||||||
|
* Increase rageshake size limit to 5mb
|
||||||
|
[\#4543](https://github.com/matrix-org/matrix-react-sdk/pull/4543)
|
||||||
|
* Update from Weblate
|
||||||
|
[\#4542](https://github.com/matrix-org/matrix-react-sdk/pull/4542)
|
||||||
|
* Guard against race when waiting for cross-signing to be ready
|
||||||
|
[\#4539](https://github.com/matrix-org/matrix-react-sdk/pull/4539)
|
||||||
|
* Wait for user to be verified in e2e setup
|
||||||
|
[\#4537](https://github.com/matrix-org/matrix-react-sdk/pull/4537)
|
||||||
|
* Convert MatrixChat to a TypeScript class
|
||||||
|
[\#4462](https://github.com/matrix-org/matrix-react-sdk/pull/4462)
|
||||||
|
* Mark room as read when escape is pressed
|
||||||
|
[\#4271](https://github.com/matrix-org/matrix-react-sdk/pull/4271)
|
||||||
|
* Only show key backup reminder when confirmed by server to be missing
|
||||||
|
[\#4534](https://github.com/matrix-org/matrix-react-sdk/pull/4534)
|
||||||
|
* Add device name to unverified session toast
|
||||||
|
[\#4535](https://github.com/matrix-org/matrix-react-sdk/pull/4535)
|
||||||
|
* Show progress when loading keys
|
||||||
|
[\#4507](https://github.com/matrix-org/matrix-react-sdk/pull/4507)
|
||||||
|
* Fix device verification toasts not disappearing
|
||||||
|
[\#4532](https://github.com/matrix-org/matrix-react-sdk/pull/4532)
|
||||||
|
* Update toast copy again
|
||||||
|
[\#4529](https://github.com/matrix-org/matrix-react-sdk/pull/4529)
|
||||||
|
* Re-apply theme after login
|
||||||
|
[\#4518](https://github.com/matrix-org/matrix-react-sdk/pull/4518)
|
||||||
|
* Reduce maximum width of toasts & allow multiple lines
|
||||||
|
[\#4525](https://github.com/matrix-org/matrix-react-sdk/pull/4525)
|
||||||
|
* Treat sessions that are there when we log in as old
|
||||||
|
[\#4524](https://github.com/matrix-org/matrix-react-sdk/pull/4524)
|
||||||
|
* Allow resetting storage from the access dialog
|
||||||
|
[\#4521](https://github.com/matrix-org/matrix-react-sdk/pull/4521)
|
||||||
|
* Update (bulk) unverified device toast copy
|
||||||
|
[\#4522](https://github.com/matrix-org/matrix-react-sdk/pull/4522)
|
||||||
|
* Make new device toasts appear above review toasts
|
||||||
|
[\#4519](https://github.com/matrix-org/matrix-react-sdk/pull/4519)
|
||||||
|
* Separate toasts for existing & new device verification
|
||||||
|
[\#4511](https://github.com/matrix-org/matrix-react-sdk/pull/4511)
|
||||||
|
* Slightly darker toggle off bg color
|
||||||
|
[\#4477](https://github.com/matrix-org/matrix-react-sdk/pull/4477)
|
||||||
|
* Fix pill vertical align
|
||||||
|
[\#4514](https://github.com/matrix-org/matrix-react-sdk/pull/4514)
|
||||||
|
* Fix set up encryption toast to use "set up" as action
|
||||||
|
[\#4502](https://github.com/matrix-org/matrix-react-sdk/pull/4502)
|
||||||
|
* Don't enable e2ee when inviting a 3pid
|
||||||
|
[\#4509](https://github.com/matrix-org/matrix-react-sdk/pull/4509)
|
||||||
|
* Fix internal link styling in Security Settings
|
||||||
|
[\#4510](https://github.com/matrix-org/matrix-react-sdk/pull/4510)
|
||||||
|
* Small custom theming fixes
|
||||||
|
[\#4508](https://github.com/matrix-org/matrix-react-sdk/pull/4508)
|
||||||
|
* Fix scaling issues
|
||||||
|
[\#4355](https://github.com/matrix-org/matrix-react-sdk/pull/4355)
|
||||||
|
* Aggregate device verify toasts
|
||||||
|
[\#4506](https://github.com/matrix-org/matrix-react-sdk/pull/4506)
|
||||||
|
* Support setting username and avatar colors in custom themes
|
||||||
|
[\#4503](https://github.com/matrix-org/matrix-react-sdk/pull/4503)
|
||||||
|
* only clear on continuations where the clear isn't done by SenderProfile
|
||||||
|
[\#4501](https://github.com/matrix-org/matrix-react-sdk/pull/4501)
|
||||||
|
* cap width of editable item list item to leave space for its X button
|
||||||
|
[\#4495](https://github.com/matrix-org/matrix-react-sdk/pull/4495)
|
||||||
|
* Add a link from settings / devices to your user profile
|
||||||
|
[\#4498](https://github.com/matrix-org/matrix-react-sdk/pull/4498)
|
||||||
|
* Update from Weblate
|
||||||
|
[\#4496](https://github.com/matrix-org/matrix-react-sdk/pull/4496)
|
||||||
|
* Make icon change in SetupEncryptionDialog
|
||||||
|
[\#4485](https://github.com/matrix-org/matrix-react-sdk/pull/4485)
|
||||||
|
* Remove invite only padlocks feature flag
|
||||||
|
[\#4487](https://github.com/matrix-org/matrix-react-sdk/pull/4487)
|
||||||
|
* Fix incorrect toast if security setup skipped
|
||||||
|
[\#4486](https://github.com/matrix-org/matrix-react-sdk/pull/4486)
|
||||||
|
* Revert "Update emojibase for fixed emoji codepoints and Emoji 13 support"
|
||||||
|
[\#4482](https://github.com/matrix-org/matrix-react-sdk/pull/4482)
|
||||||
|
* Fix widget URL templating (again)
|
||||||
|
[\#4481](https://github.com/matrix-org/matrix-react-sdk/pull/4481)
|
||||||
|
* Fix recovery link on login verification flow
|
||||||
|
[\#4479](https://github.com/matrix-org/matrix-react-sdk/pull/4479)
|
||||||
|
* Make avatars in pills occupy the entire space using cropping
|
||||||
|
[\#4476](https://github.com/matrix-org/matrix-react-sdk/pull/4476)
|
||||||
|
* Use WidgetType more often to avoid breaking new sticker pickers
|
||||||
|
[\#4458](https://github.com/matrix-org/matrix-react-sdk/pull/4458)
|
||||||
|
* Update logging for unmanaged widgets, and add TODO comments for other areas
|
||||||
|
[\#4460](https://github.com/matrix-org/matrix-react-sdk/pull/4460)
|
||||||
|
* Fix OpenID requests from widgets
|
||||||
|
[\#4459](https://github.com/matrix-org/matrix-react-sdk/pull/4459)
|
||||||
|
* Take encrypted message search out of labs
|
||||||
|
[\#4467](https://github.com/matrix-org/matrix-react-sdk/pull/4467)
|
||||||
|
* Fix BigEmoji for replies
|
||||||
|
[\#4475](https://github.com/matrix-org/matrix-react-sdk/pull/4475)
|
||||||
|
* Update login security copy and design to match Figma
|
||||||
|
[\#4472](https://github.com/matrix-org/matrix-react-sdk/pull/4472)
|
||||||
|
* Fix i18n of SSO UIA copy in Deactivate Account Dialog
|
||||||
|
[\#4471](https://github.com/matrix-org/matrix-react-sdk/pull/4471)
|
||||||
|
* Assert type of domNode as HTMLElement to fix build
|
||||||
|
[\#4470](https://github.com/matrix-org/matrix-react-sdk/pull/4470)
|
||||||
|
* Unignored in settings
|
||||||
|
[\#4466](https://github.com/matrix-org/matrix-react-sdk/pull/4466)
|
||||||
|
* Skip auth flow test for signing upload when password present
|
||||||
|
[\#4464](https://github.com/matrix-org/matrix-react-sdk/pull/4464)
|
||||||
|
* If user cannot set email during registration don't tell them to
|
||||||
|
[\#4461](https://github.com/matrix-org/matrix-react-sdk/pull/4461)
|
||||||
|
* Fix post-ts autocomplete, it is not null
|
||||||
|
[\#4463](https://github.com/matrix-org/matrix-react-sdk/pull/4463)
|
||||||
|
* Convert autocomplete stuff to TypeScript
|
||||||
|
[\#4452](https://github.com/matrix-org/matrix-react-sdk/pull/4452)
|
||||||
|
* Add a back button to the devtools verifications panel
|
||||||
|
[\#4455](https://github.com/matrix-org/matrix-react-sdk/pull/4455)
|
||||||
|
* Fix: wait until cross-signing keys are fetched to show verify button
|
||||||
|
[\#4456](https://github.com/matrix-org/matrix-react-sdk/pull/4456)
|
||||||
|
* Handle load error in create secret storage dialog
|
||||||
|
[\#4451](https://github.com/matrix-org/matrix-react-sdk/pull/4451)
|
||||||
|
* Allow iframes and Jitsi URLs in /addwidget
|
||||||
|
[\#4382](https://github.com/matrix-org/matrix-react-sdk/pull/4382)
|
||||||
|
* Support m.jitsi-typed widgets as Jitsi widgets
|
||||||
|
[\#4379](https://github.com/matrix-org/matrix-react-sdk/pull/4379)
|
||||||
|
* Don't recheck DeviceListener until after initial sync is finished
|
||||||
|
[\#4450](https://github.com/matrix-org/matrix-react-sdk/pull/4450)
|
||||||
|
* Fix CSS class in ButtonPlaceholder
|
||||||
|
[\#4449](https://github.com/matrix-org/matrix-react-sdk/pull/4449)
|
||||||
|
* Password Login make sure tab takes user to password field
|
||||||
|
[\#4441](https://github.com/matrix-org/matrix-react-sdk/pull/4441)
|
||||||
|
* Network Dropdown fix things not scrolling properly
|
||||||
|
[\#4439](https://github.com/matrix-org/matrix-react-sdk/pull/4439)
|
||||||
|
* ImageView make clicking off it easier
|
||||||
|
[\#4448](https://github.com/matrix-org/matrix-react-sdk/pull/4448)
|
||||||
|
* Add slash command to send a rageshake
|
||||||
|
[\#4443](https://github.com/matrix-org/matrix-react-sdk/pull/4443)
|
||||||
|
* EventIndex: Filter out events that don't have a propper content value.
|
||||||
|
[\#4446](https://github.com/matrix-org/matrix-react-sdk/pull/4446)
|
||||||
|
* Revert "Fix Filepanel scroll position state lost when room is changed"
|
||||||
|
[\#4445](https://github.com/matrix-org/matrix-react-sdk/pull/4445)
|
||||||
|
* Update seshat copy to remove trailing full stop
|
||||||
|
[\#4442](https://github.com/matrix-org/matrix-react-sdk/pull/4442)
|
||||||
|
* Fix Filepanel scroll position state lost when room is changed
|
||||||
|
[\#4388](https://github.com/matrix-org/matrix-react-sdk/pull/4388)
|
||||||
|
* Fix end-to-end tests for end-to-end encryption verification
|
||||||
|
[\#4436](https://github.com/matrix-org/matrix-react-sdk/pull/4436)
|
||||||
|
* Don't explode if the e2e test directory exists when crashing
|
||||||
|
[\#4437](https://github.com/matrix-org/matrix-react-sdk/pull/4437)
|
||||||
|
* Bump https-proxy-agent from 2.2.1 to 2.2.4 in /test/end-to-end-tests
|
||||||
|
[\#4430](https://github.com/matrix-org/matrix-react-sdk/pull/4430)
|
||||||
|
* Minor updates to e2e test instructions on Windows
|
||||||
|
[\#4432](https://github.com/matrix-org/matrix-react-sdk/pull/4432)
|
||||||
|
* Fix typo
|
||||||
|
[\#4435](https://github.com/matrix-org/matrix-react-sdk/pull/4435)
|
||||||
|
* Catch errors sooner so users can recover more easily
|
||||||
|
[\#4122](https://github.com/matrix-org/matrix-react-sdk/pull/4122)
|
||||||
|
* Rageshake: remind user of unsupported browser and send modernizr report
|
||||||
|
[\#4381](https://github.com/matrix-org/matrix-react-sdk/pull/4381)
|
||||||
|
* Design tweaks for DM Room Tiles
|
||||||
|
[\#4338](https://github.com/matrix-org/matrix-react-sdk/pull/4338)
|
||||||
|
* Don't break spills over multiple lines, ellipsis them at max-1-line
|
||||||
|
[\#4434](https://github.com/matrix-org/matrix-react-sdk/pull/4434)
|
||||||
|
* Turn the end-to-end tests back on and fix the lazy-loading tests
|
||||||
|
[\#4433](https://github.com/matrix-org/matrix-react-sdk/pull/4433)
|
||||||
|
* Fix key backup debug panel
|
||||||
|
[\#4431](https://github.com/matrix-org/matrix-react-sdk/pull/4431)
|
||||||
|
* Convert cross-signing feature flag to setting
|
||||||
|
[\#4416](https://github.com/matrix-org/matrix-react-sdk/pull/4416)
|
||||||
|
* Make RoomPublishSetting import-skinnable
|
||||||
|
[\#4428](https://github.com/matrix-org/matrix-react-sdk/pull/4428)
|
||||||
|
* Iterate cross-signing copy
|
||||||
|
[\#4425](https://github.com/matrix-org/matrix-react-sdk/pull/4425)
|
||||||
|
* Fix: ensure twemoji font is loaded when showing SAS emojis
|
||||||
|
[\#4422](https://github.com/matrix-org/matrix-react-sdk/pull/4422)
|
||||||
|
* Revert "Fix: load Twemoji before login so complete security gets the right
|
||||||
|
emojis during SAS"
|
||||||
|
[\#4421](https://github.com/matrix-org/matrix-react-sdk/pull/4421)
|
||||||
|
* Fix: load Twemoji before login so complete security gets the right emojis
|
||||||
|
during SAS
|
||||||
|
[\#4419](https://github.com/matrix-org/matrix-react-sdk/pull/4419)
|
||||||
|
* consolidate and fix copy to clipboard
|
||||||
|
[\#4410](https://github.com/matrix-org/matrix-react-sdk/pull/4410)
|
||||||
|
* Fix Message Context Menu options not displaying: block
|
||||||
|
[\#4418](https://github.com/matrix-org/matrix-react-sdk/pull/4418)
|
||||||
|
* Fix pills being broken by unescaped characters
|
||||||
|
[\#4411](https://github.com/matrix-org/matrix-react-sdk/pull/4411)
|
||||||
|
|
||||||
Changes in [2.5.0](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v2.5.0) (2020-05-05)
|
Changes in [2.5.0](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v2.5.0) (2020-05-05)
|
||||||
===================================================================================================
|
===================================================================================================
|
||||||
[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v2.5.0-rc.6...v2.5.0)
|
[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v2.5.0-rc.6...v2.5.0)
|
||||||
|
|
|
@ -4,7 +4,7 @@ Matrix JavaScript/ECMAScript Style Guide
|
||||||
The intention of this guide is to make Matrix's JavaScript codebase clean,
|
The intention of this guide is to make Matrix's JavaScript codebase clean,
|
||||||
consistent with other popular JavaScript styles and consistent with the rest of
|
consistent with other popular JavaScript styles and consistent with the rest of
|
||||||
the Matrix codebase. For reference, the Matrix Python style guide can be found
|
the Matrix codebase. For reference, the Matrix Python style guide can be found
|
||||||
at https://github.com/matrix-org/synapse/blob/master/docs/code_style.rst
|
at https://github.com/matrix-org/synapse/blob/master/docs/code_style.md
|
||||||
|
|
||||||
This document reflects how we would like Matrix JavaScript code to look, with
|
This document reflects how we would like Matrix JavaScript code to look, with
|
||||||
acknowledgement that a significant amount of code is written to older
|
acknowledgement that a significant amount of code is written to older
|
||||||
|
@ -17,7 +17,7 @@ writing in modern ECMAScript and using a transpile step to generate the file
|
||||||
that applications can then include. There are significant benefits in being
|
that applications can then include. There are significant benefits in being
|
||||||
able to use modern ECMAScript, although the tooling for doing so can be awkward
|
able to use modern ECMAScript, although the tooling for doing so can be awkward
|
||||||
for library code, especially with regard to translating source maps and line
|
for library code, especially with regard to translating source maps and line
|
||||||
number throgh from the original code to the final application.
|
number through from the original code to the final application.
|
||||||
|
|
||||||
General Style
|
General Style
|
||||||
-------------
|
-------------
|
||||||
|
@ -151,6 +151,7 @@ General Style
|
||||||
Don't set things to undefined. Reserve that value to mean "not yet set to anything."
|
Don't set things to undefined. Reserve that value to mean "not yet set to anything."
|
||||||
Boolean objects are verboten.
|
Boolean objects are verboten.
|
||||||
- Use JSDoc
|
- Use JSDoc
|
||||||
|
- Use switch-case statements where there are 5 or more branches running against the same variable.
|
||||||
|
|
||||||
ECMAScript
|
ECMAScript
|
||||||
----------
|
----------
|
||||||
|
|
11
package.json
11
package.json
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "matrix-react-sdk",
|
"name": "matrix-react-sdk",
|
||||||
"version": "2.5.0",
|
"version": "2.6.1",
|
||||||
"description": "SDK for matrix.org using React",
|
"description": "SDK for matrix.org using React",
|
||||||
"author": "matrix.org",
|
"author": "matrix.org",
|
||||||
"repository": {
|
"repository": {
|
||||||
|
@ -55,6 +55,7 @@
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/runtime": "^7.8.3",
|
"@babel/runtime": "^7.8.3",
|
||||||
|
"await-lock": "^2.0.1",
|
||||||
"blueimp-canvas-to-blob": "^3.5.0",
|
"blueimp-canvas-to-blob": "^3.5.0",
|
||||||
"browser-encrypt-attachment": "^0.3.0",
|
"browser-encrypt-attachment": "^0.3.0",
|
||||||
"browser-request": "^0.3.3",
|
"browser-request": "^0.3.3",
|
||||||
|
@ -117,9 +118,13 @@
|
||||||
"@babel/register": "^7.7.4",
|
"@babel/register": "^7.7.4",
|
||||||
"@peculiar/webcrypto": "^1.0.22",
|
"@peculiar/webcrypto": "^1.0.22",
|
||||||
"@types/classnames": "^2.2.10",
|
"@types/classnames": "^2.2.10",
|
||||||
|
"@types/flux": "^3.1.9",
|
||||||
|
"@types/lodash": "^4.14.152",
|
||||||
"@types/modernizr": "^3.5.3",
|
"@types/modernizr": "^3.5.3",
|
||||||
|
"@types/node": "^12.12.41",
|
||||||
"@types/qrcode": "^1.3.4",
|
"@types/qrcode": "^1.3.4",
|
||||||
"@types/react": "16.9",
|
"@types/react": "^16.9",
|
||||||
|
"@types/react-dom": "^16.9.8",
|
||||||
"@types/zxcvbn": "^4.4.0",
|
"@types/zxcvbn": "^4.4.0",
|
||||||
"babel-eslint": "^10.0.3",
|
"babel-eslint": "^10.0.3",
|
||||||
"babel-jest": "^24.9.0",
|
"babel-jest": "^24.9.0",
|
||||||
|
@ -139,6 +144,7 @@
|
||||||
"flow-parser": "^0.57.3",
|
"flow-parser": "^0.57.3",
|
||||||
"glob": "^5.0.14",
|
"glob": "^5.0.14",
|
||||||
"jest": "^24.9.0",
|
"jest": "^24.9.0",
|
||||||
|
"jest-canvas-mock": "^2.2.0",
|
||||||
"lolex": "^5.1.2",
|
"lolex": "^5.1.2",
|
||||||
"matrix-mock-request": "^1.2.3",
|
"matrix-mock-request": "^1.2.3",
|
||||||
"matrix-react-test-utils": "^0.2.2",
|
"matrix-react-test-utils": "^0.2.2",
|
||||||
|
@ -158,6 +164,7 @@
|
||||||
"testMatch": [
|
"testMatch": [
|
||||||
"<rootDir>/test/**/*-test.js"
|
"<rootDir>/test/**/*-test.js"
|
||||||
],
|
],
|
||||||
|
"setupFiles": ["jest-canvas-mock"],
|
||||||
"setupFilesAfterEnv": [
|
"setupFilesAfterEnv": [
|
||||||
"<rootDir>/test/setupTests.js"
|
"<rootDir>/test/setupTests.js"
|
||||||
],
|
],
|
||||||
|
|
|
@ -335,6 +335,9 @@ input[type=text]:focus, input[type=password]:focus, textarea:focus {
|
||||||
.mx_Dialog_header.mx_Dialog_headerWithButton > .mx_Dialog_title {
|
.mx_Dialog_header.mx_Dialog_headerWithButton > .mx_Dialog_title {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
.mx_Dialog_header.mx_Dialog_headerWithCancel > .mx_Dialog_title {
|
||||||
|
margin-right: 20px; // leave space for the 'X' cancel button
|
||||||
|
}
|
||||||
|
|
||||||
.mx_Dialog_title.danger {
|
.mx_Dialog_title.danger {
|
||||||
color: $warning-color;
|
color: $warning-color;
|
||||||
|
|
|
@ -63,7 +63,6 @@
|
||||||
@import "./views/dialogs/_DeactivateAccountDialog.scss";
|
@import "./views/dialogs/_DeactivateAccountDialog.scss";
|
||||||
@import "./views/dialogs/_DeviceVerifyDialog.scss";
|
@import "./views/dialogs/_DeviceVerifyDialog.scss";
|
||||||
@import "./views/dialogs/_DevtoolsDialog.scss";
|
@import "./views/dialogs/_DevtoolsDialog.scss";
|
||||||
@import "./views/dialogs/_EncryptedEventDialog.scss";
|
|
||||||
@import "./views/dialogs/_GroupAddressPicker.scss";
|
@import "./views/dialogs/_GroupAddressPicker.scss";
|
||||||
@import "./views/dialogs/_IncomingSasDialog.scss";
|
@import "./views/dialogs/_IncomingSasDialog.scss";
|
||||||
@import "./views/dialogs/_InviteDialog.scss";
|
@import "./views/dialogs/_InviteDialog.scss";
|
||||||
|
@ -115,7 +114,9 @@
|
||||||
@import "./views/elements/_RichText.scss";
|
@import "./views/elements/_RichText.scss";
|
||||||
@import "./views/elements/_RoleButton.scss";
|
@import "./views/elements/_RoleButton.scss";
|
||||||
@import "./views/elements/_RoomAliasField.scss";
|
@import "./views/elements/_RoomAliasField.scss";
|
||||||
|
@import "./views/elements/_Slider.scss";
|
||||||
@import "./views/elements/_Spinner.scss";
|
@import "./views/elements/_Spinner.scss";
|
||||||
|
@import "./views/elements/_StyledCheckbox.scss";
|
||||||
@import "./views/elements/_SyntaxHighlight.scss";
|
@import "./views/elements/_SyntaxHighlight.scss";
|
||||||
@import "./views/elements/_TextWithTooltip.scss";
|
@import "./views/elements/_TextWithTooltip.scss";
|
||||||
@import "./views/elements/_ToggleSwitch.scss";
|
@import "./views/elements/_ToggleSwitch.scss";
|
||||||
|
@ -123,7 +124,6 @@
|
||||||
@import "./views/elements/_TooltipButton.scss";
|
@import "./views/elements/_TooltipButton.scss";
|
||||||
@import "./views/elements/_Validation.scss";
|
@import "./views/elements/_Validation.scss";
|
||||||
@import "./views/emojipicker/_EmojiPicker.scss";
|
@import "./views/emojipicker/_EmojiPicker.scss";
|
||||||
@import "./views/globals/_MatrixToolbar.scss";
|
|
||||||
@import "./views/groups/_GroupPublicityToggle.scss";
|
@import "./views/groups/_GroupPublicityToggle.scss";
|
||||||
@import "./views/groups/_GroupRoomList.scss";
|
@import "./views/groups/_GroupRoomList.scss";
|
||||||
@import "./views/groups/_GroupUserSettings.scss";
|
@import "./views/groups/_GroupUserSettings.scss";
|
||||||
|
@ -162,6 +162,8 @@
|
||||||
@import "./views/rooms/_EditMessageComposer.scss";
|
@import "./views/rooms/_EditMessageComposer.scss";
|
||||||
@import "./views/rooms/_EntityTile.scss";
|
@import "./views/rooms/_EntityTile.scss";
|
||||||
@import "./views/rooms/_EventTile.scss";
|
@import "./views/rooms/_EventTile.scss";
|
||||||
|
@import "./views/rooms/_GroupLayout.scss";
|
||||||
|
@import "./views/rooms/_IRCLayout.scss";
|
||||||
@import "./views/rooms/_InviteOnlyIcon.scss";
|
@import "./views/rooms/_InviteOnlyIcon.scss";
|
||||||
@import "./views/rooms/_JumpToBottomButton.scss";
|
@import "./views/rooms/_JumpToBottomButton.scss";
|
||||||
@import "./views/rooms/_LinkPreviewWidget.scss";
|
@import "./views/rooms/_LinkPreviewWidget.scss";
|
||||||
|
@ -200,10 +202,12 @@
|
||||||
@import "./views/settings/_ProfileSettings.scss";
|
@import "./views/settings/_ProfileSettings.scss";
|
||||||
@import "./views/settings/_SetIdServer.scss";
|
@import "./views/settings/_SetIdServer.scss";
|
||||||
@import "./views/settings/_SetIntegrationManager.scss";
|
@import "./views/settings/_SetIntegrationManager.scss";
|
||||||
|
@import "./views/settings/_UpdateCheckButton.scss";
|
||||||
@import "./views/settings/tabs/_SettingsTab.scss";
|
@import "./views/settings/tabs/_SettingsTab.scss";
|
||||||
@import "./views/settings/tabs/room/_GeneralRoomSettingsTab.scss";
|
@import "./views/settings/tabs/room/_GeneralRoomSettingsTab.scss";
|
||||||
@import "./views/settings/tabs/room/_RolesRoomSettingsTab.scss";
|
@import "./views/settings/tabs/room/_RolesRoomSettingsTab.scss";
|
||||||
@import "./views/settings/tabs/room/_SecurityRoomSettingsTab.scss";
|
@import "./views/settings/tabs/room/_SecurityRoomSettingsTab.scss";
|
||||||
|
@import "./views/settings/tabs/user/_AppearanceUserSettingsTab.scss";
|
||||||
@import "./views/settings/tabs/user/_GeneralUserSettingsTab.scss";
|
@import "./views/settings/tabs/user/_GeneralUserSettingsTab.scss";
|
||||||
@import "./views/settings/tabs/user/_HelpUserSettingsTab.scss";
|
@import "./views/settings/tabs/user/_HelpUserSettingsTab.scss";
|
||||||
@import "./views/settings/tabs/user/_MjolnirUserSettingsTab.scss";
|
@import "./views/settings/tabs/user/_MjolnirUserSettingsTab.scss";
|
||||||
|
|
|
@ -15,6 +15,7 @@ limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
$font-1px: 0.067rem;
|
$font-1px: 0.067rem;
|
||||||
|
$font-1-5px: 0.100rem;
|
||||||
$font-2px: 0.133rem;
|
$font-2px: 0.133rem;
|
||||||
$font-3px: 0.200rem;
|
$font-3px: 0.200rem;
|
||||||
$font-4px: 0.267rem;
|
$font-4px: 0.267rem;
|
||||||
|
|
|
@ -41,10 +41,6 @@ limitations under the License.
|
||||||
height: 40px;
|
height: 40px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_MatrixChat_toolbarShowing {
|
|
||||||
height: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_MatrixChat {
|
.mx_MatrixChat {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
|
|
@ -63,6 +63,10 @@ limitations under the License.
|
||||||
padding-left: 32px;
|
padding-left: 32px;
|
||||||
padding-top: 8px;
|
padding-top: 8px;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
|
a {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_NotificationPanel .mx_EventTile_roomName a,
|
.mx_NotificationPanel .mx_EventTile_roomName a,
|
||||||
|
|
|
@ -69,7 +69,7 @@ limitations under the License.
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
.mx_TagPanel .mx_TagPanel_tagTileContainer > div {
|
.mx_TagPanel .mx_TagPanel_tagTileContainer > div {
|
||||||
height: $font-40px;
|
height: 40px;
|
||||||
padding: 10px 0 9px 0;
|
padding: 10px 0 9px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -116,7 +116,7 @@ limitations under the License.
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: -15px;
|
left: -15px;
|
||||||
border-radius: 0 3px 3px 0;
|
border-radius: 0 3px 3px 0;
|
||||||
top: -8px; // (16px / 2)
|
top: -8px; // (16px from height / 2)
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_TagPanel .mx_TagTile.mx_AccessibleButton:focus {
|
.mx_TagPanel .mx_TagTile.mx_AccessibleButton:focus {
|
||||||
|
|
|
@ -28,8 +28,8 @@ limitations under the License.
|
||||||
margin: 0 4px;
|
margin: 0 4px;
|
||||||
grid-row: 2 / 4;
|
grid-row: 2 / 4;
|
||||||
grid-column: 1;
|
grid-column: 1;
|
||||||
background-color: white;
|
background-color: $dark-panel-bg-color;
|
||||||
box-shadow: 0px 4px 12px $menu-box-shadow-color;
|
box-shadow: 0px 4px 20px rgba(0, 0, 0, 0.5);
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -37,16 +37,15 @@ limitations under the License.
|
||||||
grid-row: 1 / 3;
|
grid-row: 1 / 3;
|
||||||
grid-column: 1;
|
grid-column: 1;
|
||||||
color: $primary-fg-color;
|
color: $primary-fg-color;
|
||||||
background-color: $primary-bg-color;
|
background-color: $dark-panel-bg-color;
|
||||||
box-shadow: 0px 4px 12px $menu-box-shadow-color;
|
box-shadow: 0px 4px 20px rgba(0, 0, 0, 0.5);
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 20px 1fr;
|
grid-template-columns: 22px 1fr;
|
||||||
column-gap: 10px;
|
column-gap: 8px;
|
||||||
row-gap: 4px;
|
row-gap: 4px;
|
||||||
padding: 8px;
|
padding: 8px;
|
||||||
padding-right: 16px;
|
|
||||||
|
|
||||||
&.mx_Toast_hasIcon {
|
&.mx_Toast_hasIcon {
|
||||||
&::after {
|
&::after {
|
||||||
|
@ -68,10 +67,26 @@ limitations under the License.
|
||||||
background-image: url("$(res)/img/e2e/warning.svg");
|
background-image: url("$(res)/img/e2e/warning.svg");
|
||||||
}
|
}
|
||||||
|
|
||||||
h2, .mx_Toast_body {
|
.mx_Toast_title, .mx_Toast_body {
|
||||||
grid-column: 2;
|
grid-column: 2;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
&:not(.mx_Toast_hasIcon) {
|
||||||
|
padding-left: 12px;
|
||||||
|
|
||||||
|
.mx_Toast_title {
|
||||||
|
grid-column: 1 / -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_Toast_title,
|
||||||
|
.mx_Toast_description {
|
||||||
|
padding-right: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_Toast_title {
|
||||||
|
width: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
|
||||||
h2 {
|
h2 {
|
||||||
grid-column: 1 / 3;
|
grid-column: 1 / 3;
|
||||||
|
@ -79,6 +94,18 @@ limitations under the License.
|
||||||
margin: 0;
|
margin: 0;
|
||||||
font-size: $font-15px;
|
font-size: $font-15px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
|
display: inline;
|
||||||
|
width: auto;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
span {
|
||||||
|
padding-left: 8px;
|
||||||
|
float: right;
|
||||||
|
font-size: $font-12px;
|
||||||
|
line-height: $font-22px;
|
||||||
|
color: $muted-fg-color;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_Toast_body {
|
.mx_Toast_body {
|
||||||
|
@ -87,7 +114,13 @@ limitations under the License.
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_Toast_buttons {
|
.mx_Toast_buttons {
|
||||||
|
float: right;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
||||||
|
.mx_FormButton {
|
||||||
|
min-width: 96px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_Toast_description {
|
.mx_Toast_description {
|
||||||
|
@ -96,6 +129,15 @@ limitations under the License.
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
margin: 4px 0 11px 0;
|
margin: 4px 0 11px 0;
|
||||||
font-size: $font-12px;
|
font-size: $font-12px;
|
||||||
|
|
||||||
|
.mx_AccessibleButton_kind_link {
|
||||||
|
font-size: inherit;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_Toast_deviceID {
|
.mx_Toast_deviceID {
|
||||||
|
|
|
@ -43,7 +43,7 @@ limitations under the License.
|
||||||
margin: 0 7px;
|
margin: 0 7px;
|
||||||
mask: url('$(res)/img/feather-customised/dropdown-arrow.svg');
|
mask: url('$(res)/img/feather-customised/dropdown-arrow.svg');
|
||||||
mask-repeat: no-repeat;
|
mask-repeat: no-repeat;
|
||||||
width: 10px;
|
width: $font-22px;
|
||||||
height: 6px;
|
height: 6px;
|
||||||
background-color: $roomsublist-label-fg-color;
|
background-color: $roomsublist-label-fg-color;
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,8 +18,3 @@ limitations under the License.
|
||||||
margin-top: 10px;
|
margin-top: 10px;
|
||||||
display: flex;
|
display: flex;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_GroupAddressPicker_checkboxContainer input[type="checkbox"] {
|
|
||||||
/* Stop flex from shrinking the checkbox */
|
|
||||||
width: 20px;
|
|
||||||
}
|
|
||||||
|
|
|
@ -55,6 +55,7 @@ limitations under the License.
|
||||||
margin-left: 5px;
|
margin-left: 5px;
|
||||||
width: 20px;
|
width: 20px;
|
||||||
height: 20px;
|
height: 20px;
|
||||||
|
background-repeat: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_ShareDialog_split {
|
.mx_ShareDialog_split {
|
||||||
|
|
|
@ -21,6 +21,10 @@ limitations under the License.
|
||||||
mask-image: url('$(res)/img/feather-customised/settings.svg');
|
mask-image: url('$(res)/img/feather-customised/settings.svg');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mx_UserSettingsDialog_appearanceIcon::before {
|
||||||
|
mask-image: url('$(res)/img/feather-customised/brush.svg');
|
||||||
|
}
|
||||||
|
|
||||||
.mx_UserSettingsDialog_voiceIcon::before {
|
.mx_UserSettingsDialog_voiceIcon::before {
|
||||||
mask-image: url('$(res)/img/feather-customised/phone.svg');
|
mask-image: url('$(res)/img/feather-customised/phone.svg');
|
||||||
}
|
}
|
||||||
|
|
99
res/css/views/elements/_Slider.scss
Normal file
99
res/css/views/elements/_Slider.scss
Normal file
|
@ -0,0 +1,99 @@
|
||||||
|
/*
|
||||||
|
Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||||
|
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
.mx_Slider {
|
||||||
|
position: relative;
|
||||||
|
margin: 0px;
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_Slider_dotContainer {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_Slider_bar {
|
||||||
|
display: flex;
|
||||||
|
box-sizing: border-box;
|
||||||
|
position: absolute;
|
||||||
|
height: 1em;
|
||||||
|
width: 100%;
|
||||||
|
padding: 0 0.5em; // half the width of a dot.
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_Slider_bar > hr {
|
||||||
|
width: 100%;
|
||||||
|
height: 0.4em;
|
||||||
|
background-color: $slider-background-color;
|
||||||
|
border: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_Slider_selection {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
width: calc(100% - 1em); // 2 * half the width of a dot
|
||||||
|
height: 1em;
|
||||||
|
position: absolute;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_Slider_selectionDot {
|
||||||
|
position: absolute;
|
||||||
|
width: 1.1em;
|
||||||
|
height: 1.1em;
|
||||||
|
background-color: $slider-selection-color;
|
||||||
|
border-radius: 50%;
|
||||||
|
box-shadow: 0 0 6px lightgrey;
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_Slider_selection > hr {
|
||||||
|
margin: 0;
|
||||||
|
border: 0.2em solid $slider-selection-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_Slider_dot {
|
||||||
|
height: 1em;
|
||||||
|
width: 1em;
|
||||||
|
border-radius: 50%;
|
||||||
|
background-color: $slider-background-color;
|
||||||
|
z-index: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_Slider_dotActive {
|
||||||
|
background-color: $slider-selection-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_Slider_dotValue {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
color: $slider-background-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The following is a hack to center the labels without adding
|
||||||
|
// any width to the slider's dots.
|
||||||
|
.mx_Slider_labelContainer {
|
||||||
|
width: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_Slider_label {
|
||||||
|
position: relative;
|
||||||
|
width: fit-content;
|
||||||
|
left: -50%;
|
||||||
|
}
|
66
res/css/views/elements/_StyledCheckbox.scss
Normal file
66
res/css/views/elements/_StyledCheckbox.scss
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
/*
|
||||||
|
Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||||
|
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
.mx_Checkbox {
|
||||||
|
$size: $font-16px;
|
||||||
|
$border-size: $font-1-5px;
|
||||||
|
$border-radius: $font-4px;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
|
||||||
|
input[type=checkbox] {
|
||||||
|
display: none;
|
||||||
|
|
||||||
|
& + label {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
& + label > .mx_Checkbox_background {
|
||||||
|
display: inline-flex;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
flex-shrink: 0;
|
||||||
|
|
||||||
|
height: $size;
|
||||||
|
width: $size;
|
||||||
|
size: 0.5rem;
|
||||||
|
|
||||||
|
border: $border-size solid rgba($muted-fg-color, 0.5);
|
||||||
|
box-sizing: border-box;
|
||||||
|
border-radius: $border-radius;
|
||||||
|
|
||||||
|
img {
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
filter: invert(100%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:checked + label > .mx_Checkbox_background {
|
||||||
|
background: $accent-color;
|
||||||
|
border-color: $accent-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
& + label > *:not(.mx_Checkbox_background) {
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -190,7 +190,7 @@ limitations under the License.
|
||||||
|
|
||||||
.mx_EmojiPicker_footer {
|
.mx_EmojiPicker_footer {
|
||||||
border-top: 1px solid $message-action-bar-border-color;
|
border-top: 1px solid $message-action-bar-border-color;
|
||||||
height: 72px;
|
min-height: 72px;
|
||||||
|
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
|
@ -1,73 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright 2015, 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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
.mx_MatrixToolbar {
|
|
||||||
background-color: $accent-color;
|
|
||||||
color: $accent-fg-color;
|
|
||||||
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_MatrixToolbar_warning {
|
|
||||||
margin-left: 16px;
|
|
||||||
margin-right: 8px;
|
|
||||||
margin-top: -2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_MatrixToolbar_info {
|
|
||||||
padding-left: 16px;
|
|
||||||
padding-right: 8px;
|
|
||||||
background-color: $info-bg-color;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_MatrixToolbar_error {
|
|
||||||
padding-left: 16px;
|
|
||||||
padding-right: 8px;
|
|
||||||
background-color: $warning-bg-color;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_MatrixToolbar_content {
|
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_MatrixToolbar_link {
|
|
||||||
color: $accent-fg-color !important;
|
|
||||||
text-decoration: underline !important;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_MatrixToolbar_clickable {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_MatrixToolbar_close {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_MatrixToolbar_close img {
|
|
||||||
display: block;
|
|
||||||
float: right;
|
|
||||||
margin-right: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_MatrixToolbar_action {
|
|
||||||
margin-right: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_MatrixToolbar_changelog {
|
|
||||||
white-space: pre;
|
|
||||||
}
|
|
|
@ -96,6 +96,10 @@ $AppsDrawerBodyHeight: 273px;
|
||||||
height: $AppsDrawerBodyHeight;
|
height: $AppsDrawerBodyHeight;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mx_AppTile_persistedWrapper > div {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
.mx_AppTile_mini .mx_AppTile_persistedWrapper {
|
.mx_AppTile_mini .mx_AppTile_persistedWrapper {
|
||||||
height: 114px;
|
height: 114px;
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,7 +37,6 @@ limitations under the License.
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_EventTile_avatar {
|
.mx_EventTile_avatar {
|
||||||
position: absolute;
|
|
||||||
top: 14px;
|
top: 14px;
|
||||||
left: 8px;
|
left: 8px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
@ -68,11 +67,9 @@ limitations under the License.
|
||||||
display: inline-block; /* anti-zalgo, with overflow hidden */
|
display: inline-block; /* anti-zalgo, with overflow hidden */
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
padding-left: 65px; /* left gutter */
|
|
||||||
padding-bottom: 0px;
|
padding-bottom: 0px;
|
||||||
padding-top: 0px;
|
padding-top: 0px;
|
||||||
margin: 0px;
|
margin: 0px;
|
||||||
line-height: $font-17px;
|
|
||||||
/* the next three lines, along with overflow hidden, truncate long display names */
|
/* the next three lines, along with overflow hidden, truncate long display names */
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
|
@ -104,9 +101,7 @@ limitations under the License.
|
||||||
visibility: hidden;
|
visibility: hidden;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
left: 0px;
|
left: 0px;
|
||||||
width: 46px; /* 8 + 30 (avatar) + 8 */
|
|
||||||
text-align: center;
|
text-align: center;
|
||||||
position: absolute;
|
|
||||||
user-select: none;
|
user-select: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -117,10 +112,7 @@ limitations under the License.
|
||||||
.mx_EventTile_line, .mx_EventTile_reply {
|
.mx_EventTile_line, .mx_EventTile_reply {
|
||||||
position: relative;
|
position: relative;
|
||||||
padding-left: 65px; /* left gutter */
|
padding-left: 65px; /* left gutter */
|
||||||
padding-top: 3px;
|
|
||||||
padding-bottom: 3px;
|
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
line-height: $font-22px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_RoomView_timeline_rr_enabled,
|
.mx_RoomView_timeline_rr_enabled,
|
||||||
|
@ -151,10 +143,6 @@ limitations under the License.
|
||||||
margin-right: 10px;
|
margin-right: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_EventTile_info .mx_EventTile_line {
|
|
||||||
padding-left: 83px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* HACK to override line-height which is already marked important elsewhere */
|
/* HACK to override line-height which is already marked important elsewhere */
|
||||||
.mx_EventTile_bigEmoji.mx_EventTile_bigEmoji {
|
.mx_EventTile_bigEmoji.mx_EventTile_bigEmoji {
|
||||||
font-size: 48px !important;
|
font-size: 48px !important;
|
||||||
|
@ -171,10 +159,15 @@ limitations under the License.
|
||||||
}
|
}
|
||||||
|
|
||||||
// Explicit relationships so that it doesn't apply to nested EventTile components (e.g in Replies)
|
// Explicit relationships so that it doesn't apply to nested EventTile components (e.g in Replies)
|
||||||
|
// The first set is to handle the 'group layout' (default) and the second for the IRC layout
|
||||||
.mx_EventTile_last > div > a > .mx_MessageTimestamp,
|
.mx_EventTile_last > div > a > .mx_MessageTimestamp,
|
||||||
.mx_EventTile:hover > div > a > .mx_MessageTimestamp,
|
.mx_EventTile:hover > div > a > .mx_MessageTimestamp,
|
||||||
.mx_EventTile.mx_EventTile_actionBarFocused > div > a > .mx_MessageTimestamp,
|
.mx_EventTile.mx_EventTile_actionBarFocused > div > a > .mx_MessageTimestamp,
|
||||||
.mx_EventTile.focus-visible:focus-within > div > a > .mx_MessageTimestamp {
|
.mx_EventTile.focus-visible:focus-within > div > a > .mx_MessageTimestamp,
|
||||||
|
.mx_IRCLayout .mx_EventTile_last > a > .mx_MessageTimestamp,
|
||||||
|
.mx_IRCLayout .mx_EventTile:hover > a > .mx_MessageTimestamp,
|
||||||
|
.mx_IRCLayout .mx_EventTile.mx_EventTile_actionBarFocused > a > .mx_MessageTimestamp,
|
||||||
|
.mx_IRCLayout .mx_EventTile.focus-visible:focus-within > a > .mx_MessageTimestamp {
|
||||||
visibility: visible;
|
visibility: visible;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -560,84 +553,6 @@ limitations under the License.
|
||||||
|
|
||||||
/* end of overrides */
|
/* end of overrides */
|
||||||
|
|
||||||
.mx_MatrixChat_useCompactLayout {
|
|
||||||
.mx_EventTile {
|
|
||||||
padding-top: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_EventTile.mx_EventTile_info {
|
|
||||||
// same as the padding for non-compact .mx_EventTile.mx_EventTile_info
|
|
||||||
padding-top: 0px;
|
|
||||||
font-size: $font-13px;
|
|
||||||
.mx_EventTile_line, .mx_EventTile_reply {
|
|
||||||
line-height: $font-20px;
|
|
||||||
}
|
|
||||||
.mx_EventTile_avatar {
|
|
||||||
top: 4px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_EventTile .mx_SenderProfile {
|
|
||||||
font-size: $font-13px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_EventTile.mx_EventTile_emote {
|
|
||||||
// add a bit more space for emotes so that avatars don't collide
|
|
||||||
padding-top: 8px;
|
|
||||||
.mx_EventTile_avatar {
|
|
||||||
top: 2px;
|
|
||||||
}
|
|
||||||
.mx_EventTile_line, .mx_EventTile_reply {
|
|
||||||
padding-top: 0px;
|
|
||||||
padding-bottom: 1px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_EventTile.mx_EventTile_emote.mx_EventTile_continuation {
|
|
||||||
padding-top: 0;
|
|
||||||
.mx_EventTile_line, .mx_EventTile_reply {
|
|
||||||
padding-top: 0px;
|
|
||||||
padding-bottom: 0px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_EventTile_line, .mx_EventTile_reply {
|
|
||||||
padding-top: 0px;
|
|
||||||
padding-bottom: 0px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_EventTile_avatar {
|
|
||||||
top: 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_EventTile_e2eIcon {
|
|
||||||
top: 3px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_EventTile_readAvatars {
|
|
||||||
top: 27px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_EventTile_continuation .mx_EventTile_readAvatars,
|
|
||||||
.mx_EventTile_emote .mx_EventTile_readAvatars {
|
|
||||||
top: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_EventTile_info .mx_EventTile_readAvatars {
|
|
||||||
top: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_RoomView_MessageList h2 {
|
|
||||||
margin-top: 6px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_EventTile_content .markdown-body {
|
|
||||||
p, ul, ol, dl, blockquote, pre, table {
|
|
||||||
margin-bottom: 4px; // 1/4 of the non-compact margin-bottom
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_EventTile_tileError {
|
.mx_EventTile_tileError {
|
||||||
color: red;
|
color: red;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
|
131
res/css/views/rooms/_GroupLayout.scss
Normal file
131
res/css/views/rooms/_GroupLayout.scss
Normal file
|
@ -0,0 +1,131 @@
|
||||||
|
/*
|
||||||
|
Copyright 2015, 2016 OpenMarket Ltd
|
||||||
|
Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||||
|
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
$left-gutter: 65px;
|
||||||
|
|
||||||
|
.mx_GroupLayout {
|
||||||
|
|
||||||
|
.mx_EventTile {
|
||||||
|
> .mx_SenderProfile {
|
||||||
|
line-height: $font-17px;
|
||||||
|
padding-left: $left-gutter;
|
||||||
|
}
|
||||||
|
|
||||||
|
> .mx_EventTile_line {
|
||||||
|
padding-left: $left-gutter;
|
||||||
|
}
|
||||||
|
|
||||||
|
> .mx_EventTile_avatar {
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_MessageTimestamp {
|
||||||
|
position: absolute;
|
||||||
|
width: 46px; /* 8 + 30 (avatar) + 8 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_EventTile_line, .mx_EventTile_reply {
|
||||||
|
padding-top: 3px;
|
||||||
|
padding-bottom: 3px;
|
||||||
|
line-height: $font-22px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_EventTile_info .mx_EventTile_line {
|
||||||
|
padding-left: calc($left-gutter + 18px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Compact layout overrides */
|
||||||
|
|
||||||
|
.mx_MatrixChat_useCompactLayout {
|
||||||
|
.mx_EventTile_line, .mx_EventTile_reply {
|
||||||
|
padding-top: 0px;
|
||||||
|
padding-bottom: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_EventTile {
|
||||||
|
padding-top: 4px;
|
||||||
|
|
||||||
|
&.mx_EventTile_info {
|
||||||
|
// same as the padding for non-compact .mx_EventTile.mx_EventTile_info
|
||||||
|
padding-top: 0px;
|
||||||
|
font-size: $font-13px;
|
||||||
|
.mx_EventTile_line, .mx_EventTile_reply {
|
||||||
|
line-height: $font-20px;
|
||||||
|
}
|
||||||
|
.mx_EventTile_avatar {
|
||||||
|
top: 4px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_SenderProfile {
|
||||||
|
font-size: $font-13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.mx_EventTile_emote {
|
||||||
|
// add a bit more space for emotes so that avatars don't collide
|
||||||
|
padding-top: 8px;
|
||||||
|
.mx_EventTile_avatar {
|
||||||
|
top: 2px;
|
||||||
|
}
|
||||||
|
.mx_EventTile_line, .mx_EventTile_reply {
|
||||||
|
padding-top: 0px;
|
||||||
|
padding-bottom: 1px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.mx_EventTile_emote.mx_EventTile_continuation {
|
||||||
|
padding-top: 0;
|
||||||
|
.mx_EventTile_line, .mx_EventTile_reply {
|
||||||
|
padding-top: 0px;
|
||||||
|
padding-bottom: 0px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_EventTile_avatar {
|
||||||
|
top: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_EventTile_e2eIcon {
|
||||||
|
top: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_EventTile_readAvatars {
|
||||||
|
top: 27px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_EventTile_continuation .mx_EventTile_readAvatars,
|
||||||
|
.mx_EventTile_emote .mx_EventTile_readAvatars {
|
||||||
|
top: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_EventTile_info .mx_EventTile_readAvatars {
|
||||||
|
top: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_EventTile_content .markdown-body {
|
||||||
|
p, ul, ol, dl, blockquote, pre, table {
|
||||||
|
margin-bottom: 4px; // 1/4 of the non-compact margin-bottom
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_RoomView_MessageList h2 {
|
||||||
|
margin-top: 6px;
|
||||||
|
}
|
||||||
|
}
|
218
res/css/views/rooms/_IRCLayout.scss
Normal file
218
res/css/views/rooms/_IRCLayout.scss
Normal file
|
@ -0,0 +1,218 @@
|
||||||
|
/*
|
||||||
|
Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||||
|
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
$icon-width: 14px;
|
||||||
|
$timestamp-width: 45px;
|
||||||
|
$right-padding: 5px;
|
||||||
|
$irc-line-height: $font-18px;
|
||||||
|
|
||||||
|
.mx_IRCLayout {
|
||||||
|
--name-width: 70px;
|
||||||
|
|
||||||
|
line-height: $irc-line-height !important;
|
||||||
|
|
||||||
|
.mx_EventTile {
|
||||||
|
|
||||||
|
// timestamps are links which shouldn't be underlined
|
||||||
|
> a {
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: flex-start;
|
||||||
|
padding-top: 0;
|
||||||
|
|
||||||
|
> * {
|
||||||
|
margin-right: $right-padding;
|
||||||
|
}
|
||||||
|
|
||||||
|
> .mx_EventTile_msgOption {
|
||||||
|
order: 5;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
> .mx_SenderProfile {
|
||||||
|
order: 2;
|
||||||
|
flex-shrink: 0;
|
||||||
|
width: var(--name-width);
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
text-align: right;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
overflow: visible;
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_EventTile_line, .mx_EventTile_reply {
|
||||||
|
padding: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
order: 3;
|
||||||
|
flex-grow: 1;
|
||||||
|
flex-shrink: 1;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
> .mx_EventTile_avatar {
|
||||||
|
order: 1;
|
||||||
|
position: relative;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
flex-shrink: 0;
|
||||||
|
height: $irc-line-height;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
// Need to use important to override the js provided height and width values.
|
||||||
|
> .mx_BaseAvatar, .mx_BaseAvatar > * {
|
||||||
|
height: $font-14px !important;
|
||||||
|
width: $font-14px !important;
|
||||||
|
font-size: $font-10px !important;
|
||||||
|
line-height: $font-15px !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_MessageTimestamp {
|
||||||
|
font-size: $font-10px;
|
||||||
|
width: $timestamp-width;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
> .mx_EventTile_e2eIcon {
|
||||||
|
position: relative;
|
||||||
|
right: unset;
|
||||||
|
left: unset;
|
||||||
|
padding: 0;
|
||||||
|
order: 3;
|
||||||
|
flex-shrink: 0;
|
||||||
|
flex-grow: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_EventTile_line {
|
||||||
|
.mx_EventTile_e2eIcon,
|
||||||
|
.mx_TextualEvent,
|
||||||
|
.mx_MTextBody,
|
||||||
|
.mx_ReplyThread_wrapper_empty {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_EvenTile_line .mx_MessageActionBar,
|
||||||
|
.mx_EvenTile_line .mx_ReplyThread_wrapper {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_EventTile_reply {
|
||||||
|
order: 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_EditMessageComposer_buttons {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_EventTile_emote {
|
||||||
|
> .mx_EventTile_avatar {
|
||||||
|
margin-left: calc(var(--name-width) + $icon-width + $right-padding);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
blockquote {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_EventListSummary {
|
||||||
|
> .mx_EventTile_line {
|
||||||
|
padding-left: calc(var(--name-width) + $icon-width + $timestamp-width + 3 * $right-padding); // 15 px of padding
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_EventListSummary_avatars {
|
||||||
|
padding: 0;
|
||||||
|
margin: 0 9px 0 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_EventTile.mx_EventTile_info {
|
||||||
|
.mx_EventTile_avatar {
|
||||||
|
left: calc(var(--name-width) + 10px + $icon-width);
|
||||||
|
top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_EventTile_line {
|
||||||
|
left: calc(var(--name-width) + 10px + $icon-width);
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_TextualEvent {
|
||||||
|
line-height: $irc-line-height;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Suppress highlight thing from the normal Layout.
|
||||||
|
.mx_EventTile:hover.mx_EventTile_verified .mx_EventTile_line,
|
||||||
|
.mx_EventTile:hover.mx_EventTile_unverified .mx_EventTile_line,
|
||||||
|
.mx_EventTile:hover.mx_EventTile_unknown .mx_EventTile_line {
|
||||||
|
padding-left: 0;
|
||||||
|
border-left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_SenderProfile_hover {
|
||||||
|
background-color: $primary-bg-color;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
> span {
|
||||||
|
display: flex;
|
||||||
|
|
||||||
|
> .mx_SenderProfile_name {
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_SenderProfile:hover {
|
||||||
|
justify-content: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_SenderProfile_hover:hover {
|
||||||
|
overflow: visible;
|
||||||
|
width: max(auto, 100%);
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_ReplyThread {
|
||||||
|
margin: 0;
|
||||||
|
.mx_SenderProfile {
|
||||||
|
width: unset;
|
||||||
|
max-width: var(--name-width);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_ProfileResizer {
|
||||||
|
position: absolute;
|
||||||
|
height: 100%;
|
||||||
|
width: 15px;
|
||||||
|
left: calc(80px + var(--name-width));
|
||||||
|
cursor: col-resize;
|
||||||
|
z-index: 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Need to use important to override the js provided height and width values.
|
||||||
|
.mx_Flair > img {
|
||||||
|
height: $font-14px !important;
|
||||||
|
width: $font-14px !important;
|
||||||
|
}
|
||||||
|
}
|
|
@ -20,7 +20,7 @@ limitations under the License.
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
height: $font-34px;
|
height: 34px;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0 8px 0 10px;
|
padding: 0 8px 0 10px;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
@ -81,6 +81,7 @@ limitations under the License.
|
||||||
|
|
||||||
.mx_RoomTile_avatar_container {
|
.mx_RoomTile_avatar_container {
|
||||||
position: relative;
|
position: relative;
|
||||||
|
display: flex;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_RoomTile_avatar {
|
.mx_RoomTile_avatar {
|
||||||
|
|
23
res/css/views/settings/_UpdateCheckButton.scss
Normal file
23
res/css/views/settings/_UpdateCheckButton.scss
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
/*
|
||||||
|
Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||||
|
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
.mx_UpdateCheckButton_summary {
|
||||||
|
margin-left: 16px;
|
||||||
|
|
||||||
|
.mx_AccessibleButton_kind_link {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,45 @@
|
||||||
|
/*
|
||||||
|
Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||||
|
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
.mx_AppearanceUserSettingsTab_fontSlider,
|
||||||
|
.mx_AppearanceUserSettingsTab_themeSection .mx_Field,
|
||||||
|
.mx_AppearanceUserSettingsTab_fontScaling .mx_Field {
|
||||||
|
@mixin mx_Settings_fullWidthField;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_AppearanceUserSettingsTab_fontSlider {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
padding: 15px;
|
||||||
|
background: $font-slider-bg-color;
|
||||||
|
border-radius: 10px;
|
||||||
|
font-size: 10px;
|
||||||
|
margin-top: 24px;
|
||||||
|
margin-bottom: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_AppearanceUserSettingsTab_fontSlider_smallText {
|
||||||
|
font-size: 15px;
|
||||||
|
padding-right: 20px;
|
||||||
|
padding-left: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_AppearanceUserSettingsTab_fontSlider_largeText {
|
||||||
|
font-size: 18px;
|
||||||
|
padding-left: 20px;
|
||||||
|
padding-right: 5px;
|
||||||
|
}
|
|
@ -14,8 +14,7 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
.mx_GeneralUserSettingsTab_changePassword .mx_Field,
|
.mx_GeneralUserSettingsTab_changePassword .mx_Field {
|
||||||
.mx_GeneralUserSettingsTab_themeSection .mx_Field {
|
|
||||||
@mixin mx_Settings_fullWidthField;
|
@mixin mx_Settings_fullWidthField;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
5
res/img/feather-customised/brush.svg
Normal file
5
res/img/feather-customised/brush.svg
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M12 16.5C12 18.9853 9.98528 21 7.5 21C6.21514 21 3 21 3 21C3 21 3 17.7004 3 16.5C3 14.0147 5.01472 12 7.5 12C9.98528 12 12 14.0147 12 16.5Z" stroke="#2E2F32" stroke-linejoin="round"/>
|
||||||
|
<path d="M8.25 12L17.1955 3.69345C18.0632 2.88776 19.4127 2.91274 20.25 3.75V3.75C21.0873 4.58726 21.1122 5.93682 20.3065 6.80449L12 15.75" stroke="#2E2F32"/>
|
||||||
|
<path d="M11.25 9C11.25 9 12.3929 9.45 13.5 10.5C14.6071 11.55 15 12.75 15 12.75" stroke="#2E2F32"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 556 B |
|
@ -180,6 +180,9 @@ $breadcrumb-placeholder-bg-color: #272c35;
|
||||||
|
|
||||||
$user-tile-hover-bg-color: $header-panel-bg-color;
|
$user-tile-hover-bg-color: $header-panel-bg-color;
|
||||||
|
|
||||||
|
// FontSlider colors
|
||||||
|
$font-slider-bg-color: $room-highlight-color;
|
||||||
|
|
||||||
// ***** Mixins! *****
|
// ***** Mixins! *****
|
||||||
|
|
||||||
@define-mixin mx_DialogButton {
|
@define-mixin mx_DialogButton {
|
||||||
|
|
|
@ -262,6 +262,10 @@ $togglesw-off-color: #c1c9d6;
|
||||||
$togglesw-on-color: $accent-color;
|
$togglesw-on-color: $accent-color;
|
||||||
$togglesw-ball-color: #fff;
|
$togglesw-ball-color: #fff;
|
||||||
|
|
||||||
|
// Slider
|
||||||
|
$slider-selection-color: $accent-color;
|
||||||
|
$slider-background-color: #c1c9d6;
|
||||||
|
|
||||||
$progressbar-color: #000;
|
$progressbar-color: #000;
|
||||||
|
|
||||||
$room-warning-bg-color: $yellow-background;
|
$room-warning-bg-color: $yellow-background;
|
||||||
|
@ -302,6 +306,9 @@ $breadcrumb-placeholder-bg-color: #e8eef5;
|
||||||
|
|
||||||
$user-tile-hover-bg-color: $header-panel-bg-color;
|
$user-tile-hover-bg-color: $header-panel-bg-color;
|
||||||
|
|
||||||
|
// FontSlider colors
|
||||||
|
$font-slider-bg-color: rgba($input-darker-bg-color, 0.2);
|
||||||
|
|
||||||
// ***** Mixins! *****
|
// ***** Mixins! *****
|
||||||
|
|
||||||
@define-mixin mx_DialogButton {
|
@define-mixin mx_DialogButton {
|
||||||
|
|
19
src/@types/common.ts
Normal file
19
src/@types/common.ts
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
/*
|
||||||
|
Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||||
|
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Based on https://stackoverflow.com/a/53229857/3532235
|
||||||
|
export type Without<T, U> = {[P in Exclude<keyof T, keyof U>] ? : never}
|
||||||
|
export type XOR<T, U> = (T | U) extends object ? (Without<T, U> & U) | (Without<U, T> & T) : T | U;
|
9
src/@types/global.d.ts
vendored
9
src/@types/global.d.ts
vendored
|
@ -15,13 +15,22 @@ limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import * as ModernizrStatic from "modernizr";
|
import * as ModernizrStatic from "modernizr";
|
||||||
|
import ContentMessages from "../ContentMessages";
|
||||||
|
import { IMatrixClientPeg } from "../MatrixClientPeg";
|
||||||
|
import ToastStore from "../stores/ToastStore";
|
||||||
|
import DeviceListener from "../DeviceListener";
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface Window {
|
interface Window {
|
||||||
Modernizr: ModernizrStatic;
|
Modernizr: ModernizrStatic;
|
||||||
|
mxMatrixClientPeg: IMatrixClientPeg;
|
||||||
Olm: {
|
Olm: {
|
||||||
init: () => Promise<void>;
|
init: () => Promise<void>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
mx_ContentMessages: ContentMessages;
|
||||||
|
mx_ToastStore: ToastStore;
|
||||||
|
mx_DeviceListener: DeviceListener;
|
||||||
}
|
}
|
||||||
|
|
||||||
// workaround for https://github.com/microsoft/TypeScript/issues/30933
|
// workaround for https://github.com/microsoft/TypeScript/issues/30933
|
||||||
|
|
|
@ -19,6 +19,7 @@ import {MatrixClientPeg} from './MatrixClientPeg';
|
||||||
import DMRoomMap from './utils/DMRoomMap';
|
import DMRoomMap from './utils/DMRoomMap';
|
||||||
import {getHttpUriForMxc} from "matrix-js-sdk/src/content-repo";
|
import {getHttpUriForMxc} from "matrix-js-sdk/src/content-repo";
|
||||||
|
|
||||||
|
// Not to be used for BaseAvatar urls as that has similar default avatar fallback already
|
||||||
export function avatarUrlForMember(member, width, height, resizeMethod) {
|
export function avatarUrlForMember(member, width, height, resizeMethod) {
|
||||||
let url;
|
let url;
|
||||||
if (member && member.getAvatarUrl) {
|
if (member && member.getAvatarUrl) {
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
// @flow
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Copyright 2016 Aviral Dasgupta
|
Copyright 2016 Aviral Dasgupta
|
||||||
Copyright 2016 OpenMarket Ltd
|
Copyright 2016 OpenMarket Ltd
|
||||||
|
@ -19,9 +17,23 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {MatrixClient} from "matrix-js-sdk";
|
import {MatrixClient} from "matrix-js-sdk/src/client";
|
||||||
import dis from './dispatcher';
|
import dis from './dispatcher/dispatcher';
|
||||||
import BaseEventIndexManager from './indexing/BaseEventIndexManager';
|
import BaseEventIndexManager from './indexing/BaseEventIndexManager';
|
||||||
|
import {ActionPayload} from "./dispatcher/payloads";
|
||||||
|
import {CheckUpdatesPayload} from "./dispatcher/payloads/CheckUpdatesPayload";
|
||||||
|
import {Action} from "./dispatcher/actions";
|
||||||
|
import {hideToast as hideUpdateToast} from "./toasts/UpdateToast";
|
||||||
|
|
||||||
|
export enum UpdateCheckStatus {
|
||||||
|
Checking = "CHECKING",
|
||||||
|
Error = "ERROR",
|
||||||
|
NotAvailable = "NOTAVAILABLE",
|
||||||
|
Downloading = "DOWNLOADING",
|
||||||
|
Ready = "READY",
|
||||||
|
}
|
||||||
|
|
||||||
|
const UPDATE_DEFER_KEY = "mx_defer_update";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Base class for classes that provide platform-specific functionality
|
* Base class for classes that provide platform-specific functionality
|
||||||
|
@ -29,27 +41,25 @@ import BaseEventIndexManager from './indexing/BaseEventIndexManager';
|
||||||
*
|
*
|
||||||
* Instances of this class are provided by the application.
|
* Instances of this class are provided by the application.
|
||||||
*/
|
*/
|
||||||
export default class BasePlatform {
|
export default abstract class BasePlatform {
|
||||||
constructor() {
|
protected notificationCount = 0;
|
||||||
this.notificationCount = 0;
|
protected errorDidOccur = false;
|
||||||
this.errorDidOccur = false;
|
|
||||||
|
|
||||||
dis.register(this._onAction.bind(this));
|
constructor() {
|
||||||
|
dis.register(this.onAction);
|
||||||
}
|
}
|
||||||
|
|
||||||
_onAction(payload: Object) {
|
protected onAction = (payload: ActionPayload) => {
|
||||||
switch (payload.action) {
|
switch (payload.action) {
|
||||||
case 'on_client_not_viable':
|
case 'on_client_not_viable':
|
||||||
case 'on_logged_out':
|
case 'on_logged_out':
|
||||||
this.setNotificationCount(0);
|
this.setNotificationCount(0);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
// Used primarily for Analytics
|
// Used primarily for Analytics
|
||||||
getHumanReadableName(): string {
|
abstract getHumanReadableName(): string;
|
||||||
return 'Base Platform';
|
|
||||||
}
|
|
||||||
|
|
||||||
setNotificationCount(count: number) {
|
setNotificationCount(count: number) {
|
||||||
this.notificationCount = count;
|
this.notificationCount = count;
|
||||||
|
@ -59,6 +69,53 @@ export default class BasePlatform {
|
||||||
this.errorDidOccur = errorDidOccur;
|
this.errorDidOccur = errorDidOccur;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether we can call checkForUpdate on this platform build
|
||||||
|
*/
|
||||||
|
async canSelfUpdate(): Promise<boolean> {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
startUpdateCheck() {
|
||||||
|
hideUpdateToast();
|
||||||
|
localStorage.removeItem(UPDATE_DEFER_KEY);
|
||||||
|
dis.dispatch<CheckUpdatesPayload>({
|
||||||
|
action: Action.CheckUpdates,
|
||||||
|
status: UpdateCheckStatus.Checking,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the currently running app to the latest available version
|
||||||
|
* and replace this instance of the app with the new version.
|
||||||
|
*/
|
||||||
|
installUpdate() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the version update has been deferred and that deferment is still in effect
|
||||||
|
* @param newVersion the version string to check
|
||||||
|
*/
|
||||||
|
protected shouldShowUpdate(newVersion: string): boolean {
|
||||||
|
try {
|
||||||
|
const [version, deferUntil] = JSON.parse(localStorage.getItem(UPDATE_DEFER_KEY));
|
||||||
|
return newVersion !== version || Date.now() > deferUntil;
|
||||||
|
} catch (e) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ignore the pending update and don't prompt about this version
|
||||||
|
* until the next morning (8am).
|
||||||
|
*/
|
||||||
|
deferUpdate(newVersion: string) {
|
||||||
|
const date = new Date(Date.now() + 24 * 60 * 60 * 1000);
|
||||||
|
date.setHours(8, 0, 0, 0); // set to next 8am
|
||||||
|
localStorage.setItem(UPDATE_DEFER_KEY, JSON.stringify([newVersion, date.getTime()]));
|
||||||
|
hideUpdateToast();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns true if the platform supports displaying
|
* Returns true if the platform supports displaying
|
||||||
* notifications, otherwise false.
|
* notifications, otherwise false.
|
||||||
|
@ -84,22 +141,17 @@ export default class BasePlatform {
|
||||||
* that is 'granted' if the user allowed the request or
|
* that is 'granted' if the user allowed the request or
|
||||||
* 'denied' otherwise.
|
* 'denied' otherwise.
|
||||||
*/
|
*/
|
||||||
requestNotificationPermission(): Promise<string> {
|
abstract requestNotificationPermission(): Promise<string>;
|
||||||
}
|
|
||||||
|
|
||||||
displayNotification(title: string, msg: string, avatarUrl: string, room: Object) {
|
abstract displayNotification(title: string, msg: string, avatarUrl: string, room: Object);
|
||||||
}
|
|
||||||
|
|
||||||
loudNotification(ev: Event, 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.
|
|
||||||
*/
|
*/
|
||||||
getAppVersion(): Promise<string> {
|
abstract getAppVersion(): Promise<string>;
|
||||||
throw new Error("getAppVersion not implemented!");
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* If it's not expected that capturing the screen will work
|
* If it's not expected that capturing the screen will work
|
||||||
|
@ -114,20 +166,18 @@ export default class BasePlatform {
|
||||||
* Restarts the application, without neccessarily reloading
|
* Restarts the application, without neccessarily reloading
|
||||||
* any application code
|
* any application code
|
||||||
*/
|
*/
|
||||||
reload() {
|
abstract reload();
|
||||||
throw new Error("reload not implemented!");
|
|
||||||
}
|
|
||||||
|
|
||||||
supportsAutoLaunch(): boolean {
|
supportsAutoLaunch(): boolean {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// XXX: Surely this should be a setting like any other?
|
// XXX: Surely this should be a setting like any other?
|
||||||
async getAutoLaunchEnabled(): boolean {
|
async getAutoLaunchEnabled(): Promise<boolean> {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
async setAutoLaunchEnabled(enabled: boolean): void {
|
async setAutoLaunchEnabled(enabled: boolean): Promise<void> {
|
||||||
throw new Error("Unimplemented");
|
throw new Error("Unimplemented");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -135,11 +185,11 @@ export default class BasePlatform {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
async getAutoHideMenuBarEnabled(): boolean {
|
async getAutoHideMenuBarEnabled(): Promise<boolean> {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
async setAutoHideMenuBarEnabled(enabled: boolean): void {
|
async setAutoHideMenuBarEnabled(enabled: boolean): Promise<void> {
|
||||||
throw new Error("Unimplemented");
|
throw new Error("Unimplemented");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -147,11 +197,11 @@ export default class BasePlatform {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
async getMinimizeToTrayEnabled(): boolean {
|
async getMinimizeToTrayEnabled(): Promise<boolean> {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
async setMinimizeToTrayEnabled(enabled: boolean): void {
|
async setMinimizeToTrayEnabled(enabled: boolean): Promise<void> {
|
||||||
throw new Error("Unimplemented");
|
throw new Error("Unimplemented");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -190,4 +240,35 @@ export default class BasePlatform {
|
||||||
onKeyDown(ev: KeyboardEvent): boolean {
|
onKeyDown(ev: KeyboardEvent): boolean {
|
||||||
return false; // no shortcuts implemented
|
return false; // no shortcuts implemented
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a previously stored pickle key. The pickle key is used for
|
||||||
|
* encrypting libolm objects.
|
||||||
|
* @param {string} userId the user ID for the user that the pickle key is for.
|
||||||
|
* @param {string} userId the device ID that the pickle key is for.
|
||||||
|
* @returns {string|null} the previously stored pickle key, or null if no
|
||||||
|
* pickle key has been stored.
|
||||||
|
*/
|
||||||
|
async getPickleKey(userId: string, deviceId: string): Promise<string | null> {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create and store a pickle key for encrypting libolm objects.
|
||||||
|
* @param {string} userId the user ID for the user that the pickle key is for.
|
||||||
|
* @param {string} userId the device ID that the pickle key is for.
|
||||||
|
* @returns {string|null} the pickle key, or null if the platform does not
|
||||||
|
* support storing pickle keys.
|
||||||
|
*/
|
||||||
|
async createPickleKey(userId: string, deviceId: string): Promise<string | null> {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete a previously stored pickle key from storage.
|
||||||
|
* @param {string} userId the user ID for the user that the pickle key is for.
|
||||||
|
* @param {string} userId the device ID that the pickle key is for.
|
||||||
|
*/
|
||||||
|
async destroyPickleKey(userId: string, deviceId: string): Promise<void> {
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -59,7 +59,7 @@ import Modal from './Modal';
|
||||||
import * as sdk from './index';
|
import * as sdk from './index';
|
||||||
import { _t } from './languageHandler';
|
import { _t } from './languageHandler';
|
||||||
import Matrix from 'matrix-js-sdk';
|
import Matrix from 'matrix-js-sdk';
|
||||||
import dis from './dispatcher';
|
import dis from './dispatcher/dispatcher';
|
||||||
import { showUnknownDeviceDialogForCalls } from './cryptodevices';
|
import { showUnknownDeviceDialogForCalls } from './cryptodevices';
|
||||||
import WidgetUtils from './utils/WidgetUtils';
|
import WidgetUtils from './utils/WidgetUtils';
|
||||||
import WidgetEchoStore from './stores/WidgetEchoStore';
|
import WidgetEchoStore from './stores/WidgetEchoStore';
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
/*
|
/*
|
||||||
Copyright 2015, 2016 OpenMarket Ltd
|
Copyright 2015, 2016 OpenMarket Ltd
|
||||||
Copyright 2019 New Vector Ltd
|
Copyright 2019 New Vector Ltd
|
||||||
|
Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
|
@ -15,17 +16,18 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
'use strict';
|
import React from "react";
|
||||||
|
|
||||||
import extend from './extend';
|
import extend from './extend';
|
||||||
import dis from './dispatcher';
|
import dis from './dispatcher/dispatcher';
|
||||||
import {MatrixClientPeg} from './MatrixClientPeg';
|
import {MatrixClientPeg} from './MatrixClientPeg';
|
||||||
|
import {MatrixClient} from "matrix-js-sdk/src/client";
|
||||||
import * as sdk from './index';
|
import * as sdk from './index';
|
||||||
import { _t } from './languageHandler';
|
import { _t } from './languageHandler';
|
||||||
import Modal from './Modal';
|
import Modal from './Modal';
|
||||||
import RoomViewStore from './stores/RoomViewStore';
|
import RoomViewStore from './stores/RoomViewStore';
|
||||||
import encrypt from "browser-encrypt-attachment";
|
import encrypt from "browser-encrypt-attachment";
|
||||||
import extractPngChunks from "png-chunks-extract";
|
import extractPngChunks from "png-chunks-extract";
|
||||||
|
import Spinner from "./components/views/elements/Spinner";
|
||||||
|
|
||||||
// Polyfill for Canvas.toBlob API using Canvas.toDataURL
|
// Polyfill for Canvas.toBlob API using Canvas.toDataURL
|
||||||
import "blueimp-canvas-to-blob";
|
import "blueimp-canvas-to-blob";
|
||||||
|
@ -39,6 +41,50 @@ const PHYS_HIDPI = [0x00, 0x00, 0x16, 0x25, 0x00, 0x00, 0x16, 0x25, 0x01];
|
||||||
|
|
||||||
export class UploadCanceledError extends Error {}
|
export class UploadCanceledError extends Error {}
|
||||||
|
|
||||||
|
type ThumbnailableElement = HTMLImageElement | HTMLVideoElement;
|
||||||
|
|
||||||
|
interface IUpload {
|
||||||
|
fileName: string;
|
||||||
|
roomId: string;
|
||||||
|
total: number;
|
||||||
|
loaded: number;
|
||||||
|
promise: Promise<any>;
|
||||||
|
canceled?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IMediaConfig {
|
||||||
|
"m.upload.size"?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IContent {
|
||||||
|
body: string;
|
||||||
|
msgtype: string;
|
||||||
|
info: {
|
||||||
|
size: number;
|
||||||
|
mimetype?: string;
|
||||||
|
};
|
||||||
|
file?: string;
|
||||||
|
url?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IThumbnail {
|
||||||
|
info: {
|
||||||
|
thumbnail_info: {
|
||||||
|
w: number;
|
||||||
|
h: number;
|
||||||
|
mimetype: string;
|
||||||
|
size: number;
|
||||||
|
};
|
||||||
|
w: number;
|
||||||
|
h: number;
|
||||||
|
};
|
||||||
|
thumbnail: Blob;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IAbortablePromise<T> extends Promise<T> {
|
||||||
|
abort(): void;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a thumbnail for a image DOM element.
|
* Create a thumbnail for a image DOM element.
|
||||||
* The image will be smaller than MAX_WIDTH and MAX_HEIGHT.
|
* The image will be smaller than MAX_WIDTH and MAX_HEIGHT.
|
||||||
|
@ -51,13 +97,13 @@ export class UploadCanceledError extends Error {}
|
||||||
* about the original image and the thumbnail.
|
* about the original image and the thumbnail.
|
||||||
*
|
*
|
||||||
* @param {HTMLElement} element The element to thumbnail.
|
* @param {HTMLElement} element The element to thumbnail.
|
||||||
* @param {integer} inputWidth The width of the image in the input element.
|
* @param {number} inputWidth The width of the image in the input element.
|
||||||
* @param {integer} inputHeight the width of the image in the input element.
|
* @param {number} inputHeight the width of the image in the input element.
|
||||||
* @param {String} mimeType The mimeType to save the blob as.
|
* @param {String} mimeType The mimeType to save the blob as.
|
||||||
* @return {Promise} A promise that resolves with an object with an info key
|
* @return {Promise} A promise that resolves with an object with an info key
|
||||||
* and a thumbnail key.
|
* and a thumbnail key.
|
||||||
*/
|
*/
|
||||||
function createThumbnail(element, inputWidth, inputHeight, mimeType) {
|
function createThumbnail(element: ThumbnailableElement, inputWidth: number, inputHeight: number, mimeType: string): Promise<IThumbnail> {
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
let targetWidth = inputWidth;
|
let targetWidth = inputWidth;
|
||||||
let targetHeight = inputHeight;
|
let targetHeight = inputHeight;
|
||||||
|
@ -98,7 +144,7 @@ function createThumbnail(element, inputWidth, inputHeight, mimeType) {
|
||||||
* @param {File} imageFile The file to load in an image element.
|
* @param {File} imageFile The file to load in an image element.
|
||||||
* @return {Promise} A promise that resolves with the html image element.
|
* @return {Promise} A promise that resolves with the html image element.
|
||||||
*/
|
*/
|
||||||
async function loadImageElement(imageFile) {
|
async function loadImageElement(imageFile: File) {
|
||||||
// Load the file into an html element
|
// Load the file into an html element
|
||||||
const img = document.createElement("img");
|
const img = document.createElement("img");
|
||||||
const objectUrl = URL.createObjectURL(imageFile);
|
const objectUrl = URL.createObjectURL(imageFile);
|
||||||
|
@ -128,8 +174,7 @@ async function loadImageElement(imageFile) {
|
||||||
for (const chunk of chunks) {
|
for (const chunk of chunks) {
|
||||||
if (chunk.name === 'pHYs') {
|
if (chunk.name === 'pHYs') {
|
||||||
if (chunk.data.byteLength !== PHYS_HIDPI.length) return;
|
if (chunk.data.byteLength !== PHYS_HIDPI.length) return;
|
||||||
const hidpi = chunk.data.every((val, i) => val === PHYS_HIDPI[i]);
|
return chunk.data.every((val, i) => val === PHYS_HIDPI[i]);
|
||||||
return hidpi;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
|
@ -152,7 +197,7 @@ async function loadImageElement(imageFile) {
|
||||||
*/
|
*/
|
||||||
function infoForImageFile(matrixClient, roomId, imageFile) {
|
function infoForImageFile(matrixClient, roomId, imageFile) {
|
||||||
let thumbnailType = "image/png";
|
let thumbnailType = "image/png";
|
||||||
if (imageFile.type == "image/jpeg") {
|
if (imageFile.type === "image/jpeg") {
|
||||||
thumbnailType = "image/jpeg";
|
thumbnailType = "image/jpeg";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -175,15 +220,15 @@ function infoForImageFile(matrixClient, roomId, imageFile) {
|
||||||
* @param {File} videoFile The file to load in an video element.
|
* @param {File} videoFile The file to load in an video element.
|
||||||
* @return {Promise} A promise that resolves with the video image element.
|
* @return {Promise} A promise that resolves with the video image element.
|
||||||
*/
|
*/
|
||||||
function loadVideoElement(videoFile) {
|
function loadVideoElement(videoFile): Promise<HTMLVideoElement> {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
// Load the file into an html element
|
// Load the file into an html element
|
||||||
const video = document.createElement("video");
|
const video = document.createElement("video");
|
||||||
|
|
||||||
const reader = new FileReader();
|
const reader = new FileReader();
|
||||||
|
|
||||||
reader.onload = function(e) {
|
reader.onload = function(ev) {
|
||||||
video.src = e.target.result;
|
video.src = ev.target.result as string;
|
||||||
|
|
||||||
// Once ready, returns its size
|
// Once ready, returns its size
|
||||||
// Wait until we have enough data to thumbnail the first frame.
|
// Wait until we have enough data to thumbnail the first frame.
|
||||||
|
@ -231,11 +276,11 @@ function infoForVideoFile(matrixClient, roomId, videoFile) {
|
||||||
* @return {Promise} A promise that resolves with an ArrayBuffer when the file
|
* @return {Promise} A promise that resolves with an ArrayBuffer when the file
|
||||||
* is read.
|
* is read.
|
||||||
*/
|
*/
|
||||||
function readFileAsArrayBuffer(file) {
|
function readFileAsArrayBuffer(file: File | Blob): Promise<ArrayBuffer> {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const reader = new FileReader();
|
const reader = new FileReader();
|
||||||
reader.onload = function(e) {
|
reader.onload = function(e) {
|
||||||
resolve(e.target.result);
|
resolve(e.target.result as ArrayBuffer);
|
||||||
};
|
};
|
||||||
reader.onerror = function(e) {
|
reader.onerror = function(e) {
|
||||||
reject(e);
|
reject(e);
|
||||||
|
@ -257,11 +302,11 @@ function readFileAsArrayBuffer(file) {
|
||||||
* If the file is unencrypted then the object will have a "url" key.
|
* If the file is unencrypted then the object will have a "url" key.
|
||||||
* If the file is encrypted then the object will have a "file" key.
|
* If the file is encrypted then the object will have a "file" key.
|
||||||
*/
|
*/
|
||||||
function uploadFile(matrixClient, roomId, file, progressHandler) {
|
function uploadFile(matrixClient: MatrixClient, roomId: string, file: File | Blob, progressHandler?: any) {
|
||||||
|
let canceled = false;
|
||||||
if (matrixClient.isRoomEncrypted(roomId)) {
|
if (matrixClient.isRoomEncrypted(roomId)) {
|
||||||
// If the room is encrypted then encrypt the file before uploading it.
|
// If the room is encrypted then encrypt the file before uploading it.
|
||||||
// First read the file into memory.
|
// First read the file into memory.
|
||||||
let canceled = false;
|
|
||||||
let uploadPromise;
|
let uploadPromise;
|
||||||
let encryptInfo;
|
let encryptInfo;
|
||||||
const prom = readFileAsArrayBuffer(file).then(function(data) {
|
const prom = readFileAsArrayBuffer(file).then(function(data) {
|
||||||
|
@ -278,9 +323,9 @@ function uploadFile(matrixClient, roomId, file, progressHandler) {
|
||||||
progressHandler: progressHandler,
|
progressHandler: progressHandler,
|
||||||
includeFilename: false,
|
includeFilename: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
return uploadPromise;
|
return uploadPromise;
|
||||||
}).then(function(url) {
|
}).then(function(url) {
|
||||||
|
if (canceled) throw new UploadCanceledError();
|
||||||
// If the attachment is encrypted then bundle the URL along
|
// If the attachment is encrypted then bundle the URL along
|
||||||
// with the information needed to decrypt the attachment and
|
// with the information needed to decrypt the attachment and
|
||||||
// add it under a file key.
|
// add it under a file key.
|
||||||
|
@ -290,7 +335,7 @@ function uploadFile(matrixClient, roomId, file, progressHandler) {
|
||||||
}
|
}
|
||||||
return {"file": encryptInfo};
|
return {"file": encryptInfo};
|
||||||
});
|
});
|
||||||
prom.abort = () => {
|
(prom as IAbortablePromise<any>).abort = () => {
|
||||||
canceled = true;
|
canceled = true;
|
||||||
if (uploadPromise) MatrixClientPeg.get().cancelUpload(uploadPromise);
|
if (uploadPromise) MatrixClientPeg.get().cancelUpload(uploadPromise);
|
||||||
};
|
};
|
||||||
|
@ -300,55 +345,23 @@ function uploadFile(matrixClient, roomId, file, progressHandler) {
|
||||||
progressHandler: progressHandler,
|
progressHandler: progressHandler,
|
||||||
});
|
});
|
||||||
const promise1 = basePromise.then(function(url) {
|
const promise1 = basePromise.then(function(url) {
|
||||||
|
if (canceled) throw new UploadCanceledError();
|
||||||
// If the attachment isn't encrypted then include the URL directly.
|
// If the attachment isn't encrypted then include the URL directly.
|
||||||
return {"url": url};
|
return {"url": url};
|
||||||
});
|
});
|
||||||
// XXX: copy over the abort method to the new promise
|
promise1.abort = () => {
|
||||||
promise1.abort = basePromise.abort;
|
canceled = true;
|
||||||
|
MatrixClientPeg.get().cancelUpload(basePromise);
|
||||||
|
};
|
||||||
return promise1;
|
return promise1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class ContentMessages {
|
export default class ContentMessages {
|
||||||
constructor() {
|
private inprogress: IUpload[] = [];
|
||||||
this.inprogress = [];
|
private mediaConfig: IMediaConfig = null;
|
||||||
this.nextId = 0;
|
|
||||||
this._mediaConfig = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
static sharedInstance() {
|
sendStickerContentToRoom(url: string, roomId: string, info: string, text: string, matrixClient: MatrixClient) {
|
||||||
if (global.mx_ContentMessages === undefined) {
|
|
||||||
global.mx_ContentMessages = new ContentMessages();
|
|
||||||
}
|
|
||||||
return global.mx_ContentMessages;
|
|
||||||
}
|
|
||||||
|
|
||||||
_isFileSizeAcceptable(file) {
|
|
||||||
if (this._mediaConfig !== null &&
|
|
||||||
this._mediaConfig["m.upload.size"] !== undefined &&
|
|
||||||
file.size > this._mediaConfig["m.upload.size"]) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
_ensureMediaConfigFetched() {
|
|
||||||
if (this._mediaConfig !== null) return;
|
|
||||||
|
|
||||||
console.log("[Media Config] Fetching");
|
|
||||||
return MatrixClientPeg.get().getMediaConfig().then((config) => {
|
|
||||||
console.log("[Media Config] Fetched config:", config);
|
|
||||||
return config;
|
|
||||||
}).catch(() => {
|
|
||||||
// Media repo can't or won't report limits, so provide an empty object (no limits).
|
|
||||||
console.log("[Media Config] Could not fetch config, so not limiting uploads.");
|
|
||||||
return {};
|
|
||||||
}).then((config) => {
|
|
||||||
this._mediaConfig = config;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
sendStickerContentToRoom(url, roomId, info, text, matrixClient) {
|
|
||||||
return MatrixClientPeg.get().sendStickerMessage(roomId, url, info, text).catch((e) => {
|
return MatrixClientPeg.get().sendStickerMessage(roomId, url, info, text).catch((e) => {
|
||||||
console.warn(`Failed to send content with URL ${url} to room ${roomId}`, e);
|
console.warn(`Failed to send content with URL ${url} to room ${roomId}`, e);
|
||||||
throw e;
|
throw e;
|
||||||
|
@ -356,14 +369,14 @@ export default class ContentMessages {
|
||||||
}
|
}
|
||||||
|
|
||||||
getUploadLimit() {
|
getUploadLimit() {
|
||||||
if (this._mediaConfig !== null && this._mediaConfig["m.upload.size"] !== undefined) {
|
if (this.mediaConfig !== null && this.mediaConfig["m.upload.size"] !== undefined) {
|
||||||
return this._mediaConfig["m.upload.size"];
|
return this.mediaConfig["m.upload.size"];
|
||||||
} else {
|
} else {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async sendContentListToRoom(files, roomId, matrixClient) {
|
async sendContentListToRoom(files: File[], roomId: string, matrixClient: MatrixClient) {
|
||||||
if (matrixClient.isGuest()) {
|
if (matrixClient.isGuest()) {
|
||||||
dis.dispatch({action: 'require_registration'});
|
dis.dispatch({action: 'require_registration'});
|
||||||
return;
|
return;
|
||||||
|
@ -372,8 +385,7 @@ export default class ContentMessages {
|
||||||
const isQuoting = Boolean(RoomViewStore.getQuotingEvent());
|
const isQuoting = Boolean(RoomViewStore.getQuotingEvent());
|
||||||
if (isQuoting) {
|
if (isQuoting) {
|
||||||
const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
|
const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
|
||||||
const shouldUpload = await new Promise((resolve) => {
|
const {finished} = Modal.createTrackedDialog('Upload Reply Warning', '', QuestionDialog, {
|
||||||
Modal.createTrackedDialog('Upload Reply Warning', '', QuestionDialog, {
|
|
||||||
title: _t('Replying With Files'),
|
title: _t('Replying With Files'),
|
||||||
description: (
|
description: (
|
||||||
<div>{_t(
|
<div>{_t(
|
||||||
|
@ -383,21 +395,22 @@ export default class ContentMessages {
|
||||||
),
|
),
|
||||||
hasCancelButton: true,
|
hasCancelButton: true,
|
||||||
button: _t("Continue"),
|
button: _t("Continue"),
|
||||||
onFinished: (shouldUpload) => {
|
|
||||||
resolve(shouldUpload);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
const [shouldUpload]: [boolean] = await finished;
|
||||||
if (!shouldUpload) return;
|
if (!shouldUpload) return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await this._ensureMediaConfigFetched();
|
if (!this.mediaConfig) { // hot-path optimization to not flash a spinner if we don't need to
|
||||||
|
const modal = Modal.createDialog(Spinner, null, 'mx_Dialog_spinner');
|
||||||
|
await this.ensureMediaConfigFetched();
|
||||||
|
modal.close();
|
||||||
|
}
|
||||||
|
|
||||||
const tooBigFiles = [];
|
const tooBigFiles = [];
|
||||||
const okFiles = [];
|
const okFiles = [];
|
||||||
|
|
||||||
for (let i = 0; i < files.length; ++i) {
|
for (let i = 0; i < files.length; ++i) {
|
||||||
if (this._isFileSizeAcceptable(files[i])) {
|
if (this.isFileSizeAcceptable(files[i])) {
|
||||||
okFiles.push(files[i]);
|
okFiles.push(files[i]);
|
||||||
} else {
|
} else {
|
||||||
tooBigFiles.push(files[i]);
|
tooBigFiles.push(files[i]);
|
||||||
|
@ -406,17 +419,12 @@ export default class ContentMessages {
|
||||||
|
|
||||||
if (tooBigFiles.length > 0) {
|
if (tooBigFiles.length > 0) {
|
||||||
const UploadFailureDialog = sdk.getComponent("dialogs.UploadFailureDialog");
|
const UploadFailureDialog = sdk.getComponent("dialogs.UploadFailureDialog");
|
||||||
const uploadFailureDialogPromise = new Promise((resolve) => {
|
const {finished} = Modal.createTrackedDialog('Upload Failure', '', UploadFailureDialog, {
|
||||||
Modal.createTrackedDialog('Upload Failure', '', UploadFailureDialog, {
|
|
||||||
badFiles: tooBigFiles,
|
badFiles: tooBigFiles,
|
||||||
totalFiles: files.length,
|
totalFiles: files.length,
|
||||||
contentMessages: this,
|
contentMessages: this,
|
||||||
onFinished: (shouldContinue) => {
|
|
||||||
resolve(shouldContinue);
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
});
|
const [shouldContinue]: [boolean] = await finished;
|
||||||
const shouldContinue = await uploadFailureDialogPromise;
|
|
||||||
if (!shouldContinue) return;
|
if (!shouldContinue) return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -428,31 +436,47 @@ export default class ContentMessages {
|
||||||
for (let i = 0; i < okFiles.length; ++i) {
|
for (let i = 0; i < okFiles.length; ++i) {
|
||||||
const file = okFiles[i];
|
const file = okFiles[i];
|
||||||
if (!uploadAll) {
|
if (!uploadAll) {
|
||||||
const shouldContinue = await new Promise((resolve) => {
|
const {finished} = Modal.createTrackedDialog('Upload Files confirmation', '', UploadConfirmDialog, {
|
||||||
Modal.createTrackedDialog('Upload Files confirmation', '', UploadConfirmDialog, {
|
|
||||||
file,
|
file,
|
||||||
currentIndex: i,
|
currentIndex: i,
|
||||||
totalFiles: okFiles.length,
|
totalFiles: okFiles.length,
|
||||||
onFinished: (shouldContinue, shouldUploadAll) => {
|
});
|
||||||
|
const [shouldContinue, shouldUploadAll]: [boolean, boolean] = await finished;
|
||||||
|
if (!shouldContinue) break;
|
||||||
if (shouldUploadAll) {
|
if (shouldUploadAll) {
|
||||||
uploadAll = true;
|
uploadAll = true;
|
||||||
}
|
}
|
||||||
resolve(shouldContinue);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
});
|
|
||||||
if (!shouldContinue) break;
|
|
||||||
}
|
}
|
||||||
promBefore = this._sendContentToRoom(file, roomId, matrixClient, promBefore);
|
promBefore = this.sendContentToRoom(file, roomId, matrixClient, promBefore);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_sendContentToRoom(file, roomId, matrixClient, promBefore) {
|
getCurrentUploads() {
|
||||||
const content = {
|
return this.inprogress.filter(u => !u.canceled);
|
||||||
|
}
|
||||||
|
|
||||||
|
cancelUpload(promise: Promise<any>) {
|
||||||
|
let upload: IUpload;
|
||||||
|
for (let i = 0; i < this.inprogress.length; ++i) {
|
||||||
|
if (this.inprogress[i].promise === promise) {
|
||||||
|
upload = this.inprogress[i];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (upload) {
|
||||||
|
upload.canceled = true;
|
||||||
|
MatrixClientPeg.get().cancelUpload(upload.promise);
|
||||||
|
dis.dispatch({action: 'upload_canceled', upload});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private sendContentToRoom(file: File, roomId: string, matrixClient: MatrixClient, promBefore: Promise<any>) {
|
||||||
|
const content: IContent = {
|
||||||
body: file.name || 'Attachment',
|
body: file.name || 'Attachment',
|
||||||
info: {
|
info: {
|
||||||
size: file.size,
|
size: file.size,
|
||||||
},
|
},
|
||||||
|
msgtype: "", // set later
|
||||||
};
|
};
|
||||||
|
|
||||||
// if we have a mime type for the file, add it to the message metadata
|
// if we have a mime type for the file, add it to the message metadata
|
||||||
|
@ -461,25 +485,25 @@ export default class ContentMessages {
|
||||||
}
|
}
|
||||||
|
|
||||||
const prom = new Promise((resolve) => {
|
const prom = new Promise((resolve) => {
|
||||||
if (file.type.indexOf('image/') == 0) {
|
if (file.type.indexOf('image/') === 0) {
|
||||||
content.msgtype = 'm.image';
|
content.msgtype = 'm.image';
|
||||||
infoForImageFile(matrixClient, roomId, file).then((imageInfo) => {
|
infoForImageFile(matrixClient, roomId, file).then((imageInfo) => {
|
||||||
extend(content.info, imageInfo);
|
extend(content.info, imageInfo);
|
||||||
resolve();
|
resolve();
|
||||||
}, (error)=>{
|
}, (e) => {
|
||||||
console.error(error);
|
console.error(e);
|
||||||
content.msgtype = 'm.file';
|
content.msgtype = 'm.file';
|
||||||
resolve();
|
resolve();
|
||||||
});
|
});
|
||||||
} else if (file.type.indexOf('audio/') == 0) {
|
} else if (file.type.indexOf('audio/') === 0) {
|
||||||
content.msgtype = 'm.audio';
|
content.msgtype = 'm.audio';
|
||||||
resolve();
|
resolve();
|
||||||
} else if (file.type.indexOf('video/') == 0) {
|
} else if (file.type.indexOf('video/') === 0) {
|
||||||
content.msgtype = 'm.video';
|
content.msgtype = 'm.video';
|
||||||
infoForVideoFile(matrixClient, roomId, file).then((videoInfo) => {
|
infoForVideoFile(matrixClient, roomId, file).then((videoInfo) => {
|
||||||
extend(content.info, videoInfo);
|
extend(content.info, videoInfo);
|
||||||
resolve();
|
resolve();
|
||||||
}, (error)=>{
|
}, (e) => {
|
||||||
content.msgtype = 'm.file';
|
content.msgtype = 'm.file';
|
||||||
resolve();
|
resolve();
|
||||||
});
|
});
|
||||||
|
@ -489,11 +513,17 @@ export default class ContentMessages {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const upload = {
|
// create temporary abort handler for before the actual upload gets passed off to js-sdk
|
||||||
|
(prom as IAbortablePromise<any>).abort = () => {
|
||||||
|
upload.canceled = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const upload: IUpload = {
|
||||||
fileName: file.name || 'Attachment',
|
fileName: file.name || 'Attachment',
|
||||||
roomId: roomId,
|
roomId: roomId,
|
||||||
total: 0,
|
total: file.size,
|
||||||
loaded: 0,
|
loaded: 0,
|
||||||
|
promise: prom,
|
||||||
};
|
};
|
||||||
this.inprogress.push(upload);
|
this.inprogress.push(upload);
|
||||||
dis.dispatch({action: 'upload_started'});
|
dis.dispatch({action: 'upload_started'});
|
||||||
|
@ -501,15 +531,15 @@ export default class ContentMessages {
|
||||||
// Focus the composer view
|
// Focus the composer view
|
||||||
dis.dispatch({action: 'focus_composer'});
|
dis.dispatch({action: 'focus_composer'});
|
||||||
|
|
||||||
let error;
|
|
||||||
|
|
||||||
function onProgress(ev) {
|
function onProgress(ev) {
|
||||||
upload.total = ev.total;
|
upload.total = ev.total;
|
||||||
upload.loaded = ev.loaded;
|
upload.loaded = ev.loaded;
|
||||||
dis.dispatch({action: 'upload_progress', upload: upload});
|
dis.dispatch({action: 'upload_progress', upload: upload});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let error;
|
||||||
return prom.then(function() {
|
return prom.then(function() {
|
||||||
|
if (upload.canceled) throw new UploadCanceledError();
|
||||||
// XXX: upload.promise must be the promise that
|
// XXX: upload.promise must be the promise that
|
||||||
// is returned by uploadFile as it has an abort()
|
// is returned by uploadFile as it has an abort()
|
||||||
// method hacked onto it.
|
// method hacked onto it.
|
||||||
|
@ -520,16 +550,17 @@ export default class ContentMessages {
|
||||||
content.file = result.file;
|
content.file = result.file;
|
||||||
content.url = result.url;
|
content.url = result.url;
|
||||||
});
|
});
|
||||||
}).then((url) => {
|
}).then(() => {
|
||||||
// Await previous message being sent into the room
|
// Await previous message being sent into the room
|
||||||
return promBefore;
|
return promBefore;
|
||||||
}).then(function() {
|
}).then(function() {
|
||||||
|
if (upload.canceled) throw new UploadCanceledError();
|
||||||
return matrixClient.sendMessage(roomId, content);
|
return matrixClient.sendMessage(roomId, content);
|
||||||
}, function(err) {
|
}, function(err) {
|
||||||
error = err;
|
error = err;
|
||||||
if (!upload.canceled) {
|
if (!upload.canceled) {
|
||||||
let desc = _t("The file '%(fileName)s' failed to upload.", {fileName: upload.fileName});
|
let desc = _t("The file '%(fileName)s' failed to upload.", {fileName: upload.fileName});
|
||||||
if (err.http_status == 413) {
|
if (err.http_status === 413) {
|
||||||
desc = _t(
|
desc = _t(
|
||||||
"The file '%(fileName)s' exceeds this homeserver's size limit for uploads",
|
"The file '%(fileName)s' exceeds this homeserver's size limit for uploads",
|
||||||
{fileName: upload.fileName},
|
{fileName: upload.fileName},
|
||||||
|
@ -542,11 +573,9 @@ export default class ContentMessages {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}).finally(() => {
|
}).finally(() => {
|
||||||
const inprogressKeys = Object.keys(this.inprogress);
|
|
||||||
for (let i = 0; i < this.inprogress.length; ++i) {
|
for (let i = 0; i < this.inprogress.length; ++i) {
|
||||||
const k = inprogressKeys[i];
|
if (this.inprogress[i].promise === upload.promise) {
|
||||||
if (this.inprogress[k].promise === upload.promise) {
|
this.inprogress.splice(i, 1);
|
||||||
this.inprogress.splice(k, 1);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -555,7 +584,7 @@ export default class ContentMessages {
|
||||||
// clear the media size limit so we fetch it again next time
|
// clear the media size limit so we fetch it again next time
|
||||||
// we try to upload
|
// we try to upload
|
||||||
if (error && error.http_status === 413) {
|
if (error && error.http_status === 413) {
|
||||||
this._mediaConfig = null;
|
this.mediaConfig = null;
|
||||||
}
|
}
|
||||||
dis.dispatch({action: 'upload_failed', upload, error});
|
dis.dispatch({action: 'upload_failed', upload, error});
|
||||||
} else {
|
} else {
|
||||||
|
@ -565,24 +594,35 @@ export default class ContentMessages {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
getCurrentUploads() {
|
private isFileSizeAcceptable(file: File) {
|
||||||
return this.inprogress.filter(u => !u.canceled);
|
if (this.mediaConfig !== null &&
|
||||||
|
this.mediaConfig["m.upload.size"] !== undefined &&
|
||||||
|
file.size > this.mediaConfig["m.upload.size"]) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
cancelUpload(promise) {
|
private ensureMediaConfigFetched() {
|
||||||
const inprogressKeys = Object.keys(this.inprogress);
|
if (this.mediaConfig !== null) return;
|
||||||
let upload;
|
|
||||||
for (let i = 0; i < this.inprogress.length; ++i) {
|
console.log("[Media Config] Fetching");
|
||||||
const k = inprogressKeys[i];
|
return MatrixClientPeg.get().getMediaConfig().then((config) => {
|
||||||
if (this.inprogress[k].promise === promise) {
|
console.log("[Media Config] Fetched config:", config);
|
||||||
upload = this.inprogress[k];
|
return config;
|
||||||
break;
|
}).catch(() => {
|
||||||
}
|
// Media repo can't or won't report limits, so provide an empty object (no limits).
|
||||||
}
|
console.log("[Media Config] Could not fetch config, so not limiting uploads.");
|
||||||
if (upload) {
|
return {};
|
||||||
upload.canceled = true;
|
}).then((config) => {
|
||||||
MatrixClientPeg.get().cancelUpload(upload.promise);
|
this.mediaConfig = config;
|
||||||
dis.dispatch({action: 'upload_canceled', upload});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static sharedInstance() {
|
||||||
|
if (window.mx_ContentMessages === undefined) {
|
||||||
|
window.mx_ContentMessages = new ContentMessages();
|
||||||
|
}
|
||||||
|
return window.mx_ContentMessages;
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -16,41 +16,41 @@ limitations under the License.
|
||||||
|
|
||||||
import {MatrixClientPeg} from './MatrixClientPeg';
|
import {MatrixClientPeg} from './MatrixClientPeg';
|
||||||
import SettingsStore from './settings/SettingsStore';
|
import SettingsStore from './settings/SettingsStore';
|
||||||
import * as sdk from './index';
|
import {
|
||||||
import { _t } from './languageHandler';
|
hideToast as hideBulkUnverifiedSessionsToast,
|
||||||
import ToastStore from './stores/ToastStore';
|
showToast as showBulkUnverifiedSessionsToast
|
||||||
|
} from "./toasts/BulkUnverifiedSessionsToast";
|
||||||
|
import {
|
||||||
|
hideToast as hideSetupEncryptionToast,
|
||||||
|
Kind as SetupKind,
|
||||||
|
Kind,
|
||||||
|
showToast as showSetupEncryptionToast
|
||||||
|
} from "./toasts/SetupEncryptionToast";
|
||||||
|
import {
|
||||||
|
hideToast as hideUnverifiedSessionsToast,
|
||||||
|
showToast as showUnverifiedSessionsToast
|
||||||
|
} from "./toasts/UnverifiedSessionToast";
|
||||||
|
|
||||||
const KEY_BACKUP_POLL_INTERVAL = 5 * 60 * 1000;
|
const KEY_BACKUP_POLL_INTERVAL = 5 * 60 * 1000;
|
||||||
const THIS_DEVICE_TOAST_KEY = 'setupencryption';
|
|
||||||
const OTHER_DEVICES_TOAST_KEY = 'reviewsessions';
|
|
||||||
|
|
||||||
function toastKey(deviceId) {
|
|
||||||
return "unverified_session_" + deviceId;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default class DeviceListener {
|
export default class DeviceListener {
|
||||||
static sharedInstance() {
|
|
||||||
if (!global.mx_DeviceListener) global.mx_DeviceListener = new DeviceListener();
|
|
||||||
return global.mx_DeviceListener;
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
// device IDs for which the user has dismissed the verify toast ('Later')
|
// device IDs for which the user has dismissed the verify toast ('Later')
|
||||||
this._dismissed = new Set();
|
private dismissed = new Set<string>();
|
||||||
// has the user dismissed any of the various nag toasts to setup encryption on this device?
|
// has the user dismissed any of the various nag toasts to setup encryption on this device?
|
||||||
this._dismissedThisDeviceToast = false;
|
private dismissedThisDeviceToast = false;
|
||||||
|
|
||||||
// cache of the key backup info
|
// cache of the key backup info
|
||||||
this._keyBackupInfo = null;
|
private keyBackupInfo: object = null;
|
||||||
this._keyBackupFetchedAt = null;
|
private keyBackupFetchedAt: number = null;
|
||||||
|
|
||||||
// We keep a list of our own device IDs so we can batch ones that were already
|
// We keep a list of our own device IDs so we can batch ones that were already
|
||||||
// there the last time the app launched into a single toast, but display new
|
// there the last time the app launched into a single toast, but display new
|
||||||
// ones in their own toasts.
|
// ones in their own toasts.
|
||||||
this._ourDeviceIdsAtStart = null;
|
private ourDeviceIdsAtStart: Set<string> = null;
|
||||||
|
|
||||||
// The set of device IDs we're currently displaying toasts for
|
// The set of device IDs we're currently displaying toasts for
|
||||||
this._displayingToastsForDeviceIds = new Set();
|
private displayingToastsForDeviceIds = new Set<string>();
|
||||||
|
|
||||||
|
static sharedInstance() {
|
||||||
|
if (!window.mx_DeviceListener) window.mx_DeviceListener = new DeviceListener();
|
||||||
|
return window.mx_DeviceListener;
|
||||||
}
|
}
|
||||||
|
|
||||||
start() {
|
start() {
|
||||||
|
@ -74,12 +74,12 @@ export default class DeviceListener {
|
||||||
MatrixClientPeg.get().removeListener('accountData', this._onAccountData);
|
MatrixClientPeg.get().removeListener('accountData', this._onAccountData);
|
||||||
MatrixClientPeg.get().removeListener('sync', this._onSync);
|
MatrixClientPeg.get().removeListener('sync', this._onSync);
|
||||||
}
|
}
|
||||||
this._dismissed.clear();
|
this.dismissed.clear();
|
||||||
this._dismissedThisDeviceToast = false;
|
this.dismissedThisDeviceToast = false;
|
||||||
this._keyBackupInfo = null;
|
this.keyBackupInfo = null;
|
||||||
this._keyBackupFetchedAt = null;
|
this.keyBackupFetchedAt = null;
|
||||||
this._ourDeviceIdsAtStart = null;
|
this.ourDeviceIdsAtStart = null;
|
||||||
this._displayingToastsForDeviceIds = new Set();
|
this.displayingToastsForDeviceIds = new Set();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -87,29 +87,29 @@ export default class DeviceListener {
|
||||||
*
|
*
|
||||||
* @param {String[]} deviceIds List of device IDs to dismiss notifications for
|
* @param {String[]} deviceIds List of device IDs to dismiss notifications for
|
||||||
*/
|
*/
|
||||||
async dismissUnverifiedSessions(deviceIds) {
|
async dismissUnverifiedSessions(deviceIds: Iterable<string>) {
|
||||||
for (const d of deviceIds) {
|
for (const d of deviceIds) {
|
||||||
this._dismissed.add(d);
|
this.dismissed.add(d);
|
||||||
}
|
}
|
||||||
|
|
||||||
this._recheck();
|
this._recheck();
|
||||||
}
|
}
|
||||||
|
|
||||||
dismissEncryptionSetup() {
|
dismissEncryptionSetup() {
|
||||||
this._dismissedThisDeviceToast = true;
|
this.dismissedThisDeviceToast = true;
|
||||||
this._recheck();
|
this._recheck();
|
||||||
}
|
}
|
||||||
|
|
||||||
_ensureDeviceIdsAtStartPopulated() {
|
_ensureDeviceIdsAtStartPopulated() {
|
||||||
if (this._ourDeviceIdsAtStart === null) {
|
if (this.ourDeviceIdsAtStart === null) {
|
||||||
const cli = MatrixClientPeg.get();
|
const cli = MatrixClientPeg.get();
|
||||||
this._ourDeviceIdsAtStart = new Set(
|
this.ourDeviceIdsAtStart = new Set(
|
||||||
cli.getStoredDevicesForUser(cli.getUserId()).map(d => d.deviceId),
|
cli.getStoredDevicesForUser(cli.getUserId()).map(d => d.deviceId),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_onWillUpdateDevices = async (users, initialFetch) => {
|
_onWillUpdateDevices = async (users: string[], initialFetch?: boolean) => {
|
||||||
// If we didn't know about *any* devices before (ie. it's fresh login),
|
// If we didn't know about *any* devices before (ie. it's fresh login),
|
||||||
// then they are all pre-existing devices, so ignore this and set the
|
// then they are all pre-existing devices, so ignore this and set the
|
||||||
// devicesAtStart list to the devices that we see after the fetch.
|
// devicesAtStart list to the devices that we see after the fetch.
|
||||||
|
@ -122,17 +122,17 @@ export default class DeviceListener {
|
||||||
// before we download any new ones.
|
// before we download any new ones.
|
||||||
}
|
}
|
||||||
|
|
||||||
_onDevicesUpdated = (users) => {
|
_onDevicesUpdated = (users: string[]) => {
|
||||||
if (!users.includes(MatrixClientPeg.get().getUserId())) return;
|
if (!users.includes(MatrixClientPeg.get().getUserId())) return;
|
||||||
this._recheck();
|
this._recheck();
|
||||||
}
|
}
|
||||||
|
|
||||||
_onDeviceVerificationChanged = (userId) => {
|
_onDeviceVerificationChanged = (userId: string) => {
|
||||||
if (userId !== MatrixClientPeg.get().getUserId()) return;
|
if (userId !== MatrixClientPeg.get().getUserId()) return;
|
||||||
this._recheck();
|
this._recheck();
|
||||||
}
|
}
|
||||||
|
|
||||||
_onUserTrustStatusChanged = (userId, trustLevel) => {
|
_onUserTrustStatusChanged = (userId: string) => {
|
||||||
if (userId !== MatrixClientPeg.get().getUserId()) return;
|
if (userId !== MatrixClientPeg.get().getUserId()) return;
|
||||||
this._recheck();
|
this._recheck();
|
||||||
}
|
}
|
||||||
|
@ -163,11 +163,11 @@ export default class DeviceListener {
|
||||||
// & cache the result
|
// & cache the result
|
||||||
async _getKeyBackupInfo() {
|
async _getKeyBackupInfo() {
|
||||||
const now = (new Date()).getTime();
|
const now = (new Date()).getTime();
|
||||||
if (!this._keyBackupInfo || this._keyBackupFetchedAt < now - KEY_BACKUP_POLL_INTERVAL) {
|
if (!this.keyBackupInfo || this.keyBackupFetchedAt < now - KEY_BACKUP_POLL_INTERVAL) {
|
||||||
this._keyBackupInfo = await MatrixClientPeg.get().getKeyBackupVersion();
|
this.keyBackupInfo = await MatrixClientPeg.get().getKeyBackupVersion();
|
||||||
this._keyBackupFetchedAt = now;
|
this.keyBackupFetchedAt = now;
|
||||||
}
|
}
|
||||||
return this._keyBackupInfo;
|
return this.keyBackupInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
async _recheck() {
|
async _recheck() {
|
||||||
|
@ -186,49 +186,26 @@ export default class DeviceListener {
|
||||||
|
|
||||||
const crossSigningReady = await cli.isCrossSigningReady();
|
const crossSigningReady = await cli.isCrossSigningReady();
|
||||||
|
|
||||||
if (this._dismissedThisDeviceToast) {
|
if (this.dismissedThisDeviceToast || crossSigningReady) {
|
||||||
ToastStore.sharedInstance().dismissToast(THIS_DEVICE_TOAST_KEY);
|
hideSetupEncryptionToast();
|
||||||
} else {
|
} else {
|
||||||
if (!crossSigningReady) {
|
// make sure our keys are finished downloading
|
||||||
// make sure our keys are finished downlaoding
|
|
||||||
await cli.downloadKeys([cli.getUserId()]);
|
await cli.downloadKeys([cli.getUserId()]);
|
||||||
// cross signing isn't enabled - nag to enable it
|
// cross signing isn't enabled - nag to enable it
|
||||||
// There are 3 different toasts for:
|
// There are 3 different toasts for:
|
||||||
if (cli.getStoredCrossSigningForUser(cli.getUserId())) {
|
if (cli.getStoredCrossSigningForUser(cli.getUserId())) {
|
||||||
// Cross-signing on account but this device doesn't trust the master key (verify this session)
|
// Cross-signing on account but this device doesn't trust the master key (verify this session)
|
||||||
ToastStore.sharedInstance().addOrReplaceToast({
|
showSetupEncryptionToast(SetupKind.VERIFY_THIS_SESSION);
|
||||||
key: THIS_DEVICE_TOAST_KEY,
|
|
||||||
title: _t("Verify this session"),
|
|
||||||
icon: "verification_warning",
|
|
||||||
props: {kind: 'verify_this_session'},
|
|
||||||
component: sdk.getComponent("toasts.SetupEncryptionToast"),
|
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
const backupInfo = await this._getKeyBackupInfo();
|
const backupInfo = await this._getKeyBackupInfo();
|
||||||
if (backupInfo) {
|
if (backupInfo) {
|
||||||
// No cross-signing on account but key backup available (upgrade encryption)
|
// No cross-signing on account but key backup available (upgrade encryption)
|
||||||
ToastStore.sharedInstance().addOrReplaceToast({
|
showSetupEncryptionToast(Kind.UPGRADE_ENCRYPTION);
|
||||||
key: THIS_DEVICE_TOAST_KEY,
|
|
||||||
title: _t("Encryption upgrade available"),
|
|
||||||
icon: "verification_warning",
|
|
||||||
props: {kind: 'upgrade_encryption'},
|
|
||||||
component: sdk.getComponent("toasts.SetupEncryptionToast"),
|
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
// No cross-signing or key backup on account (set up encryption)
|
// No cross-signing or key backup on account (set up encryption)
|
||||||
ToastStore.sharedInstance().addOrReplaceToast({
|
showSetupEncryptionToast(Kind.SET_UP_ENCRYPTION);
|
||||||
key: THIS_DEVICE_TOAST_KEY,
|
|
||||||
title: _t("Set up encryption"),
|
|
||||||
icon: "verification_warning",
|
|
||||||
props: {kind: 'set_up_encryption'},
|
|
||||||
component: sdk.getComponent("toasts.SetupEncryptionToast"),
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
// cross-signing is ready, and we don't need to upgrade encryption
|
|
||||||
ToastStore.sharedInstance().dismissToast(THIS_DEVICE_TOAST_KEY);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// This needs to be done after awaiting on downloadKeys() above, so
|
// This needs to be done after awaiting on downloadKeys() above, so
|
||||||
|
@ -239,20 +216,20 @@ export default class DeviceListener {
|
||||||
// (technically could just be a boolean: we don't actually
|
// (technically could just be a boolean: we don't actually
|
||||||
// need to remember the device IDs, but for the sake of
|
// need to remember the device IDs, but for the sake of
|
||||||
// symmetry...).
|
// symmetry...).
|
||||||
const oldUnverifiedDeviceIds = new Set();
|
const oldUnverifiedDeviceIds = new Set<string>();
|
||||||
// Unverified devices that have appeared since then
|
// Unverified devices that have appeared since then
|
||||||
const newUnverifiedDeviceIds = new Set();
|
const newUnverifiedDeviceIds = new Set<string>();
|
||||||
|
|
||||||
// as long as cross-signing isn't ready,
|
// as long as cross-signing isn't ready,
|
||||||
// you can't see or dismiss any device toasts
|
// you can't see or dismiss any device toasts
|
||||||
if (crossSigningReady) {
|
if (crossSigningReady) {
|
||||||
const devices = cli.getStoredDevicesForUser(cli.getUserId());
|
const devices = cli.getStoredDevicesForUser(cli.getUserId());
|
||||||
for (const device of devices) {
|
for (const device of devices) {
|
||||||
if (device.deviceId == cli.deviceId) continue;
|
if (device.deviceId === cli.deviceId) continue;
|
||||||
|
|
||||||
const deviceTrust = await cli.checkDeviceTrust(cli.getUserId(), device.deviceId);
|
const deviceTrust = await cli.checkDeviceTrust(cli.getUserId(), device.deviceId);
|
||||||
if (!deviceTrust.isCrossSigningVerified() && !this._dismissed.has(device.deviceId)) {
|
if (!deviceTrust.isCrossSigningVerified() && !this.dismissed.has(device.deviceId)) {
|
||||||
if (this._ourDeviceIdsAtStart.has(device.deviceId)) {
|
if (this.ourDeviceIdsAtStart.has(device.deviceId)) {
|
||||||
oldUnverifiedDeviceIds.add(device.deviceId);
|
oldUnverifiedDeviceIds.add(device.deviceId);
|
||||||
} else {
|
} else {
|
||||||
newUnverifiedDeviceIds.add(device.deviceId);
|
newUnverifiedDeviceIds.add(device.deviceId);
|
||||||
|
@ -263,38 +240,23 @@ export default class DeviceListener {
|
||||||
|
|
||||||
// Display or hide the batch toast for old unverified sessions
|
// Display or hide the batch toast for old unverified sessions
|
||||||
if (oldUnverifiedDeviceIds.size > 0) {
|
if (oldUnverifiedDeviceIds.size > 0) {
|
||||||
ToastStore.sharedInstance().addOrReplaceToast({
|
showBulkUnverifiedSessionsToast(oldUnverifiedDeviceIds);
|
||||||
key: OTHER_DEVICES_TOAST_KEY,
|
|
||||||
title: _t("Review where you’re logged in"),
|
|
||||||
icon: "verification_warning",
|
|
||||||
priority: ToastStore.PRIORITY_LOW,
|
|
||||||
props: {
|
|
||||||
deviceIds: oldUnverifiedDeviceIds,
|
|
||||||
},
|
|
||||||
component: sdk.getComponent("toasts.BulkUnverifiedSessionsToast"),
|
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
ToastStore.sharedInstance().dismissToast(OTHER_DEVICES_TOAST_KEY);
|
hideBulkUnverifiedSessionsToast();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Show toasts for new unverified devices if they aren't already there
|
// Show toasts for new unverified devices if they aren't already there
|
||||||
for (const deviceId of newUnverifiedDeviceIds) {
|
for (const deviceId of newUnverifiedDeviceIds) {
|
||||||
ToastStore.sharedInstance().addOrReplaceToast({
|
showUnverifiedSessionsToast(deviceId);
|
||||||
key: toastKey(deviceId),
|
|
||||||
title: _t("New login. Was this you?"),
|
|
||||||
icon: "verification_warning",
|
|
||||||
props: { deviceId },
|
|
||||||
component: sdk.getComponent("toasts.UnverifiedSessionToast"),
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ...and hide any we don't need any more
|
// ...and hide any we don't need any more
|
||||||
for (const deviceId of this._displayingToastsForDeviceIds) {
|
for (const deviceId of this.displayingToastsForDeviceIds) {
|
||||||
if (!newUnverifiedDeviceIds.has(deviceId)) {
|
if (!newUnverifiedDeviceIds.has(deviceId)) {
|
||||||
ToastStore.sharedInstance().dismissToast(toastKey(deviceId));
|
hideUnverifiedSessionsToast(deviceId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this._displayingToastsForDeviceIds = newUnverifiedDeviceIds;
|
this.displayingToastsForDeviceIds = newUnverifiedDeviceIds;
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -17,7 +17,7 @@ limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import URL from 'url';
|
import URL from 'url';
|
||||||
import dis from './dispatcher';
|
import dis from './dispatcher/dispatcher';
|
||||||
import WidgetMessagingEndpoint from './WidgetMessagingEndpoint';
|
import WidgetMessagingEndpoint from './WidgetMessagingEndpoint';
|
||||||
import ActiveWidgetStore from './stores/ActiveWidgetStore';
|
import ActiveWidgetStore from './stores/ActiveWidgetStore';
|
||||||
import {MatrixClientPeg} from "./MatrixClientPeg";
|
import {MatrixClientPeg} from "./MatrixClientPeg";
|
||||||
|
|
|
@ -22,6 +22,7 @@ import { _t } from './languageHandler';
|
||||||
import {MatrixClientPeg} from './MatrixClientPeg';
|
import {MatrixClientPeg} from './MatrixClientPeg';
|
||||||
import GroupStore from './stores/GroupStore';
|
import GroupStore from './stores/GroupStore';
|
||||||
import {allSettled} from "./utils/promise";
|
import {allSettled} from "./utils/promise";
|
||||||
|
import StyledCheckbox from './components/views/elements/StyledCheckbox';
|
||||||
|
|
||||||
export function showGroupInviteDialog(groupId) {
|
export function showGroupInviteDialog(groupId) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
|
@ -61,19 +62,19 @@ export function showGroupAddRoomDialog(groupId) {
|
||||||
<div>{ _t("Which rooms would you like to add to this community?") }</div>
|
<div>{ _t("Which rooms would you like to add to this community?") }</div>
|
||||||
</div>;
|
</div>;
|
||||||
|
|
||||||
const checkboxContainer = <label className="mx_GroupAddressPicker_checkboxContainer">
|
const checkboxContainer = <StyledCheckbox
|
||||||
<input type="checkbox" onChange={onCheckboxClicked} />
|
className="mx_GroupAddressPicker_checkboxContainer"
|
||||||
<div>
|
onChange={onCheckboxClicked}
|
||||||
|
>
|
||||||
{ _t("Show these rooms to non-members on the community page and room list?") }
|
{ _t("Show these rooms to non-members on the community page and room list?") }
|
||||||
</div>
|
</StyledCheckbox>;
|
||||||
</label>;
|
|
||||||
|
|
||||||
const AddressPickerDialog = sdk.getComponent("dialogs.AddressPickerDialog");
|
const AddressPickerDialog = sdk.getComponent("dialogs.AddressPickerDialog");
|
||||||
Modal.createTrackedDialog('Add Rooms to Group', '', AddressPickerDialog, {
|
Modal.createTrackedDialog('Add Rooms to Group', '', AddressPickerDialog, {
|
||||||
title: _t("Add rooms to the community"),
|
title: _t("Add rooms to the community"),
|
||||||
description: description,
|
description: description,
|
||||||
extraNode: checkboxContainer,
|
extraNode: checkboxContainer,
|
||||||
placeholder: _t("Room name or alias"),
|
placeholder: _t("Room name or address"),
|
||||||
button: _t("Add to community"),
|
button: _t("Add to community"),
|
||||||
pickerType: 'room',
|
pickerType: 'room',
|
||||||
validAddressTypes: ['mx-room-id'],
|
validAddressTypes: ['mx-room-id'],
|
||||||
|
|
|
@ -1,158 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright 2017 Vector Creations Ltd
|
|
||||||
Copyright 2020 The Matrix.org Foundation C.I.C.
|
|
||||||
|
|
||||||
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 * as sdk from './index';
|
|
||||||
import Modal from './Modal';
|
|
||||||
import SettingsStore from './settings/SettingsStore';
|
|
||||||
|
|
||||||
// TODO: We can remove this once cross-signing is the only way.
|
|
||||||
// https://github.com/vector-im/riot-web/issues/11908
|
|
||||||
export default class KeyRequestHandler {
|
|
||||||
constructor(matrixClient) {
|
|
||||||
this._matrixClient = matrixClient;
|
|
||||||
|
|
||||||
// the user/device for which we currently have a dialog open
|
|
||||||
this._currentUser = null;
|
|
||||||
this._currentDevice = null;
|
|
||||||
|
|
||||||
// userId -> deviceId -> [keyRequest]
|
|
||||||
this._pendingKeyRequests = Object.create(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
handleKeyRequest(keyRequest) {
|
|
||||||
// Ignore own device key requests if cross-signing lab enabled
|
|
||||||
if (SettingsStore.getValue("feature_cross_signing")) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const userId = keyRequest.userId;
|
|
||||||
const deviceId = keyRequest.deviceId;
|
|
||||||
const requestId = keyRequest.requestId;
|
|
||||||
|
|
||||||
if (!this._pendingKeyRequests[userId]) {
|
|
||||||
this._pendingKeyRequests[userId] = Object.create(null);
|
|
||||||
}
|
|
||||||
if (!this._pendingKeyRequests[userId][deviceId]) {
|
|
||||||
this._pendingKeyRequests[userId][deviceId] = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
// check if we already have this request
|
|
||||||
const requests = this._pendingKeyRequests[userId][deviceId];
|
|
||||||
if (requests.find((r) => r.requestId === requestId)) {
|
|
||||||
console.log("Already have this key request, ignoring");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
requests.push(keyRequest);
|
|
||||||
|
|
||||||
if (this._currentUser) {
|
|
||||||
// ignore for now
|
|
||||||
console.log("Key request, but we already have a dialog open");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this._processNextRequest();
|
|
||||||
}
|
|
||||||
|
|
||||||
handleKeyRequestCancellation(cancellation) {
|
|
||||||
// Ignore own device key requests if cross-signing lab enabled
|
|
||||||
if (SettingsStore.getValue("feature_cross_signing")) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// see if we can find the request in the queue
|
|
||||||
const userId = cancellation.userId;
|
|
||||||
const deviceId = cancellation.deviceId;
|
|
||||||
const requestId = cancellation.requestId;
|
|
||||||
|
|
||||||
if (userId === this._currentUser && deviceId === this._currentDevice) {
|
|
||||||
console.log(
|
|
||||||
"room key request cancellation for the user we currently have a"
|
|
||||||
+ " dialog open for",
|
|
||||||
);
|
|
||||||
// TODO: update the dialog. For now, we just ignore the
|
|
||||||
// cancellation.
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!this._pendingKeyRequests[userId]) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const requests = this._pendingKeyRequests[userId][deviceId];
|
|
||||||
if (!requests) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const idx = requests.findIndex((r) => r.requestId === requestId);
|
|
||||||
if (idx < 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
console.log("Forgetting room key request");
|
|
||||||
requests.splice(idx, 1);
|
|
||||||
if (requests.length === 0) {
|
|
||||||
delete this._pendingKeyRequests[userId][deviceId];
|
|
||||||
if (Object.keys(this._pendingKeyRequests[userId]).length === 0) {
|
|
||||||
delete this._pendingKeyRequests[userId];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_processNextRequest() {
|
|
||||||
const userId = Object.keys(this._pendingKeyRequests)[0];
|
|
||||||
if (!userId) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const deviceId = Object.keys(this._pendingKeyRequests[userId])[0];
|
|
||||||
if (!deviceId) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
console.log(`Starting KeyShareDialog for ${userId}:${deviceId}`);
|
|
||||||
|
|
||||||
const finished = (r) => {
|
|
||||||
this._currentUser = null;
|
|
||||||
this._currentDevice = null;
|
|
||||||
|
|
||||||
if (!this._pendingKeyRequests[userId] || !this._pendingKeyRequests[userId][deviceId]) {
|
|
||||||
// request was removed in the time the dialog was displayed
|
|
||||||
this._processNextRequest();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (r) {
|
|
||||||
for (const req of this._pendingKeyRequests[userId][deviceId]) {
|
|
||||||
req.share();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
delete this._pendingKeyRequests[userId][deviceId];
|
|
||||||
if (Object.keys(this._pendingKeyRequests[userId]).length === 0) {
|
|
||||||
delete this._pendingKeyRequests[userId];
|
|
||||||
}
|
|
||||||
|
|
||||||
this._processNextRequest();
|
|
||||||
};
|
|
||||||
|
|
||||||
const KeyShareDialog = sdk.getComponent("dialogs.KeyShareDialog");
|
|
||||||
Modal.appendTrackedDialog('Key Share', 'Process Next Request', KeyShareDialog, {
|
|
||||||
matrixClient: this._matrixClient,
|
|
||||||
userId: userId,
|
|
||||||
deviceId: deviceId,
|
|
||||||
onFinished: finished,
|
|
||||||
});
|
|
||||||
this._currentUser = userId;
|
|
||||||
this._currentDevice = deviceId;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -26,7 +26,7 @@ import Analytics from './Analytics';
|
||||||
import Notifier from './Notifier';
|
import Notifier from './Notifier';
|
||||||
import UserActivity from './UserActivity';
|
import UserActivity from './UserActivity';
|
||||||
import Presence from './Presence';
|
import Presence from './Presence';
|
||||||
import dis from './dispatcher';
|
import dis from './dispatcher/dispatcher';
|
||||||
import DMRoomMap from './utils/DMRoomMap';
|
import DMRoomMap from './utils/DMRoomMap';
|
||||||
import Modal from './Modal';
|
import Modal from './Modal';
|
||||||
import * as sdk from './index';
|
import * as sdk from './index';
|
||||||
|
@ -298,6 +298,8 @@ async function _restoreFromLocalStorage(opts) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const pickleKey = await PlatformPeg.get().getPickleKey(userId, deviceId);
|
||||||
|
|
||||||
console.log(`Restoring session for ${userId}`);
|
console.log(`Restoring session for ${userId}`);
|
||||||
await _doSetLoggedIn({
|
await _doSetLoggedIn({
|
||||||
userId: userId,
|
userId: userId,
|
||||||
|
@ -306,6 +308,7 @@ async function _restoreFromLocalStorage(opts) {
|
||||||
homeserverUrl: hsUrl,
|
homeserverUrl: hsUrl,
|
||||||
identityServerUrl: isUrl,
|
identityServerUrl: isUrl,
|
||||||
guest: isGuest,
|
guest: isGuest,
|
||||||
|
pickleKey: pickleKey,
|
||||||
}, false);
|
}, false);
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
|
@ -348,9 +351,13 @@ async function _handleLoadSessionFailure(e) {
|
||||||
*
|
*
|
||||||
* @returns {Promise} promise which resolves to the new MatrixClient once it has been started
|
* @returns {Promise} promise which resolves to the new MatrixClient once it has been started
|
||||||
*/
|
*/
|
||||||
export function setLoggedIn(credentials) {
|
export async function setLoggedIn(credentials) {
|
||||||
stopMatrixClient();
|
stopMatrixClient();
|
||||||
return _doSetLoggedIn(credentials, true);
|
const pickleKey = credentials.userId && credentials.deviceId
|
||||||
|
? await PlatformPeg.get().createPickleKey(credentials.userId, credentials.deviceId)
|
||||||
|
: null;
|
||||||
|
|
||||||
|
return _doSetLoggedIn(Object.assign({}, credentials, {pickleKey}), true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -516,7 +523,9 @@ export function logout() {
|
||||||
}
|
}
|
||||||
|
|
||||||
_isLoggingOut = true;
|
_isLoggingOut = true;
|
||||||
MatrixClientPeg.get().logout().then(onLoggedOut,
|
const client = MatrixClientPeg.get();
|
||||||
|
PlatformPeg.get().destroyPickleKey(client.getUserId(), client.getDeviceId());
|
||||||
|
client.logout().then(onLoggedOut,
|
||||||
(err) => {
|
(err) => {
|
||||||
// Just throwing an error here is going to be very unhelpful
|
// Just throwing an error here is going to be very unhelpful
|
||||||
// if you're trying to log out because your server's down and
|
// if you're trying to log out because your server's down and
|
||||||
|
@ -575,10 +584,12 @@ async function startMatrixClient(startSyncing=true) {
|
||||||
// to work).
|
// to work).
|
||||||
dis.dispatch({action: 'will_start_client'}, true);
|
dis.dispatch({action: 'will_start_client'}, true);
|
||||||
|
|
||||||
|
// reset things first just in case
|
||||||
|
TypingStore.sharedInstance().reset();
|
||||||
|
ToastStore.sharedInstance().reset();
|
||||||
|
|
||||||
Notifier.start();
|
Notifier.start();
|
||||||
UserActivity.sharedInstance().start();
|
UserActivity.sharedInstance().start();
|
||||||
TypingStore.sharedInstance().reset(); // just in case
|
|
||||||
ToastStore.sharedInstance().reset();
|
|
||||||
DMRoomMap.makeShared().start();
|
DMRoomMap.makeShared().start();
|
||||||
IntegrationManagers.sharedInstance().startWatching();
|
IntegrationManagers.sharedInstance().startWatching();
|
||||||
ActiveWidgetStore.start();
|
ActiveWidgetStore.start();
|
||||||
|
|
|
@ -17,8 +17,8 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {MatrixClient, MemoryStore} from 'matrix-js-sdk';
|
import {MatrixClient} from 'matrix-js-sdk/src/client';
|
||||||
|
import {MemoryStore} from 'matrix-js-sdk/src/store/memory';
|
||||||
import * as utils from 'matrix-js-sdk/src/utils';
|
import * as utils from 'matrix-js-sdk/src/utils';
|
||||||
import {EventTimeline} from 'matrix-js-sdk/src/models/event-timeline';
|
import {EventTimeline} from 'matrix-js-sdk/src/models/event-timeline';
|
||||||
import {EventTimelineSet} from 'matrix-js-sdk/src/models/event-timeline-set';
|
import {EventTimelineSet} from 'matrix-js-sdk/src/models/event-timeline-set';
|
||||||
|
@ -34,38 +34,26 @@ import IdentityAuthClient from './IdentityAuthClient';
|
||||||
import { crossSigningCallbacks } from './CrossSigningManager';
|
import { crossSigningCallbacks } from './CrossSigningManager';
|
||||||
import {SHOW_QR_CODE_METHOD} from "matrix-js-sdk/src/crypto/verification/QRCode";
|
import {SHOW_QR_CODE_METHOD} from "matrix-js-sdk/src/crypto/verification/QRCode";
|
||||||
|
|
||||||
interface MatrixClientCreds {
|
export interface IMatrixClientCreds {
|
||||||
homeserverUrl: string,
|
homeserverUrl: string,
|
||||||
identityServerUrl: string,
|
identityServerUrl: string,
|
||||||
userId: string,
|
userId: string,
|
||||||
deviceId: string,
|
deviceId: string,
|
||||||
accessToken: string,
|
accessToken: string,
|
||||||
guest: boolean,
|
guest: boolean,
|
||||||
|
pickleKey?: string,
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// TODO: Move this to the js-sdk
|
||||||
* Wrapper object for handling the js-sdk Matrix Client object in the react-sdk
|
export interface IOpts {
|
||||||
* Handles the creation/initialisation of client objects.
|
initialSyncLimit?: number;
|
||||||
* This module provides a singleton instance of this class so the 'current'
|
pendingEventOrdering?: "detached" | "chronological";
|
||||||
* Matrix Client object is available easily.
|
lazyLoadMembers?: boolean;
|
||||||
*/
|
|
||||||
class _MatrixClientPeg {
|
|
||||||
constructor() {
|
|
||||||
this.matrixClient = null;
|
|
||||||
this._justRegisteredUserId = null;
|
|
||||||
|
|
||||||
// These are the default options used when when the
|
|
||||||
// client is started in 'start'. These can be altered
|
|
||||||
// at any time up to after the 'will_start_client'
|
|
||||||
// event is finished processing.
|
|
||||||
this.opts = {
|
|
||||||
initialSyncLimit: 20,
|
|
||||||
};
|
|
||||||
// the credentials used to init the current client object.
|
|
||||||
// used if we tear it down & recreate it with a different store
|
|
||||||
this._currentClientCreds = null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface IMatrixClientPeg {
|
||||||
|
opts: IOpts;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the script href passed to the IndexedDB web worker
|
* Sets the script href passed to the IndexedDB web worker
|
||||||
* If set, a separate web worker will be started to run the IndexedDB
|
* If set, a separate web worker will be started to run the IndexedDB
|
||||||
|
@ -73,19 +61,23 @@ class _MatrixClientPeg {
|
||||||
*
|
*
|
||||||
* @param {string} script href to the script to be passed to the web worker
|
* @param {string} script href to the script to be passed to the web worker
|
||||||
*/
|
*/
|
||||||
setIndexedDbWorkerScript(script) {
|
setIndexedDbWorkerScript(script: string): void;
|
||||||
createMatrixClient.indexedDbWorkerScript = script;
|
|
||||||
}
|
|
||||||
|
|
||||||
get(): MatrixClient {
|
/**
|
||||||
return this.matrixClient;
|
* Return the server name of the user's homeserver
|
||||||
}
|
* Throws an error if unable to deduce the homeserver name
|
||||||
|
* (eg. if the user is not logged in)
|
||||||
|
*
|
||||||
|
* @returns {string} The homeserver name, if present.
|
||||||
|
*/
|
||||||
|
getHomeserverName(): string;
|
||||||
|
|
||||||
unset() {
|
get(): MatrixClient;
|
||||||
this.matrixClient = null;
|
unset(): void;
|
||||||
|
assign(): Promise<any>;
|
||||||
|
start(): Promise<any>;
|
||||||
|
|
||||||
MatrixActionCreators.stop();
|
getCredentials(): IMatrixClientCreds;
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If we've registered a user ID we set this to the ID of the
|
* If we've registered a user ID we set this to the ID of the
|
||||||
|
@ -95,9 +87,7 @@ class _MatrixClientPeg {
|
||||||
*
|
*
|
||||||
* @param {string} uid The user ID of the user we've just registered
|
* @param {string} uid The user ID of the user we've just registered
|
||||||
*/
|
*/
|
||||||
setJustRegisteredUserId(uid) {
|
setJustRegisteredUserId(uid: string): void;
|
||||||
this._justRegisteredUserId = uid;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns true if the current user has just been registered by this
|
* Returns true if the current user has just been registered by this
|
||||||
|
@ -105,23 +95,73 @@ class _MatrixClientPeg {
|
||||||
*
|
*
|
||||||
* @returns {bool} True if user has just been registered
|
* @returns {bool} True if user has just been registered
|
||||||
*/
|
*/
|
||||||
currentUserIsJustRegistered() {
|
currentUserIsJustRegistered(): boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Replace this MatrixClientPeg's client with a client instance that has
|
||||||
|
* homeserver / identity server URLs and active credentials
|
||||||
|
*
|
||||||
|
* @param {IMatrixClientCreds} creds The new credentials to use.
|
||||||
|
*/
|
||||||
|
replaceUsingCreds(creds: IMatrixClientCreds): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wrapper object for handling the js-sdk Matrix Client object in the react-sdk
|
||||||
|
* Handles the creation/initialisation of client objects.
|
||||||
|
* This module provides a singleton instance of this class so the 'current'
|
||||||
|
* Matrix Client object is available easily.
|
||||||
|
*/
|
||||||
|
class _MatrixClientPeg implements IMatrixClientPeg {
|
||||||
|
// These are the default options used when when the
|
||||||
|
// client is started in 'start'. These can be altered
|
||||||
|
// at any time up to after the 'will_start_client'
|
||||||
|
// event is finished processing.
|
||||||
|
public opts: IOpts = {
|
||||||
|
initialSyncLimit: 20,
|
||||||
|
};
|
||||||
|
|
||||||
|
private matrixClient: MatrixClient = null;
|
||||||
|
private justRegisteredUserId: string;
|
||||||
|
|
||||||
|
// the credentials used to init the current client object.
|
||||||
|
// used if we tear it down & recreate it with a different store
|
||||||
|
private currentClientCreds: IMatrixClientCreds;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public setIndexedDbWorkerScript(script: string): void {
|
||||||
|
createMatrixClient.indexedDbWorkerScript = script;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get(): MatrixClient {
|
||||||
|
return this.matrixClient;
|
||||||
|
}
|
||||||
|
|
||||||
|
public unset(): void {
|
||||||
|
this.matrixClient = null;
|
||||||
|
|
||||||
|
MatrixActionCreators.stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
public setJustRegisteredUserId(uid: string): void {
|
||||||
|
this.justRegisteredUserId = uid;
|
||||||
|
}
|
||||||
|
|
||||||
|
public currentUserIsJustRegistered(): boolean {
|
||||||
return (
|
return (
|
||||||
this.matrixClient &&
|
this.matrixClient &&
|
||||||
this.matrixClient.credentials.userId === this._justRegisteredUserId
|
this.matrixClient.credentials.userId === this.justRegisteredUserId
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
public replaceUsingCreds(creds: IMatrixClientCreds): void {
|
||||||
* Replace this MatrixClientPeg's client with a client instance that has
|
this.currentClientCreds = creds;
|
||||||
* homeserver / identity server URLs and active credentials
|
this.createClient(creds);
|
||||||
*/
|
|
||||||
replaceUsingCreds(creds: MatrixClientCreds) {
|
|
||||||
this._currentClientCreds = creds;
|
|
||||||
this._createClient(creds);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async assign() {
|
public async assign(): Promise<any> {
|
||||||
for (const dbType of ['indexeddb', 'memory']) {
|
for (const dbType of ['indexeddb', 'memory']) {
|
||||||
try {
|
try {
|
||||||
const promise = this.matrixClient.store.startup();
|
const promise = this.matrixClient.store.startup();
|
||||||
|
@ -132,7 +172,7 @@ class _MatrixClientPeg {
|
||||||
if (dbType === 'indexeddb') {
|
if (dbType === 'indexeddb') {
|
||||||
console.error('Error starting matrixclient store - falling back to memory store', err);
|
console.error('Error starting matrixclient store - falling back to memory store', err);
|
||||||
this.matrixClient.store = new MemoryStore({
|
this.matrixClient.store = new MemoryStore({
|
||||||
localStorage: global.localStorage,
|
localStorage: localStorage,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
console.error('Failed to start memory store!', err);
|
console.error('Failed to start memory store!', err);
|
||||||
|
@ -158,9 +198,7 @@ class _MatrixClientPeg {
|
||||||
// The js-sdk found a crypto DB too new for it to use
|
// The js-sdk found a crypto DB too new for it to use
|
||||||
const CryptoStoreTooNewDialog =
|
const CryptoStoreTooNewDialog =
|
||||||
sdk.getComponent("views.dialogs.CryptoStoreTooNewDialog");
|
sdk.getComponent("views.dialogs.CryptoStoreTooNewDialog");
|
||||||
Modal.createDialog(CryptoStoreTooNewDialog, {
|
Modal.createDialog(CryptoStoreTooNewDialog);
|
||||||
host: window.location.host,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
// this can happen for a number of reasons, the most likely being
|
// this can happen for a number of reasons, the most likely being
|
||||||
// that the olm library was missing. It's not fatal.
|
// that the olm library was missing. It's not fatal.
|
||||||
|
@ -179,7 +217,7 @@ class _MatrixClientPeg {
|
||||||
return opts;
|
return opts;
|
||||||
}
|
}
|
||||||
|
|
||||||
async start() {
|
public async start(): Promise<any> {
|
||||||
const opts = await this.assign();
|
const opts = await this.assign();
|
||||||
|
|
||||||
console.log(`MatrixClientPeg: really starting MatrixClient`);
|
console.log(`MatrixClientPeg: really starting MatrixClient`);
|
||||||
|
@ -187,7 +225,7 @@ class _MatrixClientPeg {
|
||||||
console.log(`MatrixClientPeg: MatrixClient started`);
|
console.log(`MatrixClientPeg: MatrixClient started`);
|
||||||
}
|
}
|
||||||
|
|
||||||
getCredentials(): MatrixClientCreds {
|
public getCredentials(): IMatrixClientCreds {
|
||||||
return {
|
return {
|
||||||
homeserverUrl: this.matrixClient.baseUrl,
|
homeserverUrl: this.matrixClient.baseUrl,
|
||||||
identityServerUrl: this.matrixClient.idBaseUrl,
|
identityServerUrl: this.matrixClient.idBaseUrl,
|
||||||
|
@ -198,12 +236,7 @@ class _MatrixClientPeg {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
public getHomeserverName(): string {
|
||||||
* Return the server name of the user's homeserver
|
|
||||||
* Throws an error if unable to deduce the homeserver name
|
|
||||||
* (eg. if the user is not logged in)
|
|
||||||
*/
|
|
||||||
getHomeserverName() {
|
|
||||||
const matches = /^@.+:(.+)$/.exec(this.matrixClient.credentials.userId);
|
const matches = /^@.+:(.+)$/.exec(this.matrixClient.credentials.userId);
|
||||||
if (matches === null || matches.length < 1) {
|
if (matches === null || matches.length < 1) {
|
||||||
throw new Error("Failed to derive homeserver name from user ID!");
|
throw new Error("Failed to derive homeserver name from user ID!");
|
||||||
|
@ -211,13 +244,15 @@ class _MatrixClientPeg {
|
||||||
return matches[1];
|
return matches[1];
|
||||||
}
|
}
|
||||||
|
|
||||||
_createClient(creds: MatrixClientCreds) {
|
private createClient(creds: IMatrixClientCreds): void {
|
||||||
|
// TODO: Make these opts typesafe with the js-sdk
|
||||||
const opts = {
|
const opts = {
|
||||||
baseUrl: creds.homeserverUrl,
|
baseUrl: creds.homeserverUrl,
|
||||||
idBaseUrl: creds.identityServerUrl,
|
idBaseUrl: creds.identityServerUrl,
|
||||||
accessToken: creds.accessToken,
|
accessToken: creds.accessToken,
|
||||||
userId: creds.userId,
|
userId: creds.userId,
|
||||||
deviceId: creds.deviceId,
|
deviceId: creds.deviceId,
|
||||||
|
pickleKey: creds.pickleKey,
|
||||||
timelineSupport: true,
|
timelineSupport: true,
|
||||||
forceTURN: !SettingsStore.getValue('webRtcAllowPeerToPeer', false),
|
forceTURN: !SettingsStore.getValue('webRtcAllowPeerToPeer', false),
|
||||||
fallbackICEServerAllowed: !!SettingsStore.getValue('fallbackICEServerAllowed'),
|
fallbackICEServerAllowed: !!SettingsStore.getValue('fallbackICEServerAllowed'),
|
||||||
|
@ -228,9 +263,9 @@ class _MatrixClientPeg {
|
||||||
],
|
],
|
||||||
unstableClientRelationAggregation: true,
|
unstableClientRelationAggregation: true,
|
||||||
identityServer: new IdentityAuthClient(),
|
identityServer: new IdentityAuthClient(),
|
||||||
|
cryptoCallbacks: {},
|
||||||
};
|
};
|
||||||
|
|
||||||
opts.cryptoCallbacks = {};
|
|
||||||
// These are always installed regardless of the labs flag so that
|
// These are always installed regardless of the labs flag so that
|
||||||
// cross-signing features can toggle on without reloading and also be
|
// cross-signing features can toggle on without reloading and also be
|
||||||
// accessed immediately after login.
|
// accessed immediately after login.
|
||||||
|
@ -253,8 +288,8 @@ class _MatrixClientPeg {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!global.mxMatrixClientPeg) {
|
if (!window.mxMatrixClientPeg) {
|
||||||
global.mxMatrixClientPeg = new _MatrixClientPeg();
|
window.mxMatrixClientPeg = new _MatrixClientPeg();
|
||||||
}
|
}
|
||||||
|
|
||||||
export const MatrixClientPeg = global.mxMatrixClientPeg;
|
export const MatrixClientPeg = window.mxMatrixClientPeg;
|
|
@ -18,7 +18,7 @@ limitations under the License.
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import ReactDOM from 'react-dom';
|
import ReactDOM from 'react-dom';
|
||||||
import Analytics from './Analytics';
|
import Analytics from './Analytics';
|
||||||
import dis from './dispatcher';
|
import dis from './dispatcher/dispatcher';
|
||||||
import {defer} from './utils/promise';
|
import {defer} from './utils/promise';
|
||||||
import AsyncWrapper from './AsyncWrapper';
|
import AsyncWrapper from './AsyncWrapper';
|
||||||
|
|
||||||
|
|
|
@ -21,11 +21,14 @@ import PlatformPeg from './PlatformPeg';
|
||||||
import * as TextForEvent from './TextForEvent';
|
import * as TextForEvent from './TextForEvent';
|
||||||
import Analytics from './Analytics';
|
import Analytics from './Analytics';
|
||||||
import * as Avatar from './Avatar';
|
import * as Avatar from './Avatar';
|
||||||
import dis from './dispatcher';
|
import dis from './dispatcher/dispatcher';
|
||||||
import * as sdk from './index';
|
import * as sdk from './index';
|
||||||
import { _t } from './languageHandler';
|
import { _t } from './languageHandler';
|
||||||
import Modal from './Modal';
|
import Modal from './Modal';
|
||||||
import SettingsStore, {SettingLevel} from "./settings/SettingsStore";
|
import SettingsStore, {SettingLevel} from "./settings/SettingsStore";
|
||||||
|
import {
|
||||||
|
hideToast as hideNotificationsToast,
|
||||||
|
} from "./toasts/DesktopNotificationsToast";
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Dispatches:
|
* Dispatches:
|
||||||
|
@ -278,12 +281,7 @@ const Notifier = {
|
||||||
|
|
||||||
Analytics.trackEvent('Notifier', 'Set Toolbar Hidden', hidden);
|
Analytics.trackEvent('Notifier', 'Set Toolbar Hidden', hidden);
|
||||||
|
|
||||||
// XXX: why are we dispatching this here?
|
hideNotificationsToast();
|
||||||
// this is nothing to do with notifier_enabled
|
|
||||||
dis.dispatch({
|
|
||||||
action: "notifier_enabled",
|
|
||||||
value: this.isEnabled(),
|
|
||||||
});
|
|
||||||
|
|
||||||
// update the info to localStorage for persistent settings
|
// update the info to localStorage for persistent settings
|
||||||
if (persistent && global.localStorage) {
|
if (persistent && global.localStorage) {
|
||||||
|
|
|
@ -84,8 +84,14 @@ export default class PasswordReset {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await this.client.setPassword({
|
await this.client.setPassword({
|
||||||
|
// Note: Though this sounds like a login type for identity servers only, it
|
||||||
|
// has a dual purpose of being used for homeservers too.
|
||||||
type: "m.login.email.identity",
|
type: "m.login.email.identity",
|
||||||
|
// TODO: Remove `threepid_creds` once servers support proper UIA
|
||||||
|
// See https://github.com/matrix-org/synapse/issues/5665
|
||||||
|
// See https://github.com/matrix-org/matrix-doc/issues/2220
|
||||||
threepid_creds: creds,
|
threepid_creds: creds,
|
||||||
|
threepidCreds: creds,
|
||||||
}, this.password);
|
}, this.password);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (err.httpStatus === 401) {
|
if (err.httpStatus === 401) {
|
||||||
|
|
|
@ -17,7 +17,7 @@ limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {MatrixClientPeg} from "./MatrixClientPeg";
|
import {MatrixClientPeg} from "./MatrixClientPeg";
|
||||||
import dis from "./dispatcher";
|
import dis from "./dispatcher/dispatcher";
|
||||||
import Timer from './utils/Timer';
|
import Timer from './utils/Timer';
|
||||||
|
|
||||||
// Time in ms after that a user is considered as unavailable/away
|
// Time in ms after that a user is considered as unavailable/away
|
||||||
|
|
|
@ -20,7 +20,7 @@ limitations under the License.
|
||||||
* registration code.
|
* registration code.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import dis from './dispatcher';
|
import dis from './dispatcher/dispatcher';
|
||||||
import * as sdk from './index';
|
import * as sdk from './index';
|
||||||
import Modal from './Modal';
|
import Modal from './Modal';
|
||||||
import { _t } from './languageHandler';
|
import { _t } from './languageHandler';
|
||||||
|
|
|
@ -16,7 +16,7 @@ limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {MatrixClientPeg} from './MatrixClientPeg';
|
import {MatrixClientPeg} from './MatrixClientPeg';
|
||||||
import dis from './dispatcher';
|
import dis from './dispatcher/dispatcher';
|
||||||
import { EventStatus } from 'matrix-js-sdk';
|
import { EventStatus } from 'matrix-js-sdk';
|
||||||
|
|
||||||
export default class Resend {
|
export default class Resend {
|
||||||
|
|
|
@ -238,7 +238,7 @@ Example:
|
||||||
|
|
||||||
import {MatrixClientPeg} from './MatrixClientPeg';
|
import {MatrixClientPeg} from './MatrixClientPeg';
|
||||||
import { MatrixEvent } from 'matrix-js-sdk';
|
import { MatrixEvent } from 'matrix-js-sdk';
|
||||||
import dis from './dispatcher';
|
import dis from './dispatcher/dispatcher';
|
||||||
import WidgetUtils from './utils/WidgetUtils';
|
import WidgetUtils from './utils/WidgetUtils';
|
||||||
import RoomViewStore from './stores/RoomViewStore';
|
import RoomViewStore from './stores/RoomViewStore';
|
||||||
import { _t } from './languageHandler';
|
import { _t } from './languageHandler';
|
||||||
|
|
|
@ -21,7 +21,7 @@ limitations under the License.
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
|
|
||||||
import {MatrixClientPeg} from './MatrixClientPeg';
|
import {MatrixClientPeg} from './MatrixClientPeg';
|
||||||
import dis from './dispatcher';
|
import dis from './dispatcher/dispatcher';
|
||||||
import * as sdk from './index';
|
import * as sdk from './index';
|
||||||
import {_t, _td} from './languageHandler';
|
import {_t, _td} from './languageHandler';
|
||||||
import Modal from './Modal';
|
import Modal from './Modal';
|
||||||
|
@ -41,6 +41,8 @@ import { parseFragment as parseHtml } from "parse5";
|
||||||
import sendBugReport from "./rageshake/submit-rageshake";
|
import sendBugReport from "./rageshake/submit-rageshake";
|
||||||
import SdkConfig from "./SdkConfig";
|
import SdkConfig from "./SdkConfig";
|
||||||
import { ensureDMExists } from "./createRoom";
|
import { ensureDMExists } from "./createRoom";
|
||||||
|
import { ViewUserPayload } from "./dispatcher/payloads/ViewUserPayload";
|
||||||
|
import { Action } from "./dispatcher/actions";
|
||||||
|
|
||||||
// XXX: workaround for https://github.com/microsoft/TypeScript/issues/31816
|
// XXX: workaround for https://github.com/microsoft/TypeScript/issues/31816
|
||||||
interface HTMLInputEvent extends Event {
|
interface HTMLInputEvent extends Event {
|
||||||
|
@ -448,8 +450,8 @@ export const Commands = [
|
||||||
new Command({
|
new Command({
|
||||||
command: 'join',
|
command: 'join',
|
||||||
aliases: ['j', 'goto'],
|
aliases: ['j', 'goto'],
|
||||||
args: '<room-alias>',
|
args: '<room-address>',
|
||||||
description: _td('Joins room with given alias'),
|
description: _td('Joins room with given address'),
|
||||||
runFn: function(_, args) {
|
runFn: function(_, args) {
|
||||||
if (args) {
|
if (args) {
|
||||||
// Note: we support 2 versions of this command. The first is
|
// Note: we support 2 versions of this command. The first is
|
||||||
|
@ -560,7 +562,7 @@ export const Commands = [
|
||||||
}),
|
}),
|
||||||
new Command({
|
new Command({
|
||||||
command: 'part',
|
command: 'part',
|
||||||
args: '[<room-alias>]',
|
args: '[<room-address>]',
|
||||||
description: _td('Leave room'),
|
description: _td('Leave room'),
|
||||||
runFn: function(roomId, args) {
|
runFn: function(roomId, args) {
|
||||||
const cli = MatrixClientPeg.get();
|
const cli = MatrixClientPeg.get();
|
||||||
|
@ -592,7 +594,7 @@ export const Commands = [
|
||||||
}
|
}
|
||||||
if (targetRoomId) break;
|
if (targetRoomId) break;
|
||||||
}
|
}
|
||||||
if (!targetRoomId) return reject(_t('Unrecognised room alias:') + ' ' + roomAlias);
|
if (!targetRoomId) return reject(_t('Unrecognised room address:') + ' ' + roomAlias);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -943,8 +945,10 @@ export const Commands = [
|
||||||
}
|
}
|
||||||
|
|
||||||
const member = MatrixClientPeg.get().getRoom(roomId).getMember(userId);
|
const member = MatrixClientPeg.get().getRoom(roomId).getMember(userId);
|
||||||
dis.dispatch({
|
dis.dispatch<ViewUserPayload>({
|
||||||
action: 'view_user',
|
action: Action.ViewUser,
|
||||||
|
// XXX: We should be using a real member object and not assuming what the
|
||||||
|
// receiver wants.
|
||||||
member: member || {userId},
|
member: member || {userId},
|
||||||
});
|
});
|
||||||
return success();
|
return success();
|
||||||
|
|
|
@ -15,7 +15,7 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import dis from './dispatcher';
|
import dis from './dispatcher/dispatcher';
|
||||||
import Timer from './utils/Timer';
|
import Timer from './utils/Timer';
|
||||||
|
|
||||||
// important these are larger than the timeouts of timers
|
// important these are larger than the timeouts of timers
|
||||||
|
|
|
@ -1,34 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright 2017 New Vector 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 { asyncAction } from './actionCreators';
|
|
||||||
|
|
||||||
const GroupActions = {};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates an action thunk that will do an asynchronous request to fetch
|
|
||||||
* the groups to which a user is joined.
|
|
||||||
*
|
|
||||||
* @param {MatrixClient} matrixClient the matrix client to query.
|
|
||||||
* @returns {function} an action thunk that will dispatch actions
|
|
||||||
* indicating the status of the request.
|
|
||||||
* @see asyncAction
|
|
||||||
*/
|
|
||||||
GroupActions.fetchJoinedGroups = function(matrixClient) {
|
|
||||||
return asyncAction('GroupActions.fetchJoinedGroups', () => matrixClient.getJoinedGroups());
|
|
||||||
};
|
|
||||||
|
|
||||||
export default GroupActions;
|
|
34
src/actions/GroupActions.ts
Normal file
34
src/actions/GroupActions.ts
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
/*
|
||||||
|
Copyright 2017 New Vector Ltd
|
||||||
|
Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||||
|
|
||||||
|
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 { asyncAction } from './actionCreators';
|
||||||
|
import { AsyncActionPayload } from "../dispatcher/payloads";
|
||||||
|
import { MatrixClient } from "matrix-js-sdk/src/client";
|
||||||
|
|
||||||
|
export default class GroupActions {
|
||||||
|
/**
|
||||||
|
* Creates an action thunk that will do an asynchronous request to fetch
|
||||||
|
* the groups to which a user is joined.
|
||||||
|
*
|
||||||
|
* @param {MatrixClient} matrixClient the matrix client to query.
|
||||||
|
* @returns {AsyncActionPayload} An async action payload.
|
||||||
|
* @see asyncAction
|
||||||
|
*/
|
||||||
|
public static fetchJoinedGroups(matrixClient: MatrixClient): AsyncActionPayload {
|
||||||
|
return asyncAction('GroupActions.fetchJoinedGroups', () => matrixClient.getJoinedGroups(), null);
|
||||||
|
}
|
||||||
|
}
|
|
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import dis from '../dispatcher';
|
import dis from '../dispatcher/dispatcher';
|
||||||
|
|
||||||
// TODO: migrate from sync_state to MatrixActions.sync so that more js-sdk events
|
// TODO: migrate from sync_state to MatrixActions.sync so that more js-sdk events
|
||||||
// become dispatches in the same place.
|
// become dispatches in the same place.
|
||||||
|
|
|
@ -1,145 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright 2018 New Vector 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 { asyncAction } from './actionCreators';
|
|
||||||
import RoomListStore, {TAG_DM} from '../stores/RoomListStore';
|
|
||||||
import Modal from '../Modal';
|
|
||||||
import * as Rooms from '../Rooms';
|
|
||||||
import { _t } from '../languageHandler';
|
|
||||||
import * as sdk from '../index';
|
|
||||||
|
|
||||||
const RoomListActions = {};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates an action thunk that will do an asynchronous request to
|
|
||||||
* tag room.
|
|
||||||
*
|
|
||||||
* @param {MatrixClient} matrixClient the matrix client to set the
|
|
||||||
* account data on.
|
|
||||||
* @param {Room} room the room to tag.
|
|
||||||
* @param {string} oldTag the tag to remove (unless oldTag ==== newTag)
|
|
||||||
* @param {string} newTag the tag with which to tag the room.
|
|
||||||
* @param {?number} oldIndex the previous position of the room in the
|
|
||||||
* list of rooms.
|
|
||||||
* @param {?number} newIndex the new position of the room in the list
|
|
||||||
* of rooms.
|
|
||||||
* @returns {function} an action thunk.
|
|
||||||
* @see asyncAction
|
|
||||||
*/
|
|
||||||
RoomListActions.tagRoom = function(matrixClient, room, oldTag, newTag, oldIndex, newIndex) {
|
|
||||||
let metaData = null;
|
|
||||||
|
|
||||||
// Is the tag ordered manually?
|
|
||||||
if (newTag && !newTag.match(/^(m\.lowpriority|im\.vector\.fake\.(invite|recent|direct|archived))$/)) {
|
|
||||||
const lists = RoomListStore.getRoomLists();
|
|
||||||
const newList = [...lists[newTag]];
|
|
||||||
|
|
||||||
newList.sort((a, b) => a.tags[newTag].order - b.tags[newTag].order);
|
|
||||||
|
|
||||||
// If the room was moved "down" (increasing index) in the same list we
|
|
||||||
// need to use the orders of the tiles with indices shifted by +1
|
|
||||||
const offset = (
|
|
||||||
newTag === oldTag && oldIndex < newIndex
|
|
||||||
) ? 1 : 0;
|
|
||||||
|
|
||||||
const indexBefore = offset + newIndex - 1;
|
|
||||||
const indexAfter = offset + newIndex;
|
|
||||||
|
|
||||||
const prevOrder = indexBefore <= 0 ?
|
|
||||||
0 : newList[indexBefore].tags[newTag].order;
|
|
||||||
const nextOrder = indexAfter >= newList.length ?
|
|
||||||
1 : newList[indexAfter].tags[newTag].order;
|
|
||||||
|
|
||||||
metaData = {
|
|
||||||
order: (prevOrder + nextOrder) / 2.0,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return asyncAction('RoomListActions.tagRoom', () => {
|
|
||||||
const promises = [];
|
|
||||||
const roomId = room.roomId;
|
|
||||||
|
|
||||||
// Evil hack to get DMs behaving
|
|
||||||
if ((oldTag === undefined && newTag === TAG_DM) ||
|
|
||||||
(oldTag === TAG_DM && newTag === undefined)
|
|
||||||
) {
|
|
||||||
return Rooms.guessAndSetDMRoom(
|
|
||||||
room, newTag === TAG_DM,
|
|
||||||
).catch((err) => {
|
|
||||||
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
|
||||||
console.error("Failed to set direct chat tag " + err);
|
|
||||||
Modal.createTrackedDialog('Failed to set direct chat tag', '', ErrorDialog, {
|
|
||||||
title: _t('Failed to set direct chat tag'),
|
|
||||||
description: ((err && err.message) ? err.message : _t('Operation failed')),
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const hasChangedSubLists = oldTag !== newTag;
|
|
||||||
|
|
||||||
// More evilness: We will still be dealing with moving to favourites/low prio,
|
|
||||||
// but we avoid ever doing a request with TAG_DM.
|
|
||||||
//
|
|
||||||
// if we moved lists, remove the old tag
|
|
||||||
if (oldTag && oldTag !== TAG_DM &&
|
|
||||||
hasChangedSubLists
|
|
||||||
) {
|
|
||||||
const promiseToDelete = matrixClient.deleteRoomTag(
|
|
||||||
roomId, oldTag,
|
|
||||||
).catch(function(err) {
|
|
||||||
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
|
||||||
console.error("Failed to remove tag " + oldTag + " from room: " + err);
|
|
||||||
Modal.createTrackedDialog('Failed to remove tag from room', '', ErrorDialog, {
|
|
||||||
title: _t('Failed to remove tag %(tagName)s from room', {tagName: oldTag}),
|
|
||||||
description: ((err && err.message) ? err.message : _t('Operation failed')),
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
promises.push(promiseToDelete);
|
|
||||||
}
|
|
||||||
|
|
||||||
// if we moved lists or the ordering changed, add the new tag
|
|
||||||
if (newTag && newTag !== TAG_DM &&
|
|
||||||
(hasChangedSubLists || metaData)
|
|
||||||
) {
|
|
||||||
// metaData is the body of the PUT to set the tag, so it must
|
|
||||||
// at least be an empty object.
|
|
||||||
metaData = metaData || {};
|
|
||||||
|
|
||||||
const promiseToAdd = matrixClient.setRoomTag(roomId, newTag, metaData).catch(function(err) {
|
|
||||||
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
|
||||||
console.error("Failed to add tag " + newTag + " to room: " + err);
|
|
||||||
Modal.createTrackedDialog('Failed to add tag to room', '', ErrorDialog, {
|
|
||||||
title: _t('Failed to add tag %(tagName)s to room', {tagName: newTag}),
|
|
||||||
description: ((err && err.message) ? err.message : _t('Operation failed')),
|
|
||||||
});
|
|
||||||
|
|
||||||
throw err;
|
|
||||||
});
|
|
||||||
|
|
||||||
promises.push(promiseToAdd);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Promise.all(promises);
|
|
||||||
}, () => {
|
|
||||||
// For an optimistic update
|
|
||||||
return {
|
|
||||||
room, oldTag, newTag, metaData,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
export default RoomListActions;
|
|
152
src/actions/RoomListActions.ts
Normal file
152
src/actions/RoomListActions.ts
Normal file
|
@ -0,0 +1,152 @@
|
||||||
|
/*
|
||||||
|
Copyright 2018 New Vector Ltd
|
||||||
|
Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||||
|
|
||||||
|
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 { asyncAction } from './actionCreators';
|
||||||
|
import { TAG_DM } from '../stores/RoomListStore';
|
||||||
|
import Modal from '../Modal';
|
||||||
|
import * as Rooms from '../Rooms';
|
||||||
|
import { _t } from '../languageHandler';
|
||||||
|
import * as sdk from '../index';
|
||||||
|
import { MatrixClient } from "matrix-js-sdk/src/client";
|
||||||
|
import { Room } from "matrix-js-sdk/src/models/room";
|
||||||
|
import { AsyncActionPayload } from "../dispatcher/payloads";
|
||||||
|
import { RoomListStoreTempProxy } from "../stores/room-list/RoomListStoreTempProxy";
|
||||||
|
|
||||||
|
export default class RoomListActions {
|
||||||
|
/**
|
||||||
|
* Creates an action thunk that will do an asynchronous request to
|
||||||
|
* tag room.
|
||||||
|
*
|
||||||
|
* @param {MatrixClient} matrixClient the matrix client to set the
|
||||||
|
* account data on.
|
||||||
|
* @param {Room} room the room to tag.
|
||||||
|
* @param {string} oldTag the tag to remove (unless oldTag ==== newTag)
|
||||||
|
* @param {string} newTag the tag with which to tag the room.
|
||||||
|
* @param {?number} oldIndex the previous position of the room in the
|
||||||
|
* list of rooms.
|
||||||
|
* @param {?number} newIndex the new position of the room in the list
|
||||||
|
* of rooms.
|
||||||
|
* @returns {AsyncActionPayload} an async action payload
|
||||||
|
* @see asyncAction
|
||||||
|
*/
|
||||||
|
public static tagRoom(
|
||||||
|
matrixClient: MatrixClient, room: Room,
|
||||||
|
oldTag: string, newTag: string,
|
||||||
|
oldIndex: number | null, newIndex: number | null,
|
||||||
|
): AsyncActionPayload {
|
||||||
|
let metaData = null;
|
||||||
|
|
||||||
|
// Is the tag ordered manually?
|
||||||
|
if (newTag && !newTag.match(/^(m\.lowpriority|im\.vector\.fake\.(invite|recent|direct|archived))$/)) {
|
||||||
|
const lists = RoomListStoreTempProxy.getRoomLists();
|
||||||
|
const newList = [...lists[newTag]];
|
||||||
|
|
||||||
|
newList.sort((a, b) => a.tags[newTag].order - b.tags[newTag].order);
|
||||||
|
|
||||||
|
// If the room was moved "down" (increasing index) in the same list we
|
||||||
|
// need to use the orders of the tiles with indices shifted by +1
|
||||||
|
const offset = (
|
||||||
|
newTag === oldTag && oldIndex < newIndex
|
||||||
|
) ? 1 : 0;
|
||||||
|
|
||||||
|
const indexBefore = offset + newIndex - 1;
|
||||||
|
const indexAfter = offset + newIndex;
|
||||||
|
|
||||||
|
const prevOrder = indexBefore <= 0 ?
|
||||||
|
0 : newList[indexBefore].tags[newTag].order;
|
||||||
|
const nextOrder = indexAfter >= newList.length ?
|
||||||
|
1 : newList[indexAfter].tags[newTag].order;
|
||||||
|
|
||||||
|
metaData = {
|
||||||
|
order: (prevOrder + nextOrder) / 2.0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return asyncAction('RoomListActions.tagRoom', () => {
|
||||||
|
const promises = [];
|
||||||
|
const roomId = room.roomId;
|
||||||
|
|
||||||
|
// Evil hack to get DMs behaving
|
||||||
|
if ((oldTag === undefined && newTag === TAG_DM) ||
|
||||||
|
(oldTag === TAG_DM && newTag === undefined)
|
||||||
|
) {
|
||||||
|
return Rooms.guessAndSetDMRoom(
|
||||||
|
room, newTag === TAG_DM,
|
||||||
|
).catch((err) => {
|
||||||
|
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||||
|
console.error("Failed to set direct chat tag " + err);
|
||||||
|
Modal.createTrackedDialog('Failed to set direct chat tag', '', ErrorDialog, {
|
||||||
|
title: _t('Failed to set direct chat tag'),
|
||||||
|
description: ((err && err.message) ? err.message : _t('Operation failed')),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const hasChangedSubLists = oldTag !== newTag;
|
||||||
|
|
||||||
|
// More evilness: We will still be dealing with moving to favourites/low prio,
|
||||||
|
// but we avoid ever doing a request with TAG_DM.
|
||||||
|
//
|
||||||
|
// if we moved lists, remove the old tag
|
||||||
|
if (oldTag && oldTag !== TAG_DM &&
|
||||||
|
hasChangedSubLists
|
||||||
|
) {
|
||||||
|
const promiseToDelete = matrixClient.deleteRoomTag(
|
||||||
|
roomId, oldTag,
|
||||||
|
).catch(function (err) {
|
||||||
|
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||||
|
console.error("Failed to remove tag " + oldTag + " from room: " + err);
|
||||||
|
Modal.createTrackedDialog('Failed to remove tag from room', '', ErrorDialog, {
|
||||||
|
title: _t('Failed to remove tag %(tagName)s from room', {tagName: oldTag}),
|
||||||
|
description: ((err && err.message) ? err.message : _t('Operation failed')),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
promises.push(promiseToDelete);
|
||||||
|
}
|
||||||
|
|
||||||
|
// if we moved lists or the ordering changed, add the new tag
|
||||||
|
if (newTag && newTag !== TAG_DM &&
|
||||||
|
(hasChangedSubLists || metaData)
|
||||||
|
) {
|
||||||
|
// metaData is the body of the PUT to set the tag, so it must
|
||||||
|
// at least be an empty object.
|
||||||
|
metaData = metaData || {};
|
||||||
|
|
||||||
|
const promiseToAdd = matrixClient.setRoomTag(roomId, newTag, metaData).catch(function (err) {
|
||||||
|
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||||
|
console.error("Failed to add tag " + newTag + " to room: " + err);
|
||||||
|
Modal.createTrackedDialog('Failed to add tag to room', '', ErrorDialog, {
|
||||||
|
title: _t('Failed to add tag %(tagName)s to room', {tagName: newTag}),
|
||||||
|
description: ((err && err.message) ? err.message : _t('Operation failed')),
|
||||||
|
});
|
||||||
|
|
||||||
|
throw err;
|
||||||
|
});
|
||||||
|
|
||||||
|
promises.push(promiseToAdd);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Promise.all(promises);
|
||||||
|
}, () => {
|
||||||
|
// For an optimistic update
|
||||||
|
return {
|
||||||
|
room, oldTag, newTag, metaData,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,109 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright 2017 New Vector 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 Analytics from '../Analytics';
|
|
||||||
import { asyncAction } from './actionCreators';
|
|
||||||
import TagOrderStore from '../stores/TagOrderStore';
|
|
||||||
|
|
||||||
const TagOrderActions = {};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates an action thunk that will do an asynchronous request to
|
|
||||||
* move a tag in TagOrderStore to destinationIx.
|
|
||||||
*
|
|
||||||
* @param {MatrixClient} matrixClient the matrix client to set the
|
|
||||||
* account data on.
|
|
||||||
* @param {string} tag the tag to move.
|
|
||||||
* @param {number} destinationIx the new position of the tag.
|
|
||||||
* @returns {function} an action thunk that will dispatch actions
|
|
||||||
* indicating the status of the request.
|
|
||||||
* @see asyncAction
|
|
||||||
*/
|
|
||||||
TagOrderActions.moveTag = function(matrixClient, tag, destinationIx) {
|
|
||||||
// Only commit tags if the state is ready, i.e. not null
|
|
||||||
let tags = TagOrderStore.getOrderedTags();
|
|
||||||
let removedTags = TagOrderStore.getRemovedTagsAccountData() || [];
|
|
||||||
if (!tags) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
tags = tags.filter((t) => t !== tag);
|
|
||||||
tags = [...tags.slice(0, destinationIx), tag, ...tags.slice(destinationIx)];
|
|
||||||
|
|
||||||
removedTags = removedTags.filter((t) => t !== tag);
|
|
||||||
|
|
||||||
const storeId = TagOrderStore.getStoreId();
|
|
||||||
|
|
||||||
return asyncAction('TagOrderActions.moveTag', () => {
|
|
||||||
Analytics.trackEvent('TagOrderActions', 'commitTagOrdering');
|
|
||||||
return matrixClient.setAccountData(
|
|
||||||
'im.vector.web.tag_ordering',
|
|
||||||
{tags, removedTags, _storeId: storeId},
|
|
||||||
);
|
|
||||||
}, () => {
|
|
||||||
// For an optimistic update
|
|
||||||
return {tags, removedTags};
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates an action thunk that will do an asynchronous request to
|
|
||||||
* label a tag as removed in im.vector.web.tag_ordering account data.
|
|
||||||
*
|
|
||||||
* The reason this is implemented with new state `removedTags` is that
|
|
||||||
* we incrementally and initially populate `tags` with groups that
|
|
||||||
* have been joined. If we remove a group from `tags`, it will just
|
|
||||||
* get added (as it looks like a group we've recently joined).
|
|
||||||
*
|
|
||||||
* NB: If we ever support adding of tags (which is planned), we should
|
|
||||||
* take special care to remove the tag from `removedTags` when we add
|
|
||||||
* it.
|
|
||||||
*
|
|
||||||
* @param {MatrixClient} matrixClient the matrix client to set the
|
|
||||||
* account data on.
|
|
||||||
* @param {string} tag the tag to remove.
|
|
||||||
* @returns {function} an action thunk that will dispatch actions
|
|
||||||
* indicating the status of the request.
|
|
||||||
* @see asyncAction
|
|
||||||
*/
|
|
||||||
TagOrderActions.removeTag = function(matrixClient, tag) {
|
|
||||||
// Don't change tags, just removedTags
|
|
||||||
const tags = TagOrderStore.getOrderedTags();
|
|
||||||
const removedTags = TagOrderStore.getRemovedTagsAccountData() || [];
|
|
||||||
|
|
||||||
if (removedTags.includes(tag)) {
|
|
||||||
// Return a thunk that doesn't do anything, we don't even need
|
|
||||||
// an asynchronous action here, the tag is already removed.
|
|
||||||
return () => {};
|
|
||||||
}
|
|
||||||
|
|
||||||
removedTags.push(tag);
|
|
||||||
|
|
||||||
const storeId = TagOrderStore.getStoreId();
|
|
||||||
|
|
||||||
return asyncAction('TagOrderActions.removeTag', () => {
|
|
||||||
Analytics.trackEvent('TagOrderActions', 'removeTag');
|
|
||||||
return matrixClient.setAccountData(
|
|
||||||
'im.vector.web.tag_ordering',
|
|
||||||
{tags, removedTags, _storeId: storeId},
|
|
||||||
);
|
|
||||||
}, () => {
|
|
||||||
// For an optimistic update
|
|
||||||
return {removedTags};
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
export default TagOrderActions;
|
|
111
src/actions/TagOrderActions.ts
Normal file
111
src/actions/TagOrderActions.ts
Normal file
|
@ -0,0 +1,111 @@
|
||||||
|
/*
|
||||||
|
Copyright 2017 New Vector Ltd
|
||||||
|
Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||||
|
|
||||||
|
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 Analytics from '../Analytics';
|
||||||
|
import { asyncAction } from './actionCreators';
|
||||||
|
import TagOrderStore from '../stores/TagOrderStore';
|
||||||
|
import { AsyncActionPayload } from "../dispatcher/payloads";
|
||||||
|
import { MatrixClient } from "matrix-js-sdk/src/client";
|
||||||
|
|
||||||
|
export default class TagOrderActions {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an action thunk that will do an asynchronous request to
|
||||||
|
* move a tag in TagOrderStore to destinationIx.
|
||||||
|
*
|
||||||
|
* @param {MatrixClient} matrixClient the matrix client to set the
|
||||||
|
* account data on.
|
||||||
|
* @param {string} tag the tag to move.
|
||||||
|
* @param {number} destinationIx the new position of the tag.
|
||||||
|
* @returns {AsyncActionPayload} an async action payload that will
|
||||||
|
* dispatch actions indicating the status of the request.
|
||||||
|
* @see asyncAction
|
||||||
|
*/
|
||||||
|
public static moveTag(matrixClient: MatrixClient, tag: string, destinationIx: number): AsyncActionPayload {
|
||||||
|
// Only commit tags if the state is ready, i.e. not null
|
||||||
|
let tags = TagOrderStore.getOrderedTags();
|
||||||
|
let removedTags = TagOrderStore.getRemovedTagsAccountData() || [];
|
||||||
|
if (!tags) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
tags = tags.filter((t) => t !== tag);
|
||||||
|
tags = [...tags.slice(0, destinationIx), tag, ...tags.slice(destinationIx)];
|
||||||
|
|
||||||
|
removedTags = removedTags.filter((t) => t !== tag);
|
||||||
|
|
||||||
|
const storeId = TagOrderStore.getStoreId();
|
||||||
|
|
||||||
|
return asyncAction('TagOrderActions.moveTag', () => {
|
||||||
|
Analytics.trackEvent('TagOrderActions', 'commitTagOrdering');
|
||||||
|
return matrixClient.setAccountData(
|
||||||
|
'im.vector.web.tag_ordering',
|
||||||
|
{tags, removedTags, _storeId: storeId},
|
||||||
|
);
|
||||||
|
}, () => {
|
||||||
|
// For an optimistic update
|
||||||
|
return {tags, removedTags};
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an action thunk that will do an asynchronous request to
|
||||||
|
* label a tag as removed in im.vector.web.tag_ordering account data.
|
||||||
|
*
|
||||||
|
* The reason this is implemented with new state `removedTags` is that
|
||||||
|
* we incrementally and initially populate `tags` with groups that
|
||||||
|
* have been joined. If we remove a group from `tags`, it will just
|
||||||
|
* get added (as it looks like a group we've recently joined).
|
||||||
|
*
|
||||||
|
* NB: If we ever support adding of tags (which is planned), we should
|
||||||
|
* take special care to remove the tag from `removedTags` when we add
|
||||||
|
* it.
|
||||||
|
*
|
||||||
|
* @param {MatrixClient} matrixClient the matrix client to set the
|
||||||
|
* account data on.
|
||||||
|
* @param {string} tag the tag to remove.
|
||||||
|
* @returns {function} an async action payload that will dispatch
|
||||||
|
* actions indicating the status of the request.
|
||||||
|
* @see asyncAction
|
||||||
|
*/
|
||||||
|
public static removeTag(matrixClient: MatrixClient, tag: string): AsyncActionPayload {
|
||||||
|
// Don't change tags, just removedTags
|
||||||
|
const tags = TagOrderStore.getOrderedTags();
|
||||||
|
const removedTags = TagOrderStore.getRemovedTagsAccountData() || [];
|
||||||
|
|
||||||
|
if (removedTags.includes(tag)) {
|
||||||
|
// Return a thunk that doesn't do anything, we don't even need
|
||||||
|
// an asynchronous action here, the tag is already removed.
|
||||||
|
return new AsyncActionPayload(() => {});
|
||||||
|
}
|
||||||
|
|
||||||
|
removedTags.push(tag);
|
||||||
|
|
||||||
|
const storeId = TagOrderStore.getStoreId();
|
||||||
|
|
||||||
|
return asyncAction('TagOrderActions.removeTag', () => {
|
||||||
|
Analytics.trackEvent('TagOrderActions', 'removeTag');
|
||||||
|
return matrixClient.setAccountData(
|
||||||
|
'im.vector.web.tag_ordering',
|
||||||
|
{tags, removedTags, _storeId: storeId},
|
||||||
|
);
|
||||||
|
}, () => {
|
||||||
|
// For an optimistic update
|
||||||
|
return {removedTags};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,5 +1,6 @@
|
||||||
/*
|
/*
|
||||||
Copyright 2017 New Vector Ltd
|
Copyright 2017 New Vector Ltd
|
||||||
|
Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
|
@ -14,6 +15,8 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { AsyncActionPayload } from "../dispatcher/payloads";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create an action thunk that will dispatch actions indicating the current
|
* Create an action thunk that will dispatch actions indicating the current
|
||||||
* status of the Promise returned by fn.
|
* status of the Promise returned by fn.
|
||||||
|
@ -25,9 +28,9 @@ limitations under the License.
|
||||||
* @param {function?} pendingFn a function that returns an object to assign
|
* @param {function?} pendingFn a function that returns an object to assign
|
||||||
* to the `request` key of the ${id}.pending
|
* to the `request` key of the ${id}.pending
|
||||||
* payload.
|
* payload.
|
||||||
* @returns {function} an action thunk - a function that uses its single
|
* @returns {AsyncActionPayload} an async action payload. Includes a function
|
||||||
* argument as a dispatch function to dispatch the
|
* that uses its single argument as a dispatch function
|
||||||
* following actions:
|
* to dispatch the following actions:
|
||||||
* `${id}.pending` and either
|
* `${id}.pending` and either
|
||||||
* `${id}.success` or
|
* `${id}.success` or
|
||||||
* `${id}.failure`.
|
* `${id}.failure`.
|
||||||
|
@ -41,12 +44,11 @@ limitations under the License.
|
||||||
* result is the result of the promise returned by
|
* result is the result of the promise returned by
|
||||||
* `fn`.
|
* `fn`.
|
||||||
*/
|
*/
|
||||||
export function asyncAction(id, fn, pendingFn) {
|
export function asyncAction(id: string, fn: () => Promise<any>, pendingFn: () => any | null): AsyncActionPayload {
|
||||||
return (dispatch) => {
|
const helper = (dispatch) => {
|
||||||
dispatch({
|
dispatch({
|
||||||
action: id + '.pending',
|
action: id + '.pending',
|
||||||
request:
|
request: typeof pendingFn === 'function' ? pendingFn() : undefined,
|
||||||
typeof pendingFn === 'function' ? pendingFn() : undefined,
|
|
||||||
});
|
});
|
||||||
fn().then((result) => {
|
fn().then((result) => {
|
||||||
dispatch({action: id + '.success', result});
|
dispatch({action: id + '.success', result});
|
||||||
|
@ -54,4 +56,5 @@ export function asyncAction(id, fn, pendingFn) {
|
||||||
dispatch({action: id + '.failure', err});
|
dispatch({action: id + '.failure', err});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
return new AsyncActionPayload(helper);
|
||||||
}
|
}
|
|
@ -1,206 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright 2015, 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 createReactClass from 'create-react-class';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import { _t } from '../../../languageHandler';
|
|
||||||
import {MatrixClientPeg} from "../../../MatrixClientPeg";
|
|
||||||
import {Key} from "../../../Keyboard";
|
|
||||||
import * as sdk from "../../../index";
|
|
||||||
|
|
||||||
// XXX: This component is not cross-signing aware.
|
|
||||||
// https://github.com/vector-im/riot-web/issues/11752 tracks either updating this
|
|
||||||
// component or taking it out to pasture.
|
|
||||||
export default createReactClass({
|
|
||||||
displayName: 'EncryptedEventDialog',
|
|
||||||
|
|
||||||
propTypes: {
|
|
||||||
event: PropTypes.object.isRequired,
|
|
||||||
onFinished: PropTypes.func.isRequired,
|
|
||||||
},
|
|
||||||
|
|
||||||
getInitialState: function() {
|
|
||||||
return { device: null };
|
|
||||||
},
|
|
||||||
|
|
||||||
componentDidMount: function() {
|
|
||||||
this._unmounted = false;
|
|
||||||
const client = MatrixClientPeg.get();
|
|
||||||
|
|
||||||
// first try to load the device from our store.
|
|
||||||
//
|
|
||||||
this.refreshDevice().then((dev) => {
|
|
||||||
if (dev) {
|
|
||||||
return dev;
|
|
||||||
}
|
|
||||||
|
|
||||||
// tell the client to try to refresh the device list for this user
|
|
||||||
return client.downloadKeys([this.props.event.getSender()], true).then(() => {
|
|
||||||
return this.refreshDevice();
|
|
||||||
});
|
|
||||||
}).then((dev) => {
|
|
||||||
if (this._unmounted) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setState({ device: dev });
|
|
||||||
client.on("deviceVerificationChanged", this.onDeviceVerificationChanged);
|
|
||||||
}, (err)=>{
|
|
||||||
console.log("Error downloading devices", err);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
componentWillUnmount: function() {
|
|
||||||
this._unmounted = true;
|
|
||||||
const client = MatrixClientPeg.get();
|
|
||||||
if (client) {
|
|
||||||
client.removeListener("deviceVerificationChanged", this.onDeviceVerificationChanged);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
refreshDevice: function() {
|
|
||||||
// Promise.resolve to handle transition from static result to promise; can be removed
|
|
||||||
// in future
|
|
||||||
return Promise.resolve(MatrixClientPeg.get().getEventSenderDeviceInfo(this.props.event));
|
|
||||||
},
|
|
||||||
|
|
||||||
onDeviceVerificationChanged: function(userId, device) {
|
|
||||||
if (userId === this.props.event.getSender()) {
|
|
||||||
this.refreshDevice().then((dev) => {
|
|
||||||
this.setState({ device: dev });
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
onKeyDown: function(e) {
|
|
||||||
if (e.key === Key.ESCAPE) {
|
|
||||||
e.stopPropagation();
|
|
||||||
e.preventDefault();
|
|
||||||
this.props.onFinished(false);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
_renderDeviceInfo: function() {
|
|
||||||
const device = this.state.device;
|
|
||||||
if (!device) {
|
|
||||||
return (<i>{ _t('unknown device') }</i>);
|
|
||||||
}
|
|
||||||
|
|
||||||
let verificationStatus = (<b>{ _t('NOT verified') }</b>);
|
|
||||||
if (device.isBlocked()) {
|
|
||||||
verificationStatus = (<b>{ _t('Blacklisted') }</b>);
|
|
||||||
} else if (device.isVerified()) {
|
|
||||||
verificationStatus = _t('verified');
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<table>
|
|
||||||
<tbody>
|
|
||||||
<tr>
|
|
||||||
<td>{ _t('Name') }</td>
|
|
||||||
<td>{ device.getDisplayName() }</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>{ _t('Device ID') }</td>
|
|
||||||
<td><code>{ device.deviceId }</code></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>{ _t('Verification') }</td>
|
|
||||||
<td>{ verificationStatus }</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>{ _t('Ed25519 fingerprint') }</td>
|
|
||||||
<td><code>{ device.getFingerprint() }</code></td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
|
|
||||||
_renderEventInfo: function() {
|
|
||||||
const event = this.props.event;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<table>
|
|
||||||
<tbody>
|
|
||||||
<tr>
|
|
||||||
<td>{ _t('User ID') }</td>
|
|
||||||
<td>{ event.getSender() }</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>{ _t('Curve25519 identity key') }</td>
|
|
||||||
<td><code>{ event.getSenderKey() || <i>{ _t('none') }</i> }</code></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>{ _t('Claimed Ed25519 fingerprint key') }</td>
|
|
||||||
<td><code>{ event.getKeysClaimed().ed25519 || <i>{ _t('none') }</i> }</code></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>{ _t('Algorithm') }</td>
|
|
||||||
<td>{ event.getWireContent().algorithm || <i>{ _t('unencrypted') }</i> }</td>
|
|
||||||
</tr>
|
|
||||||
{
|
|
||||||
event.getContent().msgtype === 'm.bad.encrypted' ? (
|
|
||||||
<tr>
|
|
||||||
<td>{ _t('Decryption error') }</td>
|
|
||||||
<td>{ event.getContent().body }</td>
|
|
||||||
</tr>
|
|
||||||
) : null
|
|
||||||
}
|
|
||||||
<tr>
|
|
||||||
<td>{ _t('Session ID') }</td>
|
|
||||||
<td><code>{ event.getWireContent().session_id || <i>{ _t('none') }</i> }</code></td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
|
|
||||||
render: function() {
|
|
||||||
const DeviceVerifyButtons = sdk.getComponent('elements.DeviceVerifyButtons');
|
|
||||||
|
|
||||||
let buttons = null;
|
|
||||||
if (this.state.device) {
|
|
||||||
buttons = (
|
|
||||||
<DeviceVerifyButtons device={this.state.device}
|
|
||||||
userId={this.props.event.getSender()}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="mx_EncryptedEventDialog" onKeyDown={this.onKeyDown}>
|
|
||||||
<div className="mx_Dialog_title">
|
|
||||||
{ _t('End-to-end encryption information') }
|
|
||||||
</div>
|
|
||||||
<div className="mx_Dialog_content">
|
|
||||||
<h4>{ _t('Event information') }</h4>
|
|
||||||
{ this._renderEventInfo() }
|
|
||||||
|
|
||||||
<h4>{ _t('Sender session information') }</h4>
|
|
||||||
{ this._renderDeviceInfo() }
|
|
||||||
</div>
|
|
||||||
<div className="mx_Dialog_buttons">
|
|
||||||
<button className="mx_Dialog_primary" onClick={this.props.onFinished} autoFocus={true}>
|
|
||||||
{ _t('OK') }
|
|
||||||
</button>
|
|
||||||
{ buttons }
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
});
|
|
|
@ -17,11 +17,12 @@ limitations under the License.
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import * as sdk from '../../../../index';
|
import * as sdk from '../../../../index';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import dis from "../../../../dispatcher";
|
import dis from "../../../../dispatcher/dispatcher";
|
||||||
import { _t } from '../../../../languageHandler';
|
import { _t } from '../../../../languageHandler';
|
||||||
|
|
||||||
import SettingsStore, {SettingLevel} from "../../../../settings/SettingsStore";
|
import SettingsStore, {SettingLevel} from "../../../../settings/SettingsStore";
|
||||||
import EventIndexPeg from "../../../../indexing/EventIndexPeg";
|
import EventIndexPeg from "../../../../indexing/EventIndexPeg";
|
||||||
|
import {Action} from "../../../../dispatcher/actions";
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Allows the user to disable the Event Index.
|
* Allows the user to disable the Event Index.
|
||||||
|
@ -47,7 +48,7 @@ export default class DisableEventIndexDialog extends React.Component {
|
||||||
await SettingsStore.setValue('enableEventIndexing', null, SettingLevel.DEVICE, false);
|
await SettingsStore.setValue('enableEventIndexing', null, SettingLevel.DEVICE, false);
|
||||||
await EventIndexPeg.deleteEventIndex();
|
await EventIndexPeg.deleteEventIndex();
|
||||||
this.props.onFinished();
|
this.props.onFinished();
|
||||||
dis.dispatch({ action: 'view_user_settings' });
|
dis.fire(Action.ViewUserSettings);
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
|
|
@ -284,8 +284,10 @@ export default class CreateKeyBackupDialog extends React.PureComponent {
|
||||||
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
|
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
|
||||||
|
|
||||||
let matchText;
|
let matchText;
|
||||||
|
let changeText;
|
||||||
if (this.state.passPhraseConfirm === this.state.passPhrase) {
|
if (this.state.passPhraseConfirm === this.state.passPhrase) {
|
||||||
matchText = _t("That matches!");
|
matchText = _t("That matches!");
|
||||||
|
changeText = _t("Use a different passphrase?");
|
||||||
} else if (!this.state.passPhrase.startsWith(this.state.passPhraseConfirm)) {
|
} else if (!this.state.passPhrase.startsWith(this.state.passPhraseConfirm)) {
|
||||||
// only tell them they're wrong if they've actually gone wrong.
|
// only tell them they're wrong if they've actually gone wrong.
|
||||||
// Security concious readers will note that if you left riot-web unattended
|
// Security concious readers will note that if you left riot-web unattended
|
||||||
|
@ -295,6 +297,7 @@ export default class CreateKeyBackupDialog extends React.PureComponent {
|
||||||
// Note that not having typed anything at all will not hit this clause and
|
// Note that not having typed anything at all will not hit this clause and
|
||||||
// fall through so empty box === no hint.
|
// fall through so empty box === no hint.
|
||||||
matchText = _t("That doesn't match.");
|
matchText = _t("That doesn't match.");
|
||||||
|
changeText = _t("Go back to set it again.");
|
||||||
}
|
}
|
||||||
|
|
||||||
let passPhraseMatch = null;
|
let passPhraseMatch = null;
|
||||||
|
@ -303,7 +306,7 @@ export default class CreateKeyBackupDialog extends React.PureComponent {
|
||||||
<div>{matchText}</div>
|
<div>{matchText}</div>
|
||||||
<div>
|
<div>
|
||||||
<AccessibleButton element="span" className="mx_linkButton" onClick={this._onSetAgainClick}>
|
<AccessibleButton element="span" className="mx_linkButton" onClick={this._onSetAgainClick}>
|
||||||
{_t("Go back to set it again.")}
|
{changeText}
|
||||||
</AccessibleButton>
|
</AccessibleButton>
|
||||||
</div>
|
</div>
|
||||||
</div>;
|
</div>;
|
||||||
|
|
|
@ -19,9 +19,10 @@ import React from "react";
|
||||||
import PropTypes from "prop-types";
|
import PropTypes from "prop-types";
|
||||||
import * as sdk from "../../../../index";
|
import * as sdk from "../../../../index";
|
||||||
import {MatrixClientPeg} from '../../../../MatrixClientPeg';
|
import {MatrixClientPeg} from '../../../../MatrixClientPeg';
|
||||||
import dis from "../../../../dispatcher";
|
import dis from "../../../../dispatcher/dispatcher";
|
||||||
import { _t } from "../../../../languageHandler";
|
import { _t } from "../../../../languageHandler";
|
||||||
import Modal from "../../../../Modal";
|
import Modal from "../../../../Modal";
|
||||||
|
import {Action} from "../../../../dispatcher/actions";
|
||||||
|
|
||||||
export default class NewRecoveryMethodDialog extends React.PureComponent {
|
export default class NewRecoveryMethodDialog extends React.PureComponent {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
|
@ -36,7 +37,7 @@ export default class NewRecoveryMethodDialog extends React.PureComponent {
|
||||||
|
|
||||||
onGoToSettingsClick = () => {
|
onGoToSettingsClick = () => {
|
||||||
this.props.onFinished();
|
this.props.onFinished();
|
||||||
dis.dispatch({ action: 'view_user_settings' });
|
dis.fire(Action.ViewUserSettings);
|
||||||
}
|
}
|
||||||
|
|
||||||
onSetupClick = async () => {
|
onSetupClick = async () => {
|
||||||
|
|
|
@ -18,9 +18,10 @@ limitations under the License.
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import PropTypes from "prop-types";
|
import PropTypes from "prop-types";
|
||||||
import * as sdk from "../../../../index";
|
import * as sdk from "../../../../index";
|
||||||
import dis from "../../../../dispatcher";
|
import dis from "../../../../dispatcher/dispatcher";
|
||||||
import { _t } from "../../../../languageHandler";
|
import { _t } from "../../../../languageHandler";
|
||||||
import Modal from "../../../../Modal";
|
import Modal from "../../../../Modal";
|
||||||
|
import {Action} from "../../../../dispatcher/actions";
|
||||||
|
|
||||||
export default class RecoveryMethodRemovedDialog extends React.PureComponent {
|
export default class RecoveryMethodRemovedDialog extends React.PureComponent {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
|
@ -29,7 +30,7 @@ export default class RecoveryMethodRemovedDialog extends React.PureComponent {
|
||||||
|
|
||||||
onGoToSettingsClick = () => {
|
onGoToSettingsClick = () => {
|
||||||
this.props.onFinished();
|
this.props.onFinished();
|
||||||
dis.dispatch({ action: 'view_user_settings' });
|
dis.fire(Action.ViewUserSettings);
|
||||||
}
|
}
|
||||||
|
|
||||||
onSetupClick = () => {
|
onSetupClick = () => {
|
||||||
|
|
|
@ -201,7 +201,8 @@ export default class CreateSecretStorageDialog extends React.PureComponent {
|
||||||
type: 'm.id.user',
|
type: 'm.id.user',
|
||||||
user: MatrixClientPeg.get().getUserId(),
|
user: MatrixClientPeg.get().getUserId(),
|
||||||
},
|
},
|
||||||
// https://github.com/matrix-org/synapse/issues/5665
|
// TODO: Remove `user` once servers support proper UIA
|
||||||
|
// See https://github.com/matrix-org/synapse/issues/5665
|
||||||
user: MatrixClientPeg.get().getUserId(),
|
user: MatrixClientPeg.get().getUserId(),
|
||||||
password: this.state.accountPassword,
|
password: this.state.accountPassword,
|
||||||
});
|
});
|
||||||
|
@ -538,8 +539,10 @@ export default class CreateSecretStorageDialog extends React.PureComponent {
|
||||||
const Field = sdk.getComponent('views.elements.Field');
|
const Field = sdk.getComponent('views.elements.Field');
|
||||||
|
|
||||||
let matchText;
|
let matchText;
|
||||||
|
let changeText;
|
||||||
if (this.state.passPhraseConfirm === this.state.passPhrase) {
|
if (this.state.passPhraseConfirm === this.state.passPhrase) {
|
||||||
matchText = _t("That matches!");
|
matchText = _t("That matches!");
|
||||||
|
changeText = _t("Use a different passphrase?");
|
||||||
} else if (!this.state.passPhrase.startsWith(this.state.passPhraseConfirm)) {
|
} else if (!this.state.passPhrase.startsWith(this.state.passPhraseConfirm)) {
|
||||||
// only tell them they're wrong if they've actually gone wrong.
|
// only tell them they're wrong if they've actually gone wrong.
|
||||||
// Security concious readers will note that if you left riot-web unattended
|
// Security concious readers will note that if you left riot-web unattended
|
||||||
|
@ -549,6 +552,7 @@ export default class CreateSecretStorageDialog extends React.PureComponent {
|
||||||
// Note that not having typed anything at all will not hit this clause and
|
// Note that not having typed anything at all will not hit this clause and
|
||||||
// fall through so empty box === no hint.
|
// fall through so empty box === no hint.
|
||||||
matchText = _t("That doesn't match.");
|
matchText = _t("That doesn't match.");
|
||||||
|
changeText = _t("Go back to set it again.");
|
||||||
}
|
}
|
||||||
|
|
||||||
let passPhraseMatch = null;
|
let passPhraseMatch = null;
|
||||||
|
@ -557,7 +561,7 @@ export default class CreateSecretStorageDialog extends React.PureComponent {
|
||||||
<div>{matchText}</div>
|
<div>{matchText}</div>
|
||||||
<div>
|
<div>
|
||||||
<AccessibleButton element="span" className="mx_linkButton" onClick={this._onSetAgainClick}>
|
<AccessibleButton element="span" className="mx_linkButton" onClick={this._onSetAgainClick}>
|
||||||
{_t("Go back to set it again.")}
|
{changeText}
|
||||||
</AccessibleButton>
|
</AccessibleButton>
|
||||||
</div>
|
</div>
|
||||||
</div>;
|
</div>;
|
||||||
|
|
|
@ -69,7 +69,7 @@ export default class EmojiProvider extends AutocompleteProvider {
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super(EMOJI_REGEX);
|
super(EMOJI_REGEX);
|
||||||
this.matcher = new QueryMatcher(EMOJI_SHORTNAMES, {
|
this.matcher = new QueryMatcher<IEmojiShort>(EMOJI_SHORTNAMES, {
|
||||||
keys: ['emoji.emoticon', 'shortname'],
|
keys: ['emoji.emoticon', 'shortname'],
|
||||||
funcs: [
|
funcs: [
|
||||||
(o) => o.emoji.shortcodes.length > 1 ? o.emoji.shortcodes.slice(1).map(s => `:${s}:`).join(" ") : "", // aliases
|
(o) => o.emoji.shortcodes.length > 1 ? o.emoji.shortcodes.slice(1).map(s => `:${s}:`).join(" ") : "", // aliases
|
||||||
|
|
|
@ -45,7 +45,7 @@ interface IOptions<T extends {}> {
|
||||||
* @param {function[]} options.funcs List of functions that when called with the
|
* @param {function[]} options.funcs List of functions that when called with the
|
||||||
* object as an arg will return a string to use as an index
|
* object as an arg will return a string to use as an index
|
||||||
*/
|
*/
|
||||||
export default class QueryMatcher<T> {
|
export default class QueryMatcher<T extends Object> {
|
||||||
private _options: IOptions<T>;
|
private _options: IOptions<T>;
|
||||||
private _keys: IOptions<T>["keys"];
|
private _keys: IOptions<T>["keys"];
|
||||||
private _funcs: Required<IOptions<T>["funcs"]>;
|
private _funcs: Required<IOptions<T>["funcs"]>;
|
||||||
|
@ -75,7 +75,11 @@ export default class QueryMatcher<T> {
|
||||||
this._items = new Map();
|
this._items = new Map();
|
||||||
|
|
||||||
for (const object of objects) {
|
for (const object of objects) {
|
||||||
const keyValues = _at(object, this._keys);
|
// Need to use unsafe coerce here because the objects can have any
|
||||||
|
// type for their values. We assume that those values who's keys have
|
||||||
|
// been specified will be string. Also, we cannot infer all the
|
||||||
|
// types of the keys of the objects at compile.
|
||||||
|
const keyValues = _at<string>(<any>object, this._keys);
|
||||||
|
|
||||||
for (const f of this._funcs) {
|
for (const f of this._funcs) {
|
||||||
keyValues.push(f(object));
|
keyValues.push(f(object));
|
||||||
|
|
|
@ -18,7 +18,7 @@ import React from 'react';
|
||||||
import CustomRoomTagStore from '../../stores/CustomRoomTagStore';
|
import CustomRoomTagStore from '../../stores/CustomRoomTagStore';
|
||||||
import AutoHideScrollbar from './AutoHideScrollbar';
|
import AutoHideScrollbar from './AutoHideScrollbar';
|
||||||
import * as sdk from '../../index';
|
import * as sdk from '../../index';
|
||||||
import dis from '../../dispatcher';
|
import dis from '../../dispatcher/dispatcher';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import * as FormattingUtils from '../../utils/FormattingUtils';
|
import * as FormattingUtils from '../../utils/FormattingUtils';
|
||||||
|
|
||||||
|
|
|
@ -23,7 +23,7 @@ import PropTypes from 'prop-types';
|
||||||
import request from 'browser-request';
|
import request from 'browser-request';
|
||||||
import { _t } from '../../languageHandler';
|
import { _t } from '../../languageHandler';
|
||||||
import sanitizeHtml from 'sanitize-html';
|
import sanitizeHtml from 'sanitize-html';
|
||||||
import dis from '../../dispatcher';
|
import dis from '../../dispatcher/dispatcher';
|
||||||
import {MatrixClientPeg} from '../../MatrixClientPeg';
|
import {MatrixClientPeg} from '../../MatrixClientPeg';
|
||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
import MatrixClientContext from "../../contexts/MatrixClientContext";
|
import MatrixClientContext from "../../contexts/MatrixClientContext";
|
||||||
|
|
|
@ -21,7 +21,7 @@ import createReactClass from 'create-react-class';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import {MatrixClientPeg} from '../../MatrixClientPeg';
|
import {MatrixClientPeg} from '../../MatrixClientPeg';
|
||||||
import * as sdk from '../../index';
|
import * as sdk from '../../index';
|
||||||
import dis from '../../dispatcher';
|
import dis from '../../dispatcher/dispatcher';
|
||||||
import { getHostingLink } from '../../utils/HostingLink';
|
import { getHostingLink } from '../../utils/HostingLink';
|
||||||
import { sanitizedHtmlNode } from '../../HtmlUtils';
|
import { sanitizedHtmlNode } from '../../HtmlUtils';
|
||||||
import { _t, _td } from '../../languageHandler';
|
import { _t, _td } from '../../languageHandler';
|
||||||
|
@ -92,7 +92,7 @@ const CategoryRoomList = createReactClass({
|
||||||
Modal.createTrackedDialog('Add Rooms to Group Summary', '', AddressPickerDialog, {
|
Modal.createTrackedDialog('Add Rooms to Group Summary', '', AddressPickerDialog, {
|
||||||
title: _t('Add rooms to the community summary'),
|
title: _t('Add rooms to the community summary'),
|
||||||
description: _t("Which rooms would you like to add to this summary?"),
|
description: _t("Which rooms would you like to add to this summary?"),
|
||||||
placeholder: _t("Room name or alias"),
|
placeholder: _t("Room name or address"),
|
||||||
button: _t("Add to summary"),
|
button: _t("Add to summary"),
|
||||||
pickerType: 'room',
|
pickerType: 'room',
|
||||||
validAddressTypes: ['mx-room-id'],
|
validAddressTypes: ['mx-room-id'],
|
||||||
|
|
|
@ -21,7 +21,7 @@ import { getHomePageUrl } from "../../utils/pages";
|
||||||
import { _t } from "../../languageHandler";
|
import { _t } from "../../languageHandler";
|
||||||
import SdkConfig from "../../SdkConfig";
|
import SdkConfig from "../../SdkConfig";
|
||||||
import * as sdk from "../../index";
|
import * as sdk from "../../index";
|
||||||
import dis from "../../dispatcher";
|
import dis from "../../dispatcher/dispatcher";
|
||||||
|
|
||||||
const onClickSendDm = () => dis.dispatch({action: 'view_create_chat'});
|
const onClickSendDm = () => dis.dispatch({action: 'view_create_chat'});
|
||||||
const onClickExplore = () => dis.dispatch({action: 'view_room_directory'});
|
const onClickExplore = () => dis.dispatch({action: 'view_room_directory'});
|
||||||
|
|
|
@ -21,11 +21,12 @@ import PropTypes from 'prop-types';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { Key } from '../../Keyboard';
|
import { Key } from '../../Keyboard';
|
||||||
import * as sdk from '../../index';
|
import * as sdk from '../../index';
|
||||||
import dis from '../../dispatcher';
|
import dis from '../../dispatcher/dispatcher';
|
||||||
import * as VectorConferenceHandler from '../../VectorConferenceHandler';
|
import * as VectorConferenceHandler from '../../VectorConferenceHandler';
|
||||||
import SettingsStore from '../../settings/SettingsStore';
|
import SettingsStore from '../../settings/SettingsStore';
|
||||||
import {_t} from "../../languageHandler";
|
import {_t} from "../../languageHandler";
|
||||||
import Analytics from "../../Analytics";
|
import Analytics from "../../Analytics";
|
||||||
|
import RoomList2 from "../views/rooms/RoomList2";
|
||||||
|
|
||||||
|
|
||||||
const LeftPanel = createReactClass({
|
const LeftPanel = createReactClass({
|
||||||
|
@ -273,6 +274,29 @@ const LeftPanel = createReactClass({
|
||||||
breadcrumbs = (<RoomBreadcrumbs collapsed={this.props.collapsed} />);
|
breadcrumbs = (<RoomBreadcrumbs collapsed={this.props.collapsed} />);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let roomList = null;
|
||||||
|
if (SettingsStore.isFeatureEnabled("feature_new_room_list")) {
|
||||||
|
roomList = <RoomList2
|
||||||
|
onKeyDown={this._onKeyDown}
|
||||||
|
resizeNotifier={this.props.resizeNotifier}
|
||||||
|
collapsed={this.props.collapsed}
|
||||||
|
searchFilter={this.state.searchFilter}
|
||||||
|
ref={this.collectRoomList}
|
||||||
|
onFocus={this._onFocus}
|
||||||
|
onBlur={this._onBlur}
|
||||||
|
/>;
|
||||||
|
} else {
|
||||||
|
roomList = <RoomList
|
||||||
|
onKeyDown={this._onKeyDown}
|
||||||
|
onFocus={this._onFocus}
|
||||||
|
onBlur={this._onBlur}
|
||||||
|
ref={this.collectRoomList}
|
||||||
|
resizeNotifier={this.props.resizeNotifier}
|
||||||
|
collapsed={this.props.collapsed}
|
||||||
|
searchFilter={this.state.searchFilter}
|
||||||
|
ConferenceHandler={VectorConferenceHandler} />;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={containerClasses}>
|
<div className={containerClasses}>
|
||||||
{ tagPanelContainer }
|
{ tagPanelContainer }
|
||||||
|
@ -284,15 +308,7 @@ const LeftPanel = createReactClass({
|
||||||
{ exploreButton }
|
{ exploreButton }
|
||||||
{ searchBox }
|
{ searchBox }
|
||||||
</div>
|
</div>
|
||||||
<RoomList
|
{roomList}
|
||||||
onKeyDown={this._onKeyDown}
|
|
||||||
onFocus={this._onFocus}
|
|
||||||
onBlur={this._onBlur}
|
|
||||||
ref={this.collectRoomList}
|
|
||||||
resizeNotifier={this.props.resizeNotifier}
|
|
||||||
collapsed={this.props.collapsed}
|
|
||||||
searchFilter={this.state.searchFilter}
|
|
||||||
ConferenceHandler={VectorConferenceHandler} />
|
|
||||||
</aside>
|
</aside>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -27,11 +27,10 @@ import PageTypes from '../../PageTypes';
|
||||||
import CallMediaHandler from '../../CallMediaHandler';
|
import CallMediaHandler from '../../CallMediaHandler';
|
||||||
import { fixupColorFonts } from '../../utils/FontManager';
|
import { fixupColorFonts } from '../../utils/FontManager';
|
||||||
import * as sdk from '../../index';
|
import * as sdk from '../../index';
|
||||||
import dis from '../../dispatcher';
|
import dis from '../../dispatcher/dispatcher';
|
||||||
import sessionStore from '../../stores/SessionStore';
|
import sessionStore from '../../stores/SessionStore';
|
||||||
import {MatrixClientPeg, MatrixClientCreds} from '../../MatrixClientPeg';
|
import {MatrixClientPeg, IMatrixClientCreds} from '../../MatrixClientPeg';
|
||||||
import SettingsStore from "../../settings/SettingsStore";
|
import SettingsStore from "../../settings/SettingsStore";
|
||||||
import RoomListStore from "../../stores/RoomListStore";
|
|
||||||
|
|
||||||
import TagOrderActions from '../../actions/TagOrderActions';
|
import TagOrderActions from '../../actions/TagOrderActions';
|
||||||
import RoomListActions from '../../actions/RoomListActions';
|
import RoomListActions from '../../actions/RoomListActions';
|
||||||
|
@ -42,6 +41,17 @@ import * as KeyboardShortcuts from "../../accessibility/KeyboardShortcuts";
|
||||||
import HomePage from "./HomePage";
|
import HomePage from "./HomePage";
|
||||||
import ResizeNotifier from "../../utils/ResizeNotifier";
|
import ResizeNotifier from "../../utils/ResizeNotifier";
|
||||||
import PlatformPeg from "../../PlatformPeg";
|
import PlatformPeg from "../../PlatformPeg";
|
||||||
|
import { RoomListStoreTempProxy } from "../../stores/room-list/RoomListStoreTempProxy";
|
||||||
|
import { DefaultTagID } from "../../stores/room-list/models";
|
||||||
|
import {
|
||||||
|
showToast as showSetPasswordToast,
|
||||||
|
hideToast as hideSetPasswordToast
|
||||||
|
} from "../../toasts/SetPasswordToast";
|
||||||
|
import {
|
||||||
|
showToast as showServerLimitToast,
|
||||||
|
hideToast as hideServerLimitToast
|
||||||
|
} from "../../toasts/ServerLimitToast";
|
||||||
|
|
||||||
// We need to fetch each pinned message individually (if we don't already have it)
|
// We need to fetch each pinned message individually (if we don't already have it)
|
||||||
// so each pinned message may trigger a request. Limit the number per room for sanity.
|
// so each pinned message may trigger a request. Limit the number per room for sanity.
|
||||||
// NB. this is just for server notices rather than pinned messages in general.
|
// NB. this is just for server notices rather than pinned messages in general.
|
||||||
|
@ -56,7 +66,7 @@ function canElementReceiveInput(el) {
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
matrixClient: MatrixClient;
|
matrixClient: MatrixClient;
|
||||||
onRegistered: (credentials: MatrixClientCreds) => Promise<MatrixClient>;
|
onRegistered: (credentials: IMatrixClientCreds) => Promise<MatrixClient>;
|
||||||
viaServers?: string[];
|
viaServers?: string[];
|
||||||
hideToSRUsers: boolean;
|
hideToSRUsers: boolean;
|
||||||
resizeNotifier: ResizeNotifier;
|
resizeNotifier: ResizeNotifier;
|
||||||
|
@ -64,10 +74,6 @@ interface IProps {
|
||||||
initialEventPixelOffset: number;
|
initialEventPixelOffset: number;
|
||||||
leftDisabled: boolean;
|
leftDisabled: boolean;
|
||||||
rightDisabled: boolean;
|
rightDisabled: boolean;
|
||||||
showCookieBar: boolean;
|
|
||||||
hasNewVersion: boolean;
|
|
||||||
userHasGeneratedPassword: boolean;
|
|
||||||
showNotifierToolbar: boolean;
|
|
||||||
page_type: string;
|
page_type: string;
|
||||||
autoJoin: boolean;
|
autoJoin: boolean;
|
||||||
thirdPartyInvite?: object;
|
thirdPartyInvite?: object;
|
||||||
|
@ -75,7 +81,6 @@ interface IProps {
|
||||||
currentRoomId: string;
|
currentRoomId: string;
|
||||||
ConferenceHandler?: object;
|
ConferenceHandler?: object;
|
||||||
collapseLhs: boolean;
|
collapseLhs: boolean;
|
||||||
checkingForUpdate: boolean;
|
|
||||||
config: {
|
config: {
|
||||||
piwik: {
|
piwik: {
|
||||||
policyUrl: string;
|
policyUrl: string;
|
||||||
|
@ -85,10 +90,8 @@ interface IProps {
|
||||||
currentUserId?: string;
|
currentUserId?: string;
|
||||||
currentGroupId?: string;
|
currentGroupId?: string;
|
||||||
currentGroupIsNew?: boolean;
|
currentGroupIsNew?: boolean;
|
||||||
version?: string;
|
|
||||||
newVersion?: string;
|
|
||||||
newVersionReleaseNotes?: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IState {
|
interface IState {
|
||||||
mouseDown?: {
|
mouseDown?: {
|
||||||
x: number;
|
x: number;
|
||||||
|
@ -96,8 +99,6 @@ interface IState {
|
||||||
};
|
};
|
||||||
syncErrorData: any;
|
syncErrorData: any;
|
||||||
useCompactLayout: boolean;
|
useCompactLayout: boolean;
|
||||||
serverNoticeEvents: MatrixEvent[];
|
|
||||||
userHasGeneratedPassword: boolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -140,11 +141,8 @@ class LoggedInView extends React.PureComponent<IProps, IState> {
|
||||||
this.state = {
|
this.state = {
|
||||||
mouseDown: undefined,
|
mouseDown: undefined,
|
||||||
syncErrorData: undefined,
|
syncErrorData: undefined,
|
||||||
userHasGeneratedPassword: false,
|
|
||||||
// use compact timeline view
|
// use compact timeline view
|
||||||
useCompactLayout: SettingsStore.getValue('useCompactLayout'),
|
useCompactLayout: SettingsStore.getValue('useCompactLayout'),
|
||||||
// any currently active server notice events
|
|
||||||
serverNoticeEvents: [],
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// stash the MatrixClient in case we log out before we are unmounted
|
// stash the MatrixClient in case we log out before we are unmounted
|
||||||
|
@ -178,18 +176,6 @@ class LoggedInView extends React.PureComponent<IProps, IState> {
|
||||||
this._loadResizerPreferences();
|
this._loadResizerPreferences();
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate(prevProps, prevState) {
|
|
||||||
// attempt to guess when a banner was opened or closed
|
|
||||||
if (
|
|
||||||
(prevProps.showCookieBar !== this.props.showCookieBar) ||
|
|
||||||
(prevProps.hasNewVersion !== this.props.hasNewVersion) ||
|
|
||||||
(prevState.userHasGeneratedPassword !== this.state.userHasGeneratedPassword) ||
|
|
||||||
(prevProps.showNotifierToolbar !== this.props.showNotifierToolbar)
|
|
||||||
) {
|
|
||||||
this.props.resizeNotifier.notifyBannersChanged();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
document.removeEventListener('keydown', this._onNativeKeyDown, false);
|
document.removeEventListener('keydown', this._onNativeKeyDown, false);
|
||||||
this._matrixClient.removeListener("accountData", this.onAccountData);
|
this._matrixClient.removeListener("accountData", this.onAccountData);
|
||||||
|
@ -219,9 +205,11 @@ class LoggedInView extends React.PureComponent<IProps, IState> {
|
||||||
};
|
};
|
||||||
|
|
||||||
_setStateFromSessionStore = () => {
|
_setStateFromSessionStore = () => {
|
||||||
this.setState({
|
if (this._sessionStore.getCachedPassword()) {
|
||||||
userHasGeneratedPassword: Boolean(this._sessionStore.getCachedPassword()),
|
showSetPasswordToast();
|
||||||
});
|
} else {
|
||||||
|
hideSetPasswordToast();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
_createResizer() {
|
_createResizer() {
|
||||||
|
@ -293,22 +281,37 @@ class LoggedInView extends React.PureComponent<IProps, IState> {
|
||||||
|
|
||||||
if (oldSyncState === 'PREPARED' && syncState === 'SYNCING') {
|
if (oldSyncState === 'PREPARED' && syncState === 'SYNCING') {
|
||||||
this._updateServerNoticeEvents();
|
this._updateServerNoticeEvents();
|
||||||
|
} else {
|
||||||
|
this._calculateServerLimitToast(data);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
onRoomStateEvents = (ev, state) => {
|
onRoomStateEvents = (ev, state) => {
|
||||||
const roomLists = RoomListStore.getRoomLists();
|
const roomLists = RoomListStoreTempProxy.getRoomLists();
|
||||||
if (roomLists['m.server_notice'] && roomLists['m.server_notice'].some(r => r.roomId === ev.getRoomId())) {
|
if (roomLists[DefaultTagID.ServerNotice] && roomLists[DefaultTagID.ServerNotice].some(r => r.roomId === ev.getRoomId())) {
|
||||||
this._updateServerNoticeEvents();
|
this._updateServerNoticeEvents();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
_updateServerNoticeEvents = async () => {
|
_calculateServerLimitToast(syncErrorData, usageLimitEventContent?) {
|
||||||
const roomLists = RoomListStore.getRoomLists();
|
const error = syncErrorData && syncErrorData.error && syncErrorData.error.errcode === "M_RESOURCE_LIMIT_EXCEEDED";
|
||||||
if (!roomLists['m.server_notice']) return [];
|
if (error) {
|
||||||
|
usageLimitEventContent = syncErrorData.error.data;
|
||||||
|
}
|
||||||
|
|
||||||
const pinnedEvents = [];
|
if (usageLimitEventContent) {
|
||||||
for (const room of roomLists['m.server_notice']) {
|
showServerLimitToast(usageLimitEventContent.limit_type, usageLimitEventContent.admin_contact, error);
|
||||||
|
} else {
|
||||||
|
hideServerLimitToast();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_updateServerNoticeEvents = async () => {
|
||||||
|
const roomLists = RoomListStoreTempProxy.getRoomLists();
|
||||||
|
if (!roomLists[DefaultTagID.ServerNotice]) return [];
|
||||||
|
|
||||||
|
const events = [];
|
||||||
|
for (const room of roomLists[DefaultTagID.ServerNotice]) {
|
||||||
const pinStateEvent = room.currentState.getStateEvents("m.room.pinned_events", "");
|
const pinStateEvent = room.currentState.getStateEvents("m.room.pinned_events", "");
|
||||||
|
|
||||||
if (!pinStateEvent || !pinStateEvent.getContent().pinned) continue;
|
if (!pinStateEvent || !pinStateEvent.getContent().pinned) continue;
|
||||||
|
@ -317,12 +320,18 @@ class LoggedInView extends React.PureComponent<IProps, IState> {
|
||||||
for (const eventId of pinnedEventIds) {
|
for (const eventId of pinnedEventIds) {
|
||||||
const timeline = await this._matrixClient.getEventTimeline(room.getUnfilteredTimelineSet(), eventId, 0);
|
const timeline = await this._matrixClient.getEventTimeline(room.getUnfilteredTimelineSet(), eventId, 0);
|
||||||
const event = timeline.getEvents().find(ev => ev.getId() === eventId);
|
const event = timeline.getEvents().find(ev => ev.getId() === eventId);
|
||||||
if (event) pinnedEvents.push(event);
|
if (event) events.push(event);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.setState({
|
|
||||||
serverNoticeEvents: pinnedEvents,
|
const usageLimitEvent = events.find((e) => {
|
||||||
|
return (
|
||||||
|
e && e.getType() === 'm.room.message' &&
|
||||||
|
e.getContent()['server_notice_type'] === 'm.server_notice.usage_limit_reached'
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this._calculateServerLimitToast(this.state.syncErrorData, usageLimitEvent && usageLimitEvent.getContent());
|
||||||
};
|
};
|
||||||
|
|
||||||
_onPaste = (ev) => {
|
_onPaste = (ev) => {
|
||||||
|
@ -598,12 +607,6 @@ class LoggedInView extends React.PureComponent<IProps, IState> {
|
||||||
const GroupView = sdk.getComponent('structures.GroupView');
|
const GroupView = sdk.getComponent('structures.GroupView');
|
||||||
const MyGroups = sdk.getComponent('structures.MyGroups');
|
const MyGroups = sdk.getComponent('structures.MyGroups');
|
||||||
const ToastContainer = sdk.getComponent('structures.ToastContainer');
|
const ToastContainer = sdk.getComponent('structures.ToastContainer');
|
||||||
const MatrixToolbar = sdk.getComponent('globals.MatrixToolbar');
|
|
||||||
const CookieBar = sdk.getComponent('globals.CookieBar');
|
|
||||||
const NewVersionBar = sdk.getComponent('globals.NewVersionBar');
|
|
||||||
const UpdateCheckBar = sdk.getComponent('globals.UpdateCheckBar');
|
|
||||||
const PasswordNagBar = sdk.getComponent('globals.PasswordNagBar');
|
|
||||||
const ServerLimitBar = sdk.getComponent('globals.ServerLimitBar');
|
|
||||||
|
|
||||||
let pageElement;
|
let pageElement;
|
||||||
|
|
||||||
|
@ -647,46 +650,7 @@ class LoggedInView extends React.PureComponent<IProps, IState> {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
const usageLimitEvent = this.state.serverNoticeEvents.find((e) => {
|
|
||||||
return (
|
|
||||||
e && e.getType() === 'm.room.message' &&
|
|
||||||
e.getContent()['server_notice_type'] === 'm.server_notice.usage_limit_reached'
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
let topBar;
|
|
||||||
if (this.state.syncErrorData && this.state.syncErrorData.error.errcode === 'M_RESOURCE_LIMIT_EXCEEDED') {
|
|
||||||
topBar = <ServerLimitBar kind='hard'
|
|
||||||
adminContact={this.state.syncErrorData.error.data.admin_contact}
|
|
||||||
limitType={this.state.syncErrorData.error.data.limit_type}
|
|
||||||
/>;
|
|
||||||
} else if (usageLimitEvent) {
|
|
||||||
topBar = <ServerLimitBar kind='soft'
|
|
||||||
adminContact={usageLimitEvent.getContent().admin_contact}
|
|
||||||
limitType={usageLimitEvent.getContent().limit_type}
|
|
||||||
/>;
|
|
||||||
} else if (this.props.showCookieBar &&
|
|
||||||
this.props.config.piwik &&
|
|
||||||
navigator.doNotTrack !== "1"
|
|
||||||
) {
|
|
||||||
const policyUrl = this.props.config.piwik.policyUrl || null;
|
|
||||||
topBar = <CookieBar policyUrl={policyUrl} />;
|
|
||||||
} else if (this.props.hasNewVersion) {
|
|
||||||
topBar = <NewVersionBar version={this.props.version} newVersion={this.props.newVersion}
|
|
||||||
releaseNotes={this.props.newVersionReleaseNotes}
|
|
||||||
/>;
|
|
||||||
} else if (this.props.checkingForUpdate) {
|
|
||||||
topBar = <UpdateCheckBar {...this.props.checkingForUpdate} />;
|
|
||||||
} else if (this.state.userHasGeneratedPassword) {
|
|
||||||
topBar = <PasswordNagBar />;
|
|
||||||
} else if (this.props.showNotifierToolbar) {
|
|
||||||
topBar = <MatrixToolbar />;
|
|
||||||
}
|
|
||||||
|
|
||||||
let bodyClasses = 'mx_MatrixChat';
|
let bodyClasses = 'mx_MatrixChat';
|
||||||
if (topBar) {
|
|
||||||
bodyClasses += ' mx_MatrixChat_toolbarShowing';
|
|
||||||
}
|
|
||||||
if (this.state.useCompactLayout) {
|
if (this.state.useCompactLayout) {
|
||||||
bodyClasses += ' mx_MatrixChat_useCompactLayout';
|
bodyClasses += ' mx_MatrixChat_useCompactLayout';
|
||||||
}
|
}
|
||||||
|
@ -701,7 +665,6 @@ class LoggedInView extends React.PureComponent<IProps, IState> {
|
||||||
onMouseDown={this._onMouseDown}
|
onMouseDown={this._onMouseDown}
|
||||||
onMouseUp={this._onMouseUp}
|
onMouseUp={this._onMouseUp}
|
||||||
>
|
>
|
||||||
{ topBar }
|
|
||||||
<ToastContainer />
|
<ToastContainer />
|
||||||
<DragDropContext onDragEnd={this._onDragEnd}>
|
<DragDropContext onDragEnd={this._onDragEnd}>
|
||||||
<div ref={this._resizeContainer} className={bodyClasses}>
|
<div ref={this._resizeContainer} className={bodyClasses}>
|
||||||
|
|
|
@ -22,7 +22,6 @@ import {InvalidStoreError} from "matrix-js-sdk/src/errors";
|
||||||
import { RoomMember } from "matrix-js-sdk/src/models/room-member";
|
import { RoomMember } from "matrix-js-sdk/src/models/room-member";
|
||||||
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
|
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
|
||||||
import { isCryptoAvailable } from 'matrix-js-sdk/src/crypto';
|
import { isCryptoAvailable } from 'matrix-js-sdk/src/crypto';
|
||||||
|
|
||||||
// focus-visible is a Polyfill for the :focus-visible CSS pseudo-attribute used by _AccessibleButton.scss
|
// focus-visible is a Polyfill for the :focus-visible CSS pseudo-attribute used by _AccessibleButton.scss
|
||||||
import 'focus-visible';
|
import 'focus-visible';
|
||||||
// what-input helps improve keyboard accessibility
|
// what-input helps improve keyboard accessibility
|
||||||
|
@ -34,13 +33,13 @@ import {MatrixClientPeg} from "../../MatrixClientPeg";
|
||||||
import PlatformPeg from "../../PlatformPeg";
|
import PlatformPeg from "../../PlatformPeg";
|
||||||
import SdkConfig from "../../SdkConfig";
|
import SdkConfig from "../../SdkConfig";
|
||||||
import * as RoomListSorter from "../../RoomListSorter";
|
import * as RoomListSorter from "../../RoomListSorter";
|
||||||
import dis from "../../dispatcher";
|
import dis from "../../dispatcher/dispatcher";
|
||||||
import Notifier from '../../Notifier';
|
import Notifier from '../../Notifier';
|
||||||
|
|
||||||
import Modal from "../../Modal";
|
import Modal from "../../Modal";
|
||||||
import Tinter from "../../Tinter";
|
import Tinter from "../../Tinter";
|
||||||
import * as sdk from '../../index';
|
import * as sdk from '../../index';
|
||||||
import { showStartChatInviteDialog, showRoomInviteDialog } from '../../RoomInvite';
|
import { showRoomInviteDialog, showStartChatInviteDialog } from '../../RoomInvite';
|
||||||
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';
|
||||||
|
@ -50,23 +49,29 @@ import PageTypes from '../../PageTypes';
|
||||||
import { getHomePageUrl } from '../../utils/pages';
|
import { getHomePageUrl } from '../../utils/pages';
|
||||||
|
|
||||||
import createRoom from "../../createRoom";
|
import createRoom from "../../createRoom";
|
||||||
import KeyRequestHandler from '../../KeyRequestHandler';
|
|
||||||
import { _t, getCurrentLanguage } from '../../languageHandler';
|
import { _t, getCurrentLanguage } from '../../languageHandler';
|
||||||
import SettingsStore, { SettingLevel } from "../../settings/SettingsStore";
|
import SettingsStore, { SettingLevel } from "../../settings/SettingsStore";
|
||||||
import ThemeController from "../../settings/controllers/ThemeController";
|
import ThemeController from "../../settings/controllers/ThemeController";
|
||||||
import { startAnyRegistrationFlow } from "../../Registration.js";
|
import { startAnyRegistrationFlow } from "../../Registration.js";
|
||||||
import { messageForSyncError } from '../../utils/ErrorUtils';
|
import { messageForSyncError } from '../../utils/ErrorUtils';
|
||||||
import ResizeNotifier from "../../utils/ResizeNotifier";
|
import ResizeNotifier from "../../utils/ResizeNotifier";
|
||||||
import { ValidatedServerConfig } from "../../utils/AutoDiscoveryUtils";
|
import AutoDiscoveryUtils, { ValidatedServerConfig } from "../../utils/AutoDiscoveryUtils";
|
||||||
import AutoDiscoveryUtils from "../../utils/AutoDiscoveryUtils";
|
|
||||||
import DMRoomMap from '../../utils/DMRoomMap';
|
import DMRoomMap from '../../utils/DMRoomMap';
|
||||||
import { countRoomsWithNotif } from '../../RoomNotifs';
|
import { countRoomsWithNotif } from '../../RoomNotifs';
|
||||||
import { ThemeWatcher } from "../../theme";
|
import ThemeWatcher from "../../settings/watchers/ThemeWatcher";
|
||||||
|
import { FontWatcher } from '../../settings/watchers/FontWatcher';
|
||||||
import { storeRoomAliasInCache } from '../../RoomAliasCache';
|
import { storeRoomAliasInCache } from '../../RoomAliasCache';
|
||||||
import { defer, IDeferred } from "../../utils/promise";
|
import { defer, IDeferred } from "../../utils/promise";
|
||||||
import ToastStore from "../../stores/ToastStore";
|
import ToastStore from "../../stores/ToastStore";
|
||||||
import * as StorageManager from "../../utils/StorageManager";
|
import * as StorageManager from "../../utils/StorageManager";
|
||||||
import type LoggedInViewType from "./LoggedInView";
|
import type LoggedInViewType from "./LoggedInView";
|
||||||
|
import { ViewUserPayload } from "../../dispatcher/payloads/ViewUserPayload";
|
||||||
|
import { Action } from "../../dispatcher/actions";
|
||||||
|
import {
|
||||||
|
showToast as showAnalyticsToast,
|
||||||
|
hideToast as hideAnalyticsToast
|
||||||
|
} from "../../toasts/AnalyticsToast";
|
||||||
|
import {showToast as showNotificationsToast} from "../../toasts/DesktopNotificationsToast";
|
||||||
|
|
||||||
/** constants for MatrixChat.state.view */
|
/** constants for MatrixChat.state.view */
|
||||||
export enum Views {
|
export enum Views {
|
||||||
|
@ -107,7 +112,7 @@ export enum Views {
|
||||||
// re-dispatched. NOTE: some actions are non-trivial and would require
|
// re-dispatched. NOTE: some actions are non-trivial and would require
|
||||||
// re-factoring to be included in this list in future.
|
// re-factoring to be included in this list in future.
|
||||||
const ONBOARDING_FLOW_STARTERS = [
|
const ONBOARDING_FLOW_STARTERS = [
|
||||||
'view_user_settings',
|
Action.ViewUserSettings,
|
||||||
'view_create_chat',
|
'view_create_chat',
|
||||||
'view_create_room',
|
'view_create_room',
|
||||||
'view_create_group',
|
'view_create_group',
|
||||||
|
@ -168,12 +173,6 @@ interface IState {
|
||||||
leftDisabled: boolean;
|
leftDisabled: boolean;
|
||||||
middleDisabled: boolean;
|
middleDisabled: boolean;
|
||||||
// the right panel's disabled state is tracked in its store.
|
// the right panel's disabled state is tracked in its store.
|
||||||
version?: string;
|
|
||||||
newVersion?: string;
|
|
||||||
hasNewVersion: boolean;
|
|
||||||
newVersionReleaseNotes?: string;
|
|
||||||
checkingForUpdate?: string; // updateCheckStatusEnum
|
|
||||||
showCookieBar: boolean;
|
|
||||||
// Parameters used in the registration dance with the IS
|
// Parameters used in the registration dance with the IS
|
||||||
register_client_secret?: string;
|
register_client_secret?: string;
|
||||||
register_session_id?: string;
|
register_session_id?: string;
|
||||||
|
@ -183,7 +182,6 @@ interface IState {
|
||||||
hideToSRUsers: boolean;
|
hideToSRUsers: boolean;
|
||||||
syncError?: Error;
|
syncError?: Error;
|
||||||
resizeNotifier: ResizeNotifier;
|
resizeNotifier: ResizeNotifier;
|
||||||
showNotifierToolbar: boolean;
|
|
||||||
serverConfig?: ValidatedServerConfig;
|
serverConfig?: ValidatedServerConfig;
|
||||||
ready: boolean;
|
ready: boolean;
|
||||||
thirdPartyInvite?: object;
|
thirdPartyInvite?: object;
|
||||||
|
@ -216,6 +214,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||||
private readonly loggedInView: React.RefObject<LoggedInViewType>;
|
private readonly loggedInView: React.RefObject<LoggedInViewType>;
|
||||||
private readonly dispatcherRef: any;
|
private readonly dispatcherRef: any;
|
||||||
private readonly themeWatcher: ThemeWatcher;
|
private readonly themeWatcher: ThemeWatcher;
|
||||||
|
private readonly fontWatcher: FontWatcher;
|
||||||
|
|
||||||
constructor(props, context) {
|
constructor(props, context) {
|
||||||
super(props, context);
|
super(props, context);
|
||||||
|
@ -226,17 +225,10 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||||
leftDisabled: false,
|
leftDisabled: false,
|
||||||
middleDisabled: false,
|
middleDisabled: false,
|
||||||
|
|
||||||
hasNewVersion: false,
|
|
||||||
newVersionReleaseNotes: null,
|
|
||||||
checkingForUpdate: null,
|
|
||||||
|
|
||||||
showCookieBar: false,
|
|
||||||
|
|
||||||
hideToSRUsers: false,
|
hideToSRUsers: false,
|
||||||
|
|
||||||
syncError: null, // If the current syncing status is ERROR, the error object, otherwise null.
|
syncError: null, // If the current syncing status is ERROR, the error object, otherwise null.
|
||||||
resizeNotifier: new ResizeNotifier(),
|
resizeNotifier: new ResizeNotifier(),
|
||||||
showNotifierToolbar: false,
|
|
||||||
ready: false,
|
ready: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -283,8 +275,11 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||||
this.accountPasswordTimer = null;
|
this.accountPasswordTimer = null;
|
||||||
|
|
||||||
this.dispatcherRef = dis.register(this.onAction);
|
this.dispatcherRef = dis.register(this.onAction);
|
||||||
|
|
||||||
this.themeWatcher = new ThemeWatcher();
|
this.themeWatcher = new ThemeWatcher();
|
||||||
|
this.fontWatcher = new FontWatcher();
|
||||||
this.themeWatcher.start();
|
this.themeWatcher.start();
|
||||||
|
this.fontWatcher.start();
|
||||||
|
|
||||||
this.focusComposer = false;
|
this.focusComposer = false;
|
||||||
|
|
||||||
|
@ -334,12 +329,6 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (SettingsStore.getValue("showCookieBar")) {
|
|
||||||
this.setState({
|
|
||||||
showCookieBar: true,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (SettingsStore.getValue("analyticsOptIn")) {
|
if (SettingsStore.getValue("analyticsOptIn")) {
|
||||||
Analytics.enable();
|
Analytics.enable();
|
||||||
}
|
}
|
||||||
|
@ -367,6 +356,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||||
Lifecycle.stopMatrixClient();
|
Lifecycle.stopMatrixClient();
|
||||||
dis.unregister(this.dispatcherRef);
|
dis.unregister(this.dispatcherRef);
|
||||||
this.themeWatcher.stop();
|
this.themeWatcher.stop();
|
||||||
|
this.fontWatcher.stop();
|
||||||
window.removeEventListener('resize', this.handleResize);
|
window.removeEventListener('resize', this.handleResize);
|
||||||
this.state.resizeNotifier.removeListener("middlePanelResized", this.dispatchTimelineResize);
|
this.state.resizeNotifier.removeListener("middlePanelResized", this.dispatchTimelineResize);
|
||||||
|
|
||||||
|
@ -613,7 +603,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||||
case 'view_indexed_room':
|
case 'view_indexed_room':
|
||||||
this.viewIndexedRoom(payload.roomIndex);
|
this.viewIndexedRoom(payload.roomIndex);
|
||||||
break;
|
break;
|
||||||
case 'view_user_settings': {
|
case Action.ViewUserSettings: {
|
||||||
const UserSettingsDialog = sdk.getComponent("dialogs.UserSettingsDialog");
|
const UserSettingsDialog = sdk.getComponent("dialogs.UserSettingsDialog");
|
||||||
Modal.createTrackedDialog('User settings', '', UserSettingsDialog, {},
|
Modal.createTrackedDialog('User settings', '', UserSettingsDialog, {},
|
||||||
/*className=*/null, /*isPriority=*/false, /*isStatic=*/true);
|
/*className=*/null, /*isPriority=*/false, /*isStatic=*/true);
|
||||||
|
@ -680,9 +670,6 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||||
dis.dispatch({action: 'view_my_groups'});
|
dis.dispatch({action: 'view_my_groups'});
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'notifier_enabled':
|
|
||||||
this.setState({showNotifierToolbar: Notifier.shouldShowToolbar()});
|
|
||||||
break;
|
|
||||||
case 'hide_left_panel':
|
case 'hide_left_panel':
|
||||||
this.setState({
|
this.setState({
|
||||||
collapseLhs: true,
|
collapseLhs: true,
|
||||||
|
@ -730,15 +717,6 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||||
case 'client_started':
|
case 'client_started':
|
||||||
this.onClientStarted();
|
this.onClientStarted();
|
||||||
break;
|
break;
|
||||||
case 'new_version':
|
|
||||||
this.onVersion(
|
|
||||||
payload.currentVersion, payload.newVersion,
|
|
||||||
payload.releaseNotes,
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
case 'check_updates':
|
|
||||||
this.setState({ checkingForUpdate: payload.value });
|
|
||||||
break;
|
|
||||||
case 'send_event':
|
case 'send_event':
|
||||||
this.onSendEvent(payload.room_id, payload.event);
|
this.onSendEvent(payload.room_id, payload.event);
|
||||||
break;
|
break;
|
||||||
|
@ -755,19 +733,13 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||||
case 'accept_cookies':
|
case 'accept_cookies':
|
||||||
SettingsStore.setValue("analyticsOptIn", null, SettingLevel.DEVICE, true);
|
SettingsStore.setValue("analyticsOptIn", null, SettingLevel.DEVICE, true);
|
||||||
SettingsStore.setValue("showCookieBar", null, SettingLevel.DEVICE, false);
|
SettingsStore.setValue("showCookieBar", null, SettingLevel.DEVICE, false);
|
||||||
|
hideAnalyticsToast();
|
||||||
this.setState({
|
|
||||||
showCookieBar: false,
|
|
||||||
});
|
|
||||||
Analytics.enable();
|
Analytics.enable();
|
||||||
break;
|
break;
|
||||||
case 'reject_cookies':
|
case 'reject_cookies':
|
||||||
SettingsStore.setValue("analyticsOptIn", null, SettingLevel.DEVICE, false);
|
SettingsStore.setValue("analyticsOptIn", null, SettingLevel.DEVICE, false);
|
||||||
SettingsStore.setValue("showCookieBar", null, SettingLevel.DEVICE, false);
|
SettingsStore.setValue("showCookieBar", null, SettingLevel.DEVICE, false);
|
||||||
|
hideAnalyticsToast();
|
||||||
this.setState({
|
|
||||||
showCookieBar: false,
|
|
||||||
});
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -926,9 +898,20 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private viewGroup(payload) {
|
private async viewGroup(payload) {
|
||||||
const groupId = payload.group_id;
|
const groupId = payload.group_id;
|
||||||
|
|
||||||
|
// Wait for the first sync to complete
|
||||||
|
if (!this.firstSyncComplete) {
|
||||||
|
if (!this.firstSyncPromise) {
|
||||||
|
console.warn('Cannot view a group before first sync. group_id:', groupId);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await this.firstSyncPromise.promise;
|
||||||
|
}
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
|
view: Views.LOGGED_IN,
|
||||||
currentGroupId: groupId,
|
currentGroupId: groupId,
|
||||||
currentGroupIsNew: payload.group_is_new,
|
currentGroupIsNew: payload.group_is_new,
|
||||||
});
|
});
|
||||||
|
@ -1245,6 +1228,10 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||||
}
|
}
|
||||||
|
|
||||||
StorageManager.tryPersistStorage();
|
StorageManager.tryPersistStorage();
|
||||||
|
|
||||||
|
if (SettingsStore.getValue("showCookieBar") && this.props.config.piwik && navigator.doNotTrack !== "1") {
|
||||||
|
showAnalyticsToast(this.props.config.piwik && this.props.config.piwik.policyUrl);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private showScreenAfterLogin() {
|
private showScreenAfterLogin() {
|
||||||
|
@ -1372,10 +1359,13 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||||
this.firstSyncComplete = true;
|
this.firstSyncComplete = true;
|
||||||
this.firstSyncPromise.resolve();
|
this.firstSyncPromise.resolve();
|
||||||
|
|
||||||
|
if (Notifier.shouldShowToolbar()) {
|
||||||
|
showNotificationsToast();
|
||||||
|
}
|
||||||
|
|
||||||
dis.dispatch({action: 'focus_composer'});
|
dis.dispatch({action: 'focus_composer'});
|
||||||
this.setState({
|
this.setState({
|
||||||
ready: true,
|
ready: true,
|
||||||
showNotifierToolbar: Notifier.shouldShowToolbar(),
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
cli.on('Call.incoming', function(call) {
|
cli.on('Call.incoming', function(call) {
|
||||||
|
@ -1454,16 +1444,6 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||||
cli.on("Session.logged_out", () => dft.stop());
|
cli.on("Session.logged_out", () => dft.stop());
|
||||||
cli.on("Event.decrypted", (e, err) => dft.eventDecrypted(e, err));
|
cli.on("Event.decrypted", (e, err) => dft.eventDecrypted(e, err));
|
||||||
|
|
||||||
// TODO: We can remove this once cross-signing is the only way.
|
|
||||||
// https://github.com/vector-im/riot-web/issues/11908
|
|
||||||
const krh = new KeyRequestHandler(cli);
|
|
||||||
cli.on("crypto.roomKeyRequest", (req) => {
|
|
||||||
krh.handleKeyRequest(req);
|
|
||||||
});
|
|
||||||
cli.on("crypto.roomKeyRequestCancellation", (req) => {
|
|
||||||
krh.handleKeyRequestCancellation(req);
|
|
||||||
});
|
|
||||||
|
|
||||||
cli.on("Room", (room) => {
|
cli.on("Room", (room) => {
|
||||||
if (MatrixClientPeg.get().isCryptoEnabled()) {
|
if (MatrixClientPeg.get().isCryptoEnabled()) {
|
||||||
const blacklistEnabled = SettingsStore.getValueAt(
|
const blacklistEnabled = SettingsStore.getValueAt(
|
||||||
|
@ -1553,7 +1533,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||||
icon: "verification",
|
icon: "verification",
|
||||||
props: {request},
|
props: {request},
|
||||||
component: sdk.getComponent("toasts.VerificationRequestToast"),
|
component: sdk.getComponent("toasts.VerificationRequestToast"),
|
||||||
priority: ToastStore.PRIORITY_REALTIME,
|
priority: 90,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -1621,9 +1601,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||||
action: 'view_create_room',
|
action: 'view_create_room',
|
||||||
});
|
});
|
||||||
} else if (screen === 'settings') {
|
} else if (screen === 'settings') {
|
||||||
dis.dispatch({
|
dis.fire(Action.ViewUserSettings);
|
||||||
action: 'view_user_settings',
|
|
||||||
});
|
|
||||||
} else if (screen === 'welcome') {
|
} else if (screen === 'welcome') {
|
||||||
dis.dispatch({
|
dis.dispatch({
|
||||||
action: 'view_welcome_page',
|
action: 'view_welcome_page',
|
||||||
|
@ -1755,8 +1733,8 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||||
|
|
||||||
const member = new RoomMember(null, userId);
|
const member = new RoomMember(null, userId);
|
||||||
if (!member) { return; }
|
if (!member) { return; }
|
||||||
dis.dispatch({
|
dis.dispatch<ViewUserPayload>({
|
||||||
action: 'view_user',
|
action: Action.ViewUser,
|
||||||
member: member,
|
member: member,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -1829,16 +1807,6 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||||
this.showScreen("settings");
|
this.showScreen("settings");
|
||||||
};
|
};
|
||||||
|
|
||||||
onVersion(current: string, latest: string, releaseNotes?: string) {
|
|
||||||
this.setState({
|
|
||||||
version: current,
|
|
||||||
newVersion: latest,
|
|
||||||
hasNewVersion: current !== latest,
|
|
||||||
newVersionReleaseNotes: releaseNotes,
|
|
||||||
checkingForUpdate: null,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
onSendEvent(roomId: string, event: MatrixEvent) {
|
onSendEvent(roomId: string, event: MatrixEvent) {
|
||||||
const cli = MatrixClientPeg.get();
|
const cli = MatrixClientPeg.get();
|
||||||
if (!cli) {
|
if (!cli) {
|
||||||
|
@ -2033,7 +2001,6 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||||
onCloseAllSettings={this.onCloseAllSettings}
|
onCloseAllSettings={this.onCloseAllSettings}
|
||||||
onRegistered={this.onRegistered}
|
onRegistered={this.onRegistered}
|
||||||
currentRoomId={this.state.currentRoomId}
|
currentRoomId={this.state.currentRoomId}
|
||||||
showCookieBar={this.state.showCookieBar}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -29,6 +29,7 @@ import SettingsStore from '../../settings/SettingsStore';
|
||||||
import {_t} from "../../languageHandler";
|
import {_t} from "../../languageHandler";
|
||||||
import {haveTileForEvent} from "../views/rooms/EventTile";
|
import {haveTileForEvent} from "../views/rooms/EventTile";
|
||||||
import {textForEvent} from "../../TextForEvent";
|
import {textForEvent} from "../../TextForEvent";
|
||||||
|
import IRCTimelineProfileResizer from "../views/elements/IRCTimelineProfileResizer";
|
||||||
|
|
||||||
const CONTINUATION_MAX_INTERVAL = 5 * 60 * 1000; // 5 minutes
|
const CONTINUATION_MAX_INTERVAL = 5 * 60 * 1000; // 5 minutes
|
||||||
const continuedTypes = ['m.sticker', 'm.room.message'];
|
const continuedTypes = ['m.sticker', 'm.room.message'];
|
||||||
|
@ -107,10 +108,14 @@ export default class MessagePanel extends React.Component {
|
||||||
|
|
||||||
// whether to show reactions for an event
|
// whether to show reactions for an event
|
||||||
showReactions: PropTypes.bool,
|
showReactions: PropTypes.bool,
|
||||||
|
|
||||||
|
// whether to use the irc layout
|
||||||
|
useIRCLayout: PropTypes.bool,
|
||||||
};
|
};
|
||||||
|
|
||||||
constructor() {
|
// Force props to be loaded for useIRCLayout
|
||||||
super();
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
// previous positions the read marker has been in, so we can
|
// previous positions the read marker has been in, so we can
|
||||||
|
@ -597,6 +602,7 @@ export default class MessagePanel extends React.Component {
|
||||||
isSelectedEvent={highlight}
|
isSelectedEvent={highlight}
|
||||||
getRelationsForEvent={this.props.getRelationsForEvent}
|
getRelationsForEvent={this.props.getRelationsForEvent}
|
||||||
showReactions={this.props.showReactions}
|
showReactions={this.props.showReactions}
|
||||||
|
useIRCLayout={this.props.useIRCLayout}
|
||||||
/>
|
/>
|
||||||
</TileErrorBoundary>
|
</TileErrorBoundary>
|
||||||
</li>,
|
</li>,
|
||||||
|
@ -792,6 +798,15 @@ export default class MessagePanel extends React.Component {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let ircResizer = null;
|
||||||
|
if (this.props.useIRCLayout) {
|
||||||
|
ircResizer = <IRCTimelineProfileResizer
|
||||||
|
minWidth={20}
|
||||||
|
maxWidth={600}
|
||||||
|
roomId={this.props.room ? this.props.roomroomId : null}
|
||||||
|
/>;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ErrorBoundary>
|
<ErrorBoundary>
|
||||||
<ScrollPanel
|
<ScrollPanel
|
||||||
|
@ -804,6 +819,7 @@ export default class MessagePanel extends React.Component {
|
||||||
style={style}
|
style={style}
|
||||||
stickyBottom={this.props.stickyBottom}
|
stickyBottom={this.props.stickyBottom}
|
||||||
resizeNotifier={this.props.resizeNotifier}
|
resizeNotifier={this.props.resizeNotifier}
|
||||||
|
fixedChildren={ircResizer}
|
||||||
>
|
>
|
||||||
{ topSpinner }
|
{ topSpinner }
|
||||||
{ this._getEventTiles() }
|
{ this._getEventTiles() }
|
||||||
|
|
|
@ -19,7 +19,7 @@ import React from 'react';
|
||||||
import createReactClass from 'create-react-class';
|
import createReactClass from 'create-react-class';
|
||||||
import * as sdk from '../../index';
|
import * as sdk from '../../index';
|
||||||
import { _t } from '../../languageHandler';
|
import { _t } from '../../languageHandler';
|
||||||
import dis from '../../dispatcher';
|
import dis from '../../dispatcher/dispatcher';
|
||||||
import AccessibleButton from '../views/elements/AccessibleButton';
|
import AccessibleButton from '../views/elements/AccessibleButton';
|
||||||
import MatrixClientContext from "../../contexts/MatrixClientContext";
|
import MatrixClientContext from "../../contexts/MatrixClientContext";
|
||||||
import AutoHideScrollbar from "./AutoHideScrollbar";
|
import AutoHideScrollbar from "./AutoHideScrollbar";
|
||||||
|
|
|
@ -22,7 +22,7 @@ import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import * as sdk from '../../index';
|
import * as sdk from '../../index';
|
||||||
import dis from '../../dispatcher';
|
import dis from '../../dispatcher/dispatcher';
|
||||||
import RateLimitedFunc from '../../ratelimitedfunc';
|
import RateLimitedFunc from '../../ratelimitedfunc';
|
||||||
import { showGroupInviteDialog, showGroupAddRoomDialog } from '../../GroupAddressPicker';
|
import { showGroupInviteDialog, showGroupAddRoomDialog } from '../../GroupAddressPicker';
|
||||||
import GroupStore from '../../stores/GroupStore';
|
import GroupStore from '../../stores/GroupStore';
|
||||||
|
@ -30,6 +30,7 @@ import SettingsStore from "../../settings/SettingsStore";
|
||||||
import {RIGHT_PANEL_PHASES, RIGHT_PANEL_PHASES_NO_ARGS} from "../../stores/RightPanelStorePhases";
|
import {RIGHT_PANEL_PHASES, RIGHT_PANEL_PHASES_NO_ARGS} from "../../stores/RightPanelStorePhases";
|
||||||
import RightPanelStore from "../../stores/RightPanelStore";
|
import RightPanelStore from "../../stores/RightPanelStore";
|
||||||
import MatrixClientContext from "../../contexts/MatrixClientContext";
|
import MatrixClientContext from "../../contexts/MatrixClientContext";
|
||||||
|
import {Action} from "../../dispatcher/actions";
|
||||||
|
|
||||||
export default class RightPanel extends React.Component {
|
export default class RightPanel extends React.Component {
|
||||||
static get propTypes() {
|
static get propTypes() {
|
||||||
|
@ -237,7 +238,7 @@ export default class RightPanel extends React.Component {
|
||||||
// within a room, so go back to the member panel if we were in the encryption panel,
|
// within a room, so go back to the member panel if we were in the encryption panel,
|
||||||
// or the member list if we were in the member panel... phew.
|
// or the member list if we were in the member panel... phew.
|
||||||
dis.dispatch({
|
dis.dispatch({
|
||||||
action: "view_user",
|
action: Action.ViewUser,
|
||||||
member: this.state.phase === RIGHT_PANEL_PHASES.EncryptionPanel ?
|
member: this.state.phase === RIGHT_PANEL_PHASES.EncryptionPanel ?
|
||||||
this.state.member : null,
|
this.state.member : null,
|
||||||
});
|
});
|
||||||
|
@ -266,7 +267,7 @@ export default class RightPanel extends React.Component {
|
||||||
if (SettingsStore.getValue("feature_cross_signing")) {
|
if (SettingsStore.getValue("feature_cross_signing")) {
|
||||||
const onClose = () => {
|
const onClose = () => {
|
||||||
dis.dispatch({
|
dis.dispatch({
|
||||||
action: "view_user",
|
action: Action.ViewUser,
|
||||||
member: null,
|
member: null,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
|
@ -20,7 +20,7 @@ import React from 'react';
|
||||||
import createReactClass from 'create-react-class';
|
import createReactClass from 'create-react-class';
|
||||||
import {MatrixClientPeg} from "../../MatrixClientPeg";
|
import {MatrixClientPeg} from "../../MatrixClientPeg";
|
||||||
import * as sdk from "../../index";
|
import * as sdk from "../../index";
|
||||||
import dis from "../../dispatcher";
|
import dis from "../../dispatcher/dispatcher";
|
||||||
import Modal from "../../Modal";
|
import Modal from "../../Modal";
|
||||||
import { linkifyAndSanitizeHtml } from '../../HtmlUtils';
|
import { linkifyAndSanitizeHtml } from '../../HtmlUtils';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
@ -199,7 +199,7 @@ export default createReactClass({
|
||||||
|
|
||||||
let desc;
|
let desc;
|
||||||
if (alias) {
|
if (alias) {
|
||||||
desc = _t('Delete the room alias %(alias)s and remove %(name)s from the directory?', {alias: alias, name: name});
|
desc = _t('Delete the room address %(alias)s and remove %(name)s from the directory?', {alias, name});
|
||||||
} else {
|
} else {
|
||||||
desc = _t('Remove %(name)s from the directory?', {name: name});
|
desc = _t('Remove %(name)s from the directory?', {name: name});
|
||||||
}
|
}
|
||||||
|
@ -216,7 +216,7 @@ export default createReactClass({
|
||||||
|
|
||||||
MatrixClientPeg.get().setRoomDirectoryVisibility(room.room_id, 'private').then(() => {
|
MatrixClientPeg.get().setRoomDirectoryVisibility(room.room_id, 'private').then(() => {
|
||||||
if (!alias) return;
|
if (!alias) return;
|
||||||
step = _t('delete the alias.');
|
step = _t('delete the address.');
|
||||||
return MatrixClientPeg.get().deleteAlias(alias);
|
return MatrixClientPeg.get().deleteAlias(alias);
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
modal.close();
|
modal.close();
|
||||||
|
|
|
@ -25,7 +25,7 @@ import * as sdk from '../../index';
|
||||||
import {MatrixClientPeg} from '../../MatrixClientPeg';
|
import {MatrixClientPeg} from '../../MatrixClientPeg';
|
||||||
import Resend from '../../Resend';
|
import Resend from '../../Resend';
|
||||||
import * as cryptodevices from '../../cryptodevices';
|
import * as cryptodevices from '../../cryptodevices';
|
||||||
import dis from '../../dispatcher';
|
import dis from '../../dispatcher/dispatcher';
|
||||||
import {messageForResourceLimitError, messageForSendError} from '../../utils/ErrorUtils';
|
import {messageForResourceLimitError, messageForSendError} from '../../utils/ErrorUtils';
|
||||||
|
|
||||||
const STATUS_BAR_HIDDEN = 0;
|
const STATUS_BAR_HIDDEN = 0;
|
||||||
|
|
|
@ -20,7 +20,7 @@ limitations under the License.
|
||||||
import React, {createRef} from 'react';
|
import React, {createRef} from 'react';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import * as sdk from '../../index';
|
import * as sdk from '../../index';
|
||||||
import dis from '../../dispatcher';
|
import dis from '../../dispatcher/dispatcher';
|
||||||
import * as Unread from '../../Unread';
|
import * as Unread from '../../Unread';
|
||||||
import * as RoomNotifs from '../../RoomNotifs';
|
import * as RoomNotifs from '../../RoomNotifs';
|
||||||
import * as FormattingUtils from '../../utils/FormattingUtils';
|
import * as FormattingUtils from '../../utils/FormattingUtils';
|
||||||
|
@ -32,7 +32,7 @@ import RoomTile from "../views/rooms/RoomTile";
|
||||||
import LazyRenderList from "../views/elements/LazyRenderList";
|
import LazyRenderList from "../views/elements/LazyRenderList";
|
||||||
import {_t} from "../../languageHandler";
|
import {_t} from "../../languageHandler";
|
||||||
import {RovingTabIndexWrapper} from "../../accessibility/RovingTabIndex";
|
import {RovingTabIndexWrapper} from "../../accessibility/RovingTabIndex";
|
||||||
import toRem from "../../utils/rem";
|
import {toPx} from "../../utils/units";
|
||||||
|
|
||||||
// turn this on for drop & drag console debugging galore
|
// turn this on for drop & drag console debugging galore
|
||||||
const debug = false;
|
const debug = false;
|
||||||
|
@ -420,7 +420,7 @@ export default class RoomSubList extends React.PureComponent {
|
||||||
|
|
||||||
setHeight = (height) => {
|
setHeight = (height) => {
|
||||||
if (this._subList.current) {
|
if (this._subList.current) {
|
||||||
this._subList.current.style.height = toRem(height);
|
this._subList.current.style.height = toPx(height);
|
||||||
}
|
}
|
||||||
this._updateLazyRenderHeight(height);
|
this._updateLazyRenderHeight(height);
|
||||||
};
|
};
|
||||||
|
|
|
@ -34,7 +34,7 @@ import ContentMessages from '../../ContentMessages';
|
||||||
import Modal from '../../Modal';
|
import Modal from '../../Modal';
|
||||||
import * as sdk from '../../index';
|
import * as sdk from '../../index';
|
||||||
import CallHandler from '../../CallHandler';
|
import CallHandler from '../../CallHandler';
|
||||||
import dis from '../../dispatcher';
|
import dis from '../../dispatcher/dispatcher';
|
||||||
import Tinter from '../../Tinter';
|
import Tinter from '../../Tinter';
|
||||||
import rate_limited_func from '../../ratelimitedfunc';
|
import rate_limited_func from '../../ratelimitedfunc';
|
||||||
import * as ObjectUtils from '../../ObjectUtils';
|
import * as ObjectUtils from '../../ObjectUtils';
|
||||||
|
@ -164,6 +164,10 @@ export default createReactClass({
|
||||||
|
|
||||||
canReact: false,
|
canReact: false,
|
||||||
canReply: false,
|
canReply: false,
|
||||||
|
|
||||||
|
useIRCLayout: SettingsStore.getValue("feature_irc_ui"),
|
||||||
|
|
||||||
|
matrixClientIsReady: this.context && this.context.isInitialSyncComplete(),
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -193,6 +197,8 @@ export default createReactClass({
|
||||||
|
|
||||||
this._roomView = createRef();
|
this._roomView = createRef();
|
||||||
this._searchResultsPanel = createRef();
|
this._searchResultsPanel = createRef();
|
||||||
|
|
||||||
|
this._layoutWatcherRef = SettingsStore.watchSetting("feature_irc_ui", null, this.onLayoutChange);
|
||||||
},
|
},
|
||||||
|
|
||||||
_onReadReceiptsChange: function() {
|
_onReadReceiptsChange: function() {
|
||||||
|
@ -232,7 +238,8 @@ export default createReactClass({
|
||||||
initialEventId: RoomViewStore.getInitialEventId(),
|
initialEventId: RoomViewStore.getInitialEventId(),
|
||||||
isInitialEventHighlighted: RoomViewStore.isInitialEventHighlighted(),
|
isInitialEventHighlighted: RoomViewStore.isInitialEventHighlighted(),
|
||||||
forwardingEvent: RoomViewStore.getForwardingEvent(),
|
forwardingEvent: RoomViewStore.getForwardingEvent(),
|
||||||
shouldPeek: RoomViewStore.shouldPeek(),
|
// we should only peek once we have a ready client
|
||||||
|
shouldPeek: this.state.matrixClientIsReady && RoomViewStore.shouldPeek(),
|
||||||
showingPinned: SettingsStore.getValue("PinnedEvents.isOpen", roomId),
|
showingPinned: SettingsStore.getValue("PinnedEvents.isOpen", roomId),
|
||||||
showReadReceipts: SettingsStore.getValue("showReadReceipts", roomId),
|
showReadReceipts: SettingsStore.getValue("showReadReceipts", roomId),
|
||||||
};
|
};
|
||||||
|
@ -532,6 +539,14 @@ export default createReactClass({
|
||||||
// no need to do this as Dir & Settings are now overlays. It just burnt CPU.
|
// no need to do this as Dir & Settings are now overlays. It just burnt CPU.
|
||||||
// console.log("Tinter.tint from RoomView.unmount");
|
// console.log("Tinter.tint from RoomView.unmount");
|
||||||
// Tinter.tint(); // reset colourscheme
|
// Tinter.tint(); // reset colourscheme
|
||||||
|
|
||||||
|
SettingsStore.unwatchSetting(this._layoutWatcherRef);
|
||||||
|
},
|
||||||
|
|
||||||
|
onLayoutChange: function() {
|
||||||
|
this.setState({
|
||||||
|
useIRCLayout: SettingsStore.getValue("feature_irc_ui"),
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
_onRightPanelStoreUpdate: function() {
|
_onRightPanelStoreUpdate: function() {
|
||||||
|
@ -681,6 +696,16 @@ export default createReactClass({
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case 'sync_state':
|
||||||
|
if (!this.state.matrixClientIsReady) {
|
||||||
|
this.setState({
|
||||||
|
matrixClientIsReady: this.context && this.context.isInitialSyncComplete(),
|
||||||
|
}, () => {
|
||||||
|
// send another "initial" RVS update to trigger peeking if needed
|
||||||
|
this._onRoomViewStoreUpdate(true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -1663,14 +1688,16 @@ export default createReactClass({
|
||||||
const ErrorBoundary = sdk.getComponent("elements.ErrorBoundary");
|
const ErrorBoundary = sdk.getComponent("elements.ErrorBoundary");
|
||||||
|
|
||||||
if (!this.state.room) {
|
if (!this.state.room) {
|
||||||
const loading = this.state.roomLoading || this.state.peekLoading;
|
const loading = !this.state.matrixClientIsReady || this.state.roomLoading || this.state.peekLoading;
|
||||||
if (loading) {
|
if (loading) {
|
||||||
|
// Assume preview loading if we don't have a ready client or a room ID (still resolving the alias)
|
||||||
|
const previewLoading = !this.state.matrixClientIsReady || !this.state.roomId || this.state.peekLoading;
|
||||||
return (
|
return (
|
||||||
<div className="mx_RoomView">
|
<div className="mx_RoomView">
|
||||||
<ErrorBoundary>
|
<ErrorBoundary>
|
||||||
<RoomPreviewBar
|
<RoomPreviewBar
|
||||||
canPreview={false}
|
canPreview={false}
|
||||||
previewLoading={this.state.peekLoading}
|
previewLoading={previewLoading && !this.state.roomLoadError}
|
||||||
error={this.state.roomLoadError}
|
error={this.state.roomLoadError}
|
||||||
loading={loading}
|
loading={loading}
|
||||||
joining={this.state.joining}
|
joining={this.state.joining}
|
||||||
|
@ -1695,7 +1722,8 @@ export default createReactClass({
|
||||||
return (
|
return (
|
||||||
<div className="mx_RoomView">
|
<div className="mx_RoomView">
|
||||||
<ErrorBoundary>
|
<ErrorBoundary>
|
||||||
<RoomPreviewBar onJoinClick={this.onJoinButtonClicked}
|
<RoomPreviewBar
|
||||||
|
onJoinClick={this.onJoinButtonClicked}
|
||||||
onForgetClick={this.onForgetClick}
|
onForgetClick={this.onForgetClick}
|
||||||
onRejectClick={this.onRejectThreepidInviteButtonClicked}
|
onRejectClick={this.onRejectThreepidInviteButtonClicked}
|
||||||
canPreview={false} error={this.state.roomLoadError}
|
canPreview={false} error={this.state.roomLoadError}
|
||||||
|
@ -1980,6 +2008,13 @@ export default createReactClass({
|
||||||
highlightedEventId = this.state.initialEventId;
|
highlightedEventId = this.state.initialEventId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const messagePanelClassNames = classNames(
|
||||||
|
"mx_RoomView_messagePanel",
|
||||||
|
{
|
||||||
|
"mx_IRCLayout": this.state.useIRCLayout,
|
||||||
|
"mx_GroupLayout": !this.state.useIRCLayout,
|
||||||
|
});
|
||||||
|
|
||||||
// console.info("ShowUrlPreview for %s is %s", this.state.room.roomId, this.state.showUrlPreview);
|
// console.info("ShowUrlPreview for %s is %s", this.state.room.roomId, this.state.showUrlPreview);
|
||||||
const messagePanel = (
|
const messagePanel = (
|
||||||
<TimelinePanel
|
<TimelinePanel
|
||||||
|
@ -1995,11 +2030,12 @@ export default createReactClass({
|
||||||
onScroll={this.onMessageListScroll}
|
onScroll={this.onMessageListScroll}
|
||||||
onReadMarkerUpdated={this._updateTopUnreadMessagesBar}
|
onReadMarkerUpdated={this._updateTopUnreadMessagesBar}
|
||||||
showUrlPreview = {this.state.showUrlPreview}
|
showUrlPreview = {this.state.showUrlPreview}
|
||||||
className="mx_RoomView_messagePanel"
|
className={messagePanelClassNames}
|
||||||
membersLoaded={this.state.membersLoaded}
|
membersLoaded={this.state.membersLoaded}
|
||||||
permalinkCreator={this._getPermalinkCreatorForRoom(this.state.room)}
|
permalinkCreator={this._getPermalinkCreatorForRoom(this.state.room)}
|
||||||
resizeNotifier={this.props.resizeNotifier}
|
resizeNotifier={this.props.resizeNotifier}
|
||||||
showReactions={true}
|
showReactions={true}
|
||||||
|
useIRCLayout={this.state.useIRCLayout}
|
||||||
/>);
|
/>);
|
||||||
|
|
||||||
let topUnreadMessagesBar = null;
|
let topUnreadMessagesBar = null;
|
||||||
|
|
|
@ -144,6 +144,11 @@ export default createReactClass({
|
||||||
/* resizeNotifier: ResizeNotifier to know when middle column has changed size
|
/* resizeNotifier: ResizeNotifier to know when middle column has changed size
|
||||||
*/
|
*/
|
||||||
resizeNotifier: PropTypes.object,
|
resizeNotifier: PropTypes.object,
|
||||||
|
|
||||||
|
/* fixedChildren: allows for children to be passed which are rendered outside
|
||||||
|
* of the wrapper
|
||||||
|
*/
|
||||||
|
fixedChildren: PropTypes.node,
|
||||||
},
|
},
|
||||||
|
|
||||||
getDefaultProps: function() {
|
getDefaultProps: function() {
|
||||||
|
@ -881,6 +886,7 @@ export default createReactClass({
|
||||||
return (<AutoHideScrollbar wrappedRef={this._collectScroll}
|
return (<AutoHideScrollbar wrappedRef={this._collectScroll}
|
||||||
onScroll={this.onScroll}
|
onScroll={this.onScroll}
|
||||||
className={`mx_ScrollPanel ${this.props.className}`} style={this.props.style}>
|
className={`mx_ScrollPanel ${this.props.className}`} style={this.props.style}>
|
||||||
|
{ this.props.fixedChildren }
|
||||||
<div className="mx_RoomView_messageListWrapper">
|
<div className="mx_RoomView_messageListWrapper">
|
||||||
<ol ref={this._itemlist} className="mx_RoomView_MessageList" aria-live="polite" role="list">
|
<ol ref={this._itemlist} className="mx_RoomView_MessageList" aria-live="polite" role="list">
|
||||||
{ this.props.children }
|
{ this.props.children }
|
||||||
|
|
|
@ -19,7 +19,7 @@ import React, {createRef} from 'react';
|
||||||
import createReactClass from 'create-react-class';
|
import createReactClass from 'create-react-class';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { Key } from '../../Keyboard';
|
import { Key } from '../../Keyboard';
|
||||||
import dis from '../../dispatcher';
|
import dis from '../../dispatcher/dispatcher';
|
||||||
import { throttle } from 'lodash';
|
import { throttle } from 'lodash';
|
||||||
import AccessibleButton from '../../components/views/elements/AccessibleButton';
|
import AccessibleButton from '../../components/views/elements/AccessibleButton';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
|
|
@ -22,7 +22,7 @@ import TagOrderStore from '../../stores/TagOrderStore';
|
||||||
import GroupActions from '../../actions/GroupActions';
|
import GroupActions from '../../actions/GroupActions';
|
||||||
|
|
||||||
import * as sdk from '../../index';
|
import * as sdk from '../../index';
|
||||||
import dis from '../../dispatcher';
|
import dis from '../../dispatcher/dispatcher';
|
||||||
import { _t } from '../../languageHandler';
|
import { _t } from '../../languageHandler';
|
||||||
|
|
||||||
import { Droppable } from 'react-beautiful-dnd';
|
import { Droppable } from 'react-beautiful-dnd';
|
||||||
|
|
|
@ -17,7 +17,7 @@ limitations under the License.
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import createReactClass from 'create-react-class';
|
import createReactClass from 'create-react-class';
|
||||||
import * as sdk from '../../index';
|
import * as sdk from '../../index';
|
||||||
import dis from '../../dispatcher';
|
import dis from '../../dispatcher/dispatcher';
|
||||||
import Modal from '../../Modal';
|
import Modal from '../../Modal';
|
||||||
import { _t } from '../../languageHandler';
|
import { _t } from '../../languageHandler';
|
||||||
|
|
||||||
|
|
|
@ -29,7 +29,7 @@ import {MatrixClientPeg} from "../../MatrixClientPeg";
|
||||||
import * as ObjectUtils from "../../ObjectUtils";
|
import * as ObjectUtils from "../../ObjectUtils";
|
||||||
import UserActivity from "../../UserActivity";
|
import UserActivity from "../../UserActivity";
|
||||||
import Modal from "../../Modal";
|
import Modal from "../../Modal";
|
||||||
import dis from "../../dispatcher";
|
import dis from "../../dispatcher/dispatcher";
|
||||||
import * as sdk from "../../index";
|
import * as sdk from "../../index";
|
||||||
import { Key } from '../../Keyboard';
|
import { Key } from '../../Keyboard';
|
||||||
import Timer from '../../utils/Timer';
|
import Timer from '../../utils/Timer';
|
||||||
|
@ -112,6 +112,9 @@ const TimelinePanel = createReactClass({
|
||||||
|
|
||||||
// whether to show reactions for an event
|
// whether to show reactions for an event
|
||||||
showReactions: PropTypes.bool,
|
showReactions: PropTypes.bool,
|
||||||
|
|
||||||
|
// whether to use the irc layout
|
||||||
|
useIRCLayout: PropTypes.bool,
|
||||||
},
|
},
|
||||||
|
|
||||||
statics: {
|
statics: {
|
||||||
|
@ -1447,6 +1450,7 @@ const TimelinePanel = createReactClass({
|
||||||
getRelationsForEvent={this.getRelationsForEvent}
|
getRelationsForEvent={this.getRelationsForEvent}
|
||||||
editState={this.state.editState}
|
editState={this.state.editState}
|
||||||
showReactions={this.props.showReactions}
|
showReactions={this.props.showReactions}
|
||||||
|
useIRCLayout={this.props.useIRCLayout}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
|
@ -15,14 +15,21 @@ limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { _t } from '../../languageHandler';
|
import ToastStore, {IToast} from "../../stores/ToastStore";
|
||||||
import ToastStore from "../../stores/ToastStore";
|
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
|
|
||||||
export default class ToastContainer extends React.Component {
|
interface IState {
|
||||||
constructor() {
|
toasts: IToast<any>[];
|
||||||
super();
|
countSeen: number;
|
||||||
this.state = {toasts: ToastStore.sharedInstance().getToasts()};
|
}
|
||||||
|
|
||||||
|
export default class ToastContainer extends React.Component<{}, IState> {
|
||||||
|
constructor(props, context) {
|
||||||
|
super(props, context);
|
||||||
|
this.state = {
|
||||||
|
toasts: ToastStore.sharedInstance().getToasts(),
|
||||||
|
countSeen: ToastStore.sharedInstance().getCountSeen(),
|
||||||
|
};
|
||||||
|
|
||||||
// Start listening here rather than in componentDidMount because
|
// Start listening here rather than in componentDidMount because
|
||||||
// toasts may dismiss themselves in their didMount if they find
|
// toasts may dismiss themselves in their didMount if they find
|
||||||
|
@ -36,7 +43,10 @@ export default class ToastContainer extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
_onToastStoreUpdate = () => {
|
_onToastStoreUpdate = () => {
|
||||||
this.setState({toasts: ToastStore.sharedInstance().getToasts()});
|
this.setState({
|
||||||
|
toasts: ToastStore.sharedInstance().getToasts(),
|
||||||
|
countSeen: ToastStore.sharedInstance().getCountSeen(),
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
@ -50,14 +60,21 @@ export default class ToastContainer extends React.Component {
|
||||||
"mx_Toast_hasIcon": icon,
|
"mx_Toast_hasIcon": icon,
|
||||||
[`mx_Toast_icon_${icon}`]: icon,
|
[`mx_Toast_icon_${icon}`]: icon,
|
||||||
});
|
});
|
||||||
const countIndicator = isStacked ? _t(" (1/%(totalCount)s)", {totalCount}) : null;
|
|
||||||
|
let countIndicator;
|
||||||
|
if (isStacked || this.state.countSeen > 0) {
|
||||||
|
countIndicator = ` (${this.state.countSeen + 1}/${this.state.countSeen + totalCount})`;
|
||||||
|
}
|
||||||
|
|
||||||
const toastProps = Object.assign({}, props, {
|
const toastProps = Object.assign({}, props, {
|
||||||
key,
|
key,
|
||||||
toastKey: key,
|
toastKey: key,
|
||||||
});
|
});
|
||||||
toast = (<div className={toastClasses}>
|
toast = (<div className={toastClasses}>
|
||||||
<h2>{title}{countIndicator}</h2>
|
<div className="mx_Toast_title">
|
||||||
|
<h2>{title}</h2>
|
||||||
|
<span>{countIndicator}</span>
|
||||||
|
</div>
|
||||||
<div className="mx_Toast_body">{React.createElement(component, toastProps)}</div>
|
<div className="mx_Toast_body">{React.createElement(component, toastProps)}</div>
|
||||||
</div>);
|
</div>);
|
||||||
}
|
}
|
|
@ -22,7 +22,7 @@ import BaseAvatar from '../views/avatars/BaseAvatar';
|
||||||
import {MatrixClientPeg} from '../../MatrixClientPeg';
|
import {MatrixClientPeg} from '../../MatrixClientPeg';
|
||||||
import * as Avatar from '../../Avatar';
|
import * as Avatar from '../../Avatar';
|
||||||
import { _t } from '../../languageHandler';
|
import { _t } from '../../languageHandler';
|
||||||
import dis from "../../dispatcher";
|
import dis from "../../dispatcher/dispatcher";
|
||||||
import {ContextMenu, ContextMenuButton} from "./ContextMenu";
|
import {ContextMenu, ContextMenuButton} from "./ContextMenu";
|
||||||
|
|
||||||
const AVATAR_SIZE = 28;
|
const AVATAR_SIZE = 28;
|
||||||
|
|
|
@ -19,7 +19,7 @@ import React from 'react';
|
||||||
import createReactClass from 'create-react-class';
|
import createReactClass from 'create-react-class';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import ContentMessages from '../../ContentMessages';
|
import ContentMessages from '../../ContentMessages';
|
||||||
import dis from "../../dispatcher";
|
import dis from "../../dispatcher/dispatcher";
|
||||||
import filesize from "filesize";
|
import filesize from "filesize";
|
||||||
import { _t } from '../../languageHandler';
|
import { _t } from '../../languageHandler';
|
||||||
|
|
||||||
|
|
|
@ -32,7 +32,7 @@ import * as Lifecycle from '../../../Lifecycle';
|
||||||
import {MatrixClientPeg} from "../../../MatrixClientPeg";
|
import {MatrixClientPeg} from "../../../MatrixClientPeg";
|
||||||
import AuthPage from "../../views/auth/AuthPage";
|
import AuthPage from "../../views/auth/AuthPage";
|
||||||
import Login from "../../../Login";
|
import Login from "../../../Login";
|
||||||
import dis from "../../../dispatcher";
|
import dis from "../../../dispatcher/dispatcher";
|
||||||
|
|
||||||
// Phases
|
// Phases
|
||||||
// Show controls to configure server details
|
// Show controls to configure server details
|
||||||
|
|
|
@ -18,7 +18,7 @@ import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import {_t} from '../../../languageHandler';
|
import {_t} from '../../../languageHandler';
|
||||||
import * as sdk from '../../../index';
|
import * as sdk from '../../../index';
|
||||||
import dis from '../../../dispatcher';
|
import dis from '../../../dispatcher/dispatcher';
|
||||||
import * as Lifecycle from '../../../Lifecycle';
|
import * as Lifecycle from '../../../Lifecycle';
|
||||||
import Modal from '../../../Modal';
|
import Modal from '../../../Modal';
|
||||||
import {MatrixClientPeg} from "../../../MatrixClientPeg";
|
import {MatrixClientPeg} from "../../../MatrixClientPeg";
|
||||||
|
|
|
@ -355,6 +355,7 @@ export const TermsAuthEntry = createReactClass({
|
||||||
allChecked = allChecked && checked;
|
allChecked = allChecked && checked;
|
||||||
|
|
||||||
checkboxes.push(
|
checkboxes.push(
|
||||||
|
// XXX: replace with StyledCheckbox
|
||||||
<label key={"policy_checkbox_" + policy.id} className="mx_InteractiveAuthEntryComponents_termsPolicy">
|
<label key={"policy_checkbox_" + policy.id} className="mx_InteractiveAuthEntryComponents_termsPolicy">
|
||||||
<input type="checkbox" onChange={() => this._togglePolicy(policy.id)} checked={checked} />
|
<input type="checkbox" onChange={() => this._togglePolicy(policy.id)} checked={checked} />
|
||||||
<a href={policy.url} target="_blank" rel="noreferrer noopener">{ policy.name }</a>
|
<a href={policy.url} target="_blank" rel="noreferrer noopener">{ policy.name }</a>
|
||||||
|
@ -538,6 +539,7 @@ export const MsisdnAuthEntry = createReactClass({
|
||||||
type: MsisdnAuthEntry.LOGIN_TYPE,
|
type: MsisdnAuthEntry.LOGIN_TYPE,
|
||||||
// TODO: Remove `threepid_creds` once servers support proper UIA
|
// TODO: Remove `threepid_creds` once servers support proper UIA
|
||||||
// See https://github.com/vector-im/riot-web/issues/10312
|
// See https://github.com/vector-im/riot-web/issues/10312
|
||||||
|
// See https://github.com/matrix-org/matrix-doc/issues/2220
|
||||||
threepid_creds: creds,
|
threepid_creds: creds,
|
||||||
threepidCreds: creds,
|
threepidCreds: creds,
|
||||||
});
|
});
|
||||||
|
|
|
@ -23,7 +23,8 @@ import AutoDiscoveryUtils from "../../../utils/AutoDiscoveryUtils";
|
||||||
import * as ServerType from '../../views/auth/ServerTypeSelector';
|
import * as ServerType from '../../views/auth/ServerTypeSelector';
|
||||||
import ServerConfig from "./ServerConfig";
|
import ServerConfig from "./ServerConfig";
|
||||||
|
|
||||||
const MODULAR_URL = 'https://modular.im/?utm_source=riot-web&utm_medium=web&utm_campaign=riot-web-authentication';
|
const MODULAR_URL = 'https://modular.im/services/matrix-hosting-riot' +
|
||||||
|
'?utm_source=riot-web&utm_medium=web&utm_campaign=riot-web-authentication';
|
||||||
|
|
||||||
// TODO: TravisR - Can this extend ServerConfig for most things?
|
// TODO: TravisR - Can this extend ServerConfig for most things?
|
||||||
|
|
||||||
|
|
|
@ -36,7 +36,7 @@ interface IProps {
|
||||||
labelStrongPassword?: string;
|
labelStrongPassword?: string;
|
||||||
labelAllowedButUnsafe?: string;
|
labelAllowedButUnsafe?: string;
|
||||||
|
|
||||||
onChange(ev: KeyboardEvent);
|
onChange(ev: React.FormEvent<HTMLElement>);
|
||||||
onValidate(result: IValidationResult);
|
onValidate(result: IValidationResult);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -238,7 +238,7 @@ export default class PasswordLogin extends React.Component {
|
||||||
type="text"
|
type="text"
|
||||||
label={_t("Phone")}
|
label={_t("Phone")}
|
||||||
value={this.state.phoneNumber}
|
value={this.state.phoneNumber}
|
||||||
prefix={phoneCountry}
|
prefixComponent={phoneCountry}
|
||||||
onChange={this.onPhoneNumberChanged}
|
onChange={this.onPhoneNumberChanged}
|
||||||
onBlur={this.onPhoneNumberBlur}
|
onBlur={this.onPhoneNumberBlur}
|
||||||
disabled={this.props.disableSubmit}
|
disabled={this.props.disableSubmit}
|
||||||
|
|
|
@ -473,7 +473,7 @@ export default createReactClass({
|
||||||
type="text"
|
type="text"
|
||||||
label={phoneLabel}
|
label={phoneLabel}
|
||||||
value={this.state.phoneNumber}
|
value={this.state.phoneNumber}
|
||||||
prefix={phoneCountry}
|
prefixComponent={phoneCountry}
|
||||||
onChange={this.onPhoneNumberChange}
|
onChange={this.onPhoneNumberChange}
|
||||||
onValidate={this.onPhoneNumberValidate}
|
onValidate={this.onPhoneNumberValidate}
|
||||||
/>;
|
/>;
|
||||||
|
|
|
@ -22,7 +22,8 @@ import classnames from 'classnames';
|
||||||
import {ValidatedServerConfig} from "../../../utils/AutoDiscoveryUtils";
|
import {ValidatedServerConfig} from "../../../utils/AutoDiscoveryUtils";
|
||||||
import {makeType} from "../../../utils/TypeUtils";
|
import {makeType} from "../../../utils/TypeUtils";
|
||||||
|
|
||||||
const MODULAR_URL = 'https://modular.im/?utm_source=riot-web&utm_medium=web&utm_campaign=riot-web-authentication';
|
const MODULAR_URL = 'https://modular.im/services/matrix-hosting-riot' +
|
||||||
|
'?utm_source=riot-web&utm_medium=web&utm_campaign=riot-web-authentication';
|
||||||
|
|
||||||
export const FREE = 'Free';
|
export const FREE = 'Free';
|
||||||
export const PREMIUM = 'Premium';
|
export const PREMIUM = 'Premium';
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
Copyright 2015, 2016 OpenMarket Ltd
|
Copyright 2015, 2016 OpenMarket Ltd
|
||||||
Copyright 2018 New Vector Ltd
|
Copyright 2018 New Vector Ltd
|
||||||
Copyright 2019 Michael Telatynski <7t3chguy@gmail.com>
|
Copyright 2019 Michael Telatynski <7t3chguy@gmail.com>
|
||||||
Copyright 2019 The Matrix.org Foundation C.I.C.
|
Copyright 2019, 2020 The Matrix.org Foundation C.I.C.
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
|
@ -17,176 +17,114 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
import React, {useCallback, useContext, useEffect, useMemo, useState} from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import createReactClass from 'create-react-class';
|
|
||||||
import * as AvatarLogic from '../../../Avatar';
|
import * as AvatarLogic from '../../../Avatar';
|
||||||
import SettingsStore from "../../../settings/SettingsStore";
|
import SettingsStore from "../../../settings/SettingsStore";
|
||||||
import AccessibleButton from '../elements/AccessibleButton';
|
import AccessibleButton from '../elements/AccessibleButton';
|
||||||
import MatrixClientContext from "../../../contexts/MatrixClientContext";
|
import MatrixClientContext from "../../../contexts/MatrixClientContext";
|
||||||
import toRem from "../../../utils/rem";
|
import {useEventEmitter} from "../../../hooks/useEventEmitter";
|
||||||
|
import {toPx} from "../../../utils/units";
|
||||||
|
|
||||||
export default createReactClass({
|
const useImageUrl = ({url, urls}) => {
|
||||||
displayName: 'BaseAvatar',
|
const [imageUrls, setUrls] = useState([]);
|
||||||
|
const [urlsIndex, setIndex] = useState();
|
||||||
|
|
||||||
propTypes: {
|
const onError = useCallback(() => {
|
||||||
name: PropTypes.string.isRequired, // The name (first initial used as default)
|
setIndex(i => i + 1); // try the next one
|
||||||
idName: PropTypes.string, // ID for generating hash colours
|
}, []);
|
||||||
title: PropTypes.string, // onHover title text
|
const memoizedUrls = useMemo(() => urls, [JSON.stringify(urls)]); // eslint-disable-line react-hooks/exhaustive-deps
|
||||||
url: PropTypes.string, // highest priority of them all, shortcut to set in urls[0]
|
|
||||||
urls: PropTypes.array, // [highest_priority, ... , lowest_priority]
|
|
||||||
width: PropTypes.number,
|
|
||||||
height: PropTypes.number,
|
|
||||||
// XXX resizeMethod not actually used.
|
|
||||||
resizeMethod: PropTypes.string,
|
|
||||||
defaultToInitialLetter: PropTypes.bool, // true to add default url
|
|
||||||
inputRef: PropTypes.oneOfType([
|
|
||||||
// Either a function
|
|
||||||
PropTypes.func,
|
|
||||||
// Or the instance of a DOM native element
|
|
||||||
PropTypes.shape({ current: PropTypes.instanceOf(Element) }),
|
|
||||||
]),
|
|
||||||
},
|
|
||||||
|
|
||||||
statics: {
|
useEffect(() => {
|
||||||
contextType: MatrixClientContext,
|
|
||||||
},
|
|
||||||
|
|
||||||
getDefaultProps: function() {
|
|
||||||
return {
|
|
||||||
width: 40,
|
|
||||||
height: 40,
|
|
||||||
resizeMethod: 'crop',
|
|
||||||
defaultToInitialLetter: true,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
getInitialState: function() {
|
|
||||||
return this._getState(this.props);
|
|
||||||
},
|
|
||||||
|
|
||||||
componentDidMount() {
|
|
||||||
this.unmounted = false;
|
|
||||||
this.context.on('sync', this.onClientSync);
|
|
||||||
},
|
|
||||||
|
|
||||||
componentWillUnmount() {
|
|
||||||
this.unmounted = true;
|
|
||||||
this.context.removeListener('sync', this.onClientSync);
|
|
||||||
},
|
|
||||||
|
|
||||||
// TODO: [REACT-WARNING] Replace with appropriate lifecycle event
|
|
||||||
UNSAFE_componentWillReceiveProps: function(nextProps) {
|
|
||||||
// work out if we need to call setState (if the image URLs array has changed)
|
|
||||||
const newState = this._getState(nextProps);
|
|
||||||
const newImageUrls = newState.imageUrls;
|
|
||||||
const oldImageUrls = this.state.imageUrls;
|
|
||||||
if (newImageUrls.length !== oldImageUrls.length) {
|
|
||||||
this.setState(newState); // detected a new entry
|
|
||||||
} else {
|
|
||||||
// check each one to see if they are the same
|
|
||||||
for (let i = 0; i < newImageUrls.length; i++) {
|
|
||||||
if (oldImageUrls[i] !== newImageUrls[i]) {
|
|
||||||
this.setState(newState); // detected a diff
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
onClientSync: function(syncState, prevState) {
|
|
||||||
if (this.unmounted) return;
|
|
||||||
|
|
||||||
// Consider the client reconnected if there is no error with syncing.
|
|
||||||
// This means the state could be RECONNECTING, SYNCING, PREPARED or CATCHUP.
|
|
||||||
const reconnected = syncState !== "ERROR" && prevState !== syncState;
|
|
||||||
if (reconnected &&
|
|
||||||
// Did we fall back?
|
|
||||||
this.state.urlsIndex > 0
|
|
||||||
) {
|
|
||||||
// Start from the highest priority URL again
|
|
||||||
this.setState({
|
|
||||||
urlsIndex: 0,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
_getState: function(props) {
|
|
||||||
// work out the full set of urls to try to load. This is formed like so:
|
// work out the full set of urls to try to load. This is formed like so:
|
||||||
// imageUrls: [ props.url, props.urls, default image ]
|
// imageUrls: [ props.url, ...props.urls ]
|
||||||
|
|
||||||
let urls = [];
|
let _urls = [];
|
||||||
if (!SettingsStore.getValue("lowBandwidth")) {
|
if (!SettingsStore.getValue("lowBandwidth")) {
|
||||||
urls = props.urls || [];
|
_urls = memoizedUrls || [];
|
||||||
|
|
||||||
if (props.url) {
|
if (url) {
|
||||||
urls.unshift(props.url); // put in urls[0]
|
_urls.unshift(url); // put in urls[0]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let defaultImageUrl = null;
|
|
||||||
if (props.defaultToInitialLetter) {
|
|
||||||
defaultImageUrl = AvatarLogic.defaultAvatarUrlForString(
|
|
||||||
props.idName || props.name,
|
|
||||||
);
|
|
||||||
urls.push(defaultImageUrl); // lowest priority
|
|
||||||
}
|
|
||||||
|
|
||||||
// deduplicate URLs
|
// deduplicate URLs
|
||||||
urls = Array.from(new Set(urls));
|
_urls = Array.from(new Set(_urls));
|
||||||
|
|
||||||
return {
|
setIndex(0);
|
||||||
imageUrls: urls,
|
setUrls(_urls);
|
||||||
defaultImageUrl: defaultImageUrl,
|
}, [url, memoizedUrls]); // eslint-disable-line react-hooks/exhaustive-deps
|
||||||
urlsIndex: 0,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
onError: function(ev) {
|
const cli = useContext(MatrixClientContext);
|
||||||
const nextIndex = this.state.urlsIndex + 1;
|
const onClientSync = useCallback((syncState, prevState) => {
|
||||||
if (nextIndex < this.state.imageUrls.length) {
|
// Consider the client reconnected if there is no error with syncing.
|
||||||
// try the next one
|
// This means the state could be RECONNECTING, SYNCING, PREPARED or CATCHUP.
|
||||||
this.setState({
|
const reconnected = syncState !== "ERROR" && prevState !== syncState;
|
||||||
urlsIndex: nextIndex,
|
if (reconnected) {
|
||||||
});
|
setIndex(0);
|
||||||
}
|
}
|
||||||
},
|
}, []);
|
||||||
|
useEventEmitter(cli, "sync", onClientSync);
|
||||||
|
|
||||||
render: function() {
|
const imageUrl = imageUrls[urlsIndex];
|
||||||
const imageUrl = this.state.imageUrls[this.state.urlsIndex];
|
return [imageUrl, onError];
|
||||||
|
};
|
||||||
|
|
||||||
|
const BaseAvatar = (props) => {
|
||||||
const {
|
const {
|
||||||
name, idName, title, url, urls, width, height, resizeMethod,
|
name,
|
||||||
defaultToInitialLetter, onClick, inputRef,
|
idName,
|
||||||
|
title,
|
||||||
|
url,
|
||||||
|
urls,
|
||||||
|
width=40,
|
||||||
|
height=40,
|
||||||
|
resizeMethod="crop", // eslint-disable-line no-unused-vars
|
||||||
|
defaultToInitialLetter=true,
|
||||||
|
onClick,
|
||||||
|
inputRef,
|
||||||
...otherProps
|
...otherProps
|
||||||
} = this.props;
|
} = props;
|
||||||
|
|
||||||
if (imageUrl === this.state.defaultImageUrl) {
|
const [imageUrl, onError] = useImageUrl({url, urls});
|
||||||
|
|
||||||
|
if (!imageUrl && defaultToInitialLetter) {
|
||||||
const initialLetter = AvatarLogic.getInitialLetter(name);
|
const initialLetter = AvatarLogic.getInitialLetter(name);
|
||||||
const textNode = (
|
const textNode = (
|
||||||
<span className="mx_BaseAvatar_initial" aria-hidden="true"
|
<span
|
||||||
|
className="mx_BaseAvatar_initial"
|
||||||
|
aria-hidden="true"
|
||||||
style={{
|
style={{
|
||||||
fontSize: toRem(width * 0.65),
|
fontSize: toPx(width * 0.65),
|
||||||
width: toRem(width),
|
width: toPx(width),
|
||||||
lineHeight: toRem(height),
|
lineHeight: toPx(height),
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{ initialLetter }
|
{ initialLetter }
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
const imgNode = (
|
const imgNode = (
|
||||||
<img className="mx_BaseAvatar_image" src={imageUrl}
|
<img
|
||||||
alt="" title={title} onError={this.onError}
|
className="mx_BaseAvatar_image"
|
||||||
aria-hidden="true"
|
src={AvatarLogic.defaultAvatarUrlForString(idName || name)}
|
||||||
|
alt=""
|
||||||
|
title={title}
|
||||||
|
onError={onError}
|
||||||
style={{
|
style={{
|
||||||
width: toRem(width),
|
width: toPx(width),
|
||||||
height: toRem(height)
|
height: toPx(height),
|
||||||
}} />
|
}}
|
||||||
|
aria-hidden="true" />
|
||||||
);
|
);
|
||||||
|
|
||||||
if (onClick != null) {
|
if (onClick != null) {
|
||||||
return (
|
return (
|
||||||
<AccessibleButton element='span' className="mx_BaseAvatar"
|
<AccessibleButton
|
||||||
onClick={onClick} inputRef={inputRef} {...otherProps}
|
{...otherProps}
|
||||||
|
element="span"
|
||||||
|
className="mx_BaseAvatar"
|
||||||
|
onClick={onClick}
|
||||||
|
inputRef={inputRef}
|
||||||
>
|
>
|
||||||
{ textNode }
|
{ textNode }
|
||||||
{ imgNode }
|
{ imgNode }
|
||||||
|
@ -201,6 +139,7 @@ export default createReactClass({
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (onClick != null) {
|
if (onClick != null) {
|
||||||
return (
|
return (
|
||||||
<AccessibleButton
|
<AccessibleButton
|
||||||
|
@ -208,10 +147,10 @@ export default createReactClass({
|
||||||
element='img'
|
element='img'
|
||||||
src={imageUrl}
|
src={imageUrl}
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
onError={this.onError}
|
onError={onError}
|
||||||
style={{
|
style={{
|
||||||
width: toRem(width),
|
width: toPx(width),
|
||||||
height: toRem(height),
|
height: toPx(height),
|
||||||
}}
|
}}
|
||||||
title={title} alt=""
|
title={title} alt=""
|
||||||
inputRef={inputRef}
|
inputRef={inputRef}
|
||||||
|
@ -222,15 +161,38 @@ export default createReactClass({
|
||||||
<img
|
<img
|
||||||
className="mx_BaseAvatar mx_BaseAvatar_image"
|
className="mx_BaseAvatar mx_BaseAvatar_image"
|
||||||
src={imageUrl}
|
src={imageUrl}
|
||||||
onError={this.onError}
|
onError={onError}
|
||||||
style={{
|
style={{
|
||||||
width: toRem(width),
|
width: toPx(width),
|
||||||
height: toRem(height),
|
height: toPx(height),
|
||||||
}}
|
}}
|
||||||
title={title} alt=""
|
title={title} alt=""
|
||||||
ref={inputRef}
|
ref={inputRef}
|
||||||
{...otherProps} />
|
{...otherProps} />
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
};
|
||||||
});
|
|
||||||
|
BaseAvatar.displayName = "BaseAvatar";
|
||||||
|
|
||||||
|
BaseAvatar.propTypes = {
|
||||||
|
name: PropTypes.string.isRequired, // The name (first initial used as default)
|
||||||
|
idName: PropTypes.string, // ID for generating hash colours
|
||||||
|
title: PropTypes.string, // onHover title text
|
||||||
|
url: PropTypes.string, // highest priority of them all, shortcut to set in urls[0]
|
||||||
|
urls: PropTypes.array, // [highest_priority, ... , lowest_priority]
|
||||||
|
width: PropTypes.number,
|
||||||
|
height: PropTypes.number,
|
||||||
|
// XXX resizeMethod not actually used.
|
||||||
|
resizeMethod: PropTypes.string,
|
||||||
|
defaultToInitialLetter: PropTypes.bool, // true to add default url
|
||||||
|
onClick: PropTypes.func,
|
||||||
|
inputRef: PropTypes.oneOfType([
|
||||||
|
// Either a function
|
||||||
|
PropTypes.func,
|
||||||
|
// Or the instance of a DOM native element
|
||||||
|
PropTypes.shape({ current: PropTypes.instanceOf(Element) }),
|
||||||
|
]),
|
||||||
|
};
|
||||||
|
|
||||||
|
export default BaseAvatar;
|
||||||
|
|
|
@ -18,9 +18,10 @@ limitations under the License.
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import createReactClass from 'create-react-class';
|
import createReactClass from 'create-react-class';
|
||||||
import * as Avatar from '../../../Avatar';
|
|
||||||
import * as sdk from "../../../index";
|
import * as sdk from "../../../index";
|
||||||
import dis from "../../../dispatcher";
|
import dis from "../../../dispatcher/dispatcher";
|
||||||
|
import {Action} from "../../../dispatcher/actions";
|
||||||
|
import {MatrixClientPeg} from "../../../MatrixClientPeg";
|
||||||
|
|
||||||
export default createReactClass({
|
export default createReactClass({
|
||||||
displayName: 'MemberAvatar',
|
displayName: 'MemberAvatar',
|
||||||
|
@ -33,7 +34,7 @@ export default createReactClass({
|
||||||
resizeMethod: PropTypes.string,
|
resizeMethod: PropTypes.string,
|
||||||
// The onClick to give the avatar
|
// The onClick to give the avatar
|
||||||
onClick: PropTypes.func,
|
onClick: PropTypes.func,
|
||||||
// Whether the onClick of the avatar should be overriden to dispatch 'view_user'
|
// Whether the onClick of the avatar should be overriden to dispatch `Action.ViewUser`
|
||||||
viewUserOnClick: PropTypes.bool,
|
viewUserOnClick: PropTypes.bool,
|
||||||
title: PropTypes.string,
|
title: PropTypes.string,
|
||||||
},
|
},
|
||||||
|
@ -61,10 +62,14 @@ export default createReactClass({
|
||||||
return {
|
return {
|
||||||
name: props.member.name,
|
name: props.member.name,
|
||||||
title: props.title || props.member.userId,
|
title: props.title || props.member.userId,
|
||||||
imageUrl: Avatar.avatarUrlForMember(props.member,
|
imageUrl: props.member.getAvatarUrl(
|
||||||
props.width,
|
MatrixClientPeg.get().getHomeserverUrl(),
|
||||||
props.height,
|
Math.floor(props.width * window.devicePixelRatio),
|
||||||
props.resizeMethod),
|
Math.floor(props.height * window.devicePixelRatio),
|
||||||
|
props.resizeMethod,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
),
|
||||||
};
|
};
|
||||||
} else if (props.fallbackUserId) {
|
} else if (props.fallbackUserId) {
|
||||||
return {
|
return {
|
||||||
|
@ -85,7 +90,7 @@ export default createReactClass({
|
||||||
if (viewUserOnClick) {
|
if (viewUserOnClick) {
|
||||||
onClick = () => {
|
onClick = () => {
|
||||||
dis.dispatch({
|
dis.dispatch({
|
||||||
action: 'view_user',
|
action: Action.ViewUser,
|
||||||
member: this.props.member,
|
member: this.props.member,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
|
@ -23,7 +23,7 @@ import createReactClass from 'create-react-class';
|
||||||
import {EventStatus} from 'matrix-js-sdk';
|
import {EventStatus} from 'matrix-js-sdk';
|
||||||
|
|
||||||
import {MatrixClientPeg} from '../../../MatrixClientPeg';
|
import {MatrixClientPeg} from '../../../MatrixClientPeg';
|
||||||
import dis from '../../../dispatcher';
|
import dis from '../../../dispatcher/dispatcher';
|
||||||
import * as sdk from '../../../index';
|
import * as sdk from '../../../index';
|
||||||
import { _t } from '../../../languageHandler';
|
import { _t } from '../../../languageHandler';
|
||||||
import Modal from '../../../Modal';
|
import Modal from '../../../Modal';
|
||||||
|
@ -116,11 +116,6 @@ export default createReactClass({
|
||||||
this.closeMenu();
|
this.closeMenu();
|
||||||
},
|
},
|
||||||
|
|
||||||
e2eInfoClicked: function() {
|
|
||||||
this.props.e2eInfoCallback();
|
|
||||||
this.closeMenu();
|
|
||||||
},
|
|
||||||
|
|
||||||
onReportEventClick: function() {
|
onReportEventClick: function() {
|
||||||
const ReportEventDialog = sdk.getComponent("dialogs.ReportEventDialog");
|
const ReportEventDialog = sdk.getComponent("dialogs.ReportEventDialog");
|
||||||
Modal.createTrackedDialog('Report Event', '', ReportEventDialog, {
|
Modal.createTrackedDialog('Report Event', '', ReportEventDialog, {
|
||||||
|
@ -465,15 +460,6 @@ export default createReactClass({
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let e2eInfo;
|
|
||||||
if (this.props.e2eInfoCallback) {
|
|
||||||
e2eInfo = (
|
|
||||||
<MenuItem className="mx_MessageContextMenu_field" onClick={this.e2eInfoClicked}>
|
|
||||||
{ _t('End-to-end encryption information') }
|
|
||||||
</MenuItem>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
let reportEventButton;
|
let reportEventButton;
|
||||||
if (mxEvent.getSender() !== me) {
|
if (mxEvent.getSender() !== me) {
|
||||||
reportEventButton = (
|
reportEventButton = (
|
||||||
|
@ -500,7 +486,6 @@ export default createReactClass({
|
||||||
{ quoteButton }
|
{ quoteButton }
|
||||||
{ externalURLButton }
|
{ externalURLButton }
|
||||||
{ collapseReplyThread }
|
{ collapseReplyThread }
|
||||||
{ e2eInfo }
|
|
||||||
{ reportEventButton }
|
{ reportEventButton }
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue