Merge branch 'develop' into forgot-password-validation
This commit is contained in:
commit
901d5a86d0
189 changed files with 5819 additions and 2184 deletions
123
CHANGELOG.md
123
CHANGELOG.md
|
@ -1,3 +1,126 @@
|
||||||
|
Changes in [3.16.0](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v3.16.0) (2021-03-15)
|
||||||
|
=====================================================================================================
|
||||||
|
[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v3.16.0-rc.2...v3.16.0)
|
||||||
|
|
||||||
|
* Upgrade to JS SDK 9.9.0
|
||||||
|
* [Release] Change read receipt drift to be non-fractional
|
||||||
|
[\#5746](https://github.com/matrix-org/matrix-react-sdk/pull/5746)
|
||||||
|
* [Release] Properly gate SpaceRoomView behind labs
|
||||||
|
[\#5750](https://github.com/matrix-org/matrix-react-sdk/pull/5750)
|
||||||
|
|
||||||
|
Changes in [3.16.0-rc.2](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v3.16.0-rc.2) (2021-03-10)
|
||||||
|
===============================================================================================================
|
||||||
|
[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v3.16.0-rc.1...v3.16.0-rc.2)
|
||||||
|
|
||||||
|
* Fixed incorrect build output in rc.1
|
||||||
|
|
||||||
|
Changes in [3.16.0-rc.1](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v3.16.0-rc.1) (2021-03-10)
|
||||||
|
===============================================================================================================
|
||||||
|
[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v3.15.0...v3.16.0-rc.1)
|
||||||
|
|
||||||
|
* Upgrade to JS SDK 9.9.0-rc.1
|
||||||
|
* Translations update from Weblate
|
||||||
|
[\#5743](https://github.com/matrix-org/matrix-react-sdk/pull/5743)
|
||||||
|
* Document behaviour of showReadReceipts=false for sent receipts
|
||||||
|
[\#5739](https://github.com/matrix-org/matrix-react-sdk/pull/5739)
|
||||||
|
* Tweak sent marker code style
|
||||||
|
[\#5741](https://github.com/matrix-org/matrix-react-sdk/pull/5741)
|
||||||
|
* Fix sent markers disappearing for edits/reactions
|
||||||
|
[\#5737](https://github.com/matrix-org/matrix-react-sdk/pull/5737)
|
||||||
|
* Ignore to-device decryption in the room list store
|
||||||
|
[\#5740](https://github.com/matrix-org/matrix-react-sdk/pull/5740)
|
||||||
|
* Spaces suggested rooms support
|
||||||
|
[\#5736](https://github.com/matrix-org/matrix-react-sdk/pull/5736)
|
||||||
|
* Add tooltips to sent/sending receipts
|
||||||
|
[\#5738](https://github.com/matrix-org/matrix-react-sdk/pull/5738)
|
||||||
|
* Remove a bunch of useless 'use strict' definitions
|
||||||
|
[\#5735](https://github.com/matrix-org/matrix-react-sdk/pull/5735)
|
||||||
|
* [SK-1] Fix types for replaceableComponent
|
||||||
|
[\#5732](https://github.com/matrix-org/matrix-react-sdk/pull/5732)
|
||||||
|
* [SK-2] Make debugging skinning problems easier
|
||||||
|
[\#5733](https://github.com/matrix-org/matrix-react-sdk/pull/5733)
|
||||||
|
* Support sending invite reasons with /invite command
|
||||||
|
[\#5695](https://github.com/matrix-org/matrix-react-sdk/pull/5695)
|
||||||
|
* Fix clicking on the avatar for opening member info requires pixel-perfect
|
||||||
|
accuracy
|
||||||
|
[\#5717](https://github.com/matrix-org/matrix-react-sdk/pull/5717)
|
||||||
|
* Display decrypted and encrypted event source on the same dialog
|
||||||
|
[\#5713](https://github.com/matrix-org/matrix-react-sdk/pull/5713)
|
||||||
|
* Fix units of TURN server expiry time
|
||||||
|
[\#5730](https://github.com/matrix-org/matrix-react-sdk/pull/5730)
|
||||||
|
* Display room name in pills instead of address
|
||||||
|
[\#5624](https://github.com/matrix-org/matrix-react-sdk/pull/5624)
|
||||||
|
* Refresh UI for file uploads
|
||||||
|
[\#5723](https://github.com/matrix-org/matrix-react-sdk/pull/5723)
|
||||||
|
* UI refresh for uploaded files
|
||||||
|
[\#5719](https://github.com/matrix-org/matrix-react-sdk/pull/5719)
|
||||||
|
* Improve message sending states to match new designs
|
||||||
|
[\#5699](https://github.com/matrix-org/matrix-react-sdk/pull/5699)
|
||||||
|
* Add clipboard write permission for widgets
|
||||||
|
[\#5725](https://github.com/matrix-org/matrix-react-sdk/pull/5725)
|
||||||
|
* Fix widget resizing
|
||||||
|
[\#5722](https://github.com/matrix-org/matrix-react-sdk/pull/5722)
|
||||||
|
* Option for audio streaming
|
||||||
|
[\#5707](https://github.com/matrix-org/matrix-react-sdk/pull/5707)
|
||||||
|
* Show a specific error for hs_disabled
|
||||||
|
[\#5576](https://github.com/matrix-org/matrix-react-sdk/pull/5576)
|
||||||
|
* Add Edge to the targets list
|
||||||
|
[\#5721](https://github.com/matrix-org/matrix-react-sdk/pull/5721)
|
||||||
|
* File drop UI fixes and improvements
|
||||||
|
[\#5505](https://github.com/matrix-org/matrix-react-sdk/pull/5505)
|
||||||
|
* Fix Bottom border of state counters is white on the dark theme
|
||||||
|
[\#5715](https://github.com/matrix-org/matrix-react-sdk/pull/5715)
|
||||||
|
* Trim spurious whitespace of nicknames
|
||||||
|
[\#5332](https://github.com/matrix-org/matrix-react-sdk/pull/5332)
|
||||||
|
* Ensure HostSignupDialog border colour matches light theme
|
||||||
|
[\#5716](https://github.com/matrix-org/matrix-react-sdk/pull/5716)
|
||||||
|
* Don't place another call if there's already one ongoing
|
||||||
|
[\#5712](https://github.com/matrix-org/matrix-react-sdk/pull/5712)
|
||||||
|
* Space room hierarchies
|
||||||
|
[\#5706](https://github.com/matrix-org/matrix-react-sdk/pull/5706)
|
||||||
|
* Iterate Space view and right panel
|
||||||
|
[\#5705](https://github.com/matrix-org/matrix-react-sdk/pull/5705)
|
||||||
|
* Add a scroll to bottom on message sent setting
|
||||||
|
[\#5692](https://github.com/matrix-org/matrix-react-sdk/pull/5692)
|
||||||
|
* Add .tmp files to gitignore
|
||||||
|
[\#5708](https://github.com/matrix-org/matrix-react-sdk/pull/5708)
|
||||||
|
* Initial Space Room View and Creation UX
|
||||||
|
[\#5704](https://github.com/matrix-org/matrix-react-sdk/pull/5704)
|
||||||
|
* Add multi language spell check
|
||||||
|
[\#5452](https://github.com/matrix-org/matrix-react-sdk/pull/5452)
|
||||||
|
* Fix tetris effect (holes) in read receipts
|
||||||
|
[\#5697](https://github.com/matrix-org/matrix-react-sdk/pull/5697)
|
||||||
|
* Fixed edit for markdown images
|
||||||
|
[\#5703](https://github.com/matrix-org/matrix-react-sdk/pull/5703)
|
||||||
|
* Iterate Space Panel
|
||||||
|
[\#5702](https://github.com/matrix-org/matrix-react-sdk/pull/5702)
|
||||||
|
* Fix read receipts for compact layout
|
||||||
|
[\#5700](https://github.com/matrix-org/matrix-react-sdk/pull/5700)
|
||||||
|
* Space Store and Space Panel for Room List filtering
|
||||||
|
[\#5689](https://github.com/matrix-org/matrix-react-sdk/pull/5689)
|
||||||
|
* Log when turn creds expire
|
||||||
|
[\#5691](https://github.com/matrix-org/matrix-react-sdk/pull/5691)
|
||||||
|
* Null check for maxHeight in call view
|
||||||
|
[\#5690](https://github.com/matrix-org/matrix-react-sdk/pull/5690)
|
||||||
|
* Autocomplete invited users
|
||||||
|
[\#5687](https://github.com/matrix-org/matrix-react-sdk/pull/5687)
|
||||||
|
* Add send message button
|
||||||
|
[\#5535](https://github.com/matrix-org/matrix-react-sdk/pull/5535)
|
||||||
|
* Move call buttons to the room header
|
||||||
|
[\#5693](https://github.com/matrix-org/matrix-react-sdk/pull/5693)
|
||||||
|
* Use the default SSSS key if the default is set
|
||||||
|
[\#5638](https://github.com/matrix-org/matrix-react-sdk/pull/5638)
|
||||||
|
* Initial Spaces feature flag
|
||||||
|
[\#5668](https://github.com/matrix-org/matrix-react-sdk/pull/5668)
|
||||||
|
* Clean up code edge cases and add helpers
|
||||||
|
[\#5667](https://github.com/matrix-org/matrix-react-sdk/pull/5667)
|
||||||
|
* Clean up widgets when leaving the room
|
||||||
|
[\#5684](https://github.com/matrix-org/matrix-react-sdk/pull/5684)
|
||||||
|
* Fix read receipts?
|
||||||
|
[\#5567](https://github.com/matrix-org/matrix-react-sdk/pull/5567)
|
||||||
|
* Fix MAU usage alerts
|
||||||
|
[\#5678](https://github.com/matrix-org/matrix-react-sdk/pull/5678)
|
||||||
|
|
||||||
Changes in [3.15.0](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v3.15.0) (2021-03-01)
|
Changes in [3.15.0](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v3.15.0) (2021-03-01)
|
||||||
=====================================================================================================
|
=====================================================================================================
|
||||||
[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v3.15.0-rc.1...v3.15.0)
|
[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v3.15.0-rc.1...v3.15.0)
|
||||||
|
|
|
@ -21,14 +21,14 @@ caret nodes (more on that later).
|
||||||
For these reasons it doesn't use `innerText`, `textContent` or anything similar.
|
For these reasons it doesn't use `innerText`, `textContent` or anything similar.
|
||||||
The model addresses any content in the editor within as an offset within this string.
|
The model addresses any content in the editor within as an offset within this string.
|
||||||
The caret position is thus also converted from a position in the DOM tree
|
The caret position is thus also converted from a position in the DOM tree
|
||||||
to an offset in the content string. This happens in `getCaretOffsetAndText` in `dom.js`.
|
to an offset in the content string. This happens in `getCaretOffsetAndText` in `dom.ts`.
|
||||||
|
|
||||||
Once the content string and caret offset is calculated, it is passed to the `update()`
|
Once the content string and caret offset is calculated, it is passed to the `update()`
|
||||||
method of the model. The model first calculates the same content string of its current parts,
|
method of the model. The model first calculates the same content string of its current parts,
|
||||||
basically just concatenating their text. It then looks for differences between
|
basically just concatenating their text. It then looks for differences between
|
||||||
the current and the new content string. The diffing algorithm is very basic,
|
the current and the new content string. The diffing algorithm is very basic,
|
||||||
and assumes there is only one change around the caret offset,
|
and assumes there is only one change around the caret offset,
|
||||||
so this should be very inexpensive. See `diff.js` for details.
|
so this should be very inexpensive. See `diff.ts` for details.
|
||||||
|
|
||||||
The result of the diffing is the strings that were added and/or removed from
|
The result of the diffing is the strings that were added and/or removed from
|
||||||
the current content. These differences are then applied to the parts,
|
the current content. These differences are then applied to the parts,
|
||||||
|
@ -51,7 +51,7 @@ which relate poorly to text input or changes, and don't need the `beforeinput` e
|
||||||
which isn't broadly supported yet.
|
which isn't broadly supported yet.
|
||||||
|
|
||||||
Once the parts of the model are updated, the DOM of the editor is then reconciled
|
Once the parts of the model are updated, the DOM of the editor is then reconciled
|
||||||
with the new model state, see `renderModel` in `render.js` for this.
|
with the new model state, see `renderModel` in `render.ts` for this.
|
||||||
If the model didn't reject the input and didn't make any additional changes,
|
If the model didn't reject the input and didn't make any additional changes,
|
||||||
this won't make any changes to the DOM at all, and should thus be fairly efficient.
|
this won't make any changes to the DOM at all, and should thus be fairly efficient.
|
||||||
|
|
||||||
|
|
19
docs/media-handling.md
Normal file
19
docs/media-handling.md
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
# Media handling
|
||||||
|
|
||||||
|
Surely media should be as easy as just putting a URL into an `img` and calling it good, right?
|
||||||
|
Not quite. Matrix uses something called a Matrix Content URI (better known as MXC URI) to identify
|
||||||
|
content, which is then converted to a regular HTTPS URL on the homeserver. However, sometimes that
|
||||||
|
URL can change depending on deployment considerations.
|
||||||
|
|
||||||
|
The react-sdk features a [customisation endpoint](https://github.com/vector-im/element-web/blob/develop/docs/customisations.md)
|
||||||
|
for media handling where all conversions from MXC URI to HTTPS URL happen. This is to ensure that
|
||||||
|
those obscure deployments can route all their media to the right place.
|
||||||
|
|
||||||
|
For development, there are currently two functions available: `mediaFromMxc` and `mediaFromContent`.
|
||||||
|
The `mediaFromMxc` function should be self-explanatory. `mediaFromContent` takes an event content as
|
||||||
|
a parameter and will automatically parse out the source media and thumbnail. Both functions return
|
||||||
|
a `Media` object with a number of options on it, such as getting various common HTTPS URLs for the
|
||||||
|
media.
|
||||||
|
|
||||||
|
**It is extremely important that all media calls are put through this customisation endpoint.** So
|
||||||
|
much so it's a lint rule to avoid accidental use of the wrong functions.
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "matrix-react-sdk",
|
"name": "matrix-react-sdk",
|
||||||
"version": "3.15.0",
|
"version": "3.16.0",
|
||||||
"description": "SDK for matrix.org using React",
|
"description": "SDK for matrix.org using React",
|
||||||
"author": "matrix.org",
|
"author": "matrix.org",
|
||||||
"repository": {
|
"repository": {
|
||||||
|
@ -83,6 +83,7 @@
|
||||||
"matrix-js-sdk": "github:matrix-org/matrix-js-sdk#develop",
|
"matrix-js-sdk": "github:matrix-org/matrix-js-sdk#develop",
|
||||||
"matrix-widget-api": "^0.1.0-beta.13",
|
"matrix-widget-api": "^0.1.0-beta.13",
|
||||||
"minimist": "^1.2.5",
|
"minimist": "^1.2.5",
|
||||||
|
"opus-recorder": "^8.0.3",
|
||||||
"pako": "^2.0.3",
|
"pako": "^2.0.3",
|
||||||
"parse5": "^6.0.1",
|
"parse5": "^6.0.1",
|
||||||
"png-chunks-extract": "^1.0.0",
|
"png-chunks-extract": "^1.0.0",
|
||||||
|
@ -157,6 +158,7 @@
|
||||||
"jest": "^26.6.3",
|
"jest": "^26.6.3",
|
||||||
"jest-canvas-mock": "^2.3.0",
|
"jest-canvas-mock": "^2.3.0",
|
||||||
"jest-environment-jsdom-sixteen": "^1.0.3",
|
"jest-environment-jsdom-sixteen": "^1.0.3",
|
||||||
|
"jest-fetch-mock": "^3.0.3",
|
||||||
"matrix-mock-request": "^1.2.3",
|
"matrix-mock-request": "^1.2.3",
|
||||||
"matrix-react-test-utils": "^0.2.2",
|
"matrix-react-test-utils": "^0.2.2",
|
||||||
"olm": "https://packages.matrix.org/npm/olm/olm-3.2.1.tgz",
|
"olm": "https://packages.matrix.org/npm/olm/olm-3.2.1.tgz",
|
||||||
|
|
|
@ -395,6 +395,7 @@ input[type=text]:focus, input[type=password]:focus, textarea:focus {
|
||||||
border: 1px solid $accent-color;
|
border: 1px solid $accent-color;
|
||||||
color: $accent-color;
|
color: $accent-color;
|
||||||
background-color: $button-secondary-bg-color;
|
background-color: $button-secondary-bg-color;
|
||||||
|
font-family: inherit;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_Dialog button:last-child {
|
.mx_Dialog button:last-child {
|
||||||
|
|
|
@ -111,8 +111,8 @@
|
||||||
@import "./views/elements/_AddressSelector.scss";
|
@import "./views/elements/_AddressSelector.scss";
|
||||||
@import "./views/elements/_AddressTile.scss";
|
@import "./views/elements/_AddressTile.scss";
|
||||||
@import "./views/elements/_DesktopBuildsNotice.scss";
|
@import "./views/elements/_DesktopBuildsNotice.scss";
|
||||||
@import "./views/elements/_DirectorySearchBox.scss";
|
|
||||||
@import "./views/elements/_DesktopCapturerSourcePicker.scss";
|
@import "./views/elements/_DesktopCapturerSourcePicker.scss";
|
||||||
|
@import "./views/elements/_DirectorySearchBox.scss";
|
||||||
@import "./views/elements/_Dropdown.scss";
|
@import "./views/elements/_Dropdown.scss";
|
||||||
@import "./views/elements/_EditableItemList.scss";
|
@import "./views/elements/_EditableItemList.scss";
|
||||||
@import "./views/elements/_ErrorBoundary.scss";
|
@import "./views/elements/_ErrorBoundary.scss";
|
||||||
|
@ -211,13 +211,13 @@
|
||||||
@import "./views/rooms/_SendMessageComposer.scss";
|
@import "./views/rooms/_SendMessageComposer.scss";
|
||||||
@import "./views/rooms/_Stickers.scss";
|
@import "./views/rooms/_Stickers.scss";
|
||||||
@import "./views/rooms/_TopUnreadMessagesBar.scss";
|
@import "./views/rooms/_TopUnreadMessagesBar.scss";
|
||||||
|
@import "./views/rooms/_VoiceRecordComposerTile.scss";
|
||||||
@import "./views/rooms/_WhoIsTypingTile.scss";
|
@import "./views/rooms/_WhoIsTypingTile.scss";
|
||||||
@import "./views/settings/_AvatarSetting.scss";
|
@import "./views/settings/_AvatarSetting.scss";
|
||||||
@import "./views/settings/_CrossSigningPanel.scss";
|
@import "./views/settings/_CrossSigningPanel.scss";
|
||||||
@import "./views/settings/_DevicesPanel.scss";
|
@import "./views/settings/_DevicesPanel.scss";
|
||||||
@import "./views/settings/_E2eAdvancedPanel.scss";
|
@import "./views/settings/_E2eAdvancedPanel.scss";
|
||||||
@import "./views/settings/_EmailAddresses.scss";
|
@import "./views/settings/_EmailAddresses.scss";
|
||||||
@import "./views/settings/_SpellCheckLanguages.scss";
|
|
||||||
@import "./views/settings/_IntegrationManager.scss";
|
@import "./views/settings/_IntegrationManager.scss";
|
||||||
@import "./views/settings/_Notifications.scss";
|
@import "./views/settings/_Notifications.scss";
|
||||||
@import "./views/settings/_PhoneNumbers.scss";
|
@import "./views/settings/_PhoneNumbers.scss";
|
||||||
|
@ -225,6 +225,7 @@
|
||||||
@import "./views/settings/_SecureBackupPanel.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/_SpellCheckLanguages.scss";
|
||||||
@import "./views/settings/_UpdateCheckButton.scss";
|
@import "./views/settings/_UpdateCheckButton.scss";
|
||||||
@import "./views/settings/tabs/_SettingsTab.scss";
|
@import "./views/settings/tabs/_SettingsTab.scss";
|
||||||
@import "./views/settings/tabs/room/_GeneralRoomSettingsTab.scss";
|
@import "./views/settings/tabs/room/_GeneralRoomSettingsTab.scss";
|
||||||
|
|
|
@ -19,7 +19,8 @@ $roomListCollapsedWidth: 68px;
|
||||||
|
|
||||||
.mx_LeftPanel {
|
.mx_LeftPanel {
|
||||||
background-color: $roomlist-bg-color;
|
background-color: $roomlist-bg-color;
|
||||||
min-width: 260px;
|
// TODO decrease this once Spaces launches as it'll no longer need to include the 56px Community Panel
|
||||||
|
min-width: 206px;
|
||||||
max-width: 50%;
|
max-width: 50%;
|
||||||
|
|
||||||
// Create a row-based flexbox for the GroupFilterPanel and the room list
|
// Create a row-based flexbox for the GroupFilterPanel and the room list
|
||||||
|
@ -129,6 +130,10 @@ $roomListCollapsedWidth: 68px;
|
||||||
mask-repeat: no-repeat;
|
mask-repeat: no-repeat;
|
||||||
background: $secondary-fg-color;
|
background: $secondary-fg-color;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.mx_LeftPanel_exploreButton_space::before {
|
||||||
|
mask-image: url('$(res)/img/element-icons/roomlist/browse.svg');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -66,7 +66,7 @@ limitations under the License.
|
||||||
}
|
}
|
||||||
|
|
||||||
/* not the left panel, and not the resize handle, so the roomview/groupview/... */
|
/* not the left panel, and not the resize handle, so the roomview/groupview/... */
|
||||||
.mx_MatrixChat > :not(.mx_LeftPanel):not(.mx_ResizeHandle) {
|
.mx_MatrixChat > :not(.mx_LeftPanel):not(.mx_SpacePanel):not(.mx_ResizeHandle) {
|
||||||
background-color: $primary-bg-color;
|
background-color: $primary-bg-color;
|
||||||
|
|
||||||
flex: 1 1 0;
|
flex: 1 1 0;
|
||||||
|
|
|
@ -16,9 +16,8 @@ limitations under the License.
|
||||||
|
|
||||||
$topLevelHeight: 32px;
|
$topLevelHeight: 32px;
|
||||||
$nestedHeight: 24px;
|
$nestedHeight: 24px;
|
||||||
$gutterSize: 17px;
|
$gutterSize: 16px;
|
||||||
$activeStripeSize: 4px;
|
$activeBorderTransparentGap: 1px;
|
||||||
$activeBorderTransparentGap: 2px;
|
|
||||||
|
|
||||||
$activeBackgroundColor: $roomtile-selected-bg-color;
|
$activeBackgroundColor: $roomtile-selected-bg-color;
|
||||||
$activeBorderColor: $secondary-fg-color;
|
$activeBorderColor: $secondary-fg-color;
|
||||||
|
@ -36,6 +35,7 @@ $activeBorderColor: $secondary-fg-color;
|
||||||
|
|
||||||
.mx_SpacePanel_spaceTreeWrapper {
|
.mx_SpacePanel_spaceTreeWrapper {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
|
overflow-y: scroll;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_SpacePanel_toggleCollapse {
|
.mx_SpacePanel_toggleCollapse {
|
||||||
|
@ -63,21 +63,26 @@ $activeBorderColor: $secondary-fg-color;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_AutoHideScrollbar {
|
.mx_AutoHideScrollbar {
|
||||||
padding: 16px 12px 16px 0;
|
padding: 8px 0 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_SpaceButton_toggleCollapse {
|
.mx_SpaceButton_toggleCollapse {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_SpaceItem.collapsed {
|
.mx_SpaceTreeLevel {
|
||||||
.mx_SpaceButton {
|
display: flex;
|
||||||
.mx_NotificationBadge {
|
flex-direction: column;
|
||||||
right: -4px;
|
max-width: 250px;
|
||||||
top: -4px;
|
flex-grow: 1;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
.mx_SpaceItem {
|
||||||
|
display: inline-flex;
|
||||||
|
flex-flow: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_SpaceItem.collapsed {
|
||||||
& > .mx_SpaceButton > .mx_SpaceButton_toggleCollapse {
|
& > .mx_SpaceButton > .mx_SpaceButton_toggleCollapse {
|
||||||
transform: rotate(-90deg);
|
transform: rotate(-90deg);
|
||||||
}
|
}
|
||||||
|
@ -89,34 +94,42 @@ $activeBorderColor: $secondary-fg-color;
|
||||||
|
|
||||||
.mx_SpaceItem:not(.hasSubSpaces) > .mx_SpaceButton {
|
.mx_SpaceItem:not(.hasSubSpaces) > .mx_SpaceButton {
|
||||||
margin-left: $gutterSize;
|
margin-left: $gutterSize;
|
||||||
|
min-width: 40px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_SpaceButton {
|
.mx_SpaceButton {
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
position: relative;
|
|
||||||
margin-bottom: 2px;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 4px;
|
padding: 4px 4px 4px 0;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
&.mx_SpaceButton_active {
|
&.mx_SpaceButton_active {
|
||||||
&:not(.mx_SpaceButton_narrow) .mx_SpaceButton_selectionWrapper {
|
&:not(.mx_SpaceButton_narrow) .mx_SpaceButton_selectionWrapper {
|
||||||
background-color: $activeBackgroundColor;
|
background-color: $activeBackgroundColor;
|
||||||
border-radius: 8px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&.mx_SpaceButton_narrow {
|
&.mx_SpaceButton_narrow .mx_SpaceButton_selectionWrapper {
|
||||||
.mx_BaseAvatar, .mx_SpaceButton_avatarPlaceholder {
|
padding: $activeBorderTransparentGap;
|
||||||
border: 2px $activeBorderColor solid;
|
border: 3px $activeBorderColor solid;
|
||||||
border-radius: 11px;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_SpaceButton_selectionWrapper {
|
.mx_SpaceButton_selectionWrapper {
|
||||||
|
position: relative;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:not(.mx_SpaceButton_narrow) {
|
||||||
|
.mx_SpaceButton_selectionWrapper {
|
||||||
|
width: 100%;
|
||||||
|
padding-right: 16px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_SpaceButton_name {
|
.mx_SpaceButton_name {
|
||||||
|
@ -124,7 +137,6 @@ $activeBorderColor: $secondary-fg-color;
|
||||||
margin-left: 8px;
|
margin-left: 8px;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
display: block;
|
display: block;
|
||||||
max-width: 150px;
|
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
padding-right: 8px;
|
padding-right: 8px;
|
||||||
|
@ -133,8 +145,7 @@ $activeBorderColor: $secondary-fg-color;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_SpaceButton_toggleCollapse {
|
.mx_SpaceButton_toggleCollapse {
|
||||||
width: calc($gutterSize - $activeStripeSize);
|
width: $gutterSize;
|
||||||
margin-left: 1px;
|
|
||||||
height: 20px;
|
height: 20px;
|
||||||
mask-position: center;
|
mask-position: center;
|
||||||
mask-size: 20px;
|
mask-size: 20px;
|
||||||
|
@ -172,11 +183,6 @@ $activeBorderColor: $secondary-fg-color;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_SpaceButton_avatarPlaceholder {
|
|
||||||
border: $activeBorderTransparentGap transparent solid;
|
|
||||||
padding: $activeBorderTransparentGap;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.mx_SpaceButton_new .mx_SpaceButton_icon {
|
&.mx_SpaceButton_new .mx_SpaceButton_icon {
|
||||||
background-color: $accent-color;
|
background-color: $accent-color;
|
||||||
transition: all .1s ease-in-out; // TODO transition
|
transition: all .1s ease-in-out; // TODO transition
|
||||||
|
@ -196,21 +202,8 @@ $activeBorderColor: $secondary-fg-color;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_BaseAvatar {
|
.mx_BaseAvatar_image {
|
||||||
/* moving the border-radius to this element from _image
|
border-radius: 8px;
|
||||||
element so we can add a border to it without the initials being displaced */
|
|
||||||
overflow: hidden;
|
|
||||||
border: 2px transparent solid;
|
|
||||||
padding: $activeBorderTransparentGap;
|
|
||||||
|
|
||||||
.mx_BaseAvatar_initial {
|
|
||||||
top: $activeBorderTransparentGap;
|
|
||||||
left: $activeBorderTransparentGap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_BaseAvatar_image {
|
|
||||||
border-radius: 8px;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_SpaceButton_menuButton {
|
.mx_SpaceButton_menuButton {
|
||||||
|
@ -219,8 +212,9 @@ $activeBorderColor: $secondary-fg-color;
|
||||||
height: 20px;
|
height: 20px;
|
||||||
margin-top: auto;
|
margin-top: auto;
|
||||||
margin-bottom: auto;
|
margin-bottom: auto;
|
||||||
position: relative;
|
|
||||||
display: none;
|
display: none;
|
||||||
|
position: absolute;
|
||||||
|
right: 4px;
|
||||||
|
|
||||||
&::before {
|
&::before {
|
||||||
top: 2px;
|
top: 2px;
|
||||||
|
@ -239,9 +233,8 @@ $activeBorderColor: $secondary-fg-color;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_SpacePanel_badgeContainer {
|
.mx_SpacePanel_badgeContainer {
|
||||||
|
position: absolute;
|
||||||
height: 16px;
|
height: 16px;
|
||||||
// don't set width so that it takes no space when there is no badge to show
|
|
||||||
margin: auto 0; // vertically align
|
|
||||||
|
|
||||||
// Create a flexbox to make aligning dot badges easier
|
// Create a flexbox to make aligning dot badges easier
|
||||||
display: flex;
|
display: flex;
|
||||||
|
@ -261,14 +254,25 @@ $activeBorderColor: $secondary-fg-color;
|
||||||
&.collapsed {
|
&.collapsed {
|
||||||
.mx_SpaceButton {
|
.mx_SpaceButton {
|
||||||
.mx_SpacePanel_badgeContainer {
|
.mx_SpacePanel_badgeContainer {
|
||||||
position: absolute;
|
right: -3px;
|
||||||
right: 0px;
|
top: -3px;
|
||||||
top: 2px;
|
}
|
||||||
|
|
||||||
|
&.mx_SpaceButton_active .mx_SpacePanel_badgeContainer {
|
||||||
|
// when we draw the selection border we move the relative bounds of our parent
|
||||||
|
// so update our position within the bounds of the parent to maintain position overall
|
||||||
|
right: -6px;
|
||||||
|
top: -6px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&:not(.collapsed) {
|
&:not(.collapsed) {
|
||||||
|
.mx_SpacePanel_badgeContainer {
|
||||||
|
position: absolute;
|
||||||
|
right: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
.mx_SpaceButton:hover,
|
.mx_SpaceButton:hover,
|
||||||
.mx_SpaceButton:focus-within,
|
.mx_SpaceButton:focus-within,
|
||||||
.mx_SpaceButton_hasMenuOpen {
|
.mx_SpaceButton_hasMenuOpen {
|
||||||
|
@ -335,11 +339,15 @@ $activeBorderColor: $secondary-fg-color;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_SpacePanel_iconPlus::before {
|
.mx_SpacePanel_iconPlus::before {
|
||||||
mask-image: url('$(res)/img/element-icons/plus.svg');
|
mask-image: url('$(res)/img/element-icons/roomlist/plus-circle.svg');
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_SpacePanel_iconHash::before {
|
||||||
|
mask-image: url('$(res)/img/element-icons/roomlist/hash-circle.svg');
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_SpacePanel_iconExplore::before {
|
.mx_SpacePanel_iconExplore::before {
|
||||||
mask-image: url('$(res)/img/element-icons/roomlist/explore.svg');
|
mask-image: url('$(res)/img/element-icons/roomlist/browse.svg');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -31,7 +31,8 @@ limitations under the License.
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
||||||
.mx_BaseAvatar {
|
.mx_BaseAvatar {
|
||||||
margin-right: 16px;
|
margin-right: 12px;
|
||||||
|
align-self: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_BaseAvatar_image {
|
.mx_BaseAvatar_image {
|
||||||
|
@ -47,6 +48,7 @@ limitations under the License.
|
||||||
}
|
}
|
||||||
|
|
||||||
> div {
|
> div {
|
||||||
|
font-weight: 400;
|
||||||
color: $secondary-fg-color;
|
color: $secondary-fg-color;
|
||||||
font-size: $font-15px;
|
font-size: $font-15px;
|
||||||
line-height: $font-24px;
|
line-height: $font-24px;
|
||||||
|
@ -55,38 +57,71 @@ limitations under the License.
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_Dialog_content {
|
.mx_Dialog_content {
|
||||||
// TODO fix scrollbar
|
|
||||||
//display: flex;
|
|
||||||
//flex-direction: column;
|
|
||||||
//height: calc(100% - 80px);
|
|
||||||
|
|
||||||
.mx_AccessibleButton_kind_link {
|
.mx_AccessibleButton_kind_link {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_SearchBox {
|
.mx_SearchBox {
|
||||||
margin: 24px 0 28px;
|
margin: 24px 0 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_SpaceRoomDirectory_noResults {
|
||||||
|
text-align: center;
|
||||||
|
|
||||||
|
> div {
|
||||||
|
font-size: $font-15px;
|
||||||
|
line-height: $font-24px;
|
||||||
|
color: $secondary-fg-color;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_SpaceRoomDirectory_listHeader {
|
.mx_SpaceRoomDirectory_listHeader {
|
||||||
display: flex;
|
display: flex;
|
||||||
font-size: $font-12px;
|
min-height: 32px;
|
||||||
line-height: $font-15px;
|
align-items: center;
|
||||||
color: $secondary-fg-color;
|
font-size: $font-15px;
|
||||||
|
line-height: $font-24px;
|
||||||
|
color: $primary-fg-color;
|
||||||
|
|
||||||
.mx_FormButton {
|
.mx_AccessibleButton {
|
||||||
margin-bottom: 8px;
|
padding: 2px 8px;
|
||||||
|
font-weight: normal;
|
||||||
|
|
||||||
|
& + .mx_AccessibleButton {
|
||||||
|
margin-left: 16px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
> span {
|
> span {
|
||||||
margin: auto 0 0 auto;
|
margin-left: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_SpaceRoomDirectory_error {
|
||||||
|
position: relative;
|
||||||
|
font-weight: $font-semi-bold;
|
||||||
|
color: $notice-primary-color;
|
||||||
|
font-size: $font-15px;
|
||||||
|
line-height: $font-18px;
|
||||||
|
margin: 20px auto 12px;
|
||||||
|
padding-left: 24px;
|
||||||
|
width: max-content;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
height: 16px;
|
||||||
|
width: 16px;
|
||||||
|
left: 0;
|
||||||
|
background-image: url("$(res)/img/element-icons/warning-badge.svg");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_SpaceRoomDirectory_list {
|
.mx_SpaceRoomDirectory_list {
|
||||||
margin-top: 8px;
|
margin-top: 16px;
|
||||||
|
padding-bottom: 40px;
|
||||||
|
|
||||||
.mx_SpaceRoomDirectory_roomCount {
|
.mx_SpaceRoomDirectory_roomCount {
|
||||||
> h3 {
|
> h3 {
|
||||||
|
@ -106,114 +141,128 @@ limitations under the License.
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_SpaceRoomDirectory_subspace {
|
.mx_SpaceRoomDirectory_subspace {
|
||||||
margin-top: 8px;
|
.mx_BaseAvatar_image {
|
||||||
|
border-radius: 8px;
|
||||||
.mx_SpaceRoomDirectory_subspace_info {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
align-items: center;
|
|
||||||
margin-bottom: 8px;
|
|
||||||
color: $secondary-fg-color;
|
|
||||||
font-weight: $font-semi-bold;
|
|
||||||
font-size: $font-12px;
|
|
||||||
line-height: $font-15px;
|
|
||||||
|
|
||||||
.mx_BaseAvatar {
|
|
||||||
margin-right: 12px;
|
|
||||||
vertical-align: middle;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_BaseAvatar_image {
|
|
||||||
border-radius: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_SpaceRoomDirectory_actions {
|
|
||||||
text-align: right;
|
|
||||||
height: min-content;
|
|
||||||
margin-left: auto;
|
|
||||||
margin-right: 16px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_SpaceRoomDirectory_subspace_children {
|
|
||||||
margin-left: 12px;
|
|
||||||
border-left: 2px solid $space-button-outline-color;
|
|
||||||
padding-left: 24px;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_SpaceRoomDirectory_roomTile {
|
.mx_SpaceRoomDirectory_subspace_toggle {
|
||||||
padding: 16px;
|
position: absolute;
|
||||||
border-radius: 8px;
|
left: -1px;
|
||||||
border: 1px solid $space-button-outline-color;
|
top: 10px;
|
||||||
margin: 8px 0 16px;
|
height: 16px;
|
||||||
display: flex;
|
width: 16px;
|
||||||
min-height: 76px;
|
border-radius: 4px;
|
||||||
box-sizing: border-box;
|
background-color: $primary-bg-color;
|
||||||
|
|
||||||
&.mx_AccessibleButton:hover {
|
&::before {
|
||||||
background-color: rgba(141, 151, 165, 0.1);
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
height: 16px;
|
||||||
|
width: 16px;
|
||||||
|
mask-repeat: no-repeat;
|
||||||
|
mask-position: center;
|
||||||
|
background-color: $tertiary-fg-color;
|
||||||
|
mask-size: 16px;
|
||||||
|
transform: rotate(270deg);
|
||||||
|
mask-image: url('$(res)/img/feather-customised/chevron-down.svg');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.mx_SpaceRoomDirectory_subspace_toggle_shown::before {
|
||||||
|
transform: rotate(0deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_SpaceRoomDirectory_subspace_children {
|
||||||
|
position: relative;
|
||||||
|
padding-left: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_SpaceRoomDirectory_roomTile {
|
||||||
|
position: relative;
|
||||||
|
padding: 6px 16px;
|
||||||
|
border-radius: 8px;
|
||||||
|
min-height: 56px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 20px auto max-content;
|
||||||
|
grid-column-gap: 8px;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
.mx_BaseAvatar {
|
.mx_BaseAvatar {
|
||||||
margin-right: 16px;
|
grid-row: 1;
|
||||||
margin-top: 6px;
|
grid-column: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_SpaceRoomDirectory_roomTile_name {
|
||||||
|
font-weight: $font-semi-bold;
|
||||||
|
font-size: $font-15px;
|
||||||
|
line-height: $font-18px;
|
||||||
|
grid-row: 1;
|
||||||
|
grid-column: 2;
|
||||||
|
|
||||||
|
.mx_InfoTooltip {
|
||||||
|
display: inline;
|
||||||
|
margin-left: 12px;
|
||||||
|
color: $tertiary-fg-color;
|
||||||
|
font-size: $font-12px;
|
||||||
|
line-height: $font-15px;
|
||||||
|
|
||||||
|
.mx_InfoTooltip_icon {
|
||||||
|
margin-right: 4px;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_SpaceRoomDirectory_roomTile_info {
|
.mx_SpaceRoomDirectory_roomTile_info {
|
||||||
display: inline-block;
|
font-size: $font-12px;
|
||||||
font-size: $font-15px;
|
line-height: $font-15px;
|
||||||
flex-grow: 1;
|
color: $tertiary-fg-color;
|
||||||
height: min-content;
|
grid-row: 2;
|
||||||
margin: auto 0;
|
grid-column: 1/3;
|
||||||
|
|
||||||
.mx_SpaceRoomDirectory_roomTile_name {
|
|
||||||
font-weight: $font-semi-bold;
|
|
||||||
line-height: $font-18px;
|
|
||||||
}
|
|
||||||
.mx_SpaceRoomDirectory_roomTile_topic {
|
|
||||||
line-height: $font-24px;
|
|
||||||
color: $secondary-fg-color;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_SpaceRoomDirectory_roomTile_memberCount {
|
|
||||||
position: relative;
|
|
||||||
margin: auto 0 auto 24px;
|
|
||||||
padding: 0 0 0 28px;
|
|
||||||
line-height: $font-24px;
|
|
||||||
display: inline-block;
|
|
||||||
width: 32px;
|
|
||||||
|
|
||||||
&::before {
|
|
||||||
position: absolute;
|
|
||||||
content: '';
|
|
||||||
width: 24px;
|
|
||||||
height: 24px;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
mask-position: center;
|
|
||||||
mask-repeat: no-repeat;
|
|
||||||
mask-size: contain;
|
|
||||||
background-color: $secondary-fg-color;
|
|
||||||
mask-image: url('$(res)/img/element-icons/community-members.svg');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_SpaceRoomDirectory_actions {
|
.mx_SpaceRoomDirectory_actions {
|
||||||
width: 180px;
|
|
||||||
text-align: right;
|
text-align: right;
|
||||||
margin-left: 28px;
|
margin-left: 20px;
|
||||||
display: inline-flex;
|
grid-column: 3;
|
||||||
align-items: center;
|
grid-row: 1/3;
|
||||||
|
|
||||||
.mx_AccessibleButton {
|
.mx_AccessibleButton {
|
||||||
vertical-align: middle;
|
padding: 6px 18px;
|
||||||
|
|
||||||
& + .mx_AccessibleButton {
|
display: none;
|
||||||
margin-left: 24px;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mx_Checkbox {
|
||||||
|
display: inline-flex;
|
||||||
|
vertical-align: middle;
|
||||||
|
margin-left: 12px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: $groupFilterPanel-bg-color;
|
||||||
|
|
||||||
|
.mx_AccessibleButton {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_SpaceRoomDirectory_roomTile,
|
||||||
|
.mx_SpaceRoomDirectory_subspace_children {
|
||||||
|
&::before {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
background-color: $groupFilterPanel-bg-color;
|
||||||
|
width: 1px;
|
||||||
|
height: 100%;
|
||||||
|
left: 6px;
|
||||||
|
top: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -225,4 +274,17 @@ limitations under the License.
|
||||||
color: $secondary-fg-color;
|
color: $secondary-fg-color;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
> hr {
|
||||||
|
border: none;
|
||||||
|
height: 1px;
|
||||||
|
background-color: rgba(141, 151, 165, 0.2);
|
||||||
|
margin: 20px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_SpaceRoomDirectory_createRoom {
|
||||||
|
display: block;
|
||||||
|
margin: 16px auto 0;
|
||||||
|
width: max-content;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,51 @@ limitations under the License.
|
||||||
|
|
||||||
$SpaceRoomViewInnerWidth: 428px;
|
$SpaceRoomViewInnerWidth: 428px;
|
||||||
|
|
||||||
|
@define-mixin SpacePillButton {
|
||||||
|
position: relative;
|
||||||
|
padding: 16px 32px 16px 72px;
|
||||||
|
width: 432px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
border-radius: 8px;
|
||||||
|
border: 1px solid $space-button-outline-color;
|
||||||
|
font-size: $font-15px;
|
||||||
|
margin: 20px 0;
|
||||||
|
|
||||||
|
> h3 {
|
||||||
|
font-weight: $font-semi-bold;
|
||||||
|
margin: 0 0 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
> span {
|
||||||
|
color: $secondary-fg-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
position: absolute;
|
||||||
|
content: '';
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
top: 24px;
|
||||||
|
left: 20px;
|
||||||
|
mask-position: center;
|
||||||
|
mask-repeat: no-repeat;
|
||||||
|
mask-size: 24px;
|
||||||
|
background-color: $tertiary-fg-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
border-color: $accent-color;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
background-color: $accent-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
> span {
|
||||||
|
color: $primary-fg-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.mx_SpaceRoomView {
|
.mx_SpaceRoomView {
|
||||||
.mx_MainSplit > div:first-child {
|
.mx_MainSplit > div:first-child {
|
||||||
padding: 80px 60px;
|
padding: 80px 60px;
|
||||||
|
@ -44,7 +89,7 @@ $SpaceRoomViewInnerWidth: 428px;
|
||||||
width: $SpaceRoomViewInnerWidth;
|
width: $SpaceRoomViewInnerWidth;
|
||||||
text-align: right; // button alignment right
|
text-align: right; // button alignment right
|
||||||
|
|
||||||
.mx_FormButton {
|
.mx_AccessibleButton_hasKind {
|
||||||
padding: 8px 22px;
|
padding: 8px 22px;
|
||||||
margin-left: 16px;
|
margin-left: 16px;
|
||||||
}
|
}
|
||||||
|
@ -331,64 +376,8 @@ $SpaceRoomViewInnerWidth: 428px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_SpaceRoomView_privateScope {
|
.mx_SpaceRoomView_privateScope {
|
||||||
.mx_RadioButton {
|
.mx_AccessibleButton {
|
||||||
width: $SpaceRoomViewInnerWidth;
|
@mixin SpacePillButton;
|
||||||
border-radius: 8px;
|
|
||||||
border: 1px solid $space-button-outline-color;
|
|
||||||
padding: 16px 16px 16px 72px;
|
|
||||||
margin-top: 36px;
|
|
||||||
cursor: pointer;
|
|
||||||
box-sizing: border-box;
|
|
||||||
position: relative;
|
|
||||||
|
|
||||||
> div:first-of-type {
|
|
||||||
// hide radio dot
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_RadioButton_content {
|
|
||||||
margin: 0;
|
|
||||||
|
|
||||||
> h3 {
|
|
||||||
margin: 0 0 4px;
|
|
||||||
font-size: $font-15px;
|
|
||||||
font-weight: $font-semi-bold;
|
|
||||||
line-height: $font-18px;
|
|
||||||
}
|
|
||||||
|
|
||||||
> div {
|
|
||||||
color: $secondary-fg-color;
|
|
||||||
font-size: $font-15px;
|
|
||||||
line-height: $font-24px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&::before {
|
|
||||||
content: "";
|
|
||||||
position: absolute;
|
|
||||||
height: 32px;
|
|
||||||
width: 32px;
|
|
||||||
top: 24px;
|
|
||||||
left: 20px;
|
|
||||||
background-color: $secondary-fg-color;
|
|
||||||
mask-repeat: no-repeat;
|
|
||||||
mask-position: center;
|
|
||||||
mask-size: contain;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_RadioButton_checked {
|
|
||||||
border-color: $accent-color;
|
|
||||||
|
|
||||||
.mx_RadioButton_content {
|
|
||||||
> div {
|
|
||||||
color: $primary-fg-color;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&::before {
|
|
||||||
background-color: $accent-color;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_SpaceRoomView_privateScope_justMeButton::before {
|
.mx_SpaceRoomView_privateScope_justMeButton::before {
|
||||||
|
|
|
@ -14,14 +14,6 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
.mx_ViewSource_label_left {
|
|
||||||
float: left;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_ViewSource_label_right {
|
|
||||||
float: right;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_ViewSource_separator {
|
.mx_ViewSource_separator {
|
||||||
clear: both;
|
clear: both;
|
||||||
border-bottom: 1px solid #e5e5e5;
|
border-bottom: 1px solid #e5e5e5;
|
||||||
|
|
|
@ -26,50 +26,6 @@ limitations under the License.
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_CompleteSecurity_clients {
|
|
||||||
width: max-content;
|
|
||||||
margin: 36px auto 0;
|
|
||||||
|
|
||||||
.mx_CompleteSecurity_clients_desktop, .mx_CompleteSecurity_clients_mobile {
|
|
||||||
position: relative;
|
|
||||||
width: 160px;
|
|
||||||
text-align: center;
|
|
||||||
padding-top: 64px;
|
|
||||||
display: inline-block;
|
|
||||||
|
|
||||||
&::before {
|
|
||||||
content: '';
|
|
||||||
position: absolute;
|
|
||||||
height: 48px;
|
|
||||||
width: 48px;
|
|
||||||
left: 56px;
|
|
||||||
top: 0;
|
|
||||||
background-color: $muted-fg-color;
|
|
||||||
mask-repeat: no-repeat;
|
|
||||||
mask-size: contain;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_CompleteSecurity_clients_desktop {
|
|
||||||
margin-right: 56px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_CompleteSecurity_clients_desktop::before {
|
|
||||||
mask-image: url('$(res)/img/feather-customised/monitor.svg');
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_CompleteSecurity_clients_mobile::before {
|
|
||||||
mask-image: url('$(res)/img/feather-customised/smartphone.svg');
|
|
||||||
}
|
|
||||||
|
|
||||||
p {
|
|
||||||
margin-top: 16px;
|
|
||||||
font-size: $font-12px;
|
|
||||||
color: $muted-fg-color;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_CompleteSecurity_heroIcon {
|
.mx_CompleteSecurity_heroIcon {
|
||||||
width: 128px;
|
width: 128px;
|
||||||
height: 128px;
|
height: 128px;
|
||||||
|
|
|
@ -28,22 +28,23 @@ limitations under the License.
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
flex-wrap: nowrap;
|
flex-wrap: nowrap;
|
||||||
min-height: 0;
|
min-height: 0;
|
||||||
|
height: 80vh;
|
||||||
|
|
||||||
.mx_Dialog_title {
|
.mx_Dialog_title {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
||||||
.mx_BaseAvatar {
|
|
||||||
display: inline-flex;
|
|
||||||
margin: 5px 16px 5px 5px;
|
|
||||||
vertical-align: middle;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_BaseAvatar_image {
|
.mx_BaseAvatar_image {
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
vertical-align: unset;
|
vertical-align: unset;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mx_BaseAvatar {
|
||||||
|
display: inline-flex;
|
||||||
|
margin: 5px 16px 5px 5px;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
> div {
|
> div {
|
||||||
> h1 {
|
> h1 {
|
||||||
font-weight: $font-semi-bold;
|
font-weight: $font-semi-bold;
|
||||||
|
@ -101,6 +102,7 @@ limitations under the License.
|
||||||
|
|
||||||
.mx_SearchBox {
|
.mx_SearchBox {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
flex-grow: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_AddExistingToSpaceDialog_errorText {
|
.mx_AddExistingToSpaceDialog_errorText {
|
||||||
|
@ -112,7 +114,10 @@ limitations under the License.
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_AddExistingToSpaceDialog_content {
|
.mx_AddExistingToSpaceDialog_content {
|
||||||
|
flex-grow: 1;
|
||||||
|
|
||||||
.mx_AddExistingToSpaceDialog_noResults {
|
.mx_AddExistingToSpaceDialog_noResults {
|
||||||
|
display: block;
|
||||||
margin-top: 24px;
|
margin-top: 24px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -162,8 +167,14 @@ limitations under the License.
|
||||||
|
|
||||||
> span {
|
> span {
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
font-size: $font-12px;
|
font-size: $font-14px;
|
||||||
line-height: $font-15px;
|
line-height: $font-15px;
|
||||||
|
font-weight: $font-semi-bold;
|
||||||
|
|
||||||
|
.mx_AccessibleButton {
|
||||||
|
font-size: inherit;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
> * {
|
> * {
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
|
|
|
@ -49,7 +49,7 @@ limitations under the License.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_FormButton {
|
.mx_AccessibleButton_hasKind {
|
||||||
padding: 8px 22px;
|
padding: 8px 22px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
Copyright 2015, 2016 OpenMarket Ltd
|
Copyright 2015, 2016, 2021 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.
|
||||||
|
@ -16,6 +16,19 @@ limitations under the License.
|
||||||
|
|
||||||
.mx_MFileBody_download {
|
.mx_MFileBody_download {
|
||||||
color: $accent-color;
|
color: $accent-color;
|
||||||
|
|
||||||
|
.mx_MFileBody_download_icon {
|
||||||
|
// 12px instead of 14px to better match surrounding font size
|
||||||
|
width: 12px;
|
||||||
|
height: 12px;
|
||||||
|
mask-size: 12px;
|
||||||
|
|
||||||
|
mask-position: center;
|
||||||
|
mask-repeat: no-repeat;
|
||||||
|
mask-image: url("$(res)/img/download.svg");
|
||||||
|
background-color: $accent-color;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_MFileBody_download a {
|
.mx_MFileBody_download a {
|
||||||
|
|
|
@ -66,6 +66,11 @@ limitations under the License.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.mx_BasicMessageComposer_input_disabled {
|
||||||
|
pointer-events: none;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_BasicMessageComposer_AutoCompleteWrapper {
|
.mx_BasicMessageComposer_AutoCompleteWrapper {
|
||||||
|
|
|
@ -227,6 +227,10 @@ limitations under the License.
|
||||||
mask-image: url('$(res)/img/element-icons/room/composer/attach.svg');
|
mask-image: url('$(res)/img/element-icons/room/composer/attach.svg');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mx_MessageComposer_voiceMessage::before {
|
||||||
|
mask-image: url('$(res)/img/voip/mic-on-mask.svg');
|
||||||
|
}
|
||||||
|
|
||||||
.mx_MessageComposer_emoji::before {
|
.mx_MessageComposer_emoji::before {
|
||||||
mask-image: url('$(res)/img/element-icons/room/composer/emoji.svg');
|
mask-image: url('$(res)/img/element-icons/room/composer/emoji.svg');
|
||||||
}
|
}
|
||||||
|
|
|
@ -60,6 +60,8 @@ limitations under the License.
|
||||||
width: 27px;
|
width: 27px;
|
||||||
height: 24px;
|
height: 24px;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
background: none;
|
||||||
|
vertical-align: middle;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_MessageComposerFormatBar_button::after {
|
.mx_MessageComposerFormatBar_button::after {
|
||||||
|
|
|
@ -33,8 +33,13 @@ limitations under the License.
|
||||||
|
|
||||||
.mx_AccessibleButton {
|
.mx_AccessibleButton {
|
||||||
line-height: $font-24px;
|
line-height: $font-24px;
|
||||||
|
display: inline-block;
|
||||||
|
|
||||||
&::before {
|
& + .mx_AccessibleButton {
|
||||||
|
margin-left: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:not(.mx_AccessibleButton_kind_primary_outline)::before {
|
||||||
content: '';
|
content: '';
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
background-color: $button-fg-color;
|
background-color: $button-fg-color;
|
||||||
|
|
|
@ -27,6 +27,9 @@ limitations under the License.
|
||||||
.mx_RoomList_iconExplore::before {
|
.mx_RoomList_iconExplore::before {
|
||||||
mask-image: url('$(res)/img/element-icons/roomlist/explore.svg');
|
mask-image: url('$(res)/img/element-icons/roomlist/explore.svg');
|
||||||
}
|
}
|
||||||
|
.mx_RoomList_iconBrowse::before {
|
||||||
|
mask-image: url('$(res)/img/element-icons/roomlist/browse.svg');
|
||||||
|
}
|
||||||
.mx_RoomList_iconDialpad::before {
|
.mx_RoomList_iconDialpad::before {
|
||||||
mask-image: url('$(res)/img/element-icons/roomlist/dialpad.svg');
|
mask-image: url('$(res)/img/element-icons/roomlist/dialpad.svg');
|
||||||
}
|
}
|
||||||
|
@ -35,28 +38,32 @@ limitations under the License.
|
||||||
margin: 4px 12px 4px;
|
margin: 4px 12px 4px;
|
||||||
padding-top: 12px;
|
padding-top: 12px;
|
||||||
border-top: 1px solid $tertiary-fg-color;
|
border-top: 1px solid $tertiary-fg-color;
|
||||||
font-size: $font-13px;
|
font-size: $font-14px;
|
||||||
|
|
||||||
div:first-child {
|
div:first-child {
|
||||||
font-weight: $font-semi-bold;
|
font-weight: $font-semi-bold;
|
||||||
|
line-height: $font-18px;
|
||||||
|
color: $primary-fg-color;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_AccessibleButton {
|
.mx_AccessibleButton {
|
||||||
color: $secondary-fg-color;
|
color: $primary-fg-color;
|
||||||
position: relative;
|
position: relative;
|
||||||
padding: 0 0 0 24px;
|
padding: 8px 8px 8px 32px;
|
||||||
font-size: inherit;
|
font-size: inherit;
|
||||||
margin-top: 8px;
|
margin-top: 12px;
|
||||||
display: block;
|
display: block;
|
||||||
text-align: start;
|
text-align: start;
|
||||||
|
background-color: $roomlist-button-bg-color;
|
||||||
|
border-radius: 4px;
|
||||||
|
|
||||||
&::before {
|
&::before {
|
||||||
content: '';
|
content: '';
|
||||||
width: 16px;
|
width: 16px;
|
||||||
height: 16px;
|
height: 16px;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 8px;
|
||||||
left: 0;
|
left: 8px;
|
||||||
background: $secondary-fg-color;
|
background: $secondary-fg-color;
|
||||||
mask-position: center;
|
mask-position: center;
|
||||||
mask-size: contain;
|
mask-size: contain;
|
||||||
|
@ -70,5 +77,13 @@ limitations under the License.
|
||||||
&.mx_RoomList_explorePrompt_explore::before {
|
&.mx_RoomList_explorePrompt_explore::before {
|
||||||
mask-image: url('$(res)/img/element-icons/roomlist/explore.svg');
|
mask-image: url('$(res)/img/element-icons/roomlist/explore.svg');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.mx_RoomList_explorePrompt_spaceInvite::before {
|
||||||
|
mask-image: url('$(res)/img/element-icons/room/invite.svg');
|
||||||
|
}
|
||||||
|
|
||||||
|
&.mx_RoomList_explorePrompt_spaceExplore::before {
|
||||||
|
mask-image: url('$(res)/img/element-icons/roomlist/browse.svg');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -189,6 +189,10 @@ limitations under the License.
|
||||||
mask-image: url('$(res)/img/element-icons/settings.svg');
|
mask-image: url('$(res)/img/element-icons/settings.svg');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mx_RoomTile_iconInvite::before {
|
||||||
|
mask-image: url('$(res)/img/element-icons/room/invite.svg');
|
||||||
|
}
|
||||||
|
|
||||||
.mx_RoomTile_iconSignOut::before {
|
.mx_RoomTile_iconSignOut::before {
|
||||||
mask-image: url('$(res)/img/element-icons/leave.svg');
|
mask-image: url('$(res)/img/element-icons/leave.svg');
|
||||||
}
|
}
|
||||||
|
|
36
res/css/views/rooms/_VoiceRecordComposerTile.scss
Normal file
36
res/css/views/rooms/_VoiceRecordComposerTile.scss
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
/*
|
||||||
|
Copyright 2021 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_VoiceRecordComposerTile_stop {
|
||||||
|
// 28px plus a 2px border makes this a 32px square (as intended)
|
||||||
|
width: 28px;
|
||||||
|
height: 28px;
|
||||||
|
border: 2px solid $voice-record-stop-border-color;
|
||||||
|
border-radius: 32px;
|
||||||
|
margin-right: 16px; // between us and the send button
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
content: '';
|
||||||
|
width: 14px;
|
||||||
|
height: 14px;
|
||||||
|
position: absolute;
|
||||||
|
top: 7px;
|
||||||
|
left: 7px;
|
||||||
|
border-radius: 2px;
|
||||||
|
background-color: $voice-record-stop-symbol-color;
|
||||||
|
}
|
||||||
|
}
|
|
@ -14,10 +14,7 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// TODO: the space panel currently does not have a fixed width,
|
$spacePanelWidth: 71px;
|
||||||
// just the headers at each level have a max-width of 150px
|
|
||||||
// so this will look slightly off for now. We should probably use css grid for the whole main layout...
|
|
||||||
$spacePanelWidth: 200px;
|
|
||||||
|
|
||||||
.mx_SpaceCreateMenu_wrapper {
|
.mx_SpaceCreateMenu_wrapper {
|
||||||
// background blur everything except SpacePanel
|
// background blur everything except SpacePanel
|
||||||
|
@ -48,53 +45,11 @@ $spacePanelWidth: 200px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_SpaceCreateMenuType {
|
.mx_SpaceCreateMenuType {
|
||||||
position: relative;
|
@mixin SpacePillButton;
|
||||||
padding: 16px 32px 16px 72px;
|
|
||||||
width: 432px;
|
|
||||||
box-sizing: border-box;
|
|
||||||
border-radius: 8px;
|
|
||||||
border: 1px solid $input-darker-bg-color;
|
|
||||||
font-size: $font-15px;
|
|
||||||
margin: 20px 0;
|
|
||||||
|
|
||||||
> h3 {
|
|
||||||
font-weight: $font-semi-bold;
|
|
||||||
margin: 0 0 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
> span {
|
|
||||||
color: $secondary-fg-color;
|
|
||||||
}
|
|
||||||
|
|
||||||
&::before {
|
|
||||||
position: absolute;
|
|
||||||
content: '';
|
|
||||||
width: 32px;
|
|
||||||
height: 32px;
|
|
||||||
top: 24px;
|
|
||||||
left: 20px;
|
|
||||||
mask-position: center;
|
|
||||||
mask-repeat: no-repeat;
|
|
||||||
mask-size: 32px;
|
|
||||||
background-color: $tertiary-fg-color;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
border-color: $accent-color;
|
|
||||||
|
|
||||||
&::before {
|
|
||||||
background-color: $accent-color;
|
|
||||||
}
|
|
||||||
|
|
||||||
> span {
|
|
||||||
color: $primary-fg-color;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_SpaceCreateMenuType_public::before {
|
.mx_SpaceCreateMenuType_public::before {
|
||||||
mask-image: url('$(res)/img/globe.svg');
|
mask-image: url('$(res)/img/globe.svg');
|
||||||
mask-size: 26px;
|
|
||||||
}
|
}
|
||||||
.mx_SpaceCreateMenuType_private::before {
|
.mx_SpaceCreateMenuType_private::before {
|
||||||
mask-image: url('$(res)/img/element-icons/lock.svg');
|
mask-image: url('$(res)/img/element-icons/lock.svg');
|
||||||
|
@ -124,7 +79,7 @@ $spacePanelWidth: 200px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_FormButton {
|
.mx_AccessibleButton_kind_primary {
|
||||||
padding: 8px 22px;
|
padding: 8px 22px;
|
||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
display: block;
|
display: block;
|
||||||
|
|
|
@ -16,38 +16,7 @@ limitations under the License.
|
||||||
|
|
||||||
.mx_SpacePublicShare {
|
.mx_SpacePublicShare {
|
||||||
.mx_AccessibleButton {
|
.mx_AccessibleButton {
|
||||||
border: 1px solid $space-button-outline-color;
|
@mixin SpacePillButton;
|
||||||
box-sizing: border-box;
|
|
||||||
border-radius: 8px;
|
|
||||||
padding: 12px 24px 12px 52px;
|
|
||||||
margin-top: 16px;
|
|
||||||
width: $SpaceRoomViewInnerWidth;
|
|
||||||
font-size: $font-15px;
|
|
||||||
line-height: $font-24px;
|
|
||||||
position: relative;
|
|
||||||
display: flex;
|
|
||||||
|
|
||||||
> span {
|
|
||||||
color: #368bd6;
|
|
||||||
margin-left: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background-color: rgba(141, 151, 165, 0.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
&::before {
|
|
||||||
content: "";
|
|
||||||
position: absolute;
|
|
||||||
width: 30px;
|
|
||||||
height: 30px;
|
|
||||||
mask-repeat: no-repeat;
|
|
||||||
mask-size: contain;
|
|
||||||
mask-position: center;
|
|
||||||
background: $muted-fg-color;
|
|
||||||
left: 12px;
|
|
||||||
top: 9px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.mx_SpacePublicShare_shareButton::before {
|
&.mx_SpacePublicShare_shareButton::before {
|
||||||
mask-image: url('$(res)/img/element-icons/link.svg');
|
mask-image: url('$(res)/img/element-icons/link.svg');
|
||||||
|
|
4
res/img/element-icons/roomlist/browse.svg
Normal file
4
res/img/element-icons/roomlist/browse.svg
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
<svg width="18" height="17" viewBox="0 0 18 17" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M7.00262 5.60945C7.02444 6.31867 7.18204 6.99371 7.45029 7.60945H5.83106L5.49798 11.0235H8.60274L8.757 9.44233C9.29964 9.94168 9.94406 10.3321 10.6556 10.579L10.6122 11.0235H12.7966C13.3489 11.0235 13.7966 11.4712 13.7966 12.0235C13.7966 12.5758 13.3489 13.0235 12.7966 13.0235H10.4171L10.1823 15.4305C10.1287 15.9802 9.63959 16.3823 9.08991 16.3287C8.54024 16.2751 8.13811 15.786 8.19174 15.2363L8.40762 13.0235H5.30286L5.06803 15.4305C5.0144 15.9802 4.52533 16.3823 3.97565 16.3287C3.42598 16.2751 3.02385 15.786 3.07748 15.2363L3.29336 13.0235H1.6665C1.11422 13.0235 0.666504 12.5758 0.666504 12.0235C0.666504 11.4712 1.11422 11.0235 1.6665 11.0235H3.48848L3.82156 7.60945H2.26807C1.71578 7.60945 1.26807 7.16173 1.26807 6.60945C1.26807 6.05716 1.71578 5.60945 2.26807 5.60945H4.01668L4.28073 2.90297C4.33436 2.3533 4.82343 1.95117 5.37311 2.0048C5.92278 2.05842 6.32491 2.5475 6.27128 3.09717L6.02618 5.60945H7.00262Z" fill="#8D99A5"/>
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M14.4224 5.37843C14.4224 6.50754 13.5071 7.42287 12.3779 7.42287C11.2488 7.42287 10.3335 6.50754 10.3335 5.37843C10.3335 4.24931 11.2488 3.33398 12.3779 3.33398C13.5071 3.33398 14.4224 4.24931 14.4224 5.37843ZM15.8496 7.45454C16.2133 6.84764 16.4224 6.13745 16.4224 5.37843C16.4224 3.14474 14.6116 1.33398 12.3779 1.33398C10.1443 1.33398 8.3335 3.14474 8.3335 5.37843C8.3335 7.61211 10.1443 9.42287 12.3779 9.42287C13.1369 9.42287 13.8471 9.21381 14.454 8.85013C14.4853 8.89368 14.5205 8.93528 14.5597 8.97444L16.293 10.7078C16.6835 11.0983 17.3167 11.0983 17.7072 10.7078C18.0977 10.3172 18.0977 9.68408 17.7072 9.29356L15.9739 7.56023C15.9347 7.52107 15.8931 7.48584 15.8496 7.45454Z" fill="#8D99A5"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.8 KiB |
|
@ -1,5 +1,5 @@
|
||||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
<circle cx="8" cy="8" r="8" fill="#737D8C" style="mix-blend-mode:multiply"/>
|
<circle cx="8" cy="8" r="8" fill="#FF4B55"/>
|
||||||
<rect x="7" y="3" width="2" height="6" rx="1" fill="white"/>
|
<rect x="7" y="3" width="2" height="6" rx="1" fill="white"/>
|
||||||
<rect x="7" y="11" width="2" height="2" rx="1" fill="white"/>
|
<rect x="7" y="11" width="2" height="2" rx="1" fill="white"/>
|
||||||
</svg>
|
</svg>
|
||||||
|
|
Before Width: | Height: | Size: 303 B After Width: | Height: | Size: 283 B |
|
@ -1,5 +0,0 @@
|
||||||
<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="M2 5C2 3.89543 2.89543 3 4 3H20C21.1046 3 22 3.89543 22 5V15C22 16.1046 21.1046 17 20 17H4C2.89543 17 2 16.1046 2 15V5Z" stroke="#2E2F32" stroke-linecap="round" stroke-linejoin="round"/>
|
|
||||||
<path d="M8 21H16" stroke="#2E2F32" stroke-linecap="round" stroke-linejoin="round"/>
|
|
||||||
<path d="M12 17V21" stroke="#2E2F32" stroke-linecap="round" stroke-linejoin="round"/>
|
|
||||||
</svg>
|
|
Before Width: | Height: | Size: 510 B |
|
@ -1,4 +0,0 @@
|
||||||
<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="M5 4C5 2.89543 5.89543 2 7 2H17C18.1046 2 19 2.89543 19 4V20C19 21.1046 18.1046 22 17 22H7C5.89543 22 5 21.1046 5 20V4Z" stroke="#2E2F32" stroke-linecap="round" stroke-linejoin="round"/>
|
|
||||||
<circle cx="12" cy="18" r="1" fill="#2E2F32"/>
|
|
||||||
</svg>
|
|
Before Width: | Height: | Size: 386 B |
3
res/img/voip/mic-on-mask.svg
Normal file
3
res/img/voip/mic-on-mask.svg
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
<svg width="20" height="21" viewBox="0 0 20 21" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M6.09999 4.15C6.09999 1.99609 7.84608 0.25 9.99999 0.25C12.1539 0.25 13.9 1.99609 13.9 4.15V9.98254C13.9 12.1365 12.1539 13.8825 9.99999 13.8825C7.84608 13.8825 6.09999 12.1365 6.09999 9.98254V4.15ZM3.1748 8.73755C3.86516 8.73755 4.4248 9.29719 4.4248 9.98755C4.4248 13.0574 6.91483 15.5493 9.9915 15.5538C9.99433 15.5538 9.99717 15.5538 10 15.5538C10.0028 15.5538 10.0056 15.5538 10.0084 15.5538C13.085 15.5492 15.5748 13.0573 15.5748 9.98755C15.5748 9.29719 16.1344 8.73755 16.8248 8.73755C17.5152 8.73755 18.0748 9.29719 18.0748 9.98755C18.0748 14.0189 15.115 17.3576 11.25 17.9577V18.7513C11.25 19.4416 10.6904 20.0013 10 20.0013C9.30964 20.0013 8.75 19.4416 8.75 18.7513V17.9578C4.88483 17.3578 1.9248 14.0191 1.9248 9.98755C1.9248 9.29719 2.48445 8.73755 3.1748 8.73755Z" fill="#C1C6CD"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 951 B |
|
@ -189,6 +189,9 @@ $roomsublist-skeleton-ui-bg: linear-gradient(180deg, #ffffff 0%, #ffffff00 100%)
|
||||||
$groupFilterPanel-divider-color: $roomlist-header-color;
|
$groupFilterPanel-divider-color: $roomlist-header-color;
|
||||||
$space-button-outline-color: #E3E8F0;
|
$space-button-outline-color: #E3E8F0;
|
||||||
|
|
||||||
|
$voice-record-stop-border-color: #E3E8F0;
|
||||||
|
$voice-record-stop-symbol-color: $warning-color;
|
||||||
|
|
||||||
$roomtile-preview-color: #9e9e9e;
|
$roomtile-preview-color: #9e9e9e;
|
||||||
$roomtile-default-badge-bg-color: #61708b;
|
$roomtile-default-badge-bg-color: #61708b;
|
||||||
$roomtile-selected-bg-color: #fff;
|
$roomtile-selected-bg-color: #fff;
|
||||||
|
|
|
@ -180,6 +180,9 @@ $roomsublist-skeleton-ui-bg: linear-gradient(180deg, #ffffff 0%, #ffffff00 100%)
|
||||||
$groupFilterPanel-divider-color: $roomlist-header-color;
|
$groupFilterPanel-divider-color: $roomlist-header-color;
|
||||||
$space-button-outline-color: #E3E8F0;
|
$space-button-outline-color: #E3E8F0;
|
||||||
|
|
||||||
|
$voice-record-stop-border-color: #E3E8F0;
|
||||||
|
$voice-record-stop-symbol-color: $warning-color;
|
||||||
|
|
||||||
$roomtile-preview-color: $secondary-fg-color;
|
$roomtile-preview-color: $secondary-fg-color;
|
||||||
$roomtile-default-badge-bg-color: #61708b;
|
$roomtile-default-badge-bg-color: #61708b;
|
||||||
$roomtile-selected-bg-color: #FFF;
|
$roomtile-selected-bg-color: #FFF;
|
||||||
|
|
2
src/@types/global.d.ts
vendored
2
src/@types/global.d.ts
vendored
|
@ -39,6 +39,7 @@ import {ModalWidgetStore} from "../stores/ModalWidgetStore";
|
||||||
import { WidgetLayoutStore } from "../stores/widgets/WidgetLayoutStore";
|
import { WidgetLayoutStore } from "../stores/widgets/WidgetLayoutStore";
|
||||||
import VoipUserMapper from "../VoipUserMapper";
|
import VoipUserMapper from "../VoipUserMapper";
|
||||||
import {SpaceStoreClass} from "../stores/SpaceStore";
|
import {SpaceStoreClass} from "../stores/SpaceStore";
|
||||||
|
import {VoiceRecorder} from "../voice/VoiceRecorder";
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface Window {
|
interface Window {
|
||||||
|
@ -70,6 +71,7 @@ declare global {
|
||||||
mxModalWidgetStore: ModalWidgetStore;
|
mxModalWidgetStore: ModalWidgetStore;
|
||||||
mxVoipUserMapper: VoipUserMapper;
|
mxVoipUserMapper: VoipUserMapper;
|
||||||
mxSpaceStore: SpaceStoreClass;
|
mxSpaceStore: SpaceStoreClass;
|
||||||
|
mxVoiceRecorder: typeof VoiceRecorder;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Document {
|
interface Document {
|
||||||
|
|
|
@ -14,27 +14,23 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {getHttpUriForMxc} from "matrix-js-sdk/src/content-repo";
|
|
||||||
import {RoomMember} from "matrix-js-sdk/src/models/room-member";
|
import {RoomMember} from "matrix-js-sdk/src/models/room-member";
|
||||||
import {User} from "matrix-js-sdk/src/models/user";
|
import {User} from "matrix-js-sdk/src/models/user";
|
||||||
import {Room} from "matrix-js-sdk/src/models/room";
|
import {Room} from "matrix-js-sdk/src/models/room";
|
||||||
|
|
||||||
import {MatrixClientPeg} from './MatrixClientPeg';
|
|
||||||
import DMRoomMap from './utils/DMRoomMap';
|
import DMRoomMap from './utils/DMRoomMap';
|
||||||
|
import {mediaFromMxc} from "./customisations/Media";
|
||||||
|
|
||||||
export type ResizeMethod = "crop" | "scale";
|
export type ResizeMethod = "crop" | "scale";
|
||||||
|
|
||||||
// Not to be used for BaseAvatar urls as that has similar default avatar fallback already
|
// Not to be used for BaseAvatar urls as that has similar default avatar fallback already
|
||||||
export function avatarUrlForMember(member: RoomMember, width: number, height: number, resizeMethod: ResizeMethod) {
|
export function avatarUrlForMember(member: RoomMember, width: number, height: number, resizeMethod: ResizeMethod) {
|
||||||
let url: string;
|
let url: string;
|
||||||
if (member && member.getAvatarUrl) {
|
if (member?.getMxcAvatarUrl()) {
|
||||||
url = member.getAvatarUrl(
|
url = mediaFromMxc(member.getMxcAvatarUrl()).getThumbnailOfSourceHttp(
|
||||||
MatrixClientPeg.get().getHomeserverUrl(),
|
|
||||||
Math.floor(width * window.devicePixelRatio),
|
Math.floor(width * window.devicePixelRatio),
|
||||||
Math.floor(height * window.devicePixelRatio),
|
Math.floor(height * window.devicePixelRatio),
|
||||||
resizeMethod,
|
resizeMethod,
|
||||||
false,
|
|
||||||
false,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (!url) {
|
if (!url) {
|
||||||
|
@ -47,16 +43,12 @@ export function avatarUrlForMember(member: RoomMember, width: number, height: nu
|
||||||
}
|
}
|
||||||
|
|
||||||
export function avatarUrlForUser(user: User, width: number, height: number, resizeMethod?: ResizeMethod) {
|
export function avatarUrlForUser(user: User, width: number, height: number, resizeMethod?: ResizeMethod) {
|
||||||
const url = getHttpUriForMxc(
|
if (!user.avatarUrl) return null;
|
||||||
MatrixClientPeg.get().getHomeserverUrl(), user.avatarUrl,
|
return mediaFromMxc(user.avatarUrl).getThumbnailOfSourceHttp(
|
||||||
Math.floor(width * window.devicePixelRatio),
|
Math.floor(width * window.devicePixelRatio),
|
||||||
Math.floor(height * window.devicePixelRatio),
|
Math.floor(height * window.devicePixelRatio),
|
||||||
resizeMethod,
|
resizeMethod,
|
||||||
);
|
);
|
||||||
if (!url || url.length === 0) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return url;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function isValidHexColor(color: string): boolean {
|
function isValidHexColor(color: string): boolean {
|
||||||
|
@ -154,15 +146,8 @@ export function getInitialLetter(name: string): string {
|
||||||
export function avatarUrlForRoom(room: Room, width: number, height: number, resizeMethod?: ResizeMethod) {
|
export function avatarUrlForRoom(room: Room, width: number, height: number, resizeMethod?: ResizeMethod) {
|
||||||
if (!room) return null; // null-guard
|
if (!room) return null; // null-guard
|
||||||
|
|
||||||
const explicitRoomAvatar = room.getAvatarUrl(
|
if (room.getMxcAvatarUrl()) {
|
||||||
MatrixClientPeg.get().getHomeserverUrl(),
|
return mediaFromMxc(room.getMxcAvatarUrl()).getThumbnailOfSourceHttp(width, height, resizeMethod);
|
||||||
width,
|
|
||||||
height,
|
|
||||||
resizeMethod,
|
|
||||||
false,
|
|
||||||
);
|
|
||||||
if (explicitRoomAvatar) {
|
|
||||||
return explicitRoomAvatar;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// space rooms cannot be DMs so skip the rest
|
// space rooms cannot be DMs so skip the rest
|
||||||
|
@ -177,14 +162,8 @@ export function avatarUrlForRoom(room: Room, width: number, height: number, resi
|
||||||
// then still try to show any avatar (pref. other member)
|
// then still try to show any avatar (pref. other member)
|
||||||
otherMember = room.getAvatarFallbackMember();
|
otherMember = room.getAvatarFallbackMember();
|
||||||
}
|
}
|
||||||
if (otherMember) {
|
if (otherMember?.getMxcAvatarUrl()) {
|
||||||
return otherMember.getAvatarUrl(
|
return mediaFromMxc(otherMember.getMxcAvatarUrl()).getThumbnailOfSourceHttp(width, height, resizeMethod);
|
||||||
MatrixClientPeg.get().getHomeserverUrl(),
|
|
||||||
width,
|
|
||||||
height,
|
|
||||||
resizeMethod,
|
|
||||||
false,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
|
@ -788,6 +788,11 @@ export default class CallHandler {
|
||||||
// don't remove the call yet: let the hangup event handler do it (otherwise it will throw
|
// don't remove the call yet: let the hangup event handler do it (otherwise it will throw
|
||||||
// the hangup event away)
|
// the hangup event away)
|
||||||
break;
|
break;
|
||||||
|
case 'hangup_all':
|
||||||
|
for (const call of this.calls.values()) {
|
||||||
|
call.hangup(CallErrorCode.UserHangup, false);
|
||||||
|
}
|
||||||
|
break;
|
||||||
case 'answer': {
|
case 'answer': {
|
||||||
if (!this.calls.has(payload.room_id)) {
|
if (!this.calls.has(payload.room_id)) {
|
||||||
return; // no call to answer
|
return; // no call to answer
|
||||||
|
|
|
@ -14,9 +14,9 @@
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import * as Matrix from 'matrix-js-sdk';
|
|
||||||
import SettingsStore from "./settings/SettingsStore";
|
import SettingsStore from "./settings/SettingsStore";
|
||||||
import {SettingLevel} from "./settings/SettingLevel";
|
import {SettingLevel} from "./settings/SettingLevel";
|
||||||
|
import {setMatrixCallAudioInput, setMatrixCallAudioOutput, setMatrixCallVideoInput} from "matrix-js-sdk/src/matrix";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
hasAnyLabeledDevices: async function() {
|
hasAnyLabeledDevices: async function() {
|
||||||
|
@ -54,24 +54,24 @@ export default {
|
||||||
const audioDeviceId = SettingsStore.getValue("webrtc_audioinput");
|
const audioDeviceId = SettingsStore.getValue("webrtc_audioinput");
|
||||||
const videoDeviceId = SettingsStore.getValue("webrtc_videoinput");
|
const videoDeviceId = SettingsStore.getValue("webrtc_videoinput");
|
||||||
|
|
||||||
Matrix.setMatrixCallAudioOutput(audioOutDeviceId);
|
setMatrixCallAudioOutput(audioOutDeviceId);
|
||||||
Matrix.setMatrixCallAudioInput(audioDeviceId);
|
setMatrixCallAudioInput(audioDeviceId);
|
||||||
Matrix.setMatrixCallVideoInput(videoDeviceId);
|
setMatrixCallVideoInput(videoDeviceId);
|
||||||
},
|
},
|
||||||
|
|
||||||
setAudioOutput: function(deviceId) {
|
setAudioOutput: function(deviceId) {
|
||||||
SettingsStore.setValue("webrtc_audiooutput", null, SettingLevel.DEVICE, deviceId);
|
SettingsStore.setValue("webrtc_audiooutput", null, SettingLevel.DEVICE, deviceId);
|
||||||
Matrix.setMatrixCallAudioOutput(deviceId);
|
setMatrixCallAudioOutput(deviceId);
|
||||||
},
|
},
|
||||||
|
|
||||||
setAudioInput: function(deviceId) {
|
setAudioInput: function(deviceId) {
|
||||||
SettingsStore.setValue("webrtc_audioinput", null, SettingLevel.DEVICE, deviceId);
|
SettingsStore.setValue("webrtc_audioinput", null, SettingLevel.DEVICE, deviceId);
|
||||||
Matrix.setMatrixCallAudioInput(deviceId);
|
setMatrixCallAudioInput(deviceId);
|
||||||
},
|
},
|
||||||
|
|
||||||
setVideoInput: function(deviceId) {
|
setVideoInput: function(deviceId) {
|
||||||
SettingsStore.setValue("webrtc_videoinput", null, SettingLevel.DEVICE, deviceId);
|
SettingsStore.setValue("webrtc_videoinput", null, SettingLevel.DEVICE, deviceId);
|
||||||
Matrix.setMatrixCallVideoInput(deviceId);
|
setMatrixCallVideoInput(deviceId);
|
||||||
},
|
},
|
||||||
|
|
||||||
getAudioOutput: function() {
|
getAudioOutput: function() {
|
||||||
|
|
|
@ -32,10 +32,10 @@ import { AllHtmlEntities } from 'html-entities';
|
||||||
import SettingsStore from './settings/SettingsStore';
|
import SettingsStore from './settings/SettingsStore';
|
||||||
import cheerio from 'cheerio';
|
import cheerio from 'cheerio';
|
||||||
|
|
||||||
import {MatrixClientPeg} from './MatrixClientPeg';
|
|
||||||
import {tryTransformPermalinkToLocalHref} from "./utils/permalinks/Permalinks";
|
import {tryTransformPermalinkToLocalHref} from "./utils/permalinks/Permalinks";
|
||||||
import {SHORTCODE_TO_EMOJI, getEmojiFromUnicode} from "./emoji";
|
import {SHORTCODE_TO_EMOJI, getEmojiFromUnicode} from "./emoji";
|
||||||
import ReplyThread from "./components/views/elements/ReplyThread";
|
import ReplyThread from "./components/views/elements/ReplyThread";
|
||||||
|
import {mediaFromMxc} from "./customisations/Media";
|
||||||
|
|
||||||
linkifyMatrix(linkify);
|
linkifyMatrix(linkify);
|
||||||
|
|
||||||
|
@ -181,11 +181,9 @@ const transformTags: IExtendedSanitizeOptions["transformTags"] = { // custom to
|
||||||
if (!attribs.src || !attribs.src.startsWith('mxc://') || !SettingsStore.getValue("showImages")) {
|
if (!attribs.src || !attribs.src.startsWith('mxc://') || !SettingsStore.getValue("showImages")) {
|
||||||
return { tagName, attribs: {}};
|
return { tagName, attribs: {}};
|
||||||
}
|
}
|
||||||
attribs.src = MatrixClientPeg.get().mxcUrlToHttp(
|
const width = Number(attribs.width) || 800;
|
||||||
attribs.src,
|
const height = Number(attribs.height) || 600;
|
||||||
attribs.width || 800,
|
attribs.src = mediaFromMxc(attribs.src).getThumbnailOfSourceHttp(width, height);
|
||||||
attribs.height || 600,
|
|
||||||
);
|
|
||||||
return { tagName, attribs };
|
return { tagName, attribs };
|
||||||
},
|
},
|
||||||
'code': function(tagName: string, attribs: sanitizeHtml.Attributes) {
|
'code': function(tagName: string, attribs: sanitizeHtml.Attributes) {
|
||||||
|
@ -239,6 +237,7 @@ const sanitizeHtmlParams: IExtendedSanitizeOptions = {
|
||||||
'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'blockquote', 'p', 'a', 'ul', 'ol', 'sup', 'sub',
|
'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'blockquote', 'p', 'a', 'ul', 'ol', 'sup', 'sub',
|
||||||
'nl', 'li', 'b', 'i', 'u', 'strong', 'em', 'strike', 'code', 'hr', 'br', 'div',
|
'nl', 'li', 'b', 'i', 'u', 'strong', 'em', 'strike', 'code', 'hr', 'br', 'div',
|
||||||
'table', 'thead', 'caption', 'tbody', 'tr', 'th', 'td', 'pre', 'span', 'img',
|
'table', 'thead', 'caption', 'tbody', 'tr', 'th', 'td', 'pre', 'span', 'img',
|
||||||
|
'details', 'summary',
|
||||||
],
|
],
|
||||||
allowedAttributes: {
|
allowedAttributes: {
|
||||||
// custom ones first:
|
// custom ones first:
|
||||||
|
|
|
@ -14,7 +14,8 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { createClient, SERVICE_TYPES } from 'matrix-js-sdk';
|
import { SERVICE_TYPES } from 'matrix-js-sdk/src/service-types';
|
||||||
|
import { createClient } from 'matrix-js-sdk/src/matrix';
|
||||||
|
|
||||||
import {MatrixClientPeg} from './MatrixClientPeg';
|
import {MatrixClientPeg} from './MatrixClientPeg';
|
||||||
import Modal from './Modal';
|
import Modal from './Modal';
|
||||||
|
|
|
@ -17,8 +17,7 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// @ts-ignore - XXX: tsc doesn't like this: our js-sdk imports are complex so this isn't surprising
|
import { createClient } from 'matrix-js-sdk/src/matrix';
|
||||||
import Matrix from 'matrix-js-sdk';
|
|
||||||
import { InvalidStoreError } from "matrix-js-sdk/src/errors";
|
import { InvalidStoreError } from "matrix-js-sdk/src/errors";
|
||||||
import { MatrixClient } from "matrix-js-sdk/src/client";
|
import { MatrixClient } from "matrix-js-sdk/src/client";
|
||||||
import {decryptAES, encryptAES} from "matrix-js-sdk/src/crypto/aes";
|
import {decryptAES, encryptAES} from "matrix-js-sdk/src/crypto/aes";
|
||||||
|
@ -219,7 +218,7 @@ export function attemptTokenLogin(
|
||||||
button: _t("Try again"),
|
button: _t("Try again"),
|
||||||
onFinished: tryAgain => {
|
onFinished: tryAgain => {
|
||||||
if (tryAgain) {
|
if (tryAgain) {
|
||||||
const cli = Matrix.createClient({
|
const cli = createClient({
|
||||||
baseUrl: homeserver,
|
baseUrl: homeserver,
|
||||||
idBaseUrl: identityServer,
|
idBaseUrl: identityServer,
|
||||||
});
|
});
|
||||||
|
@ -276,7 +275,7 @@ function registerAsGuest(
|
||||||
console.log(`Doing guest login on ${hsUrl}`);
|
console.log(`Doing guest login on ${hsUrl}`);
|
||||||
|
|
||||||
// create a temporary MatrixClient to do the login
|
// create a temporary MatrixClient to do the login
|
||||||
const client = Matrix.createClient({
|
const client = createClient({
|
||||||
baseUrl: hsUrl,
|
baseUrl: hsUrl,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -19,7 +19,7 @@ limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// @ts-ignore - XXX: tsc doesn't like this: our js-sdk imports are complex so this isn't surprising
|
// @ts-ignore - XXX: tsc doesn't like this: our js-sdk imports are complex so this isn't surprising
|
||||||
import Matrix from "matrix-js-sdk";
|
import {createClient} from "matrix-js-sdk/src/matrix";
|
||||||
import { MatrixClient } from "matrix-js-sdk/src/client";
|
import { MatrixClient } from "matrix-js-sdk/src/client";
|
||||||
import { IMatrixClientCreds } from "./MatrixClientPeg";
|
import { IMatrixClientCreds } from "./MatrixClientPeg";
|
||||||
import SecurityCustomisations from "./customisations/Security";
|
import SecurityCustomisations from "./customisations/Security";
|
||||||
|
@ -115,7 +115,7 @@ export default class Login {
|
||||||
*/
|
*/
|
||||||
public createTemporaryClient(): MatrixClient {
|
public createTemporaryClient(): MatrixClient {
|
||||||
if (this.tempClient) return this.tempClient; // use memoization
|
if (this.tempClient) return this.tempClient; // use memoization
|
||||||
return this.tempClient = Matrix.createClient({
|
return this.tempClient = createClient({
|
||||||
baseUrl: this.hsUrl,
|
baseUrl: this.hsUrl,
|
||||||
idBaseUrl: this.isUrl,
|
idBaseUrl: this.isUrl,
|
||||||
});
|
});
|
||||||
|
@ -210,7 +210,7 @@ export async function sendLoginRequest(
|
||||||
loginType: string,
|
loginType: string,
|
||||||
loginParams: ILoginParams,
|
loginParams: ILoginParams,
|
||||||
): Promise<IMatrixClientCreds> {
|
): Promise<IMatrixClientCreds> {
|
||||||
const client = Matrix.createClient({
|
const client = createClient({
|
||||||
baseUrl: hsUrl,
|
baseUrl: hsUrl,
|
||||||
idBaseUrl: isUrl,
|
idBaseUrl: isUrl,
|
||||||
});
|
});
|
||||||
|
|
|
@ -261,7 +261,7 @@ class _MatrixClientPeg implements IMatrixClientPeg {
|
||||||
}
|
}
|
||||||
|
|
||||||
public getHomeserverName(): string {
|
public getHomeserverName(): string {
|
||||||
const matches = /^@.+:(.+)$/.exec(this.matrixClient.credentials.userId);
|
const matches = /^@[^:]+:(.+)$/.exec(this.matrixClient.credentials.userId);
|
||||||
if (matches === null || matches.length < 1) {
|
if (matches === null || matches.length < 1) {
|
||||||
throw new Error("Failed to derive homeserver name from user ID!");
|
throw new Error("Failed to derive homeserver name from user ID!");
|
||||||
}
|
}
|
||||||
|
@ -296,10 +296,11 @@ class _MatrixClientPeg implements IMatrixClientPeg {
|
||||||
// These are always installed regardless of the labs flag so that
|
// These are always installed regardless of the labs flag so that
|
||||||
// cross-signing features can toggle on without reloading and also be
|
// cross-signing features can toggle on without reloading and also be
|
||||||
// accessed immediately after login.
|
// accessed immediately after login.
|
||||||
const customisedCallbacks = {
|
Object.assign(opts.cryptoCallbacks, crossSigningCallbacks);
|
||||||
getDehydrationKey: SecurityCustomisations.getDehydrationKey,
|
if (SecurityCustomisations.getDehydrationKey) {
|
||||||
};
|
opts.cryptoCallbacks.getDehydrationKey =
|
||||||
Object.assign(opts.cryptoCallbacks, crossSigningCallbacks, customisedCallbacks);
|
SecurityCustomisations.getDehydrationKey;
|
||||||
|
}
|
||||||
|
|
||||||
this.matrixClient = createMatrixClient(opts);
|
this.matrixClient = createMatrixClient(opts);
|
||||||
|
|
||||||
|
|
|
@ -36,6 +36,7 @@ import {SettingLevel} from "./settings/SettingLevel";
|
||||||
import {isPushNotifyDisabled} from "./settings/controllers/NotificationControllers";
|
import {isPushNotifyDisabled} from "./settings/controllers/NotificationControllers";
|
||||||
import RoomViewStore from "./stores/RoomViewStore";
|
import RoomViewStore from "./stores/RoomViewStore";
|
||||||
import UserActivity from "./UserActivity";
|
import UserActivity from "./UserActivity";
|
||||||
|
import {mediaFromMxc} from "./customisations/Media";
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Dispatches:
|
* Dispatches:
|
||||||
|
@ -150,7 +151,7 @@ export const Notifier = {
|
||||||
// Ideally in here we could use MSC1310 to detect the type of file, and reject it.
|
// Ideally in here we could use MSC1310 to detect the type of file, and reject it.
|
||||||
|
|
||||||
return {
|
return {
|
||||||
url: MatrixClientPeg.get().mxcUrlToHttp(content.url),
|
url: mediaFromMxc(content.url).srcHttp,
|
||||||
name: content.name,
|
name: content.name,
|
||||||
type: content.type,
|
type: content.type,
|
||||||
size: content.size,
|
size: content.size,
|
||||||
|
|
|
@ -15,7 +15,7 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import * as Matrix from 'matrix-js-sdk';
|
import { createClient } from 'matrix-js-sdk/src/matrix';
|
||||||
import { _t } from './languageHandler';
|
import { _t } from './languageHandler';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -32,7 +32,7 @@ export default class PasswordReset {
|
||||||
* @param {string} identityUrl The URL to the IS which has linked the email -> mxid mapping.
|
* @param {string} identityUrl The URL to the IS which has linked the email -> mxid mapping.
|
||||||
*/
|
*/
|
||||||
constructor(homeserverUrl, identityUrl) {
|
constructor(homeserverUrl, identityUrl) {
|
||||||
this.client = Matrix.createClient({
|
this.client = createClient({
|
||||||
baseUrl: homeserverUrl,
|
baseUrl: homeserverUrl,
|
||||||
idBaseUrl: identityUrl,
|
idBaseUrl: identityUrl,
|
||||||
});
|
});
|
||||||
|
|
|
@ -17,7 +17,7 @@ limitations under the License.
|
||||||
|
|
||||||
import {MatrixClientPeg} from './MatrixClientPeg';
|
import {MatrixClientPeg} from './MatrixClientPeg';
|
||||||
import dis from './dispatcher/dispatcher';
|
import dis from './dispatcher/dispatcher';
|
||||||
import { EventStatus } from 'matrix-js-sdk';
|
import { EventStatus } from 'matrix-js-sdk/src/models/event';
|
||||||
|
|
||||||
export default class Resend {
|
export default class Resend {
|
||||||
static resendUnsentEvents(room) {
|
static resendUnsentEvents(room) {
|
||||||
|
|
|
@ -49,11 +49,12 @@ export function showStartChatInviteDialog(initialText) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function showRoomInviteDialog(roomId) {
|
export function showRoomInviteDialog(roomId, initialText = "") {
|
||||||
// This dialog handles the room creation internally - we don't need to worry about it.
|
// This dialog handles the room creation internally - we don't need to worry about it.
|
||||||
Modal.createTrackedDialog(
|
Modal.createTrackedDialog(
|
||||||
"Invite Users", "", InviteDialog, {
|
"Invite Users", "", InviteDialog, {
|
||||||
kind: KIND_INVITE,
|
kind: KIND_INVITE,
|
||||||
|
initialText,
|
||||||
roomId,
|
roomId,
|
||||||
},
|
},
|
||||||
/*className=*/null, /*isPriority=*/false, /*isStatic=*/true,
|
/*className=*/null, /*isPriority=*/false, /*isStatic=*/true,
|
||||||
|
|
|
@ -21,9 +21,9 @@ import { Service, startTermsFlow, TermsNotSignedError } from './Terms';
|
||||||
import {MatrixClientPeg} from "./MatrixClientPeg";
|
import {MatrixClientPeg} from "./MatrixClientPeg";
|
||||||
import request from "browser-request";
|
import request from "browser-request";
|
||||||
|
|
||||||
import * as Matrix from 'matrix-js-sdk';
|
|
||||||
import SdkConfig from "./SdkConfig";
|
import SdkConfig from "./SdkConfig";
|
||||||
import {WidgetType} from "./widgets/WidgetType";
|
import {WidgetType} from "./widgets/WidgetType";
|
||||||
|
import {SERVICE_TYPES} from "matrix-js-sdk/src/service-types";
|
||||||
|
|
||||||
// The version of the integration manager API we're intending to work with
|
// The version of the integration manager API we're intending to work with
|
||||||
const imApiVersion = "1.1";
|
const imApiVersion = "1.1";
|
||||||
|
@ -153,7 +153,7 @@ export default class ScalarAuthClient {
|
||||||
parsedImRestUrl.path = '';
|
parsedImRestUrl.path = '';
|
||||||
parsedImRestUrl.pathname = '';
|
parsedImRestUrl.pathname = '';
|
||||||
return startTermsFlow([new Service(
|
return startTermsFlow([new Service(
|
||||||
Matrix.SERVICE_TYPES.IM,
|
SERVICE_TYPES.IM,
|
||||||
parsedImRestUrl.format(),
|
parsedImRestUrl.format(),
|
||||||
token,
|
token,
|
||||||
)], this.termsInteractionCallback).then(() => {
|
)], this.termsInteractionCallback).then(() => {
|
||||||
|
|
|
@ -237,7 +237,7 @@ Example:
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {MatrixClientPeg} from './MatrixClientPeg';
|
import {MatrixClientPeg} from './MatrixClientPeg';
|
||||||
import { MatrixEvent } from 'matrix-js-sdk';
|
import { MatrixEvent } from 'matrix-js-sdk/src/models/event';
|
||||||
import dis from './dispatcher/dispatcher';
|
import dis from './dispatcher/dispatcher';
|
||||||
import WidgetUtils from './utils/WidgetUtils';
|
import WidgetUtils from './utils/WidgetUtils';
|
||||||
import RoomViewStore from './stores/RoomViewStore';
|
import RoomViewStore from './stores/RoomViewStore';
|
||||||
|
|
|
@ -20,6 +20,7 @@ limitations under the License.
|
||||||
|
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
|
|
||||||
|
import { ContentHelpers } from 'matrix-js-sdk';
|
||||||
import {MatrixClientPeg} from './MatrixClientPeg';
|
import {MatrixClientPeg} from './MatrixClientPeg';
|
||||||
import dis from './dispatcher/dispatcher';
|
import dis from './dispatcher/dispatcher';
|
||||||
import * as sdk from './index';
|
import * as sdk from './index';
|
||||||
|
@ -126,10 +127,10 @@ export class Command {
|
||||||
return this.getCommand() + " " + this.args;
|
return this.getCommand() + " " + this.args;
|
||||||
}
|
}
|
||||||
|
|
||||||
run(roomId: string, args: string, cmd: string) {
|
run(roomId: string, args: string) {
|
||||||
// if it has no runFn then its an ignored/nop command (autocomplete only) e.g `/me`
|
// if it has no runFn then its an ignored/nop command (autocomplete only) e.g `/me`
|
||||||
if (!this.runFn) return reject(_t("Command error"));
|
if (!this.runFn) return reject(_t("Command error"));
|
||||||
return this.runFn.bind(this)(roomId, args, cmd);
|
return this.runFn.bind(this)(roomId, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
getUsage() {
|
getUsage() {
|
||||||
|
@ -163,7 +164,7 @@ export const Commands = [
|
||||||
if (args) {
|
if (args) {
|
||||||
message = message + ' ' + args;
|
message = message + ' ' + args;
|
||||||
}
|
}
|
||||||
return success(MatrixClientPeg.get().sendTextMessage(roomId, message));
|
return success(ContentHelpers.makeTextMessage(message));
|
||||||
},
|
},
|
||||||
category: CommandCategories.messages,
|
category: CommandCategories.messages,
|
||||||
}),
|
}),
|
||||||
|
@ -176,7 +177,7 @@ export const Commands = [
|
||||||
if (args) {
|
if (args) {
|
||||||
message = message + ' ' + args;
|
message = message + ' ' + args;
|
||||||
}
|
}
|
||||||
return success(MatrixClientPeg.get().sendTextMessage(roomId, message));
|
return success(ContentHelpers.makeTextMessage(message));
|
||||||
},
|
},
|
||||||
category: CommandCategories.messages,
|
category: CommandCategories.messages,
|
||||||
}),
|
}),
|
||||||
|
@ -189,7 +190,7 @@ export const Commands = [
|
||||||
if (args) {
|
if (args) {
|
||||||
message = message + ' ' + args;
|
message = message + ' ' + args;
|
||||||
}
|
}
|
||||||
return success(MatrixClientPeg.get().sendTextMessage(roomId, message));
|
return success(ContentHelpers.makeTextMessage(message));
|
||||||
},
|
},
|
||||||
category: CommandCategories.messages,
|
category: CommandCategories.messages,
|
||||||
}),
|
}),
|
||||||
|
@ -202,7 +203,7 @@ export const Commands = [
|
||||||
if (args) {
|
if (args) {
|
||||||
message = message + ' ' + args;
|
message = message + ' ' + args;
|
||||||
}
|
}
|
||||||
return success(MatrixClientPeg.get().sendTextMessage(roomId, message));
|
return success(ContentHelpers.makeTextMessage(message));
|
||||||
},
|
},
|
||||||
category: CommandCategories.messages,
|
category: CommandCategories.messages,
|
||||||
}),
|
}),
|
||||||
|
@ -211,7 +212,7 @@ export const Commands = [
|
||||||
args: '<message>',
|
args: '<message>',
|
||||||
description: _td('Sends a message as plain text, without interpreting it as markdown'),
|
description: _td('Sends a message as plain text, without interpreting it as markdown'),
|
||||||
runFn: function(roomId, messages) {
|
runFn: function(roomId, messages) {
|
||||||
return success(MatrixClientPeg.get().sendTextMessage(roomId, messages));
|
return success(ContentHelpers.makeTextMessage(messages));
|
||||||
},
|
},
|
||||||
category: CommandCategories.messages,
|
category: CommandCategories.messages,
|
||||||
}),
|
}),
|
||||||
|
@ -220,7 +221,7 @@ export const Commands = [
|
||||||
args: '<message>',
|
args: '<message>',
|
||||||
description: _td('Sends a message as html, without interpreting it as markdown'),
|
description: _td('Sends a message as html, without interpreting it as markdown'),
|
||||||
runFn: function(roomId, messages) {
|
runFn: function(roomId, messages) {
|
||||||
return success(MatrixClientPeg.get().sendHtmlMessage(roomId, messages, messages));
|
return success(ContentHelpers.makeHtmlMessage(messages, messages));
|
||||||
},
|
},
|
||||||
category: CommandCategories.messages,
|
category: CommandCategories.messages,
|
||||||
}),
|
}),
|
||||||
|
@ -965,7 +966,7 @@ export const Commands = [
|
||||||
args: '<message>',
|
args: '<message>',
|
||||||
runFn: function(roomId, args) {
|
runFn: function(roomId, args) {
|
||||||
if (!args) return reject(this.getUserId());
|
if (!args) return reject(this.getUserId());
|
||||||
return success(MatrixClientPeg.get().sendHtmlMessage(roomId, args, textToHtmlRainbow(args)));
|
return success(ContentHelpers.makeHtmlMessage(args, textToHtmlRainbow(args)));
|
||||||
},
|
},
|
||||||
category: CommandCategories.messages,
|
category: CommandCategories.messages,
|
||||||
}),
|
}),
|
||||||
|
@ -975,7 +976,7 @@ export const Commands = [
|
||||||
args: '<message>',
|
args: '<message>',
|
||||||
runFn: function(roomId, args) {
|
runFn: function(roomId, args) {
|
||||||
if (!args) return reject(this.getUserId());
|
if (!args) return reject(this.getUserId());
|
||||||
return success(MatrixClientPeg.get().sendHtmlEmote(roomId, args, textToHtmlRainbow(args)));
|
return success(ContentHelpers.makeHtmlEmote(args, textToHtmlRainbow(args)));
|
||||||
},
|
},
|
||||||
category: CommandCategories.messages,
|
category: CommandCategories.messages,
|
||||||
}),
|
}),
|
||||||
|
@ -1200,10 +1201,13 @@ export function parseCommandString(input: string) {
|
||||||
* 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: string, input: string) {
|
export function getCommand(input: string) {
|
||||||
const {cmd, args} = parseCommandString(input);
|
const {cmd, args} = parseCommandString(input);
|
||||||
|
|
||||||
if (CommandMap.has(cmd) && CommandMap.get(cmd).isEnabled()) {
|
if (CommandMap.has(cmd) && CommandMap.get(cmd).isEnabled()) {
|
||||||
return () => CommandMap.get(cmd).run(roomId, args, cmd);
|
return {
|
||||||
|
cmd: CommandMap.get(cmd),
|
||||||
|
args,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,7 +19,7 @@ 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/src/client';
|
||||||
import * as MegolmExportEncryption from '../../../../utils/MegolmExportEncryption';
|
import * as MegolmExportEncryption from '../../../../utils/MegolmExportEncryption';
|
||||||
import * as sdk from '../../../../index';
|
import * as sdk from '../../../../index';
|
||||||
|
|
||||||
|
|
|
@ -17,7 +17,7 @@ limitations under the License.
|
||||||
import React, {createRef} from 'react';
|
import React, {createRef} from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
import { MatrixClient } from 'matrix-js-sdk';
|
import { MatrixClient } from 'matrix-js-sdk/src/client';
|
||||||
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';
|
||||||
|
|
|
@ -27,6 +27,7 @@ import {sortBy} from "lodash";
|
||||||
import {makeGroupPermalink} from "../utils/permalinks/Permalinks";
|
import {makeGroupPermalink} from "../utils/permalinks/Permalinks";
|
||||||
import {ICompletion, ISelectionRange} from "./Autocompleter";
|
import {ICompletion, ISelectionRange} from "./Autocompleter";
|
||||||
import FlairStore from "../stores/FlairStore";
|
import FlairStore from "../stores/FlairStore";
|
||||||
|
import {mediaFromMxc} from "../customisations/Media";
|
||||||
|
|
||||||
const COMMUNITY_REGEX = /\B\+\S*/g;
|
const COMMUNITY_REGEX = /\B\+\S*/g;
|
||||||
|
|
||||||
|
@ -95,7 +96,7 @@ export default class CommunityProvider extends AutocompleteProvider {
|
||||||
name={name || groupId}
|
name={name || groupId}
|
||||||
width={24}
|
width={24}
|
||||||
height={24}
|
height={24}
|
||||||
url={avatarUrl ? cli.mxcUrlToHttp(avatarUrl, 24, 24) : null} />
|
url={avatarUrl ? mediaFromMxc(avatarUrl).getSquareThumbnailHttp(24) : null} />
|
||||||
</PillCompletion>
|
</PillCompletion>
|
||||||
),
|
),
|
||||||
range,
|
range,
|
||||||
|
|
|
@ -18,7 +18,7 @@ limitations under the License.
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
import {Filter} from 'matrix-js-sdk';
|
import {Filter} from 'matrix-js-sdk/src/filter';
|
||||||
import * as sdk from '../../index';
|
import * as sdk from '../../index';
|
||||||
import {MatrixClientPeg} from '../../MatrixClientPeg';
|
import {MatrixClientPeg} from '../../MatrixClientPeg';
|
||||||
import EventIndexPeg from "../../indexing/EventIndexPeg";
|
import EventIndexPeg from "../../indexing/EventIndexPeg";
|
||||||
|
|
|
@ -35,10 +35,11 @@ import GroupStore from '../../stores/GroupStore';
|
||||||
import FlairStore from '../../stores/FlairStore';
|
import FlairStore from '../../stores/FlairStore';
|
||||||
import { showGroupAddRoomDialog } from '../../GroupAddressPicker';
|
import { showGroupAddRoomDialog } from '../../GroupAddressPicker';
|
||||||
import {makeGroupPermalink, makeUserPermalink} from "../../utils/permalinks/Permalinks";
|
import {makeGroupPermalink, makeUserPermalink} from "../../utils/permalinks/Permalinks";
|
||||||
import {Group} from "matrix-js-sdk";
|
import {Group} from "matrix-js-sdk/src/models/group";
|
||||||
import {allSettled, sleep} from "../../utils/promise";
|
import {allSettled, sleep} from "../../utils/promise";
|
||||||
import RightPanelStore from "../../stores/RightPanelStore";
|
import RightPanelStore from "../../stores/RightPanelStore";
|
||||||
import AutoHideScrollbar from "./AutoHideScrollbar";
|
import AutoHideScrollbar from "./AutoHideScrollbar";
|
||||||
|
import {mediaFromMxc} from "../../customisations/Media";
|
||||||
import {replaceableComponent} from "../../utils/replaceableComponent";
|
import {replaceableComponent} from "../../utils/replaceableComponent";
|
||||||
|
|
||||||
const LONG_DESC_PLACEHOLDER = _td(
|
const LONG_DESC_PLACEHOLDER = _td(
|
||||||
|
@ -368,8 +369,7 @@ class FeaturedUser extends React.Component {
|
||||||
|
|
||||||
const permalink = makeUserPermalink(this.props.summaryInfo.user_id);
|
const permalink = makeUserPermalink(this.props.summaryInfo.user_id);
|
||||||
const userNameNode = <a href={permalink} onClick={this.onClick}>{ name }</a>;
|
const userNameNode = <a href={permalink} onClick={this.onClick}>{ name }</a>;
|
||||||
const httpUrl = MatrixClientPeg.get()
|
const httpUrl = mediaFromMxc(this.props.summaryInfo.avatar_url).getSquareThumbnailHttp(64);
|
||||||
.mxcUrlToHttp(this.props.summaryInfo.avatar_url, 64, 64);
|
|
||||||
|
|
||||||
const deleteButton = this.props.editing ?
|
const deleteButton = this.props.editing ?
|
||||||
<img
|
<img
|
||||||
|
@ -981,10 +981,9 @@ export default class GroupView extends React.Component {
|
||||||
<Spinner />
|
<Spinner />
|
||||||
</div>;
|
</div>;
|
||||||
}
|
}
|
||||||
const httpInviterAvatar = this.state.inviterProfile ?
|
const httpInviterAvatar = this.state.inviterProfile
|
||||||
this._matrixClient.mxcUrlToHttp(
|
? mediaFromMxc(this.state.inviterProfile.avatarUrl).getSquareThumbnailHttp(36)
|
||||||
this.state.inviterProfile.avatarUrl, 36, 36,
|
: null;
|
||||||
) : null;
|
|
||||||
|
|
||||||
const inviter = group.inviter || {};
|
const inviter = group.inviter || {};
|
||||||
let inviterName = inviter.userId;
|
let inviterName = inviter.userId;
|
||||||
|
|
|
@ -15,7 +15,7 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {InteractiveAuth} from "matrix-js-sdk";
|
import {InteractiveAuth} from "matrix-js-sdk/src/interactive-auth";
|
||||||
import React, {createRef} from 'react';
|
import React, {createRef} from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
|
|
|
@ -16,9 +16,11 @@ limitations under the License.
|
||||||
|
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { createRef } from "react";
|
import { createRef } from "react";
|
||||||
|
import classNames from "classnames";
|
||||||
|
import { Room } from "matrix-js-sdk/src/models/room";
|
||||||
|
|
||||||
import GroupFilterPanel from "./GroupFilterPanel";
|
import GroupFilterPanel from "./GroupFilterPanel";
|
||||||
import CustomRoomTagPanel from "./CustomRoomTagPanel";
|
import CustomRoomTagPanel from "./CustomRoomTagPanel";
|
||||||
import classNames from "classnames";
|
|
||||||
import dis from "../../dispatcher/dispatcher";
|
import dis from "../../dispatcher/dispatcher";
|
||||||
import { _t } from "../../languageHandler";
|
import { _t } from "../../languageHandler";
|
||||||
import RoomList from "../views/rooms/RoomList";
|
import RoomList from "../views/rooms/RoomList";
|
||||||
|
@ -36,11 +38,11 @@ import {Key} from "../../Keyboard";
|
||||||
import IndicatorScrollbar from "../structures/IndicatorScrollbar";
|
import IndicatorScrollbar from "../structures/IndicatorScrollbar";
|
||||||
import AccessibleTooltipButton from "../views/elements/AccessibleTooltipButton";
|
import AccessibleTooltipButton from "../views/elements/AccessibleTooltipButton";
|
||||||
import { OwnProfileStore } from "../../stores/OwnProfileStore";
|
import { OwnProfileStore } from "../../stores/OwnProfileStore";
|
||||||
import { MatrixClientPeg } from "../../MatrixClientPeg";
|
|
||||||
import RoomListNumResults from "../views/rooms/RoomListNumResults";
|
import RoomListNumResults from "../views/rooms/RoomListNumResults";
|
||||||
import LeftPanelWidget from "./LeftPanelWidget";
|
import LeftPanelWidget from "./LeftPanelWidget";
|
||||||
import SpacePanel from "../views/spaces/SpacePanel";
|
|
||||||
import {replaceableComponent} from "../../utils/replaceableComponent";
|
import {replaceableComponent} from "../../utils/replaceableComponent";
|
||||||
|
import {mediaFromMxc} from "../../customisations/Media";
|
||||||
|
import SpaceStore, {UPDATE_SELECTED_SPACE} from "../../stores/SpaceStore";
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
isMinimized: boolean;
|
isMinimized: boolean;
|
||||||
|
@ -50,6 +52,7 @@ interface IProps {
|
||||||
interface IState {
|
interface IState {
|
||||||
showBreadcrumbs: boolean;
|
showBreadcrumbs: boolean;
|
||||||
showGroupFilterPanel: boolean;
|
showGroupFilterPanel: boolean;
|
||||||
|
activeSpace?: Room;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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
|
||||||
|
@ -75,11 +78,13 @@ export default class LeftPanel extends React.Component<IProps, IState> {
|
||||||
this.state = {
|
this.state = {
|
||||||
showBreadcrumbs: BreadcrumbsStore.instance.visible,
|
showBreadcrumbs: BreadcrumbsStore.instance.visible,
|
||||||
showGroupFilterPanel: SettingsStore.getValue('TagPanel.enableTagPanel'),
|
showGroupFilterPanel: SettingsStore.getValue('TagPanel.enableTagPanel'),
|
||||||
|
activeSpace: SpaceStore.instance.activeSpace,
|
||||||
};
|
};
|
||||||
|
|
||||||
BreadcrumbsStore.instance.on(UPDATE_EVENT, this.onBreadcrumbsUpdate);
|
BreadcrumbsStore.instance.on(UPDATE_EVENT, this.onBreadcrumbsUpdate);
|
||||||
RoomListStore.instance.on(LISTS_UPDATE_EVENT, this.onBreadcrumbsUpdate);
|
RoomListStore.instance.on(LISTS_UPDATE_EVENT, this.onBreadcrumbsUpdate);
|
||||||
OwnProfileStore.instance.on(UPDATE_EVENT, this.onBackgroundImageUpdate);
|
OwnProfileStore.instance.on(UPDATE_EVENT, this.onBackgroundImageUpdate);
|
||||||
|
SpaceStore.instance.on(UPDATE_SELECTED_SPACE, this.updateActiveSpace);
|
||||||
this.bgImageWatcherRef = SettingsStore.watchSetting(
|
this.bgImageWatcherRef = SettingsStore.watchSetting(
|
||||||
"RoomList.backgroundImage", null, this.onBackgroundImageUpdate);
|
"RoomList.backgroundImage", null, this.onBackgroundImageUpdate);
|
||||||
this.groupFilterPanelWatcherRef = SettingsStore.watchSetting("TagPanel.enableTagPanel", null, () => {
|
this.groupFilterPanelWatcherRef = SettingsStore.watchSetting("TagPanel.enableTagPanel", null, () => {
|
||||||
|
@ -97,9 +102,14 @@ export default class LeftPanel extends React.Component<IProps, IState> {
|
||||||
BreadcrumbsStore.instance.off(UPDATE_EVENT, this.onBreadcrumbsUpdate);
|
BreadcrumbsStore.instance.off(UPDATE_EVENT, this.onBreadcrumbsUpdate);
|
||||||
RoomListStore.instance.off(LISTS_UPDATE_EVENT, this.onBreadcrumbsUpdate);
|
RoomListStore.instance.off(LISTS_UPDATE_EVENT, this.onBreadcrumbsUpdate);
|
||||||
OwnProfileStore.instance.off(UPDATE_EVENT, this.onBackgroundImageUpdate);
|
OwnProfileStore.instance.off(UPDATE_EVENT, this.onBackgroundImageUpdate);
|
||||||
|
SpaceStore.instance.off(UPDATE_SELECTED_SPACE, this.updateActiveSpace);
|
||||||
this.props.resizeNotifier.off("middlePanelResizedNoisy", this.onResize);
|
this.props.resizeNotifier.off("middlePanelResizedNoisy", this.onResize);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private updateActiveSpace = (activeSpace: Room) => {
|
||||||
|
this.setState({ activeSpace });
|
||||||
|
};
|
||||||
|
|
||||||
private onExplore = () => {
|
private onExplore = () => {
|
||||||
dis.fire(Action.ViewRoomDirectory);
|
dis.fire(Action.ViewRoomDirectory);
|
||||||
};
|
};
|
||||||
|
@ -121,7 +131,7 @@ export default class LeftPanel extends React.Component<IProps, IState> {
|
||||||
let avatarUrl = OwnProfileStore.instance.getHttpAvatarUrl(avatarSize);
|
let avatarUrl = OwnProfileStore.instance.getHttpAvatarUrl(avatarSize);
|
||||||
const settingBgMxc = SettingsStore.getValue("RoomList.backgroundImage");
|
const settingBgMxc = SettingsStore.getValue("RoomList.backgroundImage");
|
||||||
if (settingBgMxc) {
|
if (settingBgMxc) {
|
||||||
avatarUrl = MatrixClientPeg.get().mxcUrlToHttp(settingBgMxc, avatarSize, avatarSize);
|
avatarUrl = mediaFromMxc(settingBgMxc).getSquareThumbnailHttp(avatarSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
const avatarUrlProp = `url(${avatarUrl})`;
|
const avatarUrlProp = `url(${avatarUrl})`;
|
||||||
|
@ -382,7 +392,9 @@ export default class LeftPanel extends React.Component<IProps, IState> {
|
||||||
onEnter={this.onEnter}
|
onEnter={this.onEnter}
|
||||||
/>
|
/>
|
||||||
<AccessibleTooltipButton
|
<AccessibleTooltipButton
|
||||||
className="mx_LeftPanel_exploreButton"
|
className={classNames("mx_LeftPanel_exploreButton", {
|
||||||
|
mx_LeftPanel_exploreButton_space: !!this.state.activeSpace,
|
||||||
|
})}
|
||||||
onClick={this.onExplore}
|
onClick={this.onExplore}
|
||||||
title={_t("Explore rooms")}
|
title={_t("Explore rooms")}
|
||||||
/>
|
/>
|
||||||
|
@ -392,11 +404,7 @@ export default class LeftPanel extends React.Component<IProps, IState> {
|
||||||
|
|
||||||
public render(): React.ReactNode {
|
public render(): React.ReactNode {
|
||||||
let leftLeftPanel;
|
let leftLeftPanel;
|
||||||
// Currently TagPanel.enableTagPanel is disabled when Legacy Communities are disabled so for now
|
if (this.state.showGroupFilterPanel) {
|
||||||
// ignore it and force the rendering of SpacePanel if that Labs flag is enabled.
|
|
||||||
if (SettingsStore.getValue("feature_spaces")) {
|
|
||||||
leftLeftPanel = <SpacePanel />;
|
|
||||||
} else if (this.state.showGroupFilterPanel) {
|
|
||||||
leftLeftPanel = (
|
leftLeftPanel = (
|
||||||
<div className="mx_LeftPanel_GroupFilterPanelContainer">
|
<div className="mx_LeftPanel_GroupFilterPanelContainer">
|
||||||
<GroupFilterPanel />
|
<GroupFilterPanel />
|
||||||
|
@ -412,6 +420,7 @@ export default class LeftPanel extends React.Component<IProps, IState> {
|
||||||
onBlur={this.onBlur}
|
onBlur={this.onBlur}
|
||||||
isMinimized={this.props.isMinimized}
|
isMinimized={this.props.isMinimized}
|
||||||
onResize={this.onResize}
|
onResize={this.onResize}
|
||||||
|
activeSpace={this.state.activeSpace}
|
||||||
/>;
|
/>;
|
||||||
|
|
||||||
const containerClasses = classNames({
|
const containerClasses = classNames({
|
||||||
|
|
|
@ -56,6 +56,7 @@ import Modal from "../../Modal";
|
||||||
import { ICollapseConfig } from "../../resizer/distributors/collapse";
|
import { ICollapseConfig } from "../../resizer/distributors/collapse";
|
||||||
import HostSignupContainer from '../views/host_signup/HostSignupContainer';
|
import HostSignupContainer from '../views/host_signup/HostSignupContainer';
|
||||||
import { IOpts } from "../../createRoom";
|
import { IOpts } from "../../createRoom";
|
||||||
|
import SpacePanel from "../views/spaces/SpacePanel";
|
||||||
import {replaceableComponent} from "../../utils/replaceableComponent";
|
import {replaceableComponent} from "../../utils/replaceableComponent";
|
||||||
|
|
||||||
// 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)
|
||||||
|
@ -73,7 +74,6 @@ function canElementReceiveInput(el) {
|
||||||
interface IProps {
|
interface IProps {
|
||||||
matrixClient: MatrixClient;
|
matrixClient: MatrixClient;
|
||||||
onRegistered: (credentials: IMatrixClientCreds) => Promise<MatrixClient>;
|
onRegistered: (credentials: IMatrixClientCreds) => Promise<MatrixClient>;
|
||||||
viaServers?: string[];
|
|
||||||
hideToSRUsers: boolean;
|
hideToSRUsers: boolean;
|
||||||
resizeNotifier: ResizeNotifier;
|
resizeNotifier: ResizeNotifier;
|
||||||
// eslint-disable-next-line camelcase
|
// eslint-disable-next-line camelcase
|
||||||
|
@ -142,9 +142,6 @@ class LoggedInView extends React.Component<IProps, IState> {
|
||||||
// transitioned to PWLU)
|
// transitioned to PWLU)
|
||||||
onRegistered: PropTypes.func,
|
onRegistered: PropTypes.func,
|
||||||
|
|
||||||
// Used by the RoomView to handle joining rooms
|
|
||||||
viaServers: PropTypes.arrayOf(PropTypes.string),
|
|
||||||
|
|
||||||
// and lots and lots of other stuff.
|
// and lots and lots of other stuff.
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -229,21 +226,15 @@ class LoggedInView extends React.Component<IProps, IState> {
|
||||||
let size;
|
let size;
|
||||||
let collapsed;
|
let collapsed;
|
||||||
const collapseConfig: ICollapseConfig = {
|
const collapseConfig: ICollapseConfig = {
|
||||||
// TODO: the space panel currently does not have a fixed width,
|
// TODO decrease this once Spaces launches as it'll no longer need to include the 56px Community Panel
|
||||||
// just the headers at each level have a max-width of 150px
|
toggleSize: 206 - 50,
|
||||||
// Taking 222px for the space panel for now,
|
|
||||||
// so this will look slightly off for now,
|
|
||||||
// depending on the depth of your space tree.
|
|
||||||
// To fix this, we'll need to turn toggleSize
|
|
||||||
// into a callback so it can be measured when starting the resize operation
|
|
||||||
toggleSize: 222 + 68,
|
|
||||||
onCollapsed: (_collapsed) => {
|
onCollapsed: (_collapsed) => {
|
||||||
collapsed = _collapsed;
|
collapsed = _collapsed;
|
||||||
if (_collapsed) {
|
if (_collapsed) {
|
||||||
dis.dispatch({action: "hide_left_panel"}, true);
|
dis.dispatch({action: "hide_left_panel"});
|
||||||
window.localStorage.setItem("mx_lhs_size", '0');
|
window.localStorage.setItem("mx_lhs_size", '0');
|
||||||
} else {
|
} else {
|
||||||
dis.dispatch({action: "show_left_panel"}, true);
|
dis.dispatch({action: "show_left_panel"});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onResized: (_size) => {
|
onResized: (_size) => {
|
||||||
|
@ -630,11 +621,9 @@ class LoggedInView extends React.Component<IProps, IState> {
|
||||||
case PageTypes.RoomView:
|
case PageTypes.RoomView:
|
||||||
pageElement = <RoomView
|
pageElement = <RoomView
|
||||||
ref={this._roomView}
|
ref={this._roomView}
|
||||||
autoJoin={this.props.autoJoin}
|
|
||||||
onRegistered={this.props.onRegistered}
|
onRegistered={this.props.onRegistered}
|
||||||
threepidInvite={this.props.threepidInvite}
|
threepidInvite={this.props.threepidInvite}
|
||||||
oobData={this.props.roomOobData}
|
oobData={this.props.roomOobData}
|
||||||
viaServers={this.props.viaServers}
|
|
||||||
key={this.props.currentRoomId || 'roomview'}
|
key={this.props.currentRoomId || 'roomview'}
|
||||||
resizeNotifier={this.props.resizeNotifier}
|
resizeNotifier={this.props.resizeNotifier}
|
||||||
justCreatedOpts={this.props.roomJustCreatedOpts}
|
justCreatedOpts={this.props.roomJustCreatedOpts}
|
||||||
|
@ -670,13 +659,6 @@ class LoggedInView extends React.Component<IProps, IState> {
|
||||||
bodyClasses += ' mx_MatrixChat_useCompactLayout';
|
bodyClasses += ' mx_MatrixChat_useCompactLayout';
|
||||||
}
|
}
|
||||||
|
|
||||||
const leftPanel = (
|
|
||||||
<LeftPanel
|
|
||||||
isMinimized={this.props.collapseLhs || false}
|
|
||||||
resizeNotifier={this.props.resizeNotifier}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MatrixClientContext.Provider value={this._matrixClient}>
|
<MatrixClientContext.Provider value={this._matrixClient}>
|
||||||
<div
|
<div
|
||||||
|
@ -688,7 +670,11 @@ class LoggedInView extends React.Component<IProps, IState> {
|
||||||
<ToastContainer />
|
<ToastContainer />
|
||||||
<DragDropContext onDragEnd={this._onDragEnd}>
|
<DragDropContext onDragEnd={this._onDragEnd}>
|
||||||
<div ref={this._resizeContainer} className={bodyClasses}>
|
<div ref={this._resizeContainer} className={bodyClasses}>
|
||||||
{ leftPanel }
|
{ SettingsStore.getValue("feature_spaces") ? <SpacePanel /> : null }
|
||||||
|
<LeftPanel
|
||||||
|
isMinimized={this.props.collapseLhs || false}
|
||||||
|
resizeNotifier={this.props.resizeNotifier}
|
||||||
|
/>
|
||||||
<ResizeHandle />
|
<ResizeHandle />
|
||||||
{ pageElement }
|
{ pageElement }
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,8 +1,5 @@
|
||||||
/*
|
/*
|
||||||
Copyright 2015, 2016 OpenMarket Ltd
|
Copyright 2015-2021 The Matrix.org Foundation C.I.C.
|
||||||
Copyright 2017 Vector Creations Ltd
|
|
||||||
Copyright 2017-2019 New Vector Ltd
|
|
||||||
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.
|
||||||
|
@ -18,8 +15,7 @@ limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { createRef } from 'react';
|
import React, { createRef } from 'react';
|
||||||
// @ts-ignore - XXX: no idea why this import fails
|
import { createClient } from "matrix-js-sdk/src/matrix";
|
||||||
import * as Matrix from "matrix-js-sdk";
|
|
||||||
import { InvalidStoreError } from "matrix-js-sdk/src/errors";
|
import { InvalidStoreError } from "matrix-js-sdk/src/errors";
|
||||||
import { RoomMember } from "matrix-js-sdk/src/models/room-member";
|
import { RoomMember } from "matrix-js-sdk/src/models/room-member";
|
||||||
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
|
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
|
||||||
|
@ -82,9 +78,12 @@ import {UIFeature} from "../../settings/UIFeature";
|
||||||
import { CommunityPrototypeStore } from "../../stores/CommunityPrototypeStore";
|
import { CommunityPrototypeStore } from "../../stores/CommunityPrototypeStore";
|
||||||
import DialPadModal from "../views/voip/DialPadModal";
|
import DialPadModal from "../views/voip/DialPadModal";
|
||||||
import { showToast as showMobileGuideToast } from '../../toasts/MobileGuideToast';
|
import { showToast as showMobileGuideToast } from '../../toasts/MobileGuideToast';
|
||||||
|
import { shouldUseLoginForWelcome } from "../../utils/pages";
|
||||||
import SpaceStore from "../../stores/SpaceStore";
|
import SpaceStore from "../../stores/SpaceStore";
|
||||||
import SpaceRoomDirectory from "./SpaceRoomDirectory";
|
import SpaceRoomDirectory from "./SpaceRoomDirectory";
|
||||||
import {replaceableComponent} from "../../utils/replaceableComponent";
|
import {replaceableComponent} from "../../utils/replaceableComponent";
|
||||||
|
import RoomListStore from "../../stores/room-list/RoomListStore";
|
||||||
|
import {RoomUpdateCause} from "../../stores/room-list/models";
|
||||||
|
|
||||||
/** constants for MatrixChat.state.view */
|
/** constants for MatrixChat.state.view */
|
||||||
export enum Views {
|
export enum Views {
|
||||||
|
@ -203,7 +202,6 @@ interface IState {
|
||||||
ready: boolean;
|
ready: boolean;
|
||||||
threepidInvite?: IThreepidInvite,
|
threepidInvite?: IThreepidInvite,
|
||||||
roomOobData?: object;
|
roomOobData?: object;
|
||||||
viaServers?: string[];
|
|
||||||
pendingInitialSync?: boolean;
|
pendingInitialSync?: boolean;
|
||||||
justRegistered?: boolean;
|
justRegistered?: boolean;
|
||||||
roomJustCreatedOpts?: IOpts;
|
roomJustCreatedOpts?: IOpts;
|
||||||
|
@ -582,6 +580,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'logout':
|
case 'logout':
|
||||||
|
dis.dispatch({action: "hangup_all"});
|
||||||
Lifecycle.logout();
|
Lifecycle.logout();
|
||||||
break;
|
break;
|
||||||
case 'require_registration':
|
case 'require_registration':
|
||||||
|
@ -606,12 +605,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||||
if (payload.screenAfterLogin) {
|
if (payload.screenAfterLogin) {
|
||||||
this.screenAfterLogin = payload.screenAfterLogin;
|
this.screenAfterLogin = payload.screenAfterLogin;
|
||||||
}
|
}
|
||||||
this.setStateForNewView({
|
this.viewLogin();
|
||||||
view: Views.LOGIN,
|
|
||||||
});
|
|
||||||
this.notifyNewScreen('login');
|
|
||||||
ThemeController.isLogin = true;
|
|
||||||
this.themeWatcher.recheck();
|
|
||||||
break;
|
break;
|
||||||
case 'start_password_recovery':
|
case 'start_password_recovery':
|
||||||
this.setStateForNewView({
|
this.setStateForNewView({
|
||||||
|
@ -934,7 +928,6 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||||
page_type: PageTypes.RoomView,
|
page_type: PageTypes.RoomView,
|
||||||
threepidInvite: roomInfo.threepid_invite,
|
threepidInvite: roomInfo.threepid_invite,
|
||||||
roomOobData: roomInfo.oob_data,
|
roomOobData: roomInfo.oob_data,
|
||||||
viaServers: roomInfo.via_servers,
|
|
||||||
ready: true,
|
ready: true,
|
||||||
roomJustCreatedOpts: roomInfo.justCreatedOpts,
|
roomJustCreatedOpts: roomInfo.justCreatedOpts,
|
||||||
}, () => {
|
}, () => {
|
||||||
|
@ -975,6 +968,9 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||||
}
|
}
|
||||||
|
|
||||||
private viewWelcome() {
|
private viewWelcome() {
|
||||||
|
if (shouldUseLoginForWelcome(SdkConfig.get())) {
|
||||||
|
return this.viewLogin();
|
||||||
|
}
|
||||||
this.setStateForNewView({
|
this.setStateForNewView({
|
||||||
view: Views.WELCOME,
|
view: Views.WELCOME,
|
||||||
});
|
});
|
||||||
|
@ -983,6 +979,16 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||||
this.themeWatcher.recheck();
|
this.themeWatcher.recheck();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private viewLogin(otherState?: any) {
|
||||||
|
this.setStateForNewView({
|
||||||
|
view: Views.LOGIN,
|
||||||
|
...otherState,
|
||||||
|
});
|
||||||
|
this.notifyNewScreen('login');
|
||||||
|
ThemeController.isLogin = true;
|
||||||
|
this.themeWatcher.recheck();
|
||||||
|
}
|
||||||
|
|
||||||
private viewHome(justRegistered = false) {
|
private viewHome(justRegistered = false) {
|
||||||
// The home page requires the "logged in" view, so we'll set that.
|
// The home page requires the "logged in" view, so we'll set that.
|
||||||
this.setStateForNewView({
|
this.setStateForNewView({
|
||||||
|
@ -1139,11 +1145,17 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||||
}
|
}
|
||||||
|
|
||||||
private forgetRoom(roomId: string) {
|
private forgetRoom(roomId: string) {
|
||||||
|
const room = MatrixClientPeg.get().getRoom(roomId);
|
||||||
MatrixClientPeg.get().forget(roomId).then(() => {
|
MatrixClientPeg.get().forget(roomId).then(() => {
|
||||||
// Switch to home page if we're currently viewing the forgotten room
|
// Switch to home page if we're currently viewing the forgotten room
|
||||||
if (this.state.currentRoomId === roomId) {
|
if (this.state.currentRoomId === roomId) {
|
||||||
dis.dispatch({ action: "view_home_page" });
|
dis.dispatch({ action: "view_home_page" });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// We have to manually update the room list because the forgotten room will not
|
||||||
|
// be notified to us, therefore the room list will have no other way of knowing
|
||||||
|
// the room is forgotten.
|
||||||
|
RoomListStore.instance.manualRoomUpdate(room, RoomUpdateCause.RoomRemoved);
|
||||||
}).catch((err) => {
|
}).catch((err) => {
|
||||||
const errCode = err.errcode || _td("unknown error code");
|
const errCode = err.errcode || _td("unknown error code");
|
||||||
Modal.createTrackedDialog("Failed to forget room", '', ErrorDialog, {
|
Modal.createTrackedDialog("Failed to forget room", '', ErrorDialog, {
|
||||||
|
@ -1298,17 +1310,13 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||||
* Called when the session is logged out
|
* Called when the session is logged out
|
||||||
*/
|
*/
|
||||||
private onLoggedOut() {
|
private onLoggedOut() {
|
||||||
this.notifyNewScreen('login');
|
this.viewLogin({
|
||||||
this.setStateForNewView({
|
|
||||||
view: Views.LOGIN,
|
|
||||||
ready: false,
|
ready: false,
|
||||||
collapseLhs: false,
|
collapseLhs: false,
|
||||||
currentRoomId: null,
|
currentRoomId: null,
|
||||||
});
|
});
|
||||||
this.subTitleStatus = '';
|
this.subTitleStatus = '';
|
||||||
this.setPageSubtitle();
|
this.setPageSubtitle();
|
||||||
ThemeController.isLogin = true;
|
|
||||||
this.themeWatcher.recheck();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1648,7 +1656,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||||
let cli = MatrixClientPeg.get();
|
let cli = MatrixClientPeg.get();
|
||||||
if (!cli) {
|
if (!cli) {
|
||||||
const {hsUrl, isUrl} = this.props.serverConfig;
|
const {hsUrl, isUrl} = this.props.serverConfig;
|
||||||
cli = Matrix.createClient({
|
cli = createClient({
|
||||||
baseUrl: hsUrl,
|
baseUrl: hsUrl,
|
||||||
idBaseUrl: isUrl,
|
idBaseUrl: isUrl,
|
||||||
});
|
});
|
||||||
|
|
|
@ -23,7 +23,6 @@ import classNames from 'classnames';
|
||||||
import shouldHideEvent from '../../shouldHideEvent';
|
import shouldHideEvent from '../../shouldHideEvent';
|
||||||
import {wantsDateSeparator} from '../../DateUtils';
|
import {wantsDateSeparator} from '../../DateUtils';
|
||||||
import * as sdk from '../../index';
|
import * as sdk from '../../index';
|
||||||
import dis from "../../dispatcher/dispatcher";
|
|
||||||
|
|
||||||
import {MatrixClientPeg} from '../../MatrixClientPeg';
|
import {MatrixClientPeg} from '../../MatrixClientPeg';
|
||||||
import SettingsStore from '../../settings/SettingsStore';
|
import SettingsStore from '../../settings/SettingsStore';
|
||||||
|
@ -47,6 +46,9 @@ function shouldFormContinuation(prevEvent, mxEvent) {
|
||||||
// check if within the max continuation period
|
// check if within the max continuation period
|
||||||
if (mxEvent.getTs() - prevEvent.getTs() > CONTINUATION_MAX_INTERVAL) return false;
|
if (mxEvent.getTs() - prevEvent.getTs() > CONTINUATION_MAX_INTERVAL) return false;
|
||||||
|
|
||||||
|
// As we summarise redactions, do not continue a redacted event onto a non-redacted one and vice-versa
|
||||||
|
if (mxEvent.isRedacted() !== prevEvent.isRedacted()) return false;
|
||||||
|
|
||||||
// Some events should appear as continuations from previous events of different types.
|
// Some events should appear as continuations from previous events of different types.
|
||||||
if (mxEvent.getType() !== prevEvent.getType() &&
|
if (mxEvent.getType() !== prevEvent.getType() &&
|
||||||
(!continuedTypes.includes(mxEvent.getType()) ||
|
(!continuedTypes.includes(mxEvent.getType()) ||
|
||||||
|
@ -210,13 +212,11 @@ export default class MessagePanel extends React.Component {
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
this._isMounted = true;
|
this._isMounted = true;
|
||||||
this.dispatcherRef = dis.register(this.onAction);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
this._isMounted = false;
|
this._isMounted = false;
|
||||||
SettingsStore.unwatchSetting(this._showTypingNotificationsWatcherRef);
|
SettingsStore.unwatchSetting(this._showTypingNotificationsWatcherRef);
|
||||||
dis.unregister(this.dispatcherRef);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate(prevProps, prevState) {
|
componentDidUpdate(prevProps, prevState) {
|
||||||
|
@ -229,14 +229,6 @@ export default class MessagePanel extends React.Component {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onAction = (payload) => {
|
|
||||||
switch (payload.action) {
|
|
||||||
case "scroll_to_bottom":
|
|
||||||
this.scrollToBottom();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onShowTypingNotificationsChange = () => {
|
onShowTypingNotificationsChange = () => {
|
||||||
this.setState({
|
this.setState({
|
||||||
showTypingNotifications: SettingsStore.getValue("showTypingNotifications"),
|
showTypingNotifications: SettingsStore.getValue("showTypingNotifications"),
|
||||||
|
@ -463,6 +455,20 @@ export default class MessagePanel extends React.Component {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
_getNextEventInfo(arr, i) {
|
||||||
|
const nextEvent = i < arr.length - 1
|
||||||
|
? arr[i + 1]
|
||||||
|
: null;
|
||||||
|
|
||||||
|
// The next event with tile is used to to determine the 'last successful' flag
|
||||||
|
// when rendering the tile. The shouldShowEvent function is pretty quick at what
|
||||||
|
// it does, so this should have no significant cost even when a room is used for
|
||||||
|
// not-chat purposes.
|
||||||
|
const nextTile = arr.slice(i + 1).find(e => this._shouldShowEvent(e));
|
||||||
|
|
||||||
|
return {nextEvent, nextTile};
|
||||||
|
}
|
||||||
|
|
||||||
_getEventTiles() {
|
_getEventTiles() {
|
||||||
this.eventNodes = {};
|
this.eventNodes = {};
|
||||||
|
|
||||||
|
@ -514,6 +520,7 @@ export default class MessagePanel extends React.Component {
|
||||||
const mxEv = this.props.events[i];
|
const mxEv = this.props.events[i];
|
||||||
const eventId = mxEv.getId();
|
const eventId = mxEv.getId();
|
||||||
const last = (mxEv === lastShownEvent);
|
const last = (mxEv === lastShownEvent);
|
||||||
|
const {nextEvent, nextTile} = this._getNextEventInfo(this.props.events, i);
|
||||||
|
|
||||||
if (grouper) {
|
if (grouper) {
|
||||||
if (grouper.shouldGroup(mxEv)) {
|
if (grouper.shouldGroup(mxEv)) {
|
||||||
|
@ -530,22 +537,12 @@ export default class MessagePanel extends React.Component {
|
||||||
|
|
||||||
for (const Grouper of groupers) {
|
for (const Grouper of groupers) {
|
||||||
if (Grouper.canStartGroup(this, mxEv)) {
|
if (Grouper.canStartGroup(this, mxEv)) {
|
||||||
grouper = new Grouper(this, mxEv, prevEvent, lastShownEvent);
|
grouper = new Grouper(this, mxEv, prevEvent, lastShownEvent, nextEvent, nextTile);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
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;
|
|
||||||
|
|
||||||
// The next event with tile is used to to determine the 'last successful' flag
|
|
||||||
// when rendering the tile. The shouldShowEvent function is pretty quick at what
|
|
||||||
// it does, so this should have no significant cost even when a room is used for
|
|
||||||
// not-chat purposes.
|
|
||||||
const nextTile = this.props.events.slice(i + 1).find(e => this._shouldShowEvent(e));
|
|
||||||
|
|
||||||
// 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.
|
||||||
|
@ -1038,6 +1035,103 @@ class CreationGrouper {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class RedactionGrouper {
|
||||||
|
static canStartGroup = function(panel, ev) {
|
||||||
|
return panel._shouldShowEvent(ev) && ev.isRedacted();
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(panel, ev, prevEvent, lastShownEvent, nextEvent, nextEventTile) {
|
||||||
|
this.panel = panel;
|
||||||
|
this.readMarker = panel._readMarkerForEvent(
|
||||||
|
ev.getId(),
|
||||||
|
ev === lastShownEvent,
|
||||||
|
);
|
||||||
|
this.events = [ev];
|
||||||
|
this.prevEvent = prevEvent;
|
||||||
|
this.lastShownEvent = lastShownEvent;
|
||||||
|
this.nextEvent = nextEvent;
|
||||||
|
this.nextEventTile = nextEventTile;
|
||||||
|
}
|
||||||
|
|
||||||
|
shouldGroup(ev) {
|
||||||
|
// absorb hidden events so that they do not break up streams of messages & redaction events being grouped
|
||||||
|
if (!this.panel._shouldShowEvent(ev)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (this.panel._wantsDateSeparator(this.events[0], ev.getDate())) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return ev.isRedacted();
|
||||||
|
}
|
||||||
|
|
||||||
|
add(ev) {
|
||||||
|
this.readMarker = this.readMarker || this.panel._readMarkerForEvent(
|
||||||
|
ev.getId(),
|
||||||
|
ev === this.lastShownEvent,
|
||||||
|
);
|
||||||
|
if (!this.panel._shouldShowEvent(ev)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.events.push(ev);
|
||||||
|
}
|
||||||
|
|
||||||
|
getTiles() {
|
||||||
|
if (!this.events || !this.events.length) return [];
|
||||||
|
|
||||||
|
const DateSeparator = sdk.getComponent('messages.DateSeparator');
|
||||||
|
const EventListSummary = sdk.getComponent('views.elements.EventListSummary');
|
||||||
|
|
||||||
|
const panel = this.panel;
|
||||||
|
const ret = [];
|
||||||
|
const lastShownEvent = this.lastShownEvent;
|
||||||
|
|
||||||
|
if (panel._wantsDateSeparator(this.prevEvent, this.events[0].getDate())) {
|
||||||
|
const ts = this.events[0].getTs();
|
||||||
|
ret.push(
|
||||||
|
<li key={ts+'~'}><DateSeparator key={ts+'~'} ts={ts} /></li>,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const key = "redactioneventlistsummary-" + (
|
||||||
|
this.prevEvent ? this.events[0].getId() : "initial"
|
||||||
|
);
|
||||||
|
|
||||||
|
const senders = new Set();
|
||||||
|
let eventTiles = this.events.map((e, i) => {
|
||||||
|
senders.add(e.sender);
|
||||||
|
const prevEvent = i === 0 ? this.prevEvent : this.events[i - 1];
|
||||||
|
return panel._getTilesForEvent(prevEvent, e, e === lastShownEvent, this.nextEvent, this.nextEventTile);
|
||||||
|
}).reduce((a, b) => a.concat(b), []);
|
||||||
|
|
||||||
|
if (eventTiles.length === 0) {
|
||||||
|
eventTiles = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret.push(
|
||||||
|
<EventListSummary
|
||||||
|
key={key}
|
||||||
|
threshold={2}
|
||||||
|
events={this.events}
|
||||||
|
onToggle={panel._onHeightChanged} // Update scroll state
|
||||||
|
summaryMembers={Array.from(senders)}
|
||||||
|
summaryText={_t("%(count)s messages deleted.", { count: eventTiles.length })}
|
||||||
|
>
|
||||||
|
{ eventTiles }
|
||||||
|
</EventListSummary>,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (this.readMarker) {
|
||||||
|
ret.push(this.readMarker);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
getNewPrevEvent() {
|
||||||
|
return this.events[this.events.length - 1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Wrap consecutive member events in a ListSummary, ignore if redacted
|
// Wrap consecutive member events in a ListSummary, ignore if redacted
|
||||||
class MemberGrouper {
|
class MemberGrouper {
|
||||||
static canStartGroup = function(panel, ev) {
|
static canStartGroup = function(panel, ev) {
|
||||||
|
@ -1148,4 +1242,4 @@ class MemberGrouper {
|
||||||
}
|
}
|
||||||
|
|
||||||
// all the grouper classes that we use
|
// all the grouper classes that we use
|
||||||
const groupers = [CreationGrouper, MemberGrouper];
|
const groupers = [CreationGrouper, MemberGrouper, RedactionGrouper];
|
||||||
|
|
|
@ -27,7 +27,6 @@ import { _t } from '../../languageHandler';
|
||||||
import SdkConfig from '../../SdkConfig';
|
import SdkConfig from '../../SdkConfig';
|
||||||
import { instanceForInstanceId, protocolNameForInstanceId } from '../../utils/DirectoryUtils';
|
import { instanceForInstanceId, protocolNameForInstanceId } from '../../utils/DirectoryUtils';
|
||||||
import Analytics from '../../Analytics';
|
import Analytics from '../../Analytics';
|
||||||
import {getHttpUriForMxc} from "matrix-js-sdk/src/content-repo";
|
|
||||||
import {ALL_ROOMS} from "../views/directory/NetworkDropdown";
|
import {ALL_ROOMS} from "../views/directory/NetworkDropdown";
|
||||||
import SettingsStore from "../../settings/SettingsStore";
|
import SettingsStore from "../../settings/SettingsStore";
|
||||||
import GroupFilterOrderStore from "../../stores/GroupFilterOrderStore";
|
import GroupFilterOrderStore from "../../stores/GroupFilterOrderStore";
|
||||||
|
@ -35,6 +34,7 @@ import GroupStore from "../../stores/GroupStore";
|
||||||
import FlairStore from "../../stores/FlairStore";
|
import FlairStore from "../../stores/FlairStore";
|
||||||
import CountlyAnalytics from "../../CountlyAnalytics";
|
import CountlyAnalytics from "../../CountlyAnalytics";
|
||||||
import {replaceableComponent} from "../../utils/replaceableComponent";
|
import {replaceableComponent} from "../../utils/replaceableComponent";
|
||||||
|
import {mediaFromMxc} from "../../customisations/Media";
|
||||||
|
|
||||||
const MAX_NAME_LENGTH = 80;
|
const MAX_NAME_LENGTH = 80;
|
||||||
const MAX_TOPIC_LENGTH = 800;
|
const MAX_TOPIC_LENGTH = 800;
|
||||||
|
@ -521,10 +521,9 @@ export default class RoomDirectory extends React.Component {
|
||||||
topic = `${topic.substring(0, MAX_TOPIC_LENGTH)}...`;
|
topic = `${topic.substring(0, MAX_TOPIC_LENGTH)}...`;
|
||||||
}
|
}
|
||||||
topic = linkifyAndSanitizeHtml(topic);
|
topic = linkifyAndSanitizeHtml(topic);
|
||||||
const avatarUrl = getHttpUriForMxc(
|
let avatarUrl = null;
|
||||||
MatrixClientPeg.get().getHomeserverUrl(),
|
if (room.avatar_url) avatarUrl = mediaFromMxc(room.avatar_url).getSquareThumbnailHttp(32);
|
||||||
room.avatar_url, 32, 32, "crop",
|
|
||||||
);
|
|
||||||
return [
|
return [
|
||||||
<div key={ `${room.room_id}_avatar` }
|
<div key={ `${room.room_id}_avatar` }
|
||||||
onClick={(ev) => this.onRoomClicked(room, ev)}
|
onClick={(ev) => this.onRoomClicked(room, ev)}
|
||||||
|
|
|
@ -16,7 +16,6 @@ limitations under the License.
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import Matrix from 'matrix-js-sdk';
|
|
||||||
import { _t, _td } from '../../languageHandler';
|
import { _t, _td } from '../../languageHandler';
|
||||||
import {MatrixClientPeg} from '../../MatrixClientPeg';
|
import {MatrixClientPeg} from '../../MatrixClientPeg';
|
||||||
import Resend from '../../Resend';
|
import Resend from '../../Resend';
|
||||||
|
@ -24,6 +23,7 @@ import dis from '../../dispatcher/dispatcher';
|
||||||
import {messageForResourceLimitError, messageForSendError} from '../../utils/ErrorUtils';
|
import {messageForResourceLimitError, messageForSendError} from '../../utils/ErrorUtils';
|
||||||
import {Action} from "../../dispatcher/actions";
|
import {Action} from "../../dispatcher/actions";
|
||||||
import {replaceableComponent} from "../../utils/replaceableComponent";
|
import {replaceableComponent} from "../../utils/replaceableComponent";
|
||||||
|
import {EventStatus} from "matrix-js-sdk/src/models/event";
|
||||||
|
|
||||||
const STATUS_BAR_HIDDEN = 0;
|
const STATUS_BAR_HIDDEN = 0;
|
||||||
const STATUS_BAR_EXPANDED = 1;
|
const STATUS_BAR_EXPANDED = 1;
|
||||||
|
@ -32,7 +32,7 @@ const STATUS_BAR_EXPANDED_LARGE = 2;
|
||||||
function getUnsentMessages(room) {
|
function getUnsentMessages(room) {
|
||||||
if (!room) { return []; }
|
if (!room) { return []; }
|
||||||
return room.getPendingEvents().filter(function(ev) {
|
return room.getPendingEvents().filter(function(ev) {
|
||||||
return ev.status === Matrix.EventStatus.NOT_SENT;
|
return ev.status === EventStatus.NOT_SENT;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -112,10 +112,6 @@ interface IProps {
|
||||||
inviterName?: string;
|
inviterName?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Servers the RoomView can use to try and assist joins
|
|
||||||
viaServers?: string[];
|
|
||||||
|
|
||||||
autoJoin?: boolean;
|
|
||||||
resizeNotifier: ResizeNotifier;
|
resizeNotifier: ResizeNotifier;
|
||||||
justCreatedOpts?: IOpts;
|
justCreatedOpts?: IOpts;
|
||||||
|
|
||||||
|
@ -450,9 +446,7 @@ export default class RoomView extends React.Component<IProps, IState> {
|
||||||
// now not joined because the js-sdk peeking API will clobber our historical room,
|
// now not joined because the js-sdk peeking API will clobber our historical room,
|
||||||
// making it impossible to indicate a newly joined room.
|
// making it impossible to indicate a newly joined room.
|
||||||
if (!joining && roomId) {
|
if (!joining && roomId) {
|
||||||
if (this.props.autoJoin) {
|
if (!room && shouldPeek) {
|
||||||
this.onJoinButtonClicked();
|
|
||||||
} else if (!room && shouldPeek) {
|
|
||||||
console.info("Attempting to peek into room %s", roomId);
|
console.info("Attempting to peek into room %s", roomId);
|
||||||
this.setState({
|
this.setState({
|
||||||
peekLoading: true,
|
peekLoading: true,
|
||||||
|
@ -1123,7 +1117,7 @@ export default class RoomView extends React.Component<IProps, IState> {
|
||||||
const signUrl = this.props.threepidInvite?.signUrl;
|
const signUrl = this.props.threepidInvite?.signUrl;
|
||||||
dis.dispatch({
|
dis.dispatch({
|
||||||
action: 'join_room',
|
action: 'join_room',
|
||||||
opts: { inviteSignUrl: signUrl, viaServers: this.props.viaServers },
|
opts: { inviteSignUrl: signUrl },
|
||||||
_type: "unknown", // TODO: instrumentation
|
_type: "unknown", // TODO: instrumentation
|
||||||
});
|
});
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
|
|
|
@ -32,6 +32,8 @@ export default class SearchBox extends React.Component {
|
||||||
onKeyDown: PropTypes.func,
|
onKeyDown: PropTypes.func,
|
||||||
className: PropTypes.string,
|
className: PropTypes.string,
|
||||||
placeholder: PropTypes.string.isRequired,
|
placeholder: PropTypes.string.isRequired,
|
||||||
|
autoFocus: PropTypes.bool,
|
||||||
|
initialValue: PropTypes.string,
|
||||||
|
|
||||||
// If true, the search box will focus and clear itself
|
// If true, the search box will focus and clear itself
|
||||||
// on room search focus action (it would be nicer to take
|
// on room search focus action (it would be nicer to take
|
||||||
|
@ -49,7 +51,7 @@ export default class SearchBox extends React.Component {
|
||||||
this._search = createRef();
|
this._search = createRef();
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
searchTerm: "",
|
searchTerm: this.props.initialValue || "",
|
||||||
blurred: true,
|
blurred: true,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -158,6 +160,7 @@ export default class SearchBox extends React.Component {
|
||||||
onBlur={this._onBlur}
|
onBlur={this._onBlur}
|
||||||
placeholder={ placeholder }
|
placeholder={ placeholder }
|
||||||
autoComplete="off"
|
autoComplete="off"
|
||||||
|
autoFocus={this.props.autoFocus}
|
||||||
/>
|
/>
|
||||||
{ clearButton }
|
{ clearButton }
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -14,26 +14,31 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, {useMemo, useRef, useState} from "react";
|
import React, {useMemo, useState} from "react";
|
||||||
import Room from "matrix-js-sdk/src/models/room";
|
import {Room} from "matrix-js-sdk/src/models/room";
|
||||||
import MatrixEvent from "matrix-js-sdk/src/models/event";
|
import {MatrixClient} from "matrix-js-sdk/src/client";
|
||||||
import {EventType, RoomType} from "matrix-js-sdk/src/@types/event";
|
import {EventType, RoomType} from "matrix-js-sdk/src/@types/event";
|
||||||
|
import classNames from "classnames";
|
||||||
|
import {sortBy} from "lodash";
|
||||||
|
|
||||||
import {MatrixClientPeg} from "../../MatrixClientPeg";
|
import {MatrixClientPeg} from "../../MatrixClientPeg";
|
||||||
import dis from "../../dispatcher/dispatcher";
|
import dis from "../../dispatcher/dispatcher";
|
||||||
import {_t} from "../../languageHandler";
|
import {_t} from "../../languageHandler";
|
||||||
import AccessibleButton from "../views/elements/AccessibleButton";
|
import AccessibleButton from "../views/elements/AccessibleButton";
|
||||||
import BaseDialog from "../views/dialogs/BaseDialog";
|
import BaseDialog from "../views/dialogs/BaseDialog";
|
||||||
import FormButton from "../views/elements/FormButton";
|
import Spinner from "../views/elements/Spinner";
|
||||||
import SearchBox from "./SearchBox";
|
import SearchBox from "./SearchBox";
|
||||||
import RoomAvatar from "../views/avatars/RoomAvatar";
|
import RoomAvatar from "../views/avatars/RoomAvatar";
|
||||||
import RoomName from "../views/elements/RoomName";
|
import RoomName from "../views/elements/RoomName";
|
||||||
import {useAsyncMemo} from "../../hooks/useAsyncMemo";
|
import {useAsyncMemo} from "../../hooks/useAsyncMemo";
|
||||||
import {shouldShowSpaceSettings} from "../../utils/space";
|
|
||||||
import {EnhancedMap} from "../../utils/maps";
|
import {EnhancedMap} from "../../utils/maps";
|
||||||
import StyledCheckbox from "../views/elements/StyledCheckbox";
|
import StyledCheckbox from "../views/elements/StyledCheckbox";
|
||||||
import AutoHideScrollbar from "./AutoHideScrollbar";
|
import AutoHideScrollbar from "./AutoHideScrollbar";
|
||||||
import BaseAvatar from "../views/avatars/BaseAvatar";
|
import BaseAvatar from "../views/avatars/BaseAvatar";
|
||||||
|
import {mediaFromMxc} from "../../customisations/Media";
|
||||||
|
import InfoTooltip from "../views/elements/InfoTooltip";
|
||||||
|
import TextWithTooltip from "../views/elements/TextWithTooltip";
|
||||||
|
import {useStateToggle} from "../../hooks/useStateToggle";
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
space: Room;
|
space: Room;
|
||||||
|
@ -71,237 +76,131 @@ export interface ISpaceSummaryEvent {
|
||||||
}
|
}
|
||||||
/* eslint-enable camelcase */
|
/* eslint-enable camelcase */
|
||||||
|
|
||||||
interface ISubspaceProps {
|
interface ITileProps {
|
||||||
space: ISpaceSummaryRoom;
|
room: ISpaceSummaryRoom;
|
||||||
event?: MatrixEvent;
|
suggested?: boolean;
|
||||||
editing?: boolean;
|
selected?: boolean;
|
||||||
onPreviewClick?(): void;
|
numChildRooms?: number;
|
||||||
queueAction?(action: IAction): void;
|
hasPermissions?: boolean;
|
||||||
onJoinClick?(): void;
|
onViewRoomClick(autoJoin: boolean): void;
|
||||||
|
onToggleClick?(): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const SubSpace: React.FC<ISubspaceProps> = ({
|
const Tile: React.FC<ITileProps> = ({
|
||||||
space,
|
room,
|
||||||
editing,
|
suggested,
|
||||||
event,
|
selected,
|
||||||
queueAction,
|
hasPermissions,
|
||||||
onJoinClick,
|
onToggleClick,
|
||||||
onPreviewClick,
|
onViewRoomClick,
|
||||||
|
numChildRooms,
|
||||||
children,
|
children,
|
||||||
}) => {
|
}) => {
|
||||||
const name = space.name || space.canonical_alias || space.aliases?.[0] || _t("Unnamed Space");
|
const name = room.name || room.canonical_alias || room.aliases?.[0]
|
||||||
|
|| (room.room_type === RoomType.Space ? _t("Unnamed Space") : _t("Unnamed Room"));
|
||||||
|
|
||||||
const evContent = event?.getContent();
|
const [showChildren, toggleShowChildren] = useStateToggle(true);
|
||||||
const [suggested, _setSuggested] = useState(evContent?.suggested);
|
|
||||||
const [removed, _setRemoved] = useState(!evContent?.via);
|
|
||||||
|
|
||||||
const cli = MatrixClientPeg.get();
|
|
||||||
const cliRoom = cli.getRoom(space.room_id);
|
|
||||||
const myMembership = cliRoom?.getMyMembership();
|
|
||||||
|
|
||||||
// TODO DRY code
|
|
||||||
let actions;
|
|
||||||
if (editing && queueAction) {
|
|
||||||
if (event && cli.getRoom(event.getRoomId())?.currentState.maySendStateEvent(event.getType(), cli.getUserId())) {
|
|
||||||
const setSuggested = () => {
|
|
||||||
_setSuggested(v => {
|
|
||||||
queueAction({
|
|
||||||
event,
|
|
||||||
removed,
|
|
||||||
suggested: !v,
|
|
||||||
});
|
|
||||||
return !v;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const setRemoved = () => {
|
|
||||||
_setRemoved(v => {
|
|
||||||
queueAction({
|
|
||||||
event,
|
|
||||||
removed: !v,
|
|
||||||
suggested,
|
|
||||||
});
|
|
||||||
return !v;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
if (removed) {
|
|
||||||
actions = <React.Fragment>
|
|
||||||
<FormButton kind="danger" onClick={setRemoved} label={_t("Undo")} />
|
|
||||||
</React.Fragment>;
|
|
||||||
} else {
|
|
||||||
actions = <React.Fragment>
|
|
||||||
<FormButton kind="danger" onClick={setRemoved} label={_t("Remove from Space")} />
|
|
||||||
<StyledCheckbox checked={suggested} onChange={setSuggested} />
|
|
||||||
</React.Fragment>;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
actions = <span className="mx_SpaceRoomDirectory_actionsText">
|
|
||||||
{ _t("No permissions")}
|
|
||||||
</span>;
|
|
||||||
}
|
|
||||||
// TODO confirm remove from space click behaviour here
|
|
||||||
} else {
|
|
||||||
if (myMembership === "join") {
|
|
||||||
actions = <span className="mx_SpaceRoomDirectory_actionsText">
|
|
||||||
{ _t("You're in this space")}
|
|
||||||
</span>;
|
|
||||||
} else if (onJoinClick) {
|
|
||||||
actions = <React.Fragment>
|
|
||||||
<AccessibleButton onClick={onPreviewClick} kind="link">
|
|
||||||
{ _t("Preview") }
|
|
||||||
</AccessibleButton>
|
|
||||||
<FormButton onClick={onJoinClick} label={_t("Join")} />
|
|
||||||
</React.Fragment>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let url: string;
|
|
||||||
if (space.avatar_url) {
|
|
||||||
url = MatrixClientPeg.get().mxcUrlToHttp(
|
|
||||||
space.avatar_url,
|
|
||||||
Math.floor(24 * window.devicePixelRatio),
|
|
||||||
Math.floor(24 * window.devicePixelRatio),
|
|
||||||
"crop",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return <div className="mx_SpaceRoomDirectory_subspace">
|
|
||||||
<div className="mx_SpaceRoomDirectory_subspace_info">
|
|
||||||
<BaseAvatar name={name} idName={space.room_id} url={url} width={24} height={24} />
|
|
||||||
{ name }
|
|
||||||
|
|
||||||
<div className="mx_SpaceRoomDirectory_actions">
|
|
||||||
{ actions }
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="mx_SpaceRoomDirectory_subspace_children">
|
|
||||||
{ children }
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
};
|
|
||||||
|
|
||||||
interface IAction {
|
|
||||||
event: MatrixEvent;
|
|
||||||
suggested: boolean;
|
|
||||||
removed: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface IRoomTileProps {
|
|
||||||
room: ISpaceSummaryRoom;
|
|
||||||
event?: MatrixEvent;
|
|
||||||
editing?: boolean;
|
|
||||||
onPreviewClick(): void;
|
|
||||||
queueAction?(action: IAction): void;
|
|
||||||
onJoinClick?(): void;
|
|
||||||
}
|
|
||||||
|
|
||||||
const RoomTile = ({ room, event, editing, queueAction, onPreviewClick, onJoinClick }: IRoomTileProps) => {
|
|
||||||
const name = room.name || room.canonical_alias || room.aliases?.[0] || _t("Unnamed Room");
|
|
||||||
|
|
||||||
const evContent = event?.getContent();
|
|
||||||
const [suggested, _setSuggested] = useState(evContent?.suggested);
|
|
||||||
const [removed, _setRemoved] = useState(!evContent?.via);
|
|
||||||
|
|
||||||
const cli = MatrixClientPeg.get();
|
const cli = MatrixClientPeg.get();
|
||||||
const cliRoom = cli.getRoom(room.room_id);
|
const cliRoom = cli.getRoom(room.room_id);
|
||||||
const myMembership = cliRoom?.getMyMembership();
|
const myMembership = cliRoom?.getMyMembership();
|
||||||
|
|
||||||
let actions;
|
const onPreviewClick = () => onViewRoomClick(false);
|
||||||
if (editing && queueAction) {
|
const onJoinClick = () => onViewRoomClick(true);
|
||||||
if (event && cli.getRoom(event.getRoomId())?.currentState.maySendStateEvent(event.getType(), cli.getUserId())) {
|
|
||||||
const setSuggested = () => {
|
|
||||||
_setSuggested(v => {
|
|
||||||
queueAction({
|
|
||||||
event,
|
|
||||||
removed,
|
|
||||||
suggested: !v,
|
|
||||||
});
|
|
||||||
return !v;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const setRemoved = () => {
|
let button;
|
||||||
_setRemoved(v => {
|
if (myMembership === "join") {
|
||||||
queueAction({
|
button = <AccessibleButton onClick={onPreviewClick} kind="primary_outline">
|
||||||
event,
|
{ _t("Open") }
|
||||||
removed: !v,
|
</AccessibleButton>;
|
||||||
suggested,
|
} else if (onJoinClick) {
|
||||||
});
|
button = <AccessibleButton onClick={onJoinClick} kind="primary">
|
||||||
return !v;
|
{ _t("Join") }
|
||||||
});
|
</AccessibleButton>;
|
||||||
};
|
}
|
||||||
|
|
||||||
if (removed) {
|
let checkbox;
|
||||||
actions = <React.Fragment>
|
if (onToggleClick) {
|
||||||
<FormButton kind="danger" onClick={setRemoved} label={_t("Undo")} />
|
if (hasPermissions) {
|
||||||
</React.Fragment>;
|
checkbox = <StyledCheckbox checked={!!selected} onChange={onToggleClick} />;
|
||||||
} else {
|
|
||||||
actions = <React.Fragment>
|
|
||||||
<FormButton kind="danger" onClick={setRemoved} label={_t("Remove from Space")} />
|
|
||||||
<StyledCheckbox checked={suggested} onChange={setSuggested} />
|
|
||||||
</React.Fragment>;
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
actions = <span className="mx_SpaceRoomDirectory_actionsText">
|
checkbox = <TextWithTooltip
|
||||||
{ _t("No permissions")}
|
tooltip={_t("You don't have permission")}
|
||||||
</span>;
|
onClick={ev => { ev.stopPropagation() }}
|
||||||
}
|
>
|
||||||
// TODO confirm remove from space click behaviour here
|
<StyledCheckbox disabled={true} />
|
||||||
} else {
|
</TextWithTooltip>;
|
||||||
if (myMembership === "join") {
|
|
||||||
actions = <span className="mx_SpaceRoomDirectory_actionsText">
|
|
||||||
{ _t("You're in this room")}
|
|
||||||
</span>;
|
|
||||||
} else if (onJoinClick) {
|
|
||||||
actions = <React.Fragment>
|
|
||||||
<AccessibleButton onClick={onPreviewClick} kind="link">
|
|
||||||
{ _t("Preview") }
|
|
||||||
</AccessibleButton>
|
|
||||||
<FormButton onClick={onJoinClick} label={_t("Join")} />
|
|
||||||
</React.Fragment>
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let url: string;
|
let url: string;
|
||||||
if (room.avatar_url) {
|
if (room.avatar_url) {
|
||||||
url = cli.mxcUrlToHttp(
|
url = mediaFromMxc(room.avatar_url).getSquareThumbnailHttp(Math.floor(20 * window.devicePixelRatio));
|
||||||
room.avatar_url,
|
}
|
||||||
Math.floor(32 * window.devicePixelRatio),
|
|
||||||
Math.floor(32 * window.devicePixelRatio),
|
let description = _t("%(count)s members", { count: room.num_joined_members });
|
||||||
"crop",
|
if (numChildRooms) {
|
||||||
);
|
description += " · " + _t("%(count)s rooms", { count: numChildRooms });
|
||||||
|
}
|
||||||
|
if (room.topic) {
|
||||||
|
description += " · " + room.topic;
|
||||||
|
}
|
||||||
|
|
||||||
|
let suggestedSection;
|
||||||
|
if (suggested) {
|
||||||
|
suggestedSection = <InfoTooltip tooltip={_t("This room is suggested as a good one to join")}>
|
||||||
|
{ _t("Suggested") }
|
||||||
|
</InfoTooltip>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const content = <React.Fragment>
|
const content = <React.Fragment>
|
||||||
<BaseAvatar name={name} idName={room.room_id} url={url} width={32} height={32} />
|
<BaseAvatar name={name} idName={room.room_id} url={url} width={20} height={20} />
|
||||||
|
<div className="mx_SpaceRoomDirectory_roomTile_name">
|
||||||
|
{ name }
|
||||||
|
{ suggestedSection }
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="mx_SpaceRoomDirectory_roomTile_info">
|
<div className="mx_SpaceRoomDirectory_roomTile_info">
|
||||||
<div className="mx_SpaceRoomDirectory_roomTile_name">
|
{ description }
|
||||||
{ name }
|
|
||||||
</div>
|
|
||||||
<div className="mx_SpaceRoomDirectory_roomTile_topic">
|
|
||||||
{ room.topic }
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="mx_SpaceRoomDirectory_roomTile_memberCount">
|
|
||||||
{ room.num_joined_members }
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="mx_SpaceRoomDirectory_actions">
|
<div className="mx_SpaceRoomDirectory_actions">
|
||||||
{ actions }
|
{ button }
|
||||||
|
{ checkbox }
|
||||||
</div>
|
</div>
|
||||||
</React.Fragment>;
|
</React.Fragment>;
|
||||||
|
|
||||||
if (editing) {
|
let childToggle;
|
||||||
return <div className="mx_SpaceRoomDirectory_roomTile">
|
let childSection;
|
||||||
{ content }
|
if (children) {
|
||||||
</div>
|
// the chevron is purposefully a div rather than a button as it should be ignored for a11y
|
||||||
|
childToggle = <div
|
||||||
|
className={classNames("mx_SpaceRoomDirectory_subspace_toggle", {
|
||||||
|
mx_SpaceRoomDirectory_subspace_toggle_shown: showChildren,
|
||||||
|
})}
|
||||||
|
onClick={ev => {
|
||||||
|
ev.stopPropagation();
|
||||||
|
toggleShowChildren();
|
||||||
|
}}
|
||||||
|
/>;
|
||||||
|
if (showChildren) {
|
||||||
|
childSection = <div className="mx_SpaceRoomDirectory_subspace_children">
|
||||||
|
{ children }
|
||||||
|
</div>;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return <AccessibleButton className="mx_SpaceRoomDirectory_roomTile" onClick={onPreviewClick}>
|
return <>
|
||||||
{ content }
|
<AccessibleButton
|
||||||
</AccessibleButton>;
|
className={classNames("mx_SpaceRoomDirectory_roomTile", {
|
||||||
|
mx_SpaceRoomDirectory_subspace: room.room_type === RoomType.Space,
|
||||||
|
})}
|
||||||
|
onClick={(hasPermissions && onToggleClick) ? onToggleClick : onPreviewClick}
|
||||||
|
>
|
||||||
|
{ content }
|
||||||
|
{ childToggle }
|
||||||
|
</AccessibleButton>
|
||||||
|
{ childSection }
|
||||||
|
</>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const showRoom = (room: ISpaceSummaryRoom, viaServers?: string[], autoJoin = false) => {
|
export const showRoom = (room: ISpaceSummaryRoom, viaServers?: string[], autoJoin = false) => {
|
||||||
|
@ -334,98 +233,121 @@ export const showRoom = (room: ISpaceSummaryRoom, viaServers?: string[], autoJoi
|
||||||
interface IHierarchyLevelProps {
|
interface IHierarchyLevelProps {
|
||||||
spaceId: string;
|
spaceId: string;
|
||||||
rooms: Map<string, ISpaceSummaryRoom>;
|
rooms: Map<string, ISpaceSummaryRoom>;
|
||||||
editing?: boolean;
|
relations: Map<string, Map<string, ISpaceSummaryEvent>>;
|
||||||
relations: EnhancedMap<string, string[]>;
|
|
||||||
parents: Set<string>;
|
parents: Set<string>;
|
||||||
queueAction?(action: IAction): void;
|
selectedMap?: Map<string, Set<string>>;
|
||||||
onPreviewClick(roomId: string): void;
|
onViewRoomClick(roomId: string, autoJoin: boolean): void;
|
||||||
onRemoveFromSpaceClick?(roomId: string): void;
|
onToggleClick?(parentId: string, childId: string): void;
|
||||||
onJoinClick?(roomId: string): void;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const HierarchyLevel = ({
|
export const HierarchyLevel = ({
|
||||||
spaceId,
|
spaceId,
|
||||||
rooms,
|
rooms,
|
||||||
editing,
|
|
||||||
relations,
|
relations,
|
||||||
parents,
|
parents,
|
||||||
onPreviewClick,
|
selectedMap,
|
||||||
onJoinClick,
|
onViewRoomClick,
|
||||||
queueAction,
|
onToggleClick,
|
||||||
}: IHierarchyLevelProps) => {
|
}: IHierarchyLevelProps) => {
|
||||||
const cli = MatrixClientPeg.get();
|
const cli = MatrixClientPeg.get();
|
||||||
const space = cli.getRoom(spaceId);
|
const space = cli.getRoom(spaceId);
|
||||||
// TODO respect order
|
const hasPermissions = space?.currentState.maySendStateEvent(EventType.SpaceChild, cli.getUserId())
|
||||||
const [subspaces, childRooms] = relations.get(spaceId)?.reduce((result, roomId: string) => {
|
|
||||||
if (!rooms.has(roomId)) return result; // TODO wat
|
const sortedChildren = sortBy([...(relations.get(spaceId)?.values() || [])], ev => ev.content.order || null);
|
||||||
|
const [subspaces, childRooms] = sortedChildren.reduce((result, ev: ISpaceSummaryEvent) => {
|
||||||
|
const roomId = ev.state_key;
|
||||||
|
if (!rooms.has(roomId)) return result;
|
||||||
result[rooms.get(roomId).room_type === RoomType.Space ? 0 : 1].push(roomId);
|
result[rooms.get(roomId).room_type === RoomType.Space ? 0 : 1].push(roomId);
|
||||||
return result;
|
return result;
|
||||||
}, [[], []]) || [[], []];
|
}, [[], []]) || [[], []];
|
||||||
|
|
||||||
// Don't render this subspace if it has no rooms we can show
|
|
||||||
// TODO this is broken - as a space may have subspaces we still need to show
|
|
||||||
// if (!childRooms.length) return null;
|
|
||||||
|
|
||||||
const userId = cli.getUserId();
|
|
||||||
|
|
||||||
const newParents = new Set(parents).add(spaceId);
|
const newParents = new Set(parents).add(spaceId);
|
||||||
return <React.Fragment>
|
return <React.Fragment>
|
||||||
{
|
{
|
||||||
childRooms.map(roomId => (
|
childRooms.map(roomId => (
|
||||||
<RoomTile
|
<Tile
|
||||||
key={roomId}
|
key={roomId}
|
||||||
room={rooms.get(roomId)}
|
room={rooms.get(roomId)}
|
||||||
event={space?.currentState.maySendStateEvent(EventType.SpaceChild, userId)
|
suggested={relations.get(spaceId)?.get(roomId)?.content.suggested}
|
||||||
? space?.currentState.getStateEvents(EventType.SpaceChild, roomId)
|
selected={selectedMap?.get(spaceId)?.has(roomId)}
|
||||||
: undefined}
|
onViewRoomClick={(autoJoin) => {
|
||||||
editing={editing}
|
onViewRoomClick(roomId, autoJoin);
|
||||||
queueAction={queueAction}
|
|
||||||
onPreviewClick={() => {
|
|
||||||
onPreviewClick(roomId);
|
|
||||||
}}
|
}}
|
||||||
onJoinClick={onJoinClick ? () => {
|
hasPermissions={hasPermissions}
|
||||||
onJoinClick(roomId);
|
onToggleClick={onToggleClick ? () => onToggleClick(spaceId, roomId) : undefined}
|
||||||
} : undefined}
|
|
||||||
/>
|
/>
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
subspaces.filter(roomId => !newParents.has(roomId)).map(roomId => (
|
subspaces.filter(roomId => !newParents.has(roomId)).map(roomId => (
|
||||||
<SubSpace
|
<Tile
|
||||||
key={roomId}
|
key={roomId}
|
||||||
space={rooms.get(roomId)}
|
room={rooms.get(roomId)}
|
||||||
event={space?.currentState.getStateEvents(EventType.SpaceChild, roomId)}
|
numChildRooms={Array.from(relations.get(roomId)?.values() || [])
|
||||||
editing={editing}
|
.filter(ev => rooms.get(ev.state_key)?.room_type !== RoomType.Space).length}
|
||||||
queueAction={queueAction}
|
suggested={relations.get(spaceId)?.get(roomId)?.content.suggested}
|
||||||
onPreviewClick={() => {
|
selected={selectedMap?.get(spaceId)?.has(roomId)}
|
||||||
onPreviewClick(roomId);
|
onViewRoomClick={(autoJoin) => {
|
||||||
}}
|
onViewRoomClick(roomId, autoJoin);
|
||||||
onJoinClick={() => {
|
|
||||||
onJoinClick(roomId);
|
|
||||||
}}
|
}}
|
||||||
|
hasPermissions={hasPermissions}
|
||||||
|
onToggleClick={onToggleClick ? () => onToggleClick(spaceId, roomId) : undefined}
|
||||||
>
|
>
|
||||||
<HierarchyLevel
|
<HierarchyLevel
|
||||||
spaceId={roomId}
|
spaceId={roomId}
|
||||||
rooms={rooms}
|
rooms={rooms}
|
||||||
editing={editing}
|
|
||||||
relations={relations}
|
relations={relations}
|
||||||
parents={newParents}
|
parents={newParents}
|
||||||
onPreviewClick={onPreviewClick}
|
selectedMap={selectedMap}
|
||||||
onJoinClick={onJoinClick}
|
onViewRoomClick={onViewRoomClick}
|
||||||
queueAction={queueAction}
|
onToggleClick={onToggleClick}
|
||||||
/>
|
/>
|
||||||
</SubSpace>
|
</Tile>
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
};
|
};
|
||||||
|
|
||||||
const SpaceRoomDirectory: React.FC<IProps> = ({ space, initialText = "", onFinished }) => {
|
// mutate argument refreshToken to force a reload
|
||||||
|
export const useSpaceSummary = (cli: MatrixClient, space: Room, refreshToken?: any): [
|
||||||
|
ISpaceSummaryRoom[],
|
||||||
|
Map<string, Map<string, ISpaceSummaryEvent>>,
|
||||||
|
Map<string, Set<string>>,
|
||||||
|
Map<string, Set<string>>,
|
||||||
|
] | [] => {
|
||||||
// TODO pagination
|
// TODO pagination
|
||||||
|
return useAsyncMemo(async () => {
|
||||||
|
try {
|
||||||
|
const data = await cli.getSpaceSummary(space.roomId);
|
||||||
|
|
||||||
|
const parentChildRelations = new EnhancedMap<string, Map<string, ISpaceSummaryEvent>>();
|
||||||
|
const childParentRelations = new EnhancedMap<string, Set<string>>();
|
||||||
|
const viaMap = new EnhancedMap<string, Set<string>>();
|
||||||
|
data.events.map((ev: ISpaceSummaryEvent) => {
|
||||||
|
if (ev.type === EventType.SpaceChild) {
|
||||||
|
parentChildRelations.getOrCreate(ev.room_id, new Map()).set(ev.state_key, ev);
|
||||||
|
childParentRelations.getOrCreate(ev.state_key, new Set()).add(ev.room_id);
|
||||||
|
}
|
||||||
|
if (Array.isArray(ev.content["via"])) {
|
||||||
|
const set = viaMap.getOrCreate(ev.state_key, new Set());
|
||||||
|
ev.content["via"].forEach(via => set.add(via));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return [data.rooms as ISpaceSummaryRoom[], parentChildRelations, viaMap, childParentRelations];
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e); // TODO
|
||||||
|
}
|
||||||
|
|
||||||
|
return [];
|
||||||
|
}, [space, refreshToken], []);
|
||||||
|
};
|
||||||
|
|
||||||
|
const SpaceRoomDirectory: React.FC<IProps> = ({ space, initialText = "", onFinished }) => {
|
||||||
const cli = MatrixClientPeg.get();
|
const cli = MatrixClientPeg.get();
|
||||||
|
const userId = cli.getUserId();
|
||||||
const [query, setQuery] = useState(initialText);
|
const [query, setQuery] = useState(initialText);
|
||||||
const [isEditing, setIsEditing] = useState(false);
|
|
||||||
|
|
||||||
const onCreateRoomClick = () => {
|
const onCreateRoomClick = () => {
|
||||||
dis.dispatch({
|
dis.dispatch({
|
||||||
|
@ -435,116 +357,210 @@ const SpaceRoomDirectory: React.FC<IProps> = ({ space, initialText = "", onFinis
|
||||||
onFinished();
|
onFinished();
|
||||||
};
|
};
|
||||||
|
|
||||||
// stored within a ref as we don't need to re-render when it changes
|
const [selected, setSelected] = useState(new Map<string, Set<string>>()); // Map<parentId, Set<childId>>
|
||||||
const pendingActions = useRef(new Map<string, IAction>());
|
|
||||||
|
|
||||||
let adminButton;
|
const [rooms, parentChildMap, viaMap, childParentMap] = useSpaceSummary(cli, space);
|
||||||
if (shouldShowSpaceSettings(cli, space)) { // TODO this is an imperfect test
|
|
||||||
const onManageButtonClicked = () => {
|
|
||||||
setIsEditing(true);
|
|
||||||
};
|
|
||||||
|
|
||||||
const onSaveButtonClicked = () => {
|
|
||||||
// TODO setBusy
|
|
||||||
pendingActions.current.forEach(({event, suggested, removed}) => {
|
|
||||||
const content = {
|
|
||||||
...event.getContent(),
|
|
||||||
suggested,
|
|
||||||
};
|
|
||||||
|
|
||||||
if (removed) {
|
|
||||||
delete content["via"];
|
|
||||||
}
|
|
||||||
|
|
||||||
cli.sendStateEvent(event.getRoomId(), event.getType(), content, event.getStateKey());
|
|
||||||
});
|
|
||||||
setIsEditing(false);
|
|
||||||
};
|
|
||||||
|
|
||||||
if (isEditing) {
|
|
||||||
adminButton = <React.Fragment>
|
|
||||||
<FormButton label={_t("Save changes")} onClick={onSaveButtonClicked} />
|
|
||||||
<span>{ _t("Promoted to users") }</span>
|
|
||||||
</React.Fragment>;
|
|
||||||
} else {
|
|
||||||
adminButton = <FormButton label={_t("Manage rooms")} onClick={onManageButtonClicked} />;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const [rooms, relations, viaMap] = useAsyncMemo(async () => {
|
|
||||||
try {
|
|
||||||
const data = await cli.getSpaceSummary(space.roomId);
|
|
||||||
|
|
||||||
const parentChildRelations = new EnhancedMap<string, string[]>();
|
|
||||||
const viaMap = new EnhancedMap<string, Set<string>>();
|
|
||||||
data.events.map((ev: ISpaceSummaryEvent) => {
|
|
||||||
if (ev.type === EventType.SpaceChild) {
|
|
||||||
parentChildRelations.getOrCreate(ev.room_id, []).push(ev.state_key);
|
|
||||||
}
|
|
||||||
if (Array.isArray(ev.content["via"])) {
|
|
||||||
const set = viaMap.getOrCreate(ev.state_key, new Set());
|
|
||||||
ev.content["via"].forEach(via => set.add(via));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return [data.rooms, parentChildRelations, viaMap];
|
|
||||||
} catch (e) {
|
|
||||||
console.error(e); // TODO
|
|
||||||
}
|
|
||||||
|
|
||||||
return [];
|
|
||||||
}, [space], []);
|
|
||||||
|
|
||||||
const roomsMap = useMemo(() => {
|
const roomsMap = useMemo(() => {
|
||||||
if (!rooms) return null;
|
if (!rooms) return null;
|
||||||
const lcQuery = query.toLowerCase();
|
const lcQuery = query.toLowerCase().trim();
|
||||||
|
|
||||||
const filteredRooms = rooms.filter(r => {
|
const roomsMap = new Map<string, ISpaceSummaryRoom>(rooms.map(r => [r.room_id, r]));
|
||||||
return r.room_type === RoomType.Space // always include spaces to allow filtering of sub-space rooms
|
if (!lcQuery) return roomsMap;
|
||||||
|| r.name?.toLowerCase().includes(lcQuery)
|
|
||||||
|| r.topic?.toLowerCase().includes(lcQuery);
|
const directMatches = rooms.filter(r => {
|
||||||
|
return r.name?.toLowerCase().includes(lcQuery) || r.topic?.toLowerCase().includes(lcQuery);
|
||||||
});
|
});
|
||||||
|
|
||||||
return new Map<string, ISpaceSummaryRoom>(filteredRooms.map(r => [r.room_id, r]));
|
// Walk back up the tree to find all parents of the direct matches to show their place in the hierarchy
|
||||||
// const root = rooms.get(space.roomId);
|
const visited = new Set<string>();
|
||||||
}, [rooms, query]);
|
const queue = [...directMatches.map(r => r.room_id)];
|
||||||
|
while (queue.length) {
|
||||||
|
const roomId = queue.pop();
|
||||||
|
visited.add(roomId);
|
||||||
|
childParentMap.get(roomId)?.forEach(parentId => {
|
||||||
|
if (!visited.has(parentId)) {
|
||||||
|
queue.push(parentId);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove any mappings for rooms which were not visited in the walk
|
||||||
|
Array.from(roomsMap.keys()).forEach(roomId => {
|
||||||
|
if (!visited.has(roomId)) {
|
||||||
|
roomsMap.delete(roomId);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return roomsMap;
|
||||||
|
}, [rooms, childParentMap, query]);
|
||||||
|
|
||||||
const title = <React.Fragment>
|
const title = <React.Fragment>
|
||||||
<RoomAvatar room={space} height={40} width={40} />
|
<RoomAvatar room={space} height={32} width={32} />
|
||||||
<div>
|
<div>
|
||||||
<h1>{ _t("Explore rooms") }</h1>
|
<h1>{ _t("Explore rooms") }</h1>
|
||||||
<div><RoomName room={space} /></div>
|
<div><RoomName room={space} /></div>
|
||||||
</div>
|
</div>
|
||||||
</React.Fragment>;
|
</React.Fragment>;
|
||||||
|
|
||||||
const explanation =
|
const explanation =
|
||||||
_t("If you can't find the room you're looking for, ask for an invite or <a>Create a new room</a>.", null,
|
_t("If you can't find the room you're looking for, ask for an invite or <a>create a new room</a>.", null,
|
||||||
{a: sub => {
|
{a: sub => {
|
||||||
return <AccessibleButton kind="link" onClick={onCreateRoomClick}>{sub}</AccessibleButton>;
|
return <AccessibleButton kind="link" onClick={onCreateRoomClick}>{sub}</AccessibleButton>;
|
||||||
}},
|
}},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const [error, setError] = useState("");
|
||||||
|
const [removing, setRemoving] = useState(false);
|
||||||
|
const [saving, setSaving] = useState(false);
|
||||||
|
|
||||||
let content;
|
let content;
|
||||||
if (roomsMap) {
|
if (roomsMap) {
|
||||||
content = <AutoHideScrollbar className="mx_SpaceRoomDirectory_list">
|
const numRooms = Array.from(roomsMap.values()).filter(r => r.room_type !== RoomType.Space).length;
|
||||||
<HierarchyLevel
|
const numSpaces = roomsMap.size - numRooms - 1; // -1 at the end to exclude the space we are looking at
|
||||||
spaceId={space.roomId}
|
|
||||||
rooms={roomsMap}
|
let countsStr;
|
||||||
editing={isEditing}
|
if (numSpaces > 1) {
|
||||||
relations={relations}
|
countsStr = _t("%(count)s rooms and %(numSpaces)s spaces", { count: numRooms, numSpaces });
|
||||||
parents={new Set()}
|
} else if (numSpaces > 0) {
|
||||||
queueAction={action => {
|
countsStr = _t("%(count)s rooms and 1 space", { count: numRooms, numSpaces });
|
||||||
pendingActions.current.set(action.event.room_id, action);
|
} else {
|
||||||
}}
|
countsStr = _t("%(count)s rooms", { count: numRooms, numSpaces });
|
||||||
onPreviewClick={roomId => {
|
}
|
||||||
showRoom(roomsMap.get(roomId), Array.from(viaMap.get(roomId) || []), false);
|
|
||||||
onFinished();
|
let editSection;
|
||||||
}}
|
if (space.getMyMembership() === "join" && space.currentState.maySendStateEvent(EventType.SpaceChild, userId)) {
|
||||||
onJoinClick={(roomId) => {
|
const selectedRelations = Array.from(selected.keys()).flatMap(parentId => {
|
||||||
showRoom(roomsMap.get(roomId), Array.from(viaMap.get(roomId) || []), true);
|
return [...selected.get(parentId).values()].map(childId => [parentId, childId]) as [string, string][];
|
||||||
onFinished();
|
});
|
||||||
}}
|
|
||||||
/>
|
let buttons;
|
||||||
</AutoHideScrollbar>;
|
if (selectedRelations.length) {
|
||||||
|
const selectionAllSuggested = selectedRelations.every(([parentId, childId]) => {
|
||||||
|
return parentChildMap.get(parentId)?.get(childId)?.content.suggested;
|
||||||
|
});
|
||||||
|
|
||||||
|
const disabled = removing || saving;
|
||||||
|
|
||||||
|
buttons = <>
|
||||||
|
<AccessibleButton
|
||||||
|
onClick={async () => {
|
||||||
|
setRemoving(true);
|
||||||
|
try {
|
||||||
|
for (const [parentId, childId] of selectedRelations) {
|
||||||
|
await cli.sendStateEvent(parentId, EventType.SpaceChild, {}, childId);
|
||||||
|
parentChildMap.get(parentId).get(childId).content = {};
|
||||||
|
parentChildMap.set(parentId, new Map(parentChildMap.get(parentId)));
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
setError(_t("Failed to remove some rooms. Try again later"));
|
||||||
|
}
|
||||||
|
setRemoving(false);
|
||||||
|
}}
|
||||||
|
kind="danger_outline"
|
||||||
|
disabled={disabled}
|
||||||
|
>
|
||||||
|
{ removing ? _t("Removing...") : _t("Remove") }
|
||||||
|
</AccessibleButton>
|
||||||
|
<AccessibleButton
|
||||||
|
onClick={async () => {
|
||||||
|
setSaving(true);
|
||||||
|
try {
|
||||||
|
for (const [parentId, childId] of selectedRelations) {
|
||||||
|
const suggested = !selectionAllSuggested;
|
||||||
|
const existingContent = parentChildMap.get(parentId)?.get(childId)?.content;
|
||||||
|
if (!existingContent || existingContent.suggested === suggested) continue;
|
||||||
|
|
||||||
|
const content = {
|
||||||
|
...existingContent,
|
||||||
|
suggested: !selectionAllSuggested,
|
||||||
|
};
|
||||||
|
|
||||||
|
await cli.sendStateEvent(parentId, EventType.SpaceChild, content, childId);
|
||||||
|
|
||||||
|
parentChildMap.get(parentId).get(childId).content = content;
|
||||||
|
parentChildMap.set(parentId, new Map(parentChildMap.get(parentId)));
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
setError("Failed to update some suggestions. Try again later");
|
||||||
|
}
|
||||||
|
setSaving(false);
|
||||||
|
}}
|
||||||
|
kind="primary_outline"
|
||||||
|
disabled={disabled}
|
||||||
|
>
|
||||||
|
{ saving
|
||||||
|
? _t("Saving...")
|
||||||
|
: (selectionAllSuggested ? _t("Mark as not suggested") : _t("Mark as suggested"))
|
||||||
|
}
|
||||||
|
</AccessibleButton>
|
||||||
|
</>;
|
||||||
|
}
|
||||||
|
|
||||||
|
editSection = <span>
|
||||||
|
{ buttons }
|
||||||
|
</span>;
|
||||||
|
}
|
||||||
|
|
||||||
|
let results;
|
||||||
|
if (roomsMap.size) {
|
||||||
|
results = <>
|
||||||
|
<HierarchyLevel
|
||||||
|
spaceId={space.roomId}
|
||||||
|
rooms={roomsMap}
|
||||||
|
relations={parentChildMap}
|
||||||
|
parents={new Set()}
|
||||||
|
selectedMap={selected}
|
||||||
|
onToggleClick={(parentId, childId) => {
|
||||||
|
setError("");
|
||||||
|
if (!selected.has(parentId)) {
|
||||||
|
setSelected(new Map(selected.set(parentId, new Set([childId]))));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const parentSet = selected.get(parentId);
|
||||||
|
if (!parentSet.has(childId)) {
|
||||||
|
setSelected(new Map(selected.set(parentId, new Set([...parentSet, childId]))));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
parentSet.delete(childId);
|
||||||
|
setSelected(new Map(selected.set(parentId, new Set(parentSet))));
|
||||||
|
}}
|
||||||
|
onViewRoomClick={(roomId, autoJoin) => {
|
||||||
|
showRoom(roomsMap.get(roomId), Array.from(viaMap.get(roomId) || []), autoJoin);
|
||||||
|
onFinished();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<hr />
|
||||||
|
</>;
|
||||||
|
} else {
|
||||||
|
results = <div className="mx_SpaceRoomDirectory_noResults">
|
||||||
|
<h3>{ _t("No results found") }</h3>
|
||||||
|
<div>{ _t("You may want to try a different search or check for typos.") }</div>
|
||||||
|
</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
content = <>
|
||||||
|
<div className="mx_SpaceRoomDirectory_listHeader">
|
||||||
|
{ countsStr }
|
||||||
|
{ editSection }
|
||||||
|
</div>
|
||||||
|
{ error && <div className="mx_SpaceRoomDirectory_error">
|
||||||
|
{ error }
|
||||||
|
</div> }
|
||||||
|
<AutoHideScrollbar className="mx_SpaceRoomDirectory_list">
|
||||||
|
{ results }
|
||||||
|
<AccessibleButton
|
||||||
|
onClick={onCreateRoomClick}
|
||||||
|
kind="primary"
|
||||||
|
className="mx_SpaceRoomDirectory_createRoom"
|
||||||
|
>
|
||||||
|
{ _t("Create room") }
|
||||||
|
</AccessibleButton>
|
||||||
|
</AutoHideScrollbar>
|
||||||
|
</>;
|
||||||
|
} else {
|
||||||
|
content = <Spinner />;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO loading state/error state
|
// TODO loading state/error state
|
||||||
|
@ -555,13 +571,12 @@ const SpaceRoomDirectory: React.FC<IProps> = ({ space, initialText = "", onFinis
|
||||||
|
|
||||||
<SearchBox
|
<SearchBox
|
||||||
className="mx_textinput_icon mx_textinput_search"
|
className="mx_textinput_icon mx_textinput_search"
|
||||||
placeholder={ _t("Find a room...") }
|
placeholder={ _t("Search names and description") }
|
||||||
onSearch={setQuery}
|
onSearch={setQuery}
|
||||||
|
autoFocus={true}
|
||||||
|
initialValue={initialText}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div className="mx_SpaceRoomDirectory_listHeader">
|
|
||||||
{ adminButton }
|
|
||||||
</div>
|
|
||||||
{ content }
|
{ content }
|
||||||
</div>
|
</div>
|
||||||
</BaseDialog>
|
</BaseDialog>
|
||||||
|
|
|
@ -14,9 +14,10 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, {RefObject, useContext, useRef, useState} from "react";
|
import React, {RefObject, useContext, useMemo, useRef, useState} from "react";
|
||||||
import {EventType, RoomType} from "matrix-js-sdk/src/@types/event";
|
import {EventType, RoomType} from "matrix-js-sdk/src/@types/event";
|
||||||
import {Room} from "matrix-js-sdk/src/models/room";
|
import {Room} from "matrix-js-sdk/src/models/room";
|
||||||
|
import {EventSubscription} from "fbemitter";
|
||||||
|
|
||||||
import MatrixClientContext from "../../contexts/MatrixClientContext";
|
import MatrixClientContext from "../../contexts/MatrixClientContext";
|
||||||
import RoomAvatar from "../views/avatars/RoomAvatar";
|
import RoomAvatar from "../views/avatars/RoomAvatar";
|
||||||
|
@ -25,13 +26,11 @@ import AccessibleButton from "../views/elements/AccessibleButton";
|
||||||
import RoomName from "../views/elements/RoomName";
|
import RoomName from "../views/elements/RoomName";
|
||||||
import RoomTopic from "../views/elements/RoomTopic";
|
import RoomTopic from "../views/elements/RoomTopic";
|
||||||
import InlineSpinner from "../views/elements/InlineSpinner";
|
import InlineSpinner from "../views/elements/InlineSpinner";
|
||||||
import FormButton from "../views/elements/FormButton";
|
|
||||||
import {inviteMultipleToRoom, showRoomInviteDialog} from "../../RoomInvite";
|
import {inviteMultipleToRoom, showRoomInviteDialog} from "../../RoomInvite";
|
||||||
import {useRoomMembers} from "../../hooks/useRoomMembers";
|
import {useRoomMembers} from "../../hooks/useRoomMembers";
|
||||||
import createRoom, {IOpts, Preset} from "../../createRoom";
|
import createRoom, {IOpts, Preset} from "../../createRoom";
|
||||||
import Field from "../views/elements/Field";
|
import Field from "../views/elements/Field";
|
||||||
import {useEventEmitter} from "../../hooks/useEventEmitter";
|
import {useEventEmitter} from "../../hooks/useEventEmitter";
|
||||||
import StyledRadioGroup from "../views/elements/StyledRadioGroup";
|
|
||||||
import withValidation from "../views/elements/Validation";
|
import withValidation from "../views/elements/Validation";
|
||||||
import * as Email from "../../email";
|
import * as Email from "../../email";
|
||||||
import defaultDispatcher from "../../dispatcher/dispatcher";
|
import defaultDispatcher from "../../dispatcher/dispatcher";
|
||||||
|
@ -42,18 +41,16 @@ import ErrorBoundary from "../views/elements/ErrorBoundary";
|
||||||
import {ActionPayload} from "../../dispatcher/payloads";
|
import {ActionPayload} from "../../dispatcher/payloads";
|
||||||
import RightPanel from "./RightPanel";
|
import RightPanel from "./RightPanel";
|
||||||
import RightPanelStore from "../../stores/RightPanelStore";
|
import RightPanelStore from "../../stores/RightPanelStore";
|
||||||
import {EventSubscription} from "fbemitter";
|
|
||||||
import {RightPanelPhases} from "../../stores/RightPanelStorePhases";
|
import {RightPanelPhases} from "../../stores/RightPanelStorePhases";
|
||||||
import {SetRightPanelPhasePayload} from "../../dispatcher/payloads/SetRightPanelPhasePayload";
|
import {SetRightPanelPhasePayload} from "../../dispatcher/payloads/SetRightPanelPhasePayload";
|
||||||
import {useStateArray} from "../../hooks/useStateArray";
|
import {useStateArray} from "../../hooks/useStateArray";
|
||||||
import SpacePublicShare from "../views/spaces/SpacePublicShare";
|
import SpacePublicShare from "../views/spaces/SpacePublicShare";
|
||||||
import {showAddExistingRooms, showCreateNewRoom, shouldShowSpaceSettings, showSpaceSettings} from "../../utils/space";
|
import {showAddExistingRooms, showCreateNewRoom, shouldShowSpaceSettings, showSpaceSettings} from "../../utils/space";
|
||||||
import {HierarchyLevel, ISpaceSummaryEvent, ISpaceSummaryRoom, showRoom} from "./SpaceRoomDirectory";
|
import {HierarchyLevel, ISpaceSummaryRoom, showRoom, useSpaceSummary} from "./SpaceRoomDirectory";
|
||||||
import {useAsyncMemo} from "../../hooks/useAsyncMemo";
|
|
||||||
import {EnhancedMap} from "../../utils/maps";
|
|
||||||
import AutoHideScrollbar from "./AutoHideScrollbar";
|
import AutoHideScrollbar from "./AutoHideScrollbar";
|
||||||
import MemberAvatar from "../views/avatars/MemberAvatar";
|
import MemberAvatar from "../views/avatars/MemberAvatar";
|
||||||
import {useStateToggle} from "../../hooks/useStateToggle";
|
import {useStateToggle} from "../../hooks/useStateToggle";
|
||||||
|
import SpaceStore from "../../stores/SpaceStore";
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
space: Room;
|
space: Room;
|
||||||
|
@ -66,6 +63,7 @@ interface IProps {
|
||||||
interface IState {
|
interface IState {
|
||||||
phase: Phase;
|
phase: Phase;
|
||||||
showRightPanel: boolean;
|
showRightPanel: boolean;
|
||||||
|
myMembership: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
enum Phase {
|
enum Phase {
|
||||||
|
@ -98,6 +96,8 @@ const SpacePreview = ({ space, onJoinButtonClicked, onRejectButtonClicked }) =>
|
||||||
const cli = useContext(MatrixClientContext);
|
const cli = useContext(MatrixClientContext);
|
||||||
const myMembership = useMyRoomMembership(space);
|
const myMembership = useMyRoomMembership(space);
|
||||||
|
|
||||||
|
const [busy, setBusy] = useState(false);
|
||||||
|
|
||||||
let inviterSection;
|
let inviterSection;
|
||||||
let joinButtons;
|
let joinButtons;
|
||||||
if (myMembership === "invite") {
|
if (myMembership === "invite") {
|
||||||
|
@ -121,11 +121,41 @@ const SpacePreview = ({ space, onJoinButtonClicked, onRejectButtonClicked }) =>
|
||||||
}
|
}
|
||||||
|
|
||||||
joinButtons = <>
|
joinButtons = <>
|
||||||
<FormButton label={_t("Reject")} kind="secondary" onClick={onRejectButtonClicked} />
|
<AccessibleButton
|
||||||
<FormButton label={_t("Accept")} onClick={onJoinButtonClicked} />
|
kind="secondary"
|
||||||
|
onClick={() => {
|
||||||
|
setBusy(true);
|
||||||
|
onRejectButtonClicked();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{ _t("Reject") }
|
||||||
|
</AccessibleButton>
|
||||||
|
<AccessibleButton
|
||||||
|
kind="primary"
|
||||||
|
onClick={() => {
|
||||||
|
setBusy(true);
|
||||||
|
onJoinButtonClicked();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{ _t("Accept") }
|
||||||
|
</AccessibleButton>
|
||||||
</>;
|
</>;
|
||||||
} else {
|
} else {
|
||||||
joinButtons = <FormButton label={_t("Join")} onClick={onJoinButtonClicked} />
|
joinButtons = (
|
||||||
|
<AccessibleButton
|
||||||
|
kind="primary"
|
||||||
|
onClick={() => {
|
||||||
|
setBusy(true);
|
||||||
|
onJoinButtonClicked();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{ _t("Join") }
|
||||||
|
</AccessibleButton>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (busy) {
|
||||||
|
joinButtons = <InlineSpinner />;
|
||||||
}
|
}
|
||||||
|
|
||||||
let visibilitySection;
|
let visibilitySection;
|
||||||
|
@ -196,7 +226,7 @@ const SpaceLanding = ({ space }) => {
|
||||||
|
|
||||||
const canAddRooms = myMembership === "join" && space.currentState.maySendStateEvent(EventType.SpaceChild, userId);
|
const canAddRooms = myMembership === "join" && space.currentState.maySendStateEvent(EventType.SpaceChild, userId);
|
||||||
|
|
||||||
const [_, forceUpdate] = useStateToggle(false); // TODO
|
const [refreshToken, forceUpdate] = useStateToggle(false);
|
||||||
|
|
||||||
let addRoomButtons;
|
let addRoomButtons;
|
||||||
if (canAddRooms) {
|
if (canAddRooms) {
|
||||||
|
@ -226,26 +256,13 @@ const SpaceLanding = ({ space }) => {
|
||||||
</AccessibleButton>;
|
</AccessibleButton>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const [loading, roomsMap, relations, numRooms] = useAsyncMemo(async () => {
|
const [rooms, relations, viaMap] = useSpaceSummary(cli, space, refreshToken);
|
||||||
try {
|
const [roomsMap, numRooms] = useMemo(() => {
|
||||||
const data = await cli.getSpaceSummary(space.roomId, undefined, myMembership !== "join");
|
if (!rooms) return [];
|
||||||
|
const roomsMap = new Map<string, ISpaceSummaryRoom>(rooms.map(r => [r.room_id, r]));
|
||||||
const parentChildRelations = new EnhancedMap<string, string[]>();
|
const numRooms = rooms.filter(r => r.room_type !== RoomType.Space).length;
|
||||||
data.events.map((ev: ISpaceSummaryEvent) => {
|
return [roomsMap, numRooms];
|
||||||
if (ev.type === EventType.SpaceChild) {
|
}, [rooms]);
|
||||||
parentChildRelations.getOrCreate(ev.room_id, []).push(ev.state_key);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const roomsMap = new Map<string, ISpaceSummaryRoom>(data.rooms.map(r => [r.room_id, r]));
|
|
||||||
const numRooms = data.rooms.filter(r => r.room_type !== RoomType.Space).length;
|
|
||||||
return [false, roomsMap, parentChildRelations, numRooms];
|
|
||||||
} catch (e) {
|
|
||||||
console.error(e); // TODO
|
|
||||||
}
|
|
||||||
|
|
||||||
return [false];
|
|
||||||
}, [space, _], [true]);
|
|
||||||
|
|
||||||
let previewRooms;
|
let previewRooms;
|
||||||
if (roomsMap) {
|
if (roomsMap) {
|
||||||
|
@ -257,15 +274,14 @@ const SpaceLanding = ({ space }) => {
|
||||||
<HierarchyLevel
|
<HierarchyLevel
|
||||||
spaceId={space.roomId}
|
spaceId={space.roomId}
|
||||||
rooms={roomsMap}
|
rooms={roomsMap}
|
||||||
editing={false}
|
|
||||||
relations={relations}
|
relations={relations}
|
||||||
parents={new Set()}
|
parents={new Set()}
|
||||||
onPreviewClick={roomId => {
|
onViewRoomClick={(roomId, autoJoin) => {
|
||||||
showRoom(roomsMap.get(roomId), [], false); // TODO
|
showRoom(roomsMap.get(roomId), Array.from(viaMap.get(roomId) || []), autoJoin);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</AutoHideScrollbar>;
|
</AutoHideScrollbar>;
|
||||||
} else if (loading) {
|
} else if (!rooms) {
|
||||||
previewRooms = <InlineSpinner />;
|
previewRooms = <InlineSpinner />;
|
||||||
} else {
|
} else {
|
||||||
previewRooms = <p>{_t("Your server does not support showing space hierarchies.")}</p>;
|
previewRooms = <p>{_t("Your server does not support showing space hierarchies.")}</p>;
|
||||||
|
@ -337,6 +353,7 @@ const SpaceSetupFirstRooms = ({ space, title, description, onFinished }) => {
|
||||||
placeholder={placeholders[i]}
|
placeholder={placeholders[i]}
|
||||||
value={roomNames[i]}
|
value={roomNames[i]}
|
||||||
onChange={ev => setRoomName(i, ev.target.value)}
|
onChange={ev => setRoomName(i, ev.target.value)}
|
||||||
|
autoFocus={i === 2}
|
||||||
/>;
|
/>;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -369,7 +386,7 @@ const SpaceSetupFirstRooms = ({ space, title, description, onFinished }) => {
|
||||||
let buttonLabel = _t("Skip for now");
|
let buttonLabel = _t("Skip for now");
|
||||||
if (roomNames.some(name => name.trim())) {
|
if (roomNames.some(name => name.trim())) {
|
||||||
onClick = onNextClick;
|
onClick = onNextClick;
|
||||||
buttonLabel = busy ? _t("Creating rooms...") : _t("Next")
|
buttonLabel = busy ? _t("Creating rooms...") : _t("Continue")
|
||||||
}
|
}
|
||||||
|
|
||||||
return <div>
|
return <div>
|
||||||
|
@ -380,61 +397,55 @@ const SpaceSetupFirstRooms = ({ space, title, description, onFinished }) => {
|
||||||
{ fields }
|
{ fields }
|
||||||
|
|
||||||
<div className="mx_SpaceRoomView_buttons">
|
<div className="mx_SpaceRoomView_buttons">
|
||||||
<FormButton
|
<AccessibleButton
|
||||||
label={buttonLabel}
|
kind="primary"
|
||||||
disabled={busy}
|
disabled={busy}
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
/>
|
>
|
||||||
|
{ buttonLabel }
|
||||||
|
</AccessibleButton>
|
||||||
</div>
|
</div>
|
||||||
</div>;
|
</div>;
|
||||||
};
|
};
|
||||||
|
|
||||||
const SpaceSetupPublicShare = ({ space, onFinished }) => {
|
const SpaceSetupPublicShare = ({ space, onFinished }) => {
|
||||||
return <div className="mx_SpaceRoomView_publicShare">
|
return <div className="mx_SpaceRoomView_publicShare">
|
||||||
<h1>{ _t("Share your public space") }</h1>
|
<h1>{ _t("Share %(name)s", { name: space.name }) }</h1>
|
||||||
<div className="mx_SpacePublicShare_description">{ _t("At the moment only you can see it.") }</div>
|
<div className="mx_SpaceRoomView_description">
|
||||||
|
{ _t("It's just you at the moment, it will be even better with others.") }
|
||||||
|
</div>
|
||||||
|
|
||||||
<SpacePublicShare space={space} onFinished={onFinished} />
|
<SpacePublicShare space={space} />
|
||||||
|
|
||||||
<div className="mx_SpaceRoomView_buttons">
|
<div className="mx_SpaceRoomView_buttons">
|
||||||
<FormButton label={_t("Finish")} onClick={onFinished} />
|
<AccessibleButton kind="primary" onClick={onFinished}>
|
||||||
|
{ _t("Go to my first room") }
|
||||||
|
</AccessibleButton>
|
||||||
</div>
|
</div>
|
||||||
</div>;
|
</div>;
|
||||||
};
|
};
|
||||||
|
|
||||||
const SpaceSetupPrivateScope = ({ onFinished }) => {
|
const SpaceSetupPrivateScope = ({ space, onFinished }) => {
|
||||||
const [option, setOption] = useState<string>(null);
|
|
||||||
|
|
||||||
return <div className="mx_SpaceRoomView_privateScope">
|
return <div className="mx_SpaceRoomView_privateScope">
|
||||||
<h1>{ _t("Who are you working with?") }</h1>
|
<h1>{ _t("Who are you working with?") }</h1>
|
||||||
<div className="mx_SpaceRoomView_description">{ _t("Ensure the right people have access to the space.") }</div>
|
<div className="mx_SpaceRoomView_description">
|
||||||
|
{ _t("Make sure the right people have access to %(name)s", { name: space.name }) }
|
||||||
<StyledRadioGroup
|
|
||||||
name="privateSpaceScope"
|
|
||||||
value={option}
|
|
||||||
onChange={setOption}
|
|
||||||
definitions={[
|
|
||||||
{
|
|
||||||
value: "justMe",
|
|
||||||
className: "mx_SpaceRoomView_privateScope_justMeButton",
|
|
||||||
label: <React.Fragment>
|
|
||||||
<h3>{ _t("Just Me") }</h3>
|
|
||||||
<div>{ _t("A private space just for you") }</div>
|
|
||||||
</React.Fragment>,
|
|
||||||
}, {
|
|
||||||
value: "meAndMyTeammates",
|
|
||||||
className: "mx_SpaceRoomView_privateScope_meAndMyTeammatesButton",
|
|
||||||
label: <React.Fragment>
|
|
||||||
<h3>{ _t("Me and my teammates") }</h3>
|
|
||||||
<div>{ _t("A private space for you and your teammates") }</div>
|
|
||||||
</React.Fragment>,
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<div className="mx_SpaceRoomView_buttons">
|
|
||||||
<FormButton label={_t("Next")} disabled={!option} onClick={() => onFinished(option !== "justMe")} />
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<AccessibleButton
|
||||||
|
className="mx_SpaceRoomView_privateScope_justMeButton"
|
||||||
|
onClick={() => { onFinished(false) }}
|
||||||
|
>
|
||||||
|
<h3>{ _t("Just me") }</h3>
|
||||||
|
<div>{ _t("A private space to organise your rooms") }</div>
|
||||||
|
</AccessibleButton>
|
||||||
|
<AccessibleButton
|
||||||
|
className="mx_SpaceRoomView_privateScope_meAndMyTeammatesButton"
|
||||||
|
onClick={() => { onFinished(true) }}
|
||||||
|
>
|
||||||
|
<h3>{ _t("Me and my teammates") }</h3>
|
||||||
|
<div>{ _t("A private space for you and your teammates") }</div>
|
||||||
|
</AccessibleButton>
|
||||||
</div>;
|
</div>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -464,6 +475,7 @@ const SpaceSetupPrivateInvite = ({ space, onFinished }) => {
|
||||||
onChange={ev => setEmailAddress(i, ev.target.value)}
|
onChange={ev => setEmailAddress(i, ev.target.value)}
|
||||||
ref={fieldRefs[i]}
|
ref={fieldRefs[i]}
|
||||||
onValidate={validateEmailRules}
|
onValidate={validateEmailRules}
|
||||||
|
autoFocus={i === 0}
|
||||||
/>;
|
/>;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -501,9 +513,18 @@ const SpaceSetupPrivateInvite = ({ space, onFinished }) => {
|
||||||
setBusy(false);
|
setBusy(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let onClick = onFinished;
|
||||||
|
let buttonLabel = _t("Skip for now");
|
||||||
|
if (emailAddresses.some(name => name.trim())) {
|
||||||
|
onClick = onNextClick;
|
||||||
|
buttonLabel = busy ? _t("Inviting...") : _t("Continue")
|
||||||
|
}
|
||||||
|
|
||||||
return <div className="mx_SpaceRoomView_inviteTeammates">
|
return <div className="mx_SpaceRoomView_inviteTeammates">
|
||||||
<h1>{ _t("Invite your teammates") }</h1>
|
<h1>{ _t("Invite your teammates") }</h1>
|
||||||
<div className="mx_SpaceRoomView_description">{ _t("Ensure the right people have access to the space.") }</div>
|
<div className="mx_SpaceRoomView_description">
|
||||||
|
{ _t("Make sure the right people have access. You can invite more later.") }
|
||||||
|
</div>
|
||||||
|
|
||||||
{ error && <div className="mx_SpaceRoomView_errorText">{ error }</div> }
|
{ error && <div className="mx_SpaceRoomView_errorText">{ error }</div> }
|
||||||
{ fields }
|
{ fields }
|
||||||
|
@ -518,8 +539,9 @@ const SpaceSetupPrivateInvite = ({ space, onFinished }) => {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="mx_SpaceRoomView_buttons">
|
<div className="mx_SpaceRoomView_buttons">
|
||||||
<AccessibleButton onClick={onFinished} kind="link">{_t("Skip for now")}</AccessibleButton>
|
<AccessibleButton kind="primary" disabled={busy} onClick={onClick}>
|
||||||
<FormButton label={busy ? _t("Inviting...") : _t("Next")} disabled={busy} onClick={onNextClick} />
|
{ buttonLabel }
|
||||||
|
</AccessibleButton>
|
||||||
</div>
|
</div>
|
||||||
</div>;
|
</div>;
|
||||||
};
|
};
|
||||||
|
@ -547,17 +569,26 @@ export default class SpaceRoomView extends React.PureComponent<IProps, IState> {
|
||||||
this.state = {
|
this.state = {
|
||||||
phase,
|
phase,
|
||||||
showRightPanel: RightPanelStore.getSharedInstance().isOpenForRoom,
|
showRightPanel: RightPanelStore.getSharedInstance().isOpenForRoom,
|
||||||
|
myMembership: this.props.space.getMyMembership(),
|
||||||
};
|
};
|
||||||
|
|
||||||
this.dispatcherRef = defaultDispatcher.register(this.onAction);
|
this.dispatcherRef = defaultDispatcher.register(this.onAction);
|
||||||
this.rightPanelStoreToken = RightPanelStore.getSharedInstance().addListener(this.onRightPanelStoreUpdate);
|
this.rightPanelStoreToken = RightPanelStore.getSharedInstance().addListener(this.onRightPanelStoreUpdate);
|
||||||
|
this.context.on("Room.myMembership", this.onMyMembership);
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
defaultDispatcher.unregister(this.dispatcherRef);
|
defaultDispatcher.unregister(this.dispatcherRef);
|
||||||
this.rightPanelStoreToken.remove();
|
this.rightPanelStoreToken.remove();
|
||||||
|
this.context.off("Room.myMembership", this.onMyMembership);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private onMyMembership = (room: Room, myMembership: string) => {
|
||||||
|
if (room.roomId === this.props.space.roomId) {
|
||||||
|
this.setState({ myMembership });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
private onRightPanelStoreUpdate = () => {
|
private onRightPanelStoreUpdate = () => {
|
||||||
this.setState({
|
this.setState({
|
||||||
showRightPanel: RightPanelStore.getSharedInstance().isOpenForRoom,
|
showRightPanel: RightPanelStore.getSharedInstance().isOpenForRoom,
|
||||||
|
@ -594,10 +625,45 @@ export default class SpaceRoomView extends React.PureComponent<IProps, IState> {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private goToFirstRoom = async () => {
|
||||||
|
// TODO actually go to the first room
|
||||||
|
|
||||||
|
const childRooms = SpaceStore.instance.getChildRooms(this.props.space.roomId);
|
||||||
|
if (childRooms.length) {
|
||||||
|
const room = childRooms[0];
|
||||||
|
defaultDispatcher.dispatch({
|
||||||
|
action: "view_room",
|
||||||
|
room_id: room.roomId,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let suggestedRooms = SpaceStore.instance.suggestedRooms;
|
||||||
|
if (SpaceStore.instance.activeSpace !== this.props.space) {
|
||||||
|
// the space store has the suggested rooms loaded for a different space, fetch the right ones
|
||||||
|
suggestedRooms = (await SpaceStore.instance.fetchSuggestedRooms(this.props.space, 1)).rooms;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (suggestedRooms.length) {
|
||||||
|
const room = suggestedRooms[0];
|
||||||
|
defaultDispatcher.dispatch({
|
||||||
|
action: "view_room",
|
||||||
|
room_id: room.room_id,
|
||||||
|
oobData: {
|
||||||
|
avatarUrl: room.avatar_url,
|
||||||
|
name: room.name || room.canonical_alias || room.aliases.pop() || _t("Empty room"),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setState({ phase: Phase.Landing });
|
||||||
|
};
|
||||||
|
|
||||||
private renderBody() {
|
private renderBody() {
|
||||||
switch (this.state.phase) {
|
switch (this.state.phase) {
|
||||||
case Phase.Landing:
|
case Phase.Landing:
|
||||||
if (this.props.space.getMyMembership() === "join") {
|
if (this.state.myMembership === "join") {
|
||||||
return <SpaceLanding space={this.props.space} />;
|
return <SpaceLanding space={this.props.space} />;
|
||||||
} else {
|
} else {
|
||||||
return <SpacePreview
|
return <SpacePreview
|
||||||
|
@ -610,17 +676,16 @@ export default class SpaceRoomView extends React.PureComponent<IProps, IState> {
|
||||||
return <SpaceSetupFirstRooms
|
return <SpaceSetupFirstRooms
|
||||||
space={this.props.space}
|
space={this.props.space}
|
||||||
title={_t("What are some things you want to discuss?")}
|
title={_t("What are some things you want to discuss?")}
|
||||||
description={_t("We'll create rooms for each topic.")}
|
description={_t("Let's create a room for each of them. " +
|
||||||
|
"You can add more later too, including already existing ones.")}
|
||||||
onFinished={() => this.setState({ phase: Phase.PublicShare })}
|
onFinished={() => this.setState({ phase: Phase.PublicShare })}
|
||||||
/>;
|
/>;
|
||||||
case Phase.PublicShare:
|
case Phase.PublicShare:
|
||||||
return <SpaceSetupPublicShare
|
return <SpaceSetupPublicShare space={this.props.space} onFinished={this.goToFirstRoom} />;
|
||||||
space={this.props.space}
|
|
||||||
onFinished={() => this.setState({ phase: Phase.Landing })}
|
|
||||||
/>;
|
|
||||||
|
|
||||||
case Phase.PrivateScope:
|
case Phase.PrivateScope:
|
||||||
return <SpaceSetupPrivateScope
|
return <SpaceSetupPrivateScope
|
||||||
|
space={this.props.space}
|
||||||
onFinished={(invite: boolean) => {
|
onFinished={(invite: boolean) => {
|
||||||
this.setState({ phase: invite ? Phase.PrivateInvite : Phase.PrivateCreateRooms });
|
this.setState({ phase: invite ? Phase.PrivateInvite : Phase.PrivateCreateRooms });
|
||||||
}}
|
}}
|
||||||
|
@ -634,7 +699,8 @@ export default class SpaceRoomView extends React.PureComponent<IProps, IState> {
|
||||||
return <SpaceSetupFirstRooms
|
return <SpaceSetupFirstRooms
|
||||||
space={this.props.space}
|
space={this.props.space}
|
||||||
title={_t("What projects are you working on?")}
|
title={_t("What projects are you working on?")}
|
||||||
description={_t("We'll create rooms for each of them. You can add existing rooms after setup.")}
|
description={_t("We'll create rooms for each of them. " +
|
||||||
|
"You can add more later too, including already existing ones.")}
|
||||||
onFinished={() => this.setState({ phase: Phase.Landing })}
|
onFinished={() => this.setState({ phase: Phase.Landing })}
|
||||||
/>;
|
/>;
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,8 +22,8 @@ import {LayoutPropType} from "../../settings/Layout";
|
||||||
import React, {createRef} from 'react';
|
import React, {createRef} from 'react';
|
||||||
import ReactDOM from "react-dom";
|
import ReactDOM from "react-dom";
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import {EventTimeline} from "matrix-js-sdk";
|
import {EventTimeline} from "matrix-js-sdk/src/models/event-timeline";
|
||||||
import * as Matrix from "matrix-js-sdk";
|
import {TimelineWindow} from "matrix-js-sdk/src/timeline-window";
|
||||||
import { _t } from '../../languageHandler';
|
import { _t } from '../../languageHandler';
|
||||||
import {MatrixClientPeg} from "../../MatrixClientPeg";
|
import {MatrixClientPeg} from "../../MatrixClientPeg";
|
||||||
import UserActivity from "../../UserActivity";
|
import UserActivity from "../../UserActivity";
|
||||||
|
@ -463,6 +463,9 @@ class TimelinePanel extends React.Component {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
if (payload.action === "scroll_to_bottom") {
|
||||||
|
this.jumpToLiveTimeline();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
onRoomTimeline = (ev, room, toStartOfTimeline, removed, data) => {
|
onRoomTimeline = (ev, room, toStartOfTimeline, removed, data) => {
|
||||||
|
@ -1007,7 +1010,7 @@ class TimelinePanel extends React.Component {
|
||||||
* returns a promise which will resolve when the load completes.
|
* returns a promise which will resolve when the load completes.
|
||||||
*/
|
*/
|
||||||
_loadTimeline(eventId, pixelOffset, offsetBase) {
|
_loadTimeline(eventId, pixelOffset, offsetBase) {
|
||||||
this._timelineWindow = new Matrix.TimelineWindow(
|
this._timelineWindow = new TimelineWindow(
|
||||||
MatrixClientPeg.get(), this.props.timelineSet,
|
MatrixClientPeg.get(), this.props.timelineSet,
|
||||||
{windowLimit: this.props.timelineCap});
|
{windowLimit: this.props.timelineCap});
|
||||||
|
|
||||||
|
|
|
@ -17,13 +17,14 @@ limitations under the License.
|
||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import PropTypes from "prop-types";
|
import PropTypes from "prop-types";
|
||||||
import Matrix from "matrix-js-sdk";
|
|
||||||
import {MatrixClientPeg} from "../../MatrixClientPeg";
|
import {MatrixClientPeg} from "../../MatrixClientPeg";
|
||||||
import * as sdk from "../../index";
|
import * as sdk from "../../index";
|
||||||
import Modal from '../../Modal';
|
import Modal from '../../Modal';
|
||||||
import { _t } from '../../languageHandler';
|
import { _t } from '../../languageHandler';
|
||||||
import HomePage from "./HomePage";
|
import HomePage from "./HomePage";
|
||||||
import {replaceableComponent} from "../../utils/replaceableComponent";
|
import {replaceableComponent} from "../../utils/replaceableComponent";
|
||||||
|
import {MatrixEvent} from "matrix-js-sdk/src/models/event";
|
||||||
|
import {RoomMember} from "matrix-js-sdk/src/models/room-member";
|
||||||
|
|
||||||
@replaceableComponent("structures.UserView")
|
@replaceableComponent("structures.UserView")
|
||||||
export default class UserView extends React.Component {
|
export default class UserView extends React.Component {
|
||||||
|
@ -68,8 +69,8 @@ export default class UserView extends React.Component {
|
||||||
this.setState({loading: false});
|
this.setState({loading: false});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const fakeEvent = new Matrix.MatrixEvent({type: "m.room.member", content: profileInfo});
|
const fakeEvent = new MatrixEvent({type: "m.room.member", content: profileInfo});
|
||||||
const member = new Matrix.RoomMember(null, this.props.userId);
|
const member = new RoomMember(null, this.props.userId);
|
||||||
member.setMembershipEvent(fakeEvent);
|
member.setMembershipEvent(fakeEvent);
|
||||||
this.setState({member, loading: false});
|
this.setState({member, loading: false});
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,65 +16,176 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
import React from "react";
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from "prop-types";
|
||||||
import SyntaxHighlight from '../views/elements/SyntaxHighlight';
|
import SyntaxHighlight from "../views/elements/SyntaxHighlight";
|
||||||
import {_t} from "../../languageHandler";
|
import { _t } from "../../languageHandler";
|
||||||
import * as sdk from "../../index";
|
import * as sdk from "../../index";
|
||||||
import {replaceableComponent} from "../../utils/replaceableComponent";
|
import MatrixClientContext from "../../contexts/MatrixClientContext";
|
||||||
|
import { SendCustomEvent } from "../views/dialogs/DevtoolsDialog";
|
||||||
|
import { canEditContent } from "../../utils/EventUtils";
|
||||||
|
import { MatrixClientPeg } from '../../MatrixClientPeg';
|
||||||
|
import { replaceableComponent } from "../../utils/replaceableComponent";
|
||||||
|
|
||||||
@replaceableComponent("structures.ViewSource")
|
@replaceableComponent("structures.ViewSource")
|
||||||
export default class ViewSource extends React.Component {
|
export default class ViewSource extends React.Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
content: PropTypes.object.isRequired,
|
|
||||||
onFinished: PropTypes.func.isRequired,
|
onFinished: PropTypes.func.isRequired,
|
||||||
roomId: PropTypes.string.isRequired,
|
mxEvent: PropTypes.object.isRequired, // the MatrixEvent associated with the context menu
|
||||||
eventId: PropTypes.string.isRequired,
|
|
||||||
isEncrypted: PropTypes.bool.isRequired,
|
|
||||||
decryptedContent: PropTypes.object,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
constructor(props) {
|
||||||
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
|
super(props);
|
||||||
|
|
||||||
let content;
|
this.state = {
|
||||||
if (this.props.isEncrypted) {
|
isEditing: false,
|
||||||
content = <>
|
};
|
||||||
<details open className="mx_ViewSource_details">
|
}
|
||||||
<summary>
|
|
||||||
<span className="mx_ViewSource_heading">{_t("Decrypted event source")}</span>
|
onBack() {
|
||||||
</summary>
|
// TODO: refresh the "Event ID:" modal header
|
||||||
<SyntaxHighlight className="json">
|
this.setState({ isEditing: false });
|
||||||
{ JSON.stringify(this.props.decryptedContent, null, 2) }
|
}
|
||||||
</SyntaxHighlight>
|
|
||||||
</details>
|
onEdit() {
|
||||||
<details className="mx_ViewSource_details">
|
this.setState({ isEditing: true });
|
||||||
<summary>
|
}
|
||||||
<span className="mx_ViewSource_heading">{_t("Original event source")}</span>
|
|
||||||
</summary>
|
// returns the dialog body for viewing the event source
|
||||||
<SyntaxHighlight className="json">
|
viewSourceContent() {
|
||||||
{ JSON.stringify(this.props.content, null, 2) }
|
const mxEvent = this.props.mxEvent.replacingEvent() || this.props.mxEvent; // show the replacing event, not the original, if it is an edit
|
||||||
</SyntaxHighlight>
|
const isEncrypted = mxEvent.isEncrypted();
|
||||||
</details>
|
const decryptedEventSource = mxEvent._clearEvent; // FIXME: _clearEvent is private
|
||||||
</>;
|
const originalEventSource = mxEvent.event;
|
||||||
|
|
||||||
|
if (isEncrypted) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<details open className="mx_ViewSource_details">
|
||||||
|
<summary>
|
||||||
|
<span className="mx_ViewSource_heading">{_t("Decrypted event source")}</span>
|
||||||
|
</summary>
|
||||||
|
<SyntaxHighlight className="json">{JSON.stringify(decryptedEventSource, null, 2)}</SyntaxHighlight>
|
||||||
|
</details>
|
||||||
|
<details className="mx_ViewSource_details">
|
||||||
|
<summary>
|
||||||
|
<span className="mx_ViewSource_heading">{_t("Original event source")}</span>
|
||||||
|
</summary>
|
||||||
|
<SyntaxHighlight className="json">{JSON.stringify(originalEventSource, null, 2)}</SyntaxHighlight>
|
||||||
|
</details>
|
||||||
|
</>
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
content = <>
|
return (
|
||||||
<div className="mx_ViewSource_heading">{_t("Original event source")}</div>
|
<>
|
||||||
<SyntaxHighlight className="json">
|
<div className="mx_ViewSource_heading">{_t("Original event source")}</div>
|
||||||
{ JSON.stringify(this.props.content, null, 2) }
|
<SyntaxHighlight className="json">{JSON.stringify(originalEventSource, null, 2)}</SyntaxHighlight>
|
||||||
</SyntaxHighlight>
|
</>
|
||||||
</>;
|
);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// returns the id of the initial message, not the id of the previous edit
|
||||||
|
getBaseEventId() {
|
||||||
|
const mxEvent = this.props.mxEvent.replacingEvent() || this.props.mxEvent; // show the replacing event, not the original, if it is an edit
|
||||||
|
const isEncrypted = mxEvent.isEncrypted();
|
||||||
|
const baseMxEvent = this.props.mxEvent;
|
||||||
|
|
||||||
|
if (isEncrypted) {
|
||||||
|
// `relates_to` field is inside the encrypted event
|
||||||
|
return mxEvent.event.content["m.relates_to"]?.event_id ?? baseMxEvent.getId();
|
||||||
|
} else {
|
||||||
|
return mxEvent.getContent()["m.relates_to"]?.event_id ?? baseMxEvent.getId();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// returns the SendCustomEvent component prefilled with the correct details
|
||||||
|
editSourceContent() {
|
||||||
|
const mxEvent = this.props.mxEvent.replacingEvent() || this.props.mxEvent; // show the replacing event, not the original, if it is an edit
|
||||||
|
|
||||||
|
const isStateEvent = mxEvent.isState();
|
||||||
|
const roomId = mxEvent.getRoomId();
|
||||||
|
const originalContent = mxEvent.getContent();
|
||||||
|
|
||||||
|
if (isStateEvent) {
|
||||||
|
return (
|
||||||
|
<MatrixClientContext.Consumer>
|
||||||
|
{(cli) => (
|
||||||
|
<SendCustomEvent
|
||||||
|
room={cli.getRoom(roomId)}
|
||||||
|
forceStateEvent={true}
|
||||||
|
onBack={() => this.onBack()}
|
||||||
|
inputs={{
|
||||||
|
eventType: mxEvent.getType(),
|
||||||
|
evContent: JSON.stringify(originalContent, null, "\t"),
|
||||||
|
stateKey: mxEvent.getStateKey(),
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</MatrixClientContext.Consumer>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
// prefill an edit-message event
|
||||||
|
// keep only the `body` and `msgtype` fields of originalContent
|
||||||
|
const bodyToStartFrom = originalContent["m.new_content"]?.body ?? originalContent.body; // prefill the last edit body, to start editing from there
|
||||||
|
const newContent = {
|
||||||
|
"body": ` * ${bodyToStartFrom}`,
|
||||||
|
"msgtype": originalContent.msgtype,
|
||||||
|
"m.new_content": {
|
||||||
|
body: bodyToStartFrom,
|
||||||
|
msgtype: originalContent.msgtype,
|
||||||
|
},
|
||||||
|
"m.relates_to": {
|
||||||
|
rel_type: "m.replace",
|
||||||
|
event_id: this.getBaseEventId(),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<MatrixClientContext.Consumer>
|
||||||
|
{(cli) => (
|
||||||
|
<SendCustomEvent
|
||||||
|
room={cli.getRoom(roomId)}
|
||||||
|
forceStateEvent={false}
|
||||||
|
forceGeneralEvent={true}
|
||||||
|
onBack={() => this.onBack()}
|
||||||
|
inputs={{
|
||||||
|
eventType: mxEvent.getType(),
|
||||||
|
evContent: JSON.stringify(newContent, null, "\t"),
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</MatrixClientContext.Consumer>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
canSendStateEvent(mxEvent) {
|
||||||
|
const cli = MatrixClientPeg.get();
|
||||||
|
const room = cli.getRoom(mxEvent.getRoomId());
|
||||||
|
return room.currentState.mayClientSendStateEvent(mxEvent.getType(), cli);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const BaseDialog = sdk.getComponent("views.dialogs.BaseDialog");
|
||||||
|
const mxEvent = this.props.mxEvent.replacingEvent() || this.props.mxEvent; // show the replacing event, not the original, if it is an edit
|
||||||
|
|
||||||
|
const isEditing = this.state.isEditing;
|
||||||
|
const roomId = mxEvent.getRoomId();
|
||||||
|
const eventId = mxEvent.getId();
|
||||||
|
const canEdit = mxEvent.isState() ? this.canSendStateEvent(mxEvent) : canEditContent(this.props.mxEvent);
|
||||||
return (
|
return (
|
||||||
<BaseDialog className="mx_ViewSource" onFinished={this.props.onFinished} title={_t('View Source')}>
|
<BaseDialog className="mx_ViewSource" onFinished={this.props.onFinished} title={_t("View Source")}>
|
||||||
<div className="mx_Dialog_content">
|
<div>
|
||||||
<div className="mx_ViewSource_label_left">Room ID: { this.props.roomId }</div>
|
<div>Room ID: {roomId}</div>
|
||||||
<div className="mx_ViewSource_label_left">Event ID: { this.props.eventId }</div>
|
<div>Event ID: {eventId}</div>
|
||||||
<div className="mx_ViewSource_separator" />
|
<div className="mx_ViewSource_separator" />
|
||||||
{ content }
|
{isEditing ? this.editSourceContent() : this.viewSourceContent()}
|
||||||
</div>
|
</div>
|
||||||
|
{!isEditing && canEdit && (
|
||||||
|
<div className="mx_Dialog_buttons">
|
||||||
|
<button onClick={() => this.onEdit()}>{_t("Edit")}</button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</BaseDialog>
|
</BaseDialog>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import Matrix from 'matrix-js-sdk';
|
import {createClient} from 'matrix-js-sdk/src/matrix';
|
||||||
import React, {ReactNode} from 'react';
|
import React, {ReactNode} from 'react';
|
||||||
import {MatrixClient} from "matrix-js-sdk/src/client";
|
import {MatrixClient} from "matrix-js-sdk/src/client";
|
||||||
|
|
||||||
|
@ -181,7 +181,7 @@ export default class Registration extends React.Component<IProps, IState> {
|
||||||
}
|
}
|
||||||
|
|
||||||
const {hsUrl, isUrl} = serverConfig;
|
const {hsUrl, isUrl} = serverConfig;
|
||||||
const cli = Matrix.createClient({
|
const cli = createClient({
|
||||||
baseUrl: hsUrl,
|
baseUrl: hsUrl,
|
||||||
idBaseUrl: isUrl,
|
idBaseUrl: isUrl,
|
||||||
});
|
});
|
||||||
|
|
|
@ -25,6 +25,7 @@ import AccessibleButton from '../elements/AccessibleButton';
|
||||||
import MatrixClientContext from "../../../contexts/MatrixClientContext";
|
import MatrixClientContext from "../../../contexts/MatrixClientContext";
|
||||||
import {useEventEmitter} from "../../../hooks/useEventEmitter";
|
import {useEventEmitter} from "../../../hooks/useEventEmitter";
|
||||||
import {toPx} from "../../../utils/units";
|
import {toPx} from "../../../utils/units";
|
||||||
|
import {ResizeMethod} from "../../../Avatar";
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
name: string; // The name (first initial used as default)
|
name: string; // The name (first initial used as default)
|
||||||
|
@ -35,7 +36,7 @@ interface IProps {
|
||||||
width?: number;
|
width?: number;
|
||||||
height?: number;
|
height?: number;
|
||||||
// XXX: resizeMethod not actually used.
|
// XXX: resizeMethod not actually used.
|
||||||
resizeMethod?: string;
|
resizeMethod?: ResizeMethod;
|
||||||
defaultToInitialLetter?: boolean; // true to add default url
|
defaultToInitialLetter?: boolean; // true to add default url
|
||||||
onClick?: React.MouseEventHandler;
|
onClick?: React.MouseEventHandler;
|
||||||
inputRef?: React.RefObject<HTMLImageElement & HTMLSpanElement>;
|
inputRef?: React.RefObject<HTMLImageElement & HTMLSpanElement>;
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
Copyright 2017 Vector Creations Ltd
|
Copyright 2017, 2021 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,9 +15,10 @@ limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import {MatrixClientPeg} from '../../../MatrixClientPeg';
|
|
||||||
import BaseAvatar from './BaseAvatar';
|
import BaseAvatar from './BaseAvatar';
|
||||||
import {replaceableComponent} from "../../../utils/replaceableComponent";
|
import {replaceableComponent} from "../../../utils/replaceableComponent";
|
||||||
|
import {mediaFromMxc} from "../../../customisations/Media";
|
||||||
|
import {ResizeMethod} from "../../../Avatar";
|
||||||
|
|
||||||
export interface IProps {
|
export interface IProps {
|
||||||
groupId?: string;
|
groupId?: string;
|
||||||
|
@ -25,7 +26,7 @@ export interface IProps {
|
||||||
groupAvatarUrl?: string;
|
groupAvatarUrl?: string;
|
||||||
width?: number;
|
width?: number;
|
||||||
height?: number;
|
height?: number;
|
||||||
resizeMethod?: string;
|
resizeMethod?: ResizeMethod;
|
||||||
onClick?: React.MouseEventHandler;
|
onClick?: React.MouseEventHandler;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -38,8 +39,8 @@ export default class GroupAvatar extends React.Component<IProps> {
|
||||||
};
|
};
|
||||||
|
|
||||||
getGroupAvatarUrl() {
|
getGroupAvatarUrl() {
|
||||||
return MatrixClientPeg.get().mxcUrlToHttp(
|
if (!this.props.groupAvatarUrl) return null;
|
||||||
this.props.groupAvatarUrl,
|
return mediaFromMxc(this.props.groupAvatarUrl).getThumbnailOfSourceHttp(
|
||||||
this.props.width,
|
this.props.width,
|
||||||
this.props.height,
|
this.props.height,
|
||||||
this.props.resizeMethod,
|
this.props.resizeMethod,
|
||||||
|
|
|
@ -20,16 +20,17 @@ 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 BaseAvatar from "./BaseAvatar";
|
import BaseAvatar from "./BaseAvatar";
|
||||||
import {replaceableComponent} from "../../../utils/replaceableComponent";
|
import {replaceableComponent} from "../../../utils/replaceableComponent";
|
||||||
|
import {mediaFromMxc} from "../../../customisations/Media";
|
||||||
|
import {ResizeMethod} from "../../../Avatar";
|
||||||
|
|
||||||
interface IProps extends Omit<React.ComponentProps<typeof BaseAvatar>, "name" | "idName" | "url"> {
|
interface IProps extends Omit<React.ComponentProps<typeof BaseAvatar>, "name" | "idName" | "url"> {
|
||||||
member: RoomMember;
|
member: RoomMember;
|
||||||
fallbackUserId?: string;
|
fallbackUserId?: string;
|
||||||
width: number;
|
width: number;
|
||||||
height: number;
|
height: number;
|
||||||
resizeMethod?: string;
|
resizeMethod?: ResizeMethod;
|
||||||
// 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`
|
||||||
|
@ -63,18 +64,19 @@ export default class MemberAvatar extends React.Component<IProps, IState> {
|
||||||
}
|
}
|
||||||
|
|
||||||
private static getState(props: IProps): IState {
|
private static getState(props: IProps): IState {
|
||||||
if (props.member && props.member.name) {
|
if (props.member?.name) {
|
||||||
return {
|
let imageUrl = null;
|
||||||
name: props.member.name,
|
if (props.member.getMxcAvatarUrl()) {
|
||||||
title: props.title || props.member.userId,
|
imageUrl = mediaFromMxc(props.member.getMxcAvatarUrl()).getThumbnailOfSourceHttp(
|
||||||
imageUrl: props.member.getAvatarUrl(
|
|
||||||
MatrixClientPeg.get().getHomeserverUrl(),
|
|
||||||
Math.floor(props.width * window.devicePixelRatio),
|
Math.floor(props.width * window.devicePixelRatio),
|
||||||
Math.floor(props.height * window.devicePixelRatio),
|
Math.floor(props.height * window.devicePixelRatio),
|
||||||
props.resizeMethod,
|
props.resizeMethod,
|
||||||
false,
|
);
|
||||||
false,
|
}
|
||||||
),
|
return {
|
||||||
|
name: props.member.name,
|
||||||
|
title: props.title || props.member.userId,
|
||||||
|
imageUrl: imageUrl,
|
||||||
};
|
};
|
||||||
} else if (props.fallbackUserId) {
|
} else if (props.fallbackUserId) {
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -15,7 +15,6 @@ limitations under the License.
|
||||||
*/
|
*/
|
||||||
import React, {ComponentProps} from 'react';
|
import React, {ComponentProps} from 'react';
|
||||||
import Room from 'matrix-js-sdk/src/models/room';
|
import Room from 'matrix-js-sdk/src/models/room';
|
||||||
import {getHttpUriForMxc} from 'matrix-js-sdk/src/content-repo';
|
|
||||||
|
|
||||||
import BaseAvatar from './BaseAvatar';
|
import BaseAvatar from './BaseAvatar';
|
||||||
import ImageView from '../elements/ImageView';
|
import ImageView from '../elements/ImageView';
|
||||||
|
@ -24,6 +23,7 @@ import Modal from '../../../Modal';
|
||||||
import * as Avatar from '../../../Avatar';
|
import * as Avatar from '../../../Avatar';
|
||||||
import {ResizeMethod} from "../../../Avatar";
|
import {ResizeMethod} from "../../../Avatar";
|
||||||
import {replaceableComponent} from "../../../utils/replaceableComponent";
|
import {replaceableComponent} from "../../../utils/replaceableComponent";
|
||||||
|
import {mediaFromMxc} from "../../../customisations/Media";
|
||||||
|
|
||||||
interface IProps extends Omit<ComponentProps<typeof BaseAvatar>, "name" | "idName" | "url" | "onClick"> {
|
interface IProps extends Omit<ComponentProps<typeof BaseAvatar>, "name" | "idName" | "url" | "onClick"> {
|
||||||
// Room may be left unset here, but if it is,
|
// Room may be left unset here, but if it is,
|
||||||
|
@ -90,16 +90,16 @@ export default class RoomAvatar extends React.Component<IProps, IState> {
|
||||||
};
|
};
|
||||||
|
|
||||||
private static getImageUrls(props: IProps): string[] {
|
private static getImageUrls(props: IProps): string[] {
|
||||||
return [
|
let oobAvatar = null;
|
||||||
getHttpUriForMxc(
|
if (props.oobData.avatarUrl) {
|
||||||
MatrixClientPeg.get().getHomeserverUrl(),
|
oobAvatar = mediaFromMxc(props.oobData.avatarUrl).getThumbnailOfSourceHttp(
|
||||||
// Default props don't play nicely with getDerivedStateFromProps
|
|
||||||
//props.oobData !== undefined ? props.oobData.avatarUrl : {},
|
|
||||||
props.oobData.avatarUrl,
|
|
||||||
Math.floor(props.width * window.devicePixelRatio),
|
Math.floor(props.width * window.devicePixelRatio),
|
||||||
Math.floor(props.height * window.devicePixelRatio),
|
Math.floor(props.height * window.devicePixelRatio),
|
||||||
props.resizeMethod,
|
props.resizeMethod,
|
||||||
), // highest priority
|
);
|
||||||
|
}
|
||||||
|
return [
|
||||||
|
oobAvatar, // highest priority
|
||||||
RoomAvatar.getRoomAvatarUrl(props),
|
RoomAvatar.getRoomAvatarUrl(props),
|
||||||
].filter(function(url) {
|
].filter(function(url) {
|
||||||
return (url !== null && url !== "");
|
return (url !== null && url !== "");
|
||||||
|
|
|
@ -14,21 +14,18 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, {ComponentProps, useContext} from 'react';
|
import React, {ComponentProps} from 'react';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import {getHttpUriForMxc} from "matrix-js-sdk/src/content-repo";
|
|
||||||
|
|
||||||
import MatrixClientContext from "../../../contexts/MatrixClientContext";
|
|
||||||
import {IApp} from "../../../stores/WidgetStore";
|
import {IApp} from "../../../stores/WidgetStore";
|
||||||
import BaseAvatar, {BaseAvatarType} from "./BaseAvatar";
|
import BaseAvatar, {BaseAvatarType} from "./BaseAvatar";
|
||||||
|
import {mediaFromMxc} from "../../../customisations/Media";
|
||||||
|
|
||||||
interface IProps extends Omit<ComponentProps<BaseAvatarType>, "name" | "url" | "urls"> {
|
interface IProps extends Omit<ComponentProps<BaseAvatarType>, "name" | "url" | "urls"> {
|
||||||
app: IApp;
|
app: IApp;
|
||||||
}
|
}
|
||||||
|
|
||||||
const WidgetAvatar: React.FC<IProps> = ({ app, className, width = 20, height = 20, ...props }) => {
|
const WidgetAvatar: React.FC<IProps> = ({ app, className, width = 20, height = 20, ...props }) => {
|
||||||
const cli = useContext(MatrixClientContext);
|
|
||||||
|
|
||||||
let iconUrls = [require("../../../../res/img/element-icons/room/default_app.svg")];
|
let iconUrls = [require("../../../../res/img/element-icons/room/default_app.svg")];
|
||||||
// heuristics for some better icons until Widgets support their own icons
|
// heuristics for some better icons until Widgets support their own icons
|
||||||
if (app.type.includes("jitsi")) {
|
if (app.type.includes("jitsi")) {
|
||||||
|
@ -47,7 +44,7 @@ const WidgetAvatar: React.FC<IProps> = ({ app, className, width = 20, height = 2
|
||||||
name={app.id}
|
name={app.id}
|
||||||
className={classNames("mx_WidgetAvatar", className)}
|
className={classNames("mx_WidgetAvatar", className)}
|
||||||
// MSC2765
|
// MSC2765
|
||||||
url={app.avatar_url ? getHttpUriForMxc(cli.getHomeserverUrl(), app.avatar_url, 20, 20, "crop") : undefined}
|
url={app.avatar_url ? mediaFromMxc(app.avatar_url).getSquareThumbnailHttp(20) : undefined}
|
||||||
urls={iconUrls}
|
urls={iconUrls}
|
||||||
width={width}
|
width={width}
|
||||||
height={height}
|
height={height}
|
||||||
|
|
|
@ -20,7 +20,7 @@ import PropTypes from 'prop-types';
|
||||||
import * as sdk from '../../../index';
|
import * as sdk from '../../../index';
|
||||||
import { _t } from '../../../languageHandler';
|
import { _t } from '../../../languageHandler';
|
||||||
import Modal from '../../../Modal';
|
import Modal from '../../../Modal';
|
||||||
import {Group} from 'matrix-js-sdk';
|
import {Group} from 'matrix-js-sdk/src/models/group';
|
||||||
import GroupStore from "../../../stores/GroupStore";
|
import GroupStore from "../../../stores/GroupStore";
|
||||||
import {MenuItem} from "../../structures/ContextMenu";
|
import {MenuItem} from "../../structures/ContextMenu";
|
||||||
import {replaceableComponent} from "../../../utils/replaceableComponent";
|
import {replaceableComponent} from "../../../utils/replaceableComponent";
|
||||||
|
|
|
@ -19,7 +19,7 @@ limitations under the License.
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import {EventStatus} from 'matrix-js-sdk';
|
import {EventStatus} from 'matrix-js-sdk/src/models/event';
|
||||||
|
|
||||||
import {MatrixClientPeg} from '../../../MatrixClientPeg';
|
import {MatrixClientPeg} from '../../../MatrixClientPeg';
|
||||||
import dis from '../../../dispatcher/dispatcher';
|
import dis from '../../../dispatcher/dispatcher';
|
||||||
|
@ -126,15 +126,9 @@ export default class MessageContextMenu extends React.Component {
|
||||||
};
|
};
|
||||||
|
|
||||||
onViewSourceClick = () => {
|
onViewSourceClick = () => {
|
||||||
const ev = this.props.mxEvent.replacingEvent() || this.props.mxEvent;
|
|
||||||
const ViewSource = sdk.getComponent('structures.ViewSource');
|
const ViewSource = sdk.getComponent('structures.ViewSource');
|
||||||
Modal.createTrackedDialog('View Event Source', '', ViewSource, {
|
Modal.createTrackedDialog('View Event Source', '', ViewSource, {
|
||||||
roomId: ev.getRoomId(),
|
mxEvent: this.props.mxEvent,
|
||||||
eventId: ev.getId(),
|
|
||||||
content: ev.event,
|
|
||||||
isEncrypted: ev.isEncrypted(),
|
|
||||||
// FIXME: _clearEvent is private
|
|
||||||
decryptedContent: ev._clearEvent,
|
|
||||||
}, 'mx_Dialog_viewsource');
|
}, 'mx_Dialog_viewsource');
|
||||||
this.closeMenu();
|
this.closeMenu();
|
||||||
};
|
};
|
||||||
|
|
|
@ -22,7 +22,6 @@ import {MatrixClient} from "matrix-js-sdk/src/client";
|
||||||
import {_t} from '../../../languageHandler';
|
import {_t} from '../../../languageHandler';
|
||||||
import {IDialogProps} from "./IDialogProps";
|
import {IDialogProps} from "./IDialogProps";
|
||||||
import BaseDialog from "./BaseDialog";
|
import BaseDialog from "./BaseDialog";
|
||||||
import FormButton from "../elements/FormButton";
|
|
||||||
import Dropdown from "../elements/Dropdown";
|
import Dropdown from "../elements/Dropdown";
|
||||||
import SearchBox from "../../structures/SearchBox";
|
import SearchBox from "../../structures/SearchBox";
|
||||||
import SpaceStore from "../../../stores/SpaceStore";
|
import SpaceStore from "../../../stores/SpaceStore";
|
||||||
|
@ -69,6 +68,7 @@ const AddExistingToSpaceDialog: React.FC<IProps> = ({ matrixClient: cli, space,
|
||||||
const existingRoomsSet = new Set(existingRooms);
|
const existingRoomsSet = new Set(existingRooms);
|
||||||
const rooms = cli.getVisibleRooms().filter(room => {
|
const rooms = cli.getVisibleRooms().filter(room => {
|
||||||
return !existingRoomsSet.has(room) // not already in space
|
return !existingRoomsSet.has(room) // not already in space
|
||||||
|
&& !room.isSpaceRoom() // not a space itself
|
||||||
&& room.name.toLowerCase().includes(lcQuery) // contains query
|
&& room.name.toLowerCase().includes(lcQuery) // contains query
|
||||||
&& !DMRoomMap.shared().getUserIdForRoomId(room.roomId); // not a DM
|
&& !DMRoomMap.shared().getUserIdForRoomId(room.roomId); // not a DM
|
||||||
});
|
});
|
||||||
|
@ -109,7 +109,7 @@ const AddExistingToSpaceDialog: React.FC<IProps> = ({ matrixClient: cli, space,
|
||||||
const title = <React.Fragment>
|
const title = <React.Fragment>
|
||||||
<RoomAvatar room={selectedSpace} height={40} width={40} />
|
<RoomAvatar room={selectedSpace} height={40} width={40} />
|
||||||
<div>
|
<div>
|
||||||
<h1>{ _t("Add existing spaces/rooms") }</h1>
|
<h1>{ _t("Add existing rooms") }</h1>
|
||||||
{ spaceOptionSection }
|
{ spaceOptionSection }
|
||||||
</div>
|
</div>
|
||||||
</React.Fragment>;
|
</React.Fragment>;
|
||||||
|
@ -127,29 +127,9 @@ const AddExistingToSpaceDialog: React.FC<IProps> = ({ matrixClient: cli, space,
|
||||||
className="mx_textinput_icon mx_textinput_search"
|
className="mx_textinput_icon mx_textinput_search"
|
||||||
placeholder={ _t("Filter your rooms and spaces") }
|
placeholder={ _t("Filter your rooms and spaces") }
|
||||||
onSearch={setQuery}
|
onSearch={setQuery}
|
||||||
|
autoComplete={true}
|
||||||
/>
|
/>
|
||||||
<AutoHideScrollbar className="mx_AddExistingToSpaceDialog_content" id="mx_AddExistingToSpaceDialog">
|
<AutoHideScrollbar className="mx_AddExistingToSpaceDialog_content" id="mx_AddExistingToSpaceDialog">
|
||||||
{ spaces.length > 0 ? (
|
|
||||||
<div className="mx_AddExistingToSpaceDialog_section mx_AddExistingToSpaceDialog_section_spaces">
|
|
||||||
<h3>{ _t("Spaces") }</h3>
|
|
||||||
{ spaces.map(space => {
|
|
||||||
return <Entry
|
|
||||||
key={space.roomId}
|
|
||||||
room={space}
|
|
||||||
checked={selectedToAdd.has(space)}
|
|
||||||
onChange={(checked) => {
|
|
||||||
if (checked) {
|
|
||||||
selectedToAdd.add(space);
|
|
||||||
} else {
|
|
||||||
selectedToAdd.delete(space);
|
|
||||||
}
|
|
||||||
setSelectedToAdd(new Set(selectedToAdd));
|
|
||||||
}}
|
|
||||||
/>;
|
|
||||||
}) }
|
|
||||||
</div>
|
|
||||||
) : null }
|
|
||||||
|
|
||||||
{ rooms.length > 0 ? (
|
{ rooms.length > 0 ? (
|
||||||
<div className="mx_AddExistingToSpaceDialog_section">
|
<div className="mx_AddExistingToSpaceDialog_section">
|
||||||
<h3>{ _t("Rooms") }</h3>
|
<h3>{ _t("Rooms") }</h3>
|
||||||
|
@ -171,6 +151,27 @@ const AddExistingToSpaceDialog: React.FC<IProps> = ({ matrixClient: cli, space,
|
||||||
</div>
|
</div>
|
||||||
) : undefined }
|
) : undefined }
|
||||||
|
|
||||||
|
{ spaces.length > 0 ? (
|
||||||
|
<div className="mx_AddExistingToSpaceDialog_section mx_AddExistingToSpaceDialog_section_spaces">
|
||||||
|
<h3>{ _t("Spaces") }</h3>
|
||||||
|
{ spaces.map(space => {
|
||||||
|
return <Entry
|
||||||
|
key={space.roomId}
|
||||||
|
room={space}
|
||||||
|
checked={selectedToAdd.has(space)}
|
||||||
|
onChange={(checked) => {
|
||||||
|
if (checked) {
|
||||||
|
selectedToAdd.add(space);
|
||||||
|
} else {
|
||||||
|
selectedToAdd.delete(space);
|
||||||
|
}
|
||||||
|
setSelectedToAdd(new Set(selectedToAdd));
|
||||||
|
}}
|
||||||
|
/>;
|
||||||
|
}) }
|
||||||
|
</div>
|
||||||
|
) : null }
|
||||||
|
|
||||||
{ spaces.length + rooms.length < 1 ? <span className="mx_AddExistingToSpaceDialog_noResults">
|
{ spaces.length + rooms.length < 1 ? <span className="mx_AddExistingToSpaceDialog_noResults">
|
||||||
{ _t("No results") }
|
{ _t("No results") }
|
||||||
</span> : undefined }
|
</span> : undefined }
|
||||||
|
@ -184,8 +185,8 @@ const AddExistingToSpaceDialog: React.FC<IProps> = ({ matrixClient: cli, space,
|
||||||
</AccessibleButton>
|
</AccessibleButton>
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<FormButton
|
<AccessibleButton
|
||||||
label={busy ? _t("Applying...") : _t("Apply")}
|
kind="primary"
|
||||||
disabled={busy || selectedToAdd.size < 1}
|
disabled={busy || selectedToAdd.size < 1}
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
setBusy(true);
|
setBusy(true);
|
||||||
|
@ -199,7 +200,9 @@ const AddExistingToSpaceDialog: React.FC<IProps> = ({ matrixClient: cli, space,
|
||||||
}
|
}
|
||||||
setBusy(false);
|
setBusy(false);
|
||||||
}}
|
}}
|
||||||
/>
|
>
|
||||||
|
{ busy ? _t("Adding...") : _t("Add") }
|
||||||
|
</AccessibleButton>
|
||||||
</div>
|
</div>
|
||||||
</BaseDialog>;
|
</BaseDialog>;
|
||||||
};
|
};
|
||||||
|
|
|
@ -26,12 +26,12 @@ import SdkConfig from "../../../SdkConfig";
|
||||||
import { RoomMember } from "matrix-js-sdk/src/models/room-member";
|
import { RoomMember } from "matrix-js-sdk/src/models/room-member";
|
||||||
import InviteDialog from "./InviteDialog";
|
import InviteDialog from "./InviteDialog";
|
||||||
import BaseAvatar from "../avatars/BaseAvatar";
|
import BaseAvatar from "../avatars/BaseAvatar";
|
||||||
import {getHttpUriForMxc} from "matrix-js-sdk/src/content-repo";
|
|
||||||
import {inviteMultipleToRoom, showAnyInviteErrors} from "../../../RoomInvite";
|
import {inviteMultipleToRoom, showAnyInviteErrors} from "../../../RoomInvite";
|
||||||
import StyledCheckbox from "../elements/StyledCheckbox";
|
import StyledCheckbox from "../elements/StyledCheckbox";
|
||||||
import Modal from "../../../Modal";
|
import Modal from "../../../Modal";
|
||||||
import ErrorDialog from "./ErrorDialog";
|
import ErrorDialog from "./ErrorDialog";
|
||||||
import {replaceableComponent} from "../../../utils/replaceableComponent";
|
import {replaceableComponent} from "../../../utils/replaceableComponent";
|
||||||
|
import {mediaFromMxc} from "../../../customisations/Media";
|
||||||
|
|
||||||
interface IProps extends IDialogProps {
|
interface IProps extends IDialogProps {
|
||||||
roomId: string;
|
roomId: string;
|
||||||
|
@ -142,12 +142,14 @@ export default class CommunityPrototypeInviteDialog extends React.PureComponent<
|
||||||
|
|
||||||
private renderPerson(person: IPerson, key: any) {
|
private renderPerson(person: IPerson, key: any) {
|
||||||
const avatarSize = 36;
|
const avatarSize = 36;
|
||||||
|
let avatarUrl = null;
|
||||||
|
if (person.user.getMxcAvatarUrl()) {
|
||||||
|
avatarUrl = mediaFromMxc(person.user.getMxcAvatarUrl()).getSquareThumbnailHttp(avatarSize);
|
||||||
|
}
|
||||||
return (
|
return (
|
||||||
<div className="mx_CommunityPrototypeInviteDialog_person" key={key}>
|
<div className="mx_CommunityPrototypeInviteDialog_person" key={key}>
|
||||||
<BaseAvatar
|
<BaseAvatar
|
||||||
url={getHttpUriForMxc(
|
url={avatarUrl}
|
||||||
MatrixClientPeg.get().getHomeserverUrl(), person.user.getMxcAvatarUrl(),
|
|
||||||
avatarSize, avatarSize, "crop")}
|
|
||||||
name={person.user.name}
|
name={person.user.name}
|
||||||
idName={person.user.userId}
|
idName={person.user.userId}
|
||||||
width={avatarSize}
|
width={avatarSize}
|
||||||
|
|
|
@ -16,11 +16,12 @@ limitations under the License.
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { MatrixClient } from 'matrix-js-sdk';
|
import { MatrixClient } from 'matrix-js-sdk/src/client';
|
||||||
import * as sdk from '../../../index';
|
import * as sdk from '../../../index';
|
||||||
import { _t } from '../../../languageHandler';
|
import { _t } from '../../../languageHandler';
|
||||||
import { GroupMemberType } from '../../../groups';
|
import { GroupMemberType } from '../../../groups';
|
||||||
import {replaceableComponent} from "../../../utils/replaceableComponent";
|
import {replaceableComponent} from "../../../utils/replaceableComponent";
|
||||||
|
import {mediaFromMxc} from "../../../customisations/Media";
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* A dialog for confirming an operation on another user.
|
* A dialog for confirming an operation on another user.
|
||||||
|
@ -108,8 +109,9 @@ export default class ConfirmUserActionDialog extends React.Component {
|
||||||
name = this.props.member.name;
|
name = this.props.member.name;
|
||||||
userId = this.props.member.userId;
|
userId = this.props.member.userId;
|
||||||
} else {
|
} else {
|
||||||
const httpAvatarUrl = this.props.groupMember.avatarUrl ?
|
const httpAvatarUrl = this.props.groupMember.avatarUrl
|
||||||
this.props.matrixClient.mxcUrlToHttp(this.props.groupMember.avatarUrl, 48, 48) : null;
|
? mediaFromMxc(this.props.groupMember.avatarUrl).getSquareThumbnailHttp(48)
|
||||||
|
: null;
|
||||||
name = this.props.groupMember.displayname || this.props.groupMember.userId;
|
name = this.props.groupMember.displayname || this.props.groupMember.userId;
|
||||||
userId = this.props.groupMember.userId;
|
userId = this.props.groupMember.userId;
|
||||||
avatar = <BaseAvatar name={name} url={httpAvatarUrl} width={48} height={48} />;
|
avatar = <BaseAvatar name={name} url={httpAvatarUrl} width={48} height={48} />;
|
||||||
|
|
|
@ -19,7 +19,6 @@ import PropTypes from 'prop-types';
|
||||||
import * as sdk from '../../../index';
|
import * as sdk from '../../../index';
|
||||||
import SyntaxHighlight from '../elements/SyntaxHighlight';
|
import SyntaxHighlight from '../elements/SyntaxHighlight';
|
||||||
import { _t } from '../../../languageHandler';
|
import { _t } from '../../../languageHandler';
|
||||||
import { Room, MatrixEvent } from "matrix-js-sdk";
|
|
||||||
import Field from "../elements/Field";
|
import Field from "../elements/Field";
|
||||||
import MatrixClientContext from "../../../contexts/MatrixClientContext";
|
import MatrixClientContext from "../../../contexts/MatrixClientContext";
|
||||||
import {useEventEmitter} from "../../../hooks/useEventEmitter";
|
import {useEventEmitter} from "../../../hooks/useEventEmitter";
|
||||||
|
@ -39,6 +38,8 @@ import SettingsStore, {LEVEL_ORDER} from "../../../settings/SettingsStore";
|
||||||
import Modal from "../../../Modal";
|
import Modal from "../../../Modal";
|
||||||
import ErrorDialog from "./ErrorDialog";
|
import ErrorDialog from "./ErrorDialog";
|
||||||
import {replaceableComponent} from "../../../utils/replaceableComponent";
|
import {replaceableComponent} from "../../../utils/replaceableComponent";
|
||||||
|
import {Room} from "matrix-js-sdk/src/models/room";
|
||||||
|
import {MatrixEvent} from "matrix-js-sdk/src/models/event";
|
||||||
|
|
||||||
class GenericEditor extends React.PureComponent {
|
class GenericEditor extends React.PureComponent {
|
||||||
// static propTypes = {onBack: PropTypes.func.isRequired};
|
// static propTypes = {onBack: PropTypes.func.isRequired};
|
||||||
|
@ -74,13 +75,14 @@ class GenericEditor extends React.PureComponent {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class SendCustomEvent extends GenericEditor {
|
export class SendCustomEvent extends GenericEditor {
|
||||||
static getLabel() { return _t('Send Custom Event'); }
|
static getLabel() { return _t('Send Custom Event'); }
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
onBack: PropTypes.func.isRequired,
|
onBack: PropTypes.func.isRequired,
|
||||||
room: PropTypes.instanceOf(Room).isRequired,
|
room: PropTypes.instanceOf(Room).isRequired,
|
||||||
forceStateEvent: PropTypes.bool,
|
forceStateEvent: PropTypes.bool,
|
||||||
|
forceGeneralEvent: PropTypes.bool,
|
||||||
inputs: PropTypes.object,
|
inputs: PropTypes.object,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -141,6 +143,8 @@ class SendCustomEvent extends GenericEditor {
|
||||||
</div>;
|
</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const showTglFlip = !this.state.message && !this.props.forceStateEvent && !this.props.forceGeneralEvent;
|
||||||
|
|
||||||
return <div>
|
return <div>
|
||||||
<div className="mx_DevTools_content">
|
<div className="mx_DevTools_content">
|
||||||
<div className="mx_DevTools_eventTypeStateKeyGroup">
|
<div className="mx_DevTools_eventTypeStateKeyGroup">
|
||||||
|
@ -156,7 +160,7 @@ class SendCustomEvent extends GenericEditor {
|
||||||
<div className="mx_Dialog_buttons">
|
<div className="mx_Dialog_buttons">
|
||||||
<button onClick={this.onBack}>{ _t('Back') }</button>
|
<button onClick={this.onBack}>{ _t('Back') }</button>
|
||||||
{ !this.state.message && <button onClick={this._send}>{ _t('Send') }</button> }
|
{ !this.state.message && <button onClick={this._send}>{ _t('Send') }</button> }
|
||||||
{ !this.state.message && !this.props.forceStateEvent && <div style={{float: "right"}}>
|
{ showTglFlip && <div style={{float: "right"}}>
|
||||||
<input id="isStateEvent" className="mx_DevTools_tgl mx_DevTools_tgl-flip" type="checkbox" onChange={this._onChange} checked={this.state.isStateEvent} />
|
<input id="isStateEvent" className="mx_DevTools_tgl mx_DevTools_tgl-flip" type="checkbox" onChange={this._onChange} checked={this.state.isStateEvent} />
|
||||||
<label className="mx_DevTools_tgl-btn" data-tg-off="Event" data-tg-on="State Event" htmlFor="isStateEvent" />
|
<label className="mx_DevTools_tgl-btn" data-tg-off="Event" data-tg-on="State Event" htmlFor="isStateEvent" />
|
||||||
</div> }
|
</div> }
|
||||||
|
|
|
@ -24,6 +24,7 @@ import { MatrixClientPeg } from "../../../MatrixClientPeg";
|
||||||
import { CommunityPrototypeStore } from "../../../stores/CommunityPrototypeStore";
|
import { CommunityPrototypeStore } from "../../../stores/CommunityPrototypeStore";
|
||||||
import FlairStore from "../../../stores/FlairStore";
|
import FlairStore from "../../../stores/FlairStore";
|
||||||
import {replaceableComponent} from "../../../utils/replaceableComponent";
|
import {replaceableComponent} from "../../../utils/replaceableComponent";
|
||||||
|
import {mediaFromMxc} from "../../../customisations/Media";
|
||||||
|
|
||||||
interface IProps extends IDialogProps {
|
interface IProps extends IDialogProps {
|
||||||
communityId: string;
|
communityId: string;
|
||||||
|
@ -118,7 +119,7 @@ export default class EditCommunityPrototypeDialog extends React.PureComponent<IP
|
||||||
let preview = <img src={this.state.avatarPreview} className="mx_EditCommunityPrototypeDialog_avatar" />;
|
let preview = <img src={this.state.avatarPreview} className="mx_EditCommunityPrototypeDialog_avatar" />;
|
||||||
if (!this.state.avatarPreview) {
|
if (!this.state.avatarPreview) {
|
||||||
if (this.state.currentAvatarUrl) {
|
if (this.state.currentAvatarUrl) {
|
||||||
const url = MatrixClientPeg.get().mxcUrlToHttp(this.state.currentAvatarUrl);
|
const url = mediaFromMxc(this.state.currentAvatarUrl).srcHttp;
|
||||||
preview = <img src={url} className="mx_EditCommunityPrototypeDialog_avatar" />;
|
preview = <img src={url} className="mx_EditCommunityPrototypeDialog_avatar" />;
|
||||||
} else {
|
} else {
|
||||||
preview = <div className="mx_EditCommunityPrototypeDialog_placeholderAvatar" />
|
preview = <div className="mx_EditCommunityPrototypeDialog_placeholderAvatar" />
|
||||||
|
|
|
@ -100,6 +100,20 @@ export default (props) => {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let bugReports = null;
|
||||||
|
if (SdkConfig.get().bug_report_endpoint_url) {
|
||||||
|
bugReports = (
|
||||||
|
<p>{
|
||||||
|
_t("PRO TIP: If you start a bug, please submit <debugLogsLink>debug logs</debugLogsLink> " +
|
||||||
|
"to help us track down the problem.", {}, {
|
||||||
|
debugLogsLink: sub => (
|
||||||
|
<AccessibleButton kind="link" onClick={onDebugLogsLinkClick}>{sub}</AccessibleButton>
|
||||||
|
),
|
||||||
|
})
|
||||||
|
}</p>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (<QuestionDialog
|
return (<QuestionDialog
|
||||||
className="mx_FeedbackDialog"
|
className="mx_FeedbackDialog"
|
||||||
hasCancelButton={!!hasFeedback}
|
hasCancelButton={!!hasFeedback}
|
||||||
|
@ -120,14 +134,7 @@ export default (props) => {
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}</p>
|
}</p>
|
||||||
<p>{
|
{bugReports}
|
||||||
_t("PRO TIP: If you start a bug, please submit <debugLogsLink>debug logs</debugLogsLink> " +
|
|
||||||
"to help us track down the problem.", {}, {
|
|
||||||
debugLogsLink: sub => (
|
|
||||||
<AccessibleButton kind="link" onClick={onDebugLogsLinkClick}>{sub}</AccessibleButton>
|
|
||||||
),
|
|
||||||
})
|
|
||||||
}</p>
|
|
||||||
</div>
|
</div>
|
||||||
{ countlyFeedbackSection }
|
{ countlyFeedbackSection }
|
||||||
</React.Fragment>}
|
</React.Fragment>}
|
||||||
|
|
|
@ -20,6 +20,7 @@ import {MatrixClientPeg} from '../../../MatrixClientPeg';
|
||||||
import * as sdk from '../../../index';
|
import * as sdk from '../../../index';
|
||||||
import { _t } from '../../../languageHandler';
|
import { _t } from '../../../languageHandler';
|
||||||
import {replaceableComponent} from "../../../utils/replaceableComponent";
|
import {replaceableComponent} from "../../../utils/replaceableComponent";
|
||||||
|
import {mediaFromMxc} from "../../../customisations/Media";
|
||||||
|
|
||||||
const PHASE_START = 0;
|
const PHASE_START = 0;
|
||||||
const PHASE_SHOW_SAS = 1;
|
const PHASE_SHOW_SAS = 1;
|
||||||
|
@ -123,22 +124,21 @@ export default class IncomingSasDialog extends React.Component {
|
||||||
const Spinner = sdk.getComponent("views.elements.Spinner");
|
const Spinner = sdk.getComponent("views.elements.Spinner");
|
||||||
const BaseAvatar = sdk.getComponent("avatars.BaseAvatar");
|
const BaseAvatar = sdk.getComponent("avatars.BaseAvatar");
|
||||||
|
|
||||||
const isSelf = this.props.verifier.userId == MatrixClientPeg.get().getUserId();
|
const isSelf = this.props.verifier.userId === MatrixClientPeg.get().getUserId();
|
||||||
|
|
||||||
let profile;
|
let profile;
|
||||||
if (this.state.opponentProfile) {
|
const oppProfile = this.state.opponentProfile;
|
||||||
|
if (oppProfile) {
|
||||||
|
const url = oppProfile.avatar_url
|
||||||
|
? mediaFromMxc(oppProfile.avatar_url).getSquareThumbnailHttp(Math.floor(48 * window.devicePixelRatio))
|
||||||
|
: null;
|
||||||
profile = <div className="mx_IncomingSasDialog_opponentProfile">
|
profile = <div className="mx_IncomingSasDialog_opponentProfile">
|
||||||
<BaseAvatar name={this.state.opponentProfile.displayname}
|
<BaseAvatar name={oppProfile.displayname}
|
||||||
idName={this.props.verifier.userId}
|
idName={this.props.verifier.userId}
|
||||||
url={MatrixClientPeg.get().mxcUrlToHttp(
|
url={url}
|
||||||
this.state.opponentProfile.avatar_url,
|
|
||||||
Math.floor(48 * window.devicePixelRatio),
|
|
||||||
Math.floor(48 * window.devicePixelRatio),
|
|
||||||
'crop',
|
|
||||||
)}
|
|
||||||
width={48} height={48} resizeMethod='crop'
|
width={48} height={48} resizeMethod='crop'
|
||||||
/>
|
/>
|
||||||
<h2>{this.state.opponentProfile.displayname}</h2>
|
<h2>{oppProfile.displayname}</h2>
|
||||||
</div>;
|
</div>;
|
||||||
} else if (this.state.opponentProfileError) {
|
} else if (this.state.opponentProfileError) {
|
||||||
profile = <div>
|
profile = <div>
|
||||||
|
|
|
@ -22,7 +22,6 @@ import {makeRoomPermalink, makeUserPermalink} from "../../../utils/permalinks/Pe
|
||||||
import DMRoomMap from "../../../utils/DMRoomMap";
|
import DMRoomMap from "../../../utils/DMRoomMap";
|
||||||
import {RoomMember} from "matrix-js-sdk/src/models/room-member";
|
import {RoomMember} from "matrix-js-sdk/src/models/room-member";
|
||||||
import SdkConfig from "../../../SdkConfig";
|
import SdkConfig from "../../../SdkConfig";
|
||||||
import {getHttpUriForMxc} from "matrix-js-sdk/src/content-repo";
|
|
||||||
import * as Email from "../../../email";
|
import * as Email from "../../../email";
|
||||||
import {getDefaultIdentityServerUrl, useDefaultIdentityServer} from "../../../utils/IdentityServerUtils";
|
import {getDefaultIdentityServerUrl, useDefaultIdentityServer} from "../../../utils/IdentityServerUtils";
|
||||||
import {abbreviateUrl} from "../../../utils/UrlUtils";
|
import {abbreviateUrl} from "../../../utils/UrlUtils";
|
||||||
|
@ -43,6 +42,7 @@ import CountlyAnalytics from "../../../CountlyAnalytics";
|
||||||
import {Room} from "matrix-js-sdk/src/models/room";
|
import {Room} from "matrix-js-sdk/src/models/room";
|
||||||
import { MatrixCall } from 'matrix-js-sdk/src/webrtc/call';
|
import { MatrixCall } from 'matrix-js-sdk/src/webrtc/call';
|
||||||
import {replaceableComponent} from "../../../utils/replaceableComponent";
|
import {replaceableComponent} from "../../../utils/replaceableComponent";
|
||||||
|
import {mediaFromMxc} from "../../../customisations/Media";
|
||||||
|
|
||||||
// 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 */
|
||||||
|
@ -160,9 +160,9 @@ class DMUserTile extends React.PureComponent<IDMUserTileProps> {
|
||||||
width={avatarSize} height={avatarSize} />
|
width={avatarSize} height={avatarSize} />
|
||||||
: <BaseAvatar
|
: <BaseAvatar
|
||||||
className='mx_InviteDialog_userTile_avatar'
|
className='mx_InviteDialog_userTile_avatar'
|
||||||
url={getHttpUriForMxc(
|
url={this.props.member.getMxcAvatarUrl()
|
||||||
MatrixClientPeg.get().getHomeserverUrl(), this.props.member.getMxcAvatarUrl(),
|
? mediaFromMxc(this.props.member.getMxcAvatarUrl()).getSquareThumbnailHttp(avatarSize)
|
||||||
avatarSize, avatarSize, "crop")}
|
: null}
|
||||||
name={this.props.member.name}
|
name={this.props.member.name}
|
||||||
idName={this.props.member.userId}
|
idName={this.props.member.userId}
|
||||||
width={avatarSize}
|
width={avatarSize}
|
||||||
|
@ -262,9 +262,9 @@ class DMRoomTile extends React.PureComponent<IDMRoomTileProps> {
|
||||||
src={require("../../../../res/img/icon-email-pill-avatar.svg")}
|
src={require("../../../../res/img/icon-email-pill-avatar.svg")}
|
||||||
width={avatarSize} height={avatarSize} />
|
width={avatarSize} height={avatarSize} />
|
||||||
: <BaseAvatar
|
: <BaseAvatar
|
||||||
url={getHttpUriForMxc(
|
url={this.props.member.getMxcAvatarUrl()
|
||||||
MatrixClientPeg.get().getHomeserverUrl(), this.props.member.getMxcAvatarUrl(),
|
? mediaFromMxc(this.props.member.getMxcAvatarUrl()).getSquareThumbnailHttp(avatarSize)
|
||||||
avatarSize, avatarSize, "crop")}
|
: null}
|
||||||
name={this.props.member.name}
|
name={this.props.member.name}
|
||||||
idName={this.props.member.userId}
|
idName={this.props.member.userId}
|
||||||
width={avatarSize}
|
width={avatarSize}
|
||||||
|
@ -673,7 +673,7 @@ export default class InviteDialog extends React.PureComponent<IInviteDialogProps
|
||||||
console.error(err);
|
console.error(err);
|
||||||
this.setState({
|
this.setState({
|
||||||
busy: false,
|
busy: false,
|
||||||
errorText: _t("We couldn't create your DM. Please check the users you want to invite and try again."),
|
errorText: _t("We couldn't create your DM."),
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@ -886,19 +886,21 @@ export default class InviteDialog extends React.PureComponent<IInviteDialogProps
|
||||||
};
|
};
|
||||||
|
|
||||||
_toggleMember = (member: Member) => {
|
_toggleMember = (member: Member) => {
|
||||||
let filterText = this.state.filterText;
|
if (!this.state.busy) {
|
||||||
const targets = this.state.targets.map(t => t); // cheap clone for mutation
|
let filterText = this.state.filterText;
|
||||||
const idx = targets.indexOf(member);
|
const targets = this.state.targets.map(t => t); // cheap clone for mutation
|
||||||
if (idx >= 0) {
|
const idx = targets.indexOf(member);
|
||||||
targets.splice(idx, 1);
|
if (idx >= 0) {
|
||||||
} else {
|
targets.splice(idx, 1);
|
||||||
targets.push(member);
|
} else {
|
||||||
filterText = ""; // clear the filter when the user accepts a suggestion
|
targets.push(member);
|
||||||
}
|
filterText = ""; // clear the filter when the user accepts a suggestion
|
||||||
this.setState({targets, filterText});
|
}
|
||||||
|
this.setState({targets, filterText});
|
||||||
|
|
||||||
if (this._editorRef && this._editorRef.current) {
|
if (this._editorRef && this._editorRef.current) {
|
||||||
this._editorRef.current.focus();
|
this._editorRef.current.focus();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1256,7 +1258,9 @@ export default class InviteDialog extends React.PureComponent<IInviteDialogProps
|
||||||
? _t("Invite to %(spaceName)s", {
|
? _t("Invite to %(spaceName)s", {
|
||||||
spaceName: room.name || _t("Unnamed Space"),
|
spaceName: room.name || _t("Unnamed Space"),
|
||||||
})
|
})
|
||||||
: _t("Invite to this room");
|
: _t("Invite to %(roomName)s", {
|
||||||
|
roomName: room.name || _t("Unnamed Room"),
|
||||||
|
});
|
||||||
|
|
||||||
let helpTextUntranslated;
|
let helpTextUntranslated;
|
||||||
if (isSpace) {
|
if (isSpace) {
|
||||||
|
|
|
@ -18,7 +18,7 @@ import React, {PureComponent} from 'react';
|
||||||
import * as sdk from '../../../index';
|
import * as sdk from '../../../index';
|
||||||
import { _t } from '../../../languageHandler';
|
import { _t } from '../../../languageHandler';
|
||||||
import PropTypes from "prop-types";
|
import PropTypes from "prop-types";
|
||||||
import {MatrixEvent} from "matrix-js-sdk";
|
import {MatrixEvent} from "matrix-js-sdk/src/models/event";
|
||||||
import {MatrixClientPeg} from "../../../MatrixClientPeg";
|
import {MatrixClientPeg} from "../../../MatrixClientPeg";
|
||||||
import SdkConfig from '../../../SdkConfig';
|
import SdkConfig from '../../../SdkConfig';
|
||||||
import Markdown from '../../../Markdown';
|
import Markdown from '../../../Markdown';
|
||||||
|
|
|
@ -82,6 +82,33 @@ export default class RoomUpgradeWarningDialog extends React.Component {
|
||||||
|
|
||||||
const title = this.state.isPrivate ? _t("Upgrade private room") : _t("Upgrade public room");
|
const title = this.state.isPrivate ? _t("Upgrade private room") : _t("Upgrade public room");
|
||||||
|
|
||||||
|
let bugReports = (
|
||||||
|
<p>
|
||||||
|
{_t(
|
||||||
|
"This usually only affects how the room is processed on the server. If you're " +
|
||||||
|
"having problems with your %(brand)s, please report a bug.", {brand},
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
);
|
||||||
|
if (SdkConfig.get().bug_report_endpoint_url) {
|
||||||
|
bugReports = (
|
||||||
|
<p>
|
||||||
|
{_t(
|
||||||
|
"This usually only affects how the room is processed on the server. If you're " +
|
||||||
|
"having problems with your %(brand)s, please <a>report a bug</a>.",
|
||||||
|
{
|
||||||
|
brand,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"a": (sub) => {
|
||||||
|
return <a href='#' onClick={this._openBugReportDialog}>{sub}</a>;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<BaseDialog
|
<BaseDialog
|
||||||
className='mx_RoomUpgradeWarningDialog'
|
className='mx_RoomUpgradeWarningDialog'
|
||||||
|
@ -97,20 +124,7 @@ export default class RoomUpgradeWarningDialog extends React.Component {
|
||||||
"is unstable due to bugs, missing features or security vulnerabilities.",
|
"is unstable due to bugs, missing features or security vulnerabilities.",
|
||||||
)}
|
)}
|
||||||
</p>
|
</p>
|
||||||
<p>
|
{bugReports}
|
||||||
{_t(
|
|
||||||
"This usually only affects how the room is processed on the server. If you're " +
|
|
||||||
"having problems with your %(brand)s, please <a>report a bug</a>.",
|
|
||||||
{
|
|
||||||
brand,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"a": (sub) => {
|
|
||||||
return <a href='#' onClick={this._openBugReportDialog}>{sub}</a>;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
)}
|
|
||||||
</p>
|
|
||||||
<p>
|
<p>
|
||||||
{_t(
|
{_t(
|
||||||
"You'll upgrade this room from <oldVersion /> to <newVersion />.",
|
"You'll upgrade this room from <oldVersion /> to <newVersion />.",
|
||||||
|
|
|
@ -28,7 +28,6 @@ import {getTopic} from "../elements/RoomTopic";
|
||||||
import {avatarUrlForRoom} from "../../../Avatar";
|
import {avatarUrlForRoom} from "../../../Avatar";
|
||||||
import ToggleSwitch from "../elements/ToggleSwitch";
|
import ToggleSwitch from "../elements/ToggleSwitch";
|
||||||
import AccessibleButton from "../elements/AccessibleButton";
|
import AccessibleButton from "../elements/AccessibleButton";
|
||||||
import FormButton from "../elements/FormButton";
|
|
||||||
import Modal from "../../../Modal";
|
import Modal from "../../../Modal";
|
||||||
import defaultDispatcher from "../../../dispatcher/dispatcher";
|
import defaultDispatcher from "../../../dispatcher/dispatcher";
|
||||||
import {allSettled} from "../../../utils/promise";
|
import {allSettled} from "../../../utils/promise";
|
||||||
|
@ -134,16 +133,17 @@ const SpaceSettingsDialog: React.FC<IProps> = ({ matrixClient: cli, space, onFin
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<FormButton
|
<AccessibleButton
|
||||||
kind="danger"
|
kind="danger"
|
||||||
label={_t("Leave Space")}
|
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
defaultDispatcher.dispatch({
|
defaultDispatcher.dispatch({
|
||||||
action: "leave_room",
|
action: "leave_room",
|
||||||
room_id: space.roomId,
|
room_id: space.roomId,
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
/>
|
>
|
||||||
|
{ _t("Leave Space") }
|
||||||
|
</AccessibleButton>
|
||||||
|
|
||||||
<div className="mx_SpaceSettingsDialog_buttons">
|
<div className="mx_SpaceSettingsDialog_buttons">
|
||||||
<AccessibleButton onClick={() => Modal.createDialog(DevtoolsDialog, {roomId: space.roomId})}>
|
<AccessibleButton onClick={() => Modal.createDialog(DevtoolsDialog, {roomId: space.roomId})}>
|
||||||
|
@ -152,7 +152,9 @@ const SpaceSettingsDialog: React.FC<IProps> = ({ matrixClient: cli, space, onFin
|
||||||
<AccessibleButton onClick={onFinished} disabled={busy} kind="link">
|
<AccessibleButton onClick={onFinished} disabled={busy} kind="link">
|
||||||
{ _t("Cancel") }
|
{ _t("Cancel") }
|
||||||
</AccessibleButton>
|
</AccessibleButton>
|
||||||
<FormButton onClick={onSave} disabled={busy} label={busy ? _t("Saving...") : _t("Save Changes")} />
|
<AccessibleButton onClick={onSave} disabled={busy} kind="primary">
|
||||||
|
{ busy ? _t("Saving...") : _t("Save Changes") }
|
||||||
|
</AccessibleButton>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</BaseDialog>;
|
</BaseDialog>;
|
||||||
|
|
|
@ -17,7 +17,7 @@ limitations under the License.
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import {IntegrationManagers} from "../../../integrations/IntegrationManagers";
|
import {IntegrationManagers} from "../../../integrations/IntegrationManagers";
|
||||||
import {Room} from "matrix-js-sdk";
|
import {Room} from "matrix-js-sdk/src/models/room";
|
||||||
import * as sdk from '../../../index';
|
import * as sdk from '../../../index';
|
||||||
import {dialogTermsInteractionCallback, TermsNotSignedError} from "../../../Terms";
|
import {dialogTermsInteractionCallback, TermsNotSignedError} from "../../../Terms";
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
|
|
@ -20,8 +20,8 @@ import PropTypes from 'prop-types';
|
||||||
import * as sdk from '../../../index';
|
import * as sdk from '../../../index';
|
||||||
import { _t, pickBestLanguage } from '../../../languageHandler';
|
import { _t, pickBestLanguage } from '../../../languageHandler';
|
||||||
|
|
||||||
import Matrix from 'matrix-js-sdk';
|
|
||||||
import {replaceableComponent} from "../../../utils/replaceableComponent";
|
import {replaceableComponent} from "../../../utils/replaceableComponent";
|
||||||
|
import {SERVICE_TYPES} from "matrix-js-sdk/src/service-types";
|
||||||
|
|
||||||
class TermsCheckbox extends React.PureComponent {
|
class TermsCheckbox extends React.PureComponent {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
|
@ -85,22 +85,22 @@ export default class TermsDialog extends React.PureComponent {
|
||||||
|
|
||||||
_nameForServiceType(serviceType, host) {
|
_nameForServiceType(serviceType, host) {
|
||||||
switch (serviceType) {
|
switch (serviceType) {
|
||||||
case Matrix.SERVICE_TYPES.IS:
|
case SERVICE_TYPES.IS:
|
||||||
return <div>{_t("Identity Server")}<br />({host})</div>;
|
return <div>{_t("Identity Server")}<br />({host})</div>;
|
||||||
case Matrix.SERVICE_TYPES.IM:
|
case SERVICE_TYPES.IM:
|
||||||
return <div>{_t("Integration Manager")}<br />({host})</div>;
|
return <div>{_t("Integration Manager")}<br />({host})</div>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_summaryForServiceType(serviceType) {
|
_summaryForServiceType(serviceType) {
|
||||||
switch (serviceType) {
|
switch (serviceType) {
|
||||||
case Matrix.SERVICE_TYPES.IS:
|
case SERVICE_TYPES.IS:
|
||||||
return <div>
|
return <div>
|
||||||
{_t("Find others by phone or email")}
|
{_t("Find others by phone or email")}
|
||||||
<br />
|
<br />
|
||||||
{_t("Be found by phone or email")}
|
{_t("Be found by phone or email")}
|
||||||
</div>;
|
</div>;
|
||||||
case Matrix.SERVICE_TYPES.IM:
|
case SERVICE_TYPES.IM:
|
||||||
return <div>
|
return <div>
|
||||||
{_t("Use bots, bridges, widgets and sticker packs")}
|
{_t("Use bots, bridges, widgets and sticker packs")}
|
||||||
</div>;
|
</div>;
|
||||||
|
|
|
@ -19,7 +19,7 @@ import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import * as sdk from '../../../../index';
|
import * as sdk from '../../../../index';
|
||||||
import {MatrixClientPeg} from '../../../../MatrixClientPeg';
|
import {MatrixClientPeg} from '../../../../MatrixClientPeg';
|
||||||
import { MatrixClient } from 'matrix-js-sdk';
|
import { MatrixClient } from 'matrix-js-sdk/src/client';
|
||||||
import { _t } from '../../../../languageHandler';
|
import { _t } from '../../../../languageHandler';
|
||||||
import { accessSecretStorage } from '../../../../SecurityManager';
|
import { accessSecretStorage } from '../../../../SecurityManager';
|
||||||
|
|
||||||
|
|
|
@ -19,10 +19,10 @@ import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import * as sdk from "../../../index";
|
import * as sdk from "../../../index";
|
||||||
import {MatrixClientPeg} from "../../../MatrixClientPeg";
|
|
||||||
import { _t } from '../../../languageHandler';
|
import { _t } from '../../../languageHandler';
|
||||||
import { UserAddressType } from '../../../UserAddress.js';
|
import { UserAddressType } from '../../../UserAddress.js';
|
||||||
import {replaceableComponent} from "../../../utils/replaceableComponent";
|
import {replaceableComponent} from "../../../utils/replaceableComponent";
|
||||||
|
import {mediaFromMxc} from "../../../customisations/Media";
|
||||||
|
|
||||||
@replaceableComponent("views.elements.AddressTile")
|
@replaceableComponent("views.elements.AddressTile")
|
||||||
export default class AddressTile extends React.Component {
|
export default class AddressTile extends React.Component {
|
||||||
|
@ -47,9 +47,7 @@ export default class AddressTile extends React.Component {
|
||||||
const isMatrixAddress = ['mx-user-id', 'mx-room-id'].includes(address.addressType);
|
const isMatrixAddress = ['mx-user-id', 'mx-room-id'].includes(address.addressType);
|
||||||
|
|
||||||
if (isMatrixAddress && address.avatarMxc) {
|
if (isMatrixAddress && address.avatarMxc) {
|
||||||
imgUrls.push(MatrixClientPeg.get().mxcUrlToHttp(
|
imgUrls.push(mediaFromMxc(address.avatarMxc).getSquareThumbnailHttp(25));
|
||||||
address.avatarMxc, 25, 25, 'crop',
|
|
||||||
));
|
|
||||||
} else if (address.addressType === 'email') {
|
} else if (address.addressType === 'email') {
|
||||||
imgUrls.push(require("../../../../res/img/icon-email-user.svg"));
|
imgUrls.push(require("../../../../res/img/icon-email-user.svg"));
|
||||||
}
|
}
|
||||||
|
|
|
@ -70,9 +70,7 @@ export default class EventTilePreview extends React.Component<IProps, IState> {
|
||||||
const client = MatrixClientPeg.get();
|
const client = MatrixClientPeg.get();
|
||||||
const userId = client.getUserId();
|
const userId = client.getUserId();
|
||||||
const profileInfo = await client.getProfileInfo(userId);
|
const profileInfo = await client.getProfileInfo(userId);
|
||||||
const avatarUrl = Avatar.avatarUrlForUser(
|
const avatarUrl = profileInfo.avatar_url;
|
||||||
{avatarUrl: profileInfo.avatar_url},
|
|
||||||
AVATAR_SIZE, AVATAR_SIZE, "crop");
|
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
userId,
|
userId,
|
||||||
|
@ -113,8 +111,9 @@ export default class EventTilePreview extends React.Component<IProps, IState> {
|
||||||
name: displayname,
|
name: displayname,
|
||||||
userId: userId,
|
userId: userId,
|
||||||
getAvatarUrl: (..._) => {
|
getAvatarUrl: (..._) => {
|
||||||
return avatarUrl;
|
return Avatar.avatarUrlForUser({avatarUrl}, AVATAR_SIZE, AVATAR_SIZE, "crop");
|
||||||
},
|
},
|
||||||
|
getMxcAvatarUrl: () => avatarUrl,
|
||||||
};
|
};
|
||||||
|
|
||||||
return event;
|
return event;
|
||||||
|
|
|
@ -20,6 +20,7 @@ import FlairStore from '../../../stores/FlairStore';
|
||||||
import dis from '../../../dispatcher/dispatcher';
|
import dis from '../../../dispatcher/dispatcher';
|
||||||
import MatrixClientContext from "../../../contexts/MatrixClientContext";
|
import MatrixClientContext from "../../../contexts/MatrixClientContext";
|
||||||
import {replaceableComponent} from "../../../utils/replaceableComponent";
|
import {replaceableComponent} from "../../../utils/replaceableComponent";
|
||||||
|
import {mediaFromMxc} from "../../../customisations/Media";
|
||||||
|
|
||||||
|
|
||||||
class FlairAvatar extends React.Component {
|
class FlairAvatar extends React.Component {
|
||||||
|
@ -39,8 +40,7 @@ class FlairAvatar extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const httpUrl = this.context.mxcUrlToHttp(
|
const httpUrl = mediaFromMxc(this.props.groupProfile.avatarUrl).getSquareThumbnailHttp(16);
|
||||||
this.props.groupProfile.avatarUrl, 16, 16, 'scale', false);
|
|
||||||
const tooltip = this.props.groupProfile.name ?
|
const tooltip = this.props.groupProfile.name ?
|
||||||
`${this.props.groupProfile.name} (${this.props.groupProfile.groupId})`:
|
`${this.props.groupProfile.name} (${this.props.groupProfile.groupId})`:
|
||||||
this.props.groupProfile.groupId;
|
this.props.groupProfile.groupId;
|
||||||
|
|
|
@ -1,7 +1,5 @@
|
||||||
/*
|
/*
|
||||||
Copyright 2017 Vector Creations Ltd
|
Copyright 2017 - 2019, 2021 The Matrix.org Foundation C.I.C.
|
||||||
Copyright 2018 New Vector Ltd
|
|
||||||
Copyright 2019, 2021 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.
|
||||||
|
@ -19,13 +17,15 @@ import React from 'react';
|
||||||
import * as sdk from '../../../index';
|
import * as sdk from '../../../index';
|
||||||
import dis from '../../../dispatcher/dispatcher';
|
import dis from '../../../dispatcher/dispatcher';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { Room, RoomMember } from 'matrix-js-sdk';
|
import { Room } from 'matrix-js-sdk/src/models/room';
|
||||||
|
import { RoomMember } from 'matrix-js-sdk/src/models/room-member';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import {MatrixClientPeg} from '../../../MatrixClientPeg';
|
import {MatrixClientPeg} from '../../../MatrixClientPeg';
|
||||||
import FlairStore from "../../../stores/FlairStore";
|
import FlairStore from "../../../stores/FlairStore";
|
||||||
import {getPrimaryPermalinkEntity, parseAppLocalLink} from "../../../utils/permalinks/Permalinks";
|
import {getPrimaryPermalinkEntity, parseAppLocalLink} from "../../../utils/permalinks/Permalinks";
|
||||||
import MatrixClientContext from "../../../contexts/MatrixClientContext";
|
import MatrixClientContext from "../../../contexts/MatrixClientContext";
|
||||||
import {Action} from "../../../dispatcher/actions";
|
import {Action} from "../../../dispatcher/actions";
|
||||||
|
import {mediaFromMxc} from "../../../customisations/Media";
|
||||||
import Tooltip from './Tooltip';
|
import Tooltip from './Tooltip';
|
||||||
import {replaceableComponent} from "../../../utils/replaceableComponent";
|
import {replaceableComponent} from "../../../utils/replaceableComponent";
|
||||||
|
|
||||||
|
@ -254,12 +254,12 @@ class Pill extends React.Component {
|
||||||
case Pill.TYPE_GROUP_MENTION: {
|
case Pill.TYPE_GROUP_MENTION: {
|
||||||
if (this.state.group) {
|
if (this.state.group) {
|
||||||
const {avatarUrl, groupId, name} = this.state.group;
|
const {avatarUrl, groupId, name} = this.state.group;
|
||||||
const cli = MatrixClientPeg.get();
|
|
||||||
|
|
||||||
linkText = groupId;
|
linkText = groupId;
|
||||||
if (this.props.shouldShowPillAvatar) {
|
if (this.props.shouldShowPillAvatar) {
|
||||||
avatar = <BaseAvatar name={name || groupId} width={16} height={16} aria-hidden="true"
|
avatar = <BaseAvatar
|
||||||
url={avatarUrl ? cli.mxcUrlToHttp(avatarUrl, 16, 16) : null} />;
|
name={name || groupId} width={16} height={16} aria-hidden="true"
|
||||||
|
url={avatarUrl ? mediaFromMxc(avatarUrl).getSquareThumbnailHttp(16) : null} />;
|
||||||
}
|
}
|
||||||
pillClass = 'mx_GroupPill';
|
pillClass = 'mx_GroupPill';
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,7 +21,7 @@ import {_t} from '../../../languageHandler';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import dis from '../../../dispatcher/dispatcher';
|
import dis from '../../../dispatcher/dispatcher';
|
||||||
import {wantsDateSeparator} from '../../../DateUtils';
|
import {wantsDateSeparator} from '../../../DateUtils';
|
||||||
import {MatrixEvent} from 'matrix-js-sdk';
|
import {MatrixEvent} from 'matrix-js-sdk/src/models/event';
|
||||||
import {makeUserPermalink, RoomPermalinkCreator} from "../../../utils/permalinks/Permalinks";
|
import {makeUserPermalink, RoomPermalinkCreator} from "../../../utils/permalinks/Permalinks";
|
||||||
import SettingsStore from "../../../settings/SettingsStore";
|
import SettingsStore from "../../../settings/SettingsStore";
|
||||||
import {LayoutPropType} from "../../../settings/Layout";
|
import {LayoutPropType} from "../../../settings/Layout";
|
||||||
|
|
|
@ -24,6 +24,7 @@ import AccessibleButton from "./AccessibleButton";
|
||||||
import {_t} from "../../../languageHandler";
|
import {_t} from "../../../languageHandler";
|
||||||
import {IdentityProviderBrand, IIdentityProvider, ISSOFlow} from "../../../Login";
|
import {IdentityProviderBrand, IIdentityProvider, ISSOFlow} from "../../../Login";
|
||||||
import AccessibleTooltipButton from "./AccessibleTooltipButton";
|
import AccessibleTooltipButton from "./AccessibleTooltipButton";
|
||||||
|
import {mediaFromMxc} from "../../../customisations/Media";
|
||||||
|
|
||||||
interface ISSOButtonProps extends Omit<IProps, "flow"> {
|
interface ISSOButtonProps extends Omit<IProps, "flow"> {
|
||||||
idp: IIdentityProvider;
|
idp: IIdentityProvider;
|
||||||
|
@ -72,7 +73,7 @@ const SSOButton: React.FC<ISSOButtonProps> = ({
|
||||||
brandClass = `mx_SSOButton_brand_${brandName}`;
|
brandClass = `mx_SSOButton_brand_${brandName}`;
|
||||||
icon = <img src={brandIcon} height="24" width="24" alt={brandName} />;
|
icon = <img src={brandIcon} height="24" width="24" alt={brandName} />;
|
||||||
} else if (typeof idp?.icon === "string" && idp.icon.startsWith("mxc://")) {
|
} else if (typeof idp?.icon === "string" && idp.icon.startsWith("mxc://")) {
|
||||||
const src = matrixClient.mxcUrlToHttp(idp.icon, 24, 24, "crop", true);
|
const src = mediaFromMxc(idp.icon).getSquareThumbnailHttp(24);
|
||||||
icon = <img src={src} height="24" width="24" alt={idp.name} />;
|
icon = <img src={src} height="24" width="24" alt={idp.name} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -30,6 +30,7 @@ import GroupFilterOrderStore from '../../../stores/GroupFilterOrderStore';
|
||||||
import MatrixClientContext from "../../../contexts/MatrixClientContext";
|
import MatrixClientContext from "../../../contexts/MatrixClientContext";
|
||||||
import AccessibleButton from "./AccessibleButton";
|
import AccessibleButton from "./AccessibleButton";
|
||||||
import SettingsStore from "../../../settings/SettingsStore";
|
import SettingsStore from "../../../settings/SettingsStore";
|
||||||
|
import {mediaFromMxc} from "../../../customisations/Media";
|
||||||
import {replaceableComponent} from "../../../utils/replaceableComponent";
|
import {replaceableComponent} from "../../../utils/replaceableComponent";
|
||||||
|
|
||||||
// A class for a child of GroupFilterPanel (possibly wrapped in a DNDTagTile) that represents
|
// A class for a child of GroupFilterPanel (possibly wrapped in a DNDTagTile) that represents
|
||||||
|
@ -130,11 +131,11 @@ export default class TagTile extends React.Component {
|
||||||
const BaseAvatar = sdk.getComponent('avatars.BaseAvatar');
|
const BaseAvatar = sdk.getComponent('avatars.BaseAvatar');
|
||||||
const profile = this.state.profile || {};
|
const profile = this.state.profile || {};
|
||||||
const name = profile.name || this.props.tag;
|
const name = profile.name || this.props.tag;
|
||||||
const avatarHeight = 32;
|
const avatarSize = 32;
|
||||||
|
|
||||||
const httpUrl = profile.avatarUrl ? this.context.mxcUrlToHttp(
|
const httpUrl = profile.avatarUrl
|
||||||
profile.avatarUrl, avatarHeight, avatarHeight, "crop",
|
? mediaFromMxc(profile.avatarUrl).getSquareThumbnailHttp(avatarSize)
|
||||||
) : null;
|
: null;
|
||||||
|
|
||||||
const isPrototype = SettingsStore.getValue("feature_communities_v2_prototypes");
|
const isPrototype = SettingsStore.getValue("feature_communities_v2_prototypes");
|
||||||
const className = classNames({
|
const className = classNames({
|
||||||
|
@ -180,8 +181,8 @@ export default class TagTile extends React.Component {
|
||||||
name={name}
|
name={name}
|
||||||
idName={this.props.tag}
|
idName={this.props.tag}
|
||||||
url={httpUrl}
|
url={httpUrl}
|
||||||
width={avatarHeight}
|
width={avatarSize}
|
||||||
height={avatarHeight}
|
height={avatarSize}
|
||||||
/>
|
/>
|
||||||
{contextButton}
|
{contextButton}
|
||||||
{badgeElement}
|
{badgeElement}
|
||||||
|
|
|
@ -46,12 +46,14 @@ export default class TextWithTooltip extends React.Component {
|
||||||
render() {
|
render() {
|
||||||
const Tooltip = sdk.getComponent("elements.Tooltip");
|
const Tooltip = sdk.getComponent("elements.Tooltip");
|
||||||
|
|
||||||
|
const {class: className, children, tooltip, tooltipClass, ...props} = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<span onMouseOver={this.onMouseOver} onMouseLeave={this.onMouseLeave} className={this.props.class}>
|
<span {...props} onMouseOver={this.onMouseOver} onMouseLeave={this.onMouseLeave} className={className}>
|
||||||
{this.props.children}
|
{children}
|
||||||
{this.state.hover && <Tooltip
|
{this.state.hover && <Tooltip
|
||||||
label={this.props.tooltip}
|
label={tooltip}
|
||||||
tooltipClassName={this.props.tooltipClass}
|
tooltipClassName={tooltipClass}
|
||||||
className={"mx_TextWithTooltip_tooltip"} /> }
|
className={"mx_TextWithTooltip_tooltip"} /> }
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
|
|
|
@ -27,6 +27,7 @@ import {ContextMenu, ContextMenuButton, toRightOf} from "../../structures/Contex
|
||||||
import MatrixClientContext from "../../../contexts/MatrixClientContext";
|
import MatrixClientContext from "../../../contexts/MatrixClientContext";
|
||||||
import {RovingTabIndexWrapper} from "../../../accessibility/RovingTabIndex";
|
import {RovingTabIndexWrapper} from "../../../accessibility/RovingTabIndex";
|
||||||
import {replaceableComponent} from "../../../utils/replaceableComponent";
|
import {replaceableComponent} from "../../../utils/replaceableComponent";
|
||||||
|
import {mediaFromMxc} from "../../../customisations/Media";
|
||||||
|
|
||||||
// XXX this class copies a lot from RoomTile.js
|
// XXX this class copies a lot from RoomTile.js
|
||||||
@replaceableComponent("views.groups.GroupInviteTile")
|
@replaceableComponent("views.groups.GroupInviteTile")
|
||||||
|
@ -117,8 +118,9 @@ export default class GroupInviteTile extends React.Component {
|
||||||
const BaseAvatar = sdk.getComponent('avatars.BaseAvatar');
|
const BaseAvatar = sdk.getComponent('avatars.BaseAvatar');
|
||||||
|
|
||||||
const groupName = this.props.group.name || this.props.group.groupId;
|
const groupName = this.props.group.name || this.props.group.groupId;
|
||||||
const httpAvatarUrl = this.props.group.avatarUrl ?
|
const httpAvatarUrl = this.props.group.avatarUrl
|
||||||
this.context.mxcUrlToHttp(this.props.group.avatarUrl, 24, 24) : null;
|
? mediaFromMxc(this.props.group.avatarUrl).getSquareThumbnailHttp(24)
|
||||||
|
: null;
|
||||||
|
|
||||||
const av = <BaseAvatar name={groupName} width={24} height={24} url={httpAvatarUrl} />;
|
const av = <BaseAvatar name={groupName} width={24} height={24} url={httpAvatarUrl} />;
|
||||||
|
|
||||||
|
|
|
@ -23,6 +23,7 @@ import dis from '../../../dispatcher/dispatcher';
|
||||||
import { GroupMemberType } from '../../../groups';
|
import { GroupMemberType } from '../../../groups';
|
||||||
import MatrixClientContext from "../../../contexts/MatrixClientContext";
|
import MatrixClientContext from "../../../contexts/MatrixClientContext";
|
||||||
import {replaceableComponent} from "../../../utils/replaceableComponent";
|
import {replaceableComponent} from "../../../utils/replaceableComponent";
|
||||||
|
import {mediaFromMxc} from "../../../customisations/Media";
|
||||||
|
|
||||||
@replaceableComponent("views.groups.GroupMemberTile")
|
@replaceableComponent("views.groups.GroupMemberTile")
|
||||||
export default class GroupMemberTile extends React.Component {
|
export default class GroupMemberTile extends React.Component {
|
||||||
|
@ -46,10 +47,9 @@ export default class GroupMemberTile extends React.Component {
|
||||||
const EntityTile = sdk.getComponent('rooms.EntityTile');
|
const EntityTile = sdk.getComponent('rooms.EntityTile');
|
||||||
|
|
||||||
const name = this.props.member.displayname || this.props.member.userId;
|
const name = this.props.member.displayname || this.props.member.userId;
|
||||||
const avatarUrl = this.context.mxcUrlToHttp(
|
const avatarUrl = this.props.member.avatarUrl
|
||||||
this.props.member.avatarUrl,
|
? mediaFromMxc(this.props.member.avatarUrl).getSquareThumbnailHttp(36)
|
||||||
36, 36, 'crop',
|
: null;
|
||||||
);
|
|
||||||
|
|
||||||
const av = (
|
const av = (
|
||||||
<BaseAvatar
|
<BaseAvatar
|
||||||
|
|
|
@ -25,6 +25,7 @@ import GroupStore from '../../../stores/GroupStore';
|
||||||
import MatrixClientContext from "../../../contexts/MatrixClientContext";
|
import MatrixClientContext from "../../../contexts/MatrixClientContext";
|
||||||
import AutoHideScrollbar from "../../structures/AutoHideScrollbar";
|
import AutoHideScrollbar from "../../structures/AutoHideScrollbar";
|
||||||
import {replaceableComponent} from "../../../utils/replaceableComponent";
|
import {replaceableComponent} from "../../../utils/replaceableComponent";
|
||||||
|
import {mediaFromMxc} from "../../../customisations/Media";
|
||||||
|
|
||||||
@replaceableComponent("views.groups.GroupRoomInfo")
|
@replaceableComponent("views.groups.GroupRoomInfo")
|
||||||
export default class GroupRoomInfo extends React.Component {
|
export default class GroupRoomInfo extends React.Component {
|
||||||
|
@ -204,10 +205,8 @@ export default class GroupRoomInfo extends React.Component {
|
||||||
const avatarUrl = this.state.groupRoom.avatarUrl;
|
const avatarUrl = this.state.groupRoom.avatarUrl;
|
||||||
let avatarElement;
|
let avatarElement;
|
||||||
if (avatarUrl) {
|
if (avatarUrl) {
|
||||||
const httpUrl = this.context.mxcUrlToHttp(avatarUrl, 800, 800);
|
const httpUrl = mediaFromMxc(avatarUrl).getSquareThumbnailHttp(800);
|
||||||
avatarElement = (<div className="mx_MemberInfo_avatar">
|
avatarElement = <div className="mx_MemberInfo_avatar"><img src={httpUrl} /></div>;
|
||||||
<img src={httpUrl} />
|
|
||||||
</div>);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const groupRoomName = this.state.groupRoom.displayname;
|
const groupRoomName = this.state.groupRoom.displayname;
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue