diff --git a/CHANGELOG.md b/CHANGELOG.md
index 0706e20085..e5515f1015 100644
--- a/CHANGELOG.md
+++ b/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)
===================================================================================================
[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v2.5.0-rc.6...v2.5.0)
diff --git a/code_style.md b/code_style.md
index 3ad0d38873..01c1f37146 100644
--- a/code_style.md
+++ b/code_style.md
@@ -151,6 +151,7 @@ General Style
Don't set things to undefined. Reserve that value to mean "not yet set to anything."
Boolean objects are verboten.
- Use JSDoc
+- Use switch-case statements where there are 5 or more branches running against the same variable.
ECMAScript
----------
diff --git a/package.json b/package.json
index 708ae91b0b..1783102b35 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "matrix-react-sdk",
- "version": "2.5.0",
+ "version": "2.6.1",
"description": "SDK for matrix.org using React",
"author": "matrix.org",
"repository": {
@@ -55,6 +55,7 @@
},
"dependencies": {
"@babel/runtime": "^7.8.3",
+ "await-lock": "^2.0.1",
"blueimp-canvas-to-blob": "^3.5.0",
"browser-encrypt-attachment": "^0.3.0",
"browser-request": "^0.3.3",
@@ -117,9 +118,11 @@
"@babel/register": "^7.7.4",
"@peculiar/webcrypto": "^1.0.22",
"@types/classnames": "^2.2.10",
+ "@types/flux": "^3.1.9",
"@types/modernizr": "^3.5.3",
"@types/qrcode": "^1.3.4",
"@types/react": "16.9",
+ "@types/zxcvbn": "^4.4.0",
"babel-eslint": "^10.0.3",
"babel-jest": "^24.9.0",
"chokidar": "^3.3.1",
diff --git a/res/css/_components.scss b/res/css/_components.scss
index c05e2cabc6..3a6a3257a3 100644
--- a/res/css/_components.scss
+++ b/res/css/_components.scss
@@ -41,6 +41,7 @@
@import "./views/auth/_CountryDropdown.scss";
@import "./views/auth/_InteractiveAuthEntryComponents.scss";
@import "./views/auth/_LanguageSelector.scss";
+@import "./views/auth/_PassphraseField.scss";
@import "./views/auth/_ServerConfig.scss";
@import "./views/auth/_ServerTypeSelector.scss";
@import "./views/auth/_Welcome.scss";
@@ -114,6 +115,7 @@
@import "./views/elements/_RichText.scss";
@import "./views/elements/_RoleButton.scss";
@import "./views/elements/_RoomAliasField.scss";
+@import "./views/elements/_Slider.scss";
@import "./views/elements/_Spinner.scss";
@import "./views/elements/_SyntaxHighlight.scss";
@import "./views/elements/_TextWithTooltip.scss";
@@ -161,6 +163,8 @@
@import "./views/rooms/_EditMessageComposer.scss";
@import "./views/rooms/_EntityTile.scss";
@import "./views/rooms/_EventTile.scss";
+@import "./views/rooms/_GroupLayout.scss";
+@import "./views/rooms/_IRCLayout.scss";
@import "./views/rooms/_InviteOnlyIcon.scss";
@import "./views/rooms/_JumpToBottomButton.scss";
@import "./views/rooms/_LinkPreviewWidget.scss";
@@ -203,6 +207,7 @@
@import "./views/settings/tabs/room/_GeneralRoomSettingsTab.scss";
@import "./views/settings/tabs/room/_RolesRoomSettingsTab.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/_HelpUserSettingsTab.scss";
@import "./views/settings/tabs/user/_MjolnirUserSettingsTab.scss";
diff --git a/res/css/structures/_TagPanel.scss b/res/css/structures/_TagPanel.scss
index 4a78c8df92..1f8443e395 100644
--- a/res/css/structures/_TagPanel.scss
+++ b/res/css/structures/_TagPanel.scss
@@ -69,7 +69,7 @@ limitations under the License.
height: 100%;
}
.mx_TagPanel .mx_TagPanel_tagTileContainer > div {
- height: $font-40px;
+ height: 40px;
padding: 10px 0 9px 0;
}
@@ -116,7 +116,7 @@ limitations under the License.
position: absolute;
left: -15px;
border-radius: 0 3px 3px 0;
- top: -8px; // (16px / 2)
+ top: -8px; // (16px from height / 2)
}
.mx_TagPanel .mx_TagTile.mx_AccessibleButton:focus {
diff --git a/res/css/structures/_TopLeftMenuButton.scss b/res/css/structures/_TopLeftMenuButton.scss
index 53d44e7c24..8d2e36bcd6 100644
--- a/res/css/structures/_TopLeftMenuButton.scss
+++ b/res/css/structures/_TopLeftMenuButton.scss
@@ -43,7 +43,7 @@ limitations under the License.
margin: 0 7px;
mask: url('$(res)/img/feather-customised/dropdown-arrow.svg');
mask-repeat: no-repeat;
- width: 10px;
+ width: $font-22px;
height: 6px;
background-color: $roomsublist-label-fg-color;
}
diff --git a/res/css/views/auth/_AuthBody.scss b/res/css/views/auth/_AuthBody.scss
index 4b2d6b1bf1..120da4c4f1 100644
--- a/res/css/views/auth/_AuthBody.scss
+++ b/res/css/views/auth/_AuthBody.scss
@@ -146,27 +146,3 @@ limitations under the License.
.mx_AuthBody_spinner {
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;
- }
-}
diff --git a/res/css/views/auth/_PassphraseField.scss b/res/css/views/auth/_PassphraseField.scss
new file mode 100644
index 0000000000..d1b8c47d00
--- /dev/null
+++ b/res/css/views/auth/_PassphraseField.scss
@@ -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;
+ }
+}
diff --git a/res/css/views/dialogs/_UserSettingsDialog.scss b/res/css/views/dialogs/_UserSettingsDialog.scss
index 4d831d7858..7adcc58c4e 100644
--- a/res/css/views/dialogs/_UserSettingsDialog.scss
+++ b/res/css/views/dialogs/_UserSettingsDialog.scss
@@ -21,6 +21,10 @@ limitations under the License.
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 {
mask-image: url('$(res)/img/feather-customised/phone.svg');
}
diff --git a/res/css/views/dialogs/keybackup/_CreateKeyBackupDialog.scss b/res/css/views/dialogs/keybackup/_CreateKeyBackupDialog.scss
index b9babd05f5..9be98e25b2 100644
--- a/res/css/views/dialogs/keybackup/_CreateKeyBackupDialog.scss
+++ b/res/css/views/dialogs/keybackup/_CreateKeyBackupDialog.scss
@@ -35,17 +35,6 @@ limitations under the License.
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 {
flex: none;
width: 250px;
diff --git a/res/css/views/dialogs/secretstorage/_CreateSecretStorageDialog.scss b/res/css/views/dialogs/secretstorage/_CreateSecretStorageDialog.scss
index a9ebd54b31..63e5a3de09 100644
--- a/res/css/views/dialogs/secretstorage/_CreateSecretStorageDialog.scss
+++ b/res/css/views/dialogs/secretstorage/_CreateSecretStorageDialog.scss
@@ -68,17 +68,6 @@ limitations under the License.
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 {
width: 200px;
margin-left: 20px;
diff --git a/res/css/views/elements/_Slider.scss b/res/css/views/elements/_Slider.scss
new file mode 100644
index 0000000000..58ba2813b4
--- /dev/null
+++ b/res/css/views/elements/_Slider.scss
@@ -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%;
+}
diff --git a/res/css/views/rooms/_AppsDrawer.scss b/res/css/views/rooms/_AppsDrawer.scss
index 1b1bab67bc..e4743f189e 100644
--- a/res/css/views/rooms/_AppsDrawer.scss
+++ b/res/css/views/rooms/_AppsDrawer.scss
@@ -96,6 +96,10 @@ $AppsDrawerBodyHeight: 273px;
height: $AppsDrawerBodyHeight;
}
+.mx_AppTile_persistedWrapper > div {
+ height: 100%;
+}
+
.mx_AppTile_mini .mx_AppTile_persistedWrapper {
height: 114px;
}
diff --git a/res/css/views/rooms/_EventTile.scss b/res/css/views/rooms/_EventTile.scss
index d4e54f4473..40a80f17bb 100644
--- a/res/css/views/rooms/_EventTile.scss
+++ b/res/css/views/rooms/_EventTile.scss
@@ -37,7 +37,6 @@ limitations under the License.
}
.mx_EventTile_avatar {
- position: absolute;
top: 14px;
left: 8px;
cursor: pointer;
@@ -68,11 +67,9 @@ limitations under the License.
display: inline-block; /* anti-zalgo, with overflow hidden */
overflow: hidden;
cursor: pointer;
- padding-left: 65px; /* left gutter */
padding-bottom: 0px;
padding-top: 0px;
margin: 0px;
- line-height: $font-17px;
/* the next three lines, along with overflow hidden, truncate long display names */
white-space: nowrap;
text-overflow: ellipsis;
@@ -104,9 +101,7 @@ limitations under the License.
visibility: hidden;
white-space: nowrap;
left: 0px;
- width: 46px; /* 8 + 30 (avatar) + 8 */
text-align: center;
- position: absolute;
user-select: none;
}
@@ -117,10 +112,7 @@ limitations under the License.
.mx_EventTile_line, .mx_EventTile_reply {
position: relative;
padding-left: 65px; /* left gutter */
- padding-top: 3px;
- padding-bottom: 3px;
border-radius: 4px;
- line-height: $font-22px;
}
.mx_RoomView_timeline_rr_enabled,
@@ -151,10 +143,6 @@ limitations under the License.
margin-right: 10px;
}
-.mx_EventTile_info .mx_EventTile_line {
- padding-left: 83px;
-}
-
/* HACK to override line-height which is already marked important elsewhere */
.mx_EventTile_bigEmoji.mx_EventTile_bigEmoji {
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)
+// 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:hover > 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;
}
@@ -560,84 +553,6 @@ limitations under the License.
/* 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 {
color: red;
text-align: center;
diff --git a/res/css/views/rooms/_GroupLayout.scss b/res/css/views/rooms/_GroupLayout.scss
new file mode 100644
index 0000000000..928ea75a79
--- /dev/null
+++ b/res/css/views/rooms/_GroupLayout.scss
@@ -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
+ }
+ }
+}
diff --git a/res/css/views/rooms/_IRCLayout.scss b/res/css/views/rooms/_IRCLayout.scss
new file mode 100644
index 0000000000..5f88473c5f
--- /dev/null
+++ b/res/css/views/rooms/_IRCLayout.scss
@@ -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;
+ }
+}
diff --git a/res/css/views/rooms/_RoomTile.scss b/res/css/views/rooms/_RoomTile.scss
index 5bc7d5624d..759dce5afa 100644
--- a/res/css/views/rooms/_RoomTile.scss
+++ b/res/css/views/rooms/_RoomTile.scss
@@ -20,7 +20,7 @@ limitations under the License.
flex-direction: row;
align-items: center;
cursor: pointer;
- height: $font-34px;
+ height: 32px;
margin: 0;
padding: 0 8px 0 10px;
position: relative;
@@ -81,6 +81,7 @@ limitations under the License.
.mx_RoomTile_avatar_container {
position: relative;
+ display: flex;
}
.mx_RoomTile_avatar {
diff --git a/res/css/views/settings/tabs/user/_AppearanceUserSettingsTab.scss b/res/css/views/settings/tabs/user/_AppearanceUserSettingsTab.scss
new file mode 100644
index 0000000000..e82ae3c575
--- /dev/null
+++ b/res/css/views/settings/tabs/user/_AppearanceUserSettingsTab.scss
@@ -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;
+}
diff --git a/res/css/views/settings/tabs/user/_GeneralUserSettingsTab.scss b/res/css/views/settings/tabs/user/_GeneralUserSettingsTab.scss
index 95a46b51ee..6c9b89cf5a 100644
--- a/res/css/views/settings/tabs/user/_GeneralUserSettingsTab.scss
+++ b/res/css/views/settings/tabs/user/_GeneralUserSettingsTab.scss
@@ -14,8 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
-.mx_GeneralUserSettingsTab_changePassword .mx_Field,
-.mx_GeneralUserSettingsTab_themeSection .mx_Field {
+.mx_GeneralUserSettingsTab_changePassword .mx_Field {
@mixin mx_Settings_fullWidthField;
}
diff --git a/res/img/feather-customised/brush.svg b/res/img/feather-customised/brush.svg
new file mode 100644
index 0000000000..d7f2738629
--- /dev/null
+++ b/res/img/feather-customised/brush.svg
@@ -0,0 +1,5 @@
+
diff --git a/res/themes/dark/css/_dark.scss b/res/themes/dark/css/_dark.scss
index 6224c0820f..9fb36ef1a3 100644
--- a/res/themes/dark/css/_dark.scss
+++ b/res/themes/dark/css/_dark.scss
@@ -180,6 +180,9 @@ $breadcrumb-placeholder-bg-color: #272c35;
$user-tile-hover-bg-color: $header-panel-bg-color;
+// FontSlider colors
+$font-slider-bg-color: $room-highlight-color;
+
// ***** Mixins! *****
@define-mixin mx_DialogButton {
diff --git a/res/themes/light/css/_light.scss b/res/themes/light/css/_light.scss
index f5f3013354..78fe2a74c5 100644
--- a/res/themes/light/css/_light.scss
+++ b/res/themes/light/css/_light.scss
@@ -262,6 +262,10 @@ $togglesw-off-color: #c1c9d6;
$togglesw-on-color: $accent-color;
$togglesw-ball-color: #fff;
+// Slider
+$slider-selection-color: $accent-color;
+$slider-background-color: #c1c9d6;
+
$progressbar-color: #000;
$room-warning-bg-color: $yellow-background;
@@ -302,6 +306,9 @@ $breadcrumb-placeholder-bg-color: #e8eef5;
$user-tile-hover-bg-color: $header-panel-bg-color;
+// FontSlider colors
+$font-slider-bg-color: rgba($input-darker-bg-color, 0.2);
+
// ***** Mixins! *****
@define-mixin mx_DialogButton {
diff --git a/src/BasePlatform.js b/src/BasePlatform.ts
similarity index 80%
rename from src/BasePlatform.js
rename to src/BasePlatform.ts
index 8a950dc2e3..d4a6c34daf 100644
--- a/src/BasePlatform.js
+++ b/src/BasePlatform.ts
@@ -1,5 +1,3 @@
-// @flow
-
/*
Copyright 2016 Aviral Dasgupta
Copyright 2016 OpenMarket Ltd
@@ -19,9 +17,10 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
-import {MatrixClient} from "matrix-js-sdk";
-import dis from './dispatcher';
+import {MatrixClient} from "matrix-js-sdk/src/client";
+import dis from './dispatcher/dispatcher';
import BaseEventIndexManager from './indexing/BaseEventIndexManager';
+import {ActionPayload} from "./dispatcher/payloads";
/**
* 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.
*/
-export default class BasePlatform {
- constructor() {
- this.notificationCount = 0;
- this.errorDidOccur = false;
+export default abstract class BasePlatform {
+ protected notificationCount = 0;
+ protected errorDidOccur = false;
- dis.register(this._onAction.bind(this));
+ constructor() {
+ dis.register(this.onAction);
}
- _onAction(payload: Object) {
+ protected onAction = (payload: ActionPayload) => {
switch (payload.action) {
case 'on_client_not_viable':
case 'on_logged_out':
this.setNotificationCount(0);
break;
}
- }
+ };
// Used primarily for Analytics
- getHumanReadableName(): string {
- return 'Base Platform';
- }
+ abstract getHumanReadableName(): string;
setNotificationCount(count: number) {
this.notificationCount = count;
@@ -84,22 +81,17 @@ export default class BasePlatform {
* that is 'granted' if the user allowed the request or
* 'denied' otherwise.
*/
- requestNotificationPermission(): Promise {
- }
+ abstract requestNotificationPermission(): Promise;
- displayNotification(title: string, msg: string, avatarUrl: string, room: Object) {
- }
+ abstract displayNotification(title: string, msg: string, avatarUrl: string, room: Object);
loudNotification(ev: Event, room: Object) {
- }
+ };
/**
- * Returns a promise that resolves to a string representing
- * the current version of the application.
+ * Returns a promise that resolves to a string representing the current version of the application.
*/
- getAppVersion(): Promise {
- throw new Error("getAppVersion not implemented!");
- }
+ abstract getAppVersion(): Promise;
/*
* 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
* any application code
*/
- reload() {
- throw new Error("reload not implemented!");
- }
+ abstract reload();
supportsAutoLaunch(): boolean {
return false;
}
// XXX: Surely this should be a setting like any other?
- async getAutoLaunchEnabled(): boolean {
+ async getAutoLaunchEnabled(): Promise {
return false;
}
- async setAutoLaunchEnabled(enabled: boolean): void {
+ async setAutoLaunchEnabled(enabled: boolean): Promise {
throw new Error("Unimplemented");
}
@@ -135,11 +125,11 @@ export default class BasePlatform {
return false;
}
- async getAutoHideMenuBarEnabled(): boolean {
+ async getAutoHideMenuBarEnabled(): Promise {
return false;
}
- async setAutoHideMenuBarEnabled(enabled: boolean): void {
+ async setAutoHideMenuBarEnabled(enabled: boolean): Promise {
throw new Error("Unimplemented");
}
@@ -147,11 +137,11 @@ export default class BasePlatform {
return false;
}
- async getMinimizeToTrayEnabled(): boolean {
+ async getMinimizeToTrayEnabled(): Promise {
return false;
}
- async setMinimizeToTrayEnabled(enabled: boolean): void {
+ async setMinimizeToTrayEnabled(enabled: boolean): Promise {
throw new Error("Unimplemented");
}
diff --git a/src/CallHandler.js b/src/CallHandler.js
index 2bfe10850a..c95ed16eb3 100644
--- a/src/CallHandler.js
+++ b/src/CallHandler.js
@@ -59,7 +59,7 @@ import Modal from './Modal';
import * as sdk from './index';
import { _t } from './languageHandler';
import Matrix from 'matrix-js-sdk';
-import dis from './dispatcher';
+import dis from './dispatcher/dispatcher';
import { showUnknownDeviceDialogForCalls } from './cryptodevices';
import WidgetUtils from './utils/WidgetUtils';
import WidgetEchoStore from './stores/WidgetEchoStore';
diff --git a/src/ContentMessages.js b/src/ContentMessages.js
index 34379c029b..4f5a1a1220 100644
--- a/src/ContentMessages.js
+++ b/src/ContentMessages.js
@@ -18,7 +18,7 @@ limitations under the License.
'use strict';
import extend from './extend';
-import dis from './dispatcher';
+import dis from './dispatcher/dispatcher';
import {MatrixClientPeg} from './MatrixClientPeg';
import * as sdk from './index';
import { _t } from './languageHandler';
diff --git a/src/FontWatcher.js b/src/FontWatcher.js
new file mode 100644
index 0000000000..006df202ad
--- /dev/null
+++ b/src/FontWatcher.js
@@ -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";
+ };
+}
diff --git a/src/FromWidgetPostMessageApi.js b/src/FromWidgetPostMessageApi.js
index c9793d40f7..102afa6bf1 100644
--- a/src/FromWidgetPostMessageApi.js
+++ b/src/FromWidgetPostMessageApi.js
@@ -17,7 +17,7 @@ limitations under the License.
*/
import URL from 'url';
-import dis from './dispatcher';
+import dis from './dispatcher/dispatcher';
import WidgetMessagingEndpoint from './WidgetMessagingEndpoint';
import ActiveWidgetStore from './stores/ActiveWidgetStore';
import {MatrixClientPeg} from "./MatrixClientPeg";
diff --git a/src/Lifecycle.js b/src/Lifecycle.js
index 1baa6c8e0c..22c5d48317 100644
--- a/src/Lifecycle.js
+++ b/src/Lifecycle.js
@@ -26,7 +26,7 @@ import Analytics from './Analytics';
import Notifier from './Notifier';
import UserActivity from './UserActivity';
import Presence from './Presence';
-import dis from './dispatcher';
+import dis from './dispatcher/dispatcher';
import DMRoomMap from './utils/DMRoomMap';
import Modal from './Modal';
import * as sdk from './index';
diff --git a/src/Modal.js b/src/Modal.js
index de441740f1..9b9f190d58 100644
--- a/src/Modal.js
+++ b/src/Modal.js
@@ -18,7 +18,7 @@ limitations under the License.
import React from 'react';
import ReactDOM from 'react-dom';
import Analytics from './Analytics';
-import dis from './dispatcher';
+import dis from './dispatcher/dispatcher';
import {defer} from './utils/promise';
import AsyncWrapper from './AsyncWrapper';
diff --git a/src/Notifier.js b/src/Notifier.js
index ec92840998..2ffa92452b 100644
--- a/src/Notifier.js
+++ b/src/Notifier.js
@@ -21,7 +21,7 @@ import PlatformPeg from './PlatformPeg';
import * as TextForEvent from './TextForEvent';
import Analytics from './Analytics';
import * as Avatar from './Avatar';
-import dis from './dispatcher';
+import dis from './dispatcher/dispatcher';
import * as sdk from './index';
import { _t } from './languageHandler';
import Modal from './Modal';
diff --git a/src/Presence.js b/src/Presence.js
index 2fc13a090b..42bca35f96 100644
--- a/src/Presence.js
+++ b/src/Presence.js
@@ -17,7 +17,7 @@ limitations under the License.
*/
import {MatrixClientPeg} from "./MatrixClientPeg";
-import dis from "./dispatcher";
+import dis from "./dispatcher/dispatcher";
import Timer from './utils/Timer';
// Time in ms after that a user is considered as unavailable/away
diff --git a/src/Registration.js b/src/Registration.js
index ca162bac03..32c3d9cc35 100644
--- a/src/Registration.js
+++ b/src/Registration.js
@@ -20,7 +20,7 @@ limitations under the License.
* registration code.
*/
-import dis from './dispatcher';
+import dis from './dispatcher/dispatcher';
import * as sdk from './index';
import Modal from './Modal';
import { _t } from './languageHandler';
diff --git a/src/Resend.js b/src/Resend.js
index 6d6c18cf27..f5f24bffa5 100644
--- a/src/Resend.js
+++ b/src/Resend.js
@@ -16,7 +16,7 @@ limitations under the License.
*/
import {MatrixClientPeg} from './MatrixClientPeg';
-import dis from './dispatcher';
+import dis from './dispatcher/dispatcher';
import { EventStatus } from 'matrix-js-sdk';
export default class Resend {
diff --git a/src/ScalarMessaging.js b/src/ScalarMessaging.js
index 9731e42825..315c2d86f4 100644
--- a/src/ScalarMessaging.js
+++ b/src/ScalarMessaging.js
@@ -238,7 +238,7 @@ Example:
import {MatrixClientPeg} from './MatrixClientPeg';
import { MatrixEvent } from 'matrix-js-sdk';
-import dis from './dispatcher';
+import dis from './dispatcher/dispatcher';
import WidgetUtils from './utils/WidgetUtils';
import RoomViewStore from './stores/RoomViewStore';
import { _t } from './languageHandler';
diff --git a/src/SlashCommands.tsx b/src/SlashCommands.tsx
index fbb9e2eb0e..d81da80e8d 100644
--- a/src/SlashCommands.tsx
+++ b/src/SlashCommands.tsx
@@ -21,7 +21,7 @@ limitations under the License.
import * as React from 'react';
import {MatrixClientPeg} from './MatrixClientPeg';
-import dis from './dispatcher';
+import dis from './dispatcher/dispatcher';
import * as sdk from './index';
import {_t, _td} from './languageHandler';
import Modal from './Modal';
@@ -41,6 +41,8 @@ import { parseFragment as parseHtml } from "parse5";
import sendBugReport from "./rageshake/submit-rageshake";
import SdkConfig from "./SdkConfig";
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
interface HTMLInputEvent extends Event {
@@ -943,8 +945,10 @@ export const Commands = [
}
const member = MatrixClientPeg.get().getRoom(roomId).getMember(userId);
- dis.dispatch({
- action: 'view_user',
+ dis.dispatch({
+ action: Action.ViewUser,
+ // XXX: We should be using a real member object and not assuming what the
+ // receiver wants.
member: member || {userId},
});
return success();
diff --git a/src/UserActivity.js b/src/UserActivity.js
index 0d1b4d0cc0..0174aebaf5 100644
--- a/src/UserActivity.js
+++ b/src/UserActivity.js
@@ -15,7 +15,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
-import dis from './dispatcher';
+import dis from './dispatcher/dispatcher';
import Timer from './utils/Timer';
// important these are larger than the timeouts of timers
diff --git a/src/actions/GroupActions.js b/src/actions/GroupActions.js
deleted file mode 100644
index 006c2da5b8..0000000000
--- a/src/actions/GroupActions.js
+++ /dev/null
@@ -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;
diff --git a/src/actions/GroupActions.ts b/src/actions/GroupActions.ts
new file mode 100644
index 0000000000..81470d1221
--- /dev/null
+++ b/src/actions/GroupActions.ts
@@ -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);
+ }
+}
diff --git a/src/actions/MatrixActionCreators.js b/src/actions/MatrixActionCreators.js
index c89ec44435..93a4fcf07c 100644
--- a/src/actions/MatrixActionCreators.js
+++ b/src/actions/MatrixActionCreators.js
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
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
// become dispatches in the same place.
diff --git a/src/actions/RoomListActions.js b/src/actions/RoomListActions.js
deleted file mode 100644
index 10a3848dda..0000000000
--- a/src/actions/RoomListActions.js
+++ /dev/null
@@ -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;
diff --git a/src/actions/RoomListActions.ts b/src/actions/RoomListActions.ts
new file mode 100644
index 0000000000..e15e1b0c65
--- /dev/null
+++ b/src/actions/RoomListActions.ts
@@ -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,
+ };
+ });
+ }
+}
diff --git a/src/actions/TagOrderActions.js b/src/actions/TagOrderActions.js
deleted file mode 100644
index a257ff16d8..0000000000
--- a/src/actions/TagOrderActions.js
+++ /dev/null
@@ -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;
diff --git a/src/actions/TagOrderActions.ts b/src/actions/TagOrderActions.ts
new file mode 100644
index 0000000000..bf1820d5d1
--- /dev/null
+++ b/src/actions/TagOrderActions.ts
@@ -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};
+ });
+ }
+}
diff --git a/src/actions/actionCreators.js b/src/actions/actionCreators.ts
similarity index 76%
rename from src/actions/actionCreators.js
rename to src/actions/actionCreators.ts
index 967ce609e7..c789e3cd07 100644
--- a/src/actions/actionCreators.js
+++ b/src/actions/actionCreators.ts
@@ -1,5 +1,6 @@
/*
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.
@@ -14,6 +15,8 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
+import { AsyncActionPayload } from "../dispatcher/payloads";
+
/**
* Create an action thunk that will dispatch actions indicating the current
* 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
* to the `request` key of the ${id}.pending
* payload.
- * @returns {function} an action thunk - a function that uses its single
- * argument as a dispatch function to dispatch the
- * following actions:
+ * @returns {AsyncActionPayload} an async action payload. Includes a function
+ * that uses its single argument as a dispatch function
+ * to dispatch the following actions:
* `${id}.pending` and either
* `${id}.success` or
* `${id}.failure`.
@@ -41,12 +44,11 @@ limitations under the License.
* result is the result of the promise returned by
* `fn`.
*/
-export function asyncAction(id, fn, pendingFn) {
- return (dispatch) => {
+export function asyncAction(id: string, fn: () => Promise, pendingFn: () => any | null): AsyncActionPayload {
+ const helper = (dispatch) => {
dispatch({
action: id + '.pending',
- request:
- typeof pendingFn === 'function' ? pendingFn() : undefined,
+ request: typeof pendingFn === 'function' ? pendingFn() : undefined,
});
fn().then((result) => {
dispatch({action: id + '.success', result});
@@ -54,4 +56,5 @@ export function asyncAction(id, fn, pendingFn) {
dispatch({action: id + '.failure', err});
});
};
+ return new AsyncActionPayload(helper);
}
diff --git a/src/async-components/views/dialogs/eventindex/DisableEventIndexDialog.js b/src/async-components/views/dialogs/eventindex/DisableEventIndexDialog.js
index 120b086ef6..ec4b88f759 100644
--- a/src/async-components/views/dialogs/eventindex/DisableEventIndexDialog.js
+++ b/src/async-components/views/dialogs/eventindex/DisableEventIndexDialog.js
@@ -17,11 +17,12 @@ limitations under the License.
import React from 'react';
import * as sdk from '../../../../index';
import PropTypes from 'prop-types';
-import dis from "../../../../dispatcher";
+import dis from "../../../../dispatcher/dispatcher";
import { _t } from '../../../../languageHandler';
import SettingsStore, {SettingLevel} from "../../../../settings/SettingsStore";
import EventIndexPeg from "../../../../indexing/EventIndexPeg";
+import {Action} from "../../../../dispatcher/actions";
/*
* 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 EventIndexPeg.deleteEventIndex();
this.props.onFinished();
- dis.dispatch({ action: 'view_user_settings' });
+ dis.fire(Action.ViewUserSettings);
}
render() {
diff --git a/src/async-components/views/dialogs/keybackup/CreateKeyBackupDialog.js b/src/async-components/views/dialogs/keybackup/CreateKeyBackupDialog.js
index e4e39400f6..532b2f960f 100644
--- a/src/async-components/views/dialogs/keybackup/CreateKeyBackupDialog.js
+++ b/src/async-components/views/dialogs/keybackup/CreateKeyBackupDialog.js
@@ -15,17 +15,17 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
-import React from 'react';
+import React, {createRef} from 'react';
import FileSaver from 'file-saver';
import * as sdk from '../../../../index';
import {MatrixClientPeg} from '../../../../MatrixClientPeg';
import PropTypes from 'prop-types';
-import { scorePassword } from '../../../../utils/PasswordScorer';
-import { _t } from '../../../../languageHandler';
+import {_t, _td} from '../../../../languageHandler';
import { accessSecretStorage } from '../../../../CrossSigningManager';
import SettingsStore from '../../../../settings/SettingsStore';
import AccessibleButton from "../../../../components/views/elements/AccessibleButton";
import {copyNode} from "../../../../utils/strings";
+import PassphraseField from "../../../../components/views/auth/PassphraseField";
const PHASE_PASSPHRASE = 0;
const PHASE_PASSPHRASE_CONFIRM = 1;
@@ -36,7 +36,6 @@ const PHASE_DONE = 5;
const PHASE_OPTOUT_CONFIRM = 6;
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
@@ -52,17 +51,18 @@ export default class CreateKeyBackupDialog extends React.PureComponent {
this._recoveryKeyNode = null;
this._keyBackupInfo = null;
- this._setZxcvbnResultTimeout = null;
this.state = {
secureSecretStorage: null,
phase: PHASE_PASSPHRASE,
passPhrase: '',
+ passPhraseValid: false,
passPhraseConfirm: '',
copied: false,
downloaded: false,
- zxcvbnResult: null,
};
+
+ this._passphraseField = createRef();
}
async componentDidMount() {
@@ -81,12 +81,6 @@ export default class CreateKeyBackupDialog extends React.PureComponent {
}
}
- componentWillUnmount() {
- if (this._setZxcvbnResultTimeout !== null) {
- clearTimeout(this._setZxcvbnResultTimeout);
- }
- }
-
_collectRecoveryKeyNode = (n) => {
this._recoveryKeyNode = n;
}
@@ -180,22 +174,16 @@ export default class CreateKeyBackupDialog extends React.PureComponent {
_onPassPhraseNextClick = async (e) => {
e.preventDefault();
+ if (!this._passphraseField.current) return; // unmounting
- // If we're waiting for the timeout before updating the result at this point,
- // skip ahead and do it now, otherwise we'll deny the attempt to proceed
- // even if the user entered a valid passphrase
- if (this._setZxcvbnResultTimeout !== null) {
- clearTimeout(this._setZxcvbnResultTimeout);
- this._setZxcvbnResultTimeout = null;
- await new Promise((resolve) => {
- this.setState({
- zxcvbnResult: scorePassword(this.state.passPhrase),
- }, resolve);
- });
- }
- if (this._passPhraseIsValid()) {
- this.setState({phase: PHASE_PASSPHRASE_CONFIRM});
+ await this._passphraseField.current.validate({ allowEmpty: false });
+ if (!this._passphraseField.current.state.valid) {
+ this._passphraseField.current.focus();
+ this._passphraseField.current.validate({ allowEmpty: false, focused: true });
+ return;
}
+
+ this.setState({phase: PHASE_PASSPHRASE_CONFIRM});
};
_onPassPhraseConfirmNextClick = async (e) => {
@@ -214,9 +202,9 @@ export default class CreateKeyBackupDialog extends React.PureComponent {
_onSetAgainClick = () => {
this.setState({
passPhrase: '',
+ passPhraseValid: false,
passPhraseConfirm: '',
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) => {
this.setState({
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) => {
@@ -251,35 +232,9 @@ export default class CreateKeyBackupDialog extends React.PureComponent {
});
}
- _passPhraseIsValid() {
- return this.state.zxcvbnResult && this.state.zxcvbnResult.score >= PASSWORD_MIN_SCORE;
- }
-
_renderPhasePassPhrase() {
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(