Merge remote-tracking branch 'upstream/develop' into compact-reply-rendering
|
@ -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
|
||||||
|
|
93
CHANGELOG.md
|
@ -1,3 +1,96 @@
|
||||||
|
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)
|
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)
|
[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v3.3.0-rc.1...v3.3.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'.
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "matrix-react-sdk",
|
"name": "matrix-react-sdk",
|
||||||
"version": "3.3.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": {
|
||||||
|
@ -94,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",
|
||||||
|
@ -148,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",
|
||||||
|
@ -157,7 +157,6 @@
|
||||||
"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",
|
||||||
|
|
|
@ -91,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";
|
||||||
|
@ -156,9 +157,12 @@
|
||||||
@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/rooms/_AppsDrawer.scss";
|
@import "./views/rooms/_AppsDrawer.scss";
|
||||||
@import "./views/rooms/_Autocomplete.scss";
|
@import "./views/rooms/_Autocomplete.scss";
|
||||||
|
@ -186,7 +190,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";
|
||||||
|
@ -201,10 +204,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 {
|
||||||
|
|
|
@ -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,7 +15,6 @@ limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
.mx_UserMenu {
|
.mx_UserMenu {
|
||||||
|
|
||||||
// to make the menu button sort of aligned with the explore button below
|
// to make the menu button sort of aligned with the explore button below
|
||||||
padding-right: 6px;
|
padding-right: 6px;
|
||||||
|
|
||||||
|
@ -59,7 +58,7 @@ 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/feather-customised/chevron-down.svg');
|
mask-image: url('$(res)/img/feather-customised/chevron-down.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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,39 +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_RoomRecoveryReminder {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
text-align: center;
|
|
||||||
background-color: $room-warning-bg-color;
|
|
||||||
padding: 20px;
|
|
||||||
border: 1px solid $primary-hairline-color;
|
|
||||||
border-bottom: unset;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_RoomRecoveryReminder_header {
|
|
||||||
font-weight: bold;
|
|
||||||
margin-bottom: 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_RoomRecoveryReminder_body {
|
|
||||||
margin-bottom: 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_RoomRecoveryReminder_secondary {
|
|
||||||
font-size: 90%;
|
|
||||||
margin-top: 1em;
|
|
||||||
}
|
|
|
@ -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 |
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 |
4
src/@types/global.d.ts
vendored
|
@ -28,6 +28,8 @@ 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 type {Renderer} from "react-dom";
|
||||||
|
import RightPanelStore from "../stores/RightPanelStore";
|
||||||
|
import WidgetStore from "../stores/WidgetStore";
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface Window {
|
interface Window {
|
||||||
|
@ -49,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
|
||||||
|
|
|
@ -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"),
|
||||||
|
|
|
@ -29,11 +29,10 @@ 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 { isSecretStorageBeingAccessed, accessSecretStorage } from "./CrossSigningManager";
|
|
||||||
import { isSecureBackupRequired } from './utils/WellKnownUtils';
|
import { isSecureBackupRequired } from './utils/WellKnownUtils';
|
||||||
import { isLoggedIn } from './components/structures/MatrixChat';
|
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;
|
||||||
|
|
||||||
|
@ -66,6 +65,7 @@ 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.dispatcherRef = dis.register(this._onAction);
|
||||||
this._recheck();
|
this._recheck();
|
||||||
}
|
}
|
||||||
|
@ -79,6 +79,7 @@ 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) {
|
if (this.dispatcherRef) {
|
||||||
dis.unregister(this.dispatcherRef);
|
dis.unregister(this.dispatcherRef);
|
||||||
|
@ -169,6 +170,16 @@ 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 }) => {
|
_onAction = ({ action }) => {
|
||||||
if (action !== "on_logged_in") return;
|
if (action !== "on_logged_in") return;
|
||||||
this._recheck();
|
this._recheck();
|
||||||
|
@ -189,9 +200,7 @@ export default class DeviceListener {
|
||||||
// If we're in the middle of a secret storage operation, we're likely
|
// If we're in the middle of a secret storage operation, we're likely
|
||||||
// modifying the state involved here, so don't add new toasts to setup.
|
// modifying the state involved here, so don't add new toasts to setup.
|
||||||
if (isSecretStorageBeingAccessed()) return false;
|
if (isSecretStorageBeingAccessed()) return false;
|
||||||
// In a default configuration, show the toasts. If the well-known config causes e2ee default to be false
|
// Show setup toasts once the user is in at least one encrypted room.
|
||||||
// then do not show the toasts until user is in at least one encrypted room.
|
|
||||||
if (privateShouldBeEncrypted()) return true;
|
|
||||||
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));
|
||||||
}
|
}
|
||||||
|
@ -207,8 +216,6 @@ export default class DeviceListener {
|
||||||
// (we add a listener on sync to do once check after the initial sync is done)
|
// (we add a listener on sync to do once check after the initial sync is done)
|
||||||
if (!cli.isInitialSyncComplete()) return;
|
if (!cli.isInitialSyncComplete()) return;
|
||||||
|
|
||||||
// JRS: This will change again in the next PR which moves secret storage
|
|
||||||
// later in the process.
|
|
||||||
const crossSigningReady = await cli.isCrossSigningReady();
|
const crossSigningReady = await cli.isCrossSigningReady();
|
||||||
const secretStorageReady = await cli.isSecretStorageReady();
|
const secretStorageReady = await cli.isSecretStorageReady();
|
||||||
const allSystemsReady = crossSigningReady && secretStorageReady;
|
const allSystemsReady = crossSigningReady && secretStorageReady;
|
||||||
|
|
|
@ -42,6 +42,7 @@ import {Mjolnir} from "./mjolnir/Mjolnir";
|
||||||
import DeviceListener from "./DeviceListener";
|
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 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";
|
||||||
|
@ -666,17 +667,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) {
|
||||||
|
|
|
@ -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 { crossSigningCallbacks } from './CrossSigningManager';
|
import { 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 {
|
||||||
|
|
|
@ -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";
|
||||||
|
|
|
@ -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.
|
||||||
|
@ -22,6 +22,8 @@ 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 { 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
|
||||||
|
@ -87,8 +89,6 @@ async function getSecretStorageKey({ keys: keyInfos }, ssssItemName) {
|
||||||
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= */
|
||||||
|
@ -142,7 +142,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 (
|
||||||
|
@ -181,7 +181,6 @@ export const crossSigningCallbacks = {
|
||||||
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);
|
||||||
|
@ -221,7 +220,7 @@ 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"),
|
||||||
{
|
{
|
||||||
forceReset,
|
forceReset,
|
||||||
},
|
},
|
||||||
|
@ -250,7 +249,7 @@ export async function accessSecretStorage(func = async () => { }, forceReset = f
|
||||||
'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,
|
||||||
},
|
},
|
||||||
);
|
);
|
|
@ -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, leaveRoomBehaviour } 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>',
|
||||||
|
@ -777,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"));
|
||||||
|
@ -959,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,
|
||||||
|
@ -1043,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+$/, '');
|
||||||
|
@ -1070,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);
|
||||||
|
|
|
@ -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,7 @@ 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';
|
import { isSecureBackupRequired } from '../../../../utils/WellKnownUtils';
|
||||||
|
|
||||||
const PHASE_LOADING = 0;
|
const PHASE_LOADING = 0;
|
||||||
|
@ -281,17 +282,20 @@ export default class CreateSecretStorageDialog extends React.PureComponent {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (forceReset) {
|
if (forceReset) {
|
||||||
console.log("Forcing cross-signing and secret storage reset");
|
console.log("Forcing secret storage reset");
|
||||||
await cli.bootstrapSecretStorage({
|
await cli.bootstrapSecretStorage({
|
||||||
createSecretStorageKey: async () => this._recoveryKey,
|
createSecretStorageKey: async () => this._recoveryKey,
|
||||||
setupNewKeyBackup: true,
|
setupNewKeyBackup: true,
|
||||||
setupNewSecretStorage: true,
|
setupNewSecretStorage: true,
|
||||||
});
|
});
|
||||||
await cli.bootstrapCrossSigning({
|
|
||||||
authUploadDeviceSigningKeys: this._doBootstrapUIAuth,
|
|
||||||
setupNewCrossSigning: true,
|
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
|
// 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({
|
await cli.bootstrapCrossSigning({
|
||||||
authUploadDeviceSigningKeys: this._doBootstrapUIAuth,
|
authUploadDeviceSigningKeys: this._doBootstrapUIAuth,
|
||||||
});
|
});
|
||||||
|
@ -338,7 +342,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,
|
||||||
{
|
{
|
|
@ -17,11 +17,11 @@ 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 { _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;
|
|
@ -18,9 +18,9 @@ import React, {createRef} from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
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) => {
|
|
@ -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
|
||||||
|
|
|
@ -16,7 +16,7 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, {CSSProperties, useRef, useState} from "react";
|
import React, {CSSProperties, RefObject, useRef, useState} from "react";
|
||||||
import ReactDOM from "react-dom";
|
import ReactDOM from "react-dom";
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
|
|
||||||
|
@ -416,8 +416,8 @@ export const aboveLeftOf = (elementRect: DOMRect, chevronFace = ChevronFace.None
|
||||||
return menuOptions;
|
return menuOptions;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useContextMenu = () => {
|
export const useContextMenu = (): [boolean, RefObject<HTMLElement>, () => void, () => void, (val: boolean) => void] => {
|
||||||
const button = useRef(null);
|
const button = useRef<HTMLElement>(null);
|
||||||
const [isOpen, setIsOpen] = useState(false);
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
const open = () => {
|
const open = () => {
|
||||||
setIsOpen(true);
|
setIsOpen(true);
|
||||||
|
|
|
@ -23,6 +23,8 @@ import * as sdk from '../../index';
|
||||||
import {MatrixClientPeg} from '../../MatrixClientPeg';
|
import {MatrixClientPeg} from '../../MatrixClientPeg';
|
||||||
import EventIndexPeg from "../../indexing/EventIndexPeg";
|
import EventIndexPeg from "../../indexing/EventIndexPeg";
|
||||||
import { _t } from '../../languageHandler';
|
import { _t } from '../../languageHandler';
|
||||||
|
import BaseCard from "../views/right_panel/BaseCard";
|
||||||
|
import {RightPanelPhases} from "../../stores/RightPanelStorePhases";
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Component which shows the filtered file using a TimelinePanel
|
* Component which shows the filtered file using a TimelinePanel
|
||||||
|
@ -30,6 +32,7 @@ import { _t } from '../../languageHandler';
|
||||||
class FilePanel extends React.Component {
|
class FilePanel extends React.Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
roomId: PropTypes.string.isRequired,
|
roomId: PropTypes.string.isRequired,
|
||||||
|
onClose: PropTypes.func.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
// This is used to track if a decrypted event was a live event and should be
|
// This is used to track if a decrypted event was a live event and should be
|
||||||
|
@ -188,18 +191,26 @@ class FilePanel extends React.Component {
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
if (MatrixClientPeg.get().isGuest()) {
|
if (MatrixClientPeg.get().isGuest()) {
|
||||||
return <div className="mx_FilePanel mx_RoomView_messageListWrapper">
|
return <BaseCard
|
||||||
|
className="mx_FilePanel mx_RoomView_messageListWrapper"
|
||||||
|
onClose={this.props.onClose}
|
||||||
|
previousPhase={RightPanelPhases.RoomSummary}
|
||||||
|
>
|
||||||
<div className="mx_RoomView_empty">
|
<div className="mx_RoomView_empty">
|
||||||
{ _t("You must <a>register</a> to use this functionality",
|
{ _t("You must <a>register</a> to use this functionality",
|
||||||
{},
|
{},
|
||||||
{ 'a': (sub) => <a href="#/register" key="sub">{ sub }</a> })
|
{ 'a': (sub) => <a href="#/register" key="sub">{ sub }</a> })
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>;
|
</BaseCard>;
|
||||||
} else if (this.noRoom) {
|
} else if (this.noRoom) {
|
||||||
return <div className="mx_FilePanel mx_RoomView_messageListWrapper">
|
return <BaseCard
|
||||||
|
className="mx_FilePanel mx_RoomView_messageListWrapper"
|
||||||
|
onClose={this.props.onClose}
|
||||||
|
previousPhase={RightPanelPhases.RoomSummary}
|
||||||
|
>
|
||||||
<div className="mx_RoomView_empty">{ _t("You must join the room to see its files") }</div>
|
<div className="mx_RoomView_empty">{ _t("You must join the room to see its files") }</div>
|
||||||
</div>;
|
</BaseCard>;
|
||||||
}
|
}
|
||||||
|
|
||||||
// wrap a TimelinePanel with the jump-to-event bits turned off.
|
// wrap a TimelinePanel with the jump-to-event bits turned off.
|
||||||
|
@ -215,7 +226,12 @@ class FilePanel extends React.Component {
|
||||||
// console.log("rendering TimelinePanel for timelineSet " + this.state.timelineSet.room.roomId + " " +
|
// console.log("rendering TimelinePanel for timelineSet " + this.state.timelineSet.room.roomId + " " +
|
||||||
// "(" + this.state.timelineSet._timelines.join(", ") + ")" + " with key " + this.props.roomId);
|
// "(" + this.state.timelineSet._timelines.join(", ") + ")" + " with key " + this.props.roomId);
|
||||||
return (
|
return (
|
||||||
<div className="mx_FilePanel" role="tabpanel">
|
<BaseCard
|
||||||
|
className="mx_FilePanel"
|
||||||
|
onClose={this.props.onClose}
|
||||||
|
previousPhase={RightPanelPhases.RoomSummary}
|
||||||
|
withoutScrollContainer
|
||||||
|
>
|
||||||
<TimelinePanel
|
<TimelinePanel
|
||||||
manageReadReceipts={false}
|
manageReadReceipts={false}
|
||||||
manageReadMarkers={false}
|
manageReadMarkers={false}
|
||||||
|
@ -226,13 +242,17 @@ class FilePanel extends React.Component {
|
||||||
resizeNotifier={this.props.resizeNotifier}
|
resizeNotifier={this.props.resizeNotifier}
|
||||||
empty={emptyState}
|
empty={emptyState}
|
||||||
/>
|
/>
|
||||||
</div>
|
</BaseCard>
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
return (
|
return (
|
||||||
<div className="mx_FilePanel" role="tabpanel">
|
<BaseCard
|
||||||
|
className="mx_FilePanel"
|
||||||
|
onClose={this.props.onClose}
|
||||||
|
previousPhase={RightPanelPhases.RoomSummary}
|
||||||
|
>
|
||||||
<Loader />
|
<Loader />
|
||||||
</div>
|
</BaseCard>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1322,7 +1322,7 @@ export default class GroupView extends React.Component {
|
||||||
</div>
|
</div>
|
||||||
<GroupHeaderButtons />
|
<GroupHeaderButtons />
|
||||||
</div>
|
</div>
|
||||||
<MainSplit panel={rightPanel}>
|
<MainSplit panel={rightPanel} resizeNotifier={this.props.resizeNotifier}>
|
||||||
<AutoHideScrollbar className="mx_GroupView_body">
|
<AutoHideScrollbar className="mx_GroupView_body">
|
||||||
{ this._getMembershipSection() }
|
{ this._getMembershipSection() }
|
||||||
{ this._getGroupSection() }
|
{ this._getGroupSection() }
|
||||||
|
|
|
@ -52,7 +52,7 @@ interface IState {
|
||||||
// List of CSS classes which should be included in keyboard navigation within the room list
|
// List of CSS classes which should be included in keyboard navigation within the room list
|
||||||
const cssClasses = [
|
const cssClasses = [
|
||||||
"mx_RoomSearch_input",
|
"mx_RoomSearch_input",
|
||||||
"mx_RoomSearch_icon", // minimized <RoomSearch />
|
"mx_RoomSearch_minimizedHandle", // minimized <RoomSearch />
|
||||||
"mx_RoomSublist_headerText",
|
"mx_RoomSublist_headerText",
|
||||||
"mx_RoomTile",
|
"mx_RoomTile",
|
||||||
"mx_RoomSublist_showNButton",
|
"mx_RoomSublist_showNButton",
|
||||||
|
|
|
@ -56,6 +56,7 @@ import { ViewRoomDeltaPayload } from "../../dispatcher/payloads/ViewRoomDeltaPay
|
||||||
import RoomListStore from "../../stores/room-list/RoomListStore";
|
import RoomListStore from "../../stores/room-list/RoomListStore";
|
||||||
import NonUrgentToastContainer from "./NonUrgentToastContainer";
|
import NonUrgentToastContainer from "./NonUrgentToastContainer";
|
||||||
import { ToggleRightPanelPayload } from "../../dispatcher/payloads/ToggleRightPanelPayload";
|
import { ToggleRightPanelPayload } from "../../dispatcher/payloads/ToggleRightPanelPayload";
|
||||||
|
import { IThreepidInvite } from "../../stores/ThreepidInviteStore";
|
||||||
|
|
||||||
// We need to fetch each pinned message individually (if we don't already have it)
|
// We need to fetch each pinned message individually (if we don't already have it)
|
||||||
// so each pinned message may trigger a request. Limit the number per room for sanity.
|
// so each pinned message may trigger a request. Limit the number per room for sanity.
|
||||||
|
@ -76,13 +77,12 @@ interface IProps {
|
||||||
hideToSRUsers: boolean;
|
hideToSRUsers: boolean;
|
||||||
resizeNotifier: ResizeNotifier;
|
resizeNotifier: ResizeNotifier;
|
||||||
middleDisabled: boolean;
|
middleDisabled: boolean;
|
||||||
initialEventPixelOffset: number;
|
|
||||||
leftDisabled: boolean;
|
leftDisabled: boolean;
|
||||||
rightDisabled: boolean;
|
rightDisabled: boolean;
|
||||||
// eslint-disable-next-line camelcase
|
// eslint-disable-next-line camelcase
|
||||||
page_type: string;
|
page_type: string;
|
||||||
autoJoin: boolean;
|
autoJoin: boolean;
|
||||||
thirdPartyInvite?: object;
|
threepidInvite?: IThreepidInvite;
|
||||||
roomOobData?: object;
|
roomOobData?: object;
|
||||||
currentRoomId: string;
|
currentRoomId: string;
|
||||||
ConferenceHandler?: object;
|
ConferenceHandler?: object;
|
||||||
|
@ -257,6 +257,12 @@ class LoggedInView extends React.Component<IProps, IState> {
|
||||||
window.localStorage.setItem("mx_lhs_size", '' + size);
|
window.localStorage.setItem("mx_lhs_size", '' + size);
|
||||||
this.props.resizeNotifier.notifyLeftHandleResized();
|
this.props.resizeNotifier.notifyLeftHandleResized();
|
||||||
},
|
},
|
||||||
|
onResizeStart: () => {
|
||||||
|
this.props.resizeNotifier.startResizing();
|
||||||
|
},
|
||||||
|
onResizeStop: () => {
|
||||||
|
this.props.resizeNotifier.stopResizing();
|
||||||
|
},
|
||||||
};
|
};
|
||||||
const resizer = new Resizer(
|
const resizer = new Resizer(
|
||||||
this._resizeContainer.current,
|
this._resizeContainer.current,
|
||||||
|
@ -626,10 +632,9 @@ class LoggedInView extends React.Component<IProps, IState> {
|
||||||
ref={this._roomView}
|
ref={this._roomView}
|
||||||
autoJoin={this.props.autoJoin}
|
autoJoin={this.props.autoJoin}
|
||||||
onRegistered={this.props.onRegistered}
|
onRegistered={this.props.onRegistered}
|
||||||
thirdPartyInvite={this.props.thirdPartyInvite}
|
threepidInvite={this.props.threepidInvite}
|
||||||
oobData={this.props.roomOobData}
|
oobData={this.props.roomOobData}
|
||||||
viaServers={this.props.viaServers}
|
viaServers={this.props.viaServers}
|
||||||
eventPixelOffset={this.props.initialEventPixelOffset}
|
|
||||||
key={this.props.currentRoomId || 'roomview'}
|
key={this.props.currentRoomId || 'roomview'}
|
||||||
disabled={this.props.middleDisabled}
|
disabled={this.props.middleDisabled}
|
||||||
ConferenceHandler={this.props.ConferenceHandler}
|
ConferenceHandler={this.props.ConferenceHandler}
|
||||||
|
@ -650,12 +655,13 @@ class LoggedInView extends React.Component<IProps, IState> {
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case PageTypes.UserView:
|
case PageTypes.UserView:
|
||||||
pageElement = <UserView userId={this.props.currentUserId} />;
|
pageElement = <UserView userId={this.props.currentUserId} resizeNotifier={this.props.resizeNotifier} />;
|
||||||
break;
|
break;
|
||||||
case PageTypes.GroupView:
|
case PageTypes.GroupView:
|
||||||
pageElement = <GroupView
|
pageElement = <GroupView
|
||||||
groupId={this.props.currentGroupId}
|
groupId={this.props.currentGroupId}
|
||||||
isNew={this.props.currentGroupIsNew}
|
isNew={this.props.currentGroupIsNew}
|
||||||
|
resizeNotifier={this.props.resizeNotifier}
|
||||||
/>;
|
/>;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,9 +19,18 @@ import React from 'react';
|
||||||
import { Resizable } from 're-resizable';
|
import { Resizable } from 're-resizable';
|
||||||
|
|
||||||
export default class MainSplit extends React.Component {
|
export default class MainSplit extends React.Component {
|
||||||
_onResized = (event, direction, refToElement, delta) => {
|
_onResizeStart = () => {
|
||||||
|
this.props.resizeNotifier.startResizing();
|
||||||
|
};
|
||||||
|
|
||||||
|
_onResize = () => {
|
||||||
|
this.props.resizeNotifier.notifyRightHandleResized();
|
||||||
|
};
|
||||||
|
|
||||||
|
_onResizeStop = (event, direction, refToElement, delta) => {
|
||||||
|
this.props.resizeNotifier.stopResizing();
|
||||||
window.localStorage.setItem("mx_rhs_size", this._loadSidePanelSize().width + delta.width);
|
window.localStorage.setItem("mx_rhs_size", this._loadSidePanelSize().width + delta.width);
|
||||||
}
|
};
|
||||||
|
|
||||||
_loadSidePanelSize() {
|
_loadSidePanelSize() {
|
||||||
let rhsSize = parseInt(window.localStorage.getItem("mx_rhs_size"), 10);
|
let rhsSize = parseInt(window.localStorage.getItem("mx_rhs_size"), 10);
|
||||||
|
@ -58,7 +67,9 @@ export default class MainSplit extends React.Component {
|
||||||
bottomLeft: false,
|
bottomLeft: false,
|
||||||
topLeft: false,
|
topLeft: false,
|
||||||
}}
|
}}
|
||||||
onResizeStop={this._onResized}
|
onResizeStart={this._onResizeStart}
|
||||||
|
onResize={this._onResize}
|
||||||
|
onResizeStop={this._onResizeStop}
|
||||||
className="mx_RightPanel_ResizeWrapper"
|
className="mx_RightPanel_ResizeWrapper"
|
||||||
handleClasses={{left: "mx_RightPanel_ResizeHandle"}}
|
handleClasses={{left: "mx_RightPanel_ResizeHandle"}}
|
||||||
>
|
>
|
||||||
|
|
|
@ -78,6 +78,8 @@ import { RoomNotificationStateStore } from "../../stores/notifications/RoomNotif
|
||||||
import { SettingLevel } from "../../settings/SettingLevel";
|
import { SettingLevel } from "../../settings/SettingLevel";
|
||||||
import { leaveRoomBehaviour } from "../../utils/membership";
|
import { leaveRoomBehaviour } from "../../utils/membership";
|
||||||
import CreateCommunityPrototypeDialog from "../views/dialogs/CreateCommunityPrototypeDialog";
|
import CreateCommunityPrototypeDialog from "../views/dialogs/CreateCommunityPrototypeDialog";
|
||||||
|
import ThreepidInviteStore, { IThreepidInvite, IThreepidInviteWireFormat } from "../../stores/ThreepidInviteStore";
|
||||||
|
import {UIFeature} from "../../settings/UIFeature";
|
||||||
|
|
||||||
/** constants for MatrixChat.state.view */
|
/** constants for MatrixChat.state.view */
|
||||||
export enum Views {
|
export enum Views {
|
||||||
|
@ -137,9 +139,9 @@ interface IRoomInfo {
|
||||||
|
|
||||||
auto_join?: boolean;
|
auto_join?: boolean;
|
||||||
highlighted?: boolean;
|
highlighted?: boolean;
|
||||||
third_party_invite?: object;
|
|
||||||
oob_data?: object;
|
oob_data?: object;
|
||||||
via_servers?: string[];
|
via_servers?: string[];
|
||||||
|
threepid_invite?: IThreepidInvite;
|
||||||
}
|
}
|
||||||
/* eslint-enable camelcase */
|
/* eslint-enable camelcase */
|
||||||
|
|
||||||
|
@ -147,7 +149,7 @@ interface IProps { // TODO type things better
|
||||||
config: Record<string, any>;
|
config: Record<string, any>;
|
||||||
serverConfig?: ValidatedServerConfig;
|
serverConfig?: ValidatedServerConfig;
|
||||||
ConferenceHandler?: any;
|
ConferenceHandler?: any;
|
||||||
onNewScreen: (string) => void;
|
onNewScreen: (screen: string, replaceLast: boolean) => void;
|
||||||
enableGuest?: boolean;
|
enableGuest?: boolean;
|
||||||
// the queryParams extracted from the [real] query-string of the URI
|
// the queryParams extracted from the [real] query-string of the URI
|
||||||
realQueryParams?: Record<string, string>;
|
realQueryParams?: Record<string, string>;
|
||||||
|
@ -196,7 +198,7 @@ interface IState {
|
||||||
resizeNotifier: ResizeNotifier;
|
resizeNotifier: ResizeNotifier;
|
||||||
serverConfig?: ValidatedServerConfig;
|
serverConfig?: ValidatedServerConfig;
|
||||||
ready: boolean;
|
ready: boolean;
|
||||||
thirdPartyInvite?: object;
|
threepidInvite?: IThreepidInvite,
|
||||||
roomOobData?: object;
|
roomOobData?: object;
|
||||||
viaServers?: string[];
|
viaServers?: string[];
|
||||||
pendingInitialSync?: boolean;
|
pendingInitialSync?: boolean;
|
||||||
|
@ -260,6 +262,14 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||||
// outside this.state because updating it should never trigger a
|
// outside this.state because updating it should never trigger a
|
||||||
// rerender.
|
// rerender.
|
||||||
this.screenAfterLogin = this.props.initialScreenAfterLogin;
|
this.screenAfterLogin = this.props.initialScreenAfterLogin;
|
||||||
|
if (this.screenAfterLogin) {
|
||||||
|
const params = this.screenAfterLogin.params || {};
|
||||||
|
if (this.screenAfterLogin.screen.startsWith("room/") && params['signurl'] && params['email']) {
|
||||||
|
// probably a threepid invite - try to store it
|
||||||
|
const roomId = this.screenAfterLogin.screen.substring("room/".length);
|
||||||
|
ThreepidInviteStore.instance.storeInvite(roomId, params as IThreepidInviteWireFormat);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
this.windowWidth = 10000;
|
this.windowWidth = 10000;
|
||||||
this.handleResize();
|
this.handleResize();
|
||||||
|
@ -404,8 +414,12 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||||
});
|
});
|
||||||
}).then((loadedSession) => {
|
}).then((loadedSession) => {
|
||||||
if (!loadedSession) {
|
if (!loadedSession) {
|
||||||
// fall back to showing the welcome screen
|
// fall back to showing the welcome screen... unless we have a 3pid invite pending
|
||||||
dis.dispatch({action: "view_welcome_page"});
|
if (ThreepidInviteStore.instance.pickBestInvite()) {
|
||||||
|
dis.dispatch({action: 'start_registration'});
|
||||||
|
} else {
|
||||||
|
dis.dispatch({action: "view_welcome_page"});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
// Note we don't catch errors from this: we catch everything within
|
// Note we don't catch errors from this: we catch everything within
|
||||||
|
@ -835,10 +849,8 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||||
// context of that particular event.
|
// context of that particular event.
|
||||||
// @param {boolean=} roomInfo.highlighted If true, add event_id to the hash of the URL
|
// @param {boolean=} roomInfo.highlighted If true, add event_id to the hash of the URL
|
||||||
// and alter the EventTile to appear highlighted.
|
// and alter the EventTile to appear highlighted.
|
||||||
// @param {Object=} roomInfo.third_party_invite Object containing data about the third party
|
// @param {Object=} roomInfo.threepid_invite Object containing data about the third party
|
||||||
// we received to join the room, if any.
|
// we received to join the room, if any.
|
||||||
// @param {string=} roomInfo.third_party_invite.inviteSignUrl 3pid invite sign URL
|
|
||||||
// @param {string=} roomInfo.third_party_invite.invitedEmail The email address the invite was sent to
|
|
||||||
// @param {Object=} roomInfo.oob_data Object of additional data about the room
|
// @param {Object=} roomInfo.oob_data Object of additional data about the room
|
||||||
// that has been passed out-of-band (eg.
|
// that has been passed out-of-band (eg.
|
||||||
// room name and avatar from an invite email)
|
// room name and avatar from an invite email)
|
||||||
|
@ -886,6 +898,9 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If we are redirecting to a Room Alias and it is for the room we already showing then replace history item
|
||||||
|
const replaceLast = presentedId[0] === "#" && roomInfo.room_id === this.state.currentRoomId;
|
||||||
|
|
||||||
if (roomInfo.event_id && roomInfo.highlighted) {
|
if (roomInfo.event_id && roomInfo.highlighted) {
|
||||||
presentedId += "/" + roomInfo.event_id;
|
presentedId += "/" + roomInfo.event_id;
|
||||||
}
|
}
|
||||||
|
@ -893,12 +908,12 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||||
view: Views.LOGGED_IN,
|
view: Views.LOGGED_IN,
|
||||||
currentRoomId: roomInfo.room_id || null,
|
currentRoomId: roomInfo.room_id || null,
|
||||||
page_type: PageTypes.RoomView,
|
page_type: PageTypes.RoomView,
|
||||||
thirdPartyInvite: roomInfo.third_party_invite,
|
threepidInvite: roomInfo.threepid_invite,
|
||||||
roomOobData: roomInfo.oob_data,
|
roomOobData: roomInfo.oob_data,
|
||||||
viaServers: roomInfo.via_servers,
|
viaServers: roomInfo.via_servers,
|
||||||
ready: true,
|
ready: true,
|
||||||
}, () => {
|
}, () => {
|
||||||
this.notifyNewScreen('room/' + presentedId);
|
this.notifyNewScreen('room/' + presentedId, replaceLast);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -1200,6 +1215,14 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||||
// the homepage.
|
// the homepage.
|
||||||
dis.dispatch({action: 'view_home_page'});
|
dis.dispatch({action: 'view_home_page'});
|
||||||
}
|
}
|
||||||
|
} else if (ThreepidInviteStore.instance.pickBestInvite()) {
|
||||||
|
// The user has a 3pid invite pending - show them that
|
||||||
|
const threepidInvite = ThreepidInviteStore.instance.pickBestInvite();
|
||||||
|
|
||||||
|
// HACK: This is a pretty brutal way of threading the invite back through
|
||||||
|
// our systems, but it's the safest we have for now.
|
||||||
|
const params = ThreepidInviteStore.instance.translateToWireFormat(threepidInvite);
|
||||||
|
this.showScreen(`room/${threepidInvite.roomId}`, params)
|
||||||
} else {
|
} else {
|
||||||
// The user has just logged in after registering,
|
// The user has just logged in after registering,
|
||||||
// so show the homepage.
|
// so show the homepage.
|
||||||
|
@ -1211,8 +1234,8 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||||
|
|
||||||
StorageManager.tryPersistStorage();
|
StorageManager.tryPersistStorage();
|
||||||
|
|
||||||
if (SettingsStore.getValue("showCookieBar") && this.props.config.piwik && navigator.doNotTrack !== "1") {
|
if (SettingsStore.getValue("showCookieBar") && Analytics.canEnable()) {
|
||||||
showAnalyticsToast(this.props.config.piwik && this.props.config.piwik.policyUrl);
|
showAnalyticsToast(this.props.config.piwik?.policyUrl);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1341,7 +1364,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||||
this.firstSyncComplete = true;
|
this.firstSyncComplete = true;
|
||||||
this.firstSyncPromise.resolve();
|
this.firstSyncPromise.resolve();
|
||||||
|
|
||||||
if (Notifier.shouldShowToolbar()) {
|
if (Notifier.shouldShowPrompt()) {
|
||||||
showNotificationsToast();
|
showNotificationsToast();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1350,15 +1373,19 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||||
ready: true,
|
ready: true,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
cli.on('Call.incoming', function(call) {
|
|
||||||
// we dispatch this synchronously to make sure that the event
|
if (SettingsStore.getValue(UIFeature.Voip)) {
|
||||||
// handlers on the call are set up immediately (so that if
|
cli.on('Call.incoming', function(call) {
|
||||||
// we get an immediate hangup, we don't get a stuck call)
|
// we dispatch this synchronously to make sure that the event
|
||||||
dis.dispatch({
|
// handlers on the call are set up immediately (so that if
|
||||||
action: 'incoming_call',
|
// we get an immediate hangup, we don't get a stuck call)
|
||||||
call: call,
|
dis.dispatch({
|
||||||
}, true);
|
action: 'incoming_call',
|
||||||
});
|
call: call,
|
||||||
|
}, true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
cli.on('Session.logged_out', function(errObj) {
|
cli.on('Session.logged_out', function(errObj) {
|
||||||
if (Lifecycle.isLoggingOut()) return;
|
if (Lifecycle.isLoggingOut()) return;
|
||||||
|
|
||||||
|
@ -1474,12 +1501,12 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||||
|
|
||||||
if (haveNewVersion) {
|
if (haveNewVersion) {
|
||||||
Modal.createTrackedDialogAsync('New Recovery Method', 'New Recovery Method',
|
Modal.createTrackedDialogAsync('New Recovery Method', 'New Recovery Method',
|
||||||
import('../../async-components/views/dialogs/keybackup/NewRecoveryMethodDialog'),
|
import('../../async-components/views/dialogs/security/NewRecoveryMethodDialog'),
|
||||||
{ newVersionInfo },
|
{ newVersionInfo },
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
Modal.createTrackedDialogAsync('Recovery Method Removed', 'Recovery Method Removed',
|
Modal.createTrackedDialogAsync('Recovery Method Removed', 'Recovery Method Removed',
|
||||||
import('../../async-components/views/dialogs/keybackup/RecoveryMethodRemovedDialog'),
|
import('../../async-components/views/dialogs/security/RecoveryMethodRemovedDialog'),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -1636,16 +1663,11 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||||
|
|
||||||
// TODO: Handle encoded room/event IDs: https://github.com/vector-im/element-web/issues/9149
|
// TODO: Handle encoded room/event IDs: https://github.com/vector-im/element-web/issues/9149
|
||||||
|
|
||||||
// FIXME: sort_out caseConsistency
|
let threepidInvite: IThreepidInvite;
|
||||||
const thirdPartyInvite = {
|
if (params.signurl && params.email) {
|
||||||
inviteSignUrl: params.signurl,
|
threepidInvite = ThreepidInviteStore.instance
|
||||||
invitedEmail: params.email,
|
.storeInvite(roomString, params as IThreepidInviteWireFormat);
|
||||||
};
|
}
|
||||||
const oobData = {
|
|
||||||
name: params.room_name,
|
|
||||||
avatarUrl: params.room_avatar_url,
|
|
||||||
inviterName: params.inviter_name,
|
|
||||||
};
|
|
||||||
|
|
||||||
// on our URLs there might be a ?via=matrix.org or similar to help
|
// on our URLs there might be a ?via=matrix.org or similar to help
|
||||||
// joins to the room succeed. We'll pass these through as an array
|
// joins to the room succeed. We'll pass these through as an array
|
||||||
|
@ -1666,8 +1688,15 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||||
// it as highlighted, which will propagate to RoomView and highlight the
|
// it as highlighted, which will propagate to RoomView and highlight the
|
||||||
// associated EventTile.
|
// associated EventTile.
|
||||||
highlighted: Boolean(eventId),
|
highlighted: Boolean(eventId),
|
||||||
third_party_invite: thirdPartyInvite,
|
threepid_invite: threepidInvite,
|
||||||
oob_data: oobData,
|
// TODO: Replace oob_data with the threepidInvite (which has the same info).
|
||||||
|
// This isn't done yet because it's threaded through so many more places.
|
||||||
|
// See https://github.com/vector-im/element-web/issues/15157
|
||||||
|
oob_data: {
|
||||||
|
name: threepidInvite?.roomName,
|
||||||
|
avatarUrl: threepidInvite?.roomAvatarUrl,
|
||||||
|
inviterName: threepidInvite?.inviterName,
|
||||||
|
},
|
||||||
room_alias: undefined,
|
room_alias: undefined,
|
||||||
room_id: undefined,
|
room_id: undefined,
|
||||||
};
|
};
|
||||||
|
@ -1699,9 +1728,9 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
notifyNewScreen(screen: string) {
|
notifyNewScreen(screen: string, replaceLast = false) {
|
||||||
if (this.props.onNewScreen) {
|
if (this.props.onNewScreen) {
|
||||||
this.props.onNewScreen(screen);
|
this.props.onNewScreen(screen, replaceLast);
|
||||||
}
|
}
|
||||||
this.setPageSubtitle();
|
this.setPageSubtitle();
|
||||||
}
|
}
|
||||||
|
@ -1852,6 +1881,13 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||||
return this.props.makeRegistrationUrl(params);
|
return this.props.makeRegistrationUrl(params);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* After registration or login, we run various post-auth steps before entering the app
|
||||||
|
* proper, such setting up cross-signing or verifying the new session.
|
||||||
|
*
|
||||||
|
* Note: SSO users (and any others using token login) currently do not pass through
|
||||||
|
* this, as they instead jump straight into the app after `attemptTokenLogin`.
|
||||||
|
*/
|
||||||
onUserCompletedLoginFlow = async (credentials: object, password: string) => {
|
onUserCompletedLoginFlow = async (credentials: object, password: string) => {
|
||||||
this.accountPassword = password;
|
this.accountPassword = password;
|
||||||
// self-destruct the password after 5mins
|
// self-destruct the password after 5mins
|
||||||
|
@ -1918,7 +1954,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const fragmentAfterLogin = this.getFragmentAfterLogin();
|
const fragmentAfterLogin = this.getFragmentAfterLogin();
|
||||||
let view;
|
let view = null;
|
||||||
|
|
||||||
if (this.state.view === Views.LOADING) {
|
if (this.state.view === Views.LOADING) {
|
||||||
const Spinner = sdk.getComponent('elements.Spinner');
|
const Spinner = sdk.getComponent('elements.Spinner');
|
||||||
|
@ -1997,14 +2033,15 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||||
} else if (this.state.view === Views.WELCOME) {
|
} else if (this.state.view === Views.WELCOME) {
|
||||||
const Welcome = sdk.getComponent('auth.Welcome');
|
const Welcome = sdk.getComponent('auth.Welcome');
|
||||||
view = <Welcome />;
|
view = <Welcome />;
|
||||||
} else if (this.state.view === Views.REGISTER) {
|
} else if (this.state.view === Views.REGISTER && SettingsStore.getValue(UIFeature.Registration)) {
|
||||||
const Registration = sdk.getComponent('structures.auth.Registration');
|
const Registration = sdk.getComponent('structures.auth.Registration');
|
||||||
|
const email = ThreepidInviteStore.instance.pickBestInvite()?.toEmail;
|
||||||
view = (
|
view = (
|
||||||
<Registration
|
<Registration
|
||||||
clientSecret={this.state.register_client_secret}
|
clientSecret={this.state.register_client_secret}
|
||||||
sessionId={this.state.register_session_id}
|
sessionId={this.state.register_session_id}
|
||||||
idSid={this.state.register_id_sid}
|
idSid={this.state.register_id_sid}
|
||||||
email={this.props.startingFragmentQueryParams.email}
|
email={email}
|
||||||
brand={this.props.config.brand}
|
brand={this.props.config.brand}
|
||||||
makeRegistrationUrl={this.makeRegistrationUrl}
|
makeRegistrationUrl={this.makeRegistrationUrl}
|
||||||
onLoggedIn={this.onRegisterFlowComplete}
|
onLoggedIn={this.onRegisterFlowComplete}
|
||||||
|
@ -2014,7 +2051,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||||
{...this.getServerProperties()}
|
{...this.getServerProperties()}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
} else if (this.state.view === Views.FORGOT_PASSWORD) {
|
} else if (this.state.view === Views.FORGOT_PASSWORD && SettingsStore.getValue(UIFeature.PasswordReset)) {
|
||||||
const ForgotPassword = sdk.getComponent('structures.auth.ForgotPassword');
|
const ForgotPassword = sdk.getComponent('structures.auth.ForgotPassword');
|
||||||
view = (
|
view = (
|
||||||
<ForgotPassword
|
<ForgotPassword
|
||||||
|
@ -2025,6 +2062,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
} else if (this.state.view === Views.LOGIN) {
|
} else if (this.state.view === Views.LOGIN) {
|
||||||
|
const showPasswordReset = SettingsStore.getValue(UIFeature.PasswordReset);
|
||||||
const Login = sdk.getComponent('structures.auth.Login');
|
const Login = sdk.getComponent('structures.auth.Login');
|
||||||
view = (
|
view = (
|
||||||
<Login
|
<Login
|
||||||
|
@ -2033,7 +2071,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||||
onRegisterClick={this.onRegisterClick}
|
onRegisterClick={this.onRegisterClick}
|
||||||
fallbackHsUrl={this.getFallbackHsUrl()}
|
fallbackHsUrl={this.getFallbackHsUrl()}
|
||||||
defaultDeviceDisplayName={this.props.defaultDeviceDisplayName}
|
defaultDeviceDisplayName={this.props.defaultDeviceDisplayName}
|
||||||
onForgotPasswordClick={this.onForgotPasswordClick}
|
onForgotPasswordClick={showPasswordReset ? this.onForgotPasswordClick : undefined}
|
||||||
onServerConfigChange={this.onServerConfigChange}
|
onServerConfigChange={this.onServerConfigChange}
|
||||||
fragmentAfterLogin={fragmentAfterLogin}
|
fragmentAfterLogin={fragmentAfterLogin}
|
||||||
{...this.getServerProperties()}
|
{...this.getServerProperties()}
|
||||||
|
|
|
@ -135,6 +135,9 @@ export default class MessagePanel extends React.Component {
|
||||||
|
|
||||||
// whether to use the irc layout
|
// whether to use the irc layout
|
||||||
useIRCLayout: PropTypes.bool,
|
useIRCLayout: PropTypes.bool,
|
||||||
|
|
||||||
|
// whether or not to show flair at all
|
||||||
|
enableFlair: PropTypes.bool,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Force props to be loaded for useIRCLayout
|
// Force props to be loaded for useIRCLayout
|
||||||
|
@ -515,10 +518,13 @@ export default class MessagePanel extends React.Component {
|
||||||
if (!grouper) {
|
if (!grouper) {
|
||||||
const wantTile = this._shouldShowEvent(mxEv);
|
const wantTile = this._shouldShowEvent(mxEv);
|
||||||
if (wantTile) {
|
if (wantTile) {
|
||||||
|
const nextEvent = i < this.props.events.length - 1
|
||||||
|
? this.props.events[i + 1]
|
||||||
|
: null;
|
||||||
// make sure we unpack the array returned by _getTilesForEvent,
|
// make sure we unpack the array returned by _getTilesForEvent,
|
||||||
// otherwise react will auto-generate keys and we will end up
|
// otherwise react will auto-generate keys and we will end up
|
||||||
// replacing all of the DOM elements every time we paginate.
|
// replacing all of the DOM elements every time we paginate.
|
||||||
ret.push(...this._getTilesForEvent(prevEvent, mxEv, last));
|
ret.push(...this._getTilesForEvent(prevEvent, mxEv, last, nextEvent));
|
||||||
prevEvent = mxEv;
|
prevEvent = mxEv;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -534,7 +540,7 @@ export default class MessagePanel extends React.Component {
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
_getTilesForEvent(prevEvent, mxEv, last) {
|
_getTilesForEvent(prevEvent, mxEv, last, nextEvent) {
|
||||||
const TileErrorBoundary = sdk.getComponent('messages.TileErrorBoundary');
|
const TileErrorBoundary = sdk.getComponent('messages.TileErrorBoundary');
|
||||||
const EventTile = sdk.getComponent('rooms.EventTile');
|
const EventTile = sdk.getComponent('rooms.EventTile');
|
||||||
const DateSeparator = sdk.getComponent('messages.DateSeparator');
|
const DateSeparator = sdk.getComponent('messages.DateSeparator');
|
||||||
|
@ -559,6 +565,11 @@ export default class MessagePanel extends React.Component {
|
||||||
ret.push(dateSeparator);
|
ret.push(dateSeparator);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let willWantDateSeparator = false;
|
||||||
|
if (nextEvent) {
|
||||||
|
willWantDateSeparator = this._wantsDateSeparator(mxEv, nextEvent.getDate() || new Date());
|
||||||
|
}
|
||||||
|
|
||||||
// is this a continuation of the previous message?
|
// is this a continuation of the previous message?
|
||||||
const continuation = !wantsDateSeparator && shouldFormContinuation(prevEvent, mxEv);
|
const continuation = !wantsDateSeparator && shouldFormContinuation(prevEvent, mxEv);
|
||||||
|
|
||||||
|
@ -579,7 +590,8 @@ export default class MessagePanel extends React.Component {
|
||||||
data-scroll-tokens={scrollToken}
|
data-scroll-tokens={scrollToken}
|
||||||
>
|
>
|
||||||
<TileErrorBoundary mxEvent={mxEv}>
|
<TileErrorBoundary mxEvent={mxEv}>
|
||||||
<EventTile mxEvent={mxEv}
|
<EventTile
|
||||||
|
mxEvent={mxEv}
|
||||||
continuation={continuation}
|
continuation={continuation}
|
||||||
isRedacted={mxEv.isRedacted()}
|
isRedacted={mxEv.isRedacted()}
|
||||||
replacingEventId={mxEv.replacingEventId()}
|
replacingEventId={mxEv.replacingEventId()}
|
||||||
|
@ -594,10 +606,12 @@ export default class MessagePanel extends React.Component {
|
||||||
isTwelveHour={this.props.isTwelveHour}
|
isTwelveHour={this.props.isTwelveHour}
|
||||||
permalinkCreator={this.props.permalinkCreator}
|
permalinkCreator={this.props.permalinkCreator}
|
||||||
last={last}
|
last={last}
|
||||||
|
lastInSection={willWantDateSeparator}
|
||||||
isSelectedEvent={highlight}
|
isSelectedEvent={highlight}
|
||||||
getRelationsForEvent={this.props.getRelationsForEvent}
|
getRelationsForEvent={this.props.getRelationsForEvent}
|
||||||
showReactions={this.props.showReactions}
|
showReactions={this.props.showReactions}
|
||||||
useIRCLayout={this.props.useIRCLayout}
|
useIRCLayout={this.props.useIRCLayout}
|
||||||
|
enableFlair={this.props.enableFlair}
|
||||||
/>
|
/>
|
||||||
</TileErrorBoundary>
|
</TileErrorBoundary>
|
||||||
</li>,
|
</li>,
|
||||||
|
|
|
@ -17,14 +17,21 @@ limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import PropTypes from "prop-types";
|
||||||
|
|
||||||
import { _t } from '../../languageHandler';
|
import { _t } from '../../languageHandler';
|
||||||
import {MatrixClientPeg} from "../../MatrixClientPeg";
|
import {MatrixClientPeg} from "../../MatrixClientPeg";
|
||||||
import * as sdk from "../../index";
|
import * as sdk from "../../index";
|
||||||
|
import BaseCard from "../views/right_panel/BaseCard";
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Component which shows the global notification list using a TimelinePanel
|
* Component which shows the global notification list using a TimelinePanel
|
||||||
*/
|
*/
|
||||||
class NotificationPanel extends React.Component {
|
class NotificationPanel extends React.Component {
|
||||||
|
static propTypes = {
|
||||||
|
onClose: PropTypes.func.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
// wrap a TimelinePanel with the jump-to-event bits turned off.
|
// wrap a TimelinePanel with the jump-to-event bits turned off.
|
||||||
const TimelinePanel = sdk.getComponent("structures.TimelinePanel");
|
const TimelinePanel = sdk.getComponent("structures.TimelinePanel");
|
||||||
|
@ -35,28 +42,27 @@ class NotificationPanel extends React.Component {
|
||||||
<p>{_t('You have no visible notifications in this room.')}</p>
|
<p>{_t('You have no visible notifications in this room.')}</p>
|
||||||
</div>);
|
</div>);
|
||||||
|
|
||||||
|
let content;
|
||||||
const timelineSet = MatrixClientPeg.get().getNotifTimelineSet();
|
const timelineSet = MatrixClientPeg.get().getNotifTimelineSet();
|
||||||
if (timelineSet) {
|
if (timelineSet) {
|
||||||
return (
|
content = (
|
||||||
<div className="mx_NotificationPanel" role="tabpanel">
|
<TimelinePanel
|
||||||
<TimelinePanel
|
manageReadReceipts={false}
|
||||||
manageReadReceipts={false}
|
manageReadMarkers={false}
|
||||||
manageReadMarkers={false}
|
timelineSet={timelineSet}
|
||||||
timelineSet={timelineSet}
|
showUrlPreview={false}
|
||||||
showUrlPreview={false}
|
tileShape="notif"
|
||||||
tileShape="notif"
|
empty={emptyState}
|
||||||
empty={emptyState}
|
/>
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
console.error("No notifTimelineSet available!");
|
console.error("No notifTimelineSet available!");
|
||||||
return (
|
content = <Loader />;
|
||||||
<div className="mx_NotificationPanel" role="tabpanel">
|
|
||||||
<Loader />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return <BaseCard className="mx_NotificationPanel" onClose={this.props.onClose} withoutScrollContainer>
|
||||||
|
{ content }
|
||||||
|
</BaseCard>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -21,6 +21,8 @@ limitations under the License.
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
import {Room} from "matrix-js-sdk/src/models/room";
|
||||||
|
|
||||||
import * as sdk from '../../index';
|
import * as sdk from '../../index';
|
||||||
import dis from '../../dispatcher/dispatcher';
|
import dis from '../../dispatcher/dispatcher';
|
||||||
import RateLimitedFunc from '../../ratelimitedfunc';
|
import RateLimitedFunc from '../../ratelimitedfunc';
|
||||||
|
@ -30,11 +32,14 @@ import {RightPanelPhases, RIGHT_PANEL_PHASES_NO_ARGS} from "../../stores/RightPa
|
||||||
import RightPanelStore from "../../stores/RightPanelStore";
|
import RightPanelStore from "../../stores/RightPanelStore";
|
||||||
import MatrixClientContext from "../../contexts/MatrixClientContext";
|
import MatrixClientContext from "../../contexts/MatrixClientContext";
|
||||||
import {Action} from "../../dispatcher/actions";
|
import {Action} from "../../dispatcher/actions";
|
||||||
|
import RoomSummaryCard from "../views/right_panel/RoomSummaryCard";
|
||||||
|
import WidgetCard from "../views/right_panel/WidgetCard";
|
||||||
|
import defaultDispatcher from "../../dispatcher/dispatcher";
|
||||||
|
|
||||||
export default class RightPanel extends React.Component {
|
export default class RightPanel extends React.Component {
|
||||||
static get propTypes() {
|
static get propTypes() {
|
||||||
return {
|
return {
|
||||||
roomId: PropTypes.string, // if showing panels for a given room, this is set
|
room: PropTypes.instanceOf(Room), // if showing panels for a given room, this is set
|
||||||
groupId: PropTypes.string, // if showing panels for a given group, this is set
|
groupId: PropTypes.string, // if showing panels for a given group, this is set
|
||||||
user: PropTypes.object, // used if we know the user ahead of opening the panel
|
user: PropTypes.object, // used if we know the user ahead of opening the panel
|
||||||
};
|
};
|
||||||
|
@ -45,10 +50,10 @@ export default class RightPanel extends React.Component {
|
||||||
constructor(props, context) {
|
constructor(props, context) {
|
||||||
super(props, context);
|
super(props, context);
|
||||||
this.state = {
|
this.state = {
|
||||||
|
...RightPanelStore.getSharedInstance().roomPanelPhaseParams,
|
||||||
phase: this._getPhaseFromProps(),
|
phase: this._getPhaseFromProps(),
|
||||||
isUserPrivilegedInGroup: null,
|
isUserPrivilegedInGroup: null,
|
||||||
member: this._getUserForPanel(),
|
member: this._getUserForPanel(),
|
||||||
verificationRequest: RightPanelStore.getSharedInstance().roomPanelPhaseParams.verificationRequest,
|
|
||||||
};
|
};
|
||||||
this.onAction = this.onAction.bind(this);
|
this.onAction = this.onAction.bind(this);
|
||||||
this.onRoomStateMember = this.onRoomStateMember.bind(this);
|
this.onRoomStateMember = this.onRoomStateMember.bind(this);
|
||||||
|
@ -100,10 +105,6 @@ export default class RightPanel extends React.Component {
|
||||||
}
|
}
|
||||||
return RightPanelPhases.RoomMemberInfo;
|
return RightPanelPhases.RoomMemberInfo;
|
||||||
} else {
|
} else {
|
||||||
if (!RIGHT_PANEL_PHASES_NO_ARGS.includes(rps.roomPanelPhase)) {
|
|
||||||
dis.dispatch({action: Action.SetRightPanelPhase, phase: RightPanelPhases.RoomMemberList});
|
|
||||||
return RightPanelPhases.RoomMemberList;
|
|
||||||
}
|
|
||||||
return rps.roomPanelPhase;
|
return rps.roomPanelPhase;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -161,13 +162,13 @@ export default class RightPanel extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
onRoomStateMember(ev, state, member) {
|
onRoomStateMember(ev, state, member) {
|
||||||
if (member.roomId !== this.props.roomId) {
|
if (member.roomId !== this.props.room.roomId) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// redraw the badge on the membership list
|
// redraw the badge on the membership list
|
||||||
if (this.state.phase === RightPanelPhases.RoomMemberList && member.roomId === this.props.roomId) {
|
if (this.state.phase === RightPanelPhases.RoomMemberList && member.roomId === this.props.room.roomId) {
|
||||||
this._delayedUpdate();
|
this._delayedUpdate();
|
||||||
} else if (this.state.phase === RightPanelPhases.RoomMemberInfo && member.roomId === this.props.roomId &&
|
} else if (this.state.phase === RightPanelPhases.RoomMemberInfo && member.roomId === this.props.room.roomId &&
|
||||||
member.userId === this.state.member.userId) {
|
member.userId === this.state.member.userId) {
|
||||||
// refresh the member info (e.g. new power level)
|
// refresh the member info (e.g. new power level)
|
||||||
this._delayedUpdate();
|
this._delayedUpdate();
|
||||||
|
@ -184,6 +185,7 @@ export default class RightPanel extends React.Component {
|
||||||
event: payload.event,
|
event: payload.event,
|
||||||
verificationRequest: payload.verificationRequest,
|
verificationRequest: payload.verificationRequest,
|
||||||
verificationRequestPromise: payload.verificationRequestPromise,
|
verificationRequestPromise: payload.verificationRequestPromise,
|
||||||
|
widgetId: payload.widgetId,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -211,6 +213,14 @@ export default class RightPanel extends React.Component {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
onClose = () => {
|
||||||
|
// the RightPanelStore has no way of knowing which mode room/group it is in, so we handle closing here
|
||||||
|
defaultDispatcher.dispatch({
|
||||||
|
action: Action.ToggleRightPanel,
|
||||||
|
type: this.props.groupId ? "group" : "room",
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const MemberList = sdk.getComponent('rooms.MemberList');
|
const MemberList = sdk.getComponent('rooms.MemberList');
|
||||||
const UserInfo = sdk.getComponent('right_panel.UserInfo');
|
const UserInfo = sdk.getComponent('right_panel.UserInfo');
|
||||||
|
@ -223,36 +233,42 @@ export default class RightPanel extends React.Component {
|
||||||
const GroupRoomInfo = sdk.getComponent('groups.GroupRoomInfo');
|
const GroupRoomInfo = sdk.getComponent('groups.GroupRoomInfo');
|
||||||
|
|
||||||
let panel = <div />;
|
let panel = <div />;
|
||||||
|
const roomId = this.props.room ? this.props.room.roomId : undefined;
|
||||||
|
|
||||||
switch (this.state.phase) {
|
switch (this.state.phase) {
|
||||||
case RightPanelPhases.RoomMemberList:
|
case RightPanelPhases.RoomMemberList:
|
||||||
if (this.props.roomId) {
|
if (roomId) {
|
||||||
panel = <MemberList roomId={this.props.roomId} key={this.props.roomId} />;
|
panel = <MemberList roomId={roomId} key={roomId} onClose={this.onClose} />;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case RightPanelPhases.GroupMemberList:
|
case RightPanelPhases.GroupMemberList:
|
||||||
if (this.props.groupId) {
|
if (this.props.groupId) {
|
||||||
panel = <GroupMemberList groupId={this.props.groupId} key={this.props.groupId} />;
|
panel = <GroupMemberList groupId={this.props.groupId} key={this.props.groupId} />;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case RightPanelPhases.GroupRoomList:
|
case RightPanelPhases.GroupRoomList:
|
||||||
panel = <GroupRoomList groupId={this.props.groupId} key={this.props.groupId} />;
|
panel = <GroupRoomList groupId={this.props.groupId} key={this.props.groupId} />;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case RightPanelPhases.RoomMemberInfo:
|
case RightPanelPhases.RoomMemberInfo:
|
||||||
case RightPanelPhases.EncryptionPanel:
|
case RightPanelPhases.EncryptionPanel:
|
||||||
panel = <UserInfo
|
panel = <UserInfo
|
||||||
user={this.state.member}
|
user={this.state.member}
|
||||||
roomId={this.props.roomId}
|
room={this.props.room}
|
||||||
key={this.props.roomId || this.state.member.userId}
|
key={roomId || this.state.member.userId}
|
||||||
onClose={this.onCloseUserInfo}
|
onClose={this.onCloseUserInfo}
|
||||||
phase={this.state.phase}
|
phase={this.state.phase}
|
||||||
verificationRequest={this.state.verificationRequest}
|
verificationRequest={this.state.verificationRequest}
|
||||||
verificationRequestPromise={this.state.verificationRequestPromise}
|
verificationRequestPromise={this.state.verificationRequestPromise}
|
||||||
/>;
|
/>;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case RightPanelPhases.Room3pidMemberInfo:
|
case RightPanelPhases.Room3pidMemberInfo:
|
||||||
panel = <ThirdPartyMemberInfo event={this.state.event} key={this.props.roomId} />;
|
panel = <ThirdPartyMemberInfo event={this.state.event} key={roomId} />;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case RightPanelPhases.GroupMemberInfo:
|
case RightPanelPhases.GroupMemberInfo:
|
||||||
panel = <UserInfo
|
panel = <UserInfo
|
||||||
user={this.state.member}
|
user={this.state.member}
|
||||||
|
@ -260,17 +276,28 @@ export default class RightPanel extends React.Component {
|
||||||
key={this.state.member.userId}
|
key={this.state.member.userId}
|
||||||
onClose={this.onCloseUserInfo} />;
|
onClose={this.onCloseUserInfo} />;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case RightPanelPhases.GroupRoomInfo:
|
case RightPanelPhases.GroupRoomInfo:
|
||||||
panel = <GroupRoomInfo
|
panel = <GroupRoomInfo
|
||||||
groupRoomId={this.state.groupRoomId}
|
groupRoomId={this.state.groupRoomId}
|
||||||
groupId={this.props.groupId}
|
groupId={this.props.groupId}
|
||||||
key={this.state.groupRoomId} />;
|
key={this.state.groupRoomId} />;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case RightPanelPhases.NotificationPanel:
|
case RightPanelPhases.NotificationPanel:
|
||||||
panel = <NotificationPanel />;
|
panel = <NotificationPanel onClose={this.onClose} />;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case RightPanelPhases.FilePanel:
|
case RightPanelPhases.FilePanel:
|
||||||
panel = <FilePanel roomId={this.props.roomId} resizeNotifier={this.props.resizeNotifier} />;
|
panel = <FilePanel roomId={roomId} resizeNotifier={this.props.resizeNotifier} onClose={this.onClose} />;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case RightPanelPhases.RoomSummary:
|
||||||
|
panel = <RoomSummaryCard room={this.props.room} onClose={this.onClose} />;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case RightPanelPhases.Widget:
|
||||||
|
panel = <WidgetCard room={this.props.room} widgetId={this.state.widgetId} onClose={this.onClose} />;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -70,10 +70,10 @@ export default class RoomDirectory extends React.Component {
|
||||||
this.scrollPanel = null;
|
this.scrollPanel = null;
|
||||||
this.protocols = null;
|
this.protocols = null;
|
||||||
|
|
||||||
this.setState({protocolsLoading: true});
|
this.state.protocolsLoading = true;
|
||||||
if (!MatrixClientPeg.get()) {
|
if (!MatrixClientPeg.get()) {
|
||||||
// We may not have a client yet when invoked from welcome page
|
// We may not have a client yet when invoked from welcome page
|
||||||
this.setState({protocolsLoading: false});
|
this.state.protocolsLoading = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -102,14 +102,16 @@ export default class RoomDirectory extends React.Component {
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
// We don't use the protocols in the communities v2 prototype experience
|
// We don't use the protocols in the communities v2 prototype experience
|
||||||
this.setState({protocolsLoading: false});
|
this.state.protocolsLoading = false;
|
||||||
|
|
||||||
// Grab the profile info async
|
// Grab the profile info async
|
||||||
FlairStore.getGroupProfileCached(MatrixClientPeg.get(), this.state.selectedCommunityId).then(profile => {
|
FlairStore.getGroupProfileCached(MatrixClientPeg.get(), this.state.selectedCommunityId).then(profile => {
|
||||||
this.setState({communityName: profile.name});
|
this.setState({communityName: profile.name});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
this.refreshRoomList();
|
this.refreshRoomList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -390,22 +392,12 @@ export default class RoomDirectory extends React.Component {
|
||||||
};
|
};
|
||||||
|
|
||||||
onPreviewClick = (ev, room) => {
|
onPreviewClick = (ev, room) => {
|
||||||
this.props.onFinished();
|
this.showRoom(room, null, false, true);
|
||||||
dis.dispatch({
|
|
||||||
action: 'view_room',
|
|
||||||
room_id: room.room_id,
|
|
||||||
should_peek: true,
|
|
||||||
});
|
|
||||||
ev.stopPropagation();
|
ev.stopPropagation();
|
||||||
};
|
};
|
||||||
|
|
||||||
onViewClick = (ev, room) => {
|
onViewClick = (ev, room) => {
|
||||||
this.props.onFinished();
|
this.showRoom(room);
|
||||||
dis.dispatch({
|
|
||||||
action: 'view_room',
|
|
||||||
room_id: room.room_id,
|
|
||||||
should_peek: false,
|
|
||||||
});
|
|
||||||
ev.stopPropagation();
|
ev.stopPropagation();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -426,11 +418,12 @@ export default class RoomDirectory extends React.Component {
|
||||||
this.showRoom(null, alias, autoJoin);
|
this.showRoom(null, alias, autoJoin);
|
||||||
}
|
}
|
||||||
|
|
||||||
showRoom(room, room_alias, autoJoin=false) {
|
showRoom(room, room_alias, autoJoin = false, shouldPeek = false) {
|
||||||
this.props.onFinished();
|
this.props.onFinished();
|
||||||
const payload = {
|
const payload = {
|
||||||
action: 'view_room',
|
action: 'view_room',
|
||||||
auto_join: autoJoin,
|
auto_join: autoJoin,
|
||||||
|
should_peek: shouldPeek,
|
||||||
};
|
};
|
||||||
if (room) {
|
if (room) {
|
||||||
// Don't let the user view a room they won't be able to either
|
// Don't let the user view a room they won't be able to either
|
||||||
|
@ -455,6 +448,7 @@ export default class RoomDirectory extends React.Component {
|
||||||
};
|
};
|
||||||
|
|
||||||
if (this.state.roomServer) {
|
if (this.state.roomServer) {
|
||||||
|
payload.via_servers = [this.state.roomServer];
|
||||||
payload.opts = {
|
payload.opts = {
|
||||||
viaServers: [this.state.roomServer],
|
viaServers: [this.state.roomServer],
|
||||||
};
|
};
|
||||||
|
|
|
@ -165,7 +165,7 @@ export default class RoomSearch extends React.PureComponent<IProps, IState> {
|
||||||
icon = (
|
icon = (
|
||||||
<AccessibleButton
|
<AccessibleButton
|
||||||
title={_t("Search rooms")}
|
title={_t("Search rooms")}
|
||||||
className="mx_RoomSearch_icon"
|
className="mx_RoomSearch_icon mx_RoomSearch_minimizedHandle"
|
||||||
onClick={this.openSearch}
|
onClick={this.openSearch}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
|
@ -94,7 +94,7 @@ export default class ScrollPanel extends React.Component {
|
||||||
|
|
||||||
/* startAtBottom: if set to true, the view is assumed to start
|
/* startAtBottom: if set to true, the view is assumed to start
|
||||||
* scrolled to the bottom.
|
* scrolled to the bottom.
|
||||||
* XXX: It's likley this is unecessary and can be derived from
|
* XXX: It's likely this is unnecessary and can be derived from
|
||||||
* stickyBottom, but I'm adding an extra parameter to ensure
|
* stickyBottom, but I'm adding an extra parameter to ensure
|
||||||
* behaviour stays the same for other uses of ScrollPanel.
|
* behaviour stays the same for other uses of ScrollPanel.
|
||||||
* If so, let's remove this parameter down the line.
|
* If so, let's remove this parameter down the line.
|
||||||
|
@ -138,6 +138,7 @@ export default class ScrollPanel extends React.Component {
|
||||||
/* style: styles to add to the top-level div
|
/* style: styles to add to the top-level div
|
||||||
*/
|
*/
|
||||||
style: PropTypes.object,
|
style: PropTypes.object,
|
||||||
|
|
||||||
/* resizeNotifier: ResizeNotifier to know when middle column has changed size
|
/* resizeNotifier: ResizeNotifier to know when middle column has changed size
|
||||||
*/
|
*/
|
||||||
resizeNotifier: PropTypes.object,
|
resizeNotifier: PropTypes.object,
|
||||||
|
@ -162,7 +163,7 @@ export default class ScrollPanel extends React.Component {
|
||||||
this._pendingFillRequests = {b: null, f: null};
|
this._pendingFillRequests = {b: null, f: null};
|
||||||
|
|
||||||
if (this.props.resizeNotifier) {
|
if (this.props.resizeNotifier) {
|
||||||
this.props.resizeNotifier.on("middlePanelResized", this.onResize);
|
this.props.resizeNotifier.on("middlePanelResizedNoisy", this.onResize);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.resetScrollState();
|
this.resetScrollState();
|
||||||
|
@ -192,11 +193,13 @@ export default class ScrollPanel extends React.Component {
|
||||||
this.unmounted = true;
|
this.unmounted = true;
|
||||||
|
|
||||||
if (this.props.resizeNotifier) {
|
if (this.props.resizeNotifier) {
|
||||||
this.props.resizeNotifier.removeListener("middlePanelResized", this.onResize);
|
this.props.resizeNotifier.removeListener("middlePanelResizedNoisy", this.onResize);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onScroll = ev => {
|
onScroll = ev => {
|
||||||
|
// skip scroll events caused by resizing
|
||||||
|
if (this.props.resizeNotifier && this.props.resizeNotifier.isResizing) return;
|
||||||
debuglog("onScroll", this._getScrollNode().scrollTop);
|
debuglog("onScroll", this._getScrollNode().scrollTop);
|
||||||
this._scrollTimeout.restart();
|
this._scrollTimeout.restart();
|
||||||
this._saveScrollState();
|
this._saveScrollState();
|
||||||
|
@ -206,6 +209,7 @@ export default class ScrollPanel extends React.Component {
|
||||||
};
|
};
|
||||||
|
|
||||||
onResize = () => {
|
onResize = () => {
|
||||||
|
debuglog("onResize");
|
||||||
this.checkScroll();
|
this.checkScroll();
|
||||||
// update preventShrinkingState if present
|
// update preventShrinkingState if present
|
||||||
if (this.preventShrinkingState) {
|
if (this.preventShrinkingState) {
|
||||||
|
@ -235,7 +239,6 @@ export default class ScrollPanel extends React.Component {
|
||||||
// when scrolled all the way down. E.g. Chrome 72 on debian.
|
// when scrolled all the way down. E.g. Chrome 72 on debian.
|
||||||
// so check difference <= 1;
|
// so check difference <= 1;
|
||||||
return Math.abs(sn.scrollHeight - (sn.scrollTop + sn.clientHeight)) <= 1;
|
return Math.abs(sn.scrollHeight - (sn.scrollTop + sn.clientHeight)) <= 1;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// returns the vertical height in the given direction that can be removed from
|
// returns the vertical height in the given direction that can be removed from
|
||||||
|
|
|
@ -35,6 +35,7 @@ import Timer from '../../utils/Timer';
|
||||||
import shouldHideEvent from '../../shouldHideEvent';
|
import shouldHideEvent from '../../shouldHideEvent';
|
||||||
import EditorStateTransfer from '../../utils/EditorStateTransfer';
|
import EditorStateTransfer from '../../utils/EditorStateTransfer';
|
||||||
import {haveTileForEvent} from "../views/rooms/EventTile";
|
import {haveTileForEvent} from "../views/rooms/EventTile";
|
||||||
|
import {UIFeature} from "../../settings/UIFeature";
|
||||||
|
|
||||||
const PAGINATE_SIZE = 20;
|
const PAGINATE_SIZE = 20;
|
||||||
const INITIAL_SIZE = 20;
|
const INITIAL_SIZE = 20;
|
||||||
|
@ -104,8 +105,8 @@ class TimelinePanel extends React.Component {
|
||||||
// shape property to be passed to EventTiles
|
// shape property to be passed to EventTiles
|
||||||
tileShape: PropTypes.string,
|
tileShape: PropTypes.string,
|
||||||
|
|
||||||
// placeholder text to use if the timeline is empty
|
// placeholder to use if the timeline is empty
|
||||||
empty: PropTypes.string,
|
empty: PropTypes.node,
|
||||||
|
|
||||||
// whether to show reactions for an event
|
// whether to show reactions for an event
|
||||||
showReactions: PropTypes.bool,
|
showReactions: PropTypes.bool,
|
||||||
|
@ -1446,6 +1447,7 @@ class TimelinePanel extends React.Component {
|
||||||
editState={this.state.editState}
|
editState={this.state.editState}
|
||||||
showReactions={this.props.showReactions}
|
showReactions={this.props.showReactions}
|
||||||
useIRCLayout={this.props.useIRCLayout}
|
useIRCLayout={this.props.useIRCLayout}
|
||||||
|
enableFlair={SettingsStore.getValue(UIFeature.Flair)}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -50,6 +50,7 @@ import dis from "../../dispatcher/dispatcher";
|
||||||
import { RightPanelPhases } from "../../stores/RightPanelStorePhases";
|
import { RightPanelPhases } from "../../stores/RightPanelStorePhases";
|
||||||
import ErrorDialog from "../views/dialogs/ErrorDialog";
|
import ErrorDialog from "../views/dialogs/ErrorDialog";
|
||||||
import EditCommunityPrototypeDialog from "../views/dialogs/EditCommunityPrototypeDialog";
|
import EditCommunityPrototypeDialog from "../views/dialogs/EditCommunityPrototypeDialog";
|
||||||
|
import {UIFeature} from "../../settings/UIFeature";
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
isMinimized: boolean;
|
isMinimized: boolean;
|
||||||
|
@ -285,6 +286,15 @@ export default class UserMenu extends React.Component<IProps, IState> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let feedbackButton;
|
||||||
|
if (SettingsStore.getValue(UIFeature.Feedback)) {
|
||||||
|
feedbackButton = <IconizedContextMenuOption
|
||||||
|
iconClassName="mx_UserMenu_iconMessage"
|
||||||
|
label={_t("Feedback")}
|
||||||
|
onClick={this.onProvideFeedback}
|
||||||
|
/>;
|
||||||
|
}
|
||||||
|
|
||||||
let primaryHeader = (
|
let primaryHeader = (
|
||||||
<div className="mx_UserMenu_contextMenu_name">
|
<div className="mx_UserMenu_contextMenu_name">
|
||||||
<span className="mx_UserMenu_contextMenu_displayName">
|
<span className="mx_UserMenu_contextMenu_displayName">
|
||||||
|
@ -319,11 +329,7 @@ export default class UserMenu extends React.Component<IProps, IState> {
|
||||||
label={_t("Archived rooms")}
|
label={_t("Archived rooms")}
|
||||||
onClick={this.onShowArchived}
|
onClick={this.onShowArchived}
|
||||||
/> */}
|
/> */}
|
||||||
<IconizedContextMenuOption
|
{ feedbackButton }
|
||||||
iconClassName="mx_UserMenu_iconMessage"
|
|
||||||
label={_t("Feedback")}
|
|
||||||
onClick={this.onProvideFeedback}
|
|
||||||
/>
|
|
||||||
</IconizedContextMenuOptionList>
|
</IconizedContextMenuOptionList>
|
||||||
<IconizedContextMenuOptionList red>
|
<IconizedContextMenuOptionList red>
|
||||||
<IconizedContextMenuOption
|
<IconizedContextMenuOption
|
||||||
|
@ -384,11 +390,7 @@ export default class UserMenu extends React.Component<IProps, IState> {
|
||||||
aria-label={_t("User settings")}
|
aria-label={_t("User settings")}
|
||||||
onClick={(e) => this.onSettingsOpen(e, null)}
|
onClick={(e) => this.onSettingsOpen(e, null)}
|
||||||
/>
|
/>
|
||||||
<IconizedContextMenuOption
|
{ feedbackButton }
|
||||||
iconClassName="mx_UserMenu_iconMessage"
|
|
||||||
label={_t("Feedback")}
|
|
||||||
onClick={this.onProvideFeedback}
|
|
||||||
/>
|
|
||||||
</IconizedContextMenuOptionList>
|
</IconizedContextMenuOptionList>
|
||||||
<IconizedContextMenuOptionList red>
|
<IconizedContextMenuOptionList red>
|
||||||
<IconizedContextMenuOption
|
<IconizedContextMenuOption
|
||||||
|
|
|
@ -80,7 +80,9 @@ export default class UserView extends React.Component {
|
||||||
const RightPanel = sdk.getComponent('structures.RightPanel');
|
const RightPanel = sdk.getComponent('structures.RightPanel');
|
||||||
const MainSplit = sdk.getComponent('structures.MainSplit');
|
const MainSplit = sdk.getComponent('structures.MainSplit');
|
||||||
const panel = <RightPanel user={this.state.member} />;
|
const panel = <RightPanel user={this.state.member} />;
|
||||||
return (<MainSplit panel={panel}><HomePage /></MainSplit>);
|
return (<MainSplit panel={panel} resizeNotifier={this.props.resizeNotifier}>
|
||||||
|
<HomePage />
|
||||||
|
</MainSplit>);
|
||||||
} else {
|
} else {
|
||||||
return (<div />);
|
return (<div />);
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,8 +16,9 @@ limitations under the License.
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import AsyncWrapper from '../../../AsyncWrapper';
|
import AuthPage from '../../views/auth/AuthPage';
|
||||||
import * as sdk from '../../../index';
|
import CompleteSecurityBody from '../../views/auth/CompleteSecurityBody';
|
||||||
|
import CreateCrossSigningDialog from '../../views/dialogs/security/CreateCrossSigningDialog';
|
||||||
|
|
||||||
export default class E2eSetup extends React.Component {
|
export default class E2eSetup extends React.Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
|
@ -25,21 +26,11 @@ export default class E2eSetup extends React.Component {
|
||||||
accountPassword: PropTypes.string,
|
accountPassword: PropTypes.string,
|
||||||
};
|
};
|
||||||
|
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
// awkwardly indented because https://github.com/eslint/eslint/issues/11310
|
|
||||||
this._createStorageDialogPromise =
|
|
||||||
import("../../../async-components/views/dialogs/secretstorage/CreateSecretStorageDialog");
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const AuthPage = sdk.getComponent("auth.AuthPage");
|
|
||||||
const CompleteSecurityBody = sdk.getComponent("auth.CompleteSecurityBody");
|
|
||||||
return (
|
return (
|
||||||
<AuthPage>
|
<AuthPage>
|
||||||
<CompleteSecurityBody>
|
<CompleteSecurityBody>
|
||||||
<AsyncWrapper prom={this._createStorageDialogPromise}
|
<CreateCrossSigningDialog
|
||||||
hasCancel={false}
|
|
||||||
onFinished={this.props.onFinished}
|
onFinished={this.props.onFinished}
|
||||||
accountPassword={this.props.accountPassword}
|
accountPassword={this.props.accountPassword}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -28,6 +28,8 @@ import classNames from "classnames";
|
||||||
import AuthPage from "../../views/auth/AuthPage";
|
import AuthPage from "../../views/auth/AuthPage";
|
||||||
import SSOButton from "../../views/elements/SSOButton";
|
import SSOButton from "../../views/elements/SSOButton";
|
||||||
import PlatformPeg from '../../../PlatformPeg';
|
import PlatformPeg from '../../../PlatformPeg';
|
||||||
|
import SettingsStore from "../../../settings/SettingsStore";
|
||||||
|
import {UIFeature} from "../../../settings/UIFeature";
|
||||||
|
|
||||||
// For validating phone numbers without country codes
|
// For validating phone numbers without country codes
|
||||||
const PHONE_NUMBER_REGEX = /^[0-9()\-\s]*$/;
|
const PHONE_NUMBER_REGEX = /^[0-9()\-\s]*$/;
|
||||||
|
@ -124,7 +126,11 @@ export default class LoginComponent extends React.Component {
|
||||||
'm.login.cas': () => this._renderSsoStep("cas"),
|
'm.login.cas': () => this._renderSsoStep("cas"),
|
||||||
'm.login.sso': () => this._renderSsoStep("sso"),
|
'm.login.sso': () => this._renderSsoStep("sso"),
|
||||||
};
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: [REACT-WARNING] Replace with appropriate lifecycle event
|
||||||
|
// eslint-disable-next-line camelcase
|
||||||
|
UNSAFE_componentWillMount() {
|
||||||
this._initLoginLogic();
|
this._initLoginLogic();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -675,7 +681,7 @@ export default class LoginComponent extends React.Component {
|
||||||
{_t("If you've joined lots of rooms, this might take a while")}
|
{_t("If you've joined lots of rooms, this might take a while")}
|
||||||
</div> }
|
</div> }
|
||||||
</div>;
|
</div>;
|
||||||
} else {
|
} else if (SettingsStore.getValue(UIFeature.Registration)) {
|
||||||
footer = (
|
footer = (
|
||||||
<a className="mx_AuthBody_changeFlow" onClick={this.onTryRegisterClick} href="#">
|
<a className="mx_AuthBody_changeFlow" onClick={this.onTryRegisterClick} href="#">
|
||||||
{ _t('Create account') }
|
{ _t('Create account') }
|
||||||
|
|
|
@ -25,6 +25,7 @@ import * as sdk from '../../../index';
|
||||||
import { _t } from '../../../languageHandler';
|
import { _t } from '../../../languageHandler';
|
||||||
import SettingsStore from "../../../settings/SettingsStore";
|
import SettingsStore from "../../../settings/SettingsStore";
|
||||||
import AccessibleButton from "../elements/AccessibleButton";
|
import AccessibleButton from "../elements/AccessibleButton";
|
||||||
|
import Spinner from "../elements/Spinner";
|
||||||
|
|
||||||
/* This file contains a collection of components which are used by the
|
/* This file contains a collection of components which are used by the
|
||||||
* InteractiveAuth to prompt the user to enter the information needed
|
* InteractiveAuth to prompt the user to enter the information needed
|
||||||
|
@ -404,8 +405,12 @@ export class EmailIdentityAuthEntry extends React.Component {
|
||||||
// the validation link, we won't know the email address, so if we don't have it,
|
// the validation link, we won't know the email address, so if we don't have it,
|
||||||
// assume that the link has been clicked and the server will realise when we poll.
|
// assume that the link has been clicked and the server will realise when we poll.
|
||||||
if (this.props.inputs.emailAddress === undefined) {
|
if (this.props.inputs.emailAddress === undefined) {
|
||||||
const Loader = sdk.getComponent("elements.Spinner");
|
return <Spinner />;
|
||||||
return <Loader />;
|
} else if (this.props.stageState?.emailSid) {
|
||||||
|
// we only have a session ID if the user has clicked the link in their email,
|
||||||
|
// so show a loading state instead of "an email has been sent to..." because
|
||||||
|
// that's confusing when you've already read that email.
|
||||||
|
return <Spinner />;
|
||||||
} else {
|
} else {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
|
|
|
@ -15,10 +15,14 @@ limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import classNames from "classnames";
|
||||||
|
|
||||||
import * as sdk from '../../../index';
|
import * as sdk from '../../../index';
|
||||||
import SdkConfig from '../../../SdkConfig';
|
import SdkConfig from '../../../SdkConfig';
|
||||||
import AuthPage from "./AuthPage";
|
import AuthPage from "./AuthPage";
|
||||||
import {_td} from "../../../languageHandler";
|
import {_td} from "../../../languageHandler";
|
||||||
|
import SettingsStore from "../../../settings/SettingsStore";
|
||||||
|
import {UIFeature} from "../../../settings/UIFeature";
|
||||||
|
|
||||||
// translatable strings for Welcome pages
|
// translatable strings for Welcome pages
|
||||||
_td("Sign in with SSO");
|
_td("Sign in with SSO");
|
||||||
|
@ -39,7 +43,9 @@ export default class Welcome extends React.PureComponent {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AuthPage>
|
<AuthPage>
|
||||||
<div className="mx_Welcome">
|
<div className={classNames("mx_Welcome", {
|
||||||
|
mx_WelcomePage_registrationDisabled: !SettingsStore.getValue(UIFeature.Registration),
|
||||||
|
})}>
|
||||||
<EmbeddedPage
|
<EmbeddedPage
|
||||||
className="mx_WelcomePage"
|
className="mx_WelcomePage"
|
||||||
url={pageUrl}
|
url={pageUrl}
|
||||||
|
|
|
@ -16,23 +16,24 @@ limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import {RoomMember} from "matrix-js-sdk/src/models/room-member";
|
||||||
|
|
||||||
import dis from "../../../dispatcher/dispatcher";
|
import dis from "../../../dispatcher/dispatcher";
|
||||||
import {Action} from "../../../dispatcher/actions";
|
import {Action} from "../../../dispatcher/actions";
|
||||||
import {MatrixClientPeg} from "../../../MatrixClientPeg";
|
import {MatrixClientPeg} from "../../../MatrixClientPeg";
|
||||||
import BaseAvatar from "./BaseAvatar";
|
import BaseAvatar from "./BaseAvatar";
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
// TODO: replace with correct type
|
member: RoomMember;
|
||||||
member: any;
|
fallbackUserId?: string;
|
||||||
fallbackUserId: string;
|
|
||||||
width: number;
|
width: number;
|
||||||
height: number;
|
height: number;
|
||||||
resizeMethod: string;
|
resizeMethod?: string;
|
||||||
// The onClick to give the avatar
|
// The onClick to give the avatar
|
||||||
onClick: React.MouseEventHandler;
|
onClick?: React.MouseEventHandler;
|
||||||
// Whether the onClick of the avatar should be overriden to dispatch `Action.ViewUser`
|
// Whether the onClick of the avatar should be overriden to dispatch `Action.ViewUser`
|
||||||
viewUserOnClick: boolean;
|
viewUserOnClick?: boolean;
|
||||||
title: string;
|
title?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IState {
|
interface IState {
|
||||||
|
|
|
@ -37,7 +37,7 @@ interface IOptionListProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IOptionProps extends React.ComponentProps<typeof MenuItem> {
|
interface IOptionProps extends React.ComponentProps<typeof MenuItem> {
|
||||||
iconClassName: string;
|
iconClassName?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ICheckboxProps extends React.ComponentProps<typeof MenuItemCheckbox> {
|
interface ICheckboxProps extends React.ComponentProps<typeof MenuItemCheckbox> {
|
||||||
|
@ -92,7 +92,7 @@ export const IconizedContextMenuCheckbox: React.FC<ICheckboxProps> = ({
|
||||||
|
|
||||||
export const IconizedContextMenuOption: React.FC<IOptionProps> = ({label, iconClassName, ...props}) => {
|
export const IconizedContextMenuOption: React.FC<IOptionProps> = ({label, iconClassName, ...props}) => {
|
||||||
return <MenuItem {...props} label={label}>
|
return <MenuItem {...props} label={label}>
|
||||||
<span className={classNames("mx_IconizedContextMenu_icon", iconClassName)} />
|
{ iconClassName && <span className={classNames("mx_IconizedContextMenu_icon", iconClassName)} /> }
|
||||||
<span className="mx_IconizedContextMenu_label">{label}</span>
|
<span className="mx_IconizedContextMenu_label">{label}</span>
|
||||||
</MenuItem>;
|
</MenuItem>;
|
||||||
};
|
};
|
||||||
|
|
|
@ -26,6 +26,9 @@ export default class WidgetContextMenu extends React.Component {
|
||||||
// Callback for when the revoke button is clicked. Required.
|
// Callback for when the revoke button is clicked. Required.
|
||||||
onRevokeClicked: PropTypes.func.isRequired,
|
onRevokeClicked: PropTypes.func.isRequired,
|
||||||
|
|
||||||
|
// Callback for when the unpin button is clicked. If absent, unpin will be hidden.
|
||||||
|
onUnpinClicked: PropTypes.func,
|
||||||
|
|
||||||
// Callback for when the snapshot button is clicked. Button not shown
|
// Callback for when the snapshot button is clicked. Button not shown
|
||||||
// without a callback.
|
// without a callback.
|
||||||
onSnapshotClicked: PropTypes.func,
|
onSnapshotClicked: PropTypes.func,
|
||||||
|
@ -70,6 +73,8 @@ export default class WidgetContextMenu extends React.Component {
|
||||||
this.proxyClick(this.props.onRevokeClicked);
|
this.proxyClick(this.props.onRevokeClicked);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
onUnpinClicked = () => this.proxyClick(this.props.onUnpinClicked);
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const options = [];
|
const options = [];
|
||||||
|
|
||||||
|
@ -81,6 +86,14 @@ export default class WidgetContextMenu extends React.Component {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.props.onUnpinClicked) {
|
||||||
|
options.push(
|
||||||
|
<MenuItem className="mx_WidgetContextMenu_option" onClick={this.onUnpinClicked} key="unpin">
|
||||||
|
{_t("Unpin")}
|
||||||
|
</MenuItem>,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (this.props.onReloadClicked) {
|
if (this.props.onReloadClicked) {
|
||||||
options.push(
|
options.push(
|
||||||
<MenuItem className='mx_WidgetContextMenu_option' onClick={this.onReloadClicked} key='reload'>
|
<MenuItem className='mx_WidgetContextMenu_option' onClick={this.onReloadClicked} key='reload'>
|
||||||
|
|
|
@ -34,7 +34,7 @@ export default class BugReportDialog extends React.Component {
|
||||||
busy: false,
|
busy: false,
|
||||||
err: null,
|
err: null,
|
||||||
issueUrl: "",
|
issueUrl: "",
|
||||||
text: "",
|
text: props.initialText || "",
|
||||||
progress: null,
|
progress: null,
|
||||||
downloadBusy: false,
|
downloadBusy: false,
|
||||||
downloadProgress: null,
|
downloadProgress: null,
|
||||||
|
@ -255,4 +255,5 @@ export default class BugReportDialog extends React.Component {
|
||||||
|
|
||||||
BugReportDialog.propTypes = {
|
BugReportDialog.propTypes = {
|
||||||
onFinished: PropTypes.func.isRequired,
|
onFinished: PropTypes.func.isRequired,
|
||||||
|
initialText: PropTypes.string,
|
||||||
};
|
};
|
||||||
|
|
|
@ -45,7 +45,11 @@ export default class CreateRoomDialog extends React.Component {
|
||||||
detailsOpen: false,
|
detailsOpen: false,
|
||||||
noFederate: config.default_federate === false,
|
noFederate: config.default_federate === false,
|
||||||
nameIsValid: false,
|
nameIsValid: false,
|
||||||
|
canChangeEncryption: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
MatrixClientPeg.get().doesServerForceEncryptionForPreset("private")
|
||||||
|
.then(isForced => this.setState({canChangeEncryption: !isForced}));
|
||||||
}
|
}
|
||||||
|
|
||||||
_roomCreateOptions() {
|
_roomCreateOptions() {
|
||||||
|
@ -68,7 +72,13 @@ export default class CreateRoomDialog extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this.state.isPublic) {
|
if (!this.state.isPublic) {
|
||||||
opts.encryption = this.state.isEncrypted;
|
if (this.state.canChangeEncryption) {
|
||||||
|
opts.encryption = this.state.isEncrypted;
|
||||||
|
} else {
|
||||||
|
// the server should automatically do this for us, but for safety
|
||||||
|
// we'll demand it too.
|
||||||
|
opts.encryption = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (CommunityPrototypeStore.instance.getSelectedCommunityId()) {
|
if (CommunityPrototypeStore.instance.getSelectedCommunityId()) {
|
||||||
|
@ -208,7 +218,11 @@ export default class CreateRoomDialog extends React.Component {
|
||||||
if (!this.state.isPublic) {
|
if (!this.state.isPublic) {
|
||||||
let microcopy;
|
let microcopy;
|
||||||
if (privateShouldBeEncrypted()) {
|
if (privateShouldBeEncrypted()) {
|
||||||
microcopy = _t("You can’t disable this later. Bridges & most bots won’t work yet.");
|
if (this.state.canChangeEncryption) {
|
||||||
|
microcopy = _t("You can’t disable this later. Bridges & most bots won’t work yet.");
|
||||||
|
} else {
|
||||||
|
microcopy = _t("Your server requires encryption to be enabled in private rooms.");
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
microcopy = _t("Your server admin has disabled end-to-end encryption by default " +
|
microcopy = _t("Your server admin has disabled end-to-end encryption by default " +
|
||||||
"in private rooms & Direct Messages.");
|
"in private rooms & Direct Messages.");
|
||||||
|
@ -219,6 +233,7 @@ export default class CreateRoomDialog extends React.Component {
|
||||||
onChange={this.onEncryptedChange}
|
onChange={this.onEncryptedChange}
|
||||||
value={this.state.isEncrypted}
|
value={this.state.isEncrypted}
|
||||||
className='mx_CreateRoomDialog_e2eSwitch' // for end-to-end tests
|
className='mx_CreateRoomDialog_e2eSwitch' // for end-to-end tests
|
||||||
|
disabled={!this.state.canChangeEncryption}
|
||||||
/>
|
/>
|
||||||
<p>{ microcopy }</p>
|
<p>{ microcopy }</p>
|
||||||
</React.Fragment>;
|
</React.Fragment>;
|
||||||
|
|
|
@ -38,6 +38,8 @@ import {Action} from "../../../dispatcher/actions";
|
||||||
import {DefaultTagID} from "../../../stores/room-list/models";
|
import {DefaultTagID} from "../../../stores/room-list/models";
|
||||||
import RoomListStore from "../../../stores/room-list/RoomListStore";
|
import RoomListStore from "../../../stores/room-list/RoomListStore";
|
||||||
import {CommunityPrototypeStore} from "../../../stores/CommunityPrototypeStore";
|
import {CommunityPrototypeStore} from "../../../stores/CommunityPrototypeStore";
|
||||||
|
import SettingsStore from "../../../settings/SettingsStore";
|
||||||
|
import {UIFeature} from "../../../settings/UIFeature";
|
||||||
|
|
||||||
// we have a number of types defined from the Matrix spec which can't reasonably be altered here.
|
// we have a number of types defined from the Matrix spec which can't reasonably be altered here.
|
||||||
/* eslint-disable camelcase */
|
/* eslint-disable camelcase */
|
||||||
|
@ -549,7 +551,7 @@ export default class InviteDialog extends React.PureComponent {
|
||||||
if (this.state.filterText.startsWith('@')) {
|
if (this.state.filterText.startsWith('@')) {
|
||||||
// Assume mxid
|
// Assume mxid
|
||||||
newMember = new DirectoryMember({user_id: this.state.filterText, display_name: null, avatar_url: null});
|
newMember = new DirectoryMember({user_id: this.state.filterText, display_name: null, avatar_url: null});
|
||||||
} else {
|
} else if (SettingsStore.getValue(UIFeature.IdentityServer)) {
|
||||||
// Assume email
|
// Assume email
|
||||||
newMember = new ThreepidMember(this.state.filterText);
|
newMember = new ThreepidMember(this.state.filterText);
|
||||||
}
|
}
|
||||||
|
@ -734,7 +736,7 @@ export default class InviteDialog extends React.PureComponent {
|
||||||
this.setState({tryingIdentityServer: true});
|
this.setState({tryingIdentityServer: true});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (term.indexOf('@') > 0 && Email.looksValid(term)) {
|
if (term.indexOf('@') > 0 && Email.looksValid(term) && SettingsStore.getValue(UIFeature.IdentityServer)) {
|
||||||
// Start off by suggesting the plain email while we try and resolve it
|
// Start off by suggesting the plain email while we try and resolve it
|
||||||
// to a real account.
|
// to a real account.
|
||||||
this.setState({
|
this.setState({
|
||||||
|
@ -1037,7 +1039,9 @@ export default class InviteDialog extends React.PureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
_renderIdentityServerWarning() {
|
_renderIdentityServerWarning() {
|
||||||
if (!this.state.tryingIdentityServer || this.state.canUseIdentityServer) {
|
if (!this.state.tryingIdentityServer || this.state.canUseIdentityServer ||
|
||||||
|
!SettingsStore.getValue(UIFeature.IdentityServer)
|
||||||
|
) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1086,22 +1090,38 @@ export default class InviteDialog extends React.PureComponent {
|
||||||
let buttonText;
|
let buttonText;
|
||||||
let goButtonFn;
|
let goButtonFn;
|
||||||
|
|
||||||
|
const identityServersEnabled = SettingsStore.getValue(UIFeature.IdentityServer);
|
||||||
|
|
||||||
const userId = MatrixClientPeg.get().getUserId();
|
const userId = MatrixClientPeg.get().getUserId();
|
||||||
if (this.props.kind === KIND_DM) {
|
if (this.props.kind === KIND_DM) {
|
||||||
title = _t("Direct Messages");
|
title = _t("Direct Messages");
|
||||||
helpText = _t(
|
|
||||||
"Start a conversation with someone using their name, username (like <userId/>) or email address.",
|
if (identityServersEnabled) {
|
||||||
{},
|
helpText = _t(
|
||||||
{userId: () => {
|
"Start a conversation with someone using their name, username (like <userId/>) or email address.",
|
||||||
return <a href={makeUserPermalink(userId)} rel="noreferrer noopener" target="_blank">{userId}</a>;
|
{},
|
||||||
}},
|
{userId: () => {
|
||||||
);
|
return (
|
||||||
|
<a href={makeUserPermalink(userId)} rel="noreferrer noopener" target="_blank">{userId}</a>
|
||||||
|
);
|
||||||
|
}},
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
helpText = _t(
|
||||||
|
"Start a conversation with someone using their name or username (like <userId/>).",
|
||||||
|
{},
|
||||||
|
{userId: () => {
|
||||||
|
return (
|
||||||
|
<a href={makeUserPermalink(userId)} rel="noreferrer noopener" target="_blank">{userId}</a>
|
||||||
|
);
|
||||||
|
}},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (CommunityPrototypeStore.instance.getSelectedCommunityId()) {
|
if (CommunityPrototypeStore.instance.getSelectedCommunityId()) {
|
||||||
const communityName = CommunityPrototypeStore.instance.getSelectedCommunityName();
|
const communityName = CommunityPrototypeStore.instance.getSelectedCommunityName();
|
||||||
helpText = _t(
|
const inviteText = _t("This won't invite them to %(communityName)s. " +
|
||||||
"Start a conversation with someone using their name, username (like <userId/>) or email address. " +
|
"To invite someone to %(communityName)s, click <a>here</a>",
|
||||||
"This won't invite them to %(communityName)s. To invite someone to %(communityName)s, click " +
|
|
||||||
"<a>here</a>.",
|
|
||||||
{communityName}, {
|
{communityName}, {
|
||||||
userId: () => {
|
userId: () => {
|
||||||
return (
|
return (
|
||||||
|
@ -1122,21 +1142,40 @@ export default class InviteDialog extends React.PureComponent {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
helpText = <React.Fragment>
|
||||||
|
{ helpText } {inviteText}
|
||||||
|
</React.Fragment>;
|
||||||
}
|
}
|
||||||
buttonText = _t("Go");
|
buttonText = _t("Go");
|
||||||
goButtonFn = this._startDm;
|
goButtonFn = this._startDm;
|
||||||
} else { // KIND_INVITE
|
} else { // KIND_INVITE
|
||||||
title = _t("Invite to this room");
|
title = _t("Invite to this room");
|
||||||
helpText = _t(
|
|
||||||
"Invite someone using their name, username (like <userId/>), email address or <a>share this room</a>.",
|
if (identityServersEnabled) {
|
||||||
{},
|
helpText = _t(
|
||||||
{
|
"Invite someone using their name, username (like <userId/>), email address or " +
|
||||||
userId: () =>
|
"<a>share this room</a>.",
|
||||||
<a href={makeUserPermalink(userId)} rel="noreferrer noopener" target="_blank">{userId}</a>,
|
{},
|
||||||
a: (sub) =>
|
{
|
||||||
<a href={makeRoomPermalink(this.props.roomId)} rel="noreferrer noopener" target="_blank">{sub}</a>,
|
userId: () =>
|
||||||
},
|
<a href={makeUserPermalink(userId)} rel="noreferrer noopener" target="_blank">{userId}</a>,
|
||||||
);
|
a: (sub) =>
|
||||||
|
<a href={makeRoomPermalink(this.props.roomId)} rel="noreferrer noopener" target="_blank">{sub}</a>,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
helpText = _t(
|
||||||
|
"Invite someone using their name, username (like <userId/>) or <a>share this room</a>.",
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
userId: () =>
|
||||||
|
<a href={makeUserPermalink(userId)} rel="noreferrer noopener" target="_blank">{userId}</a>,
|
||||||
|
a: (sub) =>
|
||||||
|
<a href={makeRoomPermalink(this.props.roomId)} rel="noreferrer noopener" target="_blank">{sub}</a>,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
buttonText = _t("Invite");
|
buttonText = _t("Invite");
|
||||||
goButtonFn = this._inviteUsers;
|
goButtonFn = this._inviteUsers;
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,7 +20,8 @@ import Modal from '../../../Modal';
|
||||||
import * as sdk from '../../../index';
|
import * as sdk from '../../../index';
|
||||||
import dis from '../../../dispatcher/dispatcher';
|
import dis from '../../../dispatcher/dispatcher';
|
||||||
import { _t } from '../../../languageHandler';
|
import { _t } from '../../../languageHandler';
|
||||||
import {MatrixClientPeg} from '../../../MatrixClientPeg';
|
import { MatrixClientPeg } from '../../../MatrixClientPeg';
|
||||||
|
import RestoreKeyBackupDialog from './security/RestoreKeyBackupDialog';
|
||||||
|
|
||||||
export default class LogoutDialog extends React.Component {
|
export default class LogoutDialog extends React.Component {
|
||||||
defaultProps = {
|
defaultProps = {
|
||||||
|
@ -73,7 +74,7 @@ export default class LogoutDialog extends React.Component {
|
||||||
|
|
||||||
_onExportE2eKeysClicked() {
|
_onExportE2eKeysClicked() {
|
||||||
Modal.createTrackedDialogAsync('Export E2E Keys', '',
|
Modal.createTrackedDialogAsync('Export E2E Keys', '',
|
||||||
import('../../../async-components/views/dialogs/ExportE2eKeysDialog'),
|
import('../../../async-components/views/dialogs/security/ExportE2eKeysDialog'),
|
||||||
{
|
{
|
||||||
matrixClient: MatrixClientPeg.get(),
|
matrixClient: MatrixClientPeg.get(),
|
||||||
},
|
},
|
||||||
|
@ -93,14 +94,13 @@ export default class LogoutDialog extends React.Component {
|
||||||
// A key backup exists for this account, but the creating device is not
|
// A key backup exists for this account, but the creating device is not
|
||||||
// verified, so restore the backup which will give us the keys from it and
|
// verified, so restore the backup which will give us the keys from it and
|
||||||
// allow us to trust it (ie. upload keys to it)
|
// allow us to trust it (ie. upload keys to it)
|
||||||
const RestoreKeyBackupDialog = sdk.getComponent('dialogs.keybackup.RestoreKeyBackupDialog');
|
|
||||||
Modal.createTrackedDialog(
|
Modal.createTrackedDialog(
|
||||||
'Restore Backup', '', RestoreKeyBackupDialog, null, null,
|
'Restore Backup', '', RestoreKeyBackupDialog, null, null,
|
||||||
/* priority = */ false, /* static = */ true,
|
/* priority = */ false, /* static = */ true,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
Modal.createTrackedDialogAsync("Key Backup", "Key Backup",
|
Modal.createTrackedDialogAsync("Key Backup", "Key Backup",
|
||||||
import("../../../async-components/views/dialogs/keybackup/CreateKeyBackupDialog"),
|
import("../../../async-components/views/dialogs/security/CreateKeyBackupDialog"),
|
||||||
null, null, /* priority = */ false, /* static = */ true,
|
null, null, /* priority = */ false, /* static = */ true,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,6 +29,7 @@ import * as sdk from "../../../index";
|
||||||
import {MatrixClientPeg} from "../../../MatrixClientPeg";
|
import {MatrixClientPeg} from "../../../MatrixClientPeg";
|
||||||
import dis from "../../../dispatcher/dispatcher";
|
import dis from "../../../dispatcher/dispatcher";
|
||||||
import SettingsStore from "../../../settings/SettingsStore";
|
import SettingsStore from "../../../settings/SettingsStore";
|
||||||
|
import {UIFeature} from "../../../settings/UIFeature";
|
||||||
|
|
||||||
export const ROOM_GENERAL_TAB = "ROOM_GENERAL_TAB";
|
export const ROOM_GENERAL_TAB = "ROOM_GENERAL_TAB";
|
||||||
export const ROOM_SECURITY_TAB = "ROOM_SECURITY_TAB";
|
export const ROOM_SECURITY_TAB = "ROOM_SECURITY_TAB";
|
||||||
|
@ -96,12 +97,14 @@ export default class RoomSettingsDialog extends React.Component {
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
tabs.push(new Tab(
|
if (SettingsStore.getValue(UIFeature.AdvancedSettings)) {
|
||||||
ROOM_ADVANCED_TAB,
|
tabs.push(new Tab(
|
||||||
_td("Advanced"),
|
ROOM_ADVANCED_TAB,
|
||||||
"mx_RoomSettingsDialog_warningIcon",
|
_td("Advanced"),
|
||||||
<AdvancedRoomSettingsTab roomId={this.props.roomId} closeSettingsFn={this.props.onFinished} />,
|
"mx_RoomSettingsDialog_warningIcon",
|
||||||
));
|
<AdvancedRoomSettingsTab roomId={this.props.roomId} closeSettingsFn={this.props.onFinished} />,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
return tabs;
|
return tabs;
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,6 +32,8 @@ import {copyPlaintext, selectText} from "../../../utils/strings";
|
||||||
import StyledCheckbox from '../elements/StyledCheckbox';
|
import StyledCheckbox from '../elements/StyledCheckbox';
|
||||||
import AccessibleTooltipButton from '../elements/AccessibleTooltipButton';
|
import AccessibleTooltipButton from '../elements/AccessibleTooltipButton';
|
||||||
import { IDialogProps } from "./IDialogProps";
|
import { IDialogProps } from "./IDialogProps";
|
||||||
|
import SettingsStore from "../../../settings/SettingsStore";
|
||||||
|
import {UIFeature} from "../../../settings/UIFeature";
|
||||||
|
|
||||||
const socials = [
|
const socials = [
|
||||||
{
|
{
|
||||||
|
@ -197,6 +199,35 @@ export default class ShareDialog extends React.PureComponent<IProps, IState> {
|
||||||
const matrixToUrl = this.getUrl();
|
const matrixToUrl = this.getUrl();
|
||||||
const encodedUrl = encodeURIComponent(matrixToUrl);
|
const encodedUrl = encodeURIComponent(matrixToUrl);
|
||||||
|
|
||||||
|
const showQrCode = SettingsStore.getValue(UIFeature.ShareQRCode);
|
||||||
|
const showSocials = SettingsStore.getValue(UIFeature.ShareSocial);
|
||||||
|
|
||||||
|
let qrSocialSection;
|
||||||
|
if (showQrCode || showSocials) {
|
||||||
|
qrSocialSection = <>
|
||||||
|
<hr />
|
||||||
|
<div className="mx_ShareDialog_split">
|
||||||
|
{ showQrCode && <div className="mx_ShareDialog_qrcode_container">
|
||||||
|
<QRCode data={matrixToUrl} width={256} />
|
||||||
|
</div> }
|
||||||
|
{ showSocials && <div className="mx_ShareDialog_social_container">
|
||||||
|
{ socials.map((social) => (
|
||||||
|
<a
|
||||||
|
rel="noreferrer noopener"
|
||||||
|
target="_blank"
|
||||||
|
key={social.name}
|
||||||
|
title={social.name}
|
||||||
|
href={social.url(encodedUrl)}
|
||||||
|
className="mx_ShareDialog_social_icon"
|
||||||
|
>
|
||||||
|
<img src={social.img} alt={social.name} height={64} width={64} />
|
||||||
|
</a>
|
||||||
|
)) }
|
||||||
|
</div> }
|
||||||
|
</div>
|
||||||
|
</>;
|
||||||
|
}
|
||||||
|
|
||||||
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
|
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
|
||||||
return <BaseDialog
|
return <BaseDialog
|
||||||
title={title}
|
title={title}
|
||||||
|
@ -220,27 +251,7 @@ export default class ShareDialog extends React.PureComponent<IProps, IState> {
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{ checkbox }
|
{ checkbox }
|
||||||
<hr />
|
{ qrSocialSection }
|
||||||
|
|
||||||
<div className="mx_ShareDialog_split">
|
|
||||||
<div className="mx_ShareDialog_qrcode_container">
|
|
||||||
<QRCode data={matrixToUrl} width={256} />
|
|
||||||
</div>
|
|
||||||
<div className="mx_ShareDialog_social_container">
|
|
||||||
{ socials.map((social) => (
|
|
||||||
<a
|
|
||||||
rel="noreferrer noopener"
|
|
||||||
target="_blank"
|
|
||||||
key={social.name}
|
|
||||||
title={social.name}
|
|
||||||
href={social.url(encodedUrl)}
|
|
||||||
className="mx_ShareDialog_social_icon"
|
|
||||||
>
|
|
||||||
<img src={social.img} alt={social.name} height={64} width={64} />
|
|
||||||
</a>
|
|
||||||
)) }
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</BaseDialog>;
|
</BaseDialog>;
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,6 +24,7 @@ export default ({onFinished}) => {
|
||||||
|
|
||||||
const categories = {};
|
const categories = {};
|
||||||
Commands.forEach(cmd => {
|
Commands.forEach(cmd => {
|
||||||
|
if (!cmd.isEnabled()) return;
|
||||||
if (!categories[cmd.category]) {
|
if (!categories[cmd.category]) {
|
||||||
categories[cmd.category] = [];
|
categories[cmd.category] = [];
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,6 +32,7 @@ import FlairUserSettingsTab from "../settings/tabs/user/FlairUserSettingsTab";
|
||||||
import * as sdk from "../../../index";
|
import * as sdk from "../../../index";
|
||||||
import SdkConfig from "../../../SdkConfig";
|
import SdkConfig from "../../../SdkConfig";
|
||||||
import MjolnirUserSettingsTab from "../settings/tabs/user/MjolnirUserSettingsTab";
|
import MjolnirUserSettingsTab from "../settings/tabs/user/MjolnirUserSettingsTab";
|
||||||
|
import {UIFeature} from "../../../settings/UIFeature";
|
||||||
|
|
||||||
export const USER_GENERAL_TAB = "USER_GENERAL_TAB";
|
export const USER_GENERAL_TAB = "USER_GENERAL_TAB";
|
||||||
export const USER_APPEARANCE_TAB = "USER_APPEARANCE_TAB";
|
export const USER_APPEARANCE_TAB = "USER_APPEARANCE_TAB";
|
||||||
|
@ -86,12 +87,14 @@ export default class UserSettingsDialog extends React.Component {
|
||||||
"mx_UserSettingsDialog_appearanceIcon",
|
"mx_UserSettingsDialog_appearanceIcon",
|
||||||
<AppearanceUserSettingsTab />,
|
<AppearanceUserSettingsTab />,
|
||||||
));
|
));
|
||||||
tabs.push(new Tab(
|
if (SettingsStore.getValue(UIFeature.Flair)) {
|
||||||
USER_FLAIR_TAB,
|
tabs.push(new Tab(
|
||||||
_td("Flair"),
|
USER_FLAIR_TAB,
|
||||||
"mx_UserSettingsDialog_flairIcon",
|
_td("Flair"),
|
||||||
<FlairUserSettingsTab />,
|
"mx_UserSettingsDialog_flairIcon",
|
||||||
));
|
<FlairUserSettingsTab />,
|
||||||
|
));
|
||||||
|
}
|
||||||
tabs.push(new Tab(
|
tabs.push(new Tab(
|
||||||
USER_NOTIFICATIONS_TAB,
|
USER_NOTIFICATIONS_TAB,
|
||||||
_td("Notifications"),
|
_td("Notifications"),
|
||||||
|
@ -104,12 +107,16 @@ export default class UserSettingsDialog extends React.Component {
|
||||||
"mx_UserSettingsDialog_preferencesIcon",
|
"mx_UserSettingsDialog_preferencesIcon",
|
||||||
<PreferencesUserSettingsTab />,
|
<PreferencesUserSettingsTab />,
|
||||||
));
|
));
|
||||||
tabs.push(new Tab(
|
|
||||||
USER_VOICE_TAB,
|
if (SettingsStore.getValue(UIFeature.Voip)) {
|
||||||
_td("Voice & Video"),
|
tabs.push(new Tab(
|
||||||
"mx_UserSettingsDialog_voiceIcon",
|
USER_VOICE_TAB,
|
||||||
<VoiceUserSettingsTab />,
|
_td("Voice & Video"),
|
||||||
));
|
"mx_UserSettingsDialog_voiceIcon",
|
||||||
|
<VoiceUserSettingsTab />,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
tabs.push(new Tab(
|
tabs.push(new Tab(
|
||||||
USER_SECURITY_TAB,
|
USER_SECURITY_TAB,
|
||||||
_td("Security & Privacy"),
|
_td("Security & Privacy"),
|
||||||
|
|
|
@ -16,8 +16,8 @@ limitations under the License.
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import {_t} from "../../../languageHandler";
|
import {_t} from "../../../../languageHandler";
|
||||||
import * as sdk from "../../../index";
|
import * as sdk from "../../../../index";
|
||||||
|
|
||||||
export default class ConfirmDestroyCrossSigningDialog extends React.Component {
|
export default class ConfirmDestroyCrossSigningDialog extends React.Component {
|
||||||
static propTypes = {
|
static propTypes = {
|