Merge branches 'develop' and 't3chguy/leaks' of github.com:matrix-org/matrix-react-sdk into t3chguy/leaks
Conflicts: src/components/views/avatars/BaseAvatar.js test/components/views/messages/TextualBody-test.js
This commit is contained in:
commit
880e16aaa2
222 changed files with 5383 additions and 1207 deletions
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)
|
||||||
|
|
|
@ -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
|
||||||
----------
|
----------
|
||||||
|
|
|
@ -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,11 @@
|
||||||
"@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/modernizr": "^3.5.3",
|
"@types/modernizr": "^3.5.3",
|
||||||
"@types/qrcode": "^1.3.4",
|
"@types/qrcode": "^1.3.4",
|
||||||
"@types/react": "16.9",
|
"@types/react": "16.9",
|
||||||
|
"@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",
|
||||||
"chokidar": "^3.3.1",
|
"chokidar": "^3.3.1",
|
||||||
|
|
|
@ -41,6 +41,7 @@
|
||||||
@import "./views/auth/_CountryDropdown.scss";
|
@import "./views/auth/_CountryDropdown.scss";
|
||||||
@import "./views/auth/_InteractiveAuthEntryComponents.scss";
|
@import "./views/auth/_InteractiveAuthEntryComponents.scss";
|
||||||
@import "./views/auth/_LanguageSelector.scss";
|
@import "./views/auth/_LanguageSelector.scss";
|
||||||
|
@import "./views/auth/_PassphraseField.scss";
|
||||||
@import "./views/auth/_ServerConfig.scss";
|
@import "./views/auth/_ServerConfig.scss";
|
||||||
@import "./views/auth/_ServerTypeSelector.scss";
|
@import "./views/auth/_ServerTypeSelector.scss";
|
||||||
@import "./views/auth/_Welcome.scss";
|
@import "./views/auth/_Welcome.scss";
|
||||||
|
@ -114,6 +115,7 @@
|
||||||
@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/_SyntaxHighlight.scss";
|
@import "./views/elements/_SyntaxHighlight.scss";
|
||||||
@import "./views/elements/_TextWithTooltip.scss";
|
@import "./views/elements/_TextWithTooltip.scss";
|
||||||
|
@ -161,6 +163,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";
|
||||||
|
@ -203,6 +207,7 @@
|
||||||
@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";
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -146,27 +146,3 @@ limitations under the License.
|
||||||
.mx_AuthBody_spinner {
|
.mx_AuthBody_spinner {
|
||||||
margin: 1em 0;
|
margin: 1em 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_AuthBody_passwordScore {
|
|
||||||
width: 100%;
|
|
||||||
appearance: none;
|
|
||||||
height: 4px;
|
|
||||||
border: 0;
|
|
||||||
border-radius: 2px;
|
|
||||||
position: absolute;
|
|
||||||
top: -12px;
|
|
||||||
|
|
||||||
&::-moz-progress-bar {
|
|
||||||
border-radius: 2px;
|
|
||||||
background-color: $accent-color;
|
|
||||||
}
|
|
||||||
|
|
||||||
&::-webkit-progress-bar,
|
|
||||||
&::-webkit-progress-value {
|
|
||||||
border-radius: 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&::-webkit-progress-value {
|
|
||||||
background-color: $accent-color;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
55
res/css/views/auth/_PassphraseField.scss
Normal file
55
res/css/views/auth/_PassphraseField.scss
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
/*
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
$PassphraseStrengthHigh: $accent-color;
|
||||||
|
$PassphraseStrengthMedium: $username-variant5-color;
|
||||||
|
$PassphraseStrengthLow: $notice-primary-color;
|
||||||
|
|
||||||
|
@define-mixin ProgressBarColour $colour {
|
||||||
|
color: $colour;
|
||||||
|
&::-moz-progress-bar {
|
||||||
|
background-color: $colour;
|
||||||
|
}
|
||||||
|
&::-webkit-progress-value {
|
||||||
|
background-color: $colour;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
progress.mx_PassphraseField_progress {
|
||||||
|
appearance: none;
|
||||||
|
width: 100%;
|
||||||
|
border: 0;
|
||||||
|
height: 4px;
|
||||||
|
position: absolute;
|
||||||
|
top: -12px;
|
||||||
|
|
||||||
|
border-radius: 2px;
|
||||||
|
&::-moz-progress-bar {
|
||||||
|
border-radius: 2px;
|
||||||
|
}
|
||||||
|
&::-webkit-progress-bar,
|
||||||
|
&::-webkit-progress-value {
|
||||||
|
border-radius: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@mixin ProgressBarColour $PassphraseStrengthLow;
|
||||||
|
&[value="2"], &[value="3"] {
|
||||||
|
@mixin ProgressBarColour $PassphraseStrengthMedium;
|
||||||
|
}
|
||||||
|
&[value="4"] {
|
||||||
|
@mixin ProgressBarColour $PassphraseStrengthHigh;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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');
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,17 +35,6 @@ limitations under the License.
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_CreateKeyBackupDialog_passPhraseHelp {
|
|
||||||
flex: 1;
|
|
||||||
height: 85px;
|
|
||||||
margin-left: 20px;
|
|
||||||
font-size: 80%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_CreateKeyBackupDialog_passPhraseHelp progress {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_CreateKeyBackupDialog_passPhraseInput {
|
.mx_CreateKeyBackupDialog_passPhraseInput {
|
||||||
flex: none;
|
flex: none;
|
||||||
width: 250px;
|
width: 250px;
|
||||||
|
|
|
@ -68,17 +68,6 @@ limitations under the License.
|
||||||
margin-top: 0px;
|
margin-top: 0px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_CreateSecretStorageDialog_passPhraseHelp {
|
|
||||||
flex: 1;
|
|
||||||
height: 64px;
|
|
||||||
margin-left: 20px;
|
|
||||||
font-size: 80%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_CreateSecretStorageDialog_passPhraseHelp progress {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_CreateSecretStorageDialog_passPhraseMatch {
|
.mx_CreateSecretStorageDialog_passPhraseMatch {
|
||||||
width: 200px;
|
width: 200px;
|
||||||
margin-left: 20px;
|
margin-left: 20px;
|
||||||
|
|
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%;
|
||||||
|
}
|
|
@ -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 {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
214
res/css/views/rooms/_IRCLayout.scss
Normal file
214
res/css/views/rooms/_IRCLayout.scss
Normal file
|
@ -0,0 +1,214 @@
|
||||||
|
/*
|
||||||
|
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: 4;
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
> .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;
|
||||||
|
top: -2px;
|
||||||
|
padding: 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: 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.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: 32px;
|
||||||
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 {
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
// @flow
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Copyright 2016 Aviral Dasgupta
|
Copyright 2016 Aviral Dasgupta
|
||||||
Copyright 2016 OpenMarket Ltd
|
Copyright 2016 OpenMarket Ltd
|
||||||
|
@ -19,9 +17,10 @@ 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";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Base class for classes that provide platform-specific functionality
|
* Base class for classes that provide platform-specific functionality
|
||||||
|
@ -29,27 +28,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;
|
||||||
|
@ -84,22 +81,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 +106,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 +125,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 +137,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");
|
||||||
}
|
}
|
||||||
|
|
|
@ -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';
|
||||||
|
|
|
@ -18,7 +18,7 @@ limitations under the License.
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
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 * as sdk from './index';
|
import * as sdk from './index';
|
||||||
import { _t } from './languageHandler';
|
import { _t } from './languageHandler';
|
||||||
|
|
51
src/FontWatcher.js
Normal file
51
src/FontWatcher.js
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
/*
|
||||||
|
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 dis from './dispatcher/dispatcher';
|
||||||
|
import SettingsStore, {SettingLevel} from './settings/SettingsStore';
|
||||||
|
|
||||||
|
export class FontWatcher {
|
||||||
|
static MIN_SIZE = 13;
|
||||||
|
static MAX_SIZE = 20;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this._dispatcherRef = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
start() {
|
||||||
|
this._setRootFontSize(SettingsStore.getValue("fontSize"));
|
||||||
|
this._dispatcherRef = dis.register(this._onAction);
|
||||||
|
}
|
||||||
|
|
||||||
|
stop() {
|
||||||
|
dis.unregister(this._dispatcherRef);
|
||||||
|
}
|
||||||
|
|
||||||
|
_onAction = (payload) => {
|
||||||
|
if (payload.action === 'update-font-size') {
|
||||||
|
this._setRootFontSize(payload.size);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
_setRootFontSize = (size) => {
|
||||||
|
const fontSize = Math.max(Math.min(FontWatcher.MAX_SIZE, size), FontWatcher.MIN_SIZE);
|
||||||
|
|
||||||
|
if (fontSize != size) {
|
||||||
|
SettingsStore.setValue("fontSize", null, SettingLevel.Device, fontSize);
|
||||||
|
}
|
||||||
|
document.querySelector(":root").style.fontSize = fontSize + "px";
|
||||||
|
};
|
||||||
|
}
|
|
@ -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";
|
||||||
|
|
|
@ -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';
|
||||||
|
|
|
@ -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,7 +21,7 @@ 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';
|
||||||
|
|
|
@ -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 {
|
||||||
|
@ -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);
|
||||||
}
|
}
|
|
@ -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() {
|
||||||
|
|
|
@ -15,17 +15,17 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
import React, {createRef} from 'react';
|
||||||
import FileSaver from 'file-saver';
|
import FileSaver from 'file-saver';
|
||||||
import * as sdk from '../../../../index';
|
import * as sdk from '../../../../index';
|
||||||
import {MatrixClientPeg} from '../../../../MatrixClientPeg';
|
import {MatrixClientPeg} from '../../../../MatrixClientPeg';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { scorePassword } from '../../../../utils/PasswordScorer';
|
import {_t, _td} from '../../../../languageHandler';
|
||||||
import { _t } from '../../../../languageHandler';
|
|
||||||
import { accessSecretStorage } from '../../../../CrossSigningManager';
|
import { accessSecretStorage } from '../../../../CrossSigningManager';
|
||||||
import SettingsStore from '../../../../settings/SettingsStore';
|
import SettingsStore from '../../../../settings/SettingsStore';
|
||||||
import AccessibleButton from "../../../../components/views/elements/AccessibleButton";
|
import AccessibleButton from "../../../../components/views/elements/AccessibleButton";
|
||||||
import {copyNode} from "../../../../utils/strings";
|
import {copyNode} from "../../../../utils/strings";
|
||||||
|
import PassphraseField from "../../../../components/views/auth/PassphraseField";
|
||||||
|
|
||||||
const PHASE_PASSPHRASE = 0;
|
const PHASE_PASSPHRASE = 0;
|
||||||
const PHASE_PASSPHRASE_CONFIRM = 1;
|
const PHASE_PASSPHRASE_CONFIRM = 1;
|
||||||
|
@ -36,7 +36,6 @@ const PHASE_DONE = 5;
|
||||||
const PHASE_OPTOUT_CONFIRM = 6;
|
const PHASE_OPTOUT_CONFIRM = 6;
|
||||||
|
|
||||||
const PASSWORD_MIN_SCORE = 4; // So secure, many characters, much complex, wow, etc, etc.
|
const PASSWORD_MIN_SCORE = 4; // So secure, many characters, much complex, wow, etc, etc.
|
||||||
const PASSPHRASE_FEEDBACK_DELAY = 500; // How long after keystroke to offer passphrase feedback, ms.
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Walks the user through the process of creating an e2e key backup
|
* Walks the user through the process of creating an e2e key backup
|
||||||
|
@ -52,17 +51,18 @@ export default class CreateKeyBackupDialog extends React.PureComponent {
|
||||||
|
|
||||||
this._recoveryKeyNode = null;
|
this._recoveryKeyNode = null;
|
||||||
this._keyBackupInfo = null;
|
this._keyBackupInfo = null;
|
||||||
this._setZxcvbnResultTimeout = null;
|
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
secureSecretStorage: null,
|
secureSecretStorage: null,
|
||||||
phase: PHASE_PASSPHRASE,
|
phase: PHASE_PASSPHRASE,
|
||||||
passPhrase: '',
|
passPhrase: '',
|
||||||
|
passPhraseValid: false,
|
||||||
passPhraseConfirm: '',
|
passPhraseConfirm: '',
|
||||||
copied: false,
|
copied: false,
|
||||||
downloaded: false,
|
downloaded: false,
|
||||||
zxcvbnResult: null,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
this._passphraseField = createRef();
|
||||||
}
|
}
|
||||||
|
|
||||||
async componentDidMount() {
|
async componentDidMount() {
|
||||||
|
@ -81,12 +81,6 @@ export default class CreateKeyBackupDialog extends React.PureComponent {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
|
||||||
if (this._setZxcvbnResultTimeout !== null) {
|
|
||||||
clearTimeout(this._setZxcvbnResultTimeout);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_collectRecoveryKeyNode = (n) => {
|
_collectRecoveryKeyNode = (n) => {
|
||||||
this._recoveryKeyNode = n;
|
this._recoveryKeyNode = n;
|
||||||
}
|
}
|
||||||
|
@ -180,22 +174,16 @@ export default class CreateKeyBackupDialog extends React.PureComponent {
|
||||||
|
|
||||||
_onPassPhraseNextClick = async (e) => {
|
_onPassPhraseNextClick = async (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
if (!this._passphraseField.current) return; // unmounting
|
||||||
|
|
||||||
// If we're waiting for the timeout before updating the result at this point,
|
await this._passphraseField.current.validate({ allowEmpty: false });
|
||||||
// skip ahead and do it now, otherwise we'll deny the attempt to proceed
|
if (!this._passphraseField.current.state.valid) {
|
||||||
// even if the user entered a valid passphrase
|
this._passphraseField.current.focus();
|
||||||
if (this._setZxcvbnResultTimeout !== null) {
|
this._passphraseField.current.validate({ allowEmpty: false, focused: true });
|
||||||
clearTimeout(this._setZxcvbnResultTimeout);
|
return;
|
||||||
this._setZxcvbnResultTimeout = null;
|
|
||||||
await new Promise((resolve) => {
|
|
||||||
this.setState({
|
|
||||||
zxcvbnResult: scorePassword(this.state.passPhrase),
|
|
||||||
}, resolve);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
if (this._passPhraseIsValid()) {
|
|
||||||
this.setState({phase: PHASE_PASSPHRASE_CONFIRM});
|
this.setState({phase: PHASE_PASSPHRASE_CONFIRM});
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
_onPassPhraseConfirmNextClick = async (e) => {
|
_onPassPhraseConfirmNextClick = async (e) => {
|
||||||
|
@ -214,9 +202,9 @@ export default class CreateKeyBackupDialog extends React.PureComponent {
|
||||||
_onSetAgainClick = () => {
|
_onSetAgainClick = () => {
|
||||||
this.setState({
|
this.setState({
|
||||||
passPhrase: '',
|
passPhrase: '',
|
||||||
|
passPhraseValid: false,
|
||||||
passPhraseConfirm: '',
|
passPhraseConfirm: '',
|
||||||
phase: PHASE_PASSPHRASE,
|
phase: PHASE_PASSPHRASE,
|
||||||
zxcvbnResult: null,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -226,23 +214,16 @@ export default class CreateKeyBackupDialog extends React.PureComponent {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_onPassPhraseValidate = (result) => {
|
||||||
|
this.setState({
|
||||||
|
passPhraseValid: result.valid,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
_onPassPhraseChange = (e) => {
|
_onPassPhraseChange = (e) => {
|
||||||
this.setState({
|
this.setState({
|
||||||
passPhrase: e.target.value,
|
passPhrase: e.target.value,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (this._setZxcvbnResultTimeout !== null) {
|
|
||||||
clearTimeout(this._setZxcvbnResultTimeout);
|
|
||||||
}
|
|
||||||
this._setZxcvbnResultTimeout = setTimeout(() => {
|
|
||||||
this._setZxcvbnResultTimeout = null;
|
|
||||||
this.setState({
|
|
||||||
// precompute this and keep it in state: zxcvbn is fast but
|
|
||||||
// we use it in a couple of different places so no point recomputing
|
|
||||||
// it unnecessarily.
|
|
||||||
zxcvbnResult: scorePassword(this.state.passPhrase),
|
|
||||||
});
|
|
||||||
}, PASSPHRASE_FEEDBACK_DELAY);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_onPassPhraseConfirmChange = (e) => {
|
_onPassPhraseConfirmChange = (e) => {
|
||||||
|
@ -251,35 +232,9 @@ export default class CreateKeyBackupDialog extends React.PureComponent {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
_passPhraseIsValid() {
|
|
||||||
return this.state.zxcvbnResult && this.state.zxcvbnResult.score >= PASSWORD_MIN_SCORE;
|
|
||||||
}
|
|
||||||
|
|
||||||
_renderPhasePassPhrase() {
|
_renderPhasePassPhrase() {
|
||||||
const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
|
const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
|
||||||
|
|
||||||
let strengthMeter;
|
|
||||||
let helpText;
|
|
||||||
if (this.state.zxcvbnResult) {
|
|
||||||
if (this.state.zxcvbnResult.score >= PASSWORD_MIN_SCORE) {
|
|
||||||
helpText = _t("Great! This recovery passphrase looks strong enough.");
|
|
||||||
} else {
|
|
||||||
const suggestions = [];
|
|
||||||
for (let i = 0; i < this.state.zxcvbnResult.feedback.suggestions.length; ++i) {
|
|
||||||
suggestions.push(<div key={i}>{this.state.zxcvbnResult.feedback.suggestions[i]}</div>);
|
|
||||||
}
|
|
||||||
const suggestionBlock = <div>{suggestions.length > 0 ? suggestions : _t("Keep going...")}</div>;
|
|
||||||
|
|
||||||
helpText = <div>
|
|
||||||
{this.state.zxcvbnResult.feedback.warning}
|
|
||||||
{suggestionBlock}
|
|
||||||
</div>;
|
|
||||||
}
|
|
||||||
strengthMeter = <div>
|
|
||||||
<progress max={PASSWORD_MIN_SCORE} value={this.state.zxcvbnResult.score} />
|
|
||||||
</div>;
|
|
||||||
}
|
|
||||||
|
|
||||||
return <form onSubmit={this._onPassPhraseNextClick}>
|
return <form onSubmit={this._onPassPhraseNextClick}>
|
||||||
<p>{_t(
|
<p>{_t(
|
||||||
"<b>Warning</b>: You should only set up key backup from a trusted computer.", {},
|
"<b>Warning</b>: You should only set up key backup from a trusted computer.", {},
|
||||||
|
@ -293,17 +248,19 @@ export default class CreateKeyBackupDialog extends React.PureComponent {
|
||||||
|
|
||||||
<div className="mx_CreateKeyBackupDialog_primaryContainer">
|
<div className="mx_CreateKeyBackupDialog_primaryContainer">
|
||||||
<div className="mx_CreateKeyBackupDialog_passPhraseContainer">
|
<div className="mx_CreateKeyBackupDialog_passPhraseContainer">
|
||||||
<input type="password"
|
<PassphraseField
|
||||||
onChange={this._onPassPhraseChange}
|
|
||||||
value={this.state.passPhrase}
|
|
||||||
className="mx_CreateKeyBackupDialog_passPhraseInput"
|
className="mx_CreateKeyBackupDialog_passPhraseInput"
|
||||||
placeholder={_t("Enter a recovery passphrase...")}
|
onChange={this._onPassPhraseChange}
|
||||||
|
minScore={PASSWORD_MIN_SCORE}
|
||||||
|
value={this.state.passPhrase}
|
||||||
|
onValidate={this._onPassPhraseValidate}
|
||||||
|
fieldRef={this._passphraseField}
|
||||||
autoFocus={true}
|
autoFocus={true}
|
||||||
|
label={_td("Enter a recovery passphrase")}
|
||||||
|
labelEnterPassword={_td("Enter a recovery passphrase")}
|
||||||
|
labelStrongPassword={_td("Great! This recovery passphrase looks strong enough.")}
|
||||||
|
labelAllowedButUnsafe={_td("Great! This recovery passphrase looks strong enough.")}
|
||||||
/>
|
/>
|
||||||
<div className="mx_CreateKeyBackupDialog_passPhraseHelp">
|
|
||||||
{strengthMeter}
|
|
||||||
{helpText}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -311,7 +268,7 @@ export default class CreateKeyBackupDialog extends React.PureComponent {
|
||||||
primaryButton={_t('Next')}
|
primaryButton={_t('Next')}
|
||||||
onPrimaryButtonClick={this._onPassPhraseNextClick}
|
onPrimaryButtonClick={this._onPassPhraseNextClick}
|
||||||
hasCancel={false}
|
hasCancel={false}
|
||||||
disabled={!this._passPhraseIsValid()}
|
disabled={!this.state.passPhraseValid}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
|
|
|
@ -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 = () => {
|
||||||
|
|
|
@ -15,17 +15,17 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
import React, {createRef} 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 { scorePassword } from '../../../../utils/PasswordScorer';
|
|
||||||
import FileSaver from 'file-saver';
|
import FileSaver from 'file-saver';
|
||||||
import { _t } from '../../../../languageHandler';
|
import {_t, _td} from '../../../../languageHandler';
|
||||||
import Modal from '../../../../Modal';
|
import Modal from '../../../../Modal';
|
||||||
import { promptForBackupPassphrase } from '../../../../CrossSigningManager';
|
import { promptForBackupPassphrase } from '../../../../CrossSigningManager';
|
||||||
import {copyNode} from "../../../../utils/strings";
|
import {copyNode} from "../../../../utils/strings";
|
||||||
import {SSOAuthEntry} from "../../../../components/views/auth/InteractiveAuthEntryComponents";
|
import {SSOAuthEntry} from "../../../../components/views/auth/InteractiveAuthEntryComponents";
|
||||||
|
import PassphraseField from "../../../../components/views/auth/PassphraseField";
|
||||||
|
|
||||||
const PHASE_LOADING = 0;
|
const PHASE_LOADING = 0;
|
||||||
const PHASE_LOADERROR = 1;
|
const PHASE_LOADERROR = 1;
|
||||||
|
@ -39,7 +39,6 @@ const PHASE_DONE = 8;
|
||||||
const PHASE_CONFIRM_SKIP = 9;
|
const PHASE_CONFIRM_SKIP = 9;
|
||||||
|
|
||||||
const PASSWORD_MIN_SCORE = 4; // So secure, many characters, much complex, wow, etc, etc.
|
const PASSWORD_MIN_SCORE = 4; // So secure, many characters, much complex, wow, etc, etc.
|
||||||
const PASSPHRASE_FEEDBACK_DELAY = 500; // How long after keystroke to offer passphrase feedback, ms.
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Walks the user through the process of creating a passphrase to guard Secure
|
* Walks the user through the process of creating a passphrase to guard Secure
|
||||||
|
@ -62,16 +61,15 @@ export default class CreateSecretStorageDialog extends React.PureComponent {
|
||||||
|
|
||||||
this._recoveryKey = null;
|
this._recoveryKey = null;
|
||||||
this._recoveryKeyNode = null;
|
this._recoveryKeyNode = null;
|
||||||
this._setZxcvbnResultTimeout = null;
|
|
||||||
this._backupKey = null;
|
this._backupKey = null;
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
phase: PHASE_LOADING,
|
phase: PHASE_LOADING,
|
||||||
passPhrase: '',
|
passPhrase: '',
|
||||||
|
passPhraseValid: false,
|
||||||
passPhraseConfirm: '',
|
passPhraseConfirm: '',
|
||||||
copied: false,
|
copied: false,
|
||||||
downloaded: false,
|
downloaded: false,
|
||||||
zxcvbnResult: null,
|
|
||||||
backupInfo: null,
|
backupInfo: null,
|
||||||
backupSigStatus: null,
|
backupSigStatus: null,
|
||||||
// does the server offer a UI auth flow with just m.login.password
|
// does the server offer a UI auth flow with just m.login.password
|
||||||
|
@ -83,6 +81,8 @@ export default class CreateSecretStorageDialog extends React.PureComponent {
|
||||||
useKeyBackup: true,
|
useKeyBackup: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
this._passphraseField = createRef();
|
||||||
|
|
||||||
this._fetchBackupInfo();
|
this._fetchBackupInfo();
|
||||||
if (this.state.accountPassword) {
|
if (this.state.accountPassword) {
|
||||||
// If we have an account password in memory, let's simplify and
|
// If we have an account password in memory, let's simplify and
|
||||||
|
@ -99,9 +99,6 @@ export default class CreateSecretStorageDialog extends React.PureComponent {
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
MatrixClientPeg.get().removeListener('crypto.keyBackupStatus', this._onKeyBackupStatusChange);
|
MatrixClientPeg.get().removeListener('crypto.keyBackupStatus', this._onKeyBackupStatusChange);
|
||||||
if (this._setZxcvbnResultTimeout !== null) {
|
|
||||||
clearTimeout(this._setZxcvbnResultTimeout);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async _fetchBackupInfo() {
|
async _fetchBackupInfo() {
|
||||||
|
@ -364,22 +361,16 @@ export default class CreateSecretStorageDialog extends React.PureComponent {
|
||||||
|
|
||||||
_onPassPhraseNextClick = async (e) => {
|
_onPassPhraseNextClick = async (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
if (!this._passphraseField.current) return; // unmounting
|
||||||
|
|
||||||
// If we're waiting for the timeout before updating the result at this point,
|
await this._passphraseField.current.validate({ allowEmpty: false });
|
||||||
// skip ahead and do it now, otherwise we'll deny the attempt to proceed
|
if (!this._passphraseField.current.state.valid) {
|
||||||
// even if the user entered a valid passphrase
|
this._passphraseField.current.focus();
|
||||||
if (this._setZxcvbnResultTimeout !== null) {
|
this._passphraseField.current.validate({ allowEmpty: false, focused: true });
|
||||||
clearTimeout(this._setZxcvbnResultTimeout);
|
return;
|
||||||
this._setZxcvbnResultTimeout = null;
|
|
||||||
await new Promise((resolve) => {
|
|
||||||
this.setState({
|
|
||||||
zxcvbnResult: scorePassword(this.state.passPhrase),
|
|
||||||
}, resolve);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
if (this._passPhraseIsValid()) {
|
|
||||||
this.setState({phase: PHASE_PASSPHRASE_CONFIRM});
|
this.setState({phase: PHASE_PASSPHRASE_CONFIRM});
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
_onPassPhraseConfirmNextClick = async (e) => {
|
_onPassPhraseConfirmNextClick = async (e) => {
|
||||||
|
@ -399,9 +390,9 @@ export default class CreateSecretStorageDialog extends React.PureComponent {
|
||||||
_onSetAgainClick = () => {
|
_onSetAgainClick = () => {
|
||||||
this.setState({
|
this.setState({
|
||||||
passPhrase: '',
|
passPhrase: '',
|
||||||
|
passPhraseValid: false,
|
||||||
passPhraseConfirm: '',
|
passPhraseConfirm: '',
|
||||||
phase: PHASE_PASSPHRASE,
|
phase: PHASE_PASSPHRASE,
|
||||||
zxcvbnResult: null,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -411,23 +402,16 @@ export default class CreateSecretStorageDialog extends React.PureComponent {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_onPassPhraseValidate = (result) => {
|
||||||
|
this.setState({
|
||||||
|
passPhraseValid: result.valid,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
_onPassPhraseChange = (e) => {
|
_onPassPhraseChange = (e) => {
|
||||||
this.setState({
|
this.setState({
|
||||||
passPhrase: e.target.value,
|
passPhrase: e.target.value,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (this._setZxcvbnResultTimeout !== null) {
|
|
||||||
clearTimeout(this._setZxcvbnResultTimeout);
|
|
||||||
}
|
|
||||||
this._setZxcvbnResultTimeout = setTimeout(() => {
|
|
||||||
this._setZxcvbnResultTimeout = null;
|
|
||||||
this.setState({
|
|
||||||
// precompute this and keep it in state: zxcvbn is fast but
|
|
||||||
// we use it in a couple of different places so no point recomputing
|
|
||||||
// it unnecessarily.
|
|
||||||
zxcvbnResult: scorePassword(this.state.passPhrase),
|
|
||||||
});
|
|
||||||
}, PASSPHRASE_FEEDBACK_DELAY);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_onPassPhraseConfirmChange = (e) => {
|
_onPassPhraseConfirmChange = (e) => {
|
||||||
|
@ -436,10 +420,6 @@ export default class CreateSecretStorageDialog extends React.PureComponent {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
_passPhraseIsValid() {
|
|
||||||
return this.state.zxcvbnResult && this.state.zxcvbnResult.score >= PASSWORD_MIN_SCORE;
|
|
||||||
}
|
|
||||||
|
|
||||||
_onAccountPasswordChange = (e) => {
|
_onAccountPasswordChange = (e) => {
|
||||||
this.setState({
|
this.setState({
|
||||||
accountPassword: e.target.value,
|
accountPassword: e.target.value,
|
||||||
|
@ -502,37 +482,9 @@ export default class CreateSecretStorageDialog extends React.PureComponent {
|
||||||
|
|
||||||
_renderPhasePassPhrase() {
|
_renderPhasePassPhrase() {
|
||||||
const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
|
const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
|
||||||
const Field = sdk.getComponent('views.elements.Field');
|
|
||||||
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
|
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
|
||||||
const LabelledToggleSwitch = sdk.getComponent('views.elements.LabelledToggleSwitch');
|
const LabelledToggleSwitch = sdk.getComponent('views.elements.LabelledToggleSwitch');
|
||||||
|
|
||||||
let strengthMeter;
|
|
||||||
let helpText;
|
|
||||||
if (this.state.zxcvbnResult) {
|
|
||||||
if (this.state.zxcvbnResult.score >= PASSWORD_MIN_SCORE) {
|
|
||||||
helpText = _t("Great! This recovery passphrase looks strong enough.");
|
|
||||||
} else {
|
|
||||||
// We take the warning from zxcvbn or failing that, the first
|
|
||||||
// suggestion. In practice The first is generally the most relevant
|
|
||||||
// and it's probably better to present the user with one thing to
|
|
||||||
// improve about their password than a whole collection - it can
|
|
||||||
// spit out a warning and multiple suggestions which starts getting
|
|
||||||
// very information-dense.
|
|
||||||
const suggestion = (
|
|
||||||
this.state.zxcvbnResult.feedback.warning ||
|
|
||||||
this.state.zxcvbnResult.feedback.suggestions[0]
|
|
||||||
);
|
|
||||||
const suggestionBlock = <div>{suggestion || _t("Keep going...")}</div>;
|
|
||||||
|
|
||||||
helpText = <div>
|
|
||||||
{suggestionBlock}
|
|
||||||
</div>;
|
|
||||||
}
|
|
||||||
strengthMeter = <div>
|
|
||||||
<progress max={PASSWORD_MIN_SCORE} value={this.state.zxcvbnResult.score} />
|
|
||||||
</div>;
|
|
||||||
}
|
|
||||||
|
|
||||||
return <form onSubmit={this._onPassPhraseNextClick}>
|
return <form onSubmit={this._onPassPhraseNextClick}>
|
||||||
<p>{_t(
|
<p>{_t(
|
||||||
"Set a recovery passphrase to secure encrypted information and recover it if you log out. " +
|
"Set a recovery passphrase to secure encrypted information and recover it if you log out. " +
|
||||||
|
@ -540,19 +492,19 @@ export default class CreateSecretStorageDialog extends React.PureComponent {
|
||||||
)}</p>
|
)}</p>
|
||||||
|
|
||||||
<div className="mx_CreateSecretStorageDialog_passPhraseContainer">
|
<div className="mx_CreateSecretStorageDialog_passPhraseContainer">
|
||||||
<Field
|
<PassphraseField
|
||||||
type="password"
|
|
||||||
className="mx_CreateSecretStorageDialog_passPhraseField"
|
className="mx_CreateSecretStorageDialog_passPhraseField"
|
||||||
onChange={this._onPassPhraseChange}
|
onChange={this._onPassPhraseChange}
|
||||||
|
minScore={PASSWORD_MIN_SCORE}
|
||||||
value={this.state.passPhrase}
|
value={this.state.passPhrase}
|
||||||
label={_t("Enter a recovery passphrase")}
|
onValidate={this._onPassPhraseValidate}
|
||||||
|
fieldRef={this._passphraseField}
|
||||||
autoFocus={true}
|
autoFocus={true}
|
||||||
autoComplete="new-password"
|
label={_td("Enter a recovery passphrase")}
|
||||||
|
labelEnterPassword={_td("Enter a recovery passphrase")}
|
||||||
|
labelStrongPassword={_td("Great! This recovery passphrase looks strong enough.")}
|
||||||
|
labelAllowedButUnsafe={_td("Great! This recovery passphrase looks strong enough.")}
|
||||||
/>
|
/>
|
||||||
<div className="mx_CreateSecretStorageDialog_passPhraseHelp">
|
|
||||||
{strengthMeter}
|
|
||||||
{helpText}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<LabelledToggleSwitch
|
<LabelledToggleSwitch
|
||||||
|
@ -564,7 +516,7 @@ export default class CreateSecretStorageDialog extends React.PureComponent {
|
||||||
primaryButton={_t('Continue')}
|
primaryButton={_t('Continue')}
|
||||||
onPrimaryButtonClick={this._onPassPhraseNextClick}
|
onPrimaryButtonClick={this._onPassPhraseNextClick}
|
||||||
hasCancel={false}
|
hasCancel={false}
|
||||||
disabled={!this._passPhraseIsValid()}
|
disabled={!this.state.passPhraseValid}
|
||||||
>
|
>
|
||||||
<button type="button"
|
<button type="button"
|
||||||
onClick={this._onSkipSetupClick}
|
onClick={this._onSkipSetupClick}
|
||||||
|
|
|
@ -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';
|
||||||
|
|
|
@ -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, MatrixClientCreds} 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,8 @@ 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";
|
||||||
// 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.
|
||||||
|
@ -297,18 +298,18 @@ class LoggedInView extends React.PureComponent<IProps, IState> {
|
||||||
};
|
};
|
||||||
|
|
||||||
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 () => {
|
_updateServerNoticeEvents = async () => {
|
||||||
const roomLists = RoomListStore.getRoomLists();
|
const roomLists = RoomListStoreTempProxy.getRoomLists();
|
||||||
if (!roomLists['m.server_notice']) return [];
|
if (!roomLists[DefaultTagID.ServerNotice]) return [];
|
||||||
|
|
||||||
const pinnedEvents = [];
|
const pinnedEvents = [];
|
||||||
for (const room of roomLists['m.server_notice']) {
|
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;
|
||||||
|
|
|
@ -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';
|
||||||
|
@ -57,16 +56,18 @@ 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 "../../theme";
|
||||||
|
import { FontWatcher } from '../../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";
|
||||||
|
|
||||||
/** constants for MatrixChat.state.view */
|
/** constants for MatrixChat.state.view */
|
||||||
export enum Views {
|
export enum Views {
|
||||||
|
@ -107,7 +108,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',
|
||||||
|
@ -216,6 +217,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);
|
||||||
|
@ -283,8 +285,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;
|
||||||
|
|
||||||
|
@ -367,6 +372,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 +619,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);
|
||||||
|
@ -1621,9 +1627,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 +1759,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,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -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'];
|
||||||
|
@ -109,14 +110,16 @@ export default class MessagePanel extends React.Component {
|
||||||
showReactions: PropTypes.bool,
|
showReactions: 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
|
||||||
// display 'ghost' read markers that are animating away
|
// display 'ghost' read markers that are animating away
|
||||||
ghostReadMarkers: [],
|
ghostReadMarkers: [],
|
||||||
showTypingNotifications: SettingsStore.getValue("showTypingNotifications"),
|
showTypingNotifications: SettingsStore.getValue("showTypingNotifications"),
|
||||||
|
useIRCLayout: this.useIRCLayout(SettingsStore.getValue("feature_irc_ui")),
|
||||||
};
|
};
|
||||||
|
|
||||||
// opaque readreceipt info for each userId; used by ReadReceiptMarker
|
// opaque readreceipt info for each userId; used by ReadReceiptMarker
|
||||||
|
@ -169,6 +172,8 @@ export default class MessagePanel extends React.Component {
|
||||||
|
|
||||||
this._showTypingNotificationsWatcherRef =
|
this._showTypingNotificationsWatcherRef =
|
||||||
SettingsStore.watchSetting("showTypingNotifications", null, this.onShowTypingNotificationsChange);
|
SettingsStore.watchSetting("showTypingNotifications", null, this.onShowTypingNotificationsChange);
|
||||||
|
|
||||||
|
this._layoutWatcherRef = SettingsStore.watchSetting("feature_irc_ui", null, this.onLayoutChange);
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
|
@ -178,6 +183,7 @@ export default class MessagePanel extends React.Component {
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
this._isMounted = false;
|
this._isMounted = false;
|
||||||
SettingsStore.unwatchSetting(this._showTypingNotificationsWatcherRef);
|
SettingsStore.unwatchSetting(this._showTypingNotificationsWatcherRef);
|
||||||
|
SettingsStore.unwatchSetting(this._layoutWatcherRef);
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate(prevProps, prevState) {
|
componentDidUpdate(prevProps, prevState) {
|
||||||
|
@ -196,6 +202,17 @@ export default class MessagePanel extends React.Component {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
onLayoutChange = () => {
|
||||||
|
this.setState({
|
||||||
|
useIRCLayout: this.useIRCLayout(SettingsStore.getValue("feature_irc_ui")),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
useIRCLayout(ircLayoutSelected) {
|
||||||
|
// if room is null we are not in a normal room list
|
||||||
|
return ircLayoutSelected && this.props.room;
|
||||||
|
}
|
||||||
|
|
||||||
/* get the DOM node representing the given event */
|
/* get the DOM node representing the given event */
|
||||||
getNodeForEventId(eventId) {
|
getNodeForEventId(eventId) {
|
||||||
if (!this.eventNodes) {
|
if (!this.eventNodes) {
|
||||||
|
@ -597,6 +614,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.state.useIRCLayout}
|
||||||
/>
|
/>
|
||||||
</TileErrorBoundary>
|
</TileErrorBoundary>
|
||||||
</li>,
|
</li>,
|
||||||
|
@ -779,6 +797,8 @@ export default class MessagePanel extends React.Component {
|
||||||
this.props.className,
|
this.props.className,
|
||||||
{
|
{
|
||||||
"mx_MessagePanel_alwaysShowTimestamps": this.props.alwaysShowTimestamps,
|
"mx_MessagePanel_alwaysShowTimestamps": this.props.alwaysShowTimestamps,
|
||||||
|
"mx_IRCLayout": this.state.useIRCLayout,
|
||||||
|
"mx_GroupLayout": !this.state.useIRCLayout,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -792,6 +812,15 @@ export default class MessagePanel extends React.Component {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let ircResizer = null;
|
||||||
|
if (this.state.useIRCLayout) {
|
||||||
|
ircResizer = <IRCTimelineProfileResizer
|
||||||
|
minWidth={20}
|
||||||
|
maxWidth={600}
|
||||||
|
roomId={this.props.room ? this.props.roomroomId : null}
|
||||||
|
/>;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ErrorBoundary>
|
<ErrorBoundary>
|
||||||
<ScrollPanel
|
<ScrollPanel
|
||||||
|
@ -804,6 +833,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';
|
||||||
|
|
|
@ -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';
|
||||||
|
|
|
@ -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';
|
||||||
|
|
|
@ -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';
|
||||||
|
|
||||||
|
|
|
@ -22,6 +22,7 @@ import {MatrixClientPeg} from "../../MatrixClientPeg";
|
||||||
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';
|
||||||
|
import HomePage from "./HomePage";
|
||||||
|
|
||||||
export default class UserView extends React.Component {
|
export default class UserView extends React.Component {
|
||||||
static get propTypes() {
|
static get propTypes() {
|
||||||
|
@ -79,7 +80,7 @@ export default class UserView extends React.Component {
|
||||||
const RightPanel = sdk.getComponent('structures.RightPanel');
|
const RightPanel = sdk.getComponent('structures.RightPanel');
|
||||||
const MainSplit = sdk.getComponent('structures.MainSplit');
|
const MainSplit = sdk.getComponent('structures.MainSplit');
|
||||||
const panel = <RightPanel user={this.state.member} />;
|
const panel = <RightPanel user={this.state.member} />;
|
||||||
return (<MainSplit panel={panel}><div style={{flex: "1"}} /></MainSplit>);
|
return (<MainSplit panel={panel}><HomePage /></MainSplit>);
|
||||||
} else {
|
} else {
|
||||||
return (<div />);
|
return (<div />);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
@ -243,10 +243,15 @@ export default createReactClass({
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
try {
|
try {
|
||||||
|
// We do the first registration request ourselves to discover whether we need to
|
||||||
|
// do SSO instead. If we've already started the UI Auth process though, we don't
|
||||||
|
// need to.
|
||||||
|
if (!this.state.doingUIAuth) {
|
||||||
await this._makeRegisterRequest({});
|
await this._makeRegisterRequest({});
|
||||||
// This should never succeed since we specified an empty
|
// This should never succeed since we specified an empty
|
||||||
// auth object.
|
// auth object.
|
||||||
console.log("Expecting 401 from register request but got success!");
|
console.log("Expecting 401 from register request but got success!");
|
||||||
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (e.httpStatus === 401) {
|
if (e.httpStatus === 401) {
|
||||||
this.setState({
|
this.setState({
|
||||||
|
|
|
@ -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";
|
||||||
|
|
|
@ -412,14 +412,14 @@ export const EmailIdentityAuthEntry = createReactClass({
|
||||||
this.props.onPhaseChange(DEFAULT_PHASE);
|
this.props.onPhaseChange(DEFAULT_PHASE);
|
||||||
},
|
},
|
||||||
|
|
||||||
getInitialState: function() {
|
|
||||||
return {
|
|
||||||
requestingToken: false,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
render: function() {
|
render: function() {
|
||||||
if (this.state.requestingToken) {
|
// This component is now only displayed once the token has been requested,
|
||||||
|
// so we know the email has been sent. It can also get loaded after the user
|
||||||
|
// has clicked the validation link if the server takes a while to propagate
|
||||||
|
// the validation internally. If we're in the session spawned from clicking
|
||||||
|
// the validation link, we won't know the email address, so if we don't have it,
|
||||||
|
// assume that the link has been clicked and the server will realise when we poll.
|
||||||
|
if (this.props.inputs.emailAddress === undefined) {
|
||||||
const Loader = sdk.getComponent("elements.Spinner");
|
const Loader = sdk.getComponent("elements.Spinner");
|
||||||
return <Loader />;
|
return <Loader />;
|
||||||
} else {
|
} else {
|
||||||
|
|
125
src/components/views/auth/PassphraseField.tsx
Normal file
125
src/components/views/auth/PassphraseField.tsx
Normal file
|
@ -0,0 +1,125 @@
|
||||||
|
/*
|
||||||
|
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 React, {PureComponent, RefCallback, RefObject} from "react";
|
||||||
|
import classNames from "classnames";
|
||||||
|
import zxcvbn from "zxcvbn";
|
||||||
|
|
||||||
|
import SdkConfig from "../../../SdkConfig";
|
||||||
|
import withValidation, {IFieldState, IValidationResult} from "../elements/Validation";
|
||||||
|
import {_t, _td} from "../../../languageHandler";
|
||||||
|
import Field from "../elements/Field";
|
||||||
|
|
||||||
|
interface IProps {
|
||||||
|
autoFocus?: boolean;
|
||||||
|
id?: string;
|
||||||
|
className?: string;
|
||||||
|
minScore: 0 | 1 | 2 | 3 | 4;
|
||||||
|
value: string;
|
||||||
|
fieldRef?: RefCallback<Field> | RefObject<Field>;
|
||||||
|
|
||||||
|
label?: string;
|
||||||
|
labelEnterPassword?: string;
|
||||||
|
labelStrongPassword?: string;
|
||||||
|
labelAllowedButUnsafe?: string;
|
||||||
|
|
||||||
|
onChange(ev: KeyboardEvent);
|
||||||
|
onValidate(result: IValidationResult);
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IState {
|
||||||
|
complexity: zxcvbn.ZXCVBNResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
class PassphraseField extends PureComponent<IProps, IState> {
|
||||||
|
static defaultProps = {
|
||||||
|
label: _td("Password"),
|
||||||
|
labelEnterPassword: _td("Enter password"),
|
||||||
|
labelStrongPassword: _td("Nice, strong password!"),
|
||||||
|
labelAllowedButUnsafe: _td("Password is allowed, but unsafe"),
|
||||||
|
};
|
||||||
|
|
||||||
|
state = { complexity: null };
|
||||||
|
|
||||||
|
public readonly validate = withValidation<this>({
|
||||||
|
description: function() {
|
||||||
|
const complexity = this.state.complexity;
|
||||||
|
const score = complexity ? complexity.score : 0;
|
||||||
|
return <progress className="mx_PassphraseField_progress" max={4} value={score} />;
|
||||||
|
},
|
||||||
|
rules: [
|
||||||
|
{
|
||||||
|
key: "required",
|
||||||
|
test: ({ value, allowEmpty }) => allowEmpty || !!value,
|
||||||
|
invalid: () => _t(this.props.labelEnterPassword),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "complexity",
|
||||||
|
test: async function({ value }) {
|
||||||
|
if (!value) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const { scorePassword } = await import('../../../utils/PasswordScorer');
|
||||||
|
const complexity = scorePassword(value);
|
||||||
|
this.setState({ complexity });
|
||||||
|
const safe = complexity.score >= this.props.minScore;
|
||||||
|
const allowUnsafe = SdkConfig.get()["dangerously_allow_unsafe_and_insecure_passwords"];
|
||||||
|
return allowUnsafe || safe;
|
||||||
|
},
|
||||||
|
valid: function() {
|
||||||
|
// Unsafe passwords that are valid are only possible through a
|
||||||
|
// configuration flag. We'll print some helper text to signal
|
||||||
|
// to the user that their password is allowed, but unsafe.
|
||||||
|
if (this.state.complexity.score >= this.props.minScore) {
|
||||||
|
return _t(this.props.labelStrongPassword);
|
||||||
|
}
|
||||||
|
return _t(this.props.labelAllowedButUnsafe);
|
||||||
|
},
|
||||||
|
invalid: function() {
|
||||||
|
const complexity = this.state.complexity;
|
||||||
|
if (!complexity) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const { feedback } = complexity;
|
||||||
|
return feedback.warning || feedback.suggestions[0] || _t("Keep going...");
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
onValidate = async (fieldState: IFieldState) => {
|
||||||
|
const result = await this.validate(fieldState);
|
||||||
|
this.props.onValidate(result);
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return <Field
|
||||||
|
id={this.props.id}
|
||||||
|
autoFocus={this.props.autoFocus}
|
||||||
|
className={classNames("mx_PassphraseField", this.props.className)}
|
||||||
|
ref={this.props.fieldRef}
|
||||||
|
type="password"
|
||||||
|
autoComplete="new-password"
|
||||||
|
label={_t(this.props.label)}
|
||||||
|
value={this.props.value}
|
||||||
|
onChange={this.props.onChange}
|
||||||
|
onValidate={this.onValidate}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default PassphraseField;
|
|
@ -29,6 +29,7 @@ import SdkConfig from '../../../SdkConfig';
|
||||||
import { SAFE_LOCALPART_REGEX } from '../../../Registration';
|
import { SAFE_LOCALPART_REGEX } from '../../../Registration';
|
||||||
import withValidation from '../elements/Validation';
|
import withValidation from '../elements/Validation';
|
||||||
import {ValidatedServerConfig} from "../../../utils/AutoDiscoveryUtils";
|
import {ValidatedServerConfig} from "../../../utils/AutoDiscoveryUtils";
|
||||||
|
import PassphraseField from "./PassphraseField";
|
||||||
|
|
||||||
const FIELD_EMAIL = 'field_email';
|
const FIELD_EMAIL = 'field_email';
|
||||||
const FIELD_PHONE_NUMBER = 'field_phone_number';
|
const FIELD_PHONE_NUMBER = 'field_phone_number';
|
||||||
|
@ -78,7 +79,6 @@ export default createReactClass({
|
||||||
password: this.props.defaultPassword || "",
|
password: this.props.defaultPassword || "",
|
||||||
passwordConfirm: this.props.defaultPassword || "",
|
passwordConfirm: this.props.defaultPassword || "",
|
||||||
passwordComplexity: null,
|
passwordComplexity: null,
|
||||||
passwordSafe: false,
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -264,65 +264,10 @@ export default createReactClass({
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
async onPasswordValidate(fieldState) {
|
onPasswordValidate(result) {
|
||||||
const result = await this.validatePasswordRules(fieldState);
|
|
||||||
this.markFieldValid(FIELD_PASSWORD, result.valid);
|
this.markFieldValid(FIELD_PASSWORD, result.valid);
|
||||||
return result;
|
|
||||||
},
|
},
|
||||||
|
|
||||||
validatePasswordRules: withValidation({
|
|
||||||
description: function() {
|
|
||||||
const complexity = this.state.passwordComplexity;
|
|
||||||
const score = complexity ? complexity.score : 0;
|
|
||||||
return <progress
|
|
||||||
className="mx_AuthBody_passwordScore"
|
|
||||||
max={PASSWORD_MIN_SCORE}
|
|
||||||
value={score}
|
|
||||||
/>;
|
|
||||||
},
|
|
||||||
rules: [
|
|
||||||
{
|
|
||||||
key: "required",
|
|
||||||
test: ({ value, allowEmpty }) => allowEmpty || !!value,
|
|
||||||
invalid: () => _t("Enter password"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "complexity",
|
|
||||||
test: async function({ value }) {
|
|
||||||
if (!value) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
const { scorePassword } = await import('../../../utils/PasswordScorer');
|
|
||||||
const complexity = scorePassword(value);
|
|
||||||
const safe = complexity.score >= PASSWORD_MIN_SCORE;
|
|
||||||
const allowUnsafe = SdkConfig.get()["dangerously_allow_unsafe_and_insecure_passwords"];
|
|
||||||
this.setState({
|
|
||||||
passwordComplexity: complexity,
|
|
||||||
passwordSafe: safe,
|
|
||||||
});
|
|
||||||
return allowUnsafe || safe;
|
|
||||||
},
|
|
||||||
valid: function() {
|
|
||||||
// Unsafe passwords that are valid are only possible through a
|
|
||||||
// configuration flag. We'll print some helper text to signal
|
|
||||||
// to the user that their password is allowed, but unsafe.
|
|
||||||
if (!this.state.passwordSafe) {
|
|
||||||
return _t("Password is allowed, but unsafe");
|
|
||||||
}
|
|
||||||
return _t("Nice, strong password!");
|
|
||||||
},
|
|
||||||
invalid: function() {
|
|
||||||
const complexity = this.state.passwordComplexity;
|
|
||||||
if (!complexity) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
const { feedback } = complexity;
|
|
||||||
return feedback.warning || feedback.suggestions[0] || _t("Keep going...");
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
}),
|
|
||||||
|
|
||||||
onPasswordConfirmChange(ev) {
|
onPasswordConfirmChange(ev) {
|
||||||
this.setState({
|
this.setState({
|
||||||
passwordConfirm: ev.target.value,
|
passwordConfirm: ev.target.value,
|
||||||
|
@ -484,13 +429,10 @@ export default createReactClass({
|
||||||
},
|
},
|
||||||
|
|
||||||
renderPassword() {
|
renderPassword() {
|
||||||
const Field = sdk.getComponent('elements.Field');
|
return <PassphraseField
|
||||||
return <Field
|
|
||||||
id="mx_RegistrationForm_password"
|
id="mx_RegistrationForm_password"
|
||||||
ref={field => this[FIELD_PASSWORD] = field}
|
fieldRef={field => this[FIELD_PASSWORD] = field}
|
||||||
type="password"
|
minScore={PASSWORD_MIN_SCORE}
|
||||||
autoComplete="new-password"
|
|
||||||
label={_t("Password")}
|
|
||||||
value={this.state.password}
|
value={this.state.password}
|
||||||
onChange={this.onPasswordChange}
|
onChange={this.onPasswordChange}
|
||||||
onValidate={this.onPasswordValidate}
|
onValidate={this.onPasswordValidate}
|
||||||
|
|
|
@ -24,7 +24,7 @@ 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 {useEventEmitter} from "../../../hooks/useEventEmitter";
|
import {useEventEmitter} from "../../../hooks/useEventEmitter";
|
||||||
import toRem from "../../../utils/rem";
|
import {toPx} from "../../../utils/units";
|
||||||
|
|
||||||
const useImageUrl = ({url, urls, idName, name, defaultToInitialLetter}) => {
|
const useImageUrl = ({url, urls, idName, name, defaultToInitialLetter}) => {
|
||||||
const [imageUrls, setUrls] = useState([]);
|
const [imageUrls, setUrls] = useState([]);
|
||||||
|
@ -105,9 +105,9 @@ const BaseAvatar = (props) => {
|
||||||
className="mx_BaseAvatar_initial"
|
className="mx_BaseAvatar_initial"
|
||||||
aria-hidden="true"
|
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 }
|
||||||
|
@ -121,8 +121,8 @@ const BaseAvatar = (props) => {
|
||||||
title={title}
|
title={title}
|
||||||
onError={onError}
|
onError={onError}
|
||||||
style={{
|
style={{
|
||||||
width: toRem(width),
|
width: toPx(width),
|
||||||
height: toRem(height),
|
height: toPx(height),
|
||||||
}}
|
}}
|
||||||
aria-hidden="true" />
|
aria-hidden="true" />
|
||||||
);
|
);
|
||||||
|
@ -159,8 +159,8 @@ const BaseAvatar = (props) => {
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
onError={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}
|
||||||
|
@ -173,8 +173,8 @@ const BaseAvatar = (props) => {
|
||||||
src={imageUrl}
|
src={imageUrl}
|
||||||
onError={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}
|
||||||
|
|
|
@ -20,7 +20,8 @@ import PropTypes from 'prop-types';
|
||||||
import createReactClass from 'create-react-class';
|
import createReactClass from 'create-react-class';
|
||||||
import * as Avatar from '../../../Avatar';
|
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";
|
||||||
|
|
||||||
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,
|
||||||
},
|
},
|
||||||
|
@ -85,7 +86,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';
|
||||||
|
|
|
@ -24,7 +24,7 @@ import classNames from 'classnames';
|
||||||
import * as sdk from '../../../index';
|
import * as sdk from '../../../index';
|
||||||
import { _t, _td } from '../../../languageHandler';
|
import { _t, _td } from '../../../languageHandler';
|
||||||
import {MatrixClientPeg} from '../../../MatrixClientPeg';
|
import {MatrixClientPeg} from '../../../MatrixClientPeg';
|
||||||
import dis from '../../../dispatcher';
|
import dis from '../../../dispatcher/dispatcher';
|
||||||
import DMRoomMap from '../../../utils/DMRoomMap';
|
import DMRoomMap from '../../../utils/DMRoomMap';
|
||||||
import * as Rooms from '../../../Rooms';
|
import * as Rooms from '../../../Rooms';
|
||||||
import * as RoomNotifs from '../../../RoomNotifs';
|
import * as RoomNotifs from '../../../RoomNotifs';
|
||||||
|
|
|
@ -18,7 +18,7 @@ limitations under the License.
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { _t } from '../../../languageHandler';
|
import { _t } from '../../../languageHandler';
|
||||||
import dis from '../../../dispatcher';
|
import dis from '../../../dispatcher/dispatcher';
|
||||||
import TagOrderActions from '../../../actions/TagOrderActions';
|
import TagOrderActions from '../../../actions/TagOrderActions';
|
||||||
import * as sdk from '../../../index';
|
import * as sdk from '../../../index';
|
||||||
import {MenuItem} from "../../structures/ContextMenu";
|
import {MenuItem} from "../../structures/ContextMenu";
|
||||||
|
|
|
@ -17,7 +17,7 @@ limitations under the License.
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
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 LogoutDialog from "../dialogs/LogoutDialog";
|
import LogoutDialog from "../dialogs/LogoutDialog";
|
||||||
import Modal from "../../../Modal";
|
import Modal from "../../../Modal";
|
||||||
|
@ -27,6 +27,7 @@ import {MatrixClientPeg} from '../../../MatrixClientPeg';
|
||||||
import {MenuItem} from "../../structures/ContextMenu";
|
import {MenuItem} from "../../structures/ContextMenu";
|
||||||
import * as sdk from "../../../index";
|
import * as sdk from "../../../index";
|
||||||
import {getHomePageUrl} from "../../../utils/pages";
|
import {getHomePageUrl} from "../../../utils/pages";
|
||||||
|
import {Action} from "../../../dispatcher/actions";
|
||||||
|
|
||||||
export default class TopLeftMenu extends React.Component {
|
export default class TopLeftMenu extends React.Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
|
@ -134,7 +135,7 @@ export default class TopLeftMenu extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
openSettings() {
|
openSettings() {
|
||||||
dis.dispatch({action: 'view_user_settings'});
|
dis.fire(Action.ViewUserSettings);
|
||||||
this.closeMenu();
|
this.closeMenu();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -24,7 +24,7 @@ import createReactClass from 'create-react-class';
|
||||||
import { _t, _td } from '../../../languageHandler';
|
import { _t, _td } from '../../../languageHandler';
|
||||||
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 { addressTypes, getAddressType } from '../../../UserAddress.js';
|
import { addressTypes, getAddressType } from '../../../UserAddress.js';
|
||||||
import GroupStore from '../../../stores/GroupStore';
|
import GroupStore from '../../../stores/GroupStore';
|
||||||
import * as Email from '../../../email';
|
import * as Email from '../../../email';
|
||||||
|
@ -33,6 +33,7 @@ import { getDefaultIdentityServerUrl, useDefaultIdentityServer } from '../../../
|
||||||
import { abbreviateUrl } from '../../../utils/UrlUtils';
|
import { abbreviateUrl } from '../../../utils/UrlUtils';
|
||||||
import {sleep} from "../../../utils/promise";
|
import {sleep} from "../../../utils/promise";
|
||||||
import {Key} from "../../../Keyboard";
|
import {Key} from "../../../Keyboard";
|
||||||
|
import {Action} from "../../../dispatcher/actions";
|
||||||
|
|
||||||
const TRUNCATE_QUERY_LIST = 40;
|
const TRUNCATE_QUERY_LIST = 40;
|
||||||
const QUERY_USER_DIRECTORY_DEBOUNCE_MS = 200;
|
const QUERY_USER_DIRECTORY_DEBOUNCE_MS = 200;
|
||||||
|
@ -615,7 +616,7 @@ export default createReactClass({
|
||||||
|
|
||||||
onManageSettingsClick(e) {
|
onManageSettingsClick(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
dis.dispatch({ action: 'view_user_settings' });
|
dis.fire(Action.ViewUserSettings);
|
||||||
this.onCancel();
|
this.onCancel();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -18,7 +18,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 * 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 {MatrixClientPeg} from '../../../MatrixClientPeg';
|
import {MatrixClientPeg} from '../../../MatrixClientPeg';
|
||||||
|
|
||||||
|
|
|
@ -16,7 +16,7 @@ limitations under the License.
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
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';
|
||||||
|
|
||||||
|
|
|
@ -25,7 +25,7 @@ import * as FormattingUtils from '../../../utils/FormattingUtils';
|
||||||
import { _t } from '../../../languageHandler';
|
import { _t } from '../../../languageHandler';
|
||||||
import {verificationMethods} from 'matrix-js-sdk/src/crypto';
|
import {verificationMethods} from 'matrix-js-sdk/src/crypto';
|
||||||
import {ensureDMExists} from "../../../createRoom";
|
import {ensureDMExists} from "../../../createRoom";
|
||||||
import dis from "../../../dispatcher";
|
import dis from "../../../dispatcher/dispatcher";
|
||||||
import SettingsStore from '../../../settings/SettingsStore';
|
import SettingsStore from '../../../settings/SettingsStore';
|
||||||
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";
|
||||||
import VerificationQREmojiOptions from "../verification/VerificationQREmojiOptions";
|
import VerificationQREmojiOptions from "../verification/VerificationQREmojiOptions";
|
||||||
|
|
|
@ -18,7 +18,8 @@ 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 {Action} from "../../../dispatcher/actions";
|
||||||
|
|
||||||
export default class IntegrationsDisabledDialog extends React.Component {
|
export default class IntegrationsDisabledDialog extends React.Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
|
@ -31,7 +32,7 @@ export default class IntegrationsDisabledDialog extends React.Component {
|
||||||
|
|
||||||
_onOpenSettingsClick = () => {
|
_onOpenSettingsClick = () => {
|
||||||
this.props.onFinished();
|
this.props.onFinished();
|
||||||
dis.dispatch({action: "view_user_settings"});
|
dis.fire(Action.ViewUserSettings);
|
||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
|
|
@ -27,15 +27,17 @@ import {getHttpUriForMxc} from "matrix-js-sdk/src/content-repo";
|
||||||
import * as Email from "../../../email";
|
import * as Email from "../../../email";
|
||||||
import {getDefaultIdentityServerUrl, useDefaultIdentityServer} from "../../../utils/IdentityServerUtils";
|
import {getDefaultIdentityServerUrl, useDefaultIdentityServer} from "../../../utils/IdentityServerUtils";
|
||||||
import {abbreviateUrl} from "../../../utils/UrlUtils";
|
import {abbreviateUrl} from "../../../utils/UrlUtils";
|
||||||
import dis from "../../../dispatcher";
|
import dis from "../../../dispatcher/dispatcher";
|
||||||
import IdentityAuthClient from "../../../IdentityAuthClient";
|
import IdentityAuthClient from "../../../IdentityAuthClient";
|
||||||
import Modal from "../../../Modal";
|
import Modal from "../../../Modal";
|
||||||
import {humanizeTime} from "../../../utils/humanize";
|
import {humanizeTime} from "../../../utils/humanize";
|
||||||
import createRoom, {canEncryptToAllUsers} from "../../../createRoom";
|
import createRoom, {canEncryptToAllUsers} from "../../../createRoom";
|
||||||
import {inviteMultipleToRoom} from "../../../RoomInvite";
|
import {inviteMultipleToRoom} from "../../../RoomInvite";
|
||||||
import SettingsStore from '../../../settings/SettingsStore';
|
import SettingsStore from '../../../settings/SettingsStore';
|
||||||
import RoomListStore, {TAG_DM} from "../../../stores/RoomListStore";
|
|
||||||
import {Key} from "../../../Keyboard";
|
import {Key} from "../../../Keyboard";
|
||||||
|
import {Action} from "../../../dispatcher/actions";
|
||||||
|
import {RoomListStoreTempProxy} from "../../../stores/room-list/RoomListStoreTempProxy";
|
||||||
|
import {DefaultTagID} from "../../../stores/room-list/models";
|
||||||
|
|
||||||
export const KIND_DM = "dm";
|
export const KIND_DM = "dm";
|
||||||
export const KIND_INVITE = "invite";
|
export const KIND_INVITE = "invite";
|
||||||
|
@ -343,10 +345,10 @@ export default class InviteDialog extends React.PureComponent {
|
||||||
_buildRecents(excludedTargetIds: Set<string>): {userId: string, user: RoomMember, lastActive: number} {
|
_buildRecents(excludedTargetIds: Set<string>): {userId: string, user: RoomMember, lastActive: number} {
|
||||||
const rooms = DMRoomMap.shared().getUniqueRoomsWithIndividuals(); // map of userId => js-sdk Room
|
const rooms = DMRoomMap.shared().getUniqueRoomsWithIndividuals(); // map of userId => js-sdk Room
|
||||||
|
|
||||||
// Also pull in all the rooms tagged as TAG_DM so we don't miss anything. Sometimes the
|
// Also pull in all the rooms tagged as DefaultTagID.DM so we don't miss anything. Sometimes the
|
||||||
// room list doesn't tag the room for the DMRoomMap, but does for the room list.
|
// room list doesn't tag the room for the DMRoomMap, but does for the room list.
|
||||||
const taggedRooms = RoomListStore.getRoomLists();
|
const taggedRooms = RoomListStoreTempProxy.getRoomLists();
|
||||||
const dmTaggedRooms = taggedRooms[TAG_DM];
|
const dmTaggedRooms = taggedRooms[DefaultTagID.DM];
|
||||||
const myUserId = MatrixClientPeg.get().getUserId();
|
const myUserId = MatrixClientPeg.get().getUserId();
|
||||||
for (const dmRoom of dmTaggedRooms) {
|
for (const dmRoom of dmTaggedRooms) {
|
||||||
const otherMembers = dmRoom.getJoinedMembers().filter(u => u.userId !== myUserId);
|
const otherMembers = dmRoom.getJoinedMembers().filter(u => u.userId !== myUserId);
|
||||||
|
@ -902,7 +904,7 @@ export default class InviteDialog extends React.PureComponent {
|
||||||
|
|
||||||
_onManageSettingsClick = (e) => {
|
_onManageSettingsClick = (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
dis.dispatch({ action: 'view_user_settings' });
|
dis.fire(Action.ViewUserSettings);
|
||||||
this.props.onFinished();
|
this.props.onFinished();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -18,7 +18,7 @@ limitations under the License.
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import Modal from '../../../Modal';
|
import Modal from '../../../Modal';
|
||||||
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 {MatrixClientPeg} from '../../../MatrixClientPeg';
|
import {MatrixClientPeg} from '../../../MatrixClientPeg';
|
||||||
|
|
||||||
|
|
|
@ -27,7 +27,7 @@ import NotificationSettingsTab from "../settings/tabs/room/NotificationSettingsT
|
||||||
import BridgeSettingsTab from "../settings/tabs/room/BridgeSettingsTab";
|
import BridgeSettingsTab from "../settings/tabs/room/BridgeSettingsTab";
|
||||||
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 SettingsStore from "../../../settings/SettingsStore";
|
import SettingsStore from "../../../settings/SettingsStore";
|
||||||
|
|
||||||
export default class RoomSettingsDialog extends React.Component {
|
export default class RoomSettingsDialog extends React.Component {
|
||||||
|
|
|
@ -22,6 +22,7 @@ import {_t, _td} from "../../../languageHandler";
|
||||||
import GeneralUserSettingsTab from "../settings/tabs/user/GeneralUserSettingsTab";
|
import GeneralUserSettingsTab from "../settings/tabs/user/GeneralUserSettingsTab";
|
||||||
import SettingsStore from "../../../settings/SettingsStore";
|
import SettingsStore from "../../../settings/SettingsStore";
|
||||||
import LabsUserSettingsTab from "../settings/tabs/user/LabsUserSettingsTab";
|
import LabsUserSettingsTab from "../settings/tabs/user/LabsUserSettingsTab";
|
||||||
|
import AppearanceUserSettingsTab from "../settings/tabs/user/AppearanceUserSettingsTab";
|
||||||
import SecurityUserSettingsTab from "../settings/tabs/user/SecurityUserSettingsTab";
|
import SecurityUserSettingsTab from "../settings/tabs/user/SecurityUserSettingsTab";
|
||||||
import NotificationUserSettingsTab from "../settings/tabs/user/NotificationUserSettingsTab";
|
import NotificationUserSettingsTab from "../settings/tabs/user/NotificationUserSettingsTab";
|
||||||
import PreferencesUserSettingsTab from "../settings/tabs/user/PreferencesUserSettingsTab";
|
import PreferencesUserSettingsTab from "../settings/tabs/user/PreferencesUserSettingsTab";
|
||||||
|
@ -66,6 +67,11 @@ export default class UserSettingsDialog extends React.Component {
|
||||||
"mx_UserSettingsDialog_settingsIcon",
|
"mx_UserSettingsDialog_settingsIcon",
|
||||||
<GeneralUserSettingsTab closeSettingsFn={this.props.onFinished} />,
|
<GeneralUserSettingsTab closeSettingsFn={this.props.onFinished} />,
|
||||||
));
|
));
|
||||||
|
tabs.push(new Tab(
|
||||||
|
_td("Appearance"),
|
||||||
|
"mx_UserSettingsDialog_appearanceIcon",
|
||||||
|
<AppearanceUserSettingsTab />,
|
||||||
|
));
|
||||||
tabs.push(new Tab(
|
tabs.push(new Tab(
|
||||||
_td("Flair"),
|
_td("Flair"),
|
||||||
"mx_UserSettingsDialog_flairIcon",
|
"mx_UserSettingsDialog_flairIcon",
|
||||||
|
|
|
@ -201,7 +201,7 @@ export default class RestoreKeyBackupDialog extends React.PureComponent {
|
||||||
// `accessSecretStorage` may prompt for storage access as needed.
|
// `accessSecretStorage` may prompt for storage access as needed.
|
||||||
const recoverInfo = await accessSecretStorage(async () => {
|
const recoverInfo = await accessSecretStorage(async () => {
|
||||||
return MatrixClientPeg.get().restoreKeyBackupWithSecretStorage(
|
return MatrixClientPeg.get().restoreKeyBackupWithSecretStorage(
|
||||||
this.state.backupInfo,
|
this.state.backupInfo, undefined, undefined,
|
||||||
{ progressCallback: this._progressCallback },
|
{ progressCallback: this._progressCallback },
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
|
@ -18,7 +18,7 @@ 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 AccessibleButton from './AccessibleButton';
|
import AccessibleButton from './AccessibleButton';
|
||||||
import dis from '../../../dispatcher';
|
import dis from '../../../dispatcher/dispatcher';
|
||||||
import * as sdk from '../../../index';
|
import * as sdk from '../../../index';
|
||||||
import Analytics from '../../../Analytics';
|
import Analytics from '../../../Analytics';
|
||||||
|
|
||||||
|
|
|
@ -31,7 +31,7 @@ import AppPermission from './AppPermission';
|
||||||
import AppWarning from './AppWarning';
|
import AppWarning from './AppWarning';
|
||||||
import MessageSpinner from './MessageSpinner';
|
import MessageSpinner from './MessageSpinner';
|
||||||
import WidgetUtils from '../../../utils/WidgetUtils';
|
import WidgetUtils from '../../../utils/WidgetUtils';
|
||||||
import dis from '../../../dispatcher';
|
import dis from '../../../dispatcher/dispatcher';
|
||||||
import ActiveWidgetStore from '../../../stores/ActiveWidgetStore';
|
import ActiveWidgetStore from '../../../stores/ActiveWidgetStore';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import {IntegrationManagers} from "../../../integrations/IntegrationManagers";
|
import {IntegrationManagers} from "../../../integrations/IntegrationManagers";
|
||||||
|
|
84
src/components/views/elements/Draggable.tsx
Normal file
84
src/components/views/elements/Draggable.tsx
Normal file
|
@ -0,0 +1,84 @@
|
||||||
|
/*
|
||||||
|
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 React from 'react';
|
||||||
|
|
||||||
|
interface IProps {
|
||||||
|
className: string,
|
||||||
|
dragFunc: (currentLocation: ILocationState, event: MouseEvent) => ILocationState,
|
||||||
|
onMouseUp: (event: MouseEvent) => void,
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IState {
|
||||||
|
onMouseMove: (event: MouseEvent) => void,
|
||||||
|
onMouseUp: (event: MouseEvent) => void,
|
||||||
|
location: ILocationState,
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ILocationState {
|
||||||
|
currentX: number,
|
||||||
|
currentY: number,
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class Draggable extends React.Component<IProps, IState> {
|
||||||
|
|
||||||
|
constructor(props: IProps) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
onMouseMove: this.onMouseMove.bind(this),
|
||||||
|
onMouseUp: this.onMouseUp.bind(this),
|
||||||
|
location: {
|
||||||
|
currentX: 0,
|
||||||
|
currentY: 0,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private onMouseDown = (event: MouseEvent): void => {
|
||||||
|
this.setState({
|
||||||
|
location: {
|
||||||
|
currentX: event.clientX,
|
||||||
|
currentY: event.clientY,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
document.addEventListener("mousemove", this.state.onMouseMove);
|
||||||
|
document.addEventListener("mouseup", this.state.onMouseUp);
|
||||||
|
console.log("Mouse down")
|
||||||
|
}
|
||||||
|
|
||||||
|
private onMouseUp = (event: MouseEvent): void => {
|
||||||
|
document.removeEventListener("mousemove", this.state.onMouseMove);
|
||||||
|
document.removeEventListener("mouseup", this.state.onMouseUp);
|
||||||
|
this.props.onMouseUp(event);
|
||||||
|
console.log("Mouse up")
|
||||||
|
}
|
||||||
|
|
||||||
|
private onMouseMove(event: MouseEvent): void {
|
||||||
|
console.log("Mouse Move")
|
||||||
|
const newLocation = this.props.dragFunc(this.state.location, event);
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
location: newLocation,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return <div className={this.props.className} onMouseDown={this.onMouseDown.bind(this)} />
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -19,7 +19,7 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import FlairStore from '../../../stores/FlairStore';
|
import FlairStore from '../../../stores/FlairStore';
|
||||||
import dis from '../../../dispatcher';
|
import dis from '../../../dispatcher/dispatcher';
|
||||||
import MatrixClientContext from "../../../contexts/MatrixClientContext";
|
import MatrixClientContext from "../../../contexts/MatrixClientContext";
|
||||||
|
|
||||||
|
|
||||||
|
|
88
src/components/views/elements/IRCTimelineProfileResizer.tsx
Normal file
88
src/components/views/elements/IRCTimelineProfileResizer.tsx
Normal file
|
@ -0,0 +1,88 @@
|
||||||
|
/*
|
||||||
|
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 React from 'react';
|
||||||
|
import SettingsStore, {SettingLevel} from "../../../settings/SettingsStore";
|
||||||
|
import Draggable, {ILocationState} from './Draggable';
|
||||||
|
|
||||||
|
interface IProps {
|
||||||
|
// Current room
|
||||||
|
roomId: string,
|
||||||
|
minWidth: number,
|
||||||
|
maxWidth: number,
|
||||||
|
};
|
||||||
|
|
||||||
|
interface IState {
|
||||||
|
width: number,
|
||||||
|
IRCLayoutRoot: HTMLElement,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default class IRCTimelineProfileResizer extends React.Component<IProps, IState> {
|
||||||
|
constructor(props: IProps) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
width: SettingsStore.getValue("ircDisplayNameWidth", this.props.roomId),
|
||||||
|
IRCLayoutRoot: null,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
this.setState({
|
||||||
|
IRCLayoutRoot: document.querySelector(".mx_IRCLayout") as HTMLElement,
|
||||||
|
}, () => this.updateCSSWidth(this.state.width))
|
||||||
|
}
|
||||||
|
|
||||||
|
private dragFunc = (location: ILocationState, event: React.MouseEvent<Element, MouseEvent>): ILocationState => {
|
||||||
|
const offset = event.clientX - location.currentX;
|
||||||
|
const newWidth = this.state.width + offset;
|
||||||
|
|
||||||
|
console.log({offset})
|
||||||
|
// If we're trying to go smaller than min width, don't.
|
||||||
|
if (newWidth < this.props.minWidth) {
|
||||||
|
return location;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newWidth > this.props.maxWidth) {
|
||||||
|
return location;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
width: newWidth,
|
||||||
|
});
|
||||||
|
|
||||||
|
this.updateCSSWidth.bind(this)(newWidth);
|
||||||
|
|
||||||
|
return {
|
||||||
|
currentX: event.clientX,
|
||||||
|
currentY: location.currentY,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private updateCSSWidth(newWidth: number) {
|
||||||
|
this.state.IRCLayoutRoot.style.setProperty("--name-width", newWidth + "px");
|
||||||
|
}
|
||||||
|
|
||||||
|
private onMoueUp(event: MouseEvent) {
|
||||||
|
if (this.props.roomId) {
|
||||||
|
SettingsStore.setValue("ircDisplayNameWidth", this.props.roomId, SettingLevel.ROOM_DEVICE, this.state.width);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return <Draggable className="mx_ProfileResizer" dragFunc={this.dragFunc.bind(this)} onMouseUp={this.onMoueUp.bind(this)}/>
|
||||||
|
}
|
||||||
|
};
|
|
@ -20,7 +20,7 @@ import PropTypes from 'prop-types';
|
||||||
|
|
||||||
import ResizeObserver from 'resize-observer-polyfill';
|
import ResizeObserver from 'resize-observer-polyfill';
|
||||||
|
|
||||||
import dis from '../../../dispatcher';
|
import dis from '../../../dispatcher/dispatcher';
|
||||||
|
|
||||||
// Shamelessly ripped off Modal.js. There's probably a better way
|
// Shamelessly ripped off Modal.js. There's probably a better way
|
||||||
// of doing reusable widgets like dialog boxes & menus where we go and
|
// of doing reusable widgets like dialog boxes & menus where we go and
|
||||||
|
@ -156,16 +156,70 @@ export default class PersistedElement extends React.Component {
|
||||||
child.style.display = visible ? 'block' : 'none';
|
child.style.display = visible ? 'block' : 'none';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Clip element bounding rectangle to that of the parent elements.
|
||||||
|
* This is not a full visibility check, but prevents the persisted
|
||||||
|
* element from overflowing parent containers when inside a scrolled
|
||||||
|
* area.
|
||||||
|
*/
|
||||||
|
_getClippedBoundingClientRect(element) {
|
||||||
|
let parentElement = element.parentElement;
|
||||||
|
let rect = element.getBoundingClientRect();
|
||||||
|
|
||||||
|
rect = new DOMRect(rect.left, rect.top, rect.width, rect.height);
|
||||||
|
|
||||||
|
while (parentElement) {
|
||||||
|
const parentRect = parentElement.getBoundingClientRect();
|
||||||
|
|
||||||
|
if (parentRect.left > rect.left) {
|
||||||
|
rect.width = rect.width - (parentRect.left - rect.left);
|
||||||
|
rect.x = parentRect.x;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parentRect.top > rect.top) {
|
||||||
|
rect.height = rect.height - (parentRect.top - rect.top);
|
||||||
|
rect.y = parentRect.y;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parentRect.right < rect.right) {
|
||||||
|
rect.width = rect.width - (rect.right - parentRect.right);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parentRect.bottom < rect.bottom) {
|
||||||
|
rect.height = rect.height - (rect.bottom - parentRect.bottom);
|
||||||
|
}
|
||||||
|
|
||||||
|
parentElement = parentElement.parentElement;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rect.width < 0) rect.width = 0;
|
||||||
|
if (rect.height < 0) rect.height = 0;
|
||||||
|
|
||||||
|
return rect;
|
||||||
|
}
|
||||||
|
|
||||||
updateChildPosition(child, parent) {
|
updateChildPosition(child, parent) {
|
||||||
if (!child || !parent) return;
|
if (!child || !parent) return;
|
||||||
|
|
||||||
const parentRect = parent.getBoundingClientRect();
|
const parentRect = parent.getBoundingClientRect();
|
||||||
|
const clipRect = this._getClippedBoundingClientRect(parent);
|
||||||
|
|
||||||
|
Object.assign(child.parentElement.style, {
|
||||||
|
position: 'absolute',
|
||||||
|
top: clipRect.top + 'px',
|
||||||
|
left: clipRect.left + 'px',
|
||||||
|
width: clipRect.width + 'px',
|
||||||
|
height: clipRect.height + 'px',
|
||||||
|
overflow: "hidden",
|
||||||
|
});
|
||||||
|
|
||||||
Object.assign(child.style, {
|
Object.assign(child.style, {
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
top: parentRect.top + 'px',
|
top: (parentRect.top - clipRect.top) + 'px',
|
||||||
left: parentRect.left + 'px',
|
left: (parentRect.left - clipRect.left) + 'px',
|
||||||
width: parentRect.width + 'px',
|
width: parentRect.width + 'px',
|
||||||
height: parentRect.height + 'px',
|
height: parentRect.height + 'px',
|
||||||
|
overflow: "hidden",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -18,7 +18,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 classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { Room, RoomMember } from 'matrix-js-sdk';
|
import { Room, RoomMember } from 'matrix-js-sdk';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
@ -26,6 +26,7 @@ import {MatrixClientPeg} from '../../../MatrixClientPeg';
|
||||||
import FlairStore from "../../../stores/FlairStore";
|
import FlairStore from "../../../stores/FlairStore";
|
||||||
import {getPrimaryPermalinkEntity} from "../../../utils/permalinks/Permalinks";
|
import {getPrimaryPermalinkEntity} from "../../../utils/permalinks/Permalinks";
|
||||||
import MatrixClientContext from "../../../contexts/MatrixClientContext";
|
import MatrixClientContext from "../../../contexts/MatrixClientContext";
|
||||||
|
import {Action} from "../../../dispatcher/actions";
|
||||||
|
|
||||||
// For URLs of matrix.to links in the timeline which have been reformatted by
|
// For URLs of matrix.to links in the timeline which have been reformatted by
|
||||||
// HttpUtils transformTags to relative links. This excludes event URLs (with `[^\/]*`)
|
// HttpUtils transformTags to relative links. This excludes event URLs (with `[^\/]*`)
|
||||||
|
@ -191,7 +192,7 @@ const Pill = createReactClass({
|
||||||
|
|
||||||
onUserPillClicked: function() {
|
onUserPillClicked: function() {
|
||||||
dis.dispatch({
|
dis.dispatch({
|
||||||
action: 'view_user',
|
action: Action.ViewUser,
|
||||||
member: this.state.member,
|
member: this.state.member,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
|
@ -19,7 +19,7 @@ import React from 'react';
|
||||||
import * as sdk from '../../../index';
|
import * as sdk from '../../../index';
|
||||||
import {_t} from '../../../languageHandler';
|
import {_t} from '../../../languageHandler';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import dis from '../../../dispatcher';
|
import dis from '../../../dispatcher/dispatcher';
|
||||||
import {wantsDateSeparator} from '../../../DateUtils';
|
import {wantsDateSeparator} from '../../../DateUtils';
|
||||||
import {MatrixEvent} from 'matrix-js-sdk';
|
import {MatrixEvent} from 'matrix-js-sdk';
|
||||||
import {makeUserPermalink, RoomPermalinkCreator} from "../../../utils/permalinks/Permalinks";
|
import {makeUserPermalink, RoomPermalinkCreator} from "../../../utils/permalinks/Permalinks";
|
||||||
|
@ -37,6 +37,8 @@ export default class ReplyThread extends React.Component {
|
||||||
// called when the ReplyThread contents has changed, including EventTiles thereof
|
// called when the ReplyThread contents has changed, including EventTiles thereof
|
||||||
onHeightChanged: PropTypes.func.isRequired,
|
onHeightChanged: PropTypes.func.isRequired,
|
||||||
permalinkCreator: PropTypes.instanceOf(RoomPermalinkCreator).isRequired,
|
permalinkCreator: PropTypes.instanceOf(RoomPermalinkCreator).isRequired,
|
||||||
|
// Specifies which layout to use.
|
||||||
|
useIRCLayout: PropTypes.bool,
|
||||||
};
|
};
|
||||||
|
|
||||||
static contextType = MatrixClientContext;
|
static contextType = MatrixClientContext;
|
||||||
|
@ -176,12 +178,17 @@ export default class ReplyThread extends React.Component {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
static makeThread(parentEv, onHeightChanged, permalinkCreator, ref) {
|
static makeThread(parentEv, onHeightChanged, permalinkCreator, ref, useIRCLayout) {
|
||||||
if (!ReplyThread.getParentEventId(parentEv)) {
|
if (!ReplyThread.getParentEventId(parentEv)) {
|
||||||
return <div />;
|
return <div className="mx_ReplyThread_wrapper_empty" />;
|
||||||
}
|
}
|
||||||
return <ReplyThread parentEv={parentEv} onHeightChanged={onHeightChanged}
|
return <ReplyThread
|
||||||
ref={ref} permalinkCreator={permalinkCreator} />;
|
parentEv={parentEv}
|
||||||
|
onHeightChanged={onHeightChanged}
|
||||||
|
ref={ref}
|
||||||
|
permalinkCreator={permalinkCreator}
|
||||||
|
useIRCLayout={useIRCLayout}
|
||||||
|
/>;
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
|
@ -331,11 +338,13 @@ export default class ReplyThread extends React.Component {
|
||||||
onHeightChanged={this.props.onHeightChanged}
|
onHeightChanged={this.props.onHeightChanged}
|
||||||
permalinkCreator={this.props.permalinkCreator}
|
permalinkCreator={this.props.permalinkCreator}
|
||||||
isRedacted={ev.isRedacted()}
|
isRedacted={ev.isRedacted()}
|
||||||
isTwelveHour={SettingsStore.getValue("showTwelveHourTimestamps")} />
|
isTwelveHour={SettingsStore.getValue("showTwelveHourTimestamps")}
|
||||||
|
useIRCLayout={this.props.useIRCLayout}
|
||||||
|
/>
|
||||||
</blockquote>;
|
</blockquote>;
|
||||||
});
|
});
|
||||||
|
|
||||||
return <div>
|
return <div className="mx_ReplyThread_wrapper">
|
||||||
<div>{ header }</div>
|
<div>{ header }</div>
|
||||||
<div>{ evTiles }</div>
|
<div>{ evTiles }</div>
|
||||||
</div>;
|
</div>;
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue