mirror of https://github.com/vector-im/riot-web
Merge branch 'develop' into t3chguy/spaces4.9
commit
688407abe6
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)
|
||||
=====================================================================================================
|
||||
[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v3.15.0-rc.1...v3.15.0)
|
||||
|
|
|
@ -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",
|
||||
"version": "3.15.0",
|
||||
"version": "3.16.0",
|
||||
"description": "SDK for matrix.org using React",
|
||||
"author": "matrix.org",
|
||||
"repository": {
|
||||
|
@ -157,6 +157,7 @@
|
|||
"jest": "^26.6.3",
|
||||
"jest-canvas-mock": "^2.3.0",
|
||||
"jest-environment-jsdom-sixteen": "^1.0.3",
|
||||
"jest-fetch-mock": "^3.0.3",
|
||||
"matrix-mock-request": "^1.2.3",
|
||||
"matrix-react-test-utils": "^0.2.2",
|
||||
"olm": "https://packages.matrix.org/npm/olm/olm-3.2.1.tgz",
|
||||
|
|
|
@ -489,54 +489,6 @@ input[type=text]:focus, input[type=password]:focus, textarea:focus {
|
|||
margin-top: 69px;
|
||||
}
|
||||
|
||||
.mx_Beta {
|
||||
color: red;
|
||||
margin-right: 10px;
|
||||
position: relative;
|
||||
top: -3px;
|
||||
background-color: white;
|
||||
padding: 0 4px;
|
||||
border-radius: 3px;
|
||||
border: 1px solid darkred;
|
||||
cursor: help;
|
||||
transition-duration: 200ms;
|
||||
font-size: smaller;
|
||||
filter: opacity(0.5);
|
||||
}
|
||||
|
||||
.mx_Beta:hover {
|
||||
color: white;
|
||||
border: 1px solid gray;
|
||||
background-color: darkred;
|
||||
}
|
||||
|
||||
.mx_TintableSvgButton {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
align-content: center;
|
||||
}
|
||||
|
||||
.mx_TintableSvgButton object {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
}
|
||||
|
||||
.mx_TintableSvgButton span {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
opacity: 0;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
// username colors
|
||||
// used by SenderProfile & RoomPreviewBar
|
||||
.mx_Username_color1 {
|
||||
|
@ -606,6 +558,13 @@ input[type=text]:focus, input[type=password]:focus, textarea:focus {
|
|||
}
|
||||
}
|
||||
|
||||
@define-mixin ProgressBarBgColour $colour {
|
||||
background-color: $colour;
|
||||
&::-webkit-progress-bar {
|
||||
background-color: $colour;
|
||||
}
|
||||
}
|
||||
|
||||
@define-mixin ProgressBarBorderRadius $radius {
|
||||
border-radius: $radius;
|
||||
&::-moz-progress-bar {
|
||||
|
|
|
@ -203,8 +203,9 @@ limitations under the License.
|
|||
.mx_SpaceRoomDirectory_actions {
|
||||
width: 180px;
|
||||
text-align: right;
|
||||
height: min-content;
|
||||
margin: auto 0 auto 28px;
|
||||
margin-left: 28px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
|
||||
.mx_AccessibleButton {
|
||||
vertical-align: middle;
|
||||
|
@ -223,9 +224,5 @@ limitations under the License.
|
|||
line-height: $font-15px;
|
||||
color: $secondary-fg-color;
|
||||
}
|
||||
|
||||
.mx_Checkbox {
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,6 +20,8 @@ $SpaceRoomViewInnerWidth: 428px;
|
|||
.mx_MainSplit > div:first-child {
|
||||
padding: 80px 60px;
|
||||
flex-grow: 1;
|
||||
max-height: 100%;
|
||||
overflow-y: auto;
|
||||
|
||||
h1 {
|
||||
margin: 0;
|
||||
|
@ -69,9 +71,116 @@ $SpaceRoomViewInnerWidth: 428px;
|
|||
}
|
||||
}
|
||||
|
||||
.mx_SpaceRoomView_landing {
|
||||
overflow-y: auto;
|
||||
.mx_SpaceRoomView_preview {
|
||||
padding: 32px 24px !important; // override default padding from above
|
||||
margin: auto;
|
||||
max-width: 480px;
|
||||
box-sizing: border-box;
|
||||
box-shadow: 2px 15px 30px $dialog-shadow-color;
|
||||
border: 1px solid $input-border-color;
|
||||
border-radius: 8px;
|
||||
|
||||
.mx_SpaceRoomView_preview_inviter {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 20px;
|
||||
font-size: $font-15px;
|
||||
|
||||
> div {
|
||||
margin-left: 8px;
|
||||
|
||||
.mx_SpaceRoomView_preview_inviter_name {
|
||||
line-height: $font-18px;
|
||||
}
|
||||
|
||||
.mx_SpaceRoomView_preview_inviter_mxid {
|
||||
line-height: $font-24px;
|
||||
color: $secondary-fg-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
> .mx_BaseAvatar_image,
|
||||
> .mx_BaseAvatar > .mx_BaseAvatar_image {
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
h1.mx_SpaceRoomView_preview_name {
|
||||
margin: 20px 0 !important; // override default margin from above
|
||||
}
|
||||
|
||||
.mx_SpaceRoomView_preview_info {
|
||||
color: $tertiary-fg-color;
|
||||
font-size: $font-15px;
|
||||
line-height: $font-24px;
|
||||
margin: 20px 0;
|
||||
|
||||
.mx_SpaceRoomView_preview_info_public,
|
||||
.mx_SpaceRoomView_preview_info_private {
|
||||
padding-left: 20px;
|
||||
position: relative;
|
||||
|
||||
&::before {
|
||||
position: absolute;
|
||||
content: "";
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
top: 0;
|
||||
left: -2px;
|
||||
mask-position: center;
|
||||
mask-repeat: no-repeat;
|
||||
background-color: $tertiary-fg-color;
|
||||
}
|
||||
}
|
||||
|
||||
.mx_SpaceRoomView_preview_info_public::before {
|
||||
mask-size: 12px;
|
||||
mask-image: url("$(res)/img/globe.svg");
|
||||
}
|
||||
|
||||
.mx_SpaceRoomView_preview_info_private::before {
|
||||
mask-size: 14px;
|
||||
mask-image: url("$(res)/img/element-icons/lock.svg");
|
||||
}
|
||||
|
||||
.mx_AccessibleButton_kind_link {
|
||||
color: inherit;
|
||||
position: relative;
|
||||
padding-left: 16px;
|
||||
|
||||
&::before {
|
||||
content: "·"; // visual separator
|
||||
position: absolute;
|
||||
left: 6px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.mx_SpaceRoomView_preview_topic {
|
||||
font-size: $font-14px;
|
||||
line-height: $font-22px;
|
||||
color: $secondary-fg-color;
|
||||
margin: 20px 0;
|
||||
max-height: 160px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.mx_SpaceRoomView_preview_joinButtons {
|
||||
margin-top: 20px;
|
||||
|
||||
.mx_AccessibleButton {
|
||||
width: 200px;
|
||||
box-sizing: border-box;
|
||||
padding: 14px 0;
|
||||
|
||||
& + .mx_AccessibleButton {
|
||||
margin-left: 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.mx_SpaceRoomView_landing {
|
||||
> .mx_BaseAvatar_image,
|
||||
> .mx_BaseAvatar > .mx_BaseAvatar_image {
|
||||
border-radius: 12px;
|
||||
|
@ -128,14 +237,6 @@ $SpaceRoomViewInnerWidth: 428px;
|
|||
font-size: $font-15px;
|
||||
}
|
||||
|
||||
.mx_SpaceRoomView_landing_joinButtons {
|
||||
margin-top: 24px;
|
||||
|
||||
.mx_FormButton {
|
||||
padding: 8px 22px;
|
||||
}
|
||||
}
|
||||
|
||||
.mx_SpaceRoomView_landing_adminButtons {
|
||||
margin-top: 32px;
|
||||
|
||||
|
|
|
@ -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");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
@ -15,47 +15,45 @@ limitations under the License.
|
|||
*/
|
||||
|
||||
.mx_UploadBar {
|
||||
padding-left: 65px; // line up with the shield area in the composer
|
||||
position: relative;
|
||||
|
||||
.mx_ProgressBar {
|
||||
width: calc(100% - 40px); // cheating at a right margin
|
||||
}
|
||||
}
|
||||
|
||||
.mx_UploadBar_uploadProgressOuter {
|
||||
height: 5px;
|
||||
margin-left: 63px;
|
||||
margin-top: -1px;
|
||||
padding-bottom: 5px;
|
||||
}
|
||||
|
||||
.mx_UploadBar_uploadProgressInner {
|
||||
background-color: $accent-color;
|
||||
height: 5px;
|
||||
}
|
||||
|
||||
.mx_UploadBar_uploadFilename {
|
||||
.mx_UploadBar_filename {
|
||||
margin-top: 5px;
|
||||
margin-left: 65px;
|
||||
opacity: 0.5;
|
||||
color: $primary-fg-color;
|
||||
}
|
||||
|
||||
.mx_UploadBar_uploadIcon {
|
||||
float: left;
|
||||
margin-top: 5px;
|
||||
margin-left: 14px;
|
||||
}
|
||||
|
||||
.mx_UploadBar_uploadCancel {
|
||||
float: right;
|
||||
margin-top: 5px;
|
||||
margin-right: 10px;
|
||||
color: $muted-fg-color;
|
||||
position: relative;
|
||||
opacity: 0.6;
|
||||
cursor: pointer;
|
||||
z-index: 1;
|
||||
padding-left: 22px; // 18px for icon, 4px for padding
|
||||
font-size: $font-15px;
|
||||
vertical-align: middle;
|
||||
|
||||
&::before {
|
||||
content: "";
|
||||
height: 18px;
|
||||
width: 18px;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
mask-repeat: no-repeat;
|
||||
mask-position: center;
|
||||
background-color: $muted-fg-color;
|
||||
mask-image: url('$(res)/img/element-icons/upload.svg');
|
||||
}
|
||||
}
|
||||
|
||||
.mx_UploadBar_uploadBytes {
|
||||
float: right;
|
||||
margin-top: 5px;
|
||||
margin-right: 30px;
|
||||
color: $accent-color;
|
||||
.mx_UploadBar_cancel {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
height: 16px;
|
||||
width: 16px;
|
||||
margin-right: 16px; // align over rightmost button in composer
|
||||
mask-repeat: no-repeat;
|
||||
mask-position: center;
|
||||
background-color: $muted-fg-color;
|
||||
mask-image: url('$(res)/img/icons-close.svg');
|
||||
}
|
||||
|
|
|
@ -22,9 +22,18 @@ limitations under the License.
|
|||
float: right;
|
||||
}
|
||||
|
||||
.mx_ViewSource_label_bottom {
|
||||
.mx_ViewSource_separator {
|
||||
clear: both;
|
||||
border-bottom: 1px solid #e5e5e5;
|
||||
padding-top: 0.7em;
|
||||
padding-bottom: 0.7em;
|
||||
}
|
||||
|
||||
.mx_ViewSource_heading {
|
||||
font-size: $font-17px;
|
||||
font-weight: 400;
|
||||
color: $primary-fg-color;
|
||||
margin-top: 0.7em;
|
||||
}
|
||||
|
||||
.mx_ViewSource pre {
|
||||
|
@ -34,3 +43,7 @@ limitations under the License.
|
|||
word-wrap: break-word;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
.mx_ViewSource_details {
|
||||
margin-top: 0.8em;
|
||||
}
|
||||
|
|
|
@ -26,50 +26,6 @@ limitations under the License.
|
|||
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 {
|
||||
width: 128px;
|
||||
height: 128px;
|
||||
|
|
|
@ -14,8 +14,7 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
// XXX: We shouldn't be using TemporaryTile anywhere - delete it.
|
||||
.mx_DecoratedRoomAvatar, .mx_TemporaryTile {
|
||||
.mx_DecoratedRoomAvatar, .mx_ExtraTile {
|
||||
position: relative;
|
||||
|
||||
&.mx_DecoratedRoomAvatar_cutout .mx_BaseAvatar {
|
||||
|
|
|
@ -26,7 +26,9 @@ limitations under the License.
|
|||
padding: 7px 18px;
|
||||
text-align: center;
|
||||
border-radius: 8px;
|
||||
display: inline-block;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: $font-14px;
|
||||
}
|
||||
|
||||
|
|
|
@ -33,4 +33,10 @@ limitations under the License.
|
|||
color: $notice-primary-color;
|
||||
background-color: $notice-primary-bg-color;
|
||||
}
|
||||
|
||||
&.mx_AccessibleButton_kind_secondary {
|
||||
color: $secondary-fg-color;
|
||||
border: 1px solid $secondary-fg-color;
|
||||
background-color: unset;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||
Copyright 2020, 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.
|
||||
|
@ -15,15 +15,15 @@ limitations under the License.
|
|||
*/
|
||||
|
||||
progress.mx_ProgressBar {
|
||||
height: 4px;
|
||||
height: 6px;
|
||||
width: 60px;
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
appearance: none;
|
||||
border: 0;
|
||||
border: none;
|
||||
|
||||
@mixin ProgressBarBorderRadius "10px";
|
||||
@mixin ProgressBarColour $accent-color;
|
||||
@mixin ProgressBarBorderRadius "6px";
|
||||
@mixin ProgressBarColour $progressbar-fg-color;
|
||||
@mixin ProgressBarBgColour $progressbar-bg-color;
|
||||
::-webkit-progress-value {
|
||||
transition: width 1s;
|
||||
}
|
||||
|
|
|
@ -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");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
@ -16,6 +16,19 @@ limitations under the License.
|
|||
|
||||
.mx_MFileBody_download {
|
||||
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 {
|
||||
|
@ -45,3 +58,46 @@ limitations under the License.
|
|||
* big the content of the iframe is. */
|
||||
height: 1.5em;
|
||||
}
|
||||
|
||||
.mx_MFileBody_info {
|
||||
background-color: $message-body-panel-bg-color;
|
||||
border-radius: 4px;
|
||||
width: 270px;
|
||||
padding: 8px;
|
||||
color: $message-body-panel-fg-color;
|
||||
|
||||
.mx_MFileBody_info_icon {
|
||||
background-color: $message-body-panel-icon-bg-color;
|
||||
border-radius: 20px;
|
||||
display: inline-block;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
position: relative;
|
||||
vertical-align: middle;
|
||||
margin-right: 12px;
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
mask-repeat: no-repeat;
|
||||
mask-position: center;
|
||||
mask-size: cover;
|
||||
mask-image: url('$(res)/img/element-icons/room/composer/attach.svg');
|
||||
background-color: $message-body-panel-fg-color;
|
||||
width: 13px;
|
||||
height: 15px;
|
||||
|
||||
position: absolute;
|
||||
top: 8px;
|
||||
left: 9px;
|
||||
}
|
||||
}
|
||||
|
||||
.mx_MFileBody_info_filename {
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
display: inline-block;
|
||||
width: calc(100% - 32px - 12px); // 32px icon, 12px margin on the icon
|
||||
vertical-align: middle;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,13 +14,11 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
.mx_UserInfo {
|
||||
.mx_EncryptionInfo_spinner {
|
||||
.mx_Spinner {
|
||||
margin-top: 25px;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
text-align: center;
|
||||
.mx_EncryptionInfo_spinner {
|
||||
.mx_Spinner {
|
||||
margin-top: 25px;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
text-align: center;
|
||||
}
|
||||
|
|
|
@ -370,11 +370,6 @@ $MinWidth: 240px;
|
|||
display: none;
|
||||
}
|
||||
|
||||
/* Avoid apptile iframes capturing mouse event focus when resizing */
|
||||
.mx_AppsDrawer_resizing iframe {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.mx_AppsDrawer_resizing .mx_AppTile_persistedWrapper {
|
||||
z-index: 1;
|
||||
}
|
||||
|
|
|
@ -213,23 +213,36 @@ $left-gutter: 64px;
|
|||
color: $accent-fg-color;
|
||||
}
|
||||
|
||||
.mx_EventTile_encrypting {
|
||||
color: $event-encrypting-color !important;
|
||||
}
|
||||
|
||||
.mx_EventTile_sending {
|
||||
color: $event-sending-color;
|
||||
}
|
||||
|
||||
.mx_EventTile_sending .mx_UserPill,
|
||||
.mx_EventTile_sending .mx_RoomPill {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.mx_EventTile_notSent {
|
||||
color: $event-notsent-color;
|
||||
}
|
||||
|
||||
.mx_EventTile_receiptSent,
|
||||
.mx_EventTile_receiptSending {
|
||||
// We don't use `position: relative` on the element because then it won't line
|
||||
// up with the other read receipts
|
||||
|
||||
&::before {
|
||||
background-color: $tertiary-fg-color;
|
||||
mask-repeat: no-repeat;
|
||||
mask-position: center;
|
||||
mask-size: 14px;
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
}
|
||||
}
|
||||
.mx_EventTile_receiptSent::before {
|
||||
mask-image: url('$(res)/img/element-icons/circle-sent.svg');
|
||||
}
|
||||
.mx_EventTile_receiptSending::before {
|
||||
mask-image: url('$(res)/img/element-icons/circle-sending.svg');
|
||||
}
|
||||
|
||||
.mx_EventTile_contextual {
|
||||
opacity: 0.4;
|
||||
}
|
||||
|
|
|
@ -21,7 +21,7 @@ $left-gutter: 64px;
|
|||
.mx_EventTile {
|
||||
> .mx_SenderProfile {
|
||||
line-height: $font-20px;
|
||||
padding-left: $left-gutter;
|
||||
margin-left: $left-gutter;
|
||||
}
|
||||
|
||||
> .mx_EventTile_line {
|
||||
|
|
|
@ -181,8 +181,7 @@ $irc-line-height: $font-18px;
|
|||
> span {
|
||||
display: flex;
|
||||
|
||||
> .mx_SenderProfile_name,
|
||||
> .mx_SenderProfile_aux {
|
||||
> .mx_SenderProfile_name {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
min-width: var(--name-width);
|
||||
|
@ -212,8 +211,7 @@ $irc-line-height: $font-18px;
|
|||
background: transparent;
|
||||
|
||||
> span {
|
||||
> .mx_SenderProfile_name,
|
||||
> .mx_SenderProfile_aux {
|
||||
> .mx_SenderProfile_name {
|
||||
min-width: inherit;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
<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="7.5" stroke="#8D99A5"/>
|
||||
</svg>
|
After Width: | Height: | Size: 152 B |
|
@ -0,0 +1,4 @@
|
|||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M8 15C11.866 15 15 11.866 15 8C15 4.13401 11.866 1 8 1C4.13401 1 1 4.13401 1 8C1 11.866 4.13401 15 8 15ZM8 16C12.4183 16 16 12.4183 16 8C16 3.58172 12.4183 0 8 0C3.58172 0 0 3.58172 0 8C0 12.4183 3.58172 16 8 16Z" fill="#8D99A5"/>
|
||||
<path d="M11.8697 4.95309C11.6784 4.7576 11.3597 4.74731 11.1578 4.93251L6.62066 9.04804L4.95244 7.91627C4.7293 7.77223 4.42116 7.77223 4.20865 7.95742C3.95363 8.1632 3.93238 8.5336 4.14489 8.78053L6.06813 10.9206C6.1 10.9515 6.13188 10.9926 6.17438 11.0132C6.53565 11.3013 7.07756 11.2498 7.37508 10.9L7.40695 10.8589L11.891 5.60128C12.0397 5.41608 12.0397 5.13828 11.8697 4.95309Z" fill="#8D99A5"/>
|
||||
</svg>
|
After Width: | Height: | Size: 784 B |
|
@ -0,0 +1,4 @@
|
|||
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M8.99902 14L8.99902 4" stroke="black" stroke-width="1.5" stroke-linecap="round"/>
|
||||
<path d="M12.5352 7.52441L8.99944 4.00012L5.46373 7.52441" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
After Width: | Height: | Size: 336 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 |
|
@ -138,9 +138,6 @@ $panel-divider-color: transparent;
|
|||
$widget-menu-bar-bg-color: $header-panel-bg-color;
|
||||
$widget-body-bg-color: rgba(141, 151, 165, 0.2);
|
||||
|
||||
// event tile lifecycle
|
||||
$event-sending-color: $text-secondary-color;
|
||||
|
||||
// event redaction
|
||||
$event-redacted-fg-color: #606060;
|
||||
$event-redacted-border-color: #000000;
|
||||
|
@ -173,6 +170,9 @@ $button-link-bg-color: transparent;
|
|||
// Toggle switch
|
||||
$togglesw-off-color: $room-highlight-color;
|
||||
|
||||
$progressbar-fg-color: $accent-color;
|
||||
$progressbar-bg-color: #21262c;
|
||||
|
||||
$visual-bell-bg-color: #800;
|
||||
|
||||
$room-warning-bg-color: $header-panel-bg-color;
|
||||
|
@ -203,6 +203,10 @@ $breadcrumb-placeholder-bg-color: #272c35;
|
|||
|
||||
$user-tile-hover-bg-color: $header-panel-bg-color;
|
||||
|
||||
$message-body-panel-bg-color: #21262c82;
|
||||
$message-body-panel-icon-bg-color: #8e99a4;
|
||||
$message-body-panel-fg-color: $primary-fg-color;
|
||||
|
||||
// Appearance tab colors
|
||||
$appearance-tab-border-color: $room-highlight-color;
|
||||
|
||||
|
|
|
@ -133,9 +133,6 @@ $panel-divider-color: $header-panel-border-color;
|
|||
$widget-menu-bar-bg-color: $header-panel-bg-color;
|
||||
$widget-body-bg-color: #1A1D23;
|
||||
|
||||
// event tile lifecycle
|
||||
$event-sending-color: $text-secondary-color;
|
||||
|
||||
// event redaction
|
||||
$event-redacted-fg-color: #606060;
|
||||
$event-redacted-border-color: #000000;
|
||||
|
@ -168,6 +165,9 @@ $button-link-bg-color: transparent;
|
|||
// Toggle switch
|
||||
$togglesw-off-color: $room-highlight-color;
|
||||
|
||||
$progressbar-fg-color: $accent-color;
|
||||
$progressbar-bg-color: #21262c;
|
||||
|
||||
$visual-bell-bg-color: #800;
|
||||
|
||||
$room-warning-bg-color: $header-panel-bg-color;
|
||||
|
@ -198,6 +198,10 @@ $breadcrumb-placeholder-bg-color: #272c35;
|
|||
|
||||
$user-tile-hover-bg-color: $header-panel-bg-color;
|
||||
|
||||
$message-body-panel-bg-color: #21262c82;
|
||||
$message-body-panel-icon-bg-color: #8e99a4;
|
||||
$message-body-panel-fg-color: $primary-fg-color;
|
||||
|
||||
// Appearance tab colors
|
||||
$appearance-tab-border-color: $room-highlight-color;
|
||||
|
||||
|
|
|
@ -223,8 +223,6 @@ $widget-body-bg-color: #fff;
|
|||
$yellow-background: #fff8e3;
|
||||
|
||||
// event tile lifecycle
|
||||
$event-encrypting-color: #abddbc;
|
||||
$event-sending-color: #ddd;
|
||||
$event-notsent-color: #f44;
|
||||
|
||||
$event-highlight-fg-color: $warning-color;
|
||||
|
@ -282,7 +280,8 @@ $togglesw-ball-color: #fff;
|
|||
$slider-selection-color: $accent-color;
|
||||
$slider-background-color: #c1c9d6;
|
||||
|
||||
$progressbar-color: #000;
|
||||
$progressbar-fg-color: $accent-color;
|
||||
$progressbar-bg-color: rgba(141, 151, 165, 0.2);
|
||||
|
||||
$room-warning-bg-color: $yellow-background;
|
||||
|
||||
|
@ -322,6 +321,10 @@ $breadcrumb-placeholder-bg-color: #e8eef5;
|
|||
|
||||
$user-tile-hover-bg-color: $header-panel-bg-color;
|
||||
|
||||
$message-body-panel-bg-color: #e3e8f082;
|
||||
$message-body-panel-icon-bg-color: #ffffff;
|
||||
$message-body-panel-fg-color: $muted-fg-color;
|
||||
|
||||
// FontSlider colors
|
||||
$appearance-tab-border-color: $input-darker-bg-color;
|
||||
|
||||
|
|
|
@ -220,8 +220,6 @@ $widget-body-bg-color: #FFF;
|
|||
$yellow-background: #fff8e3;
|
||||
|
||||
// event tile lifecycle
|
||||
$event-encrypting-color: #abddbc;
|
||||
$event-sending-color: #ddd;
|
||||
$event-notsent-color: #f44;
|
||||
|
||||
$event-highlight-fg-color: $warning-color;
|
||||
|
@ -279,7 +277,8 @@ $togglesw-ball-color: #fff;
|
|||
$slider-selection-color: $accent-color;
|
||||
$slider-background-color: #c1c9d6;
|
||||
|
||||
$progressbar-color: #000;
|
||||
$progressbar-fg-color: $accent-color;
|
||||
$progressbar-bg-color: rgba(141, 151, 165, 0.2);
|
||||
|
||||
$room-warning-bg-color: $yellow-background;
|
||||
|
||||
|
@ -320,6 +319,10 @@ $breadcrumb-placeholder-bg-color: #e8eef5;
|
|||
|
||||
$user-tile-hover-bg-color: $header-panel-bg-color;
|
||||
|
||||
$message-body-panel-bg-color: #e3e8f082;
|
||||
$message-body-panel-icon-bg-color: #ffffff;
|
||||
$message-body-panel-fg-color: $muted-fg-color;
|
||||
|
||||
// FontSlider colors
|
||||
$appearance-tab-border-color: $input-darker-bg-color;
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
#!/usr/bin/env node
|
||||
const fs = require('fs');
|
||||
const { promises: fsp } = fs;
|
||||
const path = require('path');
|
||||
const glob = require('glob');
|
||||
const util = require('util');
|
||||
|
@ -25,6 +26,8 @@ async function reskindex() {
|
|||
const header = args.h || args.header;
|
||||
|
||||
const strm = fs.createWriteStream(componentIndexTmp);
|
||||
// Wait for the open event to ensure the file descriptor is set
|
||||
await new Promise(resolve => strm.once("open", resolve));
|
||||
|
||||
if (header) {
|
||||
strm.write(fs.readFileSync(header));
|
||||
|
@ -53,14 +56,9 @@ async function reskindex() {
|
|||
|
||||
strm.write("export {components};\n");
|
||||
// Ensure the file has been fully written to disk before proceeding
|
||||
await util.promisify(fs.fsync)(strm.fd);
|
||||
await util.promisify(strm.end);
|
||||
fs.rename(componentIndexTmp, componentIndex, function(err) {
|
||||
if (err) {
|
||||
console.error("Error moving new index into place: " + err);
|
||||
} else {
|
||||
console.log('Reskindex: completed');
|
||||
}
|
||||
});
|
||||
await fsp.rename(componentIndexTmp, componentIndex);
|
||||
}
|
||||
|
||||
// Expects both arrays of file names to be sorted
|
||||
|
@ -77,9 +75,17 @@ function filesHaveChanged(files, prevFiles) {
|
|||
return false;
|
||||
}
|
||||
|
||||
// Wrapper since await at the top level is not well supported yet
|
||||
function run() {
|
||||
(async function() {
|
||||
await reskindex();
|
||||
console.log("Reskindex completed");
|
||||
})();
|
||||
}
|
||||
|
||||
// -w indicates watch mode where any FS events will trigger reskindex
|
||||
if (!args.w) {
|
||||
reskindex();
|
||||
run();
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -87,5 +93,5 @@ let watchDebouncer = null;
|
|||
chokidar.watch(path.join(componentsDir, componentJsGlob)).on('all', (event, path) => {
|
||||
if (path === componentIndex) return;
|
||||
if (watchDebouncer) clearTimeout(watchDebouncer);
|
||||
watchDebouncer = setTimeout(reskindex, 1000);
|
||||
watchDebouncer = setTimeout(run, 1000);
|
||||
});
|
||||
|
|
|
@ -14,27 +14,23 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import {getHttpUriForMxc} from "matrix-js-sdk/src/content-repo";
|
||||
import {RoomMember} from "matrix-js-sdk/src/models/room-member";
|
||||
import {User} from "matrix-js-sdk/src/models/user";
|
||||
import {Room} from "matrix-js-sdk/src/models/room";
|
||||
|
||||
import {MatrixClientPeg} from './MatrixClientPeg';
|
||||
import DMRoomMap from './utils/DMRoomMap';
|
||||
import {mediaFromMxc} from "./customisations/Media";
|
||||
|
||||
export type ResizeMethod = "crop" | "scale";
|
||||
|
||||
// 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) {
|
||||
let url: string;
|
||||
if (member && member.getAvatarUrl) {
|
||||
url = member.getAvatarUrl(
|
||||
MatrixClientPeg.get().getHomeserverUrl(),
|
||||
if (member?.getMxcAvatarUrl()) {
|
||||
url = mediaFromMxc(member.getMxcAvatarUrl()).getThumbnailOfSourceHttp(
|
||||
Math.floor(width * window.devicePixelRatio),
|
||||
Math.floor(height * window.devicePixelRatio),
|
||||
resizeMethod,
|
||||
false,
|
||||
false,
|
||||
);
|
||||
}
|
||||
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) {
|
||||
const url = getHttpUriForMxc(
|
||||
MatrixClientPeg.get().getHomeserverUrl(), user.avatarUrl,
|
||||
if (!user.avatarUrl) return null;
|
||||
return mediaFromMxc(user.avatarUrl).getThumbnailOfSourceHttp(
|
||||
Math.floor(width * window.devicePixelRatio),
|
||||
Math.floor(height * window.devicePixelRatio),
|
||||
resizeMethod,
|
||||
);
|
||||
if (!url || url.length === 0) {
|
||||
return null;
|
||||
}
|
||||
return url;
|
||||
}
|
||||
|
||||
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) {
|
||||
if (!room) return null; // null-guard
|
||||
|
||||
const explicitRoomAvatar = room.getAvatarUrl(
|
||||
MatrixClientPeg.get().getHomeserverUrl(),
|
||||
width,
|
||||
height,
|
||||
resizeMethod,
|
||||
false,
|
||||
);
|
||||
if (explicitRoomAvatar) {
|
||||
return explicitRoomAvatar;
|
||||
if (room.getMxcAvatarUrl()) {
|
||||
return mediaFromMxc(room.getMxcAvatarUrl()).getThumbnailOfSourceHttp(width, height, resizeMethod);
|
||||
}
|
||||
|
||||
// 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)
|
||||
otherMember = room.getAvatarFallbackMember();
|
||||
}
|
||||
if (otherMember) {
|
||||
return otherMember.getAvatarUrl(
|
||||
MatrixClientPeg.get().getHomeserverUrl(),
|
||||
width,
|
||||
height,
|
||||
resizeMethod,
|
||||
false,
|
||||
);
|
||||
if (otherMember?.getMxcAvatarUrl()) {
|
||||
return mediaFromMxc(otherMember.getMxcAvatarUrl()).getThumbnailOfSourceHttp(width, height, resizeMethod);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
|
|
@ -630,7 +630,7 @@ export default class CallHandler {
|
|||
logger.debug("Mapped real room " + roomId + " to room ID " + mappedRoomId);
|
||||
|
||||
const timeUntilTurnCresExpire = MatrixClientPeg.get().getTurnServersExpiry() - Date.now();
|
||||
console.log("Current turn creds expire in " + timeUntilTurnCresExpire + " seconds");
|
||||
console.log("Current turn creds expire in " + timeUntilTurnCresExpire + " ms");
|
||||
const call = createNewMatrixCall(MatrixClientPeg.get(), mappedRoomId);
|
||||
|
||||
this.calls.set(roomId, call);
|
||||
|
@ -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
|
||||
// the hangup event away)
|
||||
break;
|
||||
case 'hangup_all':
|
||||
for (const call of this.calls.values()) {
|
||||
call.hangup(CallErrorCode.UserHangup, false);
|
||||
}
|
||||
break;
|
||||
case 'answer': {
|
||||
if (!this.calls.has(payload.room_id)) {
|
||||
return; // no call to answer
|
||||
|
|
|
@ -32,6 +32,14 @@ import Spinner from "./components/views/elements/Spinner";
|
|||
import "blueimp-canvas-to-blob";
|
||||
import { Action } from "./dispatcher/actions";
|
||||
import CountlyAnalytics from "./CountlyAnalytics";
|
||||
import {
|
||||
UploadCanceledPayload,
|
||||
UploadErrorPayload,
|
||||
UploadFinishedPayload,
|
||||
UploadProgressPayload,
|
||||
UploadStartedPayload,
|
||||
} from "./dispatcher/payloads/UploadPayload";
|
||||
import {IUpload} from "./models/IUpload";
|
||||
|
||||
const MAX_WIDTH = 800;
|
||||
const MAX_HEIGHT = 600;
|
||||
|
@ -44,15 +52,6 @@ export class UploadCanceledError extends Error {}
|
|||
|
||||
type ThumbnailableElement = HTMLImageElement | HTMLVideoElement;
|
||||
|
||||
interface IUpload {
|
||||
fileName: string;
|
||||
roomId: string;
|
||||
total: number;
|
||||
loaded: number;
|
||||
promise: Promise<any>;
|
||||
canceled?: boolean;
|
||||
}
|
||||
|
||||
interface IMediaConfig {
|
||||
"m.upload.size"?: number;
|
||||
}
|
||||
|
@ -478,7 +477,7 @@ export default class ContentMessages {
|
|||
if (upload) {
|
||||
upload.canceled = true;
|
||||
MatrixClientPeg.get().cancelUpload(upload.promise);
|
||||
dis.dispatch({action: 'upload_canceled', upload});
|
||||
dis.dispatch<UploadCanceledPayload>({action: Action.UploadCanceled, upload});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -539,7 +538,7 @@ export default class ContentMessages {
|
|||
promise: prom,
|
||||
};
|
||||
this.inprogress.push(upload);
|
||||
dis.dispatch({action: 'upload_started'});
|
||||
dis.dispatch<UploadStartedPayload>({action: Action.UploadStarted, upload});
|
||||
|
||||
// Focus the composer view
|
||||
dis.fire(Action.FocusComposer);
|
||||
|
@ -547,7 +546,7 @@ export default class ContentMessages {
|
|||
function onProgress(ev) {
|
||||
upload.total = ev.total;
|
||||
upload.loaded = ev.loaded;
|
||||
dis.dispatch({action: 'upload_progress', upload: upload});
|
||||
dis.dispatch<UploadProgressPayload>({action: Action.UploadProgress, upload});
|
||||
}
|
||||
|
||||
let error;
|
||||
|
@ -601,9 +600,9 @@ export default class ContentMessages {
|
|||
if (error && error.http_status === 413) {
|
||||
this.mediaConfig = null;
|
||||
}
|
||||
dis.dispatch({action: 'upload_failed', upload, error});
|
||||
dis.dispatch<UploadErrorPayload>({action: Action.UploadFailed, upload, error});
|
||||
} else {
|
||||
dis.dispatch({action: 'upload_finished', upload});
|
||||
dis.dispatch<UploadFinishedPayload>({action: Action.UploadFinished, upload});
|
||||
dis.dispatch({action: 'message_sent'});
|
||||
}
|
||||
});
|
||||
|
|
|
@ -32,10 +32,10 @@ import { AllHtmlEntities } from 'html-entities';
|
|||
import SettingsStore from './settings/SettingsStore';
|
||||
import cheerio from 'cheerio';
|
||||
|
||||
import {MatrixClientPeg} from './MatrixClientPeg';
|
||||
import {tryTransformPermalinkToLocalHref} from "./utils/permalinks/Permalinks";
|
||||
import {SHORTCODE_TO_EMOJI, getEmojiFromUnicode} from "./emoji";
|
||||
import ReplyThread from "./components/views/elements/ReplyThread";
|
||||
import {mediaFromMxc} from "./customisations/Media";
|
||||
|
||||
linkifyMatrix(linkify);
|
||||
|
||||
|
@ -181,11 +181,9 @@ const transformTags: IExtendedSanitizeOptions["transformTags"] = { // custom to
|
|||
if (!attribs.src || !attribs.src.startsWith('mxc://') || !SettingsStore.getValue("showImages")) {
|
||||
return { tagName, attribs: {}};
|
||||
}
|
||||
attribs.src = MatrixClientPeg.get().mxcUrlToHttp(
|
||||
attribs.src,
|
||||
attribs.width || 800,
|
||||
attribs.height || 600,
|
||||
);
|
||||
const width = Number(attribs.width) || 800;
|
||||
const height = Number(attribs.height) || 600;
|
||||
attribs.src = mediaFromMxc(attribs.src).getThumbnailOfSourceHttp(width, height);
|
||||
return { tagName, attribs };
|
||||
},
|
||||
'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',
|
||||
'nl', 'li', 'b', 'i', 'u', 'strong', 'em', 'strike', 'code', 'hr', 'br', 'div',
|
||||
'table', 'thead', 'caption', 'tbody', 'tr', 'th', 'td', 'pre', 'span', 'img',
|
||||
'details', 'summary',
|
||||
],
|
||||
allowedAttributes: {
|
||||
// custom ones first:
|
||||
|
|
|
@ -0,0 +1,55 @@
|
|||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
import { ClientWidgetApi } from "matrix-widget-api";
|
||||
import { MatrixClientPeg } from "./MatrixClientPeg";
|
||||
import SdkConfig from "./SdkConfig";
|
||||
import { ElementWidgetActions } from "./stores/widgets/ElementWidgetActions";
|
||||
|
||||
export function getConfigLivestreamUrl() {
|
||||
return SdkConfig.get()["audioStreamUrl"];
|
||||
}
|
||||
|
||||
// Dummy rtmp URL used to signal that we want a special audio-only stream
|
||||
const AUDIOSTREAM_DUMMY_URL = 'rtmp://audiostream.dummy/';
|
||||
|
||||
async function createLiveStream(roomId: string) {
|
||||
const openIdToken = await MatrixClientPeg.get().getOpenIdToken();
|
||||
|
||||
const url = getConfigLivestreamUrl() + "/createStream";
|
||||
|
||||
const response = await window.fetch(url, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
room_id: roomId,
|
||||
openid_token: openIdToken,
|
||||
}),
|
||||
});
|
||||
|
||||
const respBody = await response.json();
|
||||
return respBody['stream_id'];
|
||||
}
|
||||
|
||||
export async function startJitsiAudioLivestream(widgetMessaging: ClientWidgetApi, roomId: string) {
|
||||
const streamId = await createLiveStream(roomId);
|
||||
|
||||
await widgetMessaging.transport.send(ElementWidgetActions.StartLiveStream, {
|
||||
rtmpStreamKey: AUDIOSTREAM_DUMMY_URL + streamId,
|
||||
});
|
||||
}
|
|
@ -261,7 +261,7 @@ class _MatrixClientPeg implements IMatrixClientPeg {
|
|||
}
|
||||
|
||||
public getHomeserverName(): string {
|
||||
const matches = /^@.+:(.+)$/.exec(this.matrixClient.credentials.userId);
|
||||
const matches = /^@[^:]+:(.+)$/.exec(this.matrixClient.credentials.userId);
|
||||
if (matches === null || matches.length < 1) {
|
||||
throw new Error("Failed to derive homeserver name from user ID!");
|
||||
}
|
||||
|
|
|
@ -36,6 +36,7 @@ import {SettingLevel} from "./settings/SettingLevel";
|
|||
import {isPushNotifyDisabled} from "./settings/controllers/NotificationControllers";
|
||||
import RoomViewStore from "./stores/RoomViewStore";
|
||||
import UserActivity from "./UserActivity";
|
||||
import {mediaFromMxc} from "./customisations/Media";
|
||||
|
||||
/*
|
||||
* Dispatches:
|
||||
|
@ -150,7 +151,7 @@ export const Notifier = {
|
|||
// Ideally in here we could use MSC1310 to detect the type of file, and reject it.
|
||||
|
||||
return {
|
||||
url: MatrixClientPeg.get().mxcUrlToHttp(content.url),
|
||||
url: mediaFromMxc(content.url).srcHttp,
|
||||
name: content.name,
|
||||
type: content.type,
|
||||
size: content.size,
|
||||
|
|
|
@ -99,9 +99,9 @@ class Presence {
|
|||
|
||||
try {
|
||||
await MatrixClientPeg.get().setPresence(this.state);
|
||||
console.info("Presence: %s", newState);
|
||||
console.info("Presence:", newState);
|
||||
} catch (err) {
|
||||
console.error("Failed to set presence: %s", err);
|
||||
console.error("Failed to set presence:", err);
|
||||
this.state = oldState;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,7 +22,7 @@ import MultiInviter from './utils/MultiInviter';
|
|||
import Modal from './Modal';
|
||||
import * as sdk from './';
|
||||
import { _t } from './languageHandler';
|
||||
import InviteDialog, {KIND_DM, KIND_INVITE, KIND_SPACE_INVITE} from "./components/views/dialogs/InviteDialog";
|
||||
import InviteDialog, {KIND_DM, KIND_INVITE} from "./components/views/dialogs/InviteDialog";
|
||||
import CommunityPrototypeInviteDialog from "./components/views/dialogs/CommunityPrototypeInviteDialog";
|
||||
import {CommunityPrototypeStore} from "./stores/CommunityPrototypeStore";
|
||||
|
||||
|
@ -50,11 +50,10 @@ export function showStartChatInviteDialog(initialText) {
|
|||
}
|
||||
|
||||
export function showRoomInviteDialog(roomId) {
|
||||
const isSpace = MatrixClientPeg.get()?.getRoom(roomId)?.isSpaceRoom();
|
||||
// This dialog handles the room creation internally - we don't need to worry about it.
|
||||
Modal.createTrackedDialog(
|
||||
"Invite Users", isSpace ? "Space" : "Room", InviteDialog, {
|
||||
kind: isSpace ? KIND_SPACE_INVITE : KIND_INVITE,
|
||||
"Invite Users", "", InviteDialog, {
|
||||
kind: KIND_INVITE,
|
||||
roomId,
|
||||
},
|
||||
/*className=*/null, /*isPriority=*/false, /*isStatic=*/true,
|
||||
|
|
|
@ -23,7 +23,7 @@ class Skinner {
|
|||
if (!name) throw new Error(`Invalid component name: ${name}`);
|
||||
if (this.components === null) {
|
||||
throw new Error(
|
||||
"Attempted to get a component before a skin has been loaded."+
|
||||
`Attempted to get a component (${name}) before a skin has been loaded.`+
|
||||
" This is probably because either:"+
|
||||
" a) Your app has not called sdk.loadSkin(), or"+
|
||||
" b) A component has called getComponent at the root level",
|
||||
|
|
|
@ -441,15 +441,14 @@ export const Commands = [
|
|||
}),
|
||||
new Command({
|
||||
command: 'invite',
|
||||
args: '<user-id>',
|
||||
args: '<user-id> [<reason>]',
|
||||
description: _td('Invites user with given id to current room'),
|
||||
runFn: function(roomId, args) {
|
||||
if (args) {
|
||||
const matches = args.match(/^(\S+)$/);
|
||||
if (matches) {
|
||||
const [address, reason] = args.split(/\s+(.+)/);
|
||||
if (address) {
|
||||
// We use a MultiInviter to re-use the invite logic, even though
|
||||
// we're only inviting one user.
|
||||
const address = matches[1];
|
||||
// If we need an identity server but don't have one, things
|
||||
// get a bit more complex here, but we try to show something
|
||||
// meaningful.
|
||||
|
@ -490,7 +489,7 @@ export const Commands = [
|
|||
}
|
||||
const inviter = new MultiInviter(roomId);
|
||||
return success(prom.then(() => {
|
||||
return inviter.invite([address]);
|
||||
return inviter.invite([address], reason);
|
||||
}).then(() => {
|
||||
if (inviter.getCompletionState(address) !== "invited") {
|
||||
throw new Error(inviter.getErrorText(address));
|
||||
|
|
|
@ -37,7 +37,7 @@ export default class VoipUserMapper {
|
|||
return results[0].userid;
|
||||
}
|
||||
|
||||
public async getOrCreateVirtualRoomForRoom(roomId: string):Promise<string> {
|
||||
public async getOrCreateVirtualRoomForRoom(roomId: string): Promise<string> {
|
||||
const userId = DMRoomMap.shared().getUserIdForRoomId(roomId);
|
||||
if (!userId) return null;
|
||||
|
||||
|
@ -52,7 +52,7 @@ export default class VoipUserMapper {
|
|||
return virtualRoomId;
|
||||
}
|
||||
|
||||
public nativeRoomForVirtualRoom(roomId: string):string {
|
||||
public nativeRoomForVirtualRoom(roomId: string): string {
|
||||
const virtualRoom = MatrixClientPeg.get().getRoom(roomId);
|
||||
if (!virtualRoom) return null;
|
||||
const virtualRoomEvent = virtualRoom.getAccountData(VIRTUAL_ROOM_EVENT_TYPE);
|
||||
|
@ -60,7 +60,7 @@ export default class VoipUserMapper {
|
|||
return virtualRoomEvent.getContent()['native_room'] || null;
|
||||
}
|
||||
|
||||
public isVirtualRoom(room: Room):boolean {
|
||||
public isVirtualRoom(room: Room): boolean {
|
||||
if (this.nativeRoomForVirtualRoom(room.roomId)) return true;
|
||||
|
||||
if (this.virtualRoomIdCache.has(room.roomId)) return true;
|
||||
|
@ -79,6 +79,8 @@ export default class VoipUserMapper {
|
|||
}
|
||||
|
||||
public async onNewInvitedRoom(invitedRoom: Room) {
|
||||
if (!CallHandler.sharedInstance().getSupportsVirtualRooms()) return;
|
||||
|
||||
const inviterId = invitedRoom.getDMInviter();
|
||||
console.log(`Checking virtual-ness of room ID ${invitedRoom.roomId}, invited by ${inviterId}`);
|
||||
const result = await CallHandler.sharedInstance().sipNativeLookup(inviterId);
|
||||
|
|
|
@ -27,6 +27,7 @@ import {sortBy} from "lodash";
|
|||
import {makeGroupPermalink} from "../utils/permalinks/Permalinks";
|
||||
import {ICompletion, ISelectionRange} from "./Autocompleter";
|
||||
import FlairStore from "../stores/FlairStore";
|
||||
import {mediaFromMxc} from "../customisations/Media";
|
||||
|
||||
const COMMUNITY_REGEX = /\B\+\S*/g;
|
||||
|
||||
|
@ -95,7 +96,7 @@ export default class CommunityProvider extends AutocompleteProvider {
|
|||
name={name || groupId}
|
||||
width={24}
|
||||
height={24}
|
||||
url={avatarUrl ? cli.mxcUrlToHttp(avatarUrl, 24, 24) : null} />
|
||||
url={avatarUrl ? mediaFromMxc(avatarUrl).getSquareThumbnailHttp(24) : null} />
|
||||
</PillCompletion>
|
||||
),
|
||||
range,
|
||||
|
|
|
@ -22,6 +22,7 @@ import classNames from "classnames";
|
|||
|
||||
import {Key} from "../../Keyboard";
|
||||
import {Writeable} from "../../@types/common";
|
||||
import {replaceableComponent} from "../../utils/replaceableComponent";
|
||||
|
||||
// Shamelessly ripped off Modal.js. There's probably a better way
|
||||
// of doing reusable widgets like dialog boxes & menus where we go and
|
||||
|
@ -91,6 +92,7 @@ interface IState {
|
|||
// Generic ContextMenu Portal wrapper
|
||||
// all options inside the menu should be of role=menuitem/menuitemcheckbox/menuitemradiobutton and have tabIndex={-1}
|
||||
// this will allow the ContextMenu to manage its own focus using arrow keys as per the ARIA guidelines.
|
||||
@replaceableComponent("structures.ContextMenu")
|
||||
export class ContextMenu extends React.PureComponent<IProps, IState> {
|
||||
private initialFocus: HTMLElement;
|
||||
|
||||
|
@ -467,6 +469,7 @@ export const useContextMenu = <T extends any = HTMLElement>(): ContextMenuTuple<
|
|||
return [isOpen, button, open, close, setIsOpen];
|
||||
};
|
||||
|
||||
@replaceableComponent("structures.LegacyContextMenu")
|
||||
export default class LegacyContextMenu extends ContextMenu {
|
||||
render() {
|
||||
return this.renderMenu(false);
|
||||
|
|
|
@ -21,7 +21,9 @@ import * as sdk from '../../index';
|
|||
import dis from '../../dispatcher/dispatcher';
|
||||
import classNames from 'classnames';
|
||||
import * as FormattingUtils from '../../utils/FormattingUtils';
|
||||
import {replaceableComponent} from "../../utils/replaceableComponent";
|
||||
|
||||
@replaceableComponent("structures.CustomRoomTagPanel")
|
||||
class CustomRoomTagPanel extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
|
|
@ -16,8 +16,6 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import request from 'browser-request';
|
||||
|
|
|
@ -26,10 +26,12 @@ import { _t } from '../../languageHandler';
|
|||
import BaseCard from "../views/right_panel/BaseCard";
|
||||
import {RightPanelPhases} from "../../stores/RightPanelStorePhases";
|
||||
import DesktopBuildsNotice, {WarningKind} from "../views/elements/DesktopBuildsNotice";
|
||||
import {replaceableComponent} from "../../utils/replaceableComponent";
|
||||
|
||||
/*
|
||||
* Component which shows the filtered file using a TimelinePanel
|
||||
*/
|
||||
@replaceableComponent("structures.FilePanel")
|
||||
class FilePanel extends React.Component {
|
||||
static propTypes = {
|
||||
roomId: PropTypes.string.isRequired,
|
||||
|
|
|
@ -16,7 +16,9 @@ limitations under the License.
|
|||
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {replaceableComponent} from "../../utils/replaceableComponent";
|
||||
|
||||
@replaceableComponent("structures.GenericErrorPage")
|
||||
export default class GenericErrorPage extends React.PureComponent {
|
||||
static propTypes = {
|
||||
title: PropTypes.object.isRequired, // jsx for title
|
||||
|
|
|
@ -30,7 +30,9 @@ import MatrixClientContext from "../../contexts/MatrixClientContext";
|
|||
import AutoHideScrollbar from "./AutoHideScrollbar";
|
||||
import SettingsStore from "../../settings/SettingsStore";
|
||||
import UserTagTile from "../views/elements/UserTagTile";
|
||||
import {replaceableComponent} from "../../utils/replaceableComponent";
|
||||
|
||||
@replaceableComponent("structures.GroupFilterPanel")
|
||||
class GroupFilterPanel extends React.Component {
|
||||
static contextType = MatrixClientContext;
|
||||
|
||||
|
|
|
@ -39,6 +39,8 @@ import {Group} from "matrix-js-sdk";
|
|||
import {allSettled, sleep} from "../../utils/promise";
|
||||
import RightPanelStore from "../../stores/RightPanelStore";
|
||||
import AutoHideScrollbar from "./AutoHideScrollbar";
|
||||
import {mediaFromMxc} from "../../customisations/Media";
|
||||
import {replaceableComponent} from "../../utils/replaceableComponent";
|
||||
|
||||
const LONG_DESC_PLACEHOLDER = _td(
|
||||
`<h1>HTML for your community's page</h1>
|
||||
|
@ -367,8 +369,7 @@ class FeaturedUser extends React.Component {
|
|||
|
||||
const permalink = makeUserPermalink(this.props.summaryInfo.user_id);
|
||||
const userNameNode = <a href={permalink} onClick={this.onClick}>{ name }</a>;
|
||||
const httpUrl = MatrixClientPeg.get()
|
||||
.mxcUrlToHttp(this.props.summaryInfo.avatar_url, 64, 64);
|
||||
const httpUrl = mediaFromMxc(this.props.summaryInfo.avatar_url).getSquareThumbnailHttp(64);
|
||||
|
||||
const deleteButton = this.props.editing ?
|
||||
<img
|
||||
|
@ -391,6 +392,7 @@ class FeaturedUser extends React.Component {
|
|||
const GROUP_JOINPOLICY_OPEN = "open";
|
||||
const GROUP_JOINPOLICY_INVITE = "invite";
|
||||
|
||||
@replaceableComponent("structures.GroupView")
|
||||
export default class GroupView extends React.Component {
|
||||
static propTypes = {
|
||||
groupId: PropTypes.string.isRequired,
|
||||
|
@ -979,10 +981,9 @@ export default class GroupView extends React.Component {
|
|||
<Spinner />
|
||||
</div>;
|
||||
}
|
||||
const httpInviterAvatar = this.state.inviterProfile ?
|
||||
this._matrixClient.mxcUrlToHttp(
|
||||
this.state.inviterProfile.avatarUrl, 36, 36,
|
||||
) : null;
|
||||
const httpInviterAvatar = this.state.inviterProfile
|
||||
? mediaFromMxc(this.state.inviterProfile.avatarUrl).getSquareThumbnailHttp(36)
|
||||
: null;
|
||||
|
||||
const inviter = group.inviter || {};
|
||||
let inviterName = inviter.userId;
|
||||
|
|
|
@ -22,11 +22,13 @@ import {
|
|||
import { _t } from "../../languageHandler";
|
||||
import { HostSignupStore } from "../../stores/HostSignupStore";
|
||||
import SdkConfig from "../../SdkConfig";
|
||||
import {replaceableComponent} from "../../utils/replaceableComponent";
|
||||
|
||||
interface IProps {}
|
||||
|
||||
interface IState {}
|
||||
|
||||
@replaceableComponent("structures.HostSignupAction")
|
||||
export default class HostSignupAction extends React.PureComponent<IProps, IState> {
|
||||
private openDialog = async () => {
|
||||
await HostSignupStore.instance.setHostSignupActive(true);
|
||||
|
|
|
@ -17,7 +17,9 @@ limitations under the License.
|
|||
import React from "react";
|
||||
import PropTypes from "prop-types";
|
||||
import AutoHideScrollbar from "./AutoHideScrollbar";
|
||||
import {replaceableComponent} from "../../utils/replaceableComponent";
|
||||
|
||||
@replaceableComponent("structures.IndicatorScrollbar")
|
||||
export default class IndicatorScrollbar extends React.Component {
|
||||
static propTypes = {
|
||||
// If true, the scrollbar will append mx_IndicatorScrollbar_leftOverflowIndicator
|
||||
|
|
|
@ -22,9 +22,11 @@ import PropTypes from 'prop-types';
|
|||
import getEntryComponentForLoginType from '../views/auth/InteractiveAuthEntryComponents';
|
||||
|
||||
import * as sdk from '../../index';
|
||||
import {replaceableComponent} from "../../utils/replaceableComponent";
|
||||
|
||||
export const ERROR_USER_CANCELLED = new Error("User cancelled auth session");
|
||||
|
||||
@replaceableComponent("structures.InteractiveAuthComponent")
|
||||
export default class InteractiveAuthComponent extends React.Component {
|
||||
static propTypes = {
|
||||
// matrix client to use for UI auth requests
|
||||
|
|
|
@ -36,9 +36,10 @@ import {Key} from "../../Keyboard";
|
|||
import IndicatorScrollbar from "../structures/IndicatorScrollbar";
|
||||
import AccessibleTooltipButton from "../views/elements/AccessibleTooltipButton";
|
||||
import { OwnProfileStore } from "../../stores/OwnProfileStore";
|
||||
import { MatrixClientPeg } from "../../MatrixClientPeg";
|
||||
import RoomListNumResults from "../views/rooms/RoomListNumResults";
|
||||
import LeftPanelWidget from "./LeftPanelWidget";
|
||||
import {replaceableComponent} from "../../utils/replaceableComponent";
|
||||
import {mediaFromMxc} from "../../customisations/Media";
|
||||
|
||||
interface IProps {
|
||||
isMinimized: boolean;
|
||||
|
@ -59,6 +60,7 @@ const cssClasses = [
|
|||
"mx_RoomSublist_showNButton",
|
||||
];
|
||||
|
||||
@replaceableComponent("structures.LeftPanel")
|
||||
export default class LeftPanel extends React.Component<IProps, IState> {
|
||||
private listContainerRef: React.RefObject<HTMLDivElement> = createRef();
|
||||
private groupFilterPanelWatcherRef: string;
|
||||
|
@ -118,7 +120,7 @@ export default class LeftPanel extends React.Component<IProps, IState> {
|
|||
let avatarUrl = OwnProfileStore.instance.getHttpAvatarUrl(avatarSize);
|
||||
const settingBgMxc = SettingsStore.getValue("RoomList.backgroundImage");
|
||||
if (settingBgMxc) {
|
||||
avatarUrl = MatrixClientPeg.get().mxcUrlToHttp(settingBgMxc, avatarSize, avatarSize);
|
||||
avatarUrl = mediaFromMxc(settingBgMxc).getSquareThumbnailHttp(avatarSize);
|
||||
}
|
||||
|
||||
const avatarUrlProp = `url(${avatarUrl})`;
|
||||
|
|
|
@ -57,6 +57,7 @@ import { ICollapseConfig } from "../../resizer/distributors/collapse";
|
|||
import HostSignupContainer from '../views/host_signup/HostSignupContainer';
|
||||
import { IOpts } from "../../createRoom";
|
||||
import SpacePanel from "../views/spaces/SpacePanel";
|
||||
import {replaceableComponent} from "../../utils/replaceableComponent";
|
||||
|
||||
// We need to fetch each pinned message individually (if we don't already have it)
|
||||
// so each pinned message may trigger a request. Limit the number per room for sanity.
|
||||
|
@ -129,6 +130,7 @@ interface IState {
|
|||
*
|
||||
* Components mounted below us can access the matrix client via the react context.
|
||||
*/
|
||||
@replaceableComponent("structures.LoggedInView")
|
||||
class LoggedInView extends React.Component<IProps, IState> {
|
||||
static displayName = 'LoggedInView';
|
||||
|
||||
|
|
|
@ -17,7 +17,9 @@ limitations under the License.
|
|||
|
||||
import React from 'react';
|
||||
import { Resizable } from 're-resizable';
|
||||
import {replaceableComponent} from "../../utils/replaceableComponent";
|
||||
|
||||
@replaceableComponent("structures.MainSplit")
|
||||
export default class MainSplit extends React.Component {
|
||||
_onResizeStart = () => {
|
||||
this.props.resizeNotifier.startResizing();
|
||||
|
|
|
@ -84,6 +84,7 @@ import DialPadModal from "../views/voip/DialPadModal";
|
|||
import { showToast as showMobileGuideToast } from '../../toasts/MobileGuideToast';
|
||||
import SpaceStore from "../../stores/SpaceStore";
|
||||
import SpaceRoomDirectory from "./SpaceRoomDirectory";
|
||||
import {replaceableComponent} from "../../utils/replaceableComponent";
|
||||
|
||||
/** constants for MatrixChat.state.view */
|
||||
export enum Views {
|
||||
|
@ -208,6 +209,7 @@ interface IState {
|
|||
roomJustCreatedOpts?: IOpts;
|
||||
}
|
||||
|
||||
@replaceableComponent("structures.MatrixChat")
|
||||
export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||
static displayName = "MatrixChat";
|
||||
|
||||
|
@ -580,6 +582,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
|||
}
|
||||
break;
|
||||
case 'logout':
|
||||
dis.dispatch({action: "hangup_all"});
|
||||
Lifecycle.logout();
|
||||
break;
|
||||
case 'require_registration':
|
||||
|
|
|
@ -34,6 +34,7 @@ import {textForEvent} from "../../TextForEvent";
|
|||
import IRCTimelineProfileResizer from "../views/elements/IRCTimelineProfileResizer";
|
||||
import DMRoomMap from "../../utils/DMRoomMap";
|
||||
import NewRoomIntro from "../views/rooms/NewRoomIntro";
|
||||
import {replaceableComponent} from "../../utils/replaceableComponent";
|
||||
|
||||
const CONTINUATION_MAX_INTERVAL = 5 * 60 * 1000; // 5 minutes
|
||||
const continuedTypes = ['m.sticker', 'm.room.message'];
|
||||
|
@ -66,6 +67,7 @@ const isMembershipChange = (e) => e.getType() === 'm.room.member' || e.getType()
|
|||
|
||||
/* (almost) stateless UI component which builds the event tiles in the room timeline.
|
||||
*/
|
||||
@replaceableComponent("structures.MessagePanel")
|
||||
export default class MessagePanel extends React.Component {
|
||||
static propTypes = {
|
||||
// true to give the component a 'display: none' style.
|
||||
|
@ -498,6 +500,9 @@ export default class MessagePanel extends React.Component {
|
|||
|
||||
let prevEvent = null; // the last event we showed
|
||||
|
||||
// Note: the EventTile might still render a "sent/sending receipt" independent of
|
||||
// this information. When not providing read receipt information, the tile is likely
|
||||
// to assume that sent receipts are to be shown more often.
|
||||
this._readReceiptsByEvent = {};
|
||||
if (this.props.showReadReceipts) {
|
||||
this._readReceiptsByEvent = this._getReadReceiptsByShownEvent();
|
||||
|
@ -534,10 +539,17 @@ export default class MessagePanel extends React.Component {
|
|||
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,
|
||||
// otherwise react will auto-generate keys and we will end up
|
||||
// replacing all of the DOM elements every time we paginate.
|
||||
ret.push(...this._getTilesForEvent(prevEvent, mxEv, last, nextEvent));
|
||||
ret.push(...this._getTilesForEvent(prevEvent, mxEv, last, nextEvent, nextTile));
|
||||
prevEvent = mxEv;
|
||||
}
|
||||
|
||||
|
@ -553,7 +565,7 @@ export default class MessagePanel extends React.Component {
|
|||
return ret;
|
||||
}
|
||||
|
||||
_getTilesForEvent(prevEvent, mxEv, last, nextEvent) {
|
||||
_getTilesForEvent(prevEvent, mxEv, last, nextEvent, nextEventWithTile) {
|
||||
const TileErrorBoundary = sdk.getComponent('messages.TileErrorBoundary');
|
||||
const EventTile = sdk.getComponent('rooms.EventTile');
|
||||
const DateSeparator = sdk.getComponent('messages.DateSeparator');
|
||||
|
@ -595,6 +607,30 @@ export default class MessagePanel extends React.Component {
|
|||
|
||||
const readReceipts = this._readReceiptsByEvent[eventId];
|
||||
|
||||
let isLastSuccessful = false;
|
||||
const isSentState = s => !s || s === 'sent';
|
||||
const isSent = isSentState(mxEv.getAssociatedStatus());
|
||||
const hasNextEvent = nextEvent && this._shouldShowEvent(nextEvent);
|
||||
if (!hasNextEvent && isSent) {
|
||||
isLastSuccessful = true;
|
||||
} else if (hasNextEvent && isSent && !isSentState(nextEvent.getAssociatedStatus())) {
|
||||
isLastSuccessful = true;
|
||||
}
|
||||
|
||||
// This is a bit nuanced, but if our next event is hidden but a future event is not
|
||||
// hidden then we're not the last successful.
|
||||
if (
|
||||
nextEventWithTile &&
|
||||
nextEventWithTile !== nextEvent &&
|
||||
isSentState(nextEventWithTile.getAssociatedStatus())
|
||||
) {
|
||||
isLastSuccessful = false;
|
||||
}
|
||||
|
||||
// We only want to consider "last successful" if the event is sent by us, otherwise of course
|
||||
// it's successful: we received it.
|
||||
isLastSuccessful = isLastSuccessful && mxEv.getSender() === MatrixClientPeg.get().getUserId();
|
||||
|
||||
// use txnId as key if available so that we don't remount during sending
|
||||
ret.push(
|
||||
<li
|
||||
|
@ -620,6 +656,7 @@ export default class MessagePanel extends React.Component {
|
|||
permalinkCreator={this.props.permalinkCreator}
|
||||
last={last}
|
||||
lastInSection={willWantDateSeparator}
|
||||
lastSuccessful={isLastSuccessful}
|
||||
isSelectedEvent={highlight}
|
||||
getRelationsForEvent={this.props.getRelationsForEvent}
|
||||
showReactions={this.props.showReactions}
|
||||
|
|
|
@ -24,7 +24,9 @@ import dis from '../../dispatcher/dispatcher';
|
|||
import AccessibleButton from '../views/elements/AccessibleButton';
|
||||
import MatrixClientContext from "../../contexts/MatrixClientContext";
|
||||
import AutoHideScrollbar from "./AutoHideScrollbar";
|
||||
import {replaceableComponent} from "../../utils/replaceableComponent";
|
||||
|
||||
@replaceableComponent("structures.MyGroups")
|
||||
export default class MyGroups extends React.Component {
|
||||
static contextType = MatrixClientContext;
|
||||
|
||||
|
|
|
@ -18,6 +18,7 @@ import * as React from "react";
|
|||
import { ComponentClass } from "../../@types/common";
|
||||
import NonUrgentToastStore from "../../stores/NonUrgentToastStore";
|
||||
import { UPDATE_EVENT } from "../../stores/AsyncStore";
|
||||
import {replaceableComponent} from "../../utils/replaceableComponent";
|
||||
|
||||
interface IProps {
|
||||
}
|
||||
|
@ -26,6 +27,7 @@ interface IState {
|
|||
toasts: ComponentClass[],
|
||||
}
|
||||
|
||||
@replaceableComponent("structures.NonUrgentToastContainer")
|
||||
export default class NonUrgentToastContainer extends React.PureComponent<IProps, IState> {
|
||||
public constructor(props, context) {
|
||||
super(props, context);
|
||||
|
|
|
@ -23,10 +23,12 @@ import { _t } from '../../languageHandler';
|
|||
import {MatrixClientPeg} from "../../MatrixClientPeg";
|
||||
import * as sdk from "../../index";
|
||||
import BaseCard from "../views/right_panel/BaseCard";
|
||||
import {replaceableComponent} from "../../utils/replaceableComponent";
|
||||
|
||||
/*
|
||||
* Component which shows the global notification list using a TimelinePanel
|
||||
*/
|
||||
@replaceableComponent("structures.NotificationPanel")
|
||||
class NotificationPanel extends React.Component {
|
||||
static propTypes = {
|
||||
onClose: PropTypes.func.isRequired,
|
||||
|
|
|
@ -34,7 +34,9 @@ import MatrixClientContext from "../../contexts/MatrixClientContext";
|
|||
import {Action} from "../../dispatcher/actions";
|
||||
import RoomSummaryCard from "../views/right_panel/RoomSummaryCard";
|
||||
import WidgetCard from "../views/right_panel/WidgetCard";
|
||||
import {replaceableComponent} from "../../utils/replaceableComponent";
|
||||
|
||||
@replaceableComponent("structures.RightPanel")
|
||||
export default class RightPanel extends React.Component {
|
||||
static get propTypes() {
|
||||
return {
|
||||
|
|
|
@ -27,13 +27,14 @@ import { _t } from '../../languageHandler';
|
|||
import SdkConfig from '../../SdkConfig';
|
||||
import { instanceForInstanceId, protocolNameForInstanceId } from '../../utils/DirectoryUtils';
|
||||
import Analytics from '../../Analytics';
|
||||
import {getHttpUriForMxc} from "matrix-js-sdk/src/content-repo";
|
||||
import {ALL_ROOMS} from "../views/directory/NetworkDropdown";
|
||||
import SettingsStore from "../../settings/SettingsStore";
|
||||
import GroupFilterOrderStore from "../../stores/GroupFilterOrderStore";
|
||||
import GroupStore from "../../stores/GroupStore";
|
||||
import FlairStore from "../../stores/FlairStore";
|
||||
import CountlyAnalytics from "../../CountlyAnalytics";
|
||||
import {replaceableComponent} from "../../utils/replaceableComponent";
|
||||
import {mediaFromMxc} from "../../customisations/Media";
|
||||
|
||||
const MAX_NAME_LENGTH = 80;
|
||||
const MAX_TOPIC_LENGTH = 800;
|
||||
|
@ -42,6 +43,7 @@ function track(action) {
|
|||
Analytics.trackEvent('RoomDirectory', action);
|
||||
}
|
||||
|
||||
@replaceableComponent("structures.RoomDirectory")
|
||||
export default class RoomDirectory extends React.Component {
|
||||
static propTypes = {
|
||||
initialText: PropTypes.string,
|
||||
|
@ -519,10 +521,9 @@ export default class RoomDirectory extends React.Component {
|
|||
topic = `${topic.substring(0, MAX_TOPIC_LENGTH)}...`;
|
||||
}
|
||||
topic = linkifyAndSanitizeHtml(topic);
|
||||
const avatarUrl = getHttpUriForMxc(
|
||||
MatrixClientPeg.get().getHomeserverUrl(),
|
||||
room.avatar_url, 32, 32, "crop",
|
||||
);
|
||||
let avatarUrl = null;
|
||||
if (room.avatar_url) avatarUrl = mediaFromMxc(room.avatar_url).getSquareThumbnailHttp(32);
|
||||
|
||||
return [
|
||||
<div key={ `${room.room_id}_avatar` }
|
||||
onClick={(ev) => this.onRoomClicked(room, ev)}
|
||||
|
|
|
@ -25,6 +25,7 @@ import AccessibleButton from "../views/elements/AccessibleButton";
|
|||
import { Action } from "../../dispatcher/actions";
|
||||
import RoomListStore from "../../stores/room-list/RoomListStore";
|
||||
import { NameFilterCondition } from "../../stores/room-list/filters/NameFilterCondition";
|
||||
import {replaceableComponent} from "../../utils/replaceableComponent";
|
||||
|
||||
interface IProps {
|
||||
isMinimized: boolean;
|
||||
|
@ -37,6 +38,7 @@ interface IState {
|
|||
focused: boolean;
|
||||
}
|
||||
|
||||
@replaceableComponent("structures.RoomSearch")
|
||||
export default class RoomSearch extends React.PureComponent<IProps, IState> {
|
||||
private dispatcherRef: string;
|
||||
private inputRef: React.RefObject<HTMLInputElement> = createRef();
|
||||
|
|
|
@ -23,6 +23,7 @@ import Resend from '../../Resend';
|
|||
import dis from '../../dispatcher/dispatcher';
|
||||
import {messageForResourceLimitError, messageForSendError} from '../../utils/ErrorUtils';
|
||||
import {Action} from "../../dispatcher/actions";
|
||||
import {replaceableComponent} from "../../utils/replaceableComponent";
|
||||
|
||||
const STATUS_BAR_HIDDEN = 0;
|
||||
const STATUS_BAR_EXPANDED = 1;
|
||||
|
@ -35,6 +36,7 @@ function getUnsentMessages(room) {
|
|||
});
|
||||
}
|
||||
|
||||
@replaceableComponent("structures.RoomStatusBar")
|
||||
export default class RoomStatusBar extends React.Component {
|
||||
static propTypes = {
|
||||
// the room this statusbar is representing.
|
||||
|
|
|
@ -82,6 +82,7 @@ import { Container, WidgetLayoutStore } from "../../stores/widgets/WidgetLayoutS
|
|||
import { objectHasDiff } from "../../utils/objects";
|
||||
import SpaceRoomView from "./SpaceRoomView";
|
||||
import { IOpts } from "../../createRoom";
|
||||
import {replaceableComponent} from "../../utils/replaceableComponent";
|
||||
|
||||
const DEBUG = false;
|
||||
let debuglog = function(msg: string) {};
|
||||
|
@ -195,6 +196,7 @@ export interface IState {
|
|||
dragCounter: number;
|
||||
}
|
||||
|
||||
@replaceableComponent("structures.RoomView")
|
||||
export default class RoomView extends React.Component<IProps, IState> {
|
||||
private readonly dispatcherRef: string;
|
||||
private readonly roomStoreToken: EventSubscription;
|
||||
|
@ -711,9 +713,9 @@ export default class RoomView extends React.Component<IProps, IState> {
|
|||
[payload.file], this.state.room.roomId, this.context);
|
||||
break;
|
||||
case 'notifier_enabled':
|
||||
case 'upload_started':
|
||||
case 'upload_finished':
|
||||
case 'upload_canceled':
|
||||
case Action.UploadStarted:
|
||||
case Action.UploadFinished:
|
||||
case Action.UploadCanceled:
|
||||
this.forceUpdate();
|
||||
break;
|
||||
case 'call_state': {
|
||||
|
@ -1911,7 +1913,7 @@ export default class RoomView extends React.Component<IProps, IState> {
|
|||
);
|
||||
}
|
||||
|
||||
if (this.state.room?.isSpaceRoom()) {
|
||||
if (SettingsStore.getValue("feature_spaces") && this.state.room?.isSpaceRoom()) {
|
||||
return <SpaceRoomView
|
||||
space={this.state.room}
|
||||
justCreatedOpts={this.props.justCreatedOpts}
|
||||
|
|
|
@ -19,6 +19,7 @@ import PropTypes from 'prop-types';
|
|||
import { Key } from '../../Keyboard';
|
||||
import Timer from '../../utils/Timer';
|
||||
import AutoHideScrollbar from "./AutoHideScrollbar";
|
||||
import {replaceableComponent} from "../../utils/replaceableComponent";
|
||||
|
||||
const DEBUG_SCROLL = false;
|
||||
|
||||
|
@ -83,6 +84,7 @@ if (DEBUG_SCROLL) {
|
|||
* offset as normal.
|
||||
*/
|
||||
|
||||
@replaceableComponent("structures.ScrollPanel")
|
||||
export default class ScrollPanel extends React.Component {
|
||||
static propTypes = {
|
||||
/* stickyBottom: if set to true, then once the user hits the bottom of
|
||||
|
|
|
@ -22,7 +22,9 @@ import dis from '../../dispatcher/dispatcher';
|
|||
import {throttle} from 'lodash';
|
||||
import AccessibleButton from '../../components/views/elements/AccessibleButton';
|
||||
import classNames from 'classnames';
|
||||
import {replaceableComponent} from "../../utils/replaceableComponent";
|
||||
|
||||
@replaceableComponent("structures.SearchBox")
|
||||
export default class SearchBox extends React.Component {
|
||||
static propTypes = {
|
||||
onSearch: PropTypes.func,
|
||||
|
|
|
@ -34,6 +34,7 @@ import {EnhancedMap} from "../../utils/maps";
|
|||
import StyledCheckbox from "../views/elements/StyledCheckbox";
|
||||
import AutoHideScrollbar from "./AutoHideScrollbar";
|
||||
import BaseAvatar from "../views/avatars/BaseAvatar";
|
||||
import {mediaFromMxc} from "../../customisations/Media";
|
||||
|
||||
interface IProps {
|
||||
space: Room;
|
||||
|
@ -64,6 +65,7 @@ export interface ISpaceSummaryEvent {
|
|||
state_key: string;
|
||||
content: {
|
||||
order?: string;
|
||||
suggested?: boolean;
|
||||
auto_join?: boolean;
|
||||
via?: string;
|
||||
};
|
||||
|
@ -91,7 +93,7 @@ const SubSpace: React.FC<ISubspaceProps> = ({
|
|||
const name = space.name || space.canonical_alias || space.aliases?.[0] || _t("Unnamed Space");
|
||||
|
||||
const evContent = event?.getContent();
|
||||
const [autoJoin, _setAutoJoin] = useState(evContent?.auto_join);
|
||||
const [suggested, _setSuggested] = useState(evContent?.suggested);
|
||||
const [removed, _setRemoved] = useState(!evContent?.via);
|
||||
|
||||
const cli = MatrixClientPeg.get();
|
||||
|
@ -102,12 +104,12 @@ const SubSpace: React.FC<ISubspaceProps> = ({
|
|||
let actions;
|
||||
if (editing && queueAction) {
|
||||
if (event && cli.getRoom(event.getRoomId())?.currentState.maySendStateEvent(event.getType(), cli.getUserId())) {
|
||||
const setAutoJoin = () => {
|
||||
_setAutoJoin(v => {
|
||||
const setSuggested = () => {
|
||||
_setSuggested(v => {
|
||||
queueAction({
|
||||
event,
|
||||
removed,
|
||||
autoJoin: !v,
|
||||
suggested: !v,
|
||||
});
|
||||
return !v;
|
||||
});
|
||||
|
@ -118,7 +120,7 @@ const SubSpace: React.FC<ISubspaceProps> = ({
|
|||
queueAction({
|
||||
event,
|
||||
removed: !v,
|
||||
autoJoin,
|
||||
suggested,
|
||||
});
|
||||
return !v;
|
||||
});
|
||||
|
@ -131,7 +133,7 @@ const SubSpace: React.FC<ISubspaceProps> = ({
|
|||
} else {
|
||||
actions = <React.Fragment>
|
||||
<FormButton kind="danger" onClick={setRemoved} label={_t("Remove from Space")} />
|
||||
<StyledCheckbox checked={autoJoin} onChange={setAutoJoin} />
|
||||
<StyledCheckbox checked={suggested} onChange={setSuggested} />
|
||||
</React.Fragment>;
|
||||
}
|
||||
} else {
|
||||
|
@ -157,12 +159,7 @@ const SubSpace: React.FC<ISubspaceProps> = ({
|
|||
|
||||
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",
|
||||
);
|
||||
url = mediaFromMxc(space.avatar_url).getSquareThumbnailHttp(Math.floor(24 * window.devicePixelRatio));
|
||||
}
|
||||
|
||||
return <div className="mx_SpaceRoomDirectory_subspace">
|
||||
|
@ -182,8 +179,8 @@ const SubSpace: React.FC<ISubspaceProps> = ({
|
|||
|
||||
interface IAction {
|
||||
event: MatrixEvent;
|
||||
suggested: boolean;
|
||||
removed: boolean;
|
||||
autoJoin: boolean;
|
||||
}
|
||||
|
||||
interface IRoomTileProps {
|
||||
|
@ -199,7 +196,7 @@ const RoomTile = ({ room, event, editing, queueAction, onPreviewClick, onJoinCli
|
|||
const name = room.name || room.canonical_alias || room.aliases?.[0] || _t("Unnamed Room");
|
||||
|
||||
const evContent = event?.getContent();
|
||||
const [autoJoin, _setAutoJoin] = useState(evContent?.auto_join);
|
||||
const [suggested, _setSuggested] = useState(evContent?.suggested);
|
||||
const [removed, _setRemoved] = useState(!evContent?.via);
|
||||
|
||||
const cli = MatrixClientPeg.get();
|
||||
|
@ -209,12 +206,12 @@ const RoomTile = ({ room, event, editing, queueAction, onPreviewClick, onJoinCli
|
|||
let actions;
|
||||
if (editing && queueAction) {
|
||||
if (event && cli.getRoom(event.getRoomId())?.currentState.maySendStateEvent(event.getType(), cli.getUserId())) {
|
||||
const setAutoJoin = () => {
|
||||
_setAutoJoin(v => {
|
||||
const setSuggested = () => {
|
||||
_setSuggested(v => {
|
||||
queueAction({
|
||||
event,
|
||||
removed,
|
||||
autoJoin: !v,
|
||||
suggested: !v,
|
||||
});
|
||||
return !v;
|
||||
});
|
||||
|
@ -225,7 +222,7 @@ const RoomTile = ({ room, event, editing, queueAction, onPreviewClick, onJoinCli
|
|||
queueAction({
|
||||
event,
|
||||
removed: !v,
|
||||
autoJoin,
|
||||
suggested,
|
||||
});
|
||||
return !v;
|
||||
});
|
||||
|
@ -238,7 +235,7 @@ const RoomTile = ({ room, event, editing, queueAction, onPreviewClick, onJoinCli
|
|||
} else {
|
||||
actions = <React.Fragment>
|
||||
<FormButton kind="danger" onClick={setRemoved} label={_t("Remove from Space")} />
|
||||
<StyledCheckbox checked={autoJoin} onChange={setAutoJoin} />
|
||||
<StyledCheckbox checked={suggested} onChange={setSuggested} />
|
||||
</React.Fragment>;
|
||||
}
|
||||
} else {
|
||||
|
@ -264,12 +261,7 @@ const RoomTile = ({ room, event, editing, queueAction, onPreviewClick, onJoinCli
|
|||
|
||||
let url: string;
|
||||
if (room.avatar_url) {
|
||||
url = cli.mxcUrlToHttp(
|
||||
room.avatar_url,
|
||||
Math.floor(32 * window.devicePixelRatio),
|
||||
Math.floor(32 * window.devicePixelRatio),
|
||||
"crop",
|
||||
);
|
||||
url = mediaFromMxc(room.avatar_url).getSquareThumbnailHttp(Math.floor(32 * window.devicePixelRatio));
|
||||
}
|
||||
|
||||
const content = <React.Fragment>
|
||||
|
@ -445,10 +437,10 @@ const SpaceRoomDirectory: React.FC<IProps> = ({ space, initialText = "", onFinis
|
|||
|
||||
const onSaveButtonClicked = () => {
|
||||
// TODO setBusy
|
||||
pendingActions.current.forEach(({event, autoJoin, removed}) => {
|
||||
pendingActions.current.forEach(({event, suggested, removed}) => {
|
||||
const content = {
|
||||
...event.getContent(),
|
||||
auto_join: autoJoin,
|
||||
suggested,
|
||||
};
|
||||
|
||||
if (removed) {
|
||||
|
@ -463,7 +455,7 @@ const SpaceRoomDirectory: React.FC<IProps> = ({ space, initialText = "", onFinis
|
|||
if (isEditing) {
|
||||
adminButton = <React.Fragment>
|
||||
<FormButton label={_t("Save changes")} onClick={onSaveButtonClicked} />
|
||||
<span>{ _t("All users join by default") }</span>
|
||||
<span>{ _t("Promoted to users") }</span>
|
||||
</React.Fragment>;
|
||||
} else {
|
||||
adminButton = <FormButton label={_t("Manage rooms")} onClick={onManageButtonClicked} />;
|
||||
|
|
|
@ -94,26 +94,95 @@ const useMyRoomMembership = (room: Room) => {
|
|||
return membership;
|
||||
};
|
||||
|
||||
const SpaceLanding = ({ space, onJoinButtonClicked, onRejectButtonClicked }) => {
|
||||
const SpacePreview = ({ space, onJoinButtonClicked, onRejectButtonClicked }) => {
|
||||
const cli = useContext(MatrixClientContext);
|
||||
const myMembership = useMyRoomMembership(space);
|
||||
const joinRule = space.getJoinRule();
|
||||
const userId = cli.getUserId();
|
||||
|
||||
let inviterSection;
|
||||
let joinButtons;
|
||||
if (myMembership === "invite") {
|
||||
joinButtons = <div className="mx_SpaceRoomView_landing_joinButtons">
|
||||
<FormButton label={_t("Accept Invite")} onClick={onJoinButtonClicked} />
|
||||
<AccessibleButton kind="link" onClick={onRejectButtonClicked}>
|
||||
{_t("Decline")}
|
||||
</AccessibleButton>
|
||||
</div>;
|
||||
} else if (myMembership !== "join" && joinRule === "public") {
|
||||
joinButtons = <div className="mx_SpaceRoomView_landing_joinButtons">
|
||||
<FormButton label={_t("Join")} onClick={onJoinButtonClicked} />
|
||||
</div>;
|
||||
const inviteSender = space.getMember(cli.getUserId())?.events.member?.getSender();
|
||||
const inviter = inviteSender && space.getMember(inviteSender);
|
||||
|
||||
if (inviteSender) {
|
||||
inviterSection = <div className="mx_SpaceRoomView_preview_inviter">
|
||||
<MemberAvatar member={inviter} width={32} height={32} />
|
||||
<div>
|
||||
<div className="mx_SpaceRoomView_preview_inviter_name">
|
||||
{ _t("<inviter/> invites you", {}, {
|
||||
inviter: () => <b>{ inviter.name || inviteSender }</b>,
|
||||
}) }
|
||||
</div>
|
||||
{ inviter ? <div className="mx_SpaceRoomView_preview_inviter_mxid">
|
||||
{ inviteSender }
|
||||
</div> : null }
|
||||
</div>
|
||||
</div>;
|
||||
}
|
||||
|
||||
joinButtons = <>
|
||||
<FormButton label={_t("Reject")} kind="secondary" onClick={onRejectButtonClicked} />
|
||||
<FormButton label={_t("Accept")} onClick={onJoinButtonClicked} />
|
||||
</>;
|
||||
} else {
|
||||
joinButtons = <FormButton label={_t("Join")} onClick={onJoinButtonClicked} />
|
||||
}
|
||||
|
||||
let visibilitySection;
|
||||
if (space.getJoinRule() === "public") {
|
||||
visibilitySection = <span className="mx_SpaceRoomView_preview_info_public">
|
||||
{ _t("Public space") }
|
||||
</span>;
|
||||
} else {
|
||||
visibilitySection = <span className="mx_SpaceRoomView_preview_info_private">
|
||||
{ _t("Private space") }
|
||||
</span>;
|
||||
}
|
||||
|
||||
return <div className="mx_SpaceRoomView_preview">
|
||||
{ inviterSection }
|
||||
<RoomAvatar room={space} height={80} width={80} viewAvatarOnClick={true} />
|
||||
<h1 className="mx_SpaceRoomView_preview_name">
|
||||
<RoomName room={space} />
|
||||
</h1>
|
||||
<div className="mx_SpaceRoomView_preview_info">
|
||||
{ visibilitySection }
|
||||
<RoomMemberCount room={space}>
|
||||
{(count) => count > 0 ? (
|
||||
<AccessibleButton
|
||||
className="mx_SpaceRoomView_preview_memberCount"
|
||||
kind="link"
|
||||
onClick={() => {
|
||||
defaultDispatcher.dispatch<SetRightPanelPhasePayload>({
|
||||
action: Action.SetRightPanelPhase,
|
||||
phase: RightPanelPhases.RoomMemberList,
|
||||
refireParams: { space },
|
||||
});
|
||||
}}
|
||||
>
|
||||
{ _t("%(count)s members", { count }) }
|
||||
</AccessibleButton>
|
||||
) : null}
|
||||
</RoomMemberCount>
|
||||
</div>
|
||||
<RoomTopic room={space}>
|
||||
{(topic, ref) =>
|
||||
<div className="mx_SpaceRoomView_preview_topic" ref={ref}>
|
||||
{ topic }
|
||||
</div>
|
||||
}
|
||||
</RoomTopic>
|
||||
<div className="mx_SpaceRoomView_preview_joinButtons">
|
||||
{ joinButtons }
|
||||
</div>
|
||||
</div>;
|
||||
};
|
||||
|
||||
const SpaceLanding = ({ space }) => {
|
||||
const cli = useContext(MatrixClientContext);
|
||||
const myMembership = useMyRoomMembership(space);
|
||||
const userId = cli.getUserId();
|
||||
|
||||
let inviteButton;
|
||||
if (myMembership === "join" && space.canInvite(userId)) {
|
||||
inviteButton = (
|
||||
|
@ -227,26 +296,7 @@ const SpaceLanding = ({ space, onJoinButtonClicked, onRejectButtonClicked }) =>
|
|||
) : null}
|
||||
</RoomMemberCount>
|
||||
</div> };
|
||||
if (myMembership === "invite") {
|
||||
const inviteSender = space.getMember(userId)?.events.member?.getSender();
|
||||
const inviter = inviteSender && space.getMember(inviteSender);
|
||||
|
||||
if (inviteSender) {
|
||||
return _t("<inviter/> invited you to <name/>", {}, {
|
||||
name: tags.name,
|
||||
inviter: () => inviter
|
||||
? <span className="mx_SpaceRoomView_landing_inviter">
|
||||
<MemberAvatar member={inviter} width={26} height={26} viewUserOnClick={true} />
|
||||
{ inviter.name }
|
||||
</span>
|
||||
: <span className="mx_SpaceRoomView_landing_inviter">
|
||||
{ inviteSender }
|
||||
</span>,
|
||||
}) as JSX.Element;
|
||||
} else {
|
||||
return _t("You have been invited to <name/>", {}, tags) as JSX.Element;
|
||||
}
|
||||
} else if (shouldShowSpaceSettings(cli, space)) {
|
||||
if (shouldShowSpaceSettings(cli, space)) {
|
||||
if (space.getJoinRule() === "public") {
|
||||
return _t("Your public space <name/>", {}, tags) as JSX.Element;
|
||||
} else {
|
||||
|
@ -260,7 +310,6 @@ const SpaceLanding = ({ space, onJoinButtonClicked, onRejectButtonClicked }) =>
|
|||
<div className="mx_SpaceRoomView_landing_topic">
|
||||
<RoomTopic room={space} />
|
||||
</div>
|
||||
{ joinButtons }
|
||||
<div className="mx_SpaceRoomView_landing_adminButtons">
|
||||
{ inviteButton }
|
||||
{ addRoomButtons }
|
||||
|
@ -548,16 +597,19 @@ export default class SpaceRoomView extends React.PureComponent<IProps, IState> {
|
|||
private renderBody() {
|
||||
switch (this.state.phase) {
|
||||
case Phase.Landing:
|
||||
return <SpaceLanding
|
||||
space={this.props.space}
|
||||
onJoinButtonClicked={this.props.onJoinButtonClicked}
|
||||
onRejectButtonClicked={this.props.onRejectButtonClicked}
|
||||
/>;
|
||||
|
||||
if (this.props.space.getMyMembership() === "join") {
|
||||
return <SpaceLanding space={this.props.space} />;
|
||||
} else {
|
||||
return <SpacePreview
|
||||
space={this.props.space}
|
||||
onJoinButtonClicked={this.props.onJoinButtonClicked}
|
||||
onRejectButtonClicked={this.props.onRejectButtonClicked}
|
||||
/>;
|
||||
}
|
||||
case Phase.PublicCreateRooms:
|
||||
return <SpaceSetupFirstRooms
|
||||
space={this.props.space}
|
||||
title={_t("What discussions do you want to have?")}
|
||||
title={_t("What are some things you want to discuss?")}
|
||||
description={_t("We'll create rooms for each topic.")}
|
||||
onFinished={() => this.setState({ phase: Phase.PublicShare })}
|
||||
/>;
|
||||
|
|
|
@ -20,6 +20,7 @@ import * as React from "react";
|
|||
import {_t} from '../../languageHandler';
|
||||
import * as sdk from "../../index";
|
||||
import AutoHideScrollbar from './AutoHideScrollbar';
|
||||
import {replaceableComponent} from "../../utils/replaceableComponent";
|
||||
|
||||
/**
|
||||
* Represents a tab for the TabbedView.
|
||||
|
@ -45,6 +46,7 @@ interface IState {
|
|||
activeTabIndex: number;
|
||||
}
|
||||
|
||||
@replaceableComponent("structures.TabbedView")
|
||||
export default class TabbedView extends React.Component<IProps, IState> {
|
||||
constructor(props: IProps) {
|
||||
super(props);
|
||||
|
|
|
@ -37,6 +37,7 @@ import EditorStateTransfer from '../../utils/EditorStateTransfer';
|
|||
import {haveTileForEvent} from "../views/rooms/EventTile";
|
||||
import {UIFeature} from "../../settings/UIFeature";
|
||||
import {objectHasDiff} from "../../utils/objects";
|
||||
import {replaceableComponent} from "../../utils/replaceableComponent";
|
||||
|
||||
const PAGINATE_SIZE = 20;
|
||||
const INITIAL_SIZE = 20;
|
||||
|
@ -55,6 +56,7 @@ if (DEBUG) {
|
|||
*
|
||||
* Also responsible for handling and sending read receipts.
|
||||
*/
|
||||
@replaceableComponent("structures.TimelinePanel")
|
||||
class TimelinePanel extends React.Component {
|
||||
static propTypes = {
|
||||
// The js-sdk EventTimelineSet object for the timeline sequence we are
|
||||
|
|
|
@ -17,12 +17,14 @@ limitations under the License.
|
|||
import * as React from "react";
|
||||
import ToastStore, {IToast} from "../../stores/ToastStore";
|
||||
import classNames from "classnames";
|
||||
import {replaceableComponent} from "../../utils/replaceableComponent";
|
||||
|
||||
interface IState {
|
||||
toasts: IToast<any>[];
|
||||
countSeen: number;
|
||||
}
|
||||
|
||||
@replaceableComponent("structures.ToastContainer")
|
||||
export default class ToastContainer extends React.Component<{}, IState> {
|
||||
constructor(props, context) {
|
||||
super(props, context);
|
||||
|
|
|
@ -1,109 +0,0 @@
|
|||
/*
|
||||
Copyright 2015, 2016 OpenMarket Ltd
|
||||
Copyright 2019 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import ContentMessages from '../../ContentMessages';
|
||||
import dis from "../../dispatcher/dispatcher";
|
||||
import filesize from "filesize";
|
||||
import { _t } from '../../languageHandler';
|
||||
|
||||
export default class UploadBar extends React.Component {
|
||||
static propTypes = {
|
||||
room: PropTypes.object,
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
this.dispatcherRef = dis.register(this.onAction);
|
||||
this.mounted = true;
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.mounted = false;
|
||||
dis.unregister(this.dispatcherRef);
|
||||
}
|
||||
|
||||
onAction = payload => {
|
||||
switch (payload.action) {
|
||||
case 'upload_progress':
|
||||
case 'upload_finished':
|
||||
case 'upload_canceled':
|
||||
case 'upload_failed':
|
||||
if (this.mounted) this.forceUpdate();
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const uploads = ContentMessages.sharedInstance().getCurrentUploads();
|
||||
|
||||
// for testing UI... - also fix up the ContentMessages.getCurrentUploads().length
|
||||
// check in RoomView
|
||||
//
|
||||
// uploads = [{
|
||||
// roomId: this.props.room.roomId,
|
||||
// loaded: 123493,
|
||||
// total: 347534,
|
||||
// fileName: "testing_fooble.jpg",
|
||||
// }];
|
||||
|
||||
if (uploads.length == 0) {
|
||||
return <div />;
|
||||
}
|
||||
|
||||
let upload;
|
||||
for (let i = 0; i < uploads.length; ++i) {
|
||||
if (uploads[i].roomId == this.props.room.roomId) {
|
||||
upload = uploads[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!upload) {
|
||||
return <div />;
|
||||
}
|
||||
|
||||
const innerProgressStyle = {
|
||||
width: ((upload.loaded / (upload.total || 1)) * 100) + '%',
|
||||
};
|
||||
let uploadedSize = filesize(upload.loaded);
|
||||
const totalSize = filesize(upload.total);
|
||||
if (uploadedSize.replace(/^.* /, '') === totalSize.replace(/^.* /, '')) {
|
||||
uploadedSize = uploadedSize.replace(/ .*/, '');
|
||||
}
|
||||
|
||||
// MUST use var name 'count' for pluralization to kick in
|
||||
const uploadText = _t(
|
||||
"Uploading %(filename)s and %(count)s others", {filename: upload.fileName, count: (uploads.length - 1)},
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="mx_UploadBar">
|
||||
<div className="mx_UploadBar_uploadProgressOuter">
|
||||
<div className="mx_UploadBar_uploadProgressInner" style={innerProgressStyle}></div>
|
||||
</div>
|
||||
<img className="mx_UploadBar_uploadIcon mx_filterFlipColor" src={require("../../../res/img/fileicon.png")} width="17" height="22" />
|
||||
<img className="mx_UploadBar_uploadCancel mx_filterFlipColor" src={require("../../../res/img/cancel.svg")} width="18" height="18"
|
||||
onClick={function() { ContentMessages.sharedInstance().cancelUpload(upload.promise); }}
|
||||
/>
|
||||
<div className="mx_UploadBar_uploadBytes">
|
||||
{ uploadedSize } / { totalSize }
|
||||
</div>
|
||||
<div className="mx_UploadBar_uploadFilename">{ uploadText }</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,102 @@
|
|||
/*
|
||||
Copyright 2015, 2016, 2019, 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.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { Room } from "matrix-js-sdk/src/models/room";
|
||||
import ContentMessages from '../../ContentMessages';
|
||||
import dis from "../../dispatcher/dispatcher";
|
||||
import filesize from "filesize";
|
||||
import { _t } from '../../languageHandler';
|
||||
import { ActionPayload } from "../../dispatcher/payloads";
|
||||
import { Action } from "../../dispatcher/actions";
|
||||
import ProgressBar from "../views/elements/ProgressBar";
|
||||
import AccessibleButton from "../views/elements/AccessibleButton";
|
||||
import { IUpload } from "../../models/IUpload";
|
||||
import {replaceableComponent} from "../../utils/replaceableComponent";
|
||||
|
||||
interface IProps {
|
||||
room: Room;
|
||||
}
|
||||
|
||||
interface IState {
|
||||
currentUpload?: IUpload;
|
||||
uploadsHere: IUpload[];
|
||||
}
|
||||
|
||||
@replaceableComponent("structures.UploadBar")
|
||||
export default class UploadBar extends React.Component<IProps, IState> {
|
||||
private dispatcherRef: string;
|
||||
private mounted: boolean;
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {uploadsHere: []};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.dispatcherRef = dis.register(this.onAction);
|
||||
this.mounted = true;
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.mounted = false;
|
||||
dis.unregister(this.dispatcherRef);
|
||||
}
|
||||
|
||||
private onAction = (payload: ActionPayload) => {
|
||||
switch (payload.action) {
|
||||
case Action.UploadStarted:
|
||||
case Action.UploadProgress:
|
||||
case Action.UploadFinished:
|
||||
case Action.UploadCanceled:
|
||||
case Action.UploadFailed: {
|
||||
if (!this.mounted) return;
|
||||
const uploads = ContentMessages.sharedInstance().getCurrentUploads();
|
||||
const uploadsHere = uploads.filter(u => u.roomId === this.props.room.roomId);
|
||||
this.setState({currentUpload: uploadsHere[0], uploadsHere});
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private onCancelClick = (ev) => {
|
||||
ev.preventDefault();
|
||||
ContentMessages.sharedInstance().cancelUpload(this.state.currentUpload.promise);
|
||||
};
|
||||
|
||||
render() {
|
||||
if (!this.state.currentUpload) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// MUST use var name 'count' for pluralization to kick in
|
||||
const uploadText = _t(
|
||||
"Uploading %(filename)s and %(count)s others", {
|
||||
filename: this.state.currentUpload.fileName,
|
||||
count: this.state.uploadsHere.length - 1,
|
||||
},
|
||||
);
|
||||
|
||||
const uploadSize = filesize(this.state.currentUpload.total);
|
||||
return (
|
||||
<div className="mx_UploadBar">
|
||||
<div className="mx_UploadBar_filename">{uploadText} ({uploadSize})</div>
|
||||
<AccessibleButton onClick={this.onCancelClick} className='mx_UploadBar_cancel' />
|
||||
<ProgressBar value={this.state.currentUpload.loaded} max={this.state.currentUpload.total} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -56,6 +56,7 @@ import HostSignupAction from "./HostSignupAction";
|
|||
import { IHostSignupConfig } from "../views/dialogs/HostSignupDialogTypes";
|
||||
import SpaceStore, { UPDATE_SELECTED_SPACE } from "../../stores/SpaceStore";
|
||||
import RoomName from "../views/elements/RoomName";
|
||||
import {replaceableComponent} from "../../utils/replaceableComponent";
|
||||
|
||||
interface IProps {
|
||||
isMinimized: boolean;
|
||||
|
@ -69,6 +70,7 @@ interface IState {
|
|||
selectedSpace?: Room;
|
||||
}
|
||||
|
||||
@replaceableComponent("structures.UserMenu")
|
||||
export default class UserMenu extends React.Component<IProps, IState> {
|
||||
private dispatcherRef: string;
|
||||
private themeWatcherRef: string;
|
||||
|
|
|
@ -23,7 +23,9 @@ import * as sdk from "../../index";
|
|||
import Modal from '../../Modal';
|
||||
import { _t } from '../../languageHandler';
|
||||
import HomePage from "./HomePage";
|
||||
import {replaceableComponent} from "../../utils/replaceableComponent";
|
||||
|
||||
@replaceableComponent("structures.UserView")
|
||||
export default class UserView extends React.Component {
|
||||
static get propTypes() {
|
||||
return {
|
||||
|
|
|
@ -16,34 +16,176 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import SyntaxHighlight from '../views/elements/SyntaxHighlight';
|
||||
import {_t} from "../../languageHandler";
|
||||
import React from "react";
|
||||
import PropTypes from "prop-types";
|
||||
import SyntaxHighlight from "../views/elements/SyntaxHighlight";
|
||||
import { _t } from "../../languageHandler";
|
||||
import * as sdk from "../../index";
|
||||
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")
|
||||
export default class ViewSource extends React.Component {
|
||||
static propTypes = {
|
||||
content: PropTypes.object.isRequired,
|
||||
onFinished: PropTypes.func.isRequired,
|
||||
roomId: PropTypes.string.isRequired,
|
||||
eventId: PropTypes.string.isRequired,
|
||||
mxEvent: PropTypes.object.isRequired, // the MatrixEvent associated with the context menu
|
||||
};
|
||||
|
||||
render() {
|
||||
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
|
||||
return (
|
||||
<BaseDialog className="mx_ViewSource" onFinished={this.props.onFinished} title={_t('View Source')}>
|
||||
<div className="mx_ViewSource_label_left">Room ID: { this.props.roomId }</div>
|
||||
<div className="mx_ViewSource_label_right">Event ID: { this.props.eventId }</div>
|
||||
<div className="mx_ViewSource_label_bottom" />
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
<div className="mx_Dialog_content">
|
||||
<SyntaxHighlight className="json">
|
||||
{ JSON.stringify(this.props.content, null, 2) }
|
||||
</SyntaxHighlight>
|
||||
this.state = {
|
||||
isEditing: false,
|
||||
};
|
||||
}
|
||||
|
||||
onBack() {
|
||||
// TODO: refresh the "Event ID:" modal header
|
||||
this.setState({ isEditing: false });
|
||||
}
|
||||
|
||||
onEdit() {
|
||||
this.setState({ isEditing: true });
|
||||
}
|
||||
|
||||
// returns the dialog body for viewing the event source
|
||||
viewSourceContent() {
|
||||
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 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 {
|
||||
return (
|
||||
<>
|
||||
<div className="mx_ViewSource_heading">{_t("Original event source")}</div>
|
||||
<SyntaxHighlight className="json">{JSON.stringify(originalEventSource, null, 2)}</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 (
|
||||
<BaseDialog className="mx_ViewSource" onFinished={this.props.onFinished} title={_t("View Source")}>
|
||||
<div>
|
||||
<div className="mx_ViewSource_label_left">Room ID: {roomId}</div>
|
||||
<div className="mx_ViewSource_label_left">Event ID: {eventId}</div>
|
||||
<div className="mx_ViewSource_separator" />
|
||||
{isEditing ? this.editSourceContent() : this.viewSourceContent()}
|
||||
</div>
|
||||
{!isEditing && canEdit && (
|
||||
<div className="mx_Dialog_buttons">
|
||||
<button onClick={() => this.onEdit()}>{_t("Edit")}</button>
|
||||
</div>
|
||||
)}
|
||||
</BaseDialog>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -20,13 +20,16 @@ import { _t } from '../../../languageHandler';
|
|||
import * as sdk from '../../../index';
|
||||
import {
|
||||
SetupEncryptionStore,
|
||||
PHASE_LOADING,
|
||||
PHASE_INTRO,
|
||||
PHASE_BUSY,
|
||||
PHASE_DONE,
|
||||
PHASE_CONFIRM_SKIP,
|
||||
} from '../../../stores/SetupEncryptionStore';
|
||||
import SetupEncryptionBody from "./SetupEncryptionBody";
|
||||
import {replaceableComponent} from "../../../utils/replaceableComponent";
|
||||
|
||||
@replaceableComponent("structures.auth.CompleteSecurity")
|
||||
export default class CompleteSecurity extends React.Component {
|
||||
static propTypes = {
|
||||
onFinished: PropTypes.func.isRequired,
|
||||
|
@ -58,7 +61,9 @@ export default class CompleteSecurity extends React.Component {
|
|||
let icon;
|
||||
let title;
|
||||
|
||||
if (phase === PHASE_INTRO) {
|
||||
if (phase === PHASE_LOADING) {
|
||||
return null;
|
||||
} else if (phase === PHASE_INTRO) {
|
||||
icon = <span className="mx_CompleteSecurity_headerIcon mx_E2EIcon_warning" />;
|
||||
title = _t("Verify this login");
|
||||
} else if (phase === PHASE_DONE) {
|
||||
|
|
|
@ -19,7 +19,9 @@ import PropTypes from 'prop-types';
|
|||
import AuthPage from '../../views/auth/AuthPage';
|
||||
import CompleteSecurityBody from '../../views/auth/CompleteSecurityBody';
|
||||
import CreateCrossSigningDialog from '../../views/dialogs/security/CreateCrossSigningDialog';
|
||||
import {replaceableComponent} from "../../../utils/replaceableComponent";
|
||||
|
||||
@replaceableComponent("structures.auth.E2eSetup")
|
||||
export default class E2eSetup extends React.Component {
|
||||
static propTypes = {
|
||||
onFinished: PropTypes.func.isRequired,
|
||||
|
|
|
@ -27,6 +27,7 @@ import classNames from 'classnames';
|
|||
import AuthPage from "../../views/auth/AuthPage";
|
||||
import CountlyAnalytics from "../../../CountlyAnalytics";
|
||||
import ServerPicker from "../../views/elements/ServerPicker";
|
||||
import {replaceableComponent} from "../../../utils/replaceableComponent";
|
||||
|
||||
// Phases
|
||||
// Show the forgot password inputs
|
||||
|
@ -38,6 +39,7 @@ const PHASE_EMAIL_SENT = 3;
|
|||
// User has clicked the link in email and completed reset
|
||||
const PHASE_DONE = 4;
|
||||
|
||||
@replaceableComponent("structures.auth.ForgotPassword")
|
||||
export default class ForgotPassword extends React.Component {
|
||||
static propTypes = {
|
||||
serverConfig: PropTypes.instanceOf(ValidatedServerConfig).isRequired,
|
||||
|
|
|
@ -35,6 +35,7 @@ import InlineSpinner from "../../views/elements/InlineSpinner";
|
|||
import Spinner from "../../views/elements/Spinner";
|
||||
import SSOButtons from "../../views/elements/SSOButtons";
|
||||
import ServerPicker from "../../views/elements/ServerPicker";
|
||||
import {replaceableComponent} from "../../../utils/replaceableComponent";
|
||||
|
||||
// These are used in several places, and come from the js-sdk's autodiscovery
|
||||
// stuff. We define them here so that they'll be picked up by i18n.
|
||||
|
@ -99,6 +100,7 @@ interface IState {
|
|||
/*
|
||||
* A wire component which glues together login UI components and Login logic
|
||||
*/
|
||||
@replaceableComponent("structures.auth.LoginComponent")
|
||||
export default class LoginComponent extends React.PureComponent<IProps, IState> {
|
||||
private unmounted = false;
|
||||
private loginLogic: Login;
|
||||
|
|
|
@ -30,6 +30,7 @@ import Login, {ISSOFlow} from "../../../Login";
|
|||
import dis from "../../../dispatcher/dispatcher";
|
||||
import SSOButtons from "../../views/elements/SSOButtons";
|
||||
import ServerPicker from '../../views/elements/ServerPicker';
|
||||
import {replaceableComponent} from "../../../utils/replaceableComponent";
|
||||
|
||||
interface IProps {
|
||||
serverConfig: ValidatedServerConfig;
|
||||
|
@ -109,6 +110,7 @@ interface IState {
|
|||
ssoFlow?: ISSOFlow;
|
||||
}
|
||||
|
||||
@replaceableComponent("structures.auth.Registration")
|
||||
export default class Registration extends React.Component<IProps, IState> {
|
||||
loginLogic: Login;
|
||||
|
||||
|
|
|
@ -17,17 +17,20 @@ limitations under the License.
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { _t } from '../../../languageHandler';
|
||||
import SdkConfig from '../../../SdkConfig';
|
||||
import { MatrixClientPeg } from '../../../MatrixClientPeg';
|
||||
import Modal from '../../../Modal';
|
||||
import VerificationRequestDialog from '../../views/dialogs/VerificationRequestDialog';
|
||||
import * as sdk from '../../../index';
|
||||
import {
|
||||
SetupEncryptionStore,
|
||||
PHASE_LOADING,
|
||||
PHASE_INTRO,
|
||||
PHASE_BUSY,
|
||||
PHASE_DONE,
|
||||
PHASE_CONFIRM_SKIP,
|
||||
PHASE_FINISHED,
|
||||
} from '../../../stores/SetupEncryptionStore';
|
||||
import {replaceableComponent} from "../../../utils/replaceableComponent";
|
||||
|
||||
function keyHasPassphrase(keyInfo) {
|
||||
return (
|
||||
|
@ -37,6 +40,7 @@ function keyHasPassphrase(keyInfo) {
|
|||
);
|
||||
}
|
||||
|
||||
@replaceableComponent("structures.auth.SetupEncryptionBody")
|
||||
export default class SetupEncryptionBody extends React.Component {
|
||||
static propTypes = {
|
||||
onFinished: PropTypes.func.isRequired,
|
||||
|
@ -81,6 +85,22 @@ export default class SetupEncryptionBody extends React.Component {
|
|||
store.usePassPhrase();
|
||||
}
|
||||
|
||||
_onVerifyClick = () => {
|
||||
const cli = MatrixClientPeg.get();
|
||||
const userId = cli.getUserId();
|
||||
const requestPromise = cli.requestVerification(userId);
|
||||
|
||||
this.props.onFinished(true);
|
||||
Modal.createTrackedDialog('New Session Verification', 'Starting dialog', VerificationRequestDialog, {
|
||||
verificationRequestPromise: requestPromise,
|
||||
member: cli.getUser(userId),
|
||||
onFinished: async () => {
|
||||
const request = await requestPromise;
|
||||
request.cancel();
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
onSkipClick = () => {
|
||||
const store = SetupEncryptionStore.sharedInstance();
|
||||
store.skip();
|
||||
|
@ -132,32 +152,22 @@ export default class SetupEncryptionBody extends React.Component {
|
|||
</AccessibleButton>;
|
||||
}
|
||||
|
||||
const brand = SdkConfig.get().brand;
|
||||
let verifyButton;
|
||||
if (store.hasDevicesToVerifyAgainst) {
|
||||
verifyButton = <AccessibleButton kind="primary" onClick={this._onVerifyClick}>
|
||||
{ _t("Verify with another session") }
|
||||
</AccessibleButton>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<p>{_t(
|
||||
"Confirm your identity by verifying this login from one of your other sessions, " +
|
||||
"granting it access to encrypted messages.",
|
||||
"Verify this login to access your encrypted messages and " +
|
||||
"prove to others that this login is really you.",
|
||||
)}</p>
|
||||
<p>{_t(
|
||||
"This requires the latest %(brand)s on your other devices:",
|
||||
{ brand },
|
||||
)}</p>
|
||||
|
||||
<div className="mx_CompleteSecurity_clients">
|
||||
<div className="mx_CompleteSecurity_clients_desktop">
|
||||
<div>{_t("%(brand)s Web", { brand })}</div>
|
||||
<div>{_t("%(brand)s Desktop", { brand })}</div>
|
||||
</div>
|
||||
<div className="mx_CompleteSecurity_clients_mobile">
|
||||
<div>{_t("%(brand)s iOS", { brand })}</div>
|
||||
<div>{_t("%(brand)s Android", { brand })}</div>
|
||||
</div>
|
||||
<p>{_t("or another cross-signing capable Matrix client")}</p>
|
||||
</div>
|
||||
|
||||
<div className="mx_CompleteSecurity_actionRow">
|
||||
{verifyButton}
|
||||
{useRecoveryKeyButton}
|
||||
<AccessibleButton kind="danger" onClick={this.onSkipClick}>
|
||||
{_t("Skip")}
|
||||
|
@ -215,7 +225,7 @@ export default class SetupEncryptionBody extends React.Component {
|
|||
</div>
|
||||
</div>
|
||||
);
|
||||
} else if (phase === PHASE_BUSY) {
|
||||
} else if (phase === PHASE_BUSY || phase === PHASE_LOADING) {
|
||||
const Spinner = sdk.getComponent('views.elements.Spinner');
|
||||
return <Spinner />;
|
||||
} else {
|
||||
|
|
|
@ -26,6 +26,7 @@ import {sendLoginRequest} from "../../../Login";
|
|||
import AuthPage from "../../views/auth/AuthPage";
|
||||
import {SSO_HOMESERVER_URL_KEY, SSO_ID_SERVER_URL_KEY} from "../../../BasePlatform";
|
||||
import SSOButtons from "../../views/elements/SSOButtons";
|
||||
import {replaceableComponent} from "../../../utils/replaceableComponent";
|
||||
|
||||
const LOGIN_VIEW = {
|
||||
LOADING: 1,
|
||||
|
@ -41,6 +42,7 @@ const FLOWS_TO_VIEWS = {
|
|||
"m.login.sso": LOGIN_VIEW.SSO,
|
||||
};
|
||||
|
||||
@replaceableComponent("structures.auth.SoftLogout")
|
||||
export default class SoftLogout extends React.Component {
|
||||
static propTypes = {
|
||||
// Query parameters from MatrixChat
|
||||
|
|
|
@ -14,10 +14,10 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import React from 'react';
|
||||
import {replaceableComponent} from "../../../utils/replaceableComponent";
|
||||
|
||||
@replaceableComponent("views.auth.AuthBody")
|
||||
export default class AuthBody extends React.PureComponent {
|
||||
render() {
|
||||
return <div className="mx_AuthBody">
|
||||
|
|
|
@ -18,7 +18,9 @@ limitations under the License.
|
|||
|
||||
import { _t } from '../../../languageHandler';
|
||||
import React from 'react';
|
||||
import {replaceableComponent} from "../../../utils/replaceableComponent";
|
||||
|
||||
@replaceableComponent("views.auth.AuthFooter")
|
||||
export default class AuthFooter extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
|
|
|
@ -18,7 +18,9 @@ limitations under the License.
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import * as sdk from '../../../index';
|
||||
import {replaceableComponent} from "../../../utils/replaceableComponent";
|
||||
|
||||
@replaceableComponent("views.auth.AuthHeader")
|
||||
export default class AuthHeader extends React.Component {
|
||||
static propTypes = {
|
||||
disableLanguageSelector: PropTypes.bool,
|
||||
|
|
|
@ -14,10 +14,10 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import React from 'react';
|
||||
import {replaceableComponent} from "../../../utils/replaceableComponent";
|
||||
|
||||
@replaceableComponent("views.auth.AuthHeaderLogo")
|
||||
export default class AuthHeaderLogo extends React.PureComponent {
|
||||
render() {
|
||||
return <div className="mx_AuthHeaderLogo">
|
||||
|
|
|
@ -18,12 +18,14 @@ import React, {createRef} from 'react';
|
|||
import PropTypes from 'prop-types';
|
||||
import { _t } from '../../../languageHandler';
|
||||
import CountlyAnalytics from "../../../CountlyAnalytics";
|
||||
import {replaceableComponent} from "../../../utils/replaceableComponent";
|
||||
|
||||
const DIV_ID = 'mx_recaptcha';
|
||||
|
||||
/**
|
||||
* A pure UI component which displays a captcha form.
|
||||
*/
|
||||
@replaceableComponent("views.auth.CaptchaForm")
|
||||
export default class CaptchaForm extends React.Component {
|
||||
static propTypes = {
|
||||
sitePublicKey: PropTypes.string,
|
||||
|
|
|
@ -14,10 +14,10 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import React from 'react';
|
||||
import {replaceableComponent} from "../../../utils/replaceableComponent";
|
||||
|
||||
@replaceableComponent("views.auth.CompleteSecurityBody")
|
||||
export default class CompleteSecurityBody extends React.PureComponent {
|
||||
render() {
|
||||
return <div className="mx_CompleteSecurityBody">
|
||||
|
|
|
@ -22,6 +22,7 @@ import * as sdk from '../../../index';
|
|||
import {COUNTRIES, getEmojiFlag} from '../../../phonenumber';
|
||||
import SdkConfig from "../../../SdkConfig";
|
||||
import { _t } from "../../../languageHandler";
|
||||
import {replaceableComponent} from "../../../utils/replaceableComponent";
|
||||
|
||||
const COUNTRIES_BY_ISO2 = {};
|
||||
for (const c of COUNTRIES) {
|
||||
|
@ -40,6 +41,7 @@ function countryMatchesSearchQuery(query, country) {
|
|||
return false;
|
||||
}
|
||||
|
||||
@replaceableComponent("views.auth.CountryDropdown")
|
||||
export default class CountryDropdown extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
|
|
@ -26,6 +26,7 @@ import SettingsStore from "../../../settings/SettingsStore";
|
|||
import AccessibleButton from "../elements/AccessibleButton";
|
||||
import Spinner from "../elements/Spinner";
|
||||
import CountlyAnalytics from "../../../CountlyAnalytics";
|
||||
import {replaceableComponent} from "../../../utils/replaceableComponent";
|
||||
|
||||
/* This file contains a collection of components which are used by the
|
||||
* InteractiveAuth to prompt the user to enter the information needed
|
||||
|
@ -75,6 +76,7 @@ import CountlyAnalytics from "../../../CountlyAnalytics";
|
|||
|
||||
export const DEFAULT_PHASE = 0;
|
||||
|
||||
@replaceableComponent("views.auth.PasswordAuthEntry")
|
||||
export class PasswordAuthEntry extends React.Component {
|
||||
static LOGIN_TYPE = "m.login.password";
|
||||
|
||||
|
@ -173,6 +175,7 @@ export class PasswordAuthEntry extends React.Component {
|
|||
}
|
||||
}
|
||||
|
||||
@replaceableComponent("views.auth.RecaptchaAuthEntry")
|
||||
export class RecaptchaAuthEntry extends React.Component {
|
||||
static LOGIN_TYPE = "m.login.recaptcha";
|
||||
|
||||
|
@ -235,6 +238,7 @@ export class RecaptchaAuthEntry extends React.Component {
|
|||
}
|
||||
}
|
||||
|
||||
@replaceableComponent("views.auth.TermsAuthEntry")
|
||||
export class TermsAuthEntry extends React.Component {
|
||||
static LOGIN_TYPE = "m.login.terms";
|
||||
|
||||
|
@ -385,6 +389,7 @@ export class TermsAuthEntry extends React.Component {
|
|||
}
|
||||
}
|
||||
|
||||
@replaceableComponent("views.auth.EmailIdentityAuthEntry")
|
||||
export class EmailIdentityAuthEntry extends React.Component {
|
||||
static LOGIN_TYPE = "m.login.email.identity";
|
||||
|
||||
|
@ -432,6 +437,7 @@ export class EmailIdentityAuthEntry extends React.Component {
|
|||
}
|
||||
}
|
||||
|
||||
@replaceableComponent("views.auth.MsisdnAuthEntry")
|
||||
export class MsisdnAuthEntry extends React.Component {
|
||||
static LOGIN_TYPE = "m.login.msisdn";
|
||||
|
||||
|
@ -578,6 +584,7 @@ export class MsisdnAuthEntry extends React.Component {
|
|||
}
|
||||
}
|
||||
|
||||
@replaceableComponent("views.auth.SSOAuthEntry")
|
||||
export class SSOAuthEntry extends React.Component {
|
||||
static propTypes = {
|
||||
matrixClient: PropTypes.object.isRequired,
|
||||
|
@ -708,6 +715,7 @@ export class SSOAuthEntry extends React.Component {
|
|||
}
|
||||
}
|
||||
|
||||
@replaceableComponent("views.auth.FallbackAuthEntry")
|
||||
export class FallbackAuthEntry extends React.Component {
|
||||
static propTypes = {
|
||||
matrixClient: PropTypes.object.isRequired,
|
||||
|
|
|
@ -22,6 +22,7 @@ import SdkConfig from "../../../SdkConfig";
|
|||
import withValidation, {IFieldState, IValidationResult} from "../elements/Validation";
|
||||
import {_t, _td} from "../../../languageHandler";
|
||||
import Field, {IInputProps} from "../elements/Field";
|
||||
import {replaceableComponent} from "../../../utils/replaceableComponent";
|
||||
|
||||
interface IProps extends Omit<IInputProps, "onValidate"> {
|
||||
autoFocus?: boolean;
|
||||
|
@ -40,6 +41,7 @@ interface IProps extends Omit<IInputProps, "onValidate"> {
|
|||
onValidate(result: IValidationResult);
|
||||
}
|
||||
|
||||
@replaceableComponent("views.auth.PassphraseField")
|
||||
class PassphraseField extends PureComponent<IProps> {
|
||||
static defaultProps = {
|
||||
label: _td("Password"),
|
||||
|
|
|
@ -26,6 +26,7 @@ import withValidation from "../elements/Validation";
|
|||
import * as Email from "../../../email";
|
||||
import Field from "../elements/Field";
|
||||
import CountryDropdown from "./CountryDropdown";
|
||||
import {replaceableComponent} from "../../../utils/replaceableComponent";
|
||||
|
||||
// For validating phone numbers without country codes
|
||||
const PHONE_NUMBER_REGEX = /^[0-9()\-\s]*$/;
|
||||
|
@ -66,6 +67,7 @@ enum LoginField {
|
|||
* A pure UI component which displays a username/password form.
|
||||
* The email/username/phone fields are fully-controlled, the password field is not.
|
||||
*/
|
||||
@replaceableComponent("views.auth.PasswordLogin")
|
||||
export default class PasswordLogin extends React.PureComponent<IProps, IState> {
|
||||
static defaultProps = {
|
||||
onUsernameChanged: function() {},
|
||||
|
|
|
@ -30,6 +30,7 @@ import PassphraseField from "./PassphraseField";
|
|||
import CountlyAnalytics from "../../../CountlyAnalytics";
|
||||
import Field from '../elements/Field';
|
||||
import RegistrationEmailPromptDialog from '../dialogs/RegistrationEmailPromptDialog';
|
||||
import {replaceableComponent} from "../../../utils/replaceableComponent";
|
||||
|
||||
enum RegistrationField {
|
||||
Email = "field_email",
|
||||
|
@ -80,6 +81,7 @@ interface IState {
|
|||
/*
|
||||
* A pure UI component which displays a registration form.
|
||||
*/
|
||||
@replaceableComponent("views.auth.RegistrationForm")
|
||||
export default class RegistrationForm extends React.PureComponent<IProps, IState> {
|
||||
static defaultProps = {
|
||||
onValidationChange: console.error,
|
||||
|
|
|
@ -24,10 +24,12 @@ import {_td} from "../../../languageHandler";
|
|||
import SettingsStore from "../../../settings/SettingsStore";
|
||||
import {UIFeature} from "../../../settings/UIFeature";
|
||||
import CountlyAnalytics from "../../../CountlyAnalytics";
|
||||
import {replaceableComponent} from "../../../utils/replaceableComponent";
|
||||
|
||||
// translatable strings for Welcome pages
|
||||
_td("Sign in with SSO");
|
||||
|
||||
@replaceableComponent("views.auth.Welcome")
|
||||
export default class Welcome extends React.PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
|
|
@ -25,6 +25,7 @@ import AccessibleButton from '../elements/AccessibleButton';
|
|||
import MatrixClientContext from "../../../contexts/MatrixClientContext";
|
||||
import {useEventEmitter} from "../../../hooks/useEventEmitter";
|
||||
import {toPx} from "../../../utils/units";
|
||||
import {ResizeMethod} from "../../../Avatar";
|
||||
|
||||
interface IProps {
|
||||
name: string; // The name (first initial used as default)
|
||||
|
@ -35,7 +36,7 @@ interface IProps {
|
|||
width?: number;
|
||||
height?: number;
|
||||
// XXX: resizeMethod not actually used.
|
||||
resizeMethod?: string;
|
||||
resizeMethod?: ResizeMethod;
|
||||
defaultToInitialLetter?: boolean; // true to add default url
|
||||
onClick?: React.MouseEventHandler;
|
||||
inputRef?: React.RefObject<HTMLImageElement & HTMLSpanElement>;
|
||||
|
|
|
@ -30,6 +30,7 @@ import {MatrixClientPeg} from "../../../MatrixClientPeg";
|
|||
import {_t} from "../../../languageHandler";
|
||||
import TextWithTooltip from "../elements/TextWithTooltip";
|
||||
import DMRoomMap from "../../../utils/DMRoomMap";
|
||||
import {replaceableComponent} from "../../../utils/replaceableComponent";
|
||||
|
||||
interface IProps {
|
||||
room: Room;
|
||||
|
@ -68,6 +69,7 @@ function tooltipText(variant: Icon) {
|
|||
}
|
||||
}
|
||||
|
||||
@replaceableComponent("views.avatars.DecoratedRoomAvatar")
|
||||
export default class DecoratedRoomAvatar extends React.PureComponent<IProps, IState> {
|
||||
private _dmUser: User;
|
||||
private isUnmounted = false;
|
||||
|
|
|
@ -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");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
@ -15,8 +15,10 @@ limitations under the License.
|
|||
*/
|
||||
|
||||
import React from 'react';
|
||||
import {MatrixClientPeg} from '../../../MatrixClientPeg';
|
||||
import BaseAvatar from './BaseAvatar';
|
||||
import {replaceableComponent} from "../../../utils/replaceableComponent";
|
||||
import {mediaFromMxc} from "../../../customisations/Media";
|
||||
import {ResizeMethod} from "../../../Avatar";
|
||||
|
||||
export interface IProps {
|
||||
groupId?: string;
|
||||
|
@ -24,10 +26,11 @@ export interface IProps {
|
|||
groupAvatarUrl?: string;
|
||||
width?: number;
|
||||
height?: number;
|
||||
resizeMethod?: string;
|
||||
resizeMethod?: ResizeMethod;
|
||||
onClick?: React.MouseEventHandler;
|
||||
}
|
||||
|
||||
@replaceableComponent("views.avatars.GroupAvatar")
|
||||
export default class GroupAvatar extends React.Component<IProps> {
|
||||
public static defaultProps = {
|
||||
width: 36,
|
||||
|
@ -36,8 +39,8 @@ export default class GroupAvatar extends React.Component<IProps> {
|
|||
};
|
||||
|
||||
getGroupAvatarUrl() {
|
||||
return MatrixClientPeg.get().mxcUrlToHttp(
|
||||
this.props.groupAvatarUrl,
|
||||
if (!this.props.groupAvatarUrl) return null;
|
||||
return mediaFromMxc(this.props.groupAvatarUrl).getThumbnailOfSourceHttp(
|
||||
this.props.width,
|
||||
this.props.height,
|
||||
this.props.resizeMethod,
|
||||
|
|
|
@ -20,15 +20,17 @@ import {RoomMember} from "matrix-js-sdk/src/models/room-member";
|
|||
|
||||
import dis from "../../../dispatcher/dispatcher";
|
||||
import {Action} from "../../../dispatcher/actions";
|
||||
import {MatrixClientPeg} from "../../../MatrixClientPeg";
|
||||
import BaseAvatar from "./BaseAvatar";
|
||||
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"> {
|
||||
member: RoomMember;
|
||||
fallbackUserId?: string;
|
||||
width: number;
|
||||
height: number;
|
||||
resizeMethod?: string;
|
||||
resizeMethod?: ResizeMethod;
|
||||
// The onClick to give the avatar
|
||||
onClick?: React.MouseEventHandler;
|
||||
// Whether the onClick of the avatar should be overriden to dispatch `Action.ViewUser`
|
||||
|
@ -42,6 +44,7 @@ interface IState {
|
|||
imageUrl?: string;
|
||||
}
|
||||
|
||||
@replaceableComponent("views.avatars.MemberAvatar")
|
||||
export default class MemberAvatar extends React.Component<IProps, IState> {
|
||||
public static defaultProps = {
|
||||
width: 40,
|
||||
|
@ -61,18 +64,19 @@ export default class MemberAvatar extends React.Component<IProps, IState> {
|
|||
}
|
||||
|
||||
private static getState(props: IProps): IState {
|
||||
if (props.member && props.member.name) {
|
||||
return {
|
||||
name: props.member.name,
|
||||
title: props.title || props.member.userId,
|
||||
imageUrl: props.member.getAvatarUrl(
|
||||
MatrixClientPeg.get().getHomeserverUrl(),
|
||||
if (props.member?.name) {
|
||||
let imageUrl = null;
|
||||
if (props.member.getMxcAvatarUrl()) {
|
||||
imageUrl = mediaFromMxc(props.member.getMxcAvatarUrl()).getThumbnailOfSourceHttp(
|
||||
Math.floor(props.width * window.devicePixelRatio),
|
||||
Math.floor(props.height * window.devicePixelRatio),
|
||||
props.resizeMethod,
|
||||
false,
|
||||
false,
|
||||
),
|
||||
);
|
||||
}
|
||||
return {
|
||||
name: props.member.name,
|
||||
title: props.title || props.member.userId,
|
||||
imageUrl: imageUrl,
|
||||
};
|
||||
} else if (props.fallbackUserId) {
|
||||
return {
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue