Merge branch 'develop' into dehydration
|
@ -2,7 +2,6 @@
|
||||||
|
|
||||||
src/components/structures/RoomDirectory.js
|
src/components/structures/RoomDirectory.js
|
||||||
src/components/structures/RoomStatusBar.js
|
src/components/structures/RoomStatusBar.js
|
||||||
src/components/structures/RoomView.js
|
|
||||||
src/components/structures/ScrollPanel.js
|
src/components/structures/ScrollPanel.js
|
||||||
src/components/structures/SearchBox.js
|
src/components/structures/SearchBox.js
|
||||||
src/components/structures/UploadBar.js
|
src/components/structures/UploadBar.js
|
||||||
|
|
|
@ -19,7 +19,7 @@ module.exports = {
|
||||||
},
|
},
|
||||||
|
|
||||||
overrides: [{
|
overrides: [{
|
||||||
"files": ["src/**/*.{ts, tsx}"],
|
"files": ["src/**/*.{ts,tsx}"],
|
||||||
"extends": ["matrix-org/ts"],
|
"extends": ["matrix-org/ts"],
|
||||||
"rules": {
|
"rules": {
|
||||||
// We disable this while we're transitioning
|
// We disable this while we're transitioning
|
||||||
|
|
162
CHANGELOG.md
|
@ -1,3 +1,165 @@
|
||||||
|
Changes in [3.4.1](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v3.4.1) (2020-09-14)
|
||||||
|
===================================================================================================
|
||||||
|
[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v3.4.0...v3.4.1)
|
||||||
|
|
||||||
|
* Don't count widgets which no longer exist towards pinned count
|
||||||
|
[\#5202](https://github.com/matrix-org/matrix-react-sdk/pull/5202)
|
||||||
|
* Fix crashes with cannot read isResizing of undefined
|
||||||
|
[\#5205](https://github.com/matrix-org/matrix-react-sdk/pull/5205)
|
||||||
|
|
||||||
|
Changes in [3.4.0](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v3.4.0) (2020-09-14)
|
||||||
|
===================================================================================================
|
||||||
|
[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v3.4.0-rc.1...v3.4.0)
|
||||||
|
|
||||||
|
* Upgrade to JS SDK 8.3.0
|
||||||
|
* [Release] Show verification status in the room summary card
|
||||||
|
[\#5196](https://github.com/matrix-org/matrix-react-sdk/pull/5196)
|
||||||
|
* Fix user info scrolling in new card view
|
||||||
|
[\#5200](https://github.com/matrix-org/matrix-react-sdk/pull/5200)
|
||||||
|
* Fix sticker picker height
|
||||||
|
[\#5199](https://github.com/matrix-org/matrix-react-sdk/pull/5199)
|
||||||
|
* [Release] Account for via in pill matching regex
|
||||||
|
[\#5190](https://github.com/matrix-org/matrix-react-sdk/pull/5190)
|
||||||
|
|
||||||
|
Changes in [3.4.0-rc.1](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v3.4.0-rc.1) (2020-09-09)
|
||||||
|
=============================================================================================================
|
||||||
|
[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v3.3.0...v3.4.0-rc.1)
|
||||||
|
|
||||||
|
* Upgrade to JS SDK 8.3.0-rc.1
|
||||||
|
* Update from Weblate
|
||||||
|
[\#5183](https://github.com/matrix-org/matrix-react-sdk/pull/5183)
|
||||||
|
* Right Panel Room Summary and Widgets
|
||||||
|
[\#5167](https://github.com/matrix-org/matrix-react-sdk/pull/5167)
|
||||||
|
* null-guard roomId in RightPanel and pass Room to UserView
|
||||||
|
[\#5180](https://github.com/matrix-org/matrix-react-sdk/pull/5180)
|
||||||
|
* Fix create-react-class regression.
|
||||||
|
[\#5178](https://github.com/matrix-org/matrix-react-sdk/pull/5178)
|
||||||
|
* Fix WatchManager for global room watchers and tidy widget code a little
|
||||||
|
[\#5176](https://github.com/matrix-org/matrix-react-sdk/pull/5176)
|
||||||
|
* Fix permalink local linkification to not strip via servers
|
||||||
|
[\#5174](https://github.com/matrix-org/matrix-react-sdk/pull/5174)
|
||||||
|
* Support creation of Jitsi widgets with "openidtoken-jwt" auth
|
||||||
|
[\#5173](https://github.com/matrix-org/matrix-react-sdk/pull/5173)
|
||||||
|
* Fix create-react-class regression.
|
||||||
|
[\#5177](https://github.com/matrix-org/matrix-react-sdk/pull/5177)
|
||||||
|
* Update openid_credentials Widget API action for MSC1960 updates
|
||||||
|
[\#5172](https://github.com/matrix-org/matrix-react-sdk/pull/5172)
|
||||||
|
* Allow persistent resizing of the widget app drawer
|
||||||
|
[\#5138](https://github.com/matrix-org/matrix-react-sdk/pull/5138)
|
||||||
|
* add lenny face command
|
||||||
|
[\#5158](https://github.com/matrix-org/matrix-react-sdk/pull/5158)
|
||||||
|
* Prep work for Settings changes with cross-signing deferral
|
||||||
|
[\#5169](https://github.com/matrix-org/matrix-react-sdk/pull/5169)
|
||||||
|
* Small code clean ups and tweaks
|
||||||
|
[\#5168](https://github.com/matrix-org/matrix-react-sdk/pull/5168)
|
||||||
|
* Fix soft crash from TruncatedList in the createReactClass conversion
|
||||||
|
[\#5170](https://github.com/matrix-org/matrix-react-sdk/pull/5170)
|
||||||
|
* Remove create-react-class
|
||||||
|
[\#5157](https://github.com/matrix-org/matrix-react-sdk/pull/5157)
|
||||||
|
* Consolidate Lodash files in bundle
|
||||||
|
[\#5162](https://github.com/matrix-org/matrix-react-sdk/pull/5162)
|
||||||
|
* Communities v2 prototype: "In community" view
|
||||||
|
[\#5161](https://github.com/matrix-org/matrix-react-sdk/pull/5161)
|
||||||
|
* Respect user preference for whether pills should have an avatar or not
|
||||||
|
[\#5165](https://github.com/matrix-org/matrix-react-sdk/pull/5165)
|
||||||
|
* Communities v2 prototype: DM copy updates
|
||||||
|
[\#5153](https://github.com/matrix-org/matrix-react-sdk/pull/5153)
|
||||||
|
* Only wait for public keys during verification
|
||||||
|
[\#5164](https://github.com/matrix-org/matrix-react-sdk/pull/5164)
|
||||||
|
* Fix eslint ts override tsx matching and delint
|
||||||
|
[\#5155](https://github.com/matrix-org/matrix-react-sdk/pull/5155)
|
||||||
|
* Fix react error about functional components can't take refs
|
||||||
|
[\#5159](https://github.com/matrix-org/matrix-react-sdk/pull/5159)
|
||||||
|
* Remove redundant components and devDependencies
|
||||||
|
[\#5156](https://github.com/matrix-org/matrix-react-sdk/pull/5156)
|
||||||
|
* Add display-capture to iframe allow for widgets
|
||||||
|
[\#5154](https://github.com/matrix-org/matrix-react-sdk/pull/5154)
|
||||||
|
* Update create room dialog copy & community prototype home icon
|
||||||
|
[\#5151](https://github.com/matrix-org/matrix-react-sdk/pull/5151)
|
||||||
|
* Migrate to new, separate APIs for cross-signing and secret storage
|
||||||
|
[\#5149](https://github.com/matrix-org/matrix-react-sdk/pull/5149)
|
||||||
|
* Fix clicking the background of the tag panel not clearing the filter
|
||||||
|
[\#5152](https://github.com/matrix-org/matrix-react-sdk/pull/5152)
|
||||||
|
* Communities v2 prototype: Associate created rooms with the selected
|
||||||
|
community
|
||||||
|
[\#5147](https://github.com/matrix-org/matrix-react-sdk/pull/5147)
|
||||||
|
* Communities v2 prototype: Tag panel selection changes
|
||||||
|
[\#5145](https://github.com/matrix-org/matrix-react-sdk/pull/5145)
|
||||||
|
* Communities v2 prototype: Create community flow
|
||||||
|
[\#5144](https://github.com/matrix-org/matrix-react-sdk/pull/5144)
|
||||||
|
* Communities v2 prototype: Override invite aesthetics for community-as-room
|
||||||
|
invites
|
||||||
|
[\#5143](https://github.com/matrix-org/matrix-react-sdk/pull/5143)
|
||||||
|
|
||||||
|
Changes in [3.3.0](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v3.3.0) (2020-09-01)
|
||||||
|
===================================================================================================
|
||||||
|
[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v3.3.0-rc.1...v3.3.0)
|
||||||
|
|
||||||
|
* Upgrade to JS SDK 8.2.0
|
||||||
|
|
||||||
|
Changes in [3.3.0-rc.1](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v3.3.0-rc.1) (2020-08-26)
|
||||||
|
=============================================================================================================
|
||||||
|
[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v3.2.0...v3.3.0-rc.1)
|
||||||
|
|
||||||
|
* Upgrade to JS SDK 8.2.0-rc.1
|
||||||
|
* Update from Weblate
|
||||||
|
[\#5146](https://github.com/matrix-org/matrix-react-sdk/pull/5146)
|
||||||
|
* BaseAvatar avoid initial render with default avatar
|
||||||
|
[\#5142](https://github.com/matrix-org/matrix-react-sdk/pull/5142)
|
||||||
|
* Enforce Secure Backup completion when requested by HS
|
||||||
|
[\#5130](https://github.com/matrix-org/matrix-react-sdk/pull/5130)
|
||||||
|
* Communities v2 prototype: Explore rooms, global state, and default room
|
||||||
|
[\#5139](https://github.com/matrix-org/matrix-react-sdk/pull/5139)
|
||||||
|
* Add communities v2 prototyping feature flag + initial tag panel prototypes
|
||||||
|
[\#5133](https://github.com/matrix-org/matrix-react-sdk/pull/5133)
|
||||||
|
* Remove some unused components
|
||||||
|
[\#5134](https://github.com/matrix-org/matrix-react-sdk/pull/5134)
|
||||||
|
* Allow avatar image view for 1:1 rooms
|
||||||
|
[\#5137](https://github.com/matrix-org/matrix-react-sdk/pull/5137)
|
||||||
|
* Send mx_local_settings in rageshake
|
||||||
|
[\#5136](https://github.com/matrix-org/matrix-react-sdk/pull/5136)
|
||||||
|
* Run all room leaving behaviour through a single function
|
||||||
|
[\#5132](https://github.com/matrix-org/matrix-react-sdk/pull/5132)
|
||||||
|
* Add clarifying comment in media device selection
|
||||||
|
[\#5131](https://github.com/matrix-org/matrix-react-sdk/pull/5131)
|
||||||
|
* Settings v3: Feature flag changes
|
||||||
|
[\#5124](https://github.com/matrix-org/matrix-react-sdk/pull/5124)
|
||||||
|
* Clear url previews if they all get edited out of the event
|
||||||
|
[\#5129](https://github.com/matrix-org/matrix-react-sdk/pull/5129)
|
||||||
|
* Consider tab completions as modifications for editing purposes to unlock
|
||||||
|
sending
|
||||||
|
[\#5128](https://github.com/matrix-org/matrix-react-sdk/pull/5128)
|
||||||
|
* Use matrix-doc for SAS emoji translations
|
||||||
|
[\#5125](https://github.com/matrix-org/matrix-react-sdk/pull/5125)
|
||||||
|
* Add a rageshake function to download the logs locally
|
||||||
|
[\#3849](https://github.com/matrix-org/matrix-react-sdk/pull/3849)
|
||||||
|
* Room List filtering visual tweaks
|
||||||
|
[\#5123](https://github.com/matrix-org/matrix-react-sdk/pull/5123)
|
||||||
|
* Make reply preview not an overlay so you can see new messages
|
||||||
|
[\#5072](https://github.com/matrix-org/matrix-react-sdk/pull/5072)
|
||||||
|
* Allow room tile context menu when minimized using right click
|
||||||
|
[\#5113](https://github.com/matrix-org/matrix-react-sdk/pull/5113)
|
||||||
|
* Add null guard to group inviter for corrupted groups
|
||||||
|
[\#5121](https://github.com/matrix-org/matrix-react-sdk/pull/5121)
|
||||||
|
* Room List styling tweaks
|
||||||
|
[\#5118](https://github.com/matrix-org/matrix-react-sdk/pull/5118)
|
||||||
|
* Fix corner rounding on images not always affecting right side
|
||||||
|
[\#5120](https://github.com/matrix-org/matrix-react-sdk/pull/5120)
|
||||||
|
* Change add room action for rooms to context menu
|
||||||
|
[\#5108](https://github.com/matrix-org/matrix-react-sdk/pull/5108)
|
||||||
|
* Switch out the globe icon and colour it depending on theme
|
||||||
|
[\#5106](https://github.com/matrix-org/matrix-react-sdk/pull/5106)
|
||||||
|
* Message Action Bar watch for event send changes
|
||||||
|
[\#5115](https://github.com/matrix-org/matrix-react-sdk/pull/5115)
|
||||||
|
* Put message previews for Emoji behind Labs
|
||||||
|
[\#5110](https://github.com/matrix-org/matrix-react-sdk/pull/5110)
|
||||||
|
* Fix styling for selected community marker
|
||||||
|
[\#5107](https://github.com/matrix-org/matrix-react-sdk/pull/5107)
|
||||||
|
* Fix action bar safe area regression
|
||||||
|
[\#5111](https://github.com/matrix-org/matrix-react-sdk/pull/5111)
|
||||||
|
* Fix /op slash command
|
||||||
|
[\#5109](https://github.com/matrix-org/matrix-react-sdk/pull/5109)
|
||||||
|
|
||||||
Changes in [3.2.0](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v3.2.0) (2020-08-17)
|
Changes in [3.2.0](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v3.2.0) (2020-08-17)
|
||||||
===================================================================================================
|
===================================================================================================
|
||||||
[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v3.2.0-rc.1...v3.2.0)
|
[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v3.2.0-rc.1...v3.2.0)
|
||||||
|
|
|
@ -120,6 +120,18 @@ Call `SettingsStore.getValue()` as you would for any other setting.
|
||||||
|
|
||||||
Call `SettingsStore.setValue("feature_name", null, SettingLevel.DEVICE, true)`.
|
Call `SettingsStore.setValue("feature_name", null, SettingLevel.DEVICE, true)`.
|
||||||
|
|
||||||
|
### A note on UI features
|
||||||
|
|
||||||
|
UI features are a different concept to plain features. Instead of being representative of unstable or
|
||||||
|
unpredicatable behaviour, they are logical chunks of UI which can be disabled by deployments for ease
|
||||||
|
of understanding with users. They are simply represented as boring settings with a convention of being
|
||||||
|
named as `UIFeature.$location` where `$location` is a rough descriptor of what is toggled, such as
|
||||||
|
`URLPreviews` or `Communities`.
|
||||||
|
|
||||||
|
UI features also tend to have their own setting controller (see below) to manipulate settings which might
|
||||||
|
be affected by the UI feature being disabled. For example, if URL previews are disabled as a UI feature
|
||||||
|
then the URL preview options will use the `UIFeatureController` to ensure they remain disabled while the
|
||||||
|
UI feature is disabled.
|
||||||
|
|
||||||
## Setting controllers
|
## Setting controllers
|
||||||
|
|
||||||
|
@ -226,4 +238,3 @@ In practice, handlers which rely on remote changes (account data, room events, e
|
||||||
generalized `WatchManager` - a class specifically designed to deduplicate the logic of managing watchers. The handlers
|
generalized `WatchManager` - a class specifically designed to deduplicate the logic of managing watchers. The handlers
|
||||||
which are localized to the local client (device) generally just trigger the `WatchManager` when they manipulate the
|
which are localized to the local client (device) generally just trigger the `WatchManager` when they manipulate the
|
||||||
setting themselves as there's nothing to really 'watch'.
|
setting themselves as there's nothing to really 'watch'.
|
||||||
|
|
||||||
|
|
10
package.json
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "matrix-react-sdk",
|
"name": "matrix-react-sdk",
|
||||||
"version": "3.2.0",
|
"version": "3.4.1",
|
||||||
"description": "SDK for matrix.org using React",
|
"description": "SDK for matrix.org using React",
|
||||||
"author": "matrix.org",
|
"author": "matrix.org",
|
||||||
"repository": {
|
"repository": {
|
||||||
|
@ -61,7 +61,6 @@
|
||||||
"classnames": "^2.2.6",
|
"classnames": "^2.2.6",
|
||||||
"commonmark": "^0.29.1",
|
"commonmark": "^0.29.1",
|
||||||
"counterpart": "^0.18.6",
|
"counterpart": "^0.18.6",
|
||||||
"create-react-class": "^15.6.3",
|
|
||||||
"diff-dom": "^4.1.6",
|
"diff-dom": "^4.1.6",
|
||||||
"diff-match-patch": "^1.0.5",
|
"diff-match-patch": "^1.0.5",
|
||||||
"emojibase-data": "^5.0.1",
|
"emojibase-data": "^5.0.1",
|
||||||
|
@ -95,6 +94,7 @@
|
||||||
"react-focus-lock": "^2.4.1",
|
"react-focus-lock": "^2.4.1",
|
||||||
"react-transition-group": "^4.4.1",
|
"react-transition-group": "^4.4.1",
|
||||||
"resize-observer-polyfill": "^1.5.1",
|
"resize-observer-polyfill": "^1.5.1",
|
||||||
|
"rfc4648": "^1.4.0",
|
||||||
"sanitize-html": "^1.27.1",
|
"sanitize-html": "^1.27.1",
|
||||||
"tar-js": "^0.3.0",
|
"tar-js": "^0.3.0",
|
||||||
"text-encoding-utf-8": "^1.0.2",
|
"text-encoding-utf-8": "^1.0.2",
|
||||||
|
@ -149,7 +149,6 @@
|
||||||
"eslint-plugin-flowtype": "^2.50.3",
|
"eslint-plugin-flowtype": "^2.50.3",
|
||||||
"eslint-plugin-react": "^7.20.3",
|
"eslint-plugin-react": "^7.20.3",
|
||||||
"eslint-plugin-react-hooks": "^2.5.1",
|
"eslint-plugin-react-hooks": "^2.5.1",
|
||||||
"file-loader": "^3.0.1",
|
|
||||||
"glob": "^5.0.15",
|
"glob": "^5.0.15",
|
||||||
"jest": "^24.9.0",
|
"jest": "^24.9.0",
|
||||||
"jest-canvas-mock": "^2.2.0",
|
"jest-canvas-mock": "^2.2.0",
|
||||||
|
@ -158,14 +157,11 @@
|
||||||
"matrix-react-test-utils": "^0.2.2",
|
"matrix-react-test-utils": "^0.2.2",
|
||||||
"react-test-renderer": "^16.13.1",
|
"react-test-renderer": "^16.13.1",
|
||||||
"rimraf": "^2.7.1",
|
"rimraf": "^2.7.1",
|
||||||
"source-map-loader": "^0.2.4",
|
|
||||||
"stylelint": "^9.10.1",
|
"stylelint": "^9.10.1",
|
||||||
"stylelint-config-standard": "^18.3.0",
|
"stylelint-config-standard": "^18.3.0",
|
||||||
"stylelint-scss": "^3.18.0",
|
"stylelint-scss": "^3.18.0",
|
||||||
"typescript": "^3.9.7",
|
"typescript": "^3.9.7",
|
||||||
"walk": "^2.3.14",
|
"walk": "^2.3.14"
|
||||||
"webpack": "^4.43.0",
|
|
||||||
"webpack-cli": "^3.3.12"
|
|
||||||
},
|
},
|
||||||
"jest": {
|
"jest": {
|
||||||
"testMatch": [
|
"testMatch": [
|
||||||
|
|
|
@ -53,21 +53,22 @@
|
||||||
@import "./views/avatars/_PulsedAvatar.scss";
|
@import "./views/avatars/_PulsedAvatar.scss";
|
||||||
@import "./views/context_menus/_IconizedContextMenu.scss";
|
@import "./views/context_menus/_IconizedContextMenu.scss";
|
||||||
@import "./views/context_menus/_MessageContextMenu.scss";
|
@import "./views/context_menus/_MessageContextMenu.scss";
|
||||||
@import "./views/context_menus/_RoomTileContextMenu.scss";
|
|
||||||
@import "./views/context_menus/_StatusMessageContextMenu.scss";
|
@import "./views/context_menus/_StatusMessageContextMenu.scss";
|
||||||
@import "./views/context_menus/_TagTileContextMenu.scss";
|
@import "./views/context_menus/_TagTileContextMenu.scss";
|
||||||
@import "./views/context_menus/_TopLeftMenu.scss";
|
|
||||||
@import "./views/context_menus/_WidgetContextMenu.scss";
|
@import "./views/context_menus/_WidgetContextMenu.scss";
|
||||||
@import "./views/dialogs/_AddressPickerDialog.scss";
|
@import "./views/dialogs/_AddressPickerDialog.scss";
|
||||||
@import "./views/dialogs/_Analytics.scss";
|
@import "./views/dialogs/_Analytics.scss";
|
||||||
@import "./views/dialogs/_BugReportDialog.scss";
|
@import "./views/dialogs/_BugReportDialog.scss";
|
||||||
@import "./views/dialogs/_ChangelogDialog.scss";
|
@import "./views/dialogs/_ChangelogDialog.scss";
|
||||||
@import "./views/dialogs/_ChatCreateOrReuseChatDialog.scss";
|
@import "./views/dialogs/_ChatCreateOrReuseChatDialog.scss";
|
||||||
|
@import "./views/dialogs/_CommunityPrototypeInviteDialog.scss";
|
||||||
@import "./views/dialogs/_ConfirmUserActionDialog.scss";
|
@import "./views/dialogs/_ConfirmUserActionDialog.scss";
|
||||||
|
@import "./views/dialogs/_CreateCommunityPrototypeDialog.scss";
|
||||||
@import "./views/dialogs/_CreateGroupDialog.scss";
|
@import "./views/dialogs/_CreateGroupDialog.scss";
|
||||||
@import "./views/dialogs/_CreateRoomDialog.scss";
|
@import "./views/dialogs/_CreateRoomDialog.scss";
|
||||||
@import "./views/dialogs/_DeactivateAccountDialog.scss";
|
@import "./views/dialogs/_DeactivateAccountDialog.scss";
|
||||||
@import "./views/dialogs/_DevtoolsDialog.scss";
|
@import "./views/dialogs/_DevtoolsDialog.scss";
|
||||||
|
@import "./views/dialogs/_EditCommunityPrototypeDialog.scss";
|
||||||
@import "./views/dialogs/_GroupAddressPicker.scss";
|
@import "./views/dialogs/_GroupAddressPicker.scss";
|
||||||
@import "./views/dialogs/_IncomingSasDialog.scss";
|
@import "./views/dialogs/_IncomingSasDialog.scss";
|
||||||
@import "./views/dialogs/_InviteDialog.scss";
|
@import "./views/dialogs/_InviteDialog.scss";
|
||||||
|
@ -90,11 +91,12 @@
|
||||||
@import "./views/dialogs/_UploadConfirmDialog.scss";
|
@import "./views/dialogs/_UploadConfirmDialog.scss";
|
||||||
@import "./views/dialogs/_UserSettingsDialog.scss";
|
@import "./views/dialogs/_UserSettingsDialog.scss";
|
||||||
@import "./views/dialogs/_WidgetOpenIDPermissionsDialog.scss";
|
@import "./views/dialogs/_WidgetOpenIDPermissionsDialog.scss";
|
||||||
@import "./views/dialogs/keybackup/_CreateKeyBackupDialog.scss";
|
@import "./views/dialogs/security/_AccessSecretStorageDialog.scss";
|
||||||
@import "./views/dialogs/keybackup/_KeyBackupFailedDialog.scss";
|
@import "./views/dialogs/security/_CreateCrossSigningDialog.scss";
|
||||||
@import "./views/dialogs/keybackup/_RestoreKeyBackupDialog.scss";
|
@import "./views/dialogs/security/_CreateKeyBackupDialog.scss";
|
||||||
@import "./views/dialogs/secretstorage/_AccessSecretStorageDialog.scss";
|
@import "./views/dialogs/security/_CreateSecretStorageDialog.scss";
|
||||||
@import "./views/dialogs/secretstorage/_CreateSecretStorageDialog.scss";
|
@import "./views/dialogs/security/_KeyBackupFailedDialog.scss";
|
||||||
|
@import "./views/dialogs/security/_RestoreKeyBackupDialog.scss";
|
||||||
@import "./views/directory/_NetworkDropdown.scss";
|
@import "./views/directory/_NetworkDropdown.scss";
|
||||||
@import "./views/elements/_AccessibleButton.scss";
|
@import "./views/elements/_AccessibleButton.scss";
|
||||||
@import "./views/elements/_AddressSelector.scss";
|
@import "./views/elements/_AddressSelector.scss";
|
||||||
|
@ -108,6 +110,7 @@
|
||||||
@import "./views/elements/_FormButton.scss";
|
@import "./views/elements/_FormButton.scss";
|
||||||
@import "./views/elements/_IconButton.scss";
|
@import "./views/elements/_IconButton.scss";
|
||||||
@import "./views/elements/_ImageView.scss";
|
@import "./views/elements/_ImageView.scss";
|
||||||
|
@import "./views/elements/_InfoTooltip.scss";
|
||||||
@import "./views/elements/_InlineSpinner.scss";
|
@import "./views/elements/_InlineSpinner.scss";
|
||||||
@import "./views/elements/_ManageIntegsButton.scss";
|
@import "./views/elements/_ManageIntegsButton.scss";
|
||||||
@import "./views/elements/_PowerSelector.scss";
|
@import "./views/elements/_PowerSelector.scss";
|
||||||
|
@ -153,11 +156,13 @@
|
||||||
@import "./views/messages/_UnknownBody.scss";
|
@import "./views/messages/_UnknownBody.scss";
|
||||||
@import "./views/messages/_ViewSourceEvent.scss";
|
@import "./views/messages/_ViewSourceEvent.scss";
|
||||||
@import "./views/messages/_common_CryptoEvent.scss";
|
@import "./views/messages/_common_CryptoEvent.scss";
|
||||||
|
@import "./views/right_panel/_BaseCard.scss";
|
||||||
@import "./views/right_panel/_EncryptionInfo.scss";
|
@import "./views/right_panel/_EncryptionInfo.scss";
|
||||||
|
@import "./views/right_panel/_RoomSummaryCard.scss";
|
||||||
@import "./views/right_panel/_UserInfo.scss";
|
@import "./views/right_panel/_UserInfo.scss";
|
||||||
@import "./views/right_panel/_VerificationPanel.scss";
|
@import "./views/right_panel/_VerificationPanel.scss";
|
||||||
|
@import "./views/right_panel/_WidgetCard.scss";
|
||||||
@import "./views/room_settings/_AliasSettings.scss";
|
@import "./views/room_settings/_AliasSettings.scss";
|
||||||
@import "./views/room_settings/_ColorSettings.scss";
|
|
||||||
@import "./views/rooms/_AppsDrawer.scss";
|
@import "./views/rooms/_AppsDrawer.scss";
|
||||||
@import "./views/rooms/_Autocomplete.scss";
|
@import "./views/rooms/_Autocomplete.scss";
|
||||||
@import "./views/rooms/_AuxPanel.scss";
|
@import "./views/rooms/_AuxPanel.scss";
|
||||||
|
@ -183,7 +188,6 @@
|
||||||
@import "./views/rooms/_RoomHeader.scss";
|
@import "./views/rooms/_RoomHeader.scss";
|
||||||
@import "./views/rooms/_RoomList.scss";
|
@import "./views/rooms/_RoomList.scss";
|
||||||
@import "./views/rooms/_RoomPreviewBar.scss";
|
@import "./views/rooms/_RoomPreviewBar.scss";
|
||||||
@import "./views/rooms/_RoomRecoveryReminder.scss";
|
|
||||||
@import "./views/rooms/_RoomSublist.scss";
|
@import "./views/rooms/_RoomSublist.scss";
|
||||||
@import "./views/rooms/_RoomTile.scss";
|
@import "./views/rooms/_RoomTile.scss";
|
||||||
@import "./views/rooms/_RoomUpgradeWarningBar.scss";
|
@import "./views/rooms/_RoomUpgradeWarningBar.scss";
|
||||||
|
@ -198,10 +202,10 @@
|
||||||
@import "./views/settings/_E2eAdvancedPanel.scss";
|
@import "./views/settings/_E2eAdvancedPanel.scss";
|
||||||
@import "./views/settings/_EmailAddresses.scss";
|
@import "./views/settings/_EmailAddresses.scss";
|
||||||
@import "./views/settings/_IntegrationManager.scss";
|
@import "./views/settings/_IntegrationManager.scss";
|
||||||
@import "./views/settings/_KeyBackupPanel.scss";
|
|
||||||
@import "./views/settings/_Notifications.scss";
|
@import "./views/settings/_Notifications.scss";
|
||||||
@import "./views/settings/_PhoneNumbers.scss";
|
@import "./views/settings/_PhoneNumbers.scss";
|
||||||
@import "./views/settings/_ProfileSettings.scss";
|
@import "./views/settings/_ProfileSettings.scss";
|
||||||
|
@import "./views/settings/_SecureBackupPanel.scss";
|
||||||
@import "./views/settings/_SetIdServer.scss";
|
@import "./views/settings/_SetIdServer.scss";
|
||||||
@import "./views/settings/_SetIntegrationManager.scss";
|
@import "./views/settings/_SetIntegrationManager.scss";
|
||||||
@import "./views/settings/_UpdateCheckButton.scss";
|
@import "./views/settings/_UpdateCheckButton.scss";
|
||||||
|
|
|
@ -23,6 +23,13 @@ limitations under the License.
|
||||||
|
|
||||||
.mx_FilePanel .mx_RoomView_messageListWrapper {
|
.mx_FilePanel .mx_RoomView_messageListWrapper {
|
||||||
margin-right: 20px;
|
margin-right: 20px;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_FilePanel .mx_RoomView_MessageList {
|
||||||
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_FilePanel .mx_RoomView_MessageList h2 {
|
.mx_FilePanel .mx_RoomView_MessageList h2 {
|
||||||
|
|
|
@ -18,6 +18,14 @@ limitations under the License.
|
||||||
display: flex;
|
display: flex;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mx_RoomHeader_buttons + .mx_HeaderButtons {
|
||||||
|
// remove the | separator line for when next to RoomHeaderButtons
|
||||||
|
// TODO: remove this once when we redo communities and make the right panel similar to the new rooms one
|
||||||
|
&::before {
|
||||||
|
content: unset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.mx_HeaderButtons::before {
|
.mx_HeaderButtons::before {
|
||||||
content: "";
|
content: "";
|
||||||
background-color: $header-divider-color;
|
background-color: $header-divider-color;
|
||||||
|
|
|
@ -25,6 +25,7 @@ limitations under the License.
|
||||||
padding: 5px;
|
padding: 5px;
|
||||||
// margin left to not allow the handle to not encroach on the space for the scrollbar
|
// margin left to not allow the handle to not encroach on the space for the scrollbar
|
||||||
margin-left: 8px;
|
margin-left: 8px;
|
||||||
|
height: calc(100vh - 51px); // height of .mx_RoomHeader.light-panel
|
||||||
|
|
||||||
&:hover .mx_RightPanel_ResizeHandle {
|
&:hover .mx_RightPanel_ResizeHandle {
|
||||||
// Need to use important to override element style attributes
|
// Need to use important to override element style attributes
|
||||||
|
|
|
@ -22,7 +22,13 @@ limitations under the License.
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_NotificationPanel .mx_RoomView_messageListWrapper {
|
.mx_NotificationPanel .mx_RoomView_messageListWrapper {
|
||||||
margin-right: 20px;
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_NotificationPanel .mx_RoomView_MessageList {
|
||||||
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_NotificationPanel .mx_RoomView_MessageList h2 {
|
.mx_NotificationPanel .mx_RoomView_MessageList h2 {
|
||||||
|
@ -35,11 +41,32 @@ limitations under the License.
|
||||||
|
|
||||||
.mx_NotificationPanel .mx_EventTile {
|
.mx_NotificationPanel .mx_EventTile {
|
||||||
word-break: break-word;
|
word-break: break-word;
|
||||||
|
position: relative;
|
||||||
|
padding-bottom: 18px;
|
||||||
|
|
||||||
|
&:not(.mx_EventTile_last):not(.mx_EventTile_lastInSection)::after {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
background-color: $tertiary-fg-color;
|
||||||
|
height: 1px;
|
||||||
|
opacity: 0.4;
|
||||||
|
content: '';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_NotificationPanel .mx_EventTile_roomName {
|
.mx_NotificationPanel .mx_EventTile_roomName {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
font-size: $font-14px;
|
font-size: $font-14px;
|
||||||
|
|
||||||
|
> * {
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
> .mx_BaseAvatar {
|
||||||
|
margin-right: 8px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_NotificationPanel .mx_EventTile_roomName a {
|
.mx_NotificationPanel .mx_EventTile_roomName a {
|
||||||
|
@ -47,8 +74,7 @@ limitations under the License.
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_NotificationPanel .mx_EventTile_avatar {
|
.mx_NotificationPanel .mx_EventTile_avatar {
|
||||||
top: 8px;
|
display: none; // we don't need this in this view
|
||||||
left: 0px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_NotificationPanel .mx_EventTile .mx_SenderProfile,
|
.mx_NotificationPanel .mx_EventTile .mx_SenderProfile,
|
||||||
|
@ -60,8 +86,7 @@ limitations under the License.
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_NotificationPanel .mx_EventTile_senderDetails {
|
.mx_NotificationPanel .mx_EventTile_senderDetails {
|
||||||
padding-left: 32px;
|
padding-left: 36px; // align with the room name
|
||||||
padding-top: 8px;
|
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
a {
|
a {
|
||||||
|
@ -82,7 +107,7 @@ limitations under the License.
|
||||||
|
|
||||||
.mx_NotificationPanel .mx_EventTile_line {
|
.mx_NotificationPanel .mx_EventTile_line {
|
||||||
margin-right: 0px;
|
margin-right: 0px;
|
||||||
padding-left: 32px;
|
padding-left: 36px; // align with the room name
|
||||||
padding-top: 0px;
|
padding-top: 0px;
|
||||||
padding-bottom: 0px;
|
padding-bottom: 0px;
|
||||||
padding-right: 0px;
|
padding-right: 0px;
|
||||||
|
|
|
@ -68,16 +68,14 @@ limitations under the License.
|
||||||
mask-repeat: no-repeat;
|
mask-repeat: no-repeat;
|
||||||
mask-size: contain;
|
mask-size: contain;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
.mx_RightPanel_membersButton::before {
|
&:hover {
|
||||||
mask-image: url('$(res)/img/element-icons/room/members.svg');
|
background: rgba($accent-color, 0.1);
|
||||||
mask-position: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_RightPanel_filesButton::before {
|
&::before {
|
||||||
mask-image: url('$(res)/img/element-icons/room/files.svg');
|
background-color: $accent-color;
|
||||||
mask-position: center;
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_RightPanel_notifsButton::before {
|
.mx_RightPanel_notifsButton::before {
|
||||||
|
@ -85,6 +83,11 @@ limitations under the License.
|
||||||
mask-position: center;
|
mask-position: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mx_RightPanel_roomSummaryButton::before {
|
||||||
|
mask-image: url('$(res)/img/element-icons/room/room-summary.svg');
|
||||||
|
mask-position: center;
|
||||||
|
}
|
||||||
|
|
||||||
.mx_RightPanel_groupMembersButton::before {
|
.mx_RightPanel_groupMembersButton::before {
|
||||||
mask-image: url('$(res)/img/element-icons/community-members.svg');
|
mask-image: url('$(res)/img/element-icons/community-members.svg');
|
||||||
mask-position: center;
|
mask-position: center;
|
||||||
|
@ -96,23 +99,11 @@ limitations under the License.
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_RightPanel_headerButton_highlight {
|
.mx_RightPanel_headerButton_highlight {
|
||||||
background: rgba($accent-color, 0.25);
|
|
||||||
// make the icon the accent color too
|
|
||||||
&::before {
|
&::before {
|
||||||
background-color: $accent-color !important;
|
background-color: $accent-color !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_RightPanel_headerButton:not(.mx_RightPanel_headerButton_highlight) {
|
|
||||||
&:hover {
|
|
||||||
background: rgba($accent-color, 0.1);
|
|
||||||
|
|
||||||
&::before {
|
|
||||||
background-color: $accent-color;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_RightPanel_headerButton_badge {
|
.mx_RightPanel_headerButton_badge {
|
||||||
font-size: $font-8px;
|
font-size: $font-8px;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
|
@ -146,7 +137,7 @@ limitations under the License.
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_RightPanel_empty {
|
.mx_RightPanel_empty {
|
||||||
margin-right: -42px;
|
margin-right: -28px;
|
||||||
|
|
||||||
h2 {
|
h2 {
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
|
|
|
@ -185,13 +185,11 @@ limitations under the License.
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_RoomView_empty {
|
.mx_RoomView_empty {
|
||||||
flex: 1 1 auto;
|
|
||||||
font-size: $font-13px;
|
font-size: $font-13px;
|
||||||
padding-left: 3em;
|
padding: 0 24px;
|
||||||
padding-right: 3em;
|
margin-right: 30px;
|
||||||
margin-right: 20px;
|
|
||||||
margin-top: 33%;
|
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
margin-bottom: 80px; // visually center the content (intentional offset)
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_RoomView_MessageList {
|
.mx_RoomView_MessageList {
|
||||||
|
|
|
@ -30,30 +30,11 @@ limitations under the License.
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_TagPanel .mx_TagPanel_clearButton_container {
|
|
||||||
/* Constant height within flex mx_TagPanel */
|
|
||||||
height: 70px;
|
|
||||||
width: 56px;
|
|
||||||
|
|
||||||
flex: none;
|
|
||||||
|
|
||||||
justify-content: center;
|
|
||||||
align-items: flex-start;
|
|
||||||
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_TagPanel .mx_TagPanel_clearButton object {
|
|
||||||
/* Same as .mx_SearchBox padding-top */
|
|
||||||
margin-top: 24px;
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_TagPanel .mx_TagPanel_divider {
|
.mx_TagPanel .mx_TagPanel_divider {
|
||||||
height: 0px;
|
height: 0px;
|
||||||
width: 34px;
|
width: 90%;
|
||||||
border-bottom: 1px solid $panel-divider-color;
|
border: none;
|
||||||
display: none;
|
border-bottom: 1px solid $tagpanel-divider-color;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_TagPanel .mx_TagPanel_scroller {
|
.mx_TagPanel .mx_TagPanel_scroller {
|
||||||
|
@ -76,12 +57,57 @@ limitations under the License.
|
||||||
// opacity: 0.5;
|
// opacity: 0.5;
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mx_TagPanel .mx_TagTile.mx_TagTile_prototype {
|
||||||
|
padding: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
.mx_TagPanel .mx_TagTile:focus,
|
.mx_TagPanel .mx_TagTile:focus,
|
||||||
.mx_TagPanel .mx_TagTile:hover,
|
.mx_TagPanel .mx_TagTile:hover,
|
||||||
.mx_TagPanel .mx_TagTile.mx_TagTile_selected {
|
.mx_TagPanel .mx_TagTile.mx_TagTile_selected {
|
||||||
// opacity: 1;
|
// opacity: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mx_TagPanel .mx_TagTile.mx_TagTile_selected_prototype {
|
||||||
|
background-color: $primary-bg-color;
|
||||||
|
border-radius: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_TagTile_selected_prototype {
|
||||||
|
.mx_TagTile_homeIcon::before {
|
||||||
|
background-color: $primary-fg-color; // dark-on-light
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_TagTile:not(.mx_TagTile_selected_prototype) .mx_TagTile_homeIcon {
|
||||||
|
background-color: $roomheader-addroom-bg-color;
|
||||||
|
border-radius: 48px;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
background-color: $roomheader-addroom-fg-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_TagTile_homeIcon {
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
mask-image: url('$(res)/img/element-icons/home.svg');
|
||||||
|
mask-position: center;
|
||||||
|
mask-repeat: no-repeat;
|
||||||
|
mask-size: 21px;
|
||||||
|
content: '';
|
||||||
|
display: inline-block;
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
position: absolute;
|
||||||
|
top: calc(50% - 16px);
|
||||||
|
left: calc(50% - 16px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.mx_TagPanel .mx_TagTile_plus {
|
.mx_TagPanel .mx_TagTile_plus {
|
||||||
margin-bottom: 12px;
|
margin-bottom: 12px;
|
||||||
height: 32px;
|
height: 32px;
|
||||||
|
|
|
@ -80,6 +80,11 @@ limitations under the License.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.mx_Toast_icon_secure_backup::after {
|
||||||
|
mask-image: url('$(res)/img/feather-customised/secure-backup.svg');
|
||||||
|
background-color: $primary-fg-color;
|
||||||
|
}
|
||||||
|
|
||||||
.mx_Toast_title, .mx_Toast_body {
|
.mx_Toast_title, .mx_Toast_body {
|
||||||
grid-column: 2;
|
grid-column: 2;
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,10 +15,33 @@ limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
.mx_UserMenu {
|
.mx_UserMenu {
|
||||||
|
// to make the menu button sort of aligned with the explore button below
|
||||||
// to make the ... button sort of aligned with the explore button below
|
|
||||||
padding-right: 6px;
|
padding-right: 6px;
|
||||||
|
|
||||||
|
&.mx_UserMenu_prototype {
|
||||||
|
// The margin & padding combination between here and the ::after is to
|
||||||
|
// align the border line with the tag panel.
|
||||||
|
margin-bottom: 6px;
|
||||||
|
|
||||||
|
padding-right: 0; // make the right edge line up with the explore button
|
||||||
|
|
||||||
|
.mx_UserMenu_headerButtons {
|
||||||
|
// considering we've eliminated right padding on the menu itself, we need to
|
||||||
|
// push the chevron in slightly (roughly lining up with the center of the
|
||||||
|
// plus buttons)
|
||||||
|
margin-right: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
// we cheat opacity on the theme colour with an after selector here
|
||||||
|
&::after {
|
||||||
|
content: '';
|
||||||
|
border-bottom: 1px solid $primary-fg-color; // XXX: Variable abuse
|
||||||
|
opacity: 0.2;
|
||||||
|
display: block;
|
||||||
|
padding-top: 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.mx_UserMenu_headerButtons {
|
.mx_UserMenu_headerButtons {
|
||||||
width: 16px;
|
width: 16px;
|
||||||
height: 16px;
|
height: 16px;
|
||||||
|
@ -35,8 +58,8 @@ limitations under the License.
|
||||||
mask-position: center;
|
mask-position: center;
|
||||||
mask-size: contain;
|
mask-size: contain;
|
||||||
mask-repeat: no-repeat;
|
mask-repeat: no-repeat;
|
||||||
background: $primary-fg-color;
|
background: $tertiary-fg-color;
|
||||||
mask-image: url('$(res)/img/element-icons/context-menu.svg');
|
mask-image: url('$(res)/img/feather-customised/chevron-down.svg');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -56,6 +79,28 @@ limitations under the License.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mx_UserMenu_doubleName {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0; // make flexbox aware that it can crush this to a tiny width
|
||||||
|
|
||||||
|
.mx_UserMenu_userName,
|
||||||
|
.mx_UserMenu_subUserName {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_UserMenu_subUserName {
|
||||||
|
color: $muted-fg-color;
|
||||||
|
font-size: $font-13px;
|
||||||
|
line-height: $font-18px;
|
||||||
|
flex: 1;
|
||||||
|
|
||||||
|
// Ellipsize any text overflow
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
overflow: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.mx_UserMenu_userName {
|
.mx_UserMenu_userName {
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
font-size: $font-15px;
|
font-size: $font-15px;
|
||||||
|
@ -89,6 +134,44 @@ limitations under the License.
|
||||||
.mx_UserMenu_contextMenu {
|
.mx_UserMenu_contextMenu {
|
||||||
width: 247px;
|
width: 247px;
|
||||||
|
|
||||||
|
// These override the styles already present on the user menu rather than try to
|
||||||
|
// define a new menu. They are specifically for the stacked menu when a community
|
||||||
|
// is being represented as a prototype.
|
||||||
|
&.mx_UserMenu_contextMenu_prototype {
|
||||||
|
padding-bottom: 16px;
|
||||||
|
|
||||||
|
.mx_UserMenu_contextMenu_header {
|
||||||
|
padding-bottom: 0;
|
||||||
|
padding-top: 16px;
|
||||||
|
|
||||||
|
&:nth-child(n + 2) {
|
||||||
|
padding-top: 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
hr {
|
||||||
|
width: 85%;
|
||||||
|
opacity: 0.2;
|
||||||
|
border: none;
|
||||||
|
border-bottom: 1px solid $primary-fg-color; // XXX: Variable abuse
|
||||||
|
}
|
||||||
|
|
||||||
|
&.mx_IconizedContextMenu {
|
||||||
|
> .mx_IconizedContextMenu_optionList {
|
||||||
|
margin-top: 4px;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
> .mx_AccessibleButton {
|
||||||
|
padding-top: 2px;
|
||||||
|
padding-bottom: 2px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
&.mx_IconizedContextMenu .mx_IconizedContextMenu_optionList_red {
|
&.mx_IconizedContextMenu .mx_IconizedContextMenu_optionList_red {
|
||||||
.mx_AccessibleButton {
|
.mx_AccessibleButton {
|
||||||
padding-top: 16px;
|
padding-top: 16px;
|
||||||
|
@ -193,4 +276,12 @@ limitations under the License.
|
||||||
.mx_UserMenu_iconSignOut::before {
|
.mx_UserMenu_iconSignOut::before {
|
||||||
mask-image: url('$(res)/img/element-icons/leave.svg');
|
mask-image: url('$(res)/img/element-icons/leave.svg');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mx_UserMenu_iconMembers::before {
|
||||||
|
mask-image: url('$(res)/img/element-icons/room/members.svg');
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_UserMenu_iconInvite::before {
|
||||||
|
mask-image: url('$(res)/img/element-icons/room/invite.svg');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,6 +18,12 @@ limitations under the License.
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
||||||
|
&.mx_WelcomePage_registrationDisabled {
|
||||||
|
.mx_ButtonCreateAccount {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_Welcome .mx_AuthBody_language {
|
.mx_Welcome .mx_AuthBody_language {
|
||||||
|
|
|
@ -82,7 +82,6 @@ limitations under the License.
|
||||||
}
|
}
|
||||||
|
|
||||||
span.mx_IconizedContextMenu_label { // labels
|
span.mx_IconizedContextMenu_label { // labels
|
||||||
padding-left: 14px;
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
|
|
||||||
|
@ -91,6 +90,10 @@ limitations under the License.
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mx_IconizedContextMenu_icon + .mx_IconizedContextMenu_label {
|
||||||
|
padding-left: 14px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,114 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright 2015, 2016 OpenMarket Ltd
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
.mx_RoomTileContextMenu {
|
|
||||||
padding: 6px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_RoomTileContextMenu_tag_icon {
|
|
||||||
padding-right: 8px;
|
|
||||||
padding-left: 4px;
|
|
||||||
display: inline-block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_RoomTileContextMenu_tag_icon_set {
|
|
||||||
padding-right: 8px;
|
|
||||||
padding-left: 4px;
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_RoomTileContextMenu_tag_field, .mx_RoomTileContextMenu_leave {
|
|
||||||
padding-top: 8px;
|
|
||||||
padding-right: 20px;
|
|
||||||
padding-bottom: 8px;
|
|
||||||
cursor: pointer;
|
|
||||||
white-space: nowrap;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
line-height: $font-16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_RoomTileContextMenu_tag_field.mx_RoomTileContextMenu_tag_fieldSet {
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_RoomTileContextMenu_tag_field.mx_RoomTileContextMenu_tag_fieldSet .mx_RoomTileContextMenu_tag_icon {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_RoomTileContextMenu_tag_field.mx_RoomTileContextMenu_tag_fieldSet .mx_RoomTileContextMenu_tag_icon_set {
|
|
||||||
display: inline-block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_RoomTileContextMenu_tag_field.mx_RoomTileContextMenu_tag_fieldDisabled {
|
|
||||||
color: rgba(0, 0, 0, 0.2);
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_RoomTileContextMenu_separator {
|
|
||||||
margin-top: 0;
|
|
||||||
margin-bottom: 0;
|
|
||||||
border-bottom-style: none;
|
|
||||||
border-left-style: none;
|
|
||||||
border-right-style: none;
|
|
||||||
border-top-style: solid;
|
|
||||||
border-top-width: 1px;
|
|
||||||
border-color: $menu-border-color;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_RoomTileContextMenu_leave {
|
|
||||||
color: $warning-color;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_RoomTileContextMenu_notif_picker {
|
|
||||||
position: absolute;
|
|
||||||
top: 16px;
|
|
||||||
left: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_RoomTileContextMenu_notif_field {
|
|
||||||
padding-top: 4px;
|
|
||||||
padding-right: 6px;
|
|
||||||
padding-bottom: 10px;
|
|
||||||
padding-left: 8px; /* 20px */
|
|
||||||
cursor: pointer;
|
|
||||||
white-space: nowrap;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_RoomTileContextMenu_notif_field.mx_RoomTileContextMenu_notif_fieldSet {
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_RoomTileContextMenu_notif_field.mx_RoomTileContextMenu_notif_fieldDisabled {
|
|
||||||
color: rgba(0, 0, 0, 0.2);
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_RoomTileContextMenu_notif_icon {
|
|
||||||
padding-right: 4px;
|
|
||||||
padding-left: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_RoomTileContextMenu_notif_activeIcon {
|
|
||||||
display: inline-block;
|
|
||||||
opacity: 0;
|
|
||||||
position: relative;
|
|
||||||
left: -5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_RoomTileContextMenu_notif_fieldSet .mx_RoomTileContextMenu_notif_activeIcon {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
|
@ -1,96 +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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
.mx_TopLeftMenu {
|
|
||||||
min-width: 210px;
|
|
||||||
border-radius: 4px;
|
|
||||||
|
|
||||||
.mx_TopLeftMenu_greyedText {
|
|
||||||
font-size: $font-12px;
|
|
||||||
opacity: 0.5;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_TopLeftMenu_upgradeLink {
|
|
||||||
font-size: $font-12px;
|
|
||||||
|
|
||||||
img {
|
|
||||||
margin-left: 5px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_TopLeftMenu_section:not(:last-child) {
|
|
||||||
border-bottom: 1px solid $menu-border-color;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_TopLeftMenu_section_noIcon {
|
|
||||||
margin: 5px 0;
|
|
||||||
padding: 5px 20px 5px 15px;
|
|
||||||
|
|
||||||
div:not(:first-child) {
|
|
||||||
margin-top: 5px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_TopLeftMenu_section_withIcon {
|
|
||||||
margin: 5px 0;
|
|
||||||
padding: 0;
|
|
||||||
list-style: none;
|
|
||||||
|
|
||||||
.mx_TopLeftMenu_icon_home::after {
|
|
||||||
mask-image: url('$(res)/img/feather-customised/home.svg');
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_TopLeftMenu_icon_help::after {
|
|
||||||
mask-image: url('$(res)/img/feather-customised/life-buoy.svg');
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_TopLeftMenu_icon_settings::after {
|
|
||||||
mask-image: url('$(res)/img/feather-customised/settings.svg');
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_TopLeftMenu_icon_signin::after {
|
|
||||||
mask-image: url('$(res)/img/feather-customised/sign-in.svg');
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_TopLeftMenu_icon_signout::after {
|
|
||||||
mask-image: url('$(res)/img/feather-customised/sign-out.svg');
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_AccessibleButton::after {
|
|
||||||
mask-repeat: no-repeat;
|
|
||||||
mask-position: 0 center;
|
|
||||||
mask-size: $font-16px;
|
|
||||||
position: absolute;
|
|
||||||
width: $font-16px;
|
|
||||||
height: $font-16px;
|
|
||||||
content: "";
|
|
||||||
top: 5px;
|
|
||||||
left: 14px;
|
|
||||||
background-color: $primary-fg-color;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_AccessibleButton {
|
|
||||||
position: relative;
|
|
||||||
cursor: pointer;
|
|
||||||
white-space: nowrap;
|
|
||||||
padding: 5px 20px 5px 43px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_AccessibleButton:hover {
|
|
||||||
background-color: $menu-selected-color;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
88
res/css/views/dialogs/_CommunityPrototypeInviteDialog.scss
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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
.mx_CommunityPrototypeInviteDialog {
|
||||||
|
&.mx_Dialog_fixedWidth {
|
||||||
|
width: 360px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_Dialog_content {
|
||||||
|
margin-bottom: 0;
|
||||||
|
|
||||||
|
.mx_CommunityPrototypeInviteDialog_people {
|
||||||
|
position: relative;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
|
||||||
|
.mx_AccessibleButton {
|
||||||
|
display: inline-block;
|
||||||
|
background-color: $focus-bg-color; // XXX: Abuse of variables
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 3px 5px;
|
||||||
|
font-size: $font-12px;
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_CommunityPrototypeInviteDialog_morePeople {
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_CommunityPrototypeInviteDialog_person {
|
||||||
|
position: relative;
|
||||||
|
margin-top: 4px;
|
||||||
|
|
||||||
|
& > * {
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_Checkbox {
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
top: calc(50% - 8px); // checkbox is 16px high
|
||||||
|
width: 16px; // to force a square
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_CommunityPrototypeInviteDialog_personIdentifiers {
|
||||||
|
display: inline-block;
|
||||||
|
|
||||||
|
& > * {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_CommunityPrototypeInviteDialog_personName {
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: $font-14px;
|
||||||
|
color: $primary-fg-color;
|
||||||
|
margin-left: 7px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_CommunityPrototypeInviteDialog_personId {
|
||||||
|
font-size: $font-12px;
|
||||||
|
color: $muted-fg-color;
|
||||||
|
margin-left: 7px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_CommunityPrototypeInviteDialog_primaryButton {
|
||||||
|
display: block;
|
||||||
|
font-size: $font-13px;
|
||||||
|
line-height: 20px;
|
||||||
|
height: 20px;
|
||||||
|
margin-top: 24px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
102
res/css/views/dialogs/_CreateCommunityPrototypeDialog.scss
Normal file
|
@ -0,0 +1,102 @@
|
||||||
|
/*
|
||||||
|
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_CreateCommunityPrototypeDialog {
|
||||||
|
.mx_Dialog_content {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
|
||||||
|
.mx_CreateCommunityPrototypeDialog_colName {
|
||||||
|
flex-basis: 66.66%;
|
||||||
|
padding-right: 100px;
|
||||||
|
|
||||||
|
.mx_Field input {
|
||||||
|
font-size: $font-16px;
|
||||||
|
line-height: $font-20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_CreateCommunityPrototypeDialog_subtext {
|
||||||
|
display: block;
|
||||||
|
color: $muted-fg-color;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
margin-top: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.mx_CreateCommunityPrototypeDialog_subtext_error {
|
||||||
|
color: $warning-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_CreateCommunityPrototypeDialog_communityId {
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
.mx_InfoTooltip {
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_AccessibleButton {
|
||||||
|
display: block;
|
||||||
|
height: 32px;
|
||||||
|
font-size: $font-16px;
|
||||||
|
line-height: 32px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_CreateCommunityPrototypeDialog_colAvatar {
|
||||||
|
flex-basis: 33.33%;
|
||||||
|
|
||||||
|
.mx_CreateCommunityPrototypeDialog_avatarContainer {
|
||||||
|
margin-top: 12px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
|
||||||
|
.mx_CreateCommunityPrototypeDialog_avatar,
|
||||||
|
.mx_CreateCommunityPrototypeDialog_placeholderAvatar {
|
||||||
|
width: 96px;
|
||||||
|
height: 96px;
|
||||||
|
border-radius: 96px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_CreateCommunityPrototypeDialog_placeholderAvatar {
|
||||||
|
background-color: #368bd6; // hardcoded for both themes
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
display: inline-block;
|
||||||
|
background-color: #fff; // hardcoded because the background is
|
||||||
|
mask-repeat: no-repeat;
|
||||||
|
mask-size: 96px;
|
||||||
|
width: 96px;
|
||||||
|
height: 96px;
|
||||||
|
mask-position: center;
|
||||||
|
content: '';
|
||||||
|
vertical-align: middle;
|
||||||
|
mask-image: url('$(res)/img/element-icons/add-photo.svg');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_CreateCommunityPrototypeDialog_tip {
|
||||||
|
& > b, & > span {
|
||||||
|
display: block;
|
||||||
|
color: $muted-fg-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
77
res/css/views/dialogs/_EditCommunityPrototypeDialog.scss
Normal file
|
@ -0,0 +1,77 @@
|
||||||
|
/*
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// XXX: many of these styles are shared with the create dialog
|
||||||
|
.mx_EditCommunityPrototypeDialog {
|
||||||
|
&.mx_Dialog_fixedWidth {
|
||||||
|
width: 360px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_Dialog_content {
|
||||||
|
margin-bottom: 12px;
|
||||||
|
|
||||||
|
.mx_AccessibleButton.mx_AccessibleButton_kind_primary {
|
||||||
|
display: block;
|
||||||
|
height: 32px;
|
||||||
|
font-size: $font-16px;
|
||||||
|
line-height: 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_EditCommunityPrototypeDialog_rowAvatar {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_EditCommunityPrototypeDialog_avatarContainer {
|
||||||
|
margin-top: 20px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
|
||||||
|
.mx_EditCommunityPrototypeDialog_avatar,
|
||||||
|
.mx_EditCommunityPrototypeDialog_placeholderAvatar {
|
||||||
|
width: 96px;
|
||||||
|
height: 96px;
|
||||||
|
border-radius: 96px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_EditCommunityPrototypeDialog_placeholderAvatar {
|
||||||
|
background-color: #368bd6; // hardcoded for both themes
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
display: inline-block;
|
||||||
|
background-color: #fff; // hardcoded because the background is
|
||||||
|
mask-repeat: no-repeat;
|
||||||
|
mask-size: 96px;
|
||||||
|
width: 96px;
|
||||||
|
height: 96px;
|
||||||
|
mask-position: center;
|
||||||
|
content: '';
|
||||||
|
vertical-align: middle;
|
||||||
|
mask-image: url('$(res)/img/element-icons/add-photo.svg');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_EditCommunityPrototypeDialog_tip {
|
||||||
|
margin-left: 20px;
|
||||||
|
|
||||||
|
& > b, & > span {
|
||||||
|
display: block;
|
||||||
|
color: $muted-fg-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -89,6 +89,13 @@ limitations under the License.
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mx_InviteDialog_subname {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
margin-top: -10px; // HACK: Positioning with margins is bad
|
||||||
|
font-size: $font-12px;
|
||||||
|
color: $muted-fg-color;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_InviteDialog_roomTile {
|
.mx_InviteDialog_roomTile {
|
||||||
|
@ -226,3 +233,7 @@ limitations under the License.
|
||||||
.mx_InviteDialog_addressBar {
|
.mx_InviteDialog_addressBar {
|
||||||
margin-right: 45px;
|
margin-right: 45px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mx_InviteDialog_helpText .mx_AccessibleButton_kind_link {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
|
@ -71,9 +71,12 @@ limitations under the License.
|
||||||
margin-right: 64px;
|
margin-right: 64px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mx_ShareDialog_qrcode_container + .mx_ShareDialog_social_container {
|
||||||
|
width: 299px;
|
||||||
|
}
|
||||||
|
|
||||||
.mx_ShareDialog_social_container {
|
.mx_ShareDialog_social_container {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
width: 299px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_ShareDialog_social_icon {
|
.mx_ShareDialog_social_icon {
|
||||||
|
|
|
@ -0,0 +1,33 @@
|
||||||
|
/*
|
||||||
|
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_CreateCrossSigningDialog {
|
||||||
|
// Why you ask? Because CompleteSecurityBody is 600px so this is the width
|
||||||
|
// we end up when in there, but when in our own dialog we set our own width
|
||||||
|
// so need to fix it to something sensible as otherwise we'd end up either
|
||||||
|
// really wide or really narrow depending on the phase. I bet you wish you
|
||||||
|
// never asked.
|
||||||
|
width: 560px;
|
||||||
|
|
||||||
|
details .mx_AccessibleButton {
|
||||||
|
margin: 1em 0; // emulate paragraph spacing because we can't put this button in a paragraph due to HTML rules
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_CreateCrossSigningDialog .mx_Dialog_title {
|
||||||
|
/* TODO: Consider setting this for all dialog titles. */
|
||||||
|
margin-bottom: 1em;
|
||||||
|
}
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
Copyright 2019 New Vector Ltd.
|
Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
|
@ -14,26 +14,21 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
.mx_ColorSettings_roomColor {
|
.mx_InfoTooltip_icon {
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
position: relative;
|
|
||||||
width: 37px;
|
|
||||||
height: 37px;
|
|
||||||
border: 1px solid #979797;
|
|
||||||
margin-right: 13px;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_ColorSettings_roomColor_selected {
|
.mx_InfoTooltip_icon::before {
|
||||||
position: absolute;
|
display: inline-block;
|
||||||
left: 10px;
|
background-color: $muted-fg-color;
|
||||||
top: 4px;
|
mask-repeat: no-repeat;
|
||||||
cursor: default !important;
|
mask-size: 16px;
|
||||||
}
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
.mx_ColorSettings_roomColorPrimary {
|
mask-position: center;
|
||||||
height: 10px;
|
content: '';
|
||||||
position: absolute;
|
vertical-align: middle;
|
||||||
bottom: 0px;
|
mask-image: url('$(res)/img/element-icons/info.svg');
|
||||||
width: 100%;
|
|
||||||
}
|
}
|
166
res/css/views/right_panel/_BaseCard.scss
Normal file
|
@ -0,0 +1,166 @@
|
||||||
|
/*
|
||||||
|
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_BaseCard {
|
||||||
|
padding: 0 8px;
|
||||||
|
overflow: hidden;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
flex: 1;
|
||||||
|
|
||||||
|
.mx_BaseCard_header {
|
||||||
|
margin: 8px 0;
|
||||||
|
|
||||||
|
> h2 {
|
||||||
|
margin: 0 44px;
|
||||||
|
font-size: $font-18px;
|
||||||
|
font-weight: $font-semi-bold;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_BaseCard_back, .mx_BaseCard_close {
|
||||||
|
position: absolute;
|
||||||
|
background-color: rgba(141, 151, 165, 0.2);
|
||||||
|
height: 20px;
|
||||||
|
width: 20px;
|
||||||
|
margin: 12px;
|
||||||
|
top: 0;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
height: 20px;
|
||||||
|
width: 20px;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
mask-repeat: no-repeat;
|
||||||
|
mask-position: center;
|
||||||
|
background-color: $rightpanel-button-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_BaseCard_back {
|
||||||
|
border-radius: 4px;
|
||||||
|
left: 0;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
transform: rotate(90deg);
|
||||||
|
mask-size: 22px;
|
||||||
|
mask-image: url('$(res)/img/feather-customised/chevron-down.svg');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_BaseCard_close {
|
||||||
|
border-radius: 10px;
|
||||||
|
right: 0;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
mask-image: url('$(res)/img/icons-close.svg');
|
||||||
|
mask-size: 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_AutoHideScrollbar {
|
||||||
|
// collapse the margin into a padding to move the scrollbar into the right gutter
|
||||||
|
margin-right: -8px;
|
||||||
|
padding-right: 8px;
|
||||||
|
min-height: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_BaseCard_Group {
|
||||||
|
margin: 20px 0 16px;
|
||||||
|
|
||||||
|
& > * {
|
||||||
|
margin-left: 12px;
|
||||||
|
margin-right: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
> h1 {
|
||||||
|
color: $tertiary-fg-color;
|
||||||
|
font-size: $font-12px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_BaseCard_Button {
|
||||||
|
padding: 10px 38px 10px 12px;
|
||||||
|
margin: 0;
|
||||||
|
position: relative;
|
||||||
|
font-size: $font-13px;
|
||||||
|
height: 20px;
|
||||||
|
line-height: 20px;
|
||||||
|
border-radius: 8px;
|
||||||
|
overflow: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: rgba(141, 151, 165, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 10px;
|
||||||
|
right: 6px;
|
||||||
|
height: 20px;
|
||||||
|
width: 20px;
|
||||||
|
mask-repeat: no-repeat;
|
||||||
|
mask-position: center;
|
||||||
|
background-color: $icon-button-color;
|
||||||
|
transform: rotate(270deg);
|
||||||
|
mask-size: 20px;
|
||||||
|
mask-image: url('$(res)/img/feather-customised/chevron-down.svg');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_BaseCard_footer {
|
||||||
|
padding-top: 4px;
|
||||||
|
text-align: center;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-around;
|
||||||
|
|
||||||
|
.mx_AccessibleButton_kind_secondary {
|
||||||
|
color: $secondary-fg-color;
|
||||||
|
background-color: rgba(141, 151, 165, 0.2);
|
||||||
|
font-weight: $font-semi-bold;
|
||||||
|
font-size: $font-14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_AccessibleButton_disabled {
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_FilePanel,
|
||||||
|
.mx_UserInfo,
|
||||||
|
.mx_NotificationPanel,
|
||||||
|
.mx_MemberList {
|
||||||
|
&.mx_BaseCard {
|
||||||
|
padding: 32px 0 0;
|
||||||
|
|
||||||
|
.mx_AutoHideScrollbar {
|
||||||
|
margin-right: unset;
|
||||||
|
padding-right: unset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
161
res/css/views/right_panel/_RoomSummaryCard.scss
Normal file
|
@ -0,0 +1,161 @@
|
||||||
|
/*
|
||||||
|
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_RoomSummaryCard {
|
||||||
|
.mx_BaseCard_header {
|
||||||
|
text-align: center;
|
||||||
|
margin-top: 20px;
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
font-weight: $font-semi-bold;
|
||||||
|
font-size: $font-18px;
|
||||||
|
margin: 12px 0 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_RoomSummaryCard_alias {
|
||||||
|
font-size: $font-13px;
|
||||||
|
color: $secondary-fg-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2, .mx_RoomSummaryCard_alias {
|
||||||
|
display: -webkit-box;
|
||||||
|
-webkit-line-clamp: 2;
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_RoomSummaryCard_avatar {
|
||||||
|
display: inline-flex;
|
||||||
|
|
||||||
|
.mx_RoomSummaryCard_e2ee {
|
||||||
|
display: inline-block;
|
||||||
|
position: relative;
|
||||||
|
width: 54px;
|
||||||
|
height: 54px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background-color: #737d8c;
|
||||||
|
margin-top: -3px; // alignment
|
||||||
|
margin-left: -10px; // overlap
|
||||||
|
border: 3px solid $dark-panel-bg-color;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 13px;
|
||||||
|
left: 13px;
|
||||||
|
height: 28px;
|
||||||
|
width: 28px;
|
||||||
|
mask-size: cover;
|
||||||
|
mask-repeat: no-repeat;
|
||||||
|
mask-position: center;
|
||||||
|
mask-image: url('$(res)/img/e2e/disabled.svg');
|
||||||
|
background-color: #ffffff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_RoomSummaryCard_e2ee_normal {
|
||||||
|
background-color: #424446;
|
||||||
|
&::before {
|
||||||
|
mask-image: url('$(res)/img/e2e/normal.svg');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_RoomSummaryCard_e2ee_verified {
|
||||||
|
background-color: #0dbd8b;
|
||||||
|
&::before {
|
||||||
|
mask-image: url('$(res)/img/e2e/verified.svg');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_RoomSummaryCard_e2ee_warning {
|
||||||
|
background-color: #ff4b55;
|
||||||
|
&::before {
|
||||||
|
mask-image: url('$(res)/img/e2e/warning.svg');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_RoomSummaryCard_aboutGroup {
|
||||||
|
.mx_RoomSummaryCard_Button {
|
||||||
|
padding-left: 44px;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 8px;
|
||||||
|
left: 10px;
|
||||||
|
height: 24px;
|
||||||
|
width: 24px;
|
||||||
|
mask-repeat: no-repeat;
|
||||||
|
mask-position: center;
|
||||||
|
background-color: $icon-button-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_RoomSummaryCard_appsGroup {
|
||||||
|
.mx_RoomSummaryCard_Button {
|
||||||
|
padding-left: 12px;
|
||||||
|
color: $tertiary-fg-color;
|
||||||
|
|
||||||
|
span {
|
||||||
|
color: $primary-fg-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
img {
|
||||||
|
vertical-align: top;
|
||||||
|
margin-right: 12px;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
content: unset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_RoomSummaryCard_icon_app_pinned::after {
|
||||||
|
mask-image: url('$(res)/img/element-icons/room/pin-upright.svg');
|
||||||
|
background-color: $accent-color;
|
||||||
|
transform: unset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_AccessibleButton_kind_link {
|
||||||
|
padding: 0;
|
||||||
|
margin-top: 12px;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
font-size: $font-13px;
|
||||||
|
font-weight: $font-semi-bold;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_RoomSummaryCard_icon_people::before {
|
||||||
|
mask-image: url("$(res)/img/element-icons/room/members.svg");
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_RoomSummaryCard_icon_files::before {
|
||||||
|
mask-image: url('$(res)/img/element-icons/room/files.svg');
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_RoomSummaryCard_icon_share::before {
|
||||||
|
mask-image: url('$(res)/img/element-icons/room/share.svg');
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_RoomSummaryCard_icon_settings::before {
|
||||||
|
mask-image: url('$(res)/img/element-icons/settings.svg');
|
||||||
|
}
|
|
@ -15,7 +15,9 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
.mx_UserInfo {
|
.mx_UserInfo.mx_BaseCard {
|
||||||
|
// UserInfo has a circular image at the top so it fits between the back & close buttons
|
||||||
|
padding-top: 0;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
|
@ -217,9 +219,8 @@ limitations under the License.
|
||||||
text-overflow: clip;
|
text-overflow: clip;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_UserInfo_scrollContainer {
|
.mx_AutoHideScrollbar {
|
||||||
flex: 1 1 0;
|
flex: 1 1 0;
|
||||||
padding-bottom: 16px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_UserInfo_container:not(.mx_UserInfo_separator) {
|
.mx_UserInfo_container:not(.mx_UserInfo_separator) {
|
||||||
|
|
62
res/css/views/right_panel/_WidgetCard.scss
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
/*
|
||||||
|
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_WidgetCard {
|
||||||
|
height: 100%;
|
||||||
|
display: contents;
|
||||||
|
|
||||||
|
.mx_AppTileFullWidth {
|
||||||
|
max-width: unset;
|
||||||
|
height: 100%;
|
||||||
|
border: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.mx_WidgetCard_noEdit {
|
||||||
|
.mx_AccessibleButton_kind_secondary {
|
||||||
|
margin: 0 12px;
|
||||||
|
|
||||||
|
&:first-child {
|
||||||
|
// expand the Pin to room primary action
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_WidgetCard_optionsButton {
|
||||||
|
position: relative;
|
||||||
|
height: 18px;
|
||||||
|
width: 26px;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
top: 6px;
|
||||||
|
left: 20px;
|
||||||
|
mask-repeat: no-repeat;
|
||||||
|
mask-position: center;
|
||||||
|
mask-size: contain;
|
||||||
|
mask-image: url('$(res)/img/element-icons/room/ellipsis.svg');
|
||||||
|
background-color: $secondary-fg-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_WidgetCard_maxPinnedTooltip {
|
||||||
|
background-color: $notice-primary-color;
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
|
@ -15,18 +15,39 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/*
|
$MiniAppTileHeight: 114px;
|
||||||
the tile title bar is 5 (top border) + 12 (title, buttons) + 5 (bottom padding) px = 22px
|
|
||||||
the body is assumed to be 300px (assumed by at least the sticker pickerm, perhaps elsewhere),
|
|
||||||
so the body height would be 300px - 22px (room for title bar) = 278px
|
|
||||||
BUT! the sticker picker also assumes it's a little less high than that because the iframe
|
|
||||||
for the sticker picker doesn't have any padding or margin on it's bottom.
|
|
||||||
so subtracking another 5px, which brings us at 273px.
|
|
||||||
*/
|
|
||||||
$AppsDrawerBodyHeight: 273px;
|
|
||||||
|
|
||||||
.mx_AppsDrawer {
|
.mx_AppsDrawer {
|
||||||
margin: 5px;
|
margin: 5px 5px 5px 18px;
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
.mx_AppsContainer_resizerHandle {
|
||||||
|
cursor: ns-resize;
|
||||||
|
border-radius: 3px;
|
||||||
|
|
||||||
|
// Override styles from library
|
||||||
|
width: unset !important;
|
||||||
|
height: 4px !important;
|
||||||
|
|
||||||
|
// This is positioned directly below frame
|
||||||
|
position: absolute;
|
||||||
|
bottom: -8px !important; // override from library
|
||||||
|
|
||||||
|
// Together, these make the bar 64px wide
|
||||||
|
// These are also overridden from the library
|
||||||
|
left: calc(50% - 32px) !important;
|
||||||
|
right: calc(50% - 32px) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
.mx_AppsContainer_resizerHandle {
|
||||||
|
opacity: 0.8;
|
||||||
|
background: $primary-fg-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_AppsDrawer_hidden {
|
.mx_AppsDrawer_hidden {
|
||||||
|
@ -36,15 +57,23 @@ $AppsDrawerBodyHeight: 273px;
|
||||||
.mx_AppsContainer {
|
.mx_AppsContainer {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
align-items: center;
|
align-items: stretch;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
height: 100%;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_AppsDrawer_minimised .mx_AppsContainer {
|
||||||
|
// override the re-resizable inline styles
|
||||||
|
height: inherit !important;
|
||||||
|
min-height: inherit !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_AddWidget_button {
|
.mx_AddWidget_button {
|
||||||
order: 2;
|
order: 2;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
margin: 5px auto 5px auto;
|
margin: -3px auto 5px 0;
|
||||||
color: $accent-color;
|
color: $accent-color;
|
||||||
font-size: $font-12px;
|
font-size: $font-12px;
|
||||||
}
|
}
|
||||||
|
@ -65,40 +94,52 @@ $AppsDrawerBodyHeight: 273px;
|
||||||
.mx_AppTile {
|
.mx_AppTile {
|
||||||
max-width: 960px;
|
max-width: 960px;
|
||||||
width: 50%;
|
width: 50%;
|
||||||
margin-right: 5px;
|
|
||||||
border: 5px solid $widget-menu-bar-bg-color;
|
border: 5px solid $widget-menu-bar-bg-color;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
}
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
.mx_AppTile:last-child {
|
& + .mx_AppTile {
|
||||||
margin-right: 1px;
|
margin-left: 5px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_AppTileFullWidth {
|
.mx_AppTileFullWidth {
|
||||||
max-width: 960px;
|
max-width: 960px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
border: 5px solid $widget-menu-bar-bg-color;
|
border: 5px solid $widget-menu-bar-bg-color;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_AppTile_mini {
|
.mx_AppTile_mini {
|
||||||
max-width: 960px;
|
max-width: 960px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
height: $MiniAppTileHeight;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_AppTile_persistedWrapper {
|
.mx_AppTile.mx_AppTile_minimised,
|
||||||
height: $AppsDrawerBodyHeight;
|
.mx_AppTileFullWidth.mx_AppTile_minimised,
|
||||||
|
.mx_AppTile_mini.mx_AppTile_minimised {
|
||||||
|
height: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mx_AppTile .mx_AppTile_persistedWrapper,
|
||||||
|
.mx_AppTileFullWidth .mx_AppTile_persistedWrapper,
|
||||||
.mx_AppTile_mini .mx_AppTile_persistedWrapper {
|
.mx_AppTile_mini .mx_AppTile_persistedWrapper {
|
||||||
height: 114px;
|
flex: 1;
|
||||||
min-width: 300px;
|
}
|
||||||
|
|
||||||
|
.mx_AppTile_persistedWrapper div {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_AppTileMenuBar {
|
.mx_AppTileMenuBar {
|
||||||
|
@ -110,6 +151,7 @@ $AppsDrawerBodyHeight: 273px;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_AppTileMenuBar_expanded {
|
.mx_AppTileMenuBar_expanded {
|
||||||
|
@ -172,7 +214,7 @@ $AppsDrawerBodyHeight: 273px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_AppTileBody {
|
.mx_AppTileBody {
|
||||||
height: $AppsDrawerBodyHeight;
|
height: 100%;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
@ -183,6 +225,13 @@ $AppsDrawerBodyHeight: 273px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mx_AppTile .mx_AppTileBody,
|
||||||
|
.mx_AppTileFullWidth .mx_AppTileBody,
|
||||||
|
.mx_AppTile_mini .mx_AppTileBody_mini {
|
||||||
|
height: inherit;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
.mx_AppTileBody_mini iframe {
|
.mx_AppTileBody_mini iframe {
|
||||||
border: none;
|
border: none;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
@ -191,7 +240,7 @@ $AppsDrawerBodyHeight: 273px;
|
||||||
|
|
||||||
.mx_AppTileBody iframe {
|
.mx_AppTileBody iframe {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: $AppsDrawerBodyHeight;
|
height: 100%;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
border: none;
|
border: none;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
@ -331,7 +380,7 @@ form.mx_Custom_Widget_Form div {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
position: relative;
|
position: relative;
|
||||||
height: $AppsDrawerBodyHeight;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_AppLoading .mx_Spinner {
|
.mx_AppLoading .mx_Spinner {
|
||||||
|
@ -358,3 +407,16 @@ form.mx_Custom_Widget_Form div {
|
||||||
.mx_AppLoading iframe {
|
.mx_AppLoading iframe {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mx_AppsDrawer_minimised .mx_AppsContainer_resizerHandle {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Avoid apptile iframes capturing mouse event focus when resizing */
|
||||||
|
.mx_AppsDrawer_resizing iframe {
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_AppsDrawer_resizing .mx_AppTile_persistedWrapper {
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
|
@ -394,16 +394,6 @@ $left-gutter: 64px;
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_EventTile_e2eIcon_hidden {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* always override hidden attribute for blocked and warning */
|
|
||||||
.mx_EventTile_e2eIcon_hidden[src*="img/e2e-blocked.svg"],
|
|
||||||
.mx_EventTile_e2eIcon_hidden[src*="img/e2e-warning.svg"] {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_EventTile_keyRequestInfo {
|
.mx_EventTile_keyRequestInfo {
|
||||||
font-size: $font-12px;
|
font-size: $font-12px;
|
||||||
}
|
}
|
||||||
|
|
|
@ -70,6 +70,10 @@ limitations under the License.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mx_MemberList_query {
|
||||||
|
height: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
.mx_MemberList_wrapper {
|
.mx_MemberList_wrapper {
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
}
|
}
|
||||||
|
|
|
@ -236,10 +236,6 @@ limitations under the License.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_RoomHeader_settingsButton::before {
|
|
||||||
mask-image: url('$(res)/img/element-icons/settings.svg');
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_RoomHeader_forgetButton::before {
|
.mx_RoomHeader_forgetButton::before {
|
||||||
mask-image: url('$(res)/img/element-icons/leave.svg');
|
mask-image: url('$(res)/img/element-icons/leave.svg');
|
||||||
width: 26px;
|
width: 26px;
|
||||||
|
@ -249,14 +245,6 @@ limitations under the License.
|
||||||
mask-image: url('$(res)/img/element-icons/room/search-inset.svg');
|
mask-image: url('$(res)/img/element-icons/room/search-inset.svg');
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_RoomHeader_shareButton::before {
|
|
||||||
mask-image: url('$(res)/img/element-icons/room/share.svg');
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_RoomHeader_manageIntegsButton::before {
|
|
||||||
mask-image: url('$(res)/img/element-icons/room/integrations.svg');
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_RoomHeader_showPanel {
|
.mx_RoomHeader_showPanel {
|
||||||
height: 16px;
|
height: 16px;
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,12 +7,19 @@
|
||||||
height: 300px;
|
height: 300px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#mx_persistedElement_stickerPicker .mx_AppTileFullWidth {
|
#mx_persistedElement_stickerPicker {
|
||||||
height: unset;
|
.mx_AppTileFullWidth {
|
||||||
box-sizing: border-box;
|
height: unset;
|
||||||
border-left: none;
|
box-sizing: border-box;
|
||||||
border-right: none;
|
border-left: none;
|
||||||
border-bottom: none;
|
border-right: none;
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
iframe {
|
||||||
|
// Sticker picker depends on the fixed height previously used for all tiles
|
||||||
|
height: 273px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_Stickers_contentPlaceholder {
|
.mx_Stickers_contentPlaceholder {
|
||||||
|
|
|
@ -28,4 +28,8 @@ limitations under the License.
|
||||||
|
|
||||||
.mx_CrossSigningPanel_buttonRow {
|
.mx_CrossSigningPanel_buttonRow {
|
||||||
margin: 1em 0;
|
margin: 1em 0;
|
||||||
|
|
||||||
|
:nth-child(n + 1) {
|
||||||
|
margin-inline-end: 10px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/*
|
/*
|
||||||
Copyright 2018 New Vector Ltd
|
Copyright 2018 New Vector Ltd
|
||||||
Copyright 2019 The Matrix.org Foundation C.I.C.
|
Copyright 2019, 2020 The Matrix.org Foundation C.I.C.
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
|
@ -15,23 +15,39 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
.mx_KeyBackupPanel_sigValid, .mx_KeyBackupPanel_sigInvalid,
|
.mx_SecureBackupPanel_sigValid, .mx_SecureBackupPanel_sigInvalid,
|
||||||
.mx_KeyBackupPanel_deviceVerified, .mx_KeyBackupPanel_deviceNotVerified {
|
.mx_SecureBackupPanel_deviceVerified, .mx_SecureBackupPanel_deviceNotVerified {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_KeyBackupPanel_sigValid, .mx_KeyBackupPanel_deviceVerified {
|
.mx_SecureBackupPanel_sigValid, .mx_SecureBackupPanel_deviceVerified {
|
||||||
color: $e2e-verified-color;
|
color: $e2e-verified-color;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_KeyBackupPanel_sigInvalid, .mx_KeyBackupPanel_deviceNotVerified {
|
.mx_SecureBackupPanel_sigInvalid, .mx_SecureBackupPanel_deviceNotVerified {
|
||||||
color: $e2e-warning-color;
|
color: $e2e-warning-color;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_KeyBackupPanel_deviceName {
|
.mx_SecureBackupPanel_deviceName {
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_KeyBackupPanel_buttonRow {
|
.mx_SecureBackupPanel_buttonRow {
|
||||||
margin: 1em 0;
|
margin: 1em 0;
|
||||||
|
|
||||||
|
:nth-child(n + 1) {
|
||||||
|
margin-inline-end: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_SecureBackupPanel_statusList {
|
||||||
|
border-spacing: 0;
|
||||||
|
|
||||||
|
td {
|
||||||
|
padding: 0;
|
||||||
|
|
||||||
|
&:first-of-type {
|
||||||
|
padding-inline-end: 1em;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
Copyright 2019 New Vector Ltd
|
Copyright 2019, 2020 New Vector Ltd
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
|
@ -14,6 +14,10 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
.mx_SettingsTab {
|
||||||
|
color: $muted-fg-color;
|
||||||
|
}
|
||||||
|
|
||||||
.mx_SettingsTab_warningText {
|
.mx_SettingsTab_warningText {
|
||||||
color: $warning-color;
|
color: $warning-color;
|
||||||
}
|
}
|
||||||
|
@ -22,6 +26,7 @@ limitations under the License.
|
||||||
font-size: $font-20px;
|
font-size: $font-20px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: $primary-fg-color;
|
color: $primary-fg-color;
|
||||||
|
margin-bottom: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_SettingsTab_heading:nth-child(n + 2) {
|
.mx_SettingsTab_heading:nth-child(n + 2) {
|
||||||
|
|
|
@ -36,6 +36,10 @@ limitations under the License.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mx_AppTile_persistedWrapper div {
|
||||||
|
min-width: 300px;
|
||||||
|
}
|
||||||
|
|
||||||
.mx_IncomingCallBox {
|
.mx_IncomingCallBox {
|
||||||
min-width: 250px;
|
min-width: 250px;
|
||||||
background-color: $primary-bg-color;
|
background-color: $primary-bg-color;
|
||||||
|
|
5
res/img/e2e/disabled.svg
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M15.38 12.27C15.76 11.42 16 10.43 16 9.27V3.05L8.99997 1L5.21997 2.11L15.38 12.27Z" fill="#010101"/>
|
||||||
|
<path d="M2.21 2.98999L2 3.04999V9.26999C2 15.63 9 17 9 17C9 17 11.71 16.47 13.76 14.53L2.21 2.98999Z" fill="#010101"/>
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M0.46967 0.46967C0.762563 0.176777 1.23744 0.176777 1.53033 0.46967L16.7203 15.6597C17.0132 15.9526 17.0132 16.4274 16.7203 16.7203C16.4274 17.0132 15.9526 17.0132 15.6597 16.7203L0.46967 1.53033C0.176777 1.23744 0.176777 0.762563 0.46967 0.46967Z" fill="#010101"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 648 B |
|
@ -1,3 +1,3 @@
|
||||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
<path d="M1.77777 8.24003V2.71114L7.99999 0.888916L14.2222 2.71114V8.24003C14.2222 13.8934 7.99999 15.1111 7.99999 15.1111C7.99999 15.1111 1.77777 13.8934 1.77777 8.24003Z" fill="#020202"/>
|
<path d="M2 9.27V3.05L9 1L16 3.05V9.27C16 15.63 9 17 9 17C9 17 2 15.63 2 9.27Z" fill="#020202"/>
|
||||||
</svg>
|
</svg>
|
||||||
|
|
Before Width: | Height: | Size: 293 B After Width: | Height: | Size: 204 B |
|
@ -1,3 +1,3 @@
|
||||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M1.77783 2.71114V8.24003C1.77783 13.8934 8.00005 15.1111 8.00005 15.1111C8.00005 15.1111 14.2223 13.8934 14.2223 8.24003V2.71114L8.00005 0.888916L1.77783 2.71114ZM10.6139 4.90635C10.7827 4.74635 11.0494 4.75524 11.2094 4.92413C11.3516 5.08413 11.3516 5.32413 11.2272 5.48413L7.47608 10.0263L7.44941 10.0619C7.20052 10.3641 6.74719 10.4086 6.44497 10.1597C6.41812 10.1463 6.39635 10.1227 6.37581 10.1005C6.36914 10.0933 6.36261 10.0862 6.35608 10.0797L4.74719 8.23079C4.56941 8.01746 4.58719 7.69746 4.80052 7.51968C4.9783 7.35968 5.23608 7.35968 5.42274 7.48413L6.8183 8.46191L10.6139 4.90635Z" fill="#0DBD8B"/>
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M2 3.05V9.27C2 15.63 9 17 9 17C9 17 16 15.63 16 9.27V3.05L9 1L2 3.05ZM11.9405 5.5196C12.1305 5.3396 12.4305 5.3496 12.6105 5.5396C12.7705 5.7196 12.7705 5.9896 12.6305 6.1696L8.41047 11.2796L8.38047 11.3196C8.10047 11.6596 7.59047 11.7096 7.25047 11.4296C7.22027 11.4145 7.19577 11.388 7.17266 11.363C7.16517 11.3549 7.15782 11.347 7.15047 11.3396L5.34047 9.2596C5.14047 9.0196 5.16047 8.6596 5.40047 8.4596C5.60047 8.2796 5.89047 8.2796 6.10047 8.4196L7.67047 9.5196L11.9405 5.5196Z" fill="#010101"/>
|
||||||
</svg>
|
</svg>
|
||||||
|
|
Before Width: | Height: | Size: 764 B After Width: | Height: | Size: 658 B |
|
@ -1,3 +1,3 @@
|
||||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M1.77783 2.71114V8.24003C1.77783 13.8934 8.00005 15.1111 8.00005 15.1111C8.00005 15.1111 14.2223 13.8934 14.2223 8.24003V2.71114L8.00005 0.888916L1.77783 2.71114ZM7.92899 3.91112C7.42232 3.94668 7.04899 4.39113 7.09343 4.89779L7.37788 8.45335C7.40455 8.76446 7.64455 9.00446 7.95566 9.03113H8.00899C8.33788 9.03113 8.61343 8.78224 8.6401 8.45335L8.92455 4.89779V4.75557C8.87121 4.2489 8.42677 3.87557 7.92899 3.91112ZM8 11.5556C8.43201 11.5556 8.78222 11.2054 8.78222 10.7733C8.78222 10.3413 8.43201 9.99112 8 9.99112C7.56799 9.99112 7.21777 10.3413 7.21777 10.7733C7.21777 11.2054 7.56799 11.5556 8 11.5556Z" fill="#FF4B55"/>
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M2 9.27V3.05L9 1L16 3.05V9.27C16 15.63 9 17 9 17C9 17 2 15.63 2 9.27ZM8.92011 4.39997C8.35011 4.43997 7.93011 4.93997 7.98011 5.50997L8.30011 9.50997C8.33011 9.85997 8.60011 10.13 8.95011 10.16H9.01011C9.38011 10.16 9.69011 9.87997 9.72011 9.50997L10.0401 5.50997V5.34997C9.98011 4.77997 9.48011 4.35997 8.92011 4.39997ZM9.88012 12.12C9.88012 12.606 9.48613 13 9.00012 13C8.51411 13 8.12012 12.606 8.12012 12.12C8.12012 11.634 8.51411 11.24 9.00012 11.24C9.48613 11.24 9.88012 11.634 9.88012 12.12Z" fill="#020202"/>
|
||||||
</svg>
|
</svg>
|
||||||
|
|
Before Width: | Height: | Size: 779 B After Width: | Height: | Size: 673 B |
5
res/img/element-icons/add-photo.svg
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
<svg width="84" height="84" viewBox="0 0 84 84" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M36.7988 34.9062C37.077 33.5217 38.2978 32.5 39.7396 32.5H44.2604C45.7022 32.5 46.923 33.5217 47.2012 34.9062C47.2429 35.1137 47.3232 35.3141 47.4627 35.4731L48.0649 36.1595C48.2548 36.3759 48.5287 36.5 48.8166 36.5H52C53.1046 36.5 54 37.3954 54 38.5V49.5C54 50.6046 53.1046 51.5 52 51.5H32C30.8954 51.5 30 50.6046 30 49.5V38.5C30 37.3954 30.8954 36.5 32 36.5H35.1834C35.4713 36.5 35.7452 36.3759 35.9351 36.1595L36.5373 35.4731C36.6768 35.3141 36.7571 35.1137 36.7988 34.9062ZM42 47.5C44.2091 47.5 46 45.7091 46 43.5C46 41.2909 44.2091 39.5 42 39.5C39.7909 39.5 38 41.2909 38 43.5C38 45.7091 39.7909 47.5 42 47.5Z" fill="white"/>
|
||||||
|
<rect x="32" y="35" width="3" height="1" rx="0.5" fill="white"/>
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M59.75 27C59.75 26.5858 59.4142 26.25 59 26.25C58.5858 26.25 58.25 26.5858 58.25 27V31.25L54 31.25C53.5858 31.25 53.25 31.5858 53.25 32C53.25 32.4142 53.5858 32.75 54 32.75L58.25 32.75V37C58.25 37.4142 58.5858 37.75 59 37.75C59.4142 37.75 59.75 37.4142 59.75 37V32.75L64 32.75C64.4142 32.75 64.75 32.4142 64.75 32C64.75 31.5858 64.4142 31.25 64 31.25L59.75 31.25V27Z" fill="white"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.3 KiB |
3
res/img/element-icons/home.svg
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M2.33301 7.28322V14.9493C2.33301 16.0735 3.25744 16.9776 4.38152 16.9659C4.90089 16.9605 5.44431 16.9567 6 16.9543V11.5C6 10.6716 6.67157 10 7.5 10H10.5C11.3284 10 12 10.6716 12 11.5V16.9662C12.6022 16.9703 13.1579 16.9748 13.6449 16.9791C14.7592 16.989 15.6663 16.0899 15.6663 14.9756V7.28178C15.6663 6.89062 15.4946 6.52064 15.1965 6.2673L9.97115 1.82572C9.411 1.3496 8.58834 1.3496 8.0282 1.82572L2.80281 6.2673C2.50477 6.52064 2.33301 6.89206 2.33301 7.28322Z" fill="#737D8C"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 634 B |
4
res/img/element-icons/info.svg
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<circle cx="10" cy="10" r="9.5" stroke="#787878"/>
|
||||||
|
<path d="M9.79248 14H11.2065V8H9.79248V14ZM10.5034 7.14844C10.9526 7.14844 11.3198 6.80469 11.3198 6.38281C11.3198 5.95703 10.9526 5.61328 10.5034 5.61328C10.0503 5.61328 9.68311 5.95703 9.68311 6.38281C9.68311 6.80469 10.0503 7.14844 10.5034 7.14844Z" fill="#787878"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 424 B |
11
res/img/element-icons/room/default_app.svg
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
<svg width="21" height="20" viewBox="0 0 21 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<rect width="20" height="20" fill="url(#paint0_linear)"/>
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M2 3V9.5H0.00390625L0.00390625 10.5H2V17H0.00390625L0.00390625 18H2V20H3V18H9.5039V20.0005H10.5039V18H17V20H18V18H20.0039V17H18V10.5H20.0039V9.5H18V3H20.0039V2H18V0L17 0V2H10.5039V0.000488281L9.5039 0.000488281V2H3V0L2 0V2H0.00390625L0.00390625 3H2ZM17 3H10.5039V9.5H17V3ZM17 10.5H10.5039V17H17V10.5ZM9.5039 10.5V17H3V10.5H9.5039ZM9.5039 3V9.5H3V3H9.5039Z" fill="white" fill-opacity="0.3" style="mix-blend-mode:lighten"/>
|
||||||
|
<circle opacity="0.8" cx="10.0039" cy="10" r="7.5" stroke="white"/>
|
||||||
|
<defs>
|
||||||
|
<linearGradient id="paint0_linear" x1="10" y1="0" x2="10" y2="20" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop stop-color="#60A6FF"/>
|
||||||
|
<stop offset="1" stop-color="#418DED"/>
|
||||||
|
</linearGradient>
|
||||||
|
</defs>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 900 B |
6
res/img/element-icons/room/default_cal.svg
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
<svg width="21" height="20" viewBox="0 0 21 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<rect x="1.99461" y="1.00002" width="18" height="18" rx="2" fill="white" stroke="#FF4B55" stroke-width="2"/>
|
||||||
|
<rect x="2.96777" y="2" width="16.9843" height="5" fill="#FF4B55"/>
|
||||||
|
<rect x="4.96533" y="9" width="2.99723" height="3" rx="0.25" fill="#FF4B55"/>
|
||||||
|
<rect x="11.9585" y="13.0005" width="2.99723" height="3" rx="0.25" fill="#FF4B55"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 442 B |
5
res/img/element-icons/room/default_clock.svg
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
<svg width="21" height="20" viewBox="0 0 21 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<rect x="1.49609" y="0.500488" width="19" height="19" rx="3.5" fill="#17191C" stroke="#17191C"/>
|
||||||
|
<path d="M18.9961 10.0005C18.9961 14.4188 15.4144 18.0005 10.9961 18.0005C6.57782 18.0005 2.99609 14.4188 2.99609 10.0005C2.99609 5.58221 6.57782 2.00049 10.9961 2.00049C15.4144 2.00049 18.9961 5.58221 18.9961 10.0005Z" fill="white"/>
|
||||||
|
<path d="M10.9961 6.00049V9.81299L13.4961 11.5005" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 569 B |
4
res/img/element-icons/room/default_doc.svg
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<rect x="-0.000976562" y="0.000488281" width="20" height="20" rx="4" fill="#FCC639"/>
|
||||||
|
<path d="M1.99902 7.00049H17.999V16.5005C17.999 17.3289 17.3274 18.0005 16.499 18.0005H3.49902C2.6706 18.0005 1.99902 17.3289 1.99902 16.5005V7.00049Z" fill="white"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 364 B |
5
res/img/element-icons/room/ellipsis.svg
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<circle cx="15.5" cy="10" r="1.5" transform="rotate(180 15.5 10)" fill="#15191E"/>
|
||||||
|
<circle cx="10" cy="10" r="1.5" transform="rotate(180 10 10)" fill="#15191E"/>
|
||||||
|
<circle cx="4.5" cy="10" r="1.5" transform="rotate(180 4.5 10)" fill="#15191E"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 358 B |
7
res/img/element-icons/room/pin-upright.svg
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M11 18.5982V15H13V18.5982C13 20.5 12.2383 22 12 22C11.7617 22 11 20.5 11 18.5982Z" fill="black"/>
|
||||||
|
<path d="M9.5 6C9.17534 5.83333 7.78566 5.2 6.61693 4C5.4482 2.8 6.12239 2 7.13026 2H12V6H9.5Z" fill="black"/>
|
||||||
|
<path d="M14.5 6C14.8247 5.83333 16.2143 5.2 17.3831 4C18.5518 2.8 17.8776 2 16.8697 2H12V6H14.5Z" fill="black"/>
|
||||||
|
<path d="M9.42857 6H14.5714L15 10H9L9.42857 6Z" fill="black"/>
|
||||||
|
<path d="M12 9C8.93114 9 6.32353 10.6927 5.37867 13.0483C4.96746 14.0735 5.89543 15 7 15H17C18.1046 15 19.0325 14.0735 18.6213 13.0483C17.6765 10.6927 15.0689 9 12 9Z" fill="black"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 681 B |
3
res/img/element-icons/room/room-summary.svg
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M22 12C22 17.5228 17.5228 22 12 22C6.47715 22 2 17.5228 2 12C2 6.47715 6.47715 2 12 2C17.5228 2 22 6.47715 22 12ZM10 12C10 12.5523 10.4477 13 11 13V16.5C11 17.0523 11.4477 17.5 12 17.5H13.5C14.0523 17.5 14.5 17.0523 14.5 16.5C14.5 15.9477 14.0523 15.5 13.5 15.5H13V12C13 11.4477 12.5523 11 12 11H11C10.4477 11 10 11.4477 10 12ZM12 10C12.8284 10 13.5 9.32843 13.5 8.5C13.5 7.67157 12.8284 7 12 7C11.1716 7 10.5 7.67157 10.5 8.5C10.5 9.32843 11.1716 10 12 10Z" fill="black"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 630 B |
|
@ -119,6 +119,8 @@ $roomlist-bg-color: rgba(33, 38, 44, 0.90);
|
||||||
$roomlist-header-color: $tertiary-fg-color;
|
$roomlist-header-color: $tertiary-fg-color;
|
||||||
$roomsublist-divider-color: $primary-fg-color;
|
$roomsublist-divider-color: $primary-fg-color;
|
||||||
|
|
||||||
|
$tagpanel-divider-color: $roomlist-header-color;
|
||||||
|
|
||||||
$roomtile-preview-color: $secondary-fg-color;
|
$roomtile-preview-color: $secondary-fg-color;
|
||||||
$roomtile-default-badge-bg-color: #61708b;
|
$roomtile-default-badge-bg-color: #61708b;
|
||||||
$roomtile-selected-bg-color: rgba(141, 151, 165, 0.2);
|
$roomtile-selected-bg-color: rgba(141, 151, 165, 0.2);
|
||||||
|
|
|
@ -116,6 +116,8 @@ $roomlist-bg-color: $header-panel-bg-color;
|
||||||
|
|
||||||
$roomsublist-divider-color: $primary-fg-color;
|
$roomsublist-divider-color: $primary-fg-color;
|
||||||
|
|
||||||
|
$tagpanel-divider-color: $roomlist-header-color;
|
||||||
|
|
||||||
$roomtile-preview-color: #9e9e9e;
|
$roomtile-preview-color: #9e9e9e;
|
||||||
$roomtile-default-badge-bg-color: #61708b;
|
$roomtile-default-badge-bg-color: #61708b;
|
||||||
$roomtile-selected-bg-color: #1A1D23;
|
$roomtile-selected-bg-color: #1A1D23;
|
||||||
|
|
|
@ -183,6 +183,8 @@ $roomlist-bg-color: $header-panel-bg-color;
|
||||||
$roomlist-header-color: $primary-fg-color;
|
$roomlist-header-color: $primary-fg-color;
|
||||||
$roomsublist-divider-color: $primary-fg-color;
|
$roomsublist-divider-color: $primary-fg-color;
|
||||||
|
|
||||||
|
$tagpanel-divider-color: $roomlist-header-color;
|
||||||
|
|
||||||
$roomtile-preview-color: #9e9e9e;
|
$roomtile-preview-color: #9e9e9e;
|
||||||
$roomtile-default-badge-bg-color: #61708b;
|
$roomtile-default-badge-bg-color: #61708b;
|
||||||
$roomtile-selected-bg-color: #fff;
|
$roomtile-selected-bg-color: #fff;
|
||||||
|
|
|
@ -177,6 +177,8 @@ $roomlist-bg-color: rgba(245, 245, 245, 0.90);
|
||||||
$roomlist-header-color: $tertiary-fg-color;
|
$roomlist-header-color: $tertiary-fg-color;
|
||||||
$roomsublist-divider-color: $primary-fg-color;
|
$roomsublist-divider-color: $primary-fg-color;
|
||||||
|
|
||||||
|
$tagpanel-divider-color: $roomlist-header-color;
|
||||||
|
|
||||||
$roomtile-preview-color: $secondary-fg-color;
|
$roomtile-preview-color: $secondary-fg-color;
|
||||||
$roomtile-default-badge-bg-color: #61708b;
|
$roomtile-default-badge-bg-color: #61708b;
|
||||||
$roomtile-selected-bg-color: #FFF;
|
$roomtile-selected-bg-color: #FFF;
|
||||||
|
|
6
src/@types/global.d.ts
vendored
|
@ -27,10 +27,14 @@ import {ModalManager} from "../Modal";
|
||||||
import SettingsStore from "../settings/SettingsStore";
|
import SettingsStore from "../settings/SettingsStore";
|
||||||
import {ActiveRoomObserver} from "../ActiveRoomObserver";
|
import {ActiveRoomObserver} from "../ActiveRoomObserver";
|
||||||
import {Notifier} from "../Notifier";
|
import {Notifier} from "../Notifier";
|
||||||
|
import type {Renderer} from "react-dom";
|
||||||
|
import RightPanelStore from "../stores/RightPanelStore";
|
||||||
|
import WidgetStore from "../stores/WidgetStore";
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface Window {
|
interface Window {
|
||||||
Modernizr: ModernizrStatic;
|
Modernizr: ModernizrStatic;
|
||||||
|
matrixChat: ReturnType<Renderer>;
|
||||||
mxMatrixClientPeg: IMatrixClientPeg;
|
mxMatrixClientPeg: IMatrixClientPeg;
|
||||||
Olm: {
|
Olm: {
|
||||||
init: () => Promise<void>;
|
init: () => Promise<void>;
|
||||||
|
@ -47,6 +51,8 @@ declare global {
|
||||||
singletonModalManager: ModalManager;
|
singletonModalManager: ModalManager;
|
||||||
mxSettingsStore: SettingsStore;
|
mxSettingsStore: SettingsStore;
|
||||||
mxNotifier: typeof Notifier;
|
mxNotifier: typeof Notifier;
|
||||||
|
mxRightPanelStore: RightPanelStore;
|
||||||
|
mxWidgetStore: WidgetStore;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Document {
|
interface Document {
|
||||||
|
|
|
@ -170,15 +170,19 @@ class Analytics {
|
||||||
return !this.baseUrl;
|
return !this.baseUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
canEnable() {
|
||||||
|
const config = SdkConfig.get();
|
||||||
|
return navigator.doNotTrack !== "1" && config && config.piwik && config.piwik.url && config.piwik.siteId;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Enable Analytics if initialized but disabled
|
* Enable Analytics if initialized but disabled
|
||||||
* otherwise try and initalize, no-op if piwik config missing
|
* otherwise try and initalize, no-op if piwik config missing
|
||||||
*/
|
*/
|
||||||
async enable() {
|
async enable() {
|
||||||
if (!this.disabled) return;
|
if (!this.disabled) return;
|
||||||
|
if (!this.canEnable()) return;
|
||||||
const config = SdkConfig.get();
|
const config = SdkConfig.get();
|
||||||
if (!config || !config.piwik || !config.piwik.url || !config.piwik.siteId) return;
|
|
||||||
|
|
||||||
this.baseUrl = new URL("piwik.php", config.piwik.url);
|
this.baseUrl = new URL("piwik.php", config.piwik.url);
|
||||||
// set constants
|
// set constants
|
||||||
|
|
|
@ -15,7 +15,7 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import createReactClass from 'create-react-class';
|
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 { _t } from './languageHandler';
|
import { _t } from './languageHandler';
|
||||||
|
@ -24,21 +24,19 @@ import { _t } from './languageHandler';
|
||||||
* Wrap an asynchronous loader function with a react component which shows a
|
* Wrap an asynchronous loader function with a react component which shows a
|
||||||
* spinner until the real component loads.
|
* spinner until the real component loads.
|
||||||
*/
|
*/
|
||||||
export default createReactClass({
|
export default class AsyncWrapper extends React.Component {
|
||||||
propTypes: {
|
static propTypes = {
|
||||||
/** A promise which resolves with the real component
|
/** A promise which resolves with the real component
|
||||||
*/
|
*/
|
||||||
prom: PropTypes.object.isRequired,
|
prom: PropTypes.object.isRequired,
|
||||||
},
|
};
|
||||||
|
|
||||||
getInitialState: function() {
|
state = {
|
||||||
return {
|
component: null,
|
||||||
component: null,
|
error: null,
|
||||||
error: null,
|
};
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
componentDidMount: function() {
|
componentDidMount() {
|
||||||
this._unmounted = false;
|
this._unmounted = false;
|
||||||
// XXX: temporary logging to try to diagnose
|
// XXX: temporary logging to try to diagnose
|
||||||
// https://github.com/vector-im/element-web/issues/3148
|
// https://github.com/vector-im/element-web/issues/3148
|
||||||
|
@ -56,17 +54,17 @@ export default createReactClass({
|
||||||
console.warn('AsyncWrapper promise failed', e);
|
console.warn('AsyncWrapper promise failed', e);
|
||||||
this.setState({error: e});
|
this.setState({error: e});
|
||||||
});
|
});
|
||||||
},
|
}
|
||||||
|
|
||||||
componentWillUnmount: function() {
|
componentWillUnmount() {
|
||||||
this._unmounted = true;
|
this._unmounted = true;
|
||||||
},
|
}
|
||||||
|
|
||||||
_onWrapperCancelClick: function() {
|
_onWrapperCancelClick = () => {
|
||||||
this.props.onFinished(false);
|
this.props.onFinished(false);
|
||||||
},
|
};
|
||||||
|
|
||||||
render: function() {
|
render() {
|
||||||
if (this.state.component) {
|
if (this.state.component) {
|
||||||
const Component = this.state.component;
|
const Component = this.state.component;
|
||||||
return <Component {...this.props} />;
|
return <Component {...this.props} />;
|
||||||
|
@ -87,6 +85,6 @@ export default createReactClass({
|
||||||
const Spinner = sdk.getComponent("elements.Spinner");
|
const Spinner = sdk.getComponent("elements.Spinner");
|
||||||
return <Spinner />;
|
return <Spinner />;
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
/*
|
/*
|
||||||
Copyright 2015, 2016 OpenMarket Ltd
|
Copyright 2015, 2016 OpenMarket Ltd
|
||||||
Copyright 2017, 2018 New Vector Ltd
|
Copyright 2017, 2018 New Vector Ltd
|
||||||
Copyright 2019 The Matrix.org Foundation C.I.C.
|
Copyright 2019, 2020 The Matrix.org Foundation C.I.C.
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
|
@ -56,7 +56,6 @@ limitations under the License.
|
||||||
import {MatrixClientPeg} from './MatrixClientPeg';
|
import {MatrixClientPeg} from './MatrixClientPeg';
|
||||||
import PlatformPeg from './PlatformPeg';
|
import PlatformPeg from './PlatformPeg';
|
||||||
import Modal from './Modal';
|
import Modal from './Modal';
|
||||||
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/dispatcher';
|
import dis from './dispatcher/dispatcher';
|
||||||
|
@ -67,6 +66,10 @@ import {generateHumanReadableId} from "./utils/NamingUtils";
|
||||||
import {Jitsi} from "./widgets/Jitsi";
|
import {Jitsi} from "./widgets/Jitsi";
|
||||||
import {WidgetType} from "./widgets/WidgetType";
|
import {WidgetType} from "./widgets/WidgetType";
|
||||||
import {SettingLevel} from "./settings/SettingLevel";
|
import {SettingLevel} from "./settings/SettingLevel";
|
||||||
|
import {base32} from "rfc4648";
|
||||||
|
|
||||||
|
import QuestionDialog from "./components/views/dialogs/QuestionDialog";
|
||||||
|
import ErrorDialog from "./components/views/dialogs/ErrorDialog";
|
||||||
|
|
||||||
global.mxCalls = {
|
global.mxCalls = {
|
||||||
//room_id: MatrixCall
|
//room_id: MatrixCall
|
||||||
|
@ -130,7 +133,6 @@ function _setCallListeners(call) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
|
||||||
Modal.createTrackedDialog('Call Failed', '', ErrorDialog, {
|
Modal.createTrackedDialog('Call Failed', '', ErrorDialog, {
|
||||||
title: _t('Call Failed'),
|
title: _t('Call Failed'),
|
||||||
description: err.message,
|
description: err.message,
|
||||||
|
@ -159,7 +161,6 @@ function _setCallListeners(call) {
|
||||||
_setCallState(call, call.roomId, "busy");
|
_setCallState(call, call.roomId, "busy");
|
||||||
pause("ringbackAudio");
|
pause("ringbackAudio");
|
||||||
play("busyAudio");
|
play("busyAudio");
|
||||||
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
|
||||||
Modal.createTrackedDialog('Call Handler', 'Call Timeout', ErrorDialog, {
|
Modal.createTrackedDialog('Call Handler', 'Call Timeout', ErrorDialog, {
|
||||||
title: _t('Call Timeout'),
|
title: _t('Call Timeout'),
|
||||||
description: _t('The remote side failed to pick up') + '.',
|
description: _t('The remote side failed to pick up') + '.',
|
||||||
|
@ -201,7 +202,6 @@ function _setCallState(call, roomId, status) {
|
||||||
|
|
||||||
function _showICEFallbackPrompt() {
|
function _showICEFallbackPrompt() {
|
||||||
const cli = MatrixClientPeg.get();
|
const cli = MatrixClientPeg.get();
|
||||||
const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
|
|
||||||
const code = sub => <code>{sub}</code>;
|
const code = sub => <code>{sub}</code>;
|
||||||
Modal.createTrackedDialog('No TURN servers', '', QuestionDialog, {
|
Modal.createTrackedDialog('No TURN servers', '', QuestionDialog, {
|
||||||
title: _t("Call failed due to misconfigured server"),
|
title: _t("Call failed due to misconfigured server"),
|
||||||
|
@ -244,7 +244,6 @@ function _onAction(payload) {
|
||||||
if (screenCapErrorString) {
|
if (screenCapErrorString) {
|
||||||
_setCallState(undefined, newCall.roomId, "ended");
|
_setCallState(undefined, newCall.roomId, "ended");
|
||||||
console.log("Can't capture screen: " + screenCapErrorString);
|
console.log("Can't capture screen: " + screenCapErrorString);
|
||||||
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
|
||||||
Modal.createTrackedDialog('Call Handler', 'Unable to capture screen', ErrorDialog, {
|
Modal.createTrackedDialog('Call Handler', 'Unable to capture screen', ErrorDialog, {
|
||||||
title: _t('Unable to capture screen'),
|
title: _t('Unable to capture screen'),
|
||||||
description: screenCapErrorString,
|
description: screenCapErrorString,
|
||||||
|
@ -264,7 +263,6 @@ function _onAction(payload) {
|
||||||
case 'place_call':
|
case 'place_call':
|
||||||
{
|
{
|
||||||
if (callHandler.getAnyActiveCall()) {
|
if (callHandler.getAnyActiveCall()) {
|
||||||
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
|
||||||
Modal.createTrackedDialog('Call Handler', 'Existing Call', ErrorDialog, {
|
Modal.createTrackedDialog('Call Handler', 'Existing Call', ErrorDialog, {
|
||||||
title: _t('Existing Call'),
|
title: _t('Existing Call'),
|
||||||
description: _t('You are already in a call.'),
|
description: _t('You are already in a call.'),
|
||||||
|
@ -274,7 +272,6 @@ function _onAction(payload) {
|
||||||
|
|
||||||
// if the runtime env doesn't do VoIP, whine.
|
// if the runtime env doesn't do VoIP, whine.
|
||||||
if (!MatrixClientPeg.get().supportsVoip()) {
|
if (!MatrixClientPeg.get().supportsVoip()) {
|
||||||
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
|
||||||
Modal.createTrackedDialog('Call Handler', 'VoIP is unsupported', ErrorDialog, {
|
Modal.createTrackedDialog('Call Handler', 'VoIP is unsupported', ErrorDialog, {
|
||||||
title: _t('VoIP is unsupported'),
|
title: _t('VoIP is unsupported'),
|
||||||
description: _t('You cannot place VoIP calls in this browser.'),
|
description: _t('You cannot place VoIP calls in this browser.'),
|
||||||
|
@ -290,7 +287,6 @@ function _onAction(payload) {
|
||||||
|
|
||||||
const members = room.getJoinedMembers();
|
const members = room.getJoinedMembers();
|
||||||
if (members.length <= 1) {
|
if (members.length <= 1) {
|
||||||
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
|
||||||
Modal.createTrackedDialog('Call Handler', 'Cannot place call with self', ErrorDialog, {
|
Modal.createTrackedDialog('Call Handler', 'Cannot place call with self', ErrorDialog, {
|
||||||
description: _t('You cannot place a call with yourself.'),
|
description: _t('You cannot place a call with yourself.'),
|
||||||
});
|
});
|
||||||
|
@ -365,8 +361,6 @@ async function _startCallApp(roomId, type) {
|
||||||
const currentJitsiWidgets = WidgetUtils.getRoomWidgetsOfType(room, WidgetType.JITSI);
|
const currentJitsiWidgets = WidgetUtils.getRoomWidgetsOfType(room, WidgetType.JITSI);
|
||||||
|
|
||||||
if (WidgetEchoStore.roomHasPendingWidgetsOfType(roomId, currentJitsiWidgets, WidgetType.JITSI)) {
|
if (WidgetEchoStore.roomHasPendingWidgetsOfType(roomId, currentJitsiWidgets, WidgetType.JITSI)) {
|
||||||
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
|
||||||
|
|
||||||
Modal.createTrackedDialog('Call already in progress', '', ErrorDialog, {
|
Modal.createTrackedDialog('Call already in progress', '', ErrorDialog, {
|
||||||
title: _t('Call in Progress'),
|
title: _t('Call in Progress'),
|
||||||
description: _t('A call is currently being placed!'),
|
description: _t('A call is currently being placed!'),
|
||||||
|
@ -379,19 +373,43 @@ async function _startCallApp(roomId, type) {
|
||||||
"Refusing to start conference call widget in " + roomId +
|
"Refusing to start conference call widget in " + roomId +
|
||||||
" a conference call widget is already present",
|
" a conference call widget is already present",
|
||||||
);
|
);
|
||||||
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
|
||||||
|
|
||||||
Modal.createTrackedDialog('Already have Jitsi Widget', '', ErrorDialog, {
|
if (WidgetUtils.canUserModifyWidgets(roomId)) {
|
||||||
title: _t('Call in Progress'),
|
Modal.createTrackedDialog('Already have Jitsi Widget', '', QuestionDialog, {
|
||||||
description: _t('A call is already in progress!'),
|
title: _t('End Call'),
|
||||||
});
|
description: _t('Remove the group call from the room?'),
|
||||||
|
button: _t('End Call'),
|
||||||
|
cancelButton: _t('Cancel'),
|
||||||
|
onFinished: (endCall) => {
|
||||||
|
if (endCall) {
|
||||||
|
WidgetUtils.setRoomWidget(roomId, currentJitsiWidgets[0].getContent()['id']);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
Modal.createTrackedDialog('Already have Jitsi Widget', '', ErrorDialog, {
|
||||||
|
title: _t('Call in Progress'),
|
||||||
|
description: _t("You don't have permission to remove the call from the room"),
|
||||||
|
});
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const confId = `JitsiConference${generateHumanReadableId()}`;
|
|
||||||
const jitsiDomain = Jitsi.getInstance().preferredDomain;
|
const jitsiDomain = Jitsi.getInstance().preferredDomain;
|
||||||
|
const jitsiAuth = await Jitsi.getInstance().getJitsiAuth();
|
||||||
|
let confId;
|
||||||
|
if (jitsiAuth === 'openidtoken-jwt') {
|
||||||
|
// Create conference ID from room ID
|
||||||
|
// For compatibility with Jitsi, use base32 without padding.
|
||||||
|
// More details here:
|
||||||
|
// https://github.com/matrix-org/prosody-mod-auth-matrix-user-verification
|
||||||
|
confId = base32.stringify(Buffer.from(roomId), { pad: false });
|
||||||
|
} else {
|
||||||
|
// Create a random human readable conference ID
|
||||||
|
confId = `JitsiConference${generateHumanReadableId()}`;
|
||||||
|
}
|
||||||
|
|
||||||
let widgetUrl = WidgetUtils.getLocalJitsiWrapperUrl();
|
let widgetUrl = WidgetUtils.getLocalJitsiWrapperUrl({auth: jitsiAuth});
|
||||||
|
|
||||||
// TODO: Remove URL hacks when the mobile clients eventually support v2 widgets
|
// TODO: Remove URL hacks when the mobile clients eventually support v2 widgets
|
||||||
const parsedUrl = new URL(widgetUrl);
|
const parsedUrl = new URL(widgetUrl);
|
||||||
|
@ -403,6 +421,7 @@ async function _startCallApp(roomId, type) {
|
||||||
conferenceId: confId,
|
conferenceId: confId,
|
||||||
isAudioOnly: type === 'voice',
|
isAudioOnly: type === 'voice',
|
||||||
domain: jitsiDomain,
|
domain: jitsiDomain,
|
||||||
|
auth: jitsiAuth,
|
||||||
};
|
};
|
||||||
|
|
||||||
const widgetId = (
|
const widgetId = (
|
||||||
|
@ -416,8 +435,6 @@ async function _startCallApp(roomId, type) {
|
||||||
console.log('Jitsi widget added');
|
console.log('Jitsi widget added');
|
||||||
}).catch((e) => {
|
}).catch((e) => {
|
||||||
if (e.errcode === 'M_FORBIDDEN') {
|
if (e.errcode === 'M_FORBIDDEN') {
|
||||||
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
|
||||||
|
|
||||||
Modal.createTrackedDialog('Call Failed', '', ErrorDialog, {
|
Modal.createTrackedDialog('Call Failed', '', ErrorDialog, {
|
||||||
title: _t('Permission Required'),
|
title: _t('Permission Required'),
|
||||||
description: _t("You do not have permission to start a conference call in this room"),
|
description: _t("You do not have permission to start a conference call in this room"),
|
||||||
|
|
|
@ -70,6 +70,7 @@ interface IContent {
|
||||||
|
|
||||||
interface IThumbnail {
|
interface IThumbnail {
|
||||||
info: {
|
info: {
|
||||||
|
// eslint-disable-next-line camelcase
|
||||||
thumbnail_info: {
|
thumbnail_info: {
|
||||||
w: number;
|
w: number;
|
||||||
h: number;
|
h: number;
|
||||||
|
@ -104,7 +105,12 @@ interface IAbortablePromise<T> extends Promise<T> {
|
||||||
* @return {Promise} A promise that resolves with an object with an info key
|
* @return {Promise} A promise that resolves with an object with an info key
|
||||||
* and a thumbnail key.
|
* and a thumbnail key.
|
||||||
*/
|
*/
|
||||||
function createThumbnail(element: ThumbnailableElement, inputWidth: number, inputHeight: number, mimeType: string): Promise<IThumbnail> {
|
function createThumbnail(
|
||||||
|
element: ThumbnailableElement,
|
||||||
|
inputWidth: number,
|
||||||
|
inputHeight: number,
|
||||||
|
mimeType: string,
|
||||||
|
): Promise<IThumbnail> {
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
let targetWidth = inputWidth;
|
let targetWidth = inputWidth;
|
||||||
let targetHeight = inputHeight;
|
let targetHeight = inputHeight;
|
||||||
|
@ -437,11 +443,13 @@ export default class ContentMessages {
|
||||||
for (let i = 0; i < okFiles.length; ++i) {
|
for (let i = 0; i < okFiles.length; ++i) {
|
||||||
const file = okFiles[i];
|
const file = okFiles[i];
|
||||||
if (!uploadAll) {
|
if (!uploadAll) {
|
||||||
const {finished} = Modal.createTrackedDialog<[boolean, boolean]>('Upload Files confirmation', '', UploadConfirmDialog, {
|
const {finished} = Modal.createTrackedDialog<[boolean, boolean]>('Upload Files confirmation',
|
||||||
file,
|
'', UploadConfirmDialog, {
|
||||||
currentIndex: i,
|
file,
|
||||||
totalFiles: okFiles.length,
|
currentIndex: i,
|
||||||
});
|
totalFiles: okFiles.length,
|
||||||
|
},
|
||||||
|
);
|
||||||
const [shouldContinue, shouldUploadAll] = await finished;
|
const [shouldContinue, shouldUploadAll] = await finished;
|
||||||
if (!shouldContinue) break;
|
if (!shouldContinue) break;
|
||||||
if (shouldUploadAll) {
|
if (shouldUploadAll) {
|
||||||
|
|
|
@ -15,6 +15,7 @@ limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {MatrixClientPeg} from './MatrixClientPeg';
|
import {MatrixClientPeg} from './MatrixClientPeg';
|
||||||
|
import dis from "./dispatcher/dispatcher";
|
||||||
import {
|
import {
|
||||||
hideToast as hideBulkUnverifiedSessionsToast,
|
hideToast as hideBulkUnverifiedSessionsToast,
|
||||||
showToast as showBulkUnverifiedSessionsToast,
|
showToast as showBulkUnverifiedSessionsToast,
|
||||||
|
@ -28,11 +29,15 @@ import {
|
||||||
hideToast as hideUnverifiedSessionsToast,
|
hideToast as hideUnverifiedSessionsToast,
|
||||||
showToast as showUnverifiedSessionsToast,
|
showToast as showUnverifiedSessionsToast,
|
||||||
} from "./toasts/UnverifiedSessionToast";
|
} from "./toasts/UnverifiedSessionToast";
|
||||||
import {privateShouldBeEncrypted} from "./createRoom";
|
import { isSecretStorageBeingAccessed, accessSecretStorage } from "./SecurityManager";
|
||||||
|
import { isSecureBackupRequired } from './utils/WellKnownUtils';
|
||||||
|
import { isLoggedIn } from './components/structures/MatrixChat';
|
||||||
|
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
|
||||||
|
|
||||||
const KEY_BACKUP_POLL_INTERVAL = 5 * 60 * 1000;
|
const KEY_BACKUP_POLL_INTERVAL = 5 * 60 * 1000;
|
||||||
|
|
||||||
export default class DeviceListener {
|
export default class DeviceListener {
|
||||||
|
private dispatcherRef: string;
|
||||||
// device IDs for which the user has dismissed the verify toast ('Later')
|
// device IDs for which the user has dismissed the verify toast ('Later')
|
||||||
private dismissed = new Set<string>();
|
private dismissed = new Set<string>();
|
||||||
// has the user dismissed any of the various nag toasts to setup encryption on this device?
|
// has the user dismissed any of the various nag toasts to setup encryption on this device?
|
||||||
|
@ -60,6 +65,8 @@ export default class DeviceListener {
|
||||||
MatrixClientPeg.get().on('crossSigning.keysChanged', this._onCrossSingingKeysChanged);
|
MatrixClientPeg.get().on('crossSigning.keysChanged', this._onCrossSingingKeysChanged);
|
||||||
MatrixClientPeg.get().on('accountData', this._onAccountData);
|
MatrixClientPeg.get().on('accountData', this._onAccountData);
|
||||||
MatrixClientPeg.get().on('sync', this._onSync);
|
MatrixClientPeg.get().on('sync', this._onSync);
|
||||||
|
MatrixClientPeg.get().on('RoomState.events', this._onRoomStateEvents);
|
||||||
|
this.dispatcherRef = dis.register(this._onAction);
|
||||||
this._recheck();
|
this._recheck();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -72,6 +79,11 @@ export default class DeviceListener {
|
||||||
MatrixClientPeg.get().removeListener('crossSigning.keysChanged', this._onCrossSingingKeysChanged);
|
MatrixClientPeg.get().removeListener('crossSigning.keysChanged', this._onCrossSingingKeysChanged);
|
||||||
MatrixClientPeg.get().removeListener('accountData', this._onAccountData);
|
MatrixClientPeg.get().removeListener('accountData', this._onAccountData);
|
||||||
MatrixClientPeg.get().removeListener('sync', this._onSync);
|
MatrixClientPeg.get().removeListener('sync', this._onSync);
|
||||||
|
MatrixClientPeg.get().removeListener('RoomState.events', this._onRoomStateEvents);
|
||||||
|
}
|
||||||
|
if (this.dispatcherRef) {
|
||||||
|
dis.unregister(this.dispatcherRef);
|
||||||
|
this.dispatcherRef = null;
|
||||||
}
|
}
|
||||||
this.dismissed.clear();
|
this.dismissed.clear();
|
||||||
this.dismissedThisDeviceToast = false;
|
this.dismissedThisDeviceToast = false;
|
||||||
|
@ -158,6 +170,21 @@ export default class DeviceListener {
|
||||||
if (state === 'PREPARED' && prevState === null) this._recheck();
|
if (state === 'PREPARED' && prevState === null) this._recheck();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
_onRoomStateEvents = (ev: MatrixEvent) => {
|
||||||
|
if (ev.getType() !== "m.room.encryption") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If a room changes to encrypted, re-check as it may be our first
|
||||||
|
// encrypted room. This also catches encrypted room creation as well.
|
||||||
|
this._recheck();
|
||||||
|
};
|
||||||
|
|
||||||
|
_onAction = ({ action }) => {
|
||||||
|
if (action !== "on_logged_in") return;
|
||||||
|
this._recheck();
|
||||||
|
};
|
||||||
|
|
||||||
// The server doesn't tell us when key backup is set up, so we poll
|
// The server doesn't tell us when key backup is set up, so we poll
|
||||||
// & cache the result
|
// & cache the result
|
||||||
async _getKeyBackupInfo() {
|
async _getKeyBackupInfo() {
|
||||||
|
@ -170,9 +197,10 @@ export default class DeviceListener {
|
||||||
}
|
}
|
||||||
|
|
||||||
private shouldShowSetupEncryptionToast() {
|
private shouldShowSetupEncryptionToast() {
|
||||||
// In a default configuration, show the toasts. If the well-known config causes e2ee default to be false
|
// If we're in the middle of a secret storage operation, we're likely
|
||||||
// then do not show the toasts until user is in at least one encrypted room.
|
// modifying the state involved here, so don't add new toasts to setup.
|
||||||
if (privateShouldBeEncrypted()) return true;
|
if (isSecretStorageBeingAccessed()) return false;
|
||||||
|
// Show setup toasts once the user is in at least one encrypted room.
|
||||||
const cli = MatrixClientPeg.get();
|
const cli = MatrixClientPeg.get();
|
||||||
return cli && cli.getRooms().some(r => cli.isRoomEncrypted(r.roomId));
|
return cli && cli.getRooms().some(r => cli.isRoomEncrypted(r.roomId));
|
||||||
}
|
}
|
||||||
|
@ -189,15 +217,20 @@ export default class DeviceListener {
|
||||||
if (!cli.isInitialSyncComplete()) return;
|
if (!cli.isInitialSyncComplete()) return;
|
||||||
|
|
||||||
const crossSigningReady = await cli.isCrossSigningReady();
|
const crossSigningReady = await cli.isCrossSigningReady();
|
||||||
|
const secretStorageReady = await cli.isSecretStorageReady();
|
||||||
|
const allSystemsReady = crossSigningReady && secretStorageReady;
|
||||||
|
|
||||||
if (this.dismissedThisDeviceToast || crossSigningReady) {
|
if (this.dismissedThisDeviceToast || allSystemsReady) {
|
||||||
hideSetupEncryptionToast();
|
hideSetupEncryptionToast();
|
||||||
} else if (this.shouldShowSetupEncryptionToast()) {
|
} else if (this.shouldShowSetupEncryptionToast()) {
|
||||||
// make sure our keys are finished downloading
|
// make sure our keys are finished downloading
|
||||||
await cli.downloadKeys([cli.getUserId()]);
|
await cli.downloadKeys([cli.getUserId()]);
|
||||||
// cross signing isn't enabled - nag to enable it
|
// cross signing isn't enabled - nag to enable it
|
||||||
// There are 3 different toasts for:
|
// There are 3 different toasts for:
|
||||||
if (cli.getStoredCrossSigningForUser(cli.getUserId())) {
|
if (
|
||||||
|
!cli.getCrossSigningId() &&
|
||||||
|
cli.getStoredCrossSigningForUser(cli.getUserId())
|
||||||
|
) {
|
||||||
// Cross-signing on account but this device doesn't trust the master key (verify this session)
|
// Cross-signing on account but this device doesn't trust the master key (verify this session)
|
||||||
showSetupEncryptionToast(SetupKind.VERIFY_THIS_SESSION);
|
showSetupEncryptionToast(SetupKind.VERIFY_THIS_SESSION);
|
||||||
} else {
|
} else {
|
||||||
|
@ -207,7 +240,15 @@ export default class DeviceListener {
|
||||||
showSetupEncryptionToast(SetupKind.UPGRADE_ENCRYPTION);
|
showSetupEncryptionToast(SetupKind.UPGRADE_ENCRYPTION);
|
||||||
} else {
|
} else {
|
||||||
// No cross-signing or key backup on account (set up encryption)
|
// No cross-signing or key backup on account (set up encryption)
|
||||||
showSetupEncryptionToast(SetupKind.SET_UP_ENCRYPTION);
|
await cli.waitForClientWellKnown();
|
||||||
|
if (isSecureBackupRequired() && isLoggedIn()) {
|
||||||
|
// If we're meant to set up, and Secure Backup is required,
|
||||||
|
// trigger the flow directly without a toast once logged in.
|
||||||
|
hideSetupEncryptionToast();
|
||||||
|
accessSecretStorage();
|
||||||
|
} else {
|
||||||
|
showSetupEncryptionToast(SetupKind.SET_UP_ENCRYPTION);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -339,33 +339,9 @@ class HtmlHighlighter extends BaseHighlighter<string> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class TextHighlighter extends BaseHighlighter<React.ReactNode> {
|
|
||||||
private key = 0;
|
|
||||||
|
|
||||||
/* create a <span> node to hold the given content
|
|
||||||
*
|
|
||||||
* snippet: content of the span
|
|
||||||
* highlight: true to highlight as a search match
|
|
||||||
*
|
|
||||||
* returns a React node
|
|
||||||
*/
|
|
||||||
protected processSnippet(snippet: string, highlight: boolean): React.ReactNode {
|
|
||||||
const key = this.key++;
|
|
||||||
|
|
||||||
let node = <span key={key} className={highlight ? this.highlightClass : null}>
|
|
||||||
{ snippet }
|
|
||||||
</span>;
|
|
||||||
|
|
||||||
if (highlight && this.highlightLink) {
|
|
||||||
node = <a key={key} href={this.highlightLink}>{ node }</a>;
|
|
||||||
}
|
|
||||||
|
|
||||||
return node;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
interface IContent {
|
interface IContent {
|
||||||
format?: string;
|
format?: string;
|
||||||
|
// eslint-disable-next-line camelcase
|
||||||
formatted_body?: string;
|
formatted_body?: string;
|
||||||
body: string;
|
body: string;
|
||||||
}
|
}
|
||||||
|
@ -474,8 +450,13 @@ export function bodyToHtml(content: IContent, highlights: string[], opts: IOpts
|
||||||
});
|
});
|
||||||
|
|
||||||
return isDisplayedWithHtml ?
|
return isDisplayedWithHtml ?
|
||||||
<span key="body" ref={opts.ref} className={className} dangerouslySetInnerHTML={{ __html: safeBody }} dir="auto" /> :
|
<span
|
||||||
<span key="body" ref={opts.ref} className={className} dir="auto">{ strippedBody }</span>;
|
key="body"
|
||||||
|
ref={opts.ref}
|
||||||
|
className={className}
|
||||||
|
dangerouslySetInnerHTML={{ __html: safeBody }}
|
||||||
|
dir="auto"
|
||||||
|
/> : <span key="body" ref={opts.ref} className={className} dir="auto">{ strippedBody }</span>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -43,6 +43,7 @@ import DeviceListener from "./DeviceListener";
|
||||||
import {Jitsi} from "./widgets/Jitsi";
|
import {Jitsi} from "./widgets/Jitsi";
|
||||||
import {SSO_HOMESERVER_URL_KEY, SSO_ID_SERVER_URL_KEY} from "./BasePlatform";
|
import {SSO_HOMESERVER_URL_KEY, SSO_ID_SERVER_URL_KEY} from "./BasePlatform";
|
||||||
import {decodeBase64, encodeBase64} from "matrix-js-sdk/src/crypto/olmlib";
|
import {decodeBase64, encodeBase64} from "matrix-js-sdk/src/crypto/olmlib";
|
||||||
|
import ThreepidInviteStore from "./stores/ThreepidInviteStore";
|
||||||
|
|
||||||
const HOMESERVER_URL_KEY = "mx_hs_url";
|
const HOMESERVER_URL_KEY = "mx_hs_url";
|
||||||
const ID_SERVER_URL_KEY = "mx_is_url";
|
const ID_SERVER_URL_KEY = "mx_is_url";
|
||||||
|
@ -708,17 +709,30 @@ export async function onLoggedOut() {
|
||||||
// that can occur when components try to use a null client.
|
// that can occur when components try to use a null client.
|
||||||
dis.dispatch({action: 'on_logged_out'}, true);
|
dis.dispatch({action: 'on_logged_out'}, true);
|
||||||
stopMatrixClient();
|
stopMatrixClient();
|
||||||
await _clearStorage();
|
await _clearStorage({deleteEverything: true});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* @param {object} opts Options for how to clear storage.
|
||||||
* @returns {Promise} promise which resolves once the stores have been cleared
|
* @returns {Promise} promise which resolves once the stores have been cleared
|
||||||
*/
|
*/
|
||||||
async function _clearStorage() {
|
async function _clearStorage(opts: {deleteEverything: boolean}) {
|
||||||
Analytics.disable();
|
Analytics.disable();
|
||||||
|
|
||||||
if (window.localStorage) {
|
if (window.localStorage) {
|
||||||
|
// try to save any 3pid invites from being obliterated
|
||||||
|
const pendingInvites = ThreepidInviteStore.instance.getWireInvites();
|
||||||
|
|
||||||
window.localStorage.clear();
|
window.localStorage.clear();
|
||||||
|
|
||||||
|
// now restore those invites
|
||||||
|
if (!opts?.deleteEverything) {
|
||||||
|
pendingInvites.forEach(i => {
|
||||||
|
const roomId = i.roomId;
|
||||||
|
delete i.roomId; // delete to avoid confusing the store
|
||||||
|
ThreepidInviteStore.instance.storeInvite(roomId, i);
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (window.sessionStorage) {
|
if (window.sessionStorage) {
|
||||||
|
|
|
@ -15,7 +15,7 @@ limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import commonmark from 'commonmark';
|
import commonmark from 'commonmark';
|
||||||
import escape from 'lodash/escape';
|
import {escape} from "lodash";
|
||||||
|
|
||||||
const ALLOWED_HTML_TAGS = ['sub', 'sup', 'del', 'u'];
|
const ALLOWED_HTML_TAGS = ['sub', 'sup', 'del', 'u'];
|
||||||
|
|
||||||
|
|
|
@ -31,7 +31,7 @@ import {verificationMethods} from 'matrix-js-sdk/src/crypto';
|
||||||
import MatrixClientBackedSettingsHandler from "./settings/handlers/MatrixClientBackedSettingsHandler";
|
import MatrixClientBackedSettingsHandler from "./settings/handlers/MatrixClientBackedSettingsHandler";
|
||||||
import * as StorageManager from './utils/StorageManager';
|
import * as StorageManager from './utils/StorageManager';
|
||||||
import IdentityAuthClient from './IdentityAuthClient';
|
import IdentityAuthClient from './IdentityAuthClient';
|
||||||
import { cacheDehydrationKey, crossSigningCallbacks } from './CrossSigningManager';
|
import { cacheDehydrationKey, crossSigningCallbacks } from './SecurityManager';
|
||||||
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";
|
||||||
|
|
||||||
export interface IMatrixClientCreds {
|
export interface IMatrixClientCreds {
|
||||||
|
|
|
@ -151,7 +151,7 @@ export class ModalManager {
|
||||||
prom: Promise<React.ComponentType>,
|
prom: Promise<React.ComponentType>,
|
||||||
props?: IProps<T>,
|
props?: IProps<T>,
|
||||||
className?: string,
|
className?: string,
|
||||||
options?: IOptions<T>
|
options?: IOptions<T>,
|
||||||
) {
|
) {
|
||||||
const modal: IModal<T> = {
|
const modal: IModal<T> = {
|
||||||
onFinished: props ? props.onFinished : null,
|
onFinished: props ? props.onFinished : null,
|
||||||
|
@ -182,7 +182,7 @@ export class ModalManager {
|
||||||
|
|
||||||
private getCloseFn<T extends any[]>(
|
private getCloseFn<T extends any[]>(
|
||||||
modal: IModal<T>,
|
modal: IModal<T>,
|
||||||
props: IProps<T>
|
props: IProps<T>,
|
||||||
): [IHandle<T>["close"], IHandle<T>["finished"]] {
|
): [IHandle<T>["close"], IHandle<T>["finished"]] {
|
||||||
const deferred = defer<T>();
|
const deferred = defer<T>();
|
||||||
return [async (...args: T) => {
|
return [async (...args: T) => {
|
||||||
|
@ -264,7 +264,7 @@ export class ModalManager {
|
||||||
className?: string,
|
className?: string,
|
||||||
isPriorityModal = false,
|
isPriorityModal = false,
|
||||||
isStaticModal = false,
|
isStaticModal = false,
|
||||||
options: IOptions<T> = {}
|
options: IOptions<T> = {},
|
||||||
): IHandle<T> {
|
): IHandle<T> {
|
||||||
const {modal, closeDialog, onFinishedProm} = this.buildModal<T>(prom, props, className, options);
|
const {modal, closeDialog, onFinishedProm} = this.buildModal<T>(prom, props, className, options);
|
||||||
if (isPriorityModal) {
|
if (isPriorityModal) {
|
||||||
|
@ -287,7 +287,7 @@ export class ModalManager {
|
||||||
private appendDialogAsync<T extends any[]>(
|
private appendDialogAsync<T extends any[]>(
|
||||||
prom: Promise<React.ComponentType>,
|
prom: Promise<React.ComponentType>,
|
||||||
props?: IProps<T>,
|
props?: IProps<T>,
|
||||||
className?: string
|
className?: string,
|
||||||
): IHandle<T> {
|
): IHandle<T> {
|
||||||
const {modal, closeDialog, onFinishedProm} = this.buildModal<T>(prom, props, className, {});
|
const {modal, closeDialog, onFinishedProm} = this.buildModal<T>(prom, props, className, {});
|
||||||
|
|
||||||
|
|
|
@ -33,6 +33,7 @@ import Modal from './Modal';
|
||||||
import SettingsStore from "./settings/SettingsStore";
|
import SettingsStore from "./settings/SettingsStore";
|
||||||
import { hideToast as hideNotificationsToast } from "./toasts/DesktopNotificationsToast";
|
import { hideToast as hideNotificationsToast } from "./toasts/DesktopNotificationsToast";
|
||||||
import {SettingLevel} from "./settings/SettingLevel";
|
import {SettingLevel} from "./settings/SettingLevel";
|
||||||
|
import {isPushNotifyDisabled} from "./settings/controllers/NotificationControllers";
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Dispatches:
|
* Dispatches:
|
||||||
|
@ -258,7 +259,7 @@ export const Notifier = {
|
||||||
}
|
}
|
||||||
// set the notifications_hidden flag, as the user has knowingly interacted
|
// set the notifications_hidden flag, as the user has knowingly interacted
|
||||||
// with the setting we shouldn't nag them any further
|
// with the setting we shouldn't nag them any further
|
||||||
this.setToolbarHidden(true);
|
this.setPromptHidden(true);
|
||||||
},
|
},
|
||||||
|
|
||||||
isEnabled: function() {
|
isEnabled: function() {
|
||||||
|
@ -283,7 +284,7 @@ export const Notifier = {
|
||||||
return SettingsStore.getValue("audioNotificationsEnabled");
|
return SettingsStore.getValue("audioNotificationsEnabled");
|
||||||
},
|
},
|
||||||
|
|
||||||
setToolbarHidden: function(hidden: boolean, persistent = true) {
|
setPromptHidden: function(hidden: boolean, persistent = true) {
|
||||||
this.toolbarHidden = hidden;
|
this.toolbarHidden = hidden;
|
||||||
|
|
||||||
Analytics.trackEvent('Notifier', 'Set Toolbar Hidden', hidden);
|
Analytics.trackEvent('Notifier', 'Set Toolbar Hidden', hidden);
|
||||||
|
@ -296,17 +297,17 @@ export const Notifier = {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
shouldShowToolbar: function() {
|
shouldShowPrompt: function() {
|
||||||
const client = MatrixClientPeg.get();
|
const client = MatrixClientPeg.get();
|
||||||
if (!client) {
|
if (!client) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
const isGuest = client.isGuest();
|
const isGuest = client.isGuest();
|
||||||
return !isGuest && this.supportsDesktopNotifications() &&
|
return !isGuest && this.supportsDesktopNotifications() && !isPushNotifyDisabled() &&
|
||||||
!this.isEnabled() && !this._isToolbarHidden();
|
!this.isEnabled() && !this._isPromptHidden();
|
||||||
},
|
},
|
||||||
|
|
||||||
_isToolbarHidden: function() {
|
_isPromptHidden: function() {
|
||||||
// Check localStorage for any such meta data
|
// Check localStorage for any such meta data
|
||||||
if (global.localStorage) {
|
if (global.localStorage) {
|
||||||
return global.localStorage.getItem("notifications_hidden") === "true";
|
return global.localStorage.getItem("notifications_hidden") === "true";
|
||||||
|
|
|
@ -23,6 +23,8 @@ import Modal from './Modal';
|
||||||
import * as sdk from './';
|
import * as sdk from './';
|
||||||
import { _t } from './languageHandler';
|
import { _t } from './languageHandler';
|
||||||
import {KIND_DM, KIND_INVITE} from "./components/views/dialogs/InviteDialog";
|
import {KIND_DM, KIND_INVITE} from "./components/views/dialogs/InviteDialog";
|
||||||
|
import CommunityPrototypeInviteDialog from "./components/views/dialogs/CommunityPrototypeInviteDialog";
|
||||||
|
import {CommunityPrototypeStore} from "./stores/CommunityPrototypeStore";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Invites multiple addresses to a room
|
* Invites multiple addresses to a room
|
||||||
|
@ -56,6 +58,23 @@ export function showRoomInviteDialog(roomId) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function showCommunityRoomInviteDialog(roomId, communityName) {
|
||||||
|
Modal.createTrackedDialog(
|
||||||
|
'Invite Users to Community', '', CommunityPrototypeInviteDialog, {communityName, roomId},
|
||||||
|
/*className=*/null, /*isPriority=*/false, /*isStatic=*/true,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function showCommunityInviteDialog(communityId) {
|
||||||
|
const chat = CommunityPrototypeStore.instance.getGeneralChat(communityId);
|
||||||
|
if (chat) {
|
||||||
|
const name = CommunityPrototypeStore.instance.getCommunityName(communityId);
|
||||||
|
showCommunityRoomInviteDialog(chat.roomId, name);
|
||||||
|
} else {
|
||||||
|
throw new Error("Failed to locate appropriate room to start an invite in");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if the given MatrixEvent is a valid 3rd party user invite.
|
* Checks if the given MatrixEvent is a valid 3rd party user invite.
|
||||||
* @param {MatrixEvent} event The event to check
|
* @param {MatrixEvent} event The event to check
|
||||||
|
@ -77,7 +96,7 @@ export function isValid3pidInvite(event) {
|
||||||
export function inviteUsersToRoom(roomId, userIds) {
|
export function inviteUsersToRoom(roomId, userIds) {
|
||||||
return inviteMultipleToRoom(roomId, userIds).then((result) => {
|
return inviteMultipleToRoom(roomId, userIds).then((result) => {
|
||||||
const room = MatrixClientPeg.get().getRoom(roomId);
|
const room = MatrixClientPeg.get().getRoom(roomId);
|
||||||
return _showAnyInviteErrors(result.states, room, result.inviter);
|
showAnyInviteErrors(result.states, room, result.inviter);
|
||||||
}).catch((err) => {
|
}).catch((err) => {
|
||||||
console.error(err.stack);
|
console.error(err.stack);
|
||||||
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||||
|
@ -88,7 +107,7 @@ export function inviteUsersToRoom(roomId, userIds) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function _showAnyInviteErrors(addrs, room, inviter) {
|
export function showAnyInviteErrors(addrs, room, inviter) {
|
||||||
// Show user any errors
|
// Show user any errors
|
||||||
const failedUsers = Object.keys(addrs).filter(a => addrs[a] === 'error');
|
const failedUsers = Object.keys(addrs).filter(a => addrs[a] === 'error');
|
||||||
if (failedUsers.length === 1 && inviter.fatal) {
|
if (failedUsers.length === 1 && inviter.fatal) {
|
||||||
|
@ -100,6 +119,7 @@ function _showAnyInviteErrors(addrs, room, inviter) {
|
||||||
title: _t("Failed to invite users to the room:", {roomName: room.name}),
|
title: _t("Failed to invite users to the room:", {roomName: room.name}),
|
||||||
description: inviter.getErrorText(failedUsers[0]),
|
description: inviter.getErrorText(failedUsers[0]),
|
||||||
});
|
});
|
||||||
|
return false;
|
||||||
} else {
|
} else {
|
||||||
const errorList = [];
|
const errorList = [];
|
||||||
for (const addr of failedUsers) {
|
for (const addr of failedUsers) {
|
||||||
|
@ -118,8 +138,9 @@ function _showAnyInviteErrors(addrs, room, inviter) {
|
||||||
title: _t("Failed to invite the following users to the %(roomName)s room:", {roomName: room.name}),
|
title: _t("Failed to invite the following users to the %(roomName)s room:", {roomName: room.name}),
|
||||||
description,
|
description,
|
||||||
});
|
});
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return addrs;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
Copyright 2019 The Matrix.org Foundation C.I.C.
|
Copyright 2019, 2020 The Matrix.org Foundation C.I.C.
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
|
@ -21,6 +21,9 @@ import { deriveKey } from 'matrix-js-sdk/src/crypto/key_passphrase';
|
||||||
import { decodeRecoveryKey } from 'matrix-js-sdk/src/crypto/recoverykey';
|
import { decodeRecoveryKey } from 'matrix-js-sdk/src/crypto/recoverykey';
|
||||||
import { _t } from './languageHandler';
|
import { _t } from './languageHandler';
|
||||||
import {encodeBase64} from "matrix-js-sdk/src/crypto/olmlib";
|
import {encodeBase64} from "matrix-js-sdk/src/crypto/olmlib";
|
||||||
|
import { isSecureBackupRequired } from './utils/WellKnownUtils';
|
||||||
|
import AccessSecretStorageDialog from './components/views/dialogs/security/AccessSecretStorageDialog';
|
||||||
|
import RestoreKeyBackupDialog from './components/views/dialogs/security/RestoreKeyBackupDialog';
|
||||||
|
|
||||||
// This stores the secret storage private keys in memory for the JS SDK. This is
|
// This stores the secret storage private keys in memory for the JS SDK. This is
|
||||||
// only meant to act as a cache to avoid prompting the user multiple times
|
// only meant to act as a cache to avoid prompting the user multiple times
|
||||||
|
@ -44,6 +47,17 @@ function isCachingAllowed() {
|
||||||
return secretStorageBeingAccessed;
|
return secretStorageBeingAccessed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This can be used by other components to check if secret storage access is in
|
||||||
|
* progress, so that we can e.g. avoid intermittently showing toasts during
|
||||||
|
* secret storage setup.
|
||||||
|
*
|
||||||
|
* @returns {bool}
|
||||||
|
*/
|
||||||
|
export function isSecretStorageBeingAccessed() {
|
||||||
|
return secretStorageBeingAccessed;
|
||||||
|
}
|
||||||
|
|
||||||
export class AccessCancelledError extends Error {
|
export class AccessCancelledError extends Error {
|
||||||
constructor() {
|
constructor() {
|
||||||
super("Secret storage access canceled");
|
super("Secret storage access canceled");
|
||||||
|
@ -67,11 +81,11 @@ async function getSecretStorageKey({ keys: keyInfos }, ssssItemName) {
|
||||||
if (keyInfoEntries.length > 1) {
|
if (keyInfoEntries.length > 1) {
|
||||||
throw new Error("Multiple storage key requests not implemented");
|
throw new Error("Multiple storage key requests not implemented");
|
||||||
}
|
}
|
||||||
const [name, info] = keyInfoEntries[0];
|
const [keyId, keyInfo] = keyInfoEntries[0];
|
||||||
|
|
||||||
// Check the in-memory cache
|
// Check the in-memory cache
|
||||||
if (isCachingAllowed() && secretStorageKeys[name]) {
|
if (isCachingAllowed() && secretStorageKeys[keyId]) {
|
||||||
return [name, secretStorageKeys[name]];
|
return [keyId, secretStorageKeys[keyId]];
|
||||||
}
|
}
|
||||||
|
|
||||||
// if we dehydrated a device, see if that key works for SSSS
|
// if we dehydrated a device, see if that key works for SSSS
|
||||||
|
@ -94,23 +108,21 @@ async function getSecretStorageKey({ keys: keyInfos }, ssssItemName) {
|
||||||
if (passphrase) {
|
if (passphrase) {
|
||||||
return deriveKey(
|
return deriveKey(
|
||||||
passphrase,
|
passphrase,
|
||||||
info.passphrase.salt,
|
keyInfo.passphrase.salt,
|
||||||
info.passphrase.iterations,
|
keyInfo.passphrase.iterations,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
return decodeRecoveryKey(recoveryKey);
|
return decodeRecoveryKey(recoveryKey);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const AccessSecretStorageDialog =
|
|
||||||
sdk.getComponent("dialogs.secretstorage.AccessSecretStorageDialog");
|
|
||||||
const { finished } = Modal.createTrackedDialog("Access Secret Storage dialog", "",
|
const { finished } = Modal.createTrackedDialog("Access Secret Storage dialog", "",
|
||||||
AccessSecretStorageDialog,
|
AccessSecretStorageDialog,
|
||||||
/* props= */
|
/* props= */
|
||||||
{
|
{
|
||||||
keyInfo: info,
|
keyInfo,
|
||||||
checkPrivateKey: async (input) => {
|
checkPrivateKey: async (input) => {
|
||||||
const key = await inputToKey(input);
|
const key = await inputToKey(input);
|
||||||
return await MatrixClientPeg.get().checkSecretStorageKey(key, info);
|
return await MatrixClientPeg.get().checkSecretStorageKey(key, keyInfo);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
/* className= */ null,
|
/* className= */ null,
|
||||||
|
@ -132,11 +144,15 @@ async function getSecretStorageKey({ keys: keyInfos }, ssssItemName) {
|
||||||
const key = await inputToKey(input);
|
const key = await inputToKey(input);
|
||||||
|
|
||||||
// Save to cache to avoid future prompts in the current session
|
// Save to cache to avoid future prompts in the current session
|
||||||
if (isCachingAllowed()) {
|
cacheSecretStorageKey(keyId, key);
|
||||||
secretStorageKeys[name] = key;
|
|
||||||
}
|
|
||||||
|
|
||||||
return [name, key];
|
return [keyId, key];
|
||||||
|
}
|
||||||
|
|
||||||
|
function cacheSecretStorageKey(keyId, key) {
|
||||||
|
if (isCachingAllowed()) {
|
||||||
|
secretStorageKeys[keyId] = key;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const onSecretRequested = async function({
|
const onSecretRequested = async function({
|
||||||
|
@ -152,7 +168,7 @@ const onSecretRequested = async function({
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!deviceTrust || !deviceTrust.isVerified()) {
|
if (!deviceTrust || !deviceTrust.isVerified()) {
|
||||||
console.log(`CrossSigningManager: Ignoring request from untrusted device ${deviceId}`);
|
console.log(`Ignoring secret request from untrusted device ${deviceId}`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
|
@ -184,13 +200,13 @@ const onSecretRequested = async function({
|
||||||
|
|
||||||
export const crossSigningCallbacks = {
|
export const crossSigningCallbacks = {
|
||||||
getSecretStorageKey,
|
getSecretStorageKey,
|
||||||
|
cacheSecretStorageKey,
|
||||||
onSecretRequested,
|
onSecretRequested,
|
||||||
};
|
};
|
||||||
|
|
||||||
export async function promptForBackupPassphrase() {
|
export async function promptForBackupPassphrase() {
|
||||||
let key;
|
let key;
|
||||||
|
|
||||||
const RestoreKeyBackupDialog = sdk.getComponent('dialogs.keybackup.RestoreKeyBackupDialog');
|
|
||||||
const { finished } = Modal.createTrackedDialog('Restore Backup', '', RestoreKeyBackupDialog, {
|
const { finished } = Modal.createTrackedDialog('Restore Backup', '', RestoreKeyBackupDialog, {
|
||||||
showSummary: false, keyCallback: k => key = k,
|
showSummary: false, keyCallback: k => key = k,
|
||||||
}, null, /* priority = */ false, /* static = */ true);
|
}, null, /* priority = */ false, /* static = */ true);
|
||||||
|
@ -230,11 +246,22 @@ export async function accessSecretStorage(func = async () => { }, forceReset = f
|
||||||
// This dialog calls bootstrap itself after guiding the user through
|
// This dialog calls bootstrap itself after guiding the user through
|
||||||
// passphrase creation.
|
// passphrase creation.
|
||||||
const { finished } = Modal.createTrackedDialogAsync('Create Secret Storage dialog', '',
|
const { finished } = Modal.createTrackedDialogAsync('Create Secret Storage dialog', '',
|
||||||
import("./async-components/views/dialogs/secretstorage/CreateSecretStorageDialog"),
|
import("./async-components/views/dialogs/security/CreateSecretStorageDialog"),
|
||||||
{
|
{
|
||||||
force: forceReset,
|
forceReset,
|
||||||
|
},
|
||||||
|
null,
|
||||||
|
/* priority = */ false,
|
||||||
|
/* static = */ true,
|
||||||
|
/* options = */ {
|
||||||
|
onBeforeClose(reason) {
|
||||||
|
// If Secure Backup is required, you cannot leave the modal.
|
||||||
|
if (reason === "backgroundClick") {
|
||||||
|
return !isSecureBackupRequired();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
null, /* priority = */ false, /* static = */ true,
|
|
||||||
);
|
);
|
||||||
const [confirmed] = await finished;
|
const [confirmed] = await finished;
|
||||||
if (!confirmed) {
|
if (!confirmed) {
|
||||||
|
@ -242,13 +269,13 @@ export async function accessSecretStorage(func = async () => { }, forceReset = f
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const InteractiveAuthDialog = sdk.getComponent("dialogs.InteractiveAuthDialog");
|
const InteractiveAuthDialog = sdk.getComponent("dialogs.InteractiveAuthDialog");
|
||||||
await cli.bootstrapSecretStorage({
|
await cli.bootstrapCrossSigning({
|
||||||
authUploadDeviceSigningKeys: async (makeRequest) => {
|
authUploadDeviceSigningKeys: async (makeRequest) => {
|
||||||
const { finished } = Modal.createTrackedDialog(
|
const { finished } = Modal.createTrackedDialog(
|
||||||
'Cross-signing keys dialog', '', InteractiveAuthDialog,
|
'Cross-signing keys dialog', '', InteractiveAuthDialog,
|
||||||
{
|
{
|
||||||
title: _t("Setting up keys"),
|
title: _t("Setting up keys"),
|
||||||
matrixClient: MatrixClientPeg.get(),
|
matrixClient: cli,
|
||||||
makeRequest,
|
makeRequest,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
@ -257,7 +284,9 @@ export async function accessSecretStorage(func = async () => { }, forceReset = f
|
||||||
throw new Error("Cross-signing key upload auth canceled");
|
throw new Error("Cross-signing key upload auth canceled");
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
getBackupPassphrase: promptForBackupPassphrase,
|
});
|
||||||
|
await cli.bootstrapSecretStorage({
|
||||||
|
getKeyBackupPassphrase: promptForBackupPassphrase,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,7 +15,7 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import _clamp from 'lodash/clamp';
|
import {clamp} from "lodash";
|
||||||
|
|
||||||
export default class SendHistoryManager {
|
export default class SendHistoryManager {
|
||||||
history: Array<HistoryItem> = [];
|
history: Array<HistoryItem> = [];
|
||||||
|
@ -54,7 +54,7 @@ export default class SendHistoryManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
getItem(offset: number): ?HistoryItem {
|
getItem(offset: number): ?HistoryItem {
|
||||||
this.currentIndex = _clamp(this.currentIndex + offset, 0, this.history.length - 1);
|
this.currentIndex = clamp(this.currentIndex + offset, 0, this.history.length - 1);
|
||||||
return this.history[this.currentIndex];
|
return this.history[this.currentIndex];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,12 +38,14 @@ import {inviteUsersToRoom} from "./RoomInvite";
|
||||||
import { WidgetType } from "./widgets/WidgetType";
|
import { WidgetType } from "./widgets/WidgetType";
|
||||||
import { Jitsi } from "./widgets/Jitsi";
|
import { Jitsi } from "./widgets/Jitsi";
|
||||||
import { parseFragment as parseHtml } from "parse5";
|
import { parseFragment as parseHtml } from "parse5";
|
||||||
import sendBugReport from "./rageshake/submit-rageshake";
|
import BugReportDialog from "./components/views/dialogs/BugReportDialog";
|
||||||
import SdkConfig from "./SdkConfig";
|
|
||||||
import { ensureDMExists } from "./createRoom";
|
import { ensureDMExists } from "./createRoom";
|
||||||
import { ViewUserPayload } from "./dispatcher/payloads/ViewUserPayload";
|
import { ViewUserPayload } from "./dispatcher/payloads/ViewUserPayload";
|
||||||
import { Action } from "./dispatcher/actions";
|
import { Action } from "./dispatcher/actions";
|
||||||
import { EffectiveMembership, getEffectiveMembership } from "./utils/membership";
|
import { EffectiveMembership, getEffectiveMembership, leaveRoomBehaviour } from "./utils/membership";
|
||||||
|
import SdkConfig from "./SdkConfig";
|
||||||
|
import SettingsStore from "./settings/SettingsStore";
|
||||||
|
import {UIFeature} from "./settings/UIFeature";
|
||||||
|
|
||||||
// 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 {
|
||||||
|
@ -88,6 +90,7 @@ interface ICommandOpts {
|
||||||
runFn?: RunFn;
|
runFn?: RunFn;
|
||||||
category: string;
|
category: string;
|
||||||
hideCompletionAfterSpace?: boolean;
|
hideCompletionAfterSpace?: boolean;
|
||||||
|
isEnabled?(): boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Command {
|
export class Command {
|
||||||
|
@ -98,6 +101,7 @@ export class Command {
|
||||||
runFn: undefined | RunFn;
|
runFn: undefined | RunFn;
|
||||||
category: string;
|
category: string;
|
||||||
hideCompletionAfterSpace: boolean;
|
hideCompletionAfterSpace: boolean;
|
||||||
|
_isEnabled?: () => boolean;
|
||||||
|
|
||||||
constructor(opts: ICommandOpts) {
|
constructor(opts: ICommandOpts) {
|
||||||
this.command = opts.command;
|
this.command = opts.command;
|
||||||
|
@ -107,6 +111,7 @@ export class Command {
|
||||||
this.runFn = opts.runFn;
|
this.runFn = opts.runFn;
|
||||||
this.category = opts.category || CommandCategories.other;
|
this.category = opts.category || CommandCategories.other;
|
||||||
this.hideCompletionAfterSpace = opts.hideCompletionAfterSpace || false;
|
this.hideCompletionAfterSpace = opts.hideCompletionAfterSpace || false;
|
||||||
|
this._isEnabled = opts.isEnabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
getCommand() {
|
getCommand() {
|
||||||
|
@ -126,6 +131,10 @@ export class Command {
|
||||||
getUsage() {
|
getUsage() {
|
||||||
return _t('Usage') + ': ' + this.getCommandWithArgs();
|
return _t('Usage') + ': ' + this.getCommandWithArgs();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isEnabled() {
|
||||||
|
return this._isEnabled ? this._isEnabled() : true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function reject(error) {
|
function reject(error) {
|
||||||
|
@ -154,6 +163,19 @@ export const Commands = [
|
||||||
},
|
},
|
||||||
category: CommandCategories.messages,
|
category: CommandCategories.messages,
|
||||||
}),
|
}),
|
||||||
|
new Command({
|
||||||
|
command: 'lenny',
|
||||||
|
args: '<message>',
|
||||||
|
description: _td('Prepends ( ͡° ͜ʖ ͡°) to a plain-text message'),
|
||||||
|
runFn: function(roomId, args) {
|
||||||
|
let message = '( ͡° ͜ʖ ͡°)';
|
||||||
|
if (args) {
|
||||||
|
message = message + ' ' + args;
|
||||||
|
}
|
||||||
|
return success(MatrixClientPeg.get().sendTextMessage(roomId, message));
|
||||||
|
},
|
||||||
|
category: CommandCategories.messages,
|
||||||
|
}),
|
||||||
new Command({
|
new Command({
|
||||||
command: 'plain',
|
command: 'plain',
|
||||||
args: '<message>',
|
args: '<message>',
|
||||||
|
@ -601,11 +623,7 @@ export const Commands = [
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!targetRoomId) targetRoomId = roomId;
|
if (!targetRoomId) targetRoomId = roomId;
|
||||||
return success(
|
return success(leaveRoomBehaviour(targetRoomId));
|
||||||
cli.leaveRoomChain(targetRoomId).then(function() {
|
|
||||||
dis.dispatch({action: 'view_next_room'});
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
category: CommandCategories.actions,
|
category: CommandCategories.actions,
|
||||||
}),
|
}),
|
||||||
|
@ -781,6 +799,7 @@ export const Commands = [
|
||||||
command: 'addwidget',
|
command: 'addwidget',
|
||||||
args: '<url | embed code | Jitsi url>',
|
args: '<url | embed code | Jitsi url>',
|
||||||
description: _td('Adds a custom widget by URL to the room'),
|
description: _td('Adds a custom widget by URL to the room'),
|
||||||
|
isEnabled: () => SettingsStore.getValue(UIFeature.Widgets),
|
||||||
runFn: function(roomId, widgetUrl) {
|
runFn: function(roomId, widgetUrl) {
|
||||||
if (!widgetUrl) {
|
if (!widgetUrl) {
|
||||||
return reject(_t("Please supply a widget URL or embed code"));
|
return reject(_t("Please supply a widget URL or embed code"));
|
||||||
|
@ -864,12 +883,12 @@ export const Commands = [
|
||||||
_t('WARNING: KEY VERIFICATION FAILED! The signing key for %(userId)s and session' +
|
_t('WARNING: KEY VERIFICATION FAILED! The signing key for %(userId)s and session' +
|
||||||
' %(deviceId)s is "%(fprint)s" which does not match the provided key ' +
|
' %(deviceId)s is "%(fprint)s" which does not match the provided key ' +
|
||||||
'"%(fingerprint)s". This could mean your communications are being intercepted!',
|
'"%(fingerprint)s". This could mean your communications are being intercepted!',
|
||||||
{
|
{
|
||||||
fprint,
|
fprint,
|
||||||
userId,
|
userId,
|
||||||
deviceId,
|
deviceId,
|
||||||
fingerprint,
|
fingerprint,
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
await cli.setDeviceVerified(userId, deviceId, true);
|
await cli.setDeviceVerified(userId, deviceId, true);
|
||||||
|
@ -883,7 +902,7 @@ export const Commands = [
|
||||||
{
|
{
|
||||||
_t('The signing key you provided matches the signing key you received ' +
|
_t('The signing key you provided matches the signing key you received ' +
|
||||||
'from %(userId)s\'s session %(deviceId)s. Session marked as verified.',
|
'from %(userId)s\'s session %(deviceId)s. Session marked as verified.',
|
||||||
{userId, deviceId})
|
{userId, deviceId})
|
||||||
}
|
}
|
||||||
</p>
|
</p>
|
||||||
</div>,
|
</div>,
|
||||||
|
@ -963,19 +982,13 @@ export const Commands = [
|
||||||
command: "rageshake",
|
command: "rageshake",
|
||||||
aliases: ["bugreport"],
|
aliases: ["bugreport"],
|
||||||
description: _td("Send a bug report with logs"),
|
description: _td("Send a bug report with logs"),
|
||||||
|
isEnabled: () => !!SdkConfig.get().bug_report_endpoint_url,
|
||||||
args: "<description>",
|
args: "<description>",
|
||||||
runFn: function(roomId, args) {
|
runFn: function(roomId, args) {
|
||||||
return success(
|
return success(
|
||||||
sendBugReport(SdkConfig.get().bug_report_endpoint_url, {
|
Modal.createTrackedDialog('Slash Commands', 'Bug Report Dialog', BugReportDialog, {
|
||||||
userText: args,
|
initialText: args,
|
||||||
sendLogs: true,
|
}).finished,
|
||||||
}).then(() => {
|
|
||||||
const InfoDialog = sdk.getComponent('dialogs.InfoDialog');
|
|
||||||
Modal.createTrackedDialog('Slash Commands', 'Rageshake sent', InfoDialog, {
|
|
||||||
title: _t('Logs sent'),
|
|
||||||
description: _t('Thank you!'),
|
|
||||||
});
|
|
||||||
}),
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
category: CommandCategories.advanced,
|
category: CommandCategories.advanced,
|
||||||
|
@ -1047,7 +1060,7 @@ Commands.forEach(cmd => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
export function parseCommandString(input) {
|
export function parseCommandString(input: string) {
|
||||||
// trim any trailing whitespace, as it can confuse the parser for
|
// trim any trailing whitespace, as it can confuse the parser for
|
||||||
// IRC-style commands
|
// IRC-style commands
|
||||||
input = input.replace(/\s+$/, '');
|
input = input.replace(/\s+$/, '');
|
||||||
|
@ -1074,10 +1087,10 @@ export function parseCommandString(input) {
|
||||||
* processing the command, or 'promise' if a request was sent out.
|
* processing the command, or 'promise' if a request was sent out.
|
||||||
* Returns null if the input didn't match a command.
|
* Returns null if the input didn't match a command.
|
||||||
*/
|
*/
|
||||||
export function getCommand(roomId, input) {
|
export function getCommand(roomId: string, input: string) {
|
||||||
const {cmd, args} = parseCommandString(input);
|
const {cmd, args} = parseCommandString(input);
|
||||||
|
|
||||||
if (CommandMap.has(cmd)) {
|
if (CommandMap.has(cmd) && CommandMap.get(cmd).isEnabled()) {
|
||||||
return () => CommandMap.get(cmd).run(roomId, args, cmd);
|
return () => CommandMap.get(cmd).run(roomId, args, cmd);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,6 +19,7 @@ import { _t } from './languageHandler';
|
||||||
import * as Roles from './Roles';
|
import * as Roles from './Roles';
|
||||||
import {isValid3pidInvite} from "./RoomInvite";
|
import {isValid3pidInvite} from "./RoomInvite";
|
||||||
import SettingsStore from "./settings/SettingsStore";
|
import SettingsStore from "./settings/SettingsStore";
|
||||||
|
import {WidgetType} from "./widgets/WidgetType";
|
||||||
import {ALL_RULE_TYPES, ROOM_RULE_TYPES, SERVER_RULE_TYPES, USER_RULE_TYPES} from "./mjolnir/BanList";
|
import {ALL_RULE_TYPES, ROOM_RULE_TYPES, SERVER_RULE_TYPES, USER_RULE_TYPES} from "./mjolnir/BanList";
|
||||||
|
|
||||||
function textForMemberEvent(ev) {
|
function textForMemberEvent(ev) {
|
||||||
|
@ -475,6 +476,10 @@ function textForWidgetEvent(event) {
|
||||||
const {name: prevName, type: prevType, url: prevUrl} = event.getPrevContent();
|
const {name: prevName, type: prevType, url: prevUrl} = event.getPrevContent();
|
||||||
const {name, type, url} = event.getContent() || {};
|
const {name, type, url} = event.getContent() || {};
|
||||||
|
|
||||||
|
if (WidgetType.JITSI.matches(type) || WidgetType.JITSI.matches(prevType)) {
|
||||||
|
return textForJitsiWidgetEvent(event, senderName, url, prevUrl);
|
||||||
|
}
|
||||||
|
|
||||||
let widgetName = name || prevName || type || prevType || '';
|
let widgetName = name || prevName || type || prevType || '';
|
||||||
// Apply sentence case to widget name
|
// Apply sentence case to widget name
|
||||||
if (widgetName && widgetName.length > 0) {
|
if (widgetName && widgetName.length > 0) {
|
||||||
|
@ -500,6 +505,24 @@ function textForWidgetEvent(event) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function textForJitsiWidgetEvent(event, senderName, url, prevUrl) {
|
||||||
|
if (url) {
|
||||||
|
if (prevUrl) {
|
||||||
|
return _t('Group call modified by %(senderName)s', {
|
||||||
|
senderName,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
return _t('Group call started by %(senderName)s', {
|
||||||
|
senderName,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return _t('Group call ended by %(senderName)s', {
|
||||||
|
senderName,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function textForMjolnirEvent(event) {
|
function textForMjolnirEvent(event) {
|
||||||
const senderName = event.getSender();
|
const senderName = event.getSender();
|
||||||
const {entity: prevEntity} = event.getPrevContent();
|
const {entity: prevEntity} = event.getPrevContent();
|
||||||
|
|
|
@ -186,7 +186,14 @@ export default class WidgetMessaging {
|
||||||
isUserWidget: this.isUserWidget,
|
isUserWidget: this.isUserWidget,
|
||||||
|
|
||||||
onFinished: async (confirm) => {
|
onFinished: async (confirm) => {
|
||||||
const responseBody = {success: confirm};
|
const responseBody = {
|
||||||
|
// Legacy (early draft) fields
|
||||||
|
success: confirm,
|
||||||
|
|
||||||
|
// New style MSC1960 fields
|
||||||
|
state: confirm ? "allowed" : "blocked",
|
||||||
|
original_request_id: ev.requestId, // eslint-disable-line camelcase
|
||||||
|
};
|
||||||
if (confirm) {
|
if (confirm) {
|
||||||
const credentials = await MatrixClientPeg.get().getOpenIdToken();
|
const credentials = await MatrixClientPeg.get().getOpenIdToken();
|
||||||
Object.assign(responseBody, credentials);
|
Object.assign(responseBody, credentials);
|
||||||
|
|
|
@ -168,7 +168,7 @@ const shortcuts: Record<Categories, IShortcut[]> = {
|
||||||
key: Key.U,
|
key: Key.U,
|
||||||
}],
|
}],
|
||||||
description: _td("Upload a file"),
|
description: _td("Upload a file"),
|
||||||
}
|
},
|
||||||
],
|
],
|
||||||
|
|
||||||
[Categories.ROOM_LIST]: [
|
[Categories.ROOM_LIST]: [
|
||||||
|
|
|
@ -190,7 +190,7 @@ export const RovingTabIndexProvider: React.FC<IProps> = ({children, handleHomeEn
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
ev.stopPropagation();
|
ev.stopPropagation();
|
||||||
} else if (onKeyDown) {
|
} else if (onKeyDown) {
|
||||||
return onKeyDown(ev, state);
|
return onKeyDown(ev, context.state);
|
||||||
}
|
}
|
||||||
}, [context.state, onKeyDown, handleHomeEnd]);
|
}, [context.state, onKeyDown, handleHomeEnd]);
|
||||||
|
|
||||||
|
|
|
@ -30,6 +30,7 @@ const Toolbar: React.FC<IProps> = ({children, ...props}) => {
|
||||||
const target = ev.target as HTMLElement;
|
const target = ev.target as HTMLElement;
|
||||||
let handled = true;
|
let handled = true;
|
||||||
|
|
||||||
|
// HOME and END are handled by RovingTabIndexProvider
|
||||||
switch (ev.key) {
|
switch (ev.key) {
|
||||||
case Key.ARROW_UP:
|
case Key.ARROW_UP:
|
||||||
case Key.ARROW_DOWN:
|
case Key.ARROW_DOWN:
|
||||||
|
@ -47,8 +48,6 @@ const Toolbar: React.FC<IProps> = ({children, ...props}) => {
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
// HOME and END are handled by RovingTabIndexProvider
|
|
||||||
|
|
||||||
default:
|
default:
|
||||||
handled = false;
|
handled = false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,7 +20,7 @@ import React from "react";
|
||||||
|
|
||||||
import AccessibleTooltipButton from "../../components/views/elements/AccessibleTooltipButton";
|
import AccessibleTooltipButton from "../../components/views/elements/AccessibleTooltipButton";
|
||||||
|
|
||||||
interface IProps extends React.ComponentProps<typeof AccessibleTooltipButton> {
|
interface IProps extends React.ComponentProps<typeof AccessibleTooltipButton> {
|
||||||
// whether or not the context menu is currently open
|
// whether or not the context menu is currently open
|
||||||
isExpanded: boolean;
|
isExpanded: boolean;
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,8 +26,9 @@ interface IProps extends React.ComponentProps<typeof AccessibleButton> {
|
||||||
|
|
||||||
// Semantic component for representing a role=menuitem
|
// Semantic component for representing a role=menuitem
|
||||||
export const MenuItem: React.FC<IProps> = ({children, label, ...props}) => {
|
export const MenuItem: React.FC<IProps> = ({children, label, ...props}) => {
|
||||||
|
const ariaLabel = props["aria-label"] || label;
|
||||||
return (
|
return (
|
||||||
<AccessibleButton {...props} role="menuitem" tabIndex={-1} aria-label={label}>
|
<AccessibleButton {...props} role="menuitem" tabIndex={-1} aria-label={ariaLabel}>
|
||||||
{ children }
|
{ children }
|
||||||
</AccessibleButton>
|
</AccessibleButton>
|
||||||
);
|
);
|
||||||
|
|
|
@ -20,7 +20,8 @@ import AccessibleTooltipButton from "../../components/views/elements/AccessibleT
|
||||||
import {useRovingTabIndex} from "../RovingTabIndex";
|
import {useRovingTabIndex} from "../RovingTabIndex";
|
||||||
import {Ref} from "./types";
|
import {Ref} from "./types";
|
||||||
|
|
||||||
interface IProps extends Omit<React.ComponentProps<typeof AccessibleTooltipButton>, "onFocus" | "inputRef" | "tabIndex"> {
|
type ATBProps = React.ComponentProps<typeof AccessibleTooltipButton>;
|
||||||
|
interface IProps extends Omit<ATBProps, "onFocus" | "inputRef" | "tabIndex"> {
|
||||||
inputRef?: Ref;
|
inputRef?: Ref;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -16,7 +16,6 @@ limitations under the License.
|
||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
|
||||||
import AccessibleButton from "../../components/views/elements/AccessibleButton";
|
|
||||||
import {useRovingTabIndex} from "../RovingTabIndex";
|
import {useRovingTabIndex} from "../RovingTabIndex";
|
||||||
import {FocusHandler, Ref} from "./types";
|
import {FocusHandler, Ref} from "./types";
|
||||||
|
|
||||||
|
|
|
@ -1,70 +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 React from "react";
|
|
||||||
import PropTypes from "prop-types";
|
|
||||||
import * as sdk from "../../../../index";
|
|
||||||
import { _t } from "../../../../languageHandler";
|
|
||||||
|
|
||||||
export default class IgnoreRecoveryReminderDialog extends React.PureComponent {
|
|
||||||
static propTypes = {
|
|
||||||
onDontAskAgain: PropTypes.func.isRequired,
|
|
||||||
onFinished: PropTypes.func.isRequired,
|
|
||||||
onSetup: PropTypes.func.isRequired,
|
|
||||||
}
|
|
||||||
|
|
||||||
onDontAskAgainClick = () => {
|
|
||||||
this.props.onFinished();
|
|
||||||
this.props.onDontAskAgain();
|
|
||||||
}
|
|
||||||
|
|
||||||
onSetupClick = () => {
|
|
||||||
this.props.onFinished();
|
|
||||||
this.props.onSetup();
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const BaseDialog = sdk.getComponent("views.dialogs.BaseDialog");
|
|
||||||
const DialogButtons = sdk.getComponent("views.elements.DialogButtons");
|
|
||||||
|
|
||||||
return (
|
|
||||||
<BaseDialog className="mx_IgnoreRecoveryReminderDialog"
|
|
||||||
onFinished={this.props.onFinished}
|
|
||||||
title={_t("Are you sure?")}
|
|
||||||
>
|
|
||||||
<div>
|
|
||||||
<p>{_t(
|
|
||||||
"Without setting up Secure Message Recovery, " +
|
|
||||||
"you'll lose your secure message history when you " +
|
|
||||||
"log out.",
|
|
||||||
)}</p>
|
|
||||||
<p>{_t(
|
|
||||||
"If you don't want to set this up now, you can later " +
|
|
||||||
"in Settings.",
|
|
||||||
)}</p>
|
|
||||||
<div className="mx_Dialog_buttons">
|
|
||||||
<DialogButtons
|
|
||||||
primaryButton={_t("Set up")}
|
|
||||||
onPrimaryButtonClick={this.onSetupClick}
|
|
||||||
cancelButton={_t("Don't ask again")}
|
|
||||||
onCancel={this.onDontAskAgainClick}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</BaseDialog>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -21,7 +21,7 @@ import * as sdk from '../../../../index';
|
||||||
import {MatrixClientPeg} from '../../../../MatrixClientPeg';
|
import {MatrixClientPeg} from '../../../../MatrixClientPeg';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import {_t, _td} from '../../../../languageHandler';
|
import {_t, _td} from '../../../../languageHandler';
|
||||||
import { accessSecretStorage } from '../../../../CrossSigningManager';
|
import { accessSecretStorage } from '../../../../SecurityManager';
|
||||||
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";
|
import PassphraseField from "../../../../components/views/auth/PassphraseField";
|
|
@ -22,7 +22,7 @@ import {MatrixClientPeg} from '../../../../MatrixClientPeg';
|
||||||
import FileSaver from 'file-saver';
|
import FileSaver from 'file-saver';
|
||||||
import {_t, _td} from '../../../../languageHandler';
|
import {_t, _td} from '../../../../languageHandler';
|
||||||
import Modal from '../../../../Modal';
|
import Modal from '../../../../Modal';
|
||||||
import { promptForBackupPassphrase } from '../../../../CrossSigningManager';
|
import { promptForBackupPassphrase } from '../../../../SecurityManager';
|
||||||
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";
|
import PassphraseField from "../../../../components/views/auth/PassphraseField";
|
||||||
|
@ -30,6 +30,8 @@ import StyledRadioButton from '../../../../components/views/elements/StyledRadio
|
||||||
import AccessibleButton from "../../../../components/views/elements/AccessibleButton";
|
import AccessibleButton from "../../../../components/views/elements/AccessibleButton";
|
||||||
import DialogButtons from "../../../../components/views/elements/DialogButtons";
|
import DialogButtons from "../../../../components/views/elements/DialogButtons";
|
||||||
import InlineSpinner from "../../../../components/views/elements/InlineSpinner";
|
import InlineSpinner from "../../../../components/views/elements/InlineSpinner";
|
||||||
|
import RestoreKeyBackupDialog from "../../../../components/views/dialogs/security/RestoreKeyBackupDialog";
|
||||||
|
import { isSecureBackupRequired } from '../../../../utils/WellKnownUtils';
|
||||||
|
|
||||||
const PHASE_LOADING = 0;
|
const PHASE_LOADING = 0;
|
||||||
const PHASE_LOADERROR = 1;
|
const PHASE_LOADERROR = 1;
|
||||||
|
@ -55,12 +57,12 @@ export default class CreateSecretStorageDialog extends React.PureComponent {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
hasCancel: PropTypes.bool,
|
hasCancel: PropTypes.bool,
|
||||||
accountPassword: PropTypes.string,
|
accountPassword: PropTypes.string,
|
||||||
force: PropTypes.bool,
|
forceReset: PropTypes.bool,
|
||||||
};
|
};
|
||||||
|
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
hasCancel: true,
|
hasCancel: true,
|
||||||
force: false,
|
forceReset: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
|
@ -85,8 +87,8 @@ export default class CreateSecretStorageDialog extends React.PureComponent {
|
||||||
canUploadKeysWithPasswordOnly: null,
|
canUploadKeysWithPasswordOnly: null,
|
||||||
accountPassword: props.accountPassword || "",
|
accountPassword: props.accountPassword || "",
|
||||||
accountPasswordCorrect: null,
|
accountPasswordCorrect: null,
|
||||||
|
|
||||||
passPhraseKeySelected: CREATE_STORAGE_OPTION_KEY,
|
passPhraseKeySelected: CREATE_STORAGE_OPTION_KEY,
|
||||||
|
canSkip: !isSecureBackupRequired(),
|
||||||
};
|
};
|
||||||
|
|
||||||
this._passphraseField = createRef();
|
this._passphraseField = createRef();
|
||||||
|
@ -117,8 +119,8 @@ export default class CreateSecretStorageDialog extends React.PureComponent {
|
||||||
MatrixClientPeg.get().isCryptoEnabled() && await MatrixClientPeg.get().isKeyBackupTrusted(backupInfo)
|
MatrixClientPeg.get().isCryptoEnabled() && await MatrixClientPeg.get().isKeyBackupTrusted(backupInfo)
|
||||||
);
|
);
|
||||||
|
|
||||||
const { force } = this.props;
|
const { forceReset } = this.props;
|
||||||
const phase = (backupInfo && !force) ? PHASE_MIGRATE : PHASE_CHOOSE_KEY_PASSPHRASE;
|
const phase = (backupInfo && !forceReset) ? PHASE_MIGRATE : PHASE_CHOOSE_KEY_PASSPHRASE;
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
phase,
|
phase,
|
||||||
|
@ -276,20 +278,28 @@ export default class CreateSecretStorageDialog extends React.PureComponent {
|
||||||
|
|
||||||
const cli = MatrixClientPeg.get();
|
const cli = MatrixClientPeg.get();
|
||||||
|
|
||||||
const { force } = this.props;
|
const { forceReset } = this.props;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (force) {
|
if (forceReset) {
|
||||||
console.log("Forcing secret storage reset"); // log something so we can debug this later
|
console.log("Forcing secret storage reset");
|
||||||
await cli.bootstrapSecretStorage({
|
await cli.bootstrapSecretStorage({
|
||||||
authUploadDeviceSigningKeys: this._doBootstrapUIAuth,
|
|
||||||
createSecretStorageKey: async () => this._recoveryKey,
|
createSecretStorageKey: async () => this._recoveryKey,
|
||||||
setupNewKeyBackup: true,
|
setupNewKeyBackup: true,
|
||||||
setupNewSecretStorage: true,
|
setupNewSecretStorage: true,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
await cli.bootstrapSecretStorage({
|
// For password authentication users after 2020-09, this cross-signing
|
||||||
|
// step will be a no-op since it is now setup during registration or login
|
||||||
|
// when needed. We should keep this here to cover other cases such as:
|
||||||
|
// * Users with existing sessions prior to 2020-09 changes
|
||||||
|
// * SSO authentication users which require interactive auth to upload
|
||||||
|
// keys (and also happen to skip all post-authentication flows at the
|
||||||
|
// moment via token login)
|
||||||
|
await cli.bootstrapCrossSigning({
|
||||||
authUploadDeviceSigningKeys: this._doBootstrapUIAuth,
|
authUploadDeviceSigningKeys: this._doBootstrapUIAuth,
|
||||||
|
});
|
||||||
|
await cli.bootstrapSecretStorage({
|
||||||
createSecretStorageKey: async () => this._recoveryKey,
|
createSecretStorageKey: async () => this._recoveryKey,
|
||||||
keyBackupInfo: this.state.backupInfo,
|
keyBackupInfo: this.state.backupInfo,
|
||||||
setupNewKeyBackup: !this.state.backupInfo,
|
setupNewKeyBackup: !this.state.backupInfo,
|
||||||
|
@ -337,7 +347,6 @@ export default class CreateSecretStorageDialog extends React.PureComponent {
|
||||||
// so let's stash it here, rather than prompting for it twice.
|
// so let's stash it here, rather than prompting for it twice.
|
||||||
const keyCallback = k => this._backupKey = k;
|
const keyCallback = k => this._backupKey = k;
|
||||||
|
|
||||||
const RestoreKeyBackupDialog = sdk.getComponent('dialogs.keybackup.RestoreKeyBackupDialog');
|
|
||||||
const { finished } = Modal.createTrackedDialog(
|
const { finished } = Modal.createTrackedDialog(
|
||||||
'Restore Backup', '', RestoreKeyBackupDialog,
|
'Restore Backup', '', RestoreKeyBackupDialog,
|
||||||
{
|
{
|
||||||
|
@ -475,7 +484,7 @@ export default class CreateSecretStorageDialog extends React.PureComponent {
|
||||||
primaryButton={_t("Continue")}
|
primaryButton={_t("Continue")}
|
||||||
onPrimaryButtonClick={this._onChooseKeyPassphraseFormSubmit}
|
onPrimaryButtonClick={this._onChooseKeyPassphraseFormSubmit}
|
||||||
onCancel={this._onCancelClick}
|
onCancel={this._onCancelClick}
|
||||||
hasCancel={true}
|
hasCancel={this.state.canSkip}
|
||||||
/>
|
/>
|
||||||
</form>;
|
</form>;
|
||||||
}
|
}
|
||||||
|
@ -692,7 +701,7 @@ export default class CreateSecretStorageDialog extends React.PureComponent {
|
||||||
<div className="mx_Dialog_buttons">
|
<div className="mx_Dialog_buttons">
|
||||||
<DialogButtons primaryButton={_t('Retry')}
|
<DialogButtons primaryButton={_t('Retry')}
|
||||||
onPrimaryButtonClick={this._onLoadRetryClick}
|
onPrimaryButtonClick={this._onLoadRetryClick}
|
||||||
hasCancel={true}
|
hasCancel={this.state.canSkip}
|
||||||
onCancel={this._onCancel}
|
onCancel={this._onCancel}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -719,7 +728,7 @@ export default class CreateSecretStorageDialog extends React.PureComponent {
|
||||||
_titleForPhase(phase) {
|
_titleForPhase(phase) {
|
||||||
switch (phase) {
|
switch (phase) {
|
||||||
case PHASE_CHOOSE_KEY_PASSPHRASE:
|
case PHASE_CHOOSE_KEY_PASSPHRASE:
|
||||||
return _t('Set up Secure backup');
|
return _t('Set up Secure Backup');
|
||||||
case PHASE_MIGRATE:
|
case PHASE_MIGRATE:
|
||||||
return _t('Upgrade your encryption');
|
return _t('Upgrade your encryption');
|
||||||
case PHASE_PASSPHRASE:
|
case PHASE_PASSPHRASE:
|
||||||
|
@ -747,7 +756,7 @@ export default class CreateSecretStorageDialog extends React.PureComponent {
|
||||||
<div className="mx_Dialog_buttons">
|
<div className="mx_Dialog_buttons">
|
||||||
<DialogButtons primaryButton={_t('Retry')}
|
<DialogButtons primaryButton={_t('Retry')}
|
||||||
onPrimaryButtonClick={this._bootstrapSecretStorage}
|
onPrimaryButtonClick={this._bootstrapSecretStorage}
|
||||||
hasCancel={true}
|
hasCancel={this.state.canSkip}
|
||||||
onCancel={this._onCancel}
|
onCancel={this._onCancel}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
|
@ -17,44 +17,40 @@ limitations under the License.
|
||||||
import FileSaver from 'file-saver';
|
import FileSaver from 'file-saver';
|
||||||
import React, {createRef} from 'react';
|
import React, {createRef} from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import createReactClass from 'create-react-class';
|
import { _t } from '../../../../languageHandler';
|
||||||
import { _t } from '../../../languageHandler';
|
|
||||||
|
|
||||||
import { MatrixClient } from 'matrix-js-sdk';
|
import { MatrixClient } from 'matrix-js-sdk';
|
||||||
import * as MegolmExportEncryption from '../../../utils/MegolmExportEncryption';
|
import * as MegolmExportEncryption from '../../../../utils/MegolmExportEncryption';
|
||||||
import * as sdk from '../../../index';
|
import * as sdk from '../../../../index';
|
||||||
|
|
||||||
const PHASE_EDIT = 1;
|
const PHASE_EDIT = 1;
|
||||||
const PHASE_EXPORTING = 2;
|
const PHASE_EXPORTING = 2;
|
||||||
|
|
||||||
export default createReactClass({
|
export default class ExportE2eKeysDialog extends React.Component {
|
||||||
displayName: 'ExportE2eKeysDialog',
|
static propTypes = {
|
||||||
|
|
||||||
propTypes: {
|
|
||||||
matrixClient: PropTypes.instanceOf(MatrixClient).isRequired,
|
matrixClient: PropTypes.instanceOf(MatrixClient).isRequired,
|
||||||
onFinished: PropTypes.func.isRequired,
|
onFinished: PropTypes.func.isRequired,
|
||||||
},
|
};
|
||||||
|
|
||||||
getInitialState: function() {
|
constructor(props) {
|
||||||
return {
|
super(props);
|
||||||
phase: PHASE_EDIT,
|
|
||||||
errStr: null,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
// TODO: [REACT-WARNING] Replace component with real class, use constructor for refs
|
|
||||||
UNSAFE_componentWillMount: function() {
|
|
||||||
this._unmounted = false;
|
this._unmounted = false;
|
||||||
|
|
||||||
this._passphrase1 = createRef();
|
this._passphrase1 = createRef();
|
||||||
this._passphrase2 = createRef();
|
this._passphrase2 = createRef();
|
||||||
},
|
|
||||||
|
|
||||||
componentWillUnmount: function() {
|
this.state = {
|
||||||
|
phase: PHASE_EDIT,
|
||||||
|
errStr: null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
this._unmounted = true;
|
this._unmounted = true;
|
||||||
},
|
}
|
||||||
|
|
||||||
_onPassphraseFormSubmit: function(ev) {
|
_onPassphraseFormSubmit = (ev) => {
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
|
|
||||||
const passphrase = this._passphrase1.current.value;
|
const passphrase = this._passphrase1.current.value;
|
||||||
|
@ -69,9 +65,9 @@ export default createReactClass({
|
||||||
|
|
||||||
this._startExport(passphrase);
|
this._startExport(passphrase);
|
||||||
return false;
|
return false;
|
||||||
},
|
};
|
||||||
|
|
||||||
_startExport: function(passphrase) {
|
_startExport(passphrase) {
|
||||||
// extra Promise.resolve() to turn synchronous exceptions into
|
// extra Promise.resolve() to turn synchronous exceptions into
|
||||||
// asynchronous ones.
|
// asynchronous ones.
|
||||||
Promise.resolve().then(() => {
|
Promise.resolve().then(() => {
|
||||||
|
@ -102,15 +98,15 @@ export default createReactClass({
|
||||||
errStr: null,
|
errStr: null,
|
||||||
phase: PHASE_EXPORTING,
|
phase: PHASE_EXPORTING,
|
||||||
});
|
});
|
||||||
},
|
}
|
||||||
|
|
||||||
_onCancelClick: function(ev) {
|
_onCancelClick = (ev) => {
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
this.props.onFinished(false);
|
this.props.onFinished(false);
|
||||||
return false;
|
return false;
|
||||||
},
|
};
|
||||||
|
|
||||||
render: function() {
|
render() {
|
||||||
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
|
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
|
||||||
|
|
||||||
const disableForm = (this.state.phase === PHASE_EXPORTING);
|
const disableForm = (this.state.phase === PHASE_EXPORTING);
|
||||||
|
@ -184,5 +180,5 @@ export default createReactClass({
|
||||||
</form>
|
</form>
|
||||||
</BaseDialog>
|
</BaseDialog>
|
||||||
);
|
);
|
||||||
},
|
}
|
||||||
});
|
}
|
|
@ -16,12 +16,11 @@ limitations under the License.
|
||||||
|
|
||||||
import React, {createRef} from 'react';
|
import React, {createRef} from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import createReactClass from 'create-react-class';
|
|
||||||
|
|
||||||
import { MatrixClient } from 'matrix-js-sdk';
|
import { MatrixClient } from 'matrix-js-sdk';
|
||||||
import * as MegolmExportEncryption from '../../../utils/MegolmExportEncryption';
|
import * as MegolmExportEncryption from '../../../../utils/MegolmExportEncryption';
|
||||||
import * as sdk from '../../../index';
|
import * as sdk from '../../../../index';
|
||||||
import { _t } from '../../../languageHandler';
|
import { _t } from '../../../../languageHandler';
|
||||||
|
|
||||||
function readFileAsArrayBuffer(file) {
|
function readFileAsArrayBuffer(file) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
|
@ -38,48 +37,45 @@ function readFileAsArrayBuffer(file) {
|
||||||
const PHASE_EDIT = 1;
|
const PHASE_EDIT = 1;
|
||||||
const PHASE_IMPORTING = 2;
|
const PHASE_IMPORTING = 2;
|
||||||
|
|
||||||
export default createReactClass({
|
export default class ImportE2eKeysDialog extends React.Component {
|
||||||
displayName: 'ImportE2eKeysDialog',
|
static propTypes = {
|
||||||
|
|
||||||
propTypes: {
|
|
||||||
matrixClient: PropTypes.instanceOf(MatrixClient).isRequired,
|
matrixClient: PropTypes.instanceOf(MatrixClient).isRequired,
|
||||||
onFinished: PropTypes.func.isRequired,
|
onFinished: PropTypes.func.isRequired,
|
||||||
},
|
};
|
||||||
|
|
||||||
getInitialState: function() {
|
constructor(props) {
|
||||||
return {
|
super(props);
|
||||||
enableSubmit: false,
|
|
||||||
phase: PHASE_EDIT,
|
|
||||||
errStr: null,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
// TODO: [REACT-WARNING] Replace component with real class, use constructor for refs
|
|
||||||
UNSAFE_componentWillMount: function() {
|
|
||||||
this._unmounted = false;
|
this._unmounted = false;
|
||||||
|
|
||||||
this._file = createRef();
|
this._file = createRef();
|
||||||
this._passphrase = createRef();
|
this._passphrase = createRef();
|
||||||
},
|
|
||||||
|
|
||||||
componentWillUnmount: function() {
|
this.state = {
|
||||||
|
enableSubmit: false,
|
||||||
|
phase: PHASE_EDIT,
|
||||||
|
errStr: null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
this._unmounted = true;
|
this._unmounted = true;
|
||||||
},
|
}
|
||||||
|
|
||||||
_onFormChange: function(ev) {
|
_onFormChange = (ev) => {
|
||||||
const files = this._file.current.files || [];
|
const files = this._file.current.files || [];
|
||||||
this.setState({
|
this.setState({
|
||||||
enableSubmit: (this._passphrase.current.value !== "" && files.length > 0),
|
enableSubmit: (this._passphrase.current.value !== "" && files.length > 0),
|
||||||
});
|
});
|
||||||
},
|
};
|
||||||
|
|
||||||
_onFormSubmit: function(ev) {
|
_onFormSubmit = (ev) => {
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
this._startImport(this._file.current.files[0], this._passphrase.current.value);
|
this._startImport(this._file.current.files[0], this._passphrase.current.value);
|
||||||
return false;
|
return false;
|
||||||
},
|
};
|
||||||
|
|
||||||
_startImport: function(file, passphrase) {
|
_startImport(file, passphrase) {
|
||||||
this.setState({
|
this.setState({
|
||||||
errStr: null,
|
errStr: null,
|
||||||
phase: PHASE_IMPORTING,
|
phase: PHASE_IMPORTING,
|
||||||
|
@ -105,15 +101,15 @@ export default createReactClass({
|
||||||
phase: PHASE_EDIT,
|
phase: PHASE_EDIT,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
},
|
}
|
||||||
|
|
||||||
_onCancelClick: function(ev) {
|
_onCancelClick = (ev) => {
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
this.props.onFinished(false);
|
this.props.onFinished(false);
|
||||||
return false;
|
return false;
|
||||||
},
|
};
|
||||||
|
|
||||||
render: function() {
|
render() {
|
||||||
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
|
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
|
||||||
|
|
||||||
const disableForm = (this.state.phase !== PHASE_EDIT);
|
const disableForm = (this.state.phase !== PHASE_EDIT);
|
||||||
|
@ -188,5 +184,5 @@ export default createReactClass({
|
||||||
</form>
|
</form>
|
||||||
</BaseDialog>
|
</BaseDialog>
|
||||||
);
|
);
|
||||||
},
|
}
|
||||||
});
|
}
|
|
@ -22,6 +22,7 @@ import {MatrixClientPeg} from '../../../../MatrixClientPeg';
|
||||||
import dis from "../../../../dispatcher/dispatcher";
|
import dis from "../../../../dispatcher/dispatcher";
|
||||||
import { _t } from "../../../../languageHandler";
|
import { _t } from "../../../../languageHandler";
|
||||||
import Modal from "../../../../Modal";
|
import Modal from "../../../../Modal";
|
||||||
|
import RestoreKeyBackupDialog from "../../../../components/views/dialogs/security/RestoreKeyBackupDialog";
|
||||||
import {Action} from "../../../../dispatcher/actions";
|
import {Action} from "../../../../dispatcher/actions";
|
||||||
|
|
||||||
export default class NewRecoveryMethodDialog extends React.PureComponent {
|
export default class NewRecoveryMethodDialog extends React.PureComponent {
|
||||||
|
@ -41,7 +42,6 @@ export default class NewRecoveryMethodDialog extends React.PureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
onSetupClick = async () => {
|
onSetupClick = async () => {
|
||||||
const RestoreKeyBackupDialog = sdk.getComponent('dialogs.keybackup.RestoreKeyBackupDialog');
|
|
||||||
Modal.createTrackedDialog(
|
Modal.createTrackedDialog(
|
||||||
'Restore Backup', '', RestoreKeyBackupDialog, {
|
'Restore Backup', '', RestoreKeyBackupDialog, {
|
||||||
onFinished: this.props.onFinished,
|
onFinished: this.props.onFinished,
|
|
@ -47,7 +47,7 @@ export default class CommandProvider extends AutocompleteProvider {
|
||||||
if (command[0] !== command[1]) {
|
if (command[0] !== command[1]) {
|
||||||
// The input looks like a command with arguments, perform exact match
|
// The input looks like a command with arguments, perform exact match
|
||||||
const name = command[1].substr(1); // strip leading `/`
|
const name = command[1].substr(1); // strip leading `/`
|
||||||
if (CommandMap.has(name)) {
|
if (CommandMap.has(name) && CommandMap.get(name).isEnabled()) {
|
||||||
// some commands, namely `me` and `ddg` don't suit having the usage shown whilst typing their arguments
|
// some commands, namely `me` and `ddg` don't suit having the usage shown whilst typing their arguments
|
||||||
if (CommandMap.get(name).hideCompletionAfterSpace) return [];
|
if (CommandMap.get(name).hideCompletionAfterSpace) return [];
|
||||||
matches = [CommandMap.get(name)];
|
matches = [CommandMap.get(name)];
|
||||||
|
@ -63,7 +63,7 @@ export default class CommandProvider extends AutocompleteProvider {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
return matches.map((result) => {
|
return matches.filter(cmd => cmd.isEnabled()).map((result) => {
|
||||||
let completion = result.getCommand() + ' ';
|
let completion = result.getCommand() + ' ';
|
||||||
const usedAlias = result.aliases.find(alias => `/${alias}` === command[1]);
|
const usedAlias = result.aliases.find(alias => `/${alias}` === command[1]);
|
||||||
// If the command (or an alias) is the same as the one they entered, we don't want to discard their arguments
|
// If the command (or an alias) is the same as the one they entered, we don't want to discard their arguments
|
||||||
|
@ -89,7 +89,11 @@ export default class CommandProvider extends AutocompleteProvider {
|
||||||
|
|
||||||
renderCompletions(completions: React.ReactNode[]): React.ReactNode {
|
renderCompletions(completions: React.ReactNode[]): React.ReactNode {
|
||||||
return (
|
return (
|
||||||
<div className="mx_Autocomplete_Completion_container_block" role="listbox" aria-label={_t("Command Autocomplete")}>
|
<div
|
||||||
|
className="mx_Autocomplete_Completion_container_block"
|
||||||
|
role="listbox"
|
||||||
|
aria-label={_t("Command Autocomplete")}
|
||||||
|
>
|
||||||
{ completions }
|
{ completions }
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -23,7 +23,7 @@ import {MatrixClientPeg} from '../MatrixClientPeg';
|
||||||
import QueryMatcher from './QueryMatcher';
|
import QueryMatcher from './QueryMatcher';
|
||||||
import {PillCompletion} from './Components';
|
import {PillCompletion} from './Components';
|
||||||
import * as sdk from '../index';
|
import * as sdk from '../index';
|
||||||
import _sortBy from 'lodash/sortBy';
|
import {sortBy} from "lodash";
|
||||||
import {makeGroupPermalink} from "../utils/permalinks/Permalinks";
|
import {makeGroupPermalink} from "../utils/permalinks/Permalinks";
|
||||||
import {ICompletion, ISelectionRange} from "./Autocompleter";
|
import {ICompletion, ISelectionRange} from "./Autocompleter";
|
||||||
import FlairStore from "../stores/FlairStore";
|
import FlairStore from "../stores/FlairStore";
|
||||||
|
@ -81,7 +81,7 @@ export default class CommunityProvider extends AutocompleteProvider {
|
||||||
|
|
||||||
const matchedString = command[0];
|
const matchedString = command[0];
|
||||||
completions = this.matcher.match(matchedString);
|
completions = this.matcher.match(matchedString);
|
||||||
completions = _sortBy(completions, [
|
completions = sortBy(completions, [
|
||||||
(c) => score(matchedString, c.groupId),
|
(c) => score(matchedString, c.groupId),
|
||||||
(c) => c.groupId.length,
|
(c) => c.groupId.length,
|
||||||
]).map(({avatarUrl, groupId, name}) => ({
|
]).map(({avatarUrl, groupId, name}) => ({
|
||||||
|
@ -91,15 +91,15 @@ export default class CommunityProvider extends AutocompleteProvider {
|
||||||
href: makeGroupPermalink(groupId),
|
href: makeGroupPermalink(groupId),
|
||||||
component: (
|
component: (
|
||||||
<PillCompletion title={name} description={groupId}>
|
<PillCompletion title={name} description={groupId}>
|
||||||
<BaseAvatar name={name || groupId}
|
<BaseAvatar
|
||||||
width={24}
|
name={name || groupId}
|
||||||
height={24}
|
width={24}
|
||||||
url={avatarUrl ? cli.mxcUrlToHttp(avatarUrl, 24, 24) : null} />
|
height={24}
|
||||||
|
url={avatarUrl ? cli.mxcUrlToHttp(avatarUrl, 24, 24) : null} />
|
||||||
</PillCompletion>
|
</PillCompletion>
|
||||||
),
|
),
|
||||||
range,
|
range,
|
||||||
}))
|
})).slice(0, 4);
|
||||||
.slice(0, 4);
|
|
||||||
}
|
}
|
||||||
return completions;
|
return completions;
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,9 +34,9 @@ export const TextualCompletion = forwardRef<ITextualCompletionProps, any>((props
|
||||||
const {title, subtitle, description, className, ...restProps} = props;
|
const {title, subtitle, description, className, ...restProps} = props;
|
||||||
return (
|
return (
|
||||||
<div {...restProps}
|
<div {...restProps}
|
||||||
className={classNames('mx_Autocomplete_Completion_block', className)}
|
className={classNames('mx_Autocomplete_Completion_block', className)}
|
||||||
role="option"
|
role="option"
|
||||||
ref={ref}
|
ref={ref}
|
||||||
>
|
>
|
||||||
<span className="mx_Autocomplete_Completion_title">{ title }</span>
|
<span className="mx_Autocomplete_Completion_title">{ title }</span>
|
||||||
<span className="mx_Autocomplete_Completion_subtitle">{ subtitle }</span>
|
<span className="mx_Autocomplete_Completion_subtitle">{ subtitle }</span>
|
||||||
|
@ -53,9 +53,9 @@ export const PillCompletion = forwardRef<IPillCompletionProps, any>((props, ref)
|
||||||
const {title, subtitle, description, className, children, ...restProps} = props;
|
const {title, subtitle, description, className, children, ...restProps} = props;
|
||||||
return (
|
return (
|
||||||
<div {...restProps}
|
<div {...restProps}
|
||||||
className={classNames('mx_Autocomplete_Completion_pill', className)}
|
className={classNames('mx_Autocomplete_Completion_pill', className)}
|
||||||
role="option"
|
role="option"
|
||||||
ref={ref}
|
ref={ref}
|
||||||
>
|
>
|
||||||
{ children }
|
{ children }
|
||||||
<span className="mx_Autocomplete_Completion_title">{ title }</span>
|
<span className="mx_Autocomplete_Completion_title">{ title }</span>
|
||||||
|
|
|
@ -23,8 +23,7 @@ import AutocompleteProvider from './AutocompleteProvider';
|
||||||
import QueryMatcher from './QueryMatcher';
|
import QueryMatcher from './QueryMatcher';
|
||||||
import {PillCompletion} from './Components';
|
import {PillCompletion} from './Components';
|
||||||
import {ICompletion, ISelectionRange} from './Autocompleter';
|
import {ICompletion, ISelectionRange} from './Autocompleter';
|
||||||
import _uniq from 'lodash/uniq';
|
import {uniq, sortBy} from 'lodash';
|
||||||
import _sortBy from 'lodash/sortBy';
|
|
||||||
import SettingsStore from "../settings/SettingsStore";
|
import SettingsStore from "../settings/SettingsStore";
|
||||||
import { shortcodeToUnicode } from '../HtmlUtils';
|
import { shortcodeToUnicode } from '../HtmlUtils';
|
||||||
import { EMOJI, IEmoji } from '../emoji';
|
import { EMOJI, IEmoji } from '../emoji';
|
||||||
|
@ -115,7 +114,7 @@ export default class EmojiProvider extends AutocompleteProvider {
|
||||||
}
|
}
|
||||||
// Finally, sort by original ordering
|
// Finally, sort by original ordering
|
||||||
sorters.push((c) => c._orderBy);
|
sorters.push((c) => c._orderBy);
|
||||||
completions = _sortBy(_uniq(completions), sorters);
|
completions = sortBy(uniq(completions), sorters);
|
||||||
|
|
||||||
completions = completions.map(({shortname}) => {
|
completions = completions.map(({shortname}) => {
|
||||||
const unicode = shortcodeToUnicode(shortname);
|
const unicode = shortcodeToUnicode(shortname);
|
||||||
|
@ -139,7 +138,11 @@ export default class EmojiProvider extends AutocompleteProvider {
|
||||||
|
|
||||||
renderCompletions(completions: React.ReactNode[]): React.ReactNode {
|
renderCompletions(completions: React.ReactNode[]): React.ReactNode {
|
||||||
return (
|
return (
|
||||||
<div className="mx_Autocomplete_Completion_container_pill" role="listbox" aria-label={_t("Emoji Autocomplete")}>
|
<div
|
||||||
|
className="mx_Autocomplete_Completion_container_pill"
|
||||||
|
role="listbox"
|
||||||
|
aria-label={_t("Emoji Autocomplete")}
|
||||||
|
>
|
||||||
{ completions }
|
{ completions }
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -16,8 +16,7 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import _at from 'lodash/at';
|
import {at, uniq} from 'lodash';
|
||||||
import _uniq from 'lodash/uniq';
|
|
||||||
import {removeHiddenChars} from "matrix-js-sdk/src/utils";
|
import {removeHiddenChars} from "matrix-js-sdk/src/utils";
|
||||||
|
|
||||||
interface IOptions<T extends {}> {
|
interface IOptions<T extends {}> {
|
||||||
|
@ -73,7 +72,7 @@ export default class QueryMatcher<T extends Object> {
|
||||||
// type for their values. We assume that those values who's keys have
|
// type for their values. We assume that those values who's keys have
|
||||||
// been specified will be string. Also, we cannot infer all the
|
// been specified will be string. Also, we cannot infer all the
|
||||||
// types of the keys of the objects at compile.
|
// types of the keys of the objects at compile.
|
||||||
const keyValues = _at<string>(<any>object, this._options.keys);
|
const keyValues = at<string>(<any>object, this._options.keys);
|
||||||
|
|
||||||
if (this._options.funcs) {
|
if (this._options.funcs) {
|
||||||
for (const f of this._options.funcs) {
|
for (const f of this._options.funcs) {
|
||||||
|
@ -137,7 +136,7 @@ export default class QueryMatcher<T extends Object> {
|
||||||
});
|
});
|
||||||
|
|
||||||
// Now map the keys to the result objects. Also remove any duplicates.
|
// Now map the keys to the result objects. Also remove any duplicates.
|
||||||
return _uniq(matches.map((match) => match.object));
|
return uniq(matches.map((match) => match.object));
|
||||||
}
|
}
|
||||||
|
|
||||||
private processQuery(query: string): string {
|
private processQuery(query: string): string {
|
||||||
|
|
|
@ -27,7 +27,7 @@ import {PillCompletion} from './Components';
|
||||||
import * as sdk from '../index';
|
import * as sdk from '../index';
|
||||||
import {makeRoomPermalink} from "../utils/permalinks/Permalinks";
|
import {makeRoomPermalink} from "../utils/permalinks/Permalinks";
|
||||||
import {ICompletion, ISelectionRange} from "./Autocompleter";
|
import {ICompletion, ISelectionRange} from "./Autocompleter";
|
||||||
import { uniqBy, sortBy } from 'lodash';
|
import {uniqBy, sortBy} from "lodash";
|
||||||
|
|
||||||
const ROOM_REGEX = /\B#\S*/g;
|
const ROOM_REGEX = /\B#\S*/g;
|
||||||
|
|
||||||
|
@ -110,9 +110,7 @@ export default class RoomProvider extends AutocompleteProvider {
|
||||||
),
|
),
|
||||||
range,
|
range,
|
||||||
};
|
};
|
||||||
})
|
}).filter((completion) => !!completion.completion && completion.completion.length > 0).slice(0, 4);
|
||||||
.filter((completion) => !!completion.completion && completion.completion.length > 0)
|
|
||||||
.slice(0, 4);
|
|
||||||
}
|
}
|
||||||
return completions;
|
return completions;
|
||||||
}
|
}
|
||||||
|
|