Merge remote-tracking branch 'upstream/develop' into ts/address-stuff
This commit is contained in:
commit
b1f4ba28d7
281 changed files with 2816 additions and 2309 deletions
|
@ -46,6 +46,7 @@
|
||||||
"start:build": "babel src -w -s -d lib --verbose --extensions \".ts,.js\"",
|
"start:build": "babel src -w -s -d lib --verbose --extensions \".ts,.js\"",
|
||||||
"lint": "yarn lint:types && yarn lint:js && yarn lint:style",
|
"lint": "yarn lint:types && yarn lint:js && yarn lint:style",
|
||||||
"lint:js": "eslint --max-warnings 0 src test",
|
"lint:js": "eslint --max-warnings 0 src test",
|
||||||
|
"lint:js-fix": "eslint --fix src test",
|
||||||
"lint:types": "tsc --noEmit --jsx react",
|
"lint:types": "tsc --noEmit --jsx react",
|
||||||
"lint:style": "stylelint 'res/css/**/*.scss'",
|
"lint:style": "stylelint 'res/css/**/*.scss'",
|
||||||
"test": "jest",
|
"test": "jest",
|
||||||
|
|
|
@ -201,6 +201,7 @@
|
||||||
@import "./views/rooms/_EditMessageComposer.scss";
|
@import "./views/rooms/_EditMessageComposer.scss";
|
||||||
@import "./views/rooms/_EntityTile.scss";
|
@import "./views/rooms/_EntityTile.scss";
|
||||||
@import "./views/rooms/_EventTile.scss";
|
@import "./views/rooms/_EventTile.scss";
|
||||||
|
@import "./views/rooms/_EventBubbleTile.scss";
|
||||||
@import "./views/rooms/_GroupLayout.scss";
|
@import "./views/rooms/_GroupLayout.scss";
|
||||||
@import "./views/rooms/_IRCLayout.scss";
|
@import "./views/rooms/_IRCLayout.scss";
|
||||||
@import "./views/rooms/_JumpToBottomButton.scss";
|
@import "./views/rooms/_JumpToBottomButton.scss";
|
||||||
|
|
|
@ -27,6 +27,7 @@ limitations under the License.
|
||||||
// https://bugzilla.mozilla.org/show_bug.cgi?id=255139
|
// https://bugzilla.mozilla.org/show_bug.cgi?id=255139
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
|
line-height: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_BaseAvatar_initial {
|
.mx_BaseAvatar_initial {
|
||||||
|
|
|
@ -18,7 +18,6 @@ $timelineImageBorderRadius: 4px;
|
||||||
|
|
||||||
.mx_MImageBody {
|
.mx_MImageBody {
|
||||||
display: block;
|
display: block;
|
||||||
margin-right: 34px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_MImageBody_thumbnail {
|
.mx_MImageBody_thumbnail {
|
||||||
|
|
|
@ -26,6 +26,7 @@ limitations under the License.
|
||||||
height: 24px;
|
height: 24px;
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
margin-left: 4px;
|
margin-left: 4px;
|
||||||
|
margin-right: 4px;
|
||||||
|
|
||||||
&::before {
|
&::before {
|
||||||
content: '';
|
content: '';
|
||||||
|
|
323
res/css/views/rooms/_EventBubbleTile.scss
Normal file
323
res/css/views/rooms/_EventBubbleTile.scss
Normal file
|
@ -0,0 +1,323 @@
|
||||||
|
/*
|
||||||
|
Copyright 2021 The Matrix.org Foundation C.I.C.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
.mx_EventTile[data-layout=bubble],
|
||||||
|
.mx_EventTile[data-layout=bubble] ~ .mx_EventListSummary {
|
||||||
|
--avatarSize: 32px;
|
||||||
|
--gutterSize: 11px;
|
||||||
|
--cornerRadius: 12px;
|
||||||
|
--maxWidth: 70%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_EventTile[data-layout=bubble] {
|
||||||
|
|
||||||
|
position: relative;
|
||||||
|
margin-top: var(--gutterSize);
|
||||||
|
margin-left: 50px;
|
||||||
|
margin-right: 100px;
|
||||||
|
|
||||||
|
&.mx_EventTile_continuation {
|
||||||
|
margin-top: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* For replies */
|
||||||
|
.mx_EventTile {
|
||||||
|
padding-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
&::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: -1px;
|
||||||
|
bottom: -1px;
|
||||||
|
left: -60px;
|
||||||
|
right: -60px;
|
||||||
|
z-index: -1;
|
||||||
|
background: $eventbubble-bg-hover;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_EventTile_avatar {
|
||||||
|
img {
|
||||||
|
box-shadow: 0 0 0 3px $eventbubble-bg-hover;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_SenderProfile,
|
||||||
|
.mx_EventTile_line {
|
||||||
|
width: fit-content;
|
||||||
|
max-width: 70%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_SenderProfile {
|
||||||
|
position: relative;
|
||||||
|
top: -2px;
|
||||||
|
left: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&[data-self=false] {
|
||||||
|
.mx_EventTile_line {
|
||||||
|
border-bottom-right-radius: var(--cornerRadius);
|
||||||
|
}
|
||||||
|
.mx_EventTile_avatar {
|
||||||
|
left: -34px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_MessageActionBar {
|
||||||
|
right: 0;
|
||||||
|
transform: translate3d(50%, 50%, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
--backgroundColor: $eventbubble-others-bg;
|
||||||
|
}
|
||||||
|
&[data-self=true] {
|
||||||
|
.mx_EventTile_line {
|
||||||
|
border-bottom-left-radius: var(--cornerRadius);
|
||||||
|
float: right;
|
||||||
|
> a {
|
||||||
|
left: auto;
|
||||||
|
right: -48px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.mx_SenderProfile {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.mx_ReactionsRow {
|
||||||
|
float: right;
|
||||||
|
clear: right;
|
||||||
|
display: flex;
|
||||||
|
|
||||||
|
/* Moving the "add reaction button" before the reactions */
|
||||||
|
> :last-child {
|
||||||
|
order: -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.mx_EventTile_avatar {
|
||||||
|
top: -19px; // height of the sender block
|
||||||
|
right: -35px;
|
||||||
|
}
|
||||||
|
|
||||||
|
--backgroundColor: $eventbubble-self-bg;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_EventTile_line {
|
||||||
|
position: relative;
|
||||||
|
padding: var(--gutterSize);
|
||||||
|
border-top-left-radius: var(--cornerRadius);
|
||||||
|
border-top-right-radius: var(--cornerRadius);
|
||||||
|
background: var(--backgroundColor);
|
||||||
|
display: flex;
|
||||||
|
gap: 5px;
|
||||||
|
margin: 0 -12px 0 -9px;
|
||||||
|
> a {
|
||||||
|
position: absolute;
|
||||||
|
left: -48px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.mx_EventTile_continuation[data-self=false] .mx_EventTile_line {
|
||||||
|
border-top-left-radius: 0;
|
||||||
|
}
|
||||||
|
&.mx_EventTile_lastInSection[data-self=false] .mx_EventTile_line {
|
||||||
|
border-bottom-left-radius: var(--cornerRadius);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.mx_EventTile_continuation[data-self=true] .mx_EventTile_line {
|
||||||
|
border-top-right-radius: 0;
|
||||||
|
}
|
||||||
|
&.mx_EventTile_lastInSection[data-self=true] .mx_EventTile_line {
|
||||||
|
border-bottom-right-radius: var(--cornerRadius);
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_EventTile_avatar {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
line-height: 1;
|
||||||
|
img {
|
||||||
|
box-shadow: 0 0 0 3px $eventbubble-avatar-outline;
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&[data-has-reply=true] {
|
||||||
|
> .mx_EventTile_line {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_ReplyThread_show {
|
||||||
|
order: 99999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_ReplyThread {
|
||||||
|
margin: 0 calc(-1 * var(--gutterSize));
|
||||||
|
|
||||||
|
.mx_EventTile_reply {
|
||||||
|
max-width: 90%;
|
||||||
|
padding: 0;
|
||||||
|
> a {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_EventTile {
|
||||||
|
display: flex;
|
||||||
|
gap: var(--gutterSize);
|
||||||
|
.mx_EventTile_avatar {
|
||||||
|
position: static;
|
||||||
|
}
|
||||||
|
.mx_SenderProfile {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_EditMessageComposer_buttons {
|
||||||
|
position: static;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_ReactionsRow {
|
||||||
|
margin-right: -18px;
|
||||||
|
margin-left: -9px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_ReplyThread {
|
||||||
|
border-left-width: 2px;
|
||||||
|
border-left-color: $eventbubble-reply-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.mx_EventTile_bubbleContainer,
|
||||||
|
&.mx_EventTile_info,
|
||||||
|
& ~ .mx_EventListSummary[data-expanded=false] {
|
||||||
|
--backgroundColor: transparent;
|
||||||
|
--gutterSize: 0;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
.mx_EventTile_avatar {
|
||||||
|
position: static;
|
||||||
|
order: -1;
|
||||||
|
margin-right: 5px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
& ~ .mx_EventListSummary {
|
||||||
|
--maxWidth: 80%;
|
||||||
|
margin-left: calc(var(--avatarSize) + var(--gutterSize));
|
||||||
|
margin-right: calc(var(--gutterSize) + var(--avatarSize));
|
||||||
|
.mx_EventListSummary_toggle {
|
||||||
|
float: none;
|
||||||
|
margin: 0;
|
||||||
|
order: 9;
|
||||||
|
margin-left: 5px;
|
||||||
|
}
|
||||||
|
.mx_EventListSummary_avatars {
|
||||||
|
padding-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
content: "";
|
||||||
|
clear: both;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_EventTile {
|
||||||
|
margin: 0 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_EventTile_line {
|
||||||
|
margin: 0 5px;
|
||||||
|
> a {
|
||||||
|
left: auto;
|
||||||
|
right: 0;
|
||||||
|
transform: translateX(calc(100% + 5px));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_MessageActionBar {
|
||||||
|
transform: translate3d(50%, 0, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
& ~ .mx_EventListSummary[data-expanded=false] {
|
||||||
|
padding: 0 34px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* events that do not require bubble layout */
|
||||||
|
& ~ .mx_EventListSummary,
|
||||||
|
&.mx_EventTile_bad {
|
||||||
|
.mx_EventTile_line {
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
&::before {
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
& + .mx_EventListSummary {
|
||||||
|
.mx_EventTile {
|
||||||
|
margin-top: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_EventListSummary_toggle {
|
||||||
|
margin-right: 55px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Special layout scenario for "Unable To Decrypt (UTD)" events */
|
||||||
|
&.mx_EventTile_bad > .mx_EventTile_line {
|
||||||
|
display: grid;
|
||||||
|
grid-template:
|
||||||
|
"reply reply" auto
|
||||||
|
"shield body" auto
|
||||||
|
"shield link" auto
|
||||||
|
/ auto 1fr;
|
||||||
|
.mx_EventTile_e2eIcon {
|
||||||
|
grid-area: shield;
|
||||||
|
}
|
||||||
|
.mx_UnknownBody {
|
||||||
|
grid-area: body;
|
||||||
|
}
|
||||||
|
.mx_EventTile_keyRequestInfo {
|
||||||
|
grid-area: link;
|
||||||
|
}
|
||||||
|
.mx_ReplyThread_wrapper {
|
||||||
|
grid-area: reply;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.mx_EventTile_readAvatars {
|
||||||
|
position: absolute;
|
||||||
|
right: -110px;
|
||||||
|
bottom: 0;
|
||||||
|
top: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_MTextBody {
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,6 +1,6 @@
|
||||||
/*
|
/*
|
||||||
Copyright 2015, 2016 OpenMarket Ltd
|
Copyright 2015, 2016 OpenMarket Ltd
|
||||||
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");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
|
@ -18,15 +18,14 @@ limitations under the License.
|
||||||
$left-gutter: 64px;
|
$left-gutter: 64px;
|
||||||
$hover-select-border: 4px;
|
$hover-select-border: 4px;
|
||||||
|
|
||||||
.mx_EventTile {
|
.mx_EventTile:not([data-layout=bubble]) {
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
clear: both;
|
clear: both;
|
||||||
padding-top: 18px;
|
padding-top: 18px;
|
||||||
font-size: $font-14px;
|
font-size: $font-14px;
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
|
||||||
|
|
||||||
.mx_EventTile.mx_EventTile_info {
|
&.mx_EventTile_info {
|
||||||
padding-top: 1px;
|
padding-top: 1px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -37,12 +36,12 @@ $hover-select-border: 4px;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_EventTile.mx_EventTile_info .mx_EventTile_avatar {
|
&.mx_EventTile_info .mx_EventTile_avatar {
|
||||||
top: $font-6px;
|
top: $font-6px;
|
||||||
left: $left-gutter;
|
left: $left-gutter;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_EventTile_continuation {
|
&.mx_EventTile_continuation {
|
||||||
padding-top: 0px !important;
|
padding-top: 0px !important;
|
||||||
|
|
||||||
&.mx_EventTile_isEditing {
|
&.mx_EventTile_isEditing {
|
||||||
|
@ -51,11 +50,11 @@ $hover-select-border: 4px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_EventTile_isEditing {
|
&.mx_EventTile_isEditing {
|
||||||
background-color: $header-panel-bg-color;
|
background-color: $header-panel-bg-color;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_EventTile .mx_SenderProfile {
|
.mx_SenderProfile {
|
||||||
color: $primary-fg-color;
|
color: $primary-fg-color;
|
||||||
font-size: $font-14px;
|
font-size: $font-14px;
|
||||||
display: inline-block; /* anti-zalgo, with overflow hidden */
|
display: inline-block; /* anti-zalgo, with overflow hidden */
|
||||||
|
@ -70,7 +69,7 @@ $hover-select-border: 4px;
|
||||||
max-width: calc(100% - $left-gutter);
|
max-width: calc(100% - $left-gutter);
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_EventTile .mx_SenderProfile .mx_Flair {
|
.mx_SenderProfile .mx_Flair {
|
||||||
opacity: 0.7;
|
opacity: 0.7;
|
||||||
margin-left: 5px;
|
margin-left: 5px;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
|
@ -85,11 +84,11 @@ $hover-select-border: 4px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_EventTile_isEditing .mx_MessageTimestamp {
|
&.mx_EventTile_isEditing .mx_MessageTimestamp {
|
||||||
visibility: hidden;
|
visibility: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_EventTile .mx_MessageTimestamp {
|
.mx_MessageTimestamp {
|
||||||
display: block;
|
display: block;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
left: 0px;
|
left: 0px;
|
||||||
|
@ -97,7 +96,7 @@ $hover-select-border: 4px;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_EventTile_continuation .mx_EventTile_line {
|
&.mx_EventTile_continuation .mx_EventTile_line {
|
||||||
clear: both;
|
clear: both;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -107,63 +106,25 @@ $hover-select-border: 4px;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_RoomView_timeline_rr_enabled,
|
|
||||||
// on ELS we need the margin to allow interaction with the expand/collapse button which is normally in the RR gutter
|
|
||||||
.mx_EventListSummary {
|
|
||||||
.mx_EventTile_line {
|
|
||||||
/* ideally should be 100px, but 95px gives us a max thumbnail size of 800x600, which is nice */
|
|
||||||
margin-right: 110px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_EventTile_bubbleContainer {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 1fr 100px;
|
|
||||||
|
|
||||||
.mx_EventTile_line {
|
|
||||||
margin-right: 0;
|
|
||||||
grid-column: 1 / 3;
|
|
||||||
// override default padding of mx_EventTile_line so that we can be centered
|
|
||||||
padding: 0 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_EventTile_msgOption {
|
|
||||||
grid-column: 2;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_EventTile_reply {
|
.mx_EventTile_reply {
|
||||||
margin-right: 10px;
|
margin-right: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* HACK to override line-height which is already marked important elsewhere */
|
&.mx_EventTile_selected > div > a > .mx_MessageTimestamp {
|
||||||
.mx_EventTile_bigEmoji.mx_EventTile_bigEmoji {
|
|
||||||
font-size: 48px !important;
|
|
||||||
line-height: 57px !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_EventTile_selected > div > a > .mx_MessageTimestamp {
|
|
||||||
left: calc(-$hover-select-border);
|
left: calc(-$hover-select-border);
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_EventTile:hover .mx_MessageActionBar,
|
|
||||||
.mx_EventTile.mx_EventTile_actionBarFocused .mx_MessageActionBar,
|
|
||||||
[data-whatinput='keyboard'] .mx_EventTile:focus-within .mx_MessageActionBar,
|
|
||||||
.mx_EventTile.focus-visible:focus-within .mx_MessageActionBar {
|
|
||||||
visibility: visible;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* this is used for the tile for the event which is selected via the URL.
|
/* this is used for the tile for the event which is selected via the URL.
|
||||||
* TODO: ultimately we probably want some transition on here.
|
* TODO: ultimately we probably want some transition on here.
|
||||||
*/
|
*/
|
||||||
.mx_EventTile_selected > .mx_EventTile_line {
|
&.mx_EventTile_selected > .mx_EventTile_line {
|
||||||
border-left: $accent-color 4px solid;
|
border-left: $accent-color 4px solid;
|
||||||
padding-left: calc($left-gutter - $hover-select-border);
|
padding-left: calc($left-gutter - $hover-select-border);
|
||||||
background-color: $event-selected-color;
|
background-color: $event-selected-color;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_EventTile_highlight,
|
&.mx_EventTile_highlight,
|
||||||
.mx_EventTile_highlight .markdown-body {
|
&.mx_EventTile_highlight .markdown-body {
|
||||||
color: $event-highlight-fg-color;
|
color: $event-highlight-fg-color;
|
||||||
|
|
||||||
.mx_EventTile_line {
|
.mx_EventTile_line {
|
||||||
|
@ -171,17 +132,17 @@ $hover-select-border: 4px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_EventTile_info .mx_EventTile_line {
|
&.mx_EventTile_info .mx_EventTile_line {
|
||||||
padding-left: calc($left-gutter + 18px);
|
padding-left: calc($left-gutter + 18px);
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_EventTile_selected.mx_EventTile_info .mx_EventTile_line {
|
&.mx_EventTile_selected.mx_EventTile_info .mx_EventTile_line {
|
||||||
padding-left: calc($left-gutter + 18px - $hover-select-border);
|
padding-left: calc($left-gutter + 18px - $hover-select-border);
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_EventTile:hover .mx_EventTile_line,
|
&.mx_EventTile:hover .mx_EventTile_line,
|
||||||
.mx_EventTile.mx_EventTile_actionBarFocused .mx_EventTile_line,
|
&.mx_EventTile.mx_EventTile_actionBarFocused .mx_EventTile_line,
|
||||||
.mx_EventTile.focus-visible:focus-within .mx_EventTile_line {
|
&.mx_EventTile.focus-visible:focus-within .mx_EventTile_line {
|
||||||
background-color: $event-selected-color;
|
background-color: $event-selected-color;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -225,7 +186,7 @@ $hover-select-border: 4px;
|
||||||
mask-image: url('$(res)/img/element-icons/circle-sending.svg');
|
mask-image: url('$(res)/img/element-icons/circle-sending.svg');
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_EventTile_contextual {
|
&.mx_EventTile_contextual {
|
||||||
opacity: 0.4;
|
opacity: 0.4;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -247,36 +208,6 @@ $hover-select-border: 4px;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_EventTile_readAvatars {
|
|
||||||
position: relative;
|
|
||||||
display: inline-block;
|
|
||||||
width: 14px;
|
|
||||||
height: 14px;
|
|
||||||
// This aligns the avatar with the last line of the
|
|
||||||
// message. We want to move it one line up - 2.2rem
|
|
||||||
top: -2.2rem;
|
|
||||||
user-select: none;
|
|
||||||
z-index: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_EventTile_readAvatars .mx_BaseAvatar {
|
|
||||||
position: absolute;
|
|
||||||
display: inline-block;
|
|
||||||
height: $font-14px;
|
|
||||||
width: $font-14px;
|
|
||||||
|
|
||||||
will-change: left, top;
|
|
||||||
transition:
|
|
||||||
left var(--transition-short) ease-out,
|
|
||||||
top var(--transition-standard) ease-out;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_EventTile_readAvatarRemainder {
|
|
||||||
color: $event-timestamp-color;
|
|
||||||
font-size: $font-11px;
|
|
||||||
position: absolute;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* all the overflow-y: hidden; are to trap Zalgos -
|
/* all the overflow-y: hidden; are to trap Zalgos -
|
||||||
but they introduce an implicit overflow-x: auto.
|
but they introduce an implicit overflow-x: auto.
|
||||||
so make that explicitly hidden too to avoid random
|
so make that explicitly hidden too to avoid random
|
||||||
|
@ -314,15 +245,147 @@ $hover-select-border: 4px;
|
||||||
filter: none;
|
filter: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&:hover.mx_EventTile_verified .mx_EventTile_line,
|
||||||
|
&:hover.mx_EventTile_unverified .mx_EventTile_line,
|
||||||
|
&:hover.mx_EventTile_unknown .mx_EventTile_line {
|
||||||
|
padding-left: calc($left-gutter - $hover-select-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover.mx_EventTile_verified .mx_EventTile_line {
|
||||||
|
border-left: $e2e-verified-color $EventTile_e2e_state_indicator_width solid;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover.mx_EventTile_unverified .mx_EventTile_line {
|
||||||
|
border-left: $e2e-unverified-color $EventTile_e2e_state_indicator_width solid;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover.mx_EventTile_unknown .mx_EventTile_line {
|
||||||
|
border-left: $e2e-unknown-color $EventTile_e2e_state_indicator_width solid;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover.mx_EventTile_verified.mx_EventTile_info .mx_EventTile_line,
|
||||||
|
&:hover.mx_EventTile_unverified.mx_EventTile_info .mx_EventTile_line,
|
||||||
|
&:hover.mx_EventTile_unknown.mx_EventTile_info .mx_EventTile_line {
|
||||||
|
padding-left: calc($left-gutter + 18px - $hover-select-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* End to end encryption stuff */
|
||||||
|
&:hover .mx_EventTile_e2eIcon {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Explicit relationships so that it doesn't apply to nested EventTile components (e.g in Replies)
|
||||||
|
&:hover.mx_EventTile_verified .mx_EventTile_line > a > .mx_MessageTimestamp,
|
||||||
|
&:hover.mx_EventTile_unverified .mx_EventTile_line > a > .mx_MessageTimestamp,
|
||||||
|
&:hover.mx_EventTile_unknown .mx_EventTile_line > a > .mx_MessageTimestamp {
|
||||||
|
left: calc(-$hover-select-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Explicit relationships so that it doesn't apply to nested EventTile components (e.g in Replies)
|
||||||
|
&:hover.mx_EventTile_verified .mx_EventTile_line > .mx_EventTile_e2eIcon,
|
||||||
|
&:hover.mx_EventTile_unverified .mx_EventTile_line > .mx_EventTile_e2eIcon,
|
||||||
|
&:hover.mx_EventTile_unknown .mx_EventTile_line > .mx_EventTile_e2eIcon {
|
||||||
|
display: block;
|
||||||
|
left: 41px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_MImageBody {
|
||||||
|
margin-right: 34px;
|
||||||
|
}
|
||||||
|
|
||||||
.mx_EventTile_e2eIcon {
|
.mx_EventTile_e2eIcon {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 6px;
|
top: 6px;
|
||||||
left: 44px;
|
left: 44px;
|
||||||
|
bottom: 0;
|
||||||
|
right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_ReactionsRow {
|
||||||
|
margin: 0;
|
||||||
|
padding: 6px 60px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_RoomView_timeline_rr_enabled {
|
||||||
|
|
||||||
|
.mx_EventTile:not([data-layout=bubble]) {
|
||||||
|
.mx_EventTile_line {
|
||||||
|
/* ideally should be 100px, but 95px gives us a max thumbnail size of 800x600, which is nice */
|
||||||
|
margin-right: 110px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// on ELS we need the margin to allow interaction with the expand/collapse button which is normally in the RR gutter
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_EventTile_bubbleContainer {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 100px;
|
||||||
|
|
||||||
|
.mx_EventTile_line {
|
||||||
|
margin-right: 0;
|
||||||
|
grid-column: 1 / 3;
|
||||||
|
// override default padding of mx_EventTile_line so that we can be centered
|
||||||
|
padding: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_EventTile_msgOption {
|
||||||
|
grid-column: 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_EventTile_readAvatars {
|
||||||
|
position: relative;
|
||||||
|
display: inline-block;
|
||||||
|
width: 14px;
|
||||||
|
height: 14px;
|
||||||
|
// This aligns the avatar with the last line of the
|
||||||
|
// message. We want to move it one line up - 2.2rem
|
||||||
|
top: -2.2rem;
|
||||||
|
user-select: none;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_EventTile_readAvatars .mx_BaseAvatar {
|
||||||
|
position: absolute;
|
||||||
|
display: inline-block;
|
||||||
|
height: $font-14px;
|
||||||
|
width: $font-14px;
|
||||||
|
|
||||||
|
will-change: left, top;
|
||||||
|
transition:
|
||||||
|
left var(--transition-short) ease-out,
|
||||||
|
top var(--transition-standard) ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_EventTile_readAvatarRemainder {
|
||||||
|
color: $event-timestamp-color;
|
||||||
|
font-size: $font-11px;
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* HACK to override line-height which is already marked important elsewhere */
|
||||||
|
.mx_EventTile_bigEmoji.mx_EventTile_bigEmoji {
|
||||||
|
font-size: 48px !important;
|
||||||
|
line-height: 57px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_EventTile_content .mx_EventTile_edited {
|
||||||
|
user-select: none;
|
||||||
|
font-size: $font-12px;
|
||||||
|
color: $roomtopic-color;
|
||||||
|
display: inline-block;
|
||||||
|
margin-left: 9px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.mx_EventTile_e2eIcon {
|
||||||
|
position: relative;
|
||||||
width: 14px;
|
width: 14px;
|
||||||
height: 14px;
|
height: 14px;
|
||||||
display: block;
|
display: block;
|
||||||
bottom: 0;
|
|
||||||
right: 0;
|
|
||||||
opacity: 0.2;
|
opacity: 0.2;
|
||||||
background-repeat: no-repeat;
|
background-repeat: no-repeat;
|
||||||
background-size: contain;
|
background-size: contain;
|
||||||
|
@ -381,87 +444,6 @@ $hover-select-border: 4px;
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_EventTile_keyRequestInfo {
|
|
||||||
font-size: $font-12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_EventTile_keyRequestInfo_text {
|
|
||||||
opacity: 0.5;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_EventTile_keyRequestInfo_text a {
|
|
||||||
color: $primary-fg-color;
|
|
||||||
text-decoration: underline;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_EventTile_keyRequestInfo_tooltip_contents p {
|
|
||||||
text-align: auto;
|
|
||||||
margin-left: 3px;
|
|
||||||
margin-right: 3px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_EventTile_keyRequestInfo_tooltip_contents p:first-child {
|
|
||||||
margin-top: 0px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_EventTile_keyRequestInfo_tooltip_contents p:last-child {
|
|
||||||
margin-bottom: 0px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_EventTile:hover.mx_EventTile_verified .mx_EventTile_line,
|
|
||||||
.mx_EventTile:hover.mx_EventTile_unverified .mx_EventTile_line,
|
|
||||||
.mx_EventTile:hover.mx_EventTile_unknown .mx_EventTile_line {
|
|
||||||
padding-left: calc($left-gutter - $hover-select-border);
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_EventTile:hover.mx_EventTile_verified .mx_EventTile_line {
|
|
||||||
border-left: $e2e-verified-color $EventTile_e2e_state_indicator_width solid;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_EventTile:hover.mx_EventTile_unverified .mx_EventTile_line {
|
|
||||||
border-left: $e2e-unverified-color $EventTile_e2e_state_indicator_width solid;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_EventTile:hover.mx_EventTile_unknown .mx_EventTile_line {
|
|
||||||
border-left: $e2e-unknown-color $EventTile_e2e_state_indicator_width solid;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_EventTile:hover.mx_EventTile_verified.mx_EventTile_info .mx_EventTile_line,
|
|
||||||
.mx_EventTile:hover.mx_EventTile_unverified.mx_EventTile_info .mx_EventTile_line,
|
|
||||||
.mx_EventTile:hover.mx_EventTile_unknown.mx_EventTile_info .mx_EventTile_line {
|
|
||||||
padding-left: calc($left-gutter + 18px - $hover-select-border);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* End to end encryption stuff */
|
|
||||||
.mx_EventTile:hover .mx_EventTile_e2eIcon {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Explicit relationships so that it doesn't apply to nested EventTile components (e.g in Replies)
|
|
||||||
.mx_EventTile:hover.mx_EventTile_verified .mx_EventTile_line > a > .mx_MessageTimestamp,
|
|
||||||
.mx_EventTile:hover.mx_EventTile_unverified .mx_EventTile_line > a > .mx_MessageTimestamp,
|
|
||||||
.mx_EventTile:hover.mx_EventTile_unknown .mx_EventTile_line > a > .mx_MessageTimestamp {
|
|
||||||
left: calc(-$hover-select-border);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Explicit relationships so that it doesn't apply to nested EventTile components (e.g in Replies)
|
|
||||||
.mx_EventTile:hover.mx_EventTile_verified .mx_EventTile_line > .mx_EventTile_e2eIcon,
|
|
||||||
.mx_EventTile:hover.mx_EventTile_unverified .mx_EventTile_line > .mx_EventTile_e2eIcon,
|
|
||||||
.mx_EventTile:hover.mx_EventTile_unknown .mx_EventTile_line > .mx_EventTile_e2eIcon {
|
|
||||||
display: block;
|
|
||||||
left: 41px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx_EventTile_content .mx_EventTile_edited {
|
|
||||||
user-select: none;
|
|
||||||
font-size: $font-12px;
|
|
||||||
color: $roomtopic-color;
|
|
||||||
display: inline-block;
|
|
||||||
margin-left: 9px;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Various markdown overrides */
|
/* Various markdown overrides */
|
||||||
|
|
||||||
.mx_EventTile_body pre {
|
.mx_EventTile_body pre {
|
||||||
|
@ -595,6 +577,35 @@ $hover-select-border: 4px;
|
||||||
|
|
||||||
/* end of overrides */
|
/* end of overrides */
|
||||||
|
|
||||||
|
|
||||||
|
.mx_EventTile_keyRequestInfo {
|
||||||
|
font-size: $font-12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_EventTile_keyRequestInfo_text {
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_EventTile_keyRequestInfo_text a {
|
||||||
|
color: $primary-fg-color;
|
||||||
|
text-decoration: underline;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_EventTile_keyRequestInfo_tooltip_contents p {
|
||||||
|
text-align: auto;
|
||||||
|
margin-left: 3px;
|
||||||
|
margin-right: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_EventTile_keyRequestInfo_tooltip_contents p:first-child {
|
||||||
|
margin-top: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_EventTile_keyRequestInfo_tooltip_contents p:last-child {
|
||||||
|
margin-bottom: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
.mx_EventTile_tileError {
|
.mx_EventTile_tileError {
|
||||||
color: red;
|
color: red;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
@ -615,6 +626,13 @@ $hover-select-border: 4px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mx_EventTile:hover .mx_MessageActionBar,
|
||||||
|
.mx_EventTile.mx_EventTile_actionBarFocused .mx_MessageActionBar,
|
||||||
|
[data-whatinput='keyboard'] .mx_EventTile:focus-within .mx_MessageActionBar,
|
||||||
|
.mx_EventTile.focus-visible:focus-within .mx_MessageActionBar {
|
||||||
|
visibility: visible;
|
||||||
|
}
|
||||||
|
|
||||||
@media only screen and (max-width: 480px) {
|
@media only screen and (max-width: 480px) {
|
||||||
.mx_EventTile_line, .mx_EventTile_reply {
|
.mx_EventTile_line, .mx_EventTile_reply {
|
||||||
padding-left: 0;
|
padding-left: 0;
|
||||||
|
|
|
@ -227,6 +227,13 @@ $groupFilterPanel-background-blur-amount: 30px;
|
||||||
|
|
||||||
$composer-shadow-color: rgba(0, 0, 0, 0.28);
|
$composer-shadow-color: rgba(0, 0, 0, 0.28);
|
||||||
|
|
||||||
|
// Bubble tiles
|
||||||
|
$eventbubble-self-bg: #143A34;
|
||||||
|
$eventbubble-others-bg: #394049;
|
||||||
|
$eventbubble-bg-hover: #433C23;
|
||||||
|
$eventbubble-avatar-outline: $bg-color;
|
||||||
|
$eventbubble-reply-color: #C1C6CD;
|
||||||
|
|
||||||
// ***** Mixins! *****
|
// ***** Mixins! *****
|
||||||
|
|
||||||
@define-mixin mx_DialogButton {
|
@define-mixin mx_DialogButton {
|
||||||
|
|
|
@ -347,6 +347,13 @@ $appearance-tab-border-color: $input-darker-bg-color;
|
||||||
|
|
||||||
$composer-shadow-color: tranparent;
|
$composer-shadow-color: tranparent;
|
||||||
|
|
||||||
|
// Bubble tiles
|
||||||
|
$eventbubble-self-bg: #F8FDFC;
|
||||||
|
$eventbubble-others-bg: #F7F8F9;
|
||||||
|
$eventbubble-bg-hover: rgb(242, 242, 242);
|
||||||
|
$eventbubble-avatar-outline: #fff;
|
||||||
|
$eventbubble-reply-color: #C1C6CD;
|
||||||
|
|
||||||
// ***** Mixins! *****
|
// ***** Mixins! *****
|
||||||
|
|
||||||
@define-mixin mx_DialogButton {
|
@define-mixin mx_DialogButton {
|
||||||
|
|
|
@ -349,6 +349,13 @@ $groupFilterPanel-background-blur-amount: 20px;
|
||||||
|
|
||||||
$composer-shadow-color: rgba(0, 0, 0, 0.04);
|
$composer-shadow-color: rgba(0, 0, 0, 0.04);
|
||||||
|
|
||||||
|
// Bubble tiles
|
||||||
|
$eventbubble-self-bg: #F8FDFC;
|
||||||
|
$eventbubble-others-bg: #F7F8F9;
|
||||||
|
$eventbubble-bg-hover: #FEFCF5;
|
||||||
|
$eventbubble-avatar-outline: $primary-bg-color;
|
||||||
|
$eventbubble-reply-color: #C1C6CD;
|
||||||
|
|
||||||
// ***** Mixins! *****
|
// ***** Mixins! *****
|
||||||
|
|
||||||
@define-mixin mx_DialogButton {
|
@define-mixin mx_DialogButton {
|
||||||
|
|
4
src/@types/global.d.ts
vendored
4
src/@types/global.d.ts
vendored
|
@ -50,6 +50,8 @@ import UIStore from "../stores/UIStore";
|
||||||
import { SetupEncryptionStore } from "../stores/SetupEncryptionStore";
|
import { SetupEncryptionStore } from "../stores/SetupEncryptionStore";
|
||||||
import { RoomScrollStateStore } from "../stores/RoomScrollStateStore";
|
import { RoomScrollStateStore } from "../stores/RoomScrollStateStore";
|
||||||
|
|
||||||
|
/* eslint-disable @typescript-eslint/naming-convention */
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface Window {
|
interface Window {
|
||||||
matrixChat: ReturnType<Renderer>;
|
matrixChat: ReturnType<Renderer>;
|
||||||
|
@ -186,3 +188,5 @@ declare global {
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* eslint-enable @typescript-eslint/naming-convention */
|
||||||
|
|
|
@ -270,7 +270,7 @@ export class Analytics {
|
||||||
localStorage.removeItem(LAST_VISIT_TS_KEY);
|
localStorage.removeItem(LAST_VISIT_TS_KEY);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _track(data: IData) {
|
private async track(data: IData) {
|
||||||
if (this.disabled) return;
|
if (this.disabled) return;
|
||||||
|
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
|
@ -304,7 +304,7 @@ export class Analytics {
|
||||||
}
|
}
|
||||||
|
|
||||||
public ping() {
|
public ping() {
|
||||||
this._track({
|
this.track({
|
||||||
ping: "1",
|
ping: "1",
|
||||||
});
|
});
|
||||||
localStorage.setItem(LAST_VISIT_TS_KEY, String(new Date().getTime())); // update last visit ts
|
localStorage.setItem(LAST_VISIT_TS_KEY, String(new Date().getTime())); // update last visit ts
|
||||||
|
@ -324,14 +324,14 @@ export class Analytics {
|
||||||
// But continue anyway because we still want to track the change
|
// But continue anyway because we still want to track the change
|
||||||
}
|
}
|
||||||
|
|
||||||
this._track({
|
this.track({
|
||||||
gt_ms: String(generationTimeMs),
|
gt_ms: String(generationTimeMs),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public trackEvent(category: string, action: string, name?: string, value?: string) {
|
public trackEvent(category: string, action: string, name?: string, value?: string) {
|
||||||
if (this.disabled) return;
|
if (this.disabled) return;
|
||||||
this._track({
|
this.track({
|
||||||
e_c: category,
|
e_c: category,
|
||||||
e_a: action,
|
e_a: action,
|
||||||
e_n: name,
|
e_n: name,
|
||||||
|
|
|
@ -33,6 +33,7 @@ import { isSecretStorageBeingAccessed, accessSecretStorage } from "./SecurityMan
|
||||||
import { isSecureBackupRequired } from './utils/WellKnownUtils';
|
import { isSecureBackupRequired } from './utils/WellKnownUtils';
|
||||||
import { isLoggedIn } from './components/structures/MatrixChat';
|
import { isLoggedIn } from './components/structures/MatrixChat';
|
||||||
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
|
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
|
||||||
|
import { ActionPayload } from "./dispatcher/payloads";
|
||||||
|
|
||||||
const KEY_BACKUP_POLL_INTERVAL = 5 * 60 * 1000;
|
const KEY_BACKUP_POLL_INTERVAL = 5 * 60 * 1000;
|
||||||
|
|
||||||
|
@ -58,28 +59,28 @@ export default class DeviceListener {
|
||||||
}
|
}
|
||||||
|
|
||||||
start() {
|
start() {
|
||||||
MatrixClientPeg.get().on('crypto.willUpdateDevices', this._onWillUpdateDevices);
|
MatrixClientPeg.get().on('crypto.willUpdateDevices', this.onWillUpdateDevices);
|
||||||
MatrixClientPeg.get().on('crypto.devicesUpdated', this._onDevicesUpdated);
|
MatrixClientPeg.get().on('crypto.devicesUpdated', this.onDevicesUpdated);
|
||||||
MatrixClientPeg.get().on('deviceVerificationChanged', this._onDeviceVerificationChanged);
|
MatrixClientPeg.get().on('deviceVerificationChanged', this.onDeviceVerificationChanged);
|
||||||
MatrixClientPeg.get().on('userTrustStatusChanged', this._onUserTrustStatusChanged);
|
MatrixClientPeg.get().on('userTrustStatusChanged', this.onUserTrustStatusChanged);
|
||||||
MatrixClientPeg.get().on('crossSigning.keysChanged', this._onCrossSingingKeysChanged);
|
MatrixClientPeg.get().on('crossSigning.keysChanged', this.onCrossSingingKeysChanged);
|
||||||
MatrixClientPeg.get().on('accountData', this._onAccountData);
|
MatrixClientPeg.get().on('accountData', this.onAccountData);
|
||||||
MatrixClientPeg.get().on('sync', this._onSync);
|
MatrixClientPeg.get().on('sync', this.onSync);
|
||||||
MatrixClientPeg.get().on('RoomState.events', this._onRoomStateEvents);
|
MatrixClientPeg.get().on('RoomState.events', this.onRoomStateEvents);
|
||||||
this.dispatcherRef = dis.register(this._onAction);
|
this.dispatcherRef = dis.register(this.onAction);
|
||||||
this._recheck();
|
this.recheck();
|
||||||
}
|
}
|
||||||
|
|
||||||
stop() {
|
stop() {
|
||||||
if (MatrixClientPeg.get()) {
|
if (MatrixClientPeg.get()) {
|
||||||
MatrixClientPeg.get().removeListener('crypto.willUpdateDevices', this._onWillUpdateDevices);
|
MatrixClientPeg.get().removeListener('crypto.willUpdateDevices', this.onWillUpdateDevices);
|
||||||
MatrixClientPeg.get().removeListener('crypto.devicesUpdated', this._onDevicesUpdated);
|
MatrixClientPeg.get().removeListener('crypto.devicesUpdated', this.onDevicesUpdated);
|
||||||
MatrixClientPeg.get().removeListener('deviceVerificationChanged', this._onDeviceVerificationChanged);
|
MatrixClientPeg.get().removeListener('deviceVerificationChanged', this.onDeviceVerificationChanged);
|
||||||
MatrixClientPeg.get().removeListener('userTrustStatusChanged', this._onUserTrustStatusChanged);
|
MatrixClientPeg.get().removeListener('userTrustStatusChanged', this.onUserTrustStatusChanged);
|
||||||
MatrixClientPeg.get().removeListener('crossSigning.keysChanged', this._onCrossSingingKeysChanged);
|
MatrixClientPeg.get().removeListener('crossSigning.keysChanged', this.onCrossSingingKeysChanged);
|
||||||
MatrixClientPeg.get().removeListener('accountData', this._onAccountData);
|
MatrixClientPeg.get().removeListener('accountData', this.onAccountData);
|
||||||
MatrixClientPeg.get().removeListener('sync', this._onSync);
|
MatrixClientPeg.get().removeListener('sync', this.onSync);
|
||||||
MatrixClientPeg.get().removeListener('RoomState.events', this._onRoomStateEvents);
|
MatrixClientPeg.get().removeListener('RoomState.events', this.onRoomStateEvents);
|
||||||
}
|
}
|
||||||
if (this.dispatcherRef) {
|
if (this.dispatcherRef) {
|
||||||
dis.unregister(this.dispatcherRef);
|
dis.unregister(this.dispatcherRef);
|
||||||
|
@ -103,15 +104,15 @@ export default class DeviceListener {
|
||||||
this.dismissed.add(d);
|
this.dismissed.add(d);
|
||||||
}
|
}
|
||||||
|
|
||||||
this._recheck();
|
this.recheck();
|
||||||
}
|
}
|
||||||
|
|
||||||
dismissEncryptionSetup() {
|
dismissEncryptionSetup() {
|
||||||
this.dismissedThisDeviceToast = true;
|
this.dismissedThisDeviceToast = true;
|
||||||
this._recheck();
|
this.recheck();
|
||||||
}
|
}
|
||||||
|
|
||||||
_ensureDeviceIdsAtStartPopulated() {
|
private ensureDeviceIdsAtStartPopulated() {
|
||||||
if (this.ourDeviceIdsAtStart === null) {
|
if (this.ourDeviceIdsAtStart === null) {
|
||||||
const cli = MatrixClientPeg.get();
|
const cli = MatrixClientPeg.get();
|
||||||
this.ourDeviceIdsAtStart = new Set(
|
this.ourDeviceIdsAtStart = new Set(
|
||||||
|
@ -120,39 +121,39 @@ export default class DeviceListener {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_onWillUpdateDevices = async (users: string[], initialFetch?: boolean) => {
|
private onWillUpdateDevices = async (users: string[], initialFetch?: boolean) => {
|
||||||
// If we didn't know about *any* devices before (ie. it's fresh login),
|
// If we didn't know about *any* devices before (ie. it's fresh login),
|
||||||
// then they are all pre-existing devices, so ignore this and set the
|
// then they are all pre-existing devices, so ignore this and set the
|
||||||
// devicesAtStart list to the devices that we see after the fetch.
|
// devicesAtStart list to the devices that we see after the fetch.
|
||||||
if (initialFetch) return;
|
if (initialFetch) return;
|
||||||
|
|
||||||
const myUserId = MatrixClientPeg.get().getUserId();
|
const myUserId = MatrixClientPeg.get().getUserId();
|
||||||
if (users.includes(myUserId)) this._ensureDeviceIdsAtStartPopulated();
|
if (users.includes(myUserId)) this.ensureDeviceIdsAtStartPopulated();
|
||||||
|
|
||||||
// No need to do a recheck here: we just need to get a snapshot of our devices
|
// No need to do a recheck here: we just need to get a snapshot of our devices
|
||||||
// before we download any new ones.
|
// before we download any new ones.
|
||||||
};
|
};
|
||||||
|
|
||||||
_onDevicesUpdated = (users: string[]) => {
|
private onDevicesUpdated = (users: string[]) => {
|
||||||
if (!users.includes(MatrixClientPeg.get().getUserId())) return;
|
if (!users.includes(MatrixClientPeg.get().getUserId())) return;
|
||||||
this._recheck();
|
this.recheck();
|
||||||
};
|
};
|
||||||
|
|
||||||
_onDeviceVerificationChanged = (userId: string) => {
|
private onDeviceVerificationChanged = (userId: string) => {
|
||||||
if (userId !== MatrixClientPeg.get().getUserId()) return;
|
if (userId !== MatrixClientPeg.get().getUserId()) return;
|
||||||
this._recheck();
|
this.recheck();
|
||||||
};
|
};
|
||||||
|
|
||||||
_onUserTrustStatusChanged = (userId: string) => {
|
private onUserTrustStatusChanged = (userId: string) => {
|
||||||
if (userId !== MatrixClientPeg.get().getUserId()) return;
|
if (userId !== MatrixClientPeg.get().getUserId()) return;
|
||||||
this._recheck();
|
this.recheck();
|
||||||
};
|
};
|
||||||
|
|
||||||
_onCrossSingingKeysChanged = () => {
|
private onCrossSingingKeysChanged = () => {
|
||||||
this._recheck();
|
this.recheck();
|
||||||
};
|
};
|
||||||
|
|
||||||
_onAccountData = (ev) => {
|
private onAccountData = (ev: MatrixEvent) => {
|
||||||
// User may have:
|
// User may have:
|
||||||
// * migrated SSSS to symmetric
|
// * migrated SSSS to symmetric
|
||||||
// * uploaded keys to secret storage
|
// * uploaded keys to secret storage
|
||||||
|
@ -163,32 +164,32 @@ export default class DeviceListener {
|
||||||
ev.getType().startsWith('m.cross_signing.') ||
|
ev.getType().startsWith('m.cross_signing.') ||
|
||||||
ev.getType() === 'm.megolm_backup.v1'
|
ev.getType() === 'm.megolm_backup.v1'
|
||||||
) {
|
) {
|
||||||
this._recheck();
|
this.recheck();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
_onSync = (state, prevState) => {
|
private onSync = (state, prevState) => {
|
||||||
if (state === 'PREPARED' && prevState === null) this._recheck();
|
if (state === 'PREPARED' && prevState === null) this.recheck();
|
||||||
};
|
};
|
||||||
|
|
||||||
_onRoomStateEvents = (ev: MatrixEvent) => {
|
private onRoomStateEvents = (ev: MatrixEvent) => {
|
||||||
if (ev.getType() !== "m.room.encryption") {
|
if (ev.getType() !== "m.room.encryption") {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If a room changes to encrypted, re-check as it may be our first
|
// If a room changes to encrypted, re-check as it may be our first
|
||||||
// encrypted room. This also catches encrypted room creation as well.
|
// encrypted room. This also catches encrypted room creation as well.
|
||||||
this._recheck();
|
this.recheck();
|
||||||
};
|
};
|
||||||
|
|
||||||
_onAction = ({ action }) => {
|
private onAction = ({ action }: ActionPayload) => {
|
||||||
if (action !== "on_logged_in") return;
|
if (action !== "on_logged_in") return;
|
||||||
this._recheck();
|
this.recheck();
|
||||||
};
|
};
|
||||||
|
|
||||||
// The server doesn't tell us when key backup is set up, so we poll
|
// The server doesn't tell us when key backup is set up, so we poll
|
||||||
// & cache the result
|
// & cache the result
|
||||||
async _getKeyBackupInfo() {
|
private async getKeyBackupInfo() {
|
||||||
const now = (new Date()).getTime();
|
const now = (new Date()).getTime();
|
||||||
if (!this.keyBackupInfo || this.keyBackupFetchedAt < now - KEY_BACKUP_POLL_INTERVAL) {
|
if (!this.keyBackupInfo || this.keyBackupFetchedAt < now - KEY_BACKUP_POLL_INTERVAL) {
|
||||||
this.keyBackupInfo = await MatrixClientPeg.get().getKeyBackupVersion();
|
this.keyBackupInfo = await MatrixClientPeg.get().getKeyBackupVersion();
|
||||||
|
@ -206,7 +207,7 @@ export default class DeviceListener {
|
||||||
return cli && cli.getRooms().some(r => cli.isRoomEncrypted(r.roomId));
|
return cli && cli.getRooms().some(r => cli.isRoomEncrypted(r.roomId));
|
||||||
}
|
}
|
||||||
|
|
||||||
async _recheck() {
|
private async recheck() {
|
||||||
const cli = MatrixClientPeg.get();
|
const cli = MatrixClientPeg.get();
|
||||||
|
|
||||||
if (!await cli.doesServerSupportUnstableFeature("org.matrix.e2e_cross_signing")) return;
|
if (!await cli.doesServerSupportUnstableFeature("org.matrix.e2e_cross_signing")) return;
|
||||||
|
@ -235,7 +236,7 @@ export default class DeviceListener {
|
||||||
// Cross-signing on account but this device doesn't trust the master key (verify this session)
|
// Cross-signing on account but this device doesn't trust the master key (verify this session)
|
||||||
showSetupEncryptionToast(SetupKind.VERIFY_THIS_SESSION);
|
showSetupEncryptionToast(SetupKind.VERIFY_THIS_SESSION);
|
||||||
} else {
|
} else {
|
||||||
const backupInfo = await this._getKeyBackupInfo();
|
const backupInfo = await this.getKeyBackupInfo();
|
||||||
if (backupInfo) {
|
if (backupInfo) {
|
||||||
// No cross-signing on account but key backup available (upgrade encryption)
|
// No cross-signing on account but key backup available (upgrade encryption)
|
||||||
showSetupEncryptionToast(SetupKind.UPGRADE_ENCRYPTION);
|
showSetupEncryptionToast(SetupKind.UPGRADE_ENCRYPTION);
|
||||||
|
@ -256,7 +257,7 @@ export default class DeviceListener {
|
||||||
|
|
||||||
// This needs to be done after awaiting on downloadKeys() above, so
|
// This needs to be done after awaiting on downloadKeys() above, so
|
||||||
// we make sure we get the devices after the fetch is done.
|
// we make sure we get the devices after the fetch is done.
|
||||||
this._ensureDeviceIdsAtStartPopulated();
|
this.ensureDeviceIdsAtStartPopulated();
|
||||||
|
|
||||||
// Unverified devices that were there last time the app ran
|
// Unverified devices that were there last time the app ran
|
||||||
// (technically could just be a boolean: we don't actually
|
// (technically could just be a boolean: we don't actually
|
||||||
|
|
|
@ -105,7 +105,7 @@ export interface IMatrixClientPeg {
|
||||||
* This module provides a singleton instance of this class so the 'current'
|
* This module provides a singleton instance of this class so the 'current'
|
||||||
* Matrix Client object is available easily.
|
* Matrix Client object is available easily.
|
||||||
*/
|
*/
|
||||||
class _MatrixClientPeg implements IMatrixClientPeg {
|
class MatrixClientPegClass implements IMatrixClientPeg {
|
||||||
// These are the default options used when when the
|
// These are the default options used when when the
|
||||||
// client is started in 'start'. These can be altered
|
// client is started in 'start'. These can be altered
|
||||||
// at any time up to after the 'will_start_client'
|
// at any time up to after the 'will_start_client'
|
||||||
|
@ -300,7 +300,7 @@ class _MatrixClientPeg implements IMatrixClientPeg {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!window.mxMatrixClientPeg) {
|
if (!window.mxMatrixClientPeg) {
|
||||||
window.mxMatrixClientPeg = new _MatrixClientPeg();
|
window.mxMatrixClientPeg = new MatrixClientPegClass();
|
||||||
}
|
}
|
||||||
|
|
||||||
export const MatrixClientPeg = window.mxMatrixClientPeg;
|
export const MatrixClientPeg = window.mxMatrixClientPeg;
|
||||||
|
|
|
@ -522,7 +522,7 @@ export const Commands = [
|
||||||
aliases: ['j', 'goto'],
|
aliases: ['j', 'goto'],
|
||||||
args: '<room-address>',
|
args: '<room-address>',
|
||||||
description: _td('Joins room with given address'),
|
description: _td('Joins room with given address'),
|
||||||
runFn: function(_, args) {
|
runFn: function(roomId, args) {
|
||||||
if (args) {
|
if (args) {
|
||||||
// Note: we support 2 versions of this command. The first is
|
// Note: we support 2 versions of this command. The first is
|
||||||
// the public-facing one for most users and the other is a
|
// the public-facing one for most users and the other is a
|
||||||
|
@ -1069,7 +1069,7 @@ export const Commands = [
|
||||||
command: "msg",
|
command: "msg",
|
||||||
description: _td("Sends a message to the given user"),
|
description: _td("Sends a message to the given user"),
|
||||||
args: "<user-id> <message>",
|
args: "<user-id> <message>",
|
||||||
runFn: function(_, args) {
|
runFn: function(roomId, args) {
|
||||||
if (args) {
|
if (args) {
|
||||||
// matches the first whitespace delimited group and then the rest of the string
|
// matches the first whitespace delimited group and then the rest of the string
|
||||||
const matches = args.match(/^(\S+?)(?: +(.*))?$/s);
|
const matches = args.match(/^(\S+?)(?: +(.*))?$/s);
|
||||||
|
|
|
@ -109,7 +109,7 @@ export default class UserProvider extends AutocompleteProvider {
|
||||||
limit = -1,
|
limit = -1,
|
||||||
): Promise<ICompletion[]> {
|
): Promise<ICompletion[]> {
|
||||||
// lazy-load user list into matcher
|
// lazy-load user list into matcher
|
||||||
if (!this.users) this._makeUsers();
|
if (!this.users) this.makeUsers();
|
||||||
|
|
||||||
let completions = [];
|
let completions = [];
|
||||||
const { command, range } = this.getCurrentCommand(rawQuery, selection, force);
|
const { command, range } = this.getCurrentCommand(rawQuery, selection, force);
|
||||||
|
@ -147,7 +147,7 @@ export default class UserProvider extends AutocompleteProvider {
|
||||||
return _t('Users');
|
return _t('Users');
|
||||||
}
|
}
|
||||||
|
|
||||||
_makeUsers() {
|
private makeUsers() {
|
||||||
const events = this.room.getLiveTimeline().getEvents();
|
const events = this.room.getLiveTimeline().getEvents();
|
||||||
const lastSpoken = {};
|
const lastSpoken = {};
|
||||||
|
|
||||||
|
|
|
@ -17,8 +17,8 @@ limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import * as PropTypes from 'prop-types';
|
|
||||||
import { MatrixClient } from 'matrix-js-sdk/src/client';
|
import { MatrixClient } from 'matrix-js-sdk/src/client';
|
||||||
|
import { MatrixEvent } from 'matrix-js-sdk/src/models/event';
|
||||||
|
|
||||||
import { Key } from '../../Keyboard';
|
import { Key } from '../../Keyboard';
|
||||||
import PageTypes from '../../PageTypes';
|
import PageTypes from '../../PageTypes';
|
||||||
|
@ -79,6 +79,8 @@ function canElementReceiveInput(el) {
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
matrixClient: MatrixClient;
|
matrixClient: MatrixClient;
|
||||||
|
// Called with the credentials of a registered user (if they were a ROU that
|
||||||
|
// transitioned to PWLU)
|
||||||
onRegistered: (credentials: IMatrixClientCreds) => Promise<MatrixClient>;
|
onRegistered: (credentials: IMatrixClientCreds) => Promise<MatrixClient>;
|
||||||
hideToSRUsers: boolean;
|
hideToSRUsers: boolean;
|
||||||
resizeNotifier: ResizeNotifier;
|
resizeNotifier: ResizeNotifier;
|
||||||
|
@ -140,18 +142,6 @@ interface IState {
|
||||||
class LoggedInView extends React.Component<IProps, IState> {
|
class LoggedInView extends React.Component<IProps, IState> {
|
||||||
static displayName = 'LoggedInView';
|
static displayName = 'LoggedInView';
|
||||||
|
|
||||||
static propTypes = {
|
|
||||||
matrixClient: PropTypes.instanceOf(MatrixClient).isRequired,
|
|
||||||
page_type: PropTypes.string.isRequired,
|
|
||||||
onRoomCreated: PropTypes.func,
|
|
||||||
|
|
||||||
// Called with the credentials of a registered user (if they were a ROU that
|
|
||||||
// transitioned to PWLU)
|
|
||||||
onRegistered: PropTypes.func,
|
|
||||||
|
|
||||||
// and lots and lots of other stuff.
|
|
||||||
};
|
|
||||||
|
|
||||||
protected readonly _matrixClient: MatrixClient;
|
protected readonly _matrixClient: MatrixClient;
|
||||||
protected readonly _roomView: React.RefObject<any>;
|
protected readonly _roomView: React.RefObject<any>;
|
||||||
protected readonly _resizeContainer: React.RefObject<ResizeHandle>;
|
protected readonly _resizeContainer: React.RefObject<ResizeHandle>;
|
||||||
|
@ -181,10 +171,10 @@ class LoggedInView extends React.Component<IProps, IState> {
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
document.addEventListener('keydown', this._onNativeKeyDown, false);
|
document.addEventListener('keydown', this.onNativeKeyDown, false);
|
||||||
CallHandler.sharedInstance().addListener(CallHandlerEvent.CallsChanged, this.onCallsChanged);
|
CallHandler.sharedInstance().addListener(CallHandlerEvent.CallsChanged, this.onCallsChanged);
|
||||||
|
|
||||||
this._updateServerNoticeEvents();
|
this.updateServerNoticeEvents();
|
||||||
|
|
||||||
this._matrixClient.on("accountData", this.onAccountData);
|
this._matrixClient.on("accountData", this.onAccountData);
|
||||||
this._matrixClient.on("sync", this.onSync);
|
this._matrixClient.on("sync", this.onSync);
|
||||||
|
@ -200,13 +190,13 @@ class LoggedInView extends React.Component<IProps, IState> {
|
||||||
"useCompactLayout", null, this.onCompactLayoutChanged,
|
"useCompactLayout", null, this.onCompactLayoutChanged,
|
||||||
);
|
);
|
||||||
|
|
||||||
this.resizer = this._createResizer();
|
this.resizer = this.createResizer();
|
||||||
this.resizer.attach();
|
this.resizer.attach();
|
||||||
this._loadResizerPreferences();
|
this.loadResizerPreferences();
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
document.removeEventListener('keydown', this._onNativeKeyDown, false);
|
document.removeEventListener('keydown', this.onNativeKeyDown, false);
|
||||||
CallHandler.sharedInstance().removeListener(CallHandlerEvent.CallsChanged, this.onCallsChanged);
|
CallHandler.sharedInstance().removeListener(CallHandlerEvent.CallsChanged, this.onCallsChanged);
|
||||||
this._matrixClient.removeListener("accountData", this.onAccountData);
|
this._matrixClient.removeListener("accountData", this.onAccountData);
|
||||||
this._matrixClient.removeListener("sync", this.onSync);
|
this._matrixClient.removeListener("sync", this.onSync);
|
||||||
|
@ -221,37 +211,37 @@ class LoggedInView extends React.Component<IProps, IState> {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
canResetTimelineInRoom = (roomId) => {
|
public canResetTimelineInRoom = (roomId: string) => {
|
||||||
if (!this._roomView.current) {
|
if (!this._roomView.current) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return this._roomView.current.canResetTimeline();
|
return this._roomView.current.canResetTimeline();
|
||||||
};
|
};
|
||||||
|
|
||||||
_createResizer() {
|
private createResizer() {
|
||||||
let size;
|
let panelSize;
|
||||||
let collapsed;
|
let panelCollapsed;
|
||||||
const collapseConfig: ICollapseConfig = {
|
const collapseConfig: ICollapseConfig = {
|
||||||
// TODO decrease this once Spaces launches as it'll no longer need to include the 56px Community Panel
|
// TODO decrease this once Spaces launches as it'll no longer need to include the 56px Community Panel
|
||||||
toggleSize: 206 - 50,
|
toggleSize: 206 - 50,
|
||||||
onCollapsed: (_collapsed) => {
|
onCollapsed: (collapsed) => {
|
||||||
collapsed = _collapsed;
|
panelCollapsed = collapsed;
|
||||||
if (_collapsed) {
|
if (collapsed) {
|
||||||
dis.dispatch({ action: "hide_left_panel" });
|
dis.dispatch({ action: "hide_left_panel" });
|
||||||
window.localStorage.setItem("mx_lhs_size", '0');
|
window.localStorage.setItem("mx_lhs_size", '0');
|
||||||
} else {
|
} else {
|
||||||
dis.dispatch({ action: "show_left_panel" });
|
dis.dispatch({ action: "show_left_panel" });
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onResized: (_size) => {
|
onResized: (size) => {
|
||||||
size = _size;
|
panelSize = size;
|
||||||
this.props.resizeNotifier.notifyLeftHandleResized();
|
this.props.resizeNotifier.notifyLeftHandleResized();
|
||||||
},
|
},
|
||||||
onResizeStart: () => {
|
onResizeStart: () => {
|
||||||
this.props.resizeNotifier.startResizing();
|
this.props.resizeNotifier.startResizing();
|
||||||
},
|
},
|
||||||
onResizeStop: () => {
|
onResizeStop: () => {
|
||||||
if (!collapsed) window.localStorage.setItem("mx_lhs_size", '' + size);
|
if (!panelCollapsed) window.localStorage.setItem("mx_lhs_size", '' + panelSize);
|
||||||
this.props.resizeNotifier.stopResizing();
|
this.props.resizeNotifier.stopResizing();
|
||||||
},
|
},
|
||||||
isItemCollapsed: domNode => {
|
isItemCollapsed: domNode => {
|
||||||
|
@ -267,7 +257,7 @@ class LoggedInView extends React.Component<IProps, IState> {
|
||||||
return resizer;
|
return resizer;
|
||||||
}
|
}
|
||||||
|
|
||||||
_loadResizerPreferences() {
|
private loadResizerPreferences() {
|
||||||
let lhsSize = parseInt(window.localStorage.getItem("mx_lhs_size"), 10);
|
let lhsSize = parseInt(window.localStorage.getItem("mx_lhs_size"), 10);
|
||||||
if (isNaN(lhsSize)) {
|
if (isNaN(lhsSize)) {
|
||||||
lhsSize = 350;
|
lhsSize = 350;
|
||||||
|
@ -275,7 +265,7 @@ class LoggedInView extends React.Component<IProps, IState> {
|
||||||
this.resizer.forHandleAt(0).resize(lhsSize);
|
this.resizer.forHandleAt(0).resize(lhsSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
onAccountData = (event) => {
|
private onAccountData = (event: MatrixEvent) => {
|
||||||
if (event.getType() === "m.ignored_user_list") {
|
if (event.getType() === "m.ignored_user_list") {
|
||||||
dis.dispatch({ action: "ignore_state_changed" });
|
dis.dispatch({ action: "ignore_state_changed" });
|
||||||
}
|
}
|
||||||
|
@ -307,16 +297,16 @@ class LoggedInView extends React.Component<IProps, IState> {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (oldSyncState === 'PREPARED' && syncState === 'SYNCING') {
|
if (oldSyncState === 'PREPARED' && syncState === 'SYNCING') {
|
||||||
this._updateServerNoticeEvents();
|
this.updateServerNoticeEvents();
|
||||||
} else {
|
} else {
|
||||||
this._calculateServerLimitToast(this.state.syncErrorData, this.state.usageLimitEventContent);
|
this.calculateServerLimitToast(this.state.syncErrorData, this.state.usageLimitEventContent);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
onRoomStateEvents = (ev, state) => {
|
onRoomStateEvents = (ev, state) => {
|
||||||
const serverNoticeList = RoomListStore.instance.orderedLists[DefaultTagID.ServerNotice];
|
const serverNoticeList = RoomListStore.instance.orderedLists[DefaultTagID.ServerNotice];
|
||||||
if (serverNoticeList && serverNoticeList.some(r => r.roomId === ev.getRoomId())) {
|
if (serverNoticeList && serverNoticeList.some(r => r.roomId === ev.getRoomId())) {
|
||||||
this._updateServerNoticeEvents();
|
this.updateServerNoticeEvents();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -326,7 +316,7 @@ class LoggedInView extends React.Component<IProps, IState> {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
_calculateServerLimitToast(syncError: IState["syncErrorData"], usageLimitEventContent?: IUsageLimit) {
|
private calculateServerLimitToast(syncError: IState["syncErrorData"], usageLimitEventContent?: IUsageLimit) {
|
||||||
const error = syncError && syncError.error && syncError.error.errcode === "M_RESOURCE_LIMIT_EXCEEDED";
|
const error = syncError && syncError.error && syncError.error.errcode === "M_RESOURCE_LIMIT_EXCEEDED";
|
||||||
if (error) {
|
if (error) {
|
||||||
usageLimitEventContent = syncError.error.data;
|
usageLimitEventContent = syncError.error.data;
|
||||||
|
@ -346,7 +336,7 @@ class LoggedInView extends React.Component<IProps, IState> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_updateServerNoticeEvents = async () => {
|
private updateServerNoticeEvents = async () => {
|
||||||
const serverNoticeList = RoomListStore.instance.orderedLists[DefaultTagID.ServerNotice];
|
const serverNoticeList = RoomListStore.instance.orderedLists[DefaultTagID.ServerNotice];
|
||||||
if (!serverNoticeList) return [];
|
if (!serverNoticeList) return [];
|
||||||
|
|
||||||
|
@ -378,7 +368,7 @@ class LoggedInView extends React.Component<IProps, IState> {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
const usageLimitEventContent = usageLimitEvent && usageLimitEvent.getContent();
|
const usageLimitEventContent = usageLimitEvent && usageLimitEvent.getContent();
|
||||||
this._calculateServerLimitToast(this.state.syncErrorData, usageLimitEventContent);
|
this.calculateServerLimitToast(this.state.syncErrorData, usageLimitEventContent);
|
||||||
this.setState({
|
this.setState({
|
||||||
usageLimitEventContent,
|
usageLimitEventContent,
|
||||||
usageLimitEventTs: pinnedEventTs,
|
usageLimitEventTs: pinnedEventTs,
|
||||||
|
@ -387,7 +377,7 @@ class LoggedInView extends React.Component<IProps, IState> {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
_onPaste = (ev) => {
|
private onPaste = (ev) => {
|
||||||
let canReceiveInput = false;
|
let canReceiveInput = false;
|
||||||
let element = ev.target;
|
let element = ev.target;
|
||||||
// test for all parents because the target can be a child of a contenteditable element
|
// test for all parents because the target can be a child of a contenteditable element
|
||||||
|
@ -425,22 +415,22 @@ class LoggedInView extends React.Component<IProps, IState> {
|
||||||
We also listen with a native listener on the document to get keydown events when no element is focused.
|
We also listen with a native listener on the document to get keydown events when no element is focused.
|
||||||
Bubbling is irrelevant here as the target is the body element.
|
Bubbling is irrelevant here as the target is the body element.
|
||||||
*/
|
*/
|
||||||
_onReactKeyDown = (ev) => {
|
private onReactKeyDown = (ev) => {
|
||||||
// events caught while bubbling up on the root element
|
// events caught while bubbling up on the root element
|
||||||
// of this component, so something must be focused.
|
// of this component, so something must be focused.
|
||||||
this._onKeyDown(ev);
|
this.onKeyDown(ev);
|
||||||
};
|
};
|
||||||
|
|
||||||
_onNativeKeyDown = (ev) => {
|
private onNativeKeyDown = (ev) => {
|
||||||
// only pass this if there is no focused element.
|
// only pass this if there is no focused element.
|
||||||
// if there is, _onKeyDown will be called by the
|
// if there is, onKeyDown will be called by the
|
||||||
// react keydown handler that respects the react bubbling order.
|
// react keydown handler that respects the react bubbling order.
|
||||||
if (ev.target === document.body) {
|
if (ev.target === document.body) {
|
||||||
this._onKeyDown(ev);
|
this.onKeyDown(ev);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
_onKeyDown = (ev) => {
|
private onKeyDown = (ev) => {
|
||||||
let handled = false;
|
let handled = false;
|
||||||
|
|
||||||
const roomAction = getKeyBindingsManager().getRoomAction(ev);
|
const roomAction = getKeyBindingsManager().getRoomAction(ev);
|
||||||
|
@ -450,7 +440,7 @@ class LoggedInView extends React.Component<IProps, IState> {
|
||||||
case RoomAction.JumpToFirstMessage:
|
case RoomAction.JumpToFirstMessage:
|
||||||
case RoomAction.JumpToLatestMessage:
|
case RoomAction.JumpToLatestMessage:
|
||||||
// pass the event down to the scroll panel
|
// pass the event down to the scroll panel
|
||||||
this._onScrollKeyPressed(ev);
|
this.onScrollKeyPressed(ev);
|
||||||
handled = true;
|
handled = true;
|
||||||
break;
|
break;
|
||||||
case RoomAction.FocusSearch:
|
case RoomAction.FocusSearch:
|
||||||
|
@ -565,7 +555,7 @@ class LoggedInView extends React.Component<IProps, IState> {
|
||||||
* dispatch a page-up/page-down/etc to the appropriate component
|
* dispatch a page-up/page-down/etc to the appropriate component
|
||||||
* @param {Object} ev The key event
|
* @param {Object} ev The key event
|
||||||
*/
|
*/
|
||||||
_onScrollKeyPressed = (ev) => {
|
private onScrollKeyPressed = (ev) => {
|
||||||
if (this._roomView.current) {
|
if (this._roomView.current) {
|
||||||
this._roomView.current.handleScrollKey(ev);
|
this._roomView.current.handleScrollKey(ev);
|
||||||
}
|
}
|
||||||
|
@ -625,8 +615,8 @@ class LoggedInView extends React.Component<IProps, IState> {
|
||||||
return (
|
return (
|
||||||
<MatrixClientContext.Provider value={this._matrixClient}>
|
<MatrixClientContext.Provider value={this._matrixClient}>
|
||||||
<div
|
<div
|
||||||
onPaste={this._onPaste}
|
onPaste={this.onPaste}
|
||||||
onKeyDown={this._onReactKeyDown}
|
onKeyDown={this.onReactKeyDown}
|
||||||
className='mx_MatrixChat_wrapper'
|
className='mx_MatrixChat_wrapper'
|
||||||
aria-hidden={this.props.hideToSRUsers}
|
aria-hidden={this.props.hideToSRUsers}
|
||||||
>
|
>
|
||||||
|
|
|
@ -431,7 +431,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: [REACT-WARNING] Replace with appropriate lifecycle stage
|
// TODO: [REACT-WARNING] Replace with appropriate lifecycle stage
|
||||||
// eslint-disable-next-line camelcase
|
// eslint-disable-next-line
|
||||||
UNSAFE_componentWillUpdate(props, state) {
|
UNSAFE_componentWillUpdate(props, state) {
|
||||||
if (this.shouldTrackPageChange(this.state, state)) {
|
if (this.shouldTrackPageChange(this.state, state)) {
|
||||||
this.startPageChangeTimer();
|
this.startPageChangeTimer();
|
||||||
|
@ -1864,13 +1864,6 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||||
dis.dispatch({ action: 'timeline_resize' });
|
dis.dispatch({ action: 'timeline_resize' });
|
||||||
}
|
}
|
||||||
|
|
||||||
onRoomCreated(roomId: string) {
|
|
||||||
dis.dispatch({
|
|
||||||
action: "view_room",
|
|
||||||
room_id: roomId,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
onRegisterClick = () => {
|
onRegisterClick = () => {
|
||||||
this.showScreen("register");
|
this.showScreen("register");
|
||||||
};
|
};
|
||||||
|
@ -2043,7 +2036,6 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||||
{...this.state}
|
{...this.state}
|
||||||
ref={this.loggedInView}
|
ref={this.loggedInView}
|
||||||
matrixClient={MatrixClientPeg.get()}
|
matrixClient={MatrixClientPeg.get()}
|
||||||
onRoomCreated={this.onRoomCreated}
|
|
||||||
onRegistered={this.onRegistered}
|
onRegistered={this.onRegistered}
|
||||||
currentRoomId={this.state.currentRoomId}
|
currentRoomId={this.state.currentRoomId}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -653,8 +653,10 @@ export default class MessagePanel extends React.Component<IProps, IState> {
|
||||||
}
|
}
|
||||||
|
|
||||||
let willWantDateSeparator = false;
|
let willWantDateSeparator = false;
|
||||||
|
let lastInSection = true;
|
||||||
if (nextEvent) {
|
if (nextEvent) {
|
||||||
willWantDateSeparator = this.wantsDateSeparator(mxEv, nextEvent.getDate() || new Date());
|
willWantDateSeparator = this.wantsDateSeparator(mxEv, nextEvent.getDate() || new Date());
|
||||||
|
lastInSection = willWantDateSeparator || mxEv.getSender() !== nextEvent.getSender();
|
||||||
}
|
}
|
||||||
|
|
||||||
// is this a continuation of the previous message?
|
// is this a continuation of the previous message?
|
||||||
|
@ -712,7 +714,7 @@ export default class MessagePanel extends React.Component<IProps, IState> {
|
||||||
isTwelveHour={this.props.isTwelveHour}
|
isTwelveHour={this.props.isTwelveHour}
|
||||||
permalinkCreator={this.props.permalinkCreator}
|
permalinkCreator={this.props.permalinkCreator}
|
||||||
last={last}
|
last={last}
|
||||||
lastInSection={willWantDateSeparator}
|
lastInSection={lastInSection}
|
||||||
lastSuccessful={isLastSuccessful}
|
lastSuccessful={isLastSuccessful}
|
||||||
isSelectedEvent={highlight}
|
isSelectedEvent={highlight}
|
||||||
getRelationsForEvent={this.props.getRelationsForEvent}
|
getRelationsForEvent={this.props.getRelationsForEvent}
|
||||||
|
@ -720,6 +722,7 @@ export default class MessagePanel extends React.Component<IProps, IState> {
|
||||||
layout={this.props.layout}
|
layout={this.props.layout}
|
||||||
enableFlair={this.props.enableFlair}
|
enableFlair={this.props.enableFlair}
|
||||||
showReadReceipts={this.props.showReadReceipts}
|
showReadReceipts={this.props.showReadReceipts}
|
||||||
|
hideSender={this.props.room.getMembers().length <= 2 && this.props.layout === Layout.Bubble}
|
||||||
/>
|
/>
|
||||||
</TileErrorBoundary>,
|
</TileErrorBoundary>,
|
||||||
);
|
);
|
||||||
|
|
|
@ -17,6 +17,7 @@ limitations under the License.
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Room } from "matrix-js-sdk/src/models/room";
|
import { Room } from "matrix-js-sdk/src/models/room";
|
||||||
|
import { RoomState } from "matrix-js-sdk/src/models/room-state";
|
||||||
import { User } from "matrix-js-sdk/src/models/user";
|
import { User } from "matrix-js-sdk/src/models/user";
|
||||||
import { RoomMember } from "matrix-js-sdk/src/models/room-member";
|
import { RoomMember } from "matrix-js-sdk/src/models/room-member";
|
||||||
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
|
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
|
||||||
|
@ -152,7 +153,7 @@ export default class RightPanel extends React.Component<IProps, IState> {
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: [REACT-WARNING] Replace with appropriate lifecycle event
|
// TODO: [REACT-WARNING] Replace with appropriate lifecycle event
|
||||||
UNSAFE_componentWillReceiveProps(newProps) { // eslint-disable-line camelcase
|
UNSAFE_componentWillReceiveProps(newProps) { // eslint-disable-line
|
||||||
if (newProps.groupId !== this.props.groupId) {
|
if (newProps.groupId !== this.props.groupId) {
|
||||||
this.unregisterGroupStore();
|
this.unregisterGroupStore();
|
||||||
this.initGroupStore(newProps.groupId);
|
this.initGroupStore(newProps.groupId);
|
||||||
|
@ -174,7 +175,7 @@ export default class RightPanel extends React.Component<IProps, IState> {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
private onRoomStateMember = (ev: MatrixEvent, _, member: RoomMember) => {
|
private onRoomStateMember = (ev: MatrixEvent, state: RoomState, member: RoomMember) => {
|
||||||
if (!this.props.room || member.roomId !== this.props.room.roomId) {
|
if (!this.props.room || member.roomId !== this.props.room.roomId) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -814,7 +814,7 @@ export default class RoomDirectory extends React.Component<IProps, IState> {
|
||||||
}) : _t("Explore rooms");
|
}) : _t("Explore rooms");
|
||||||
return (
|
return (
|
||||||
<BaseDialog
|
<BaseDialog
|
||||||
className={'mx_RoomDirectory_dialog'}
|
className="mx_RoomDirectory_dialog"
|
||||||
hasCancel={true}
|
hasCancel={true}
|
||||||
onFinished={this.onFinished}
|
onFinished={this.onFinished}
|
||||||
title={title}
|
title={title}
|
||||||
|
|
|
@ -458,7 +458,7 @@ const SpaceSetupFirstRooms = ({ space, title, description, onFinished }) => {
|
||||||
const numFields = 3;
|
const numFields = 3;
|
||||||
const placeholders = [_t("General"), _t("Random"), _t("Support")];
|
const placeholders = [_t("General"), _t("Random"), _t("Support")];
|
||||||
const [roomNames, setRoomName] = useStateArray(numFields, [_t("General"), _t("Random"), ""]);
|
const [roomNames, setRoomName] = useStateArray(numFields, [_t("General"), _t("Random"), ""]);
|
||||||
const fields = new Array(numFields).fill(0).map((_, i) => {
|
const fields = new Array(numFields).fill(0).map((x, i) => {
|
||||||
const name = "roomName" + i;
|
const name = "roomName" + i;
|
||||||
return <Field
|
return <Field
|
||||||
key={name}
|
key={name}
|
||||||
|
@ -625,7 +625,7 @@ const SpaceSetupPrivateInvite = ({ space, onFinished }) => {
|
||||||
const numFields = 3;
|
const numFields = 3;
|
||||||
const fieldRefs: RefObject<Field>[] = [useRef(), useRef(), useRef()];
|
const fieldRefs: RefObject<Field>[] = [useRef(), useRef(), useRef()];
|
||||||
const [emailAddresses, setEmailAddress] = useStateArray(numFields, "");
|
const [emailAddresses, setEmailAddress] = useStateArray(numFields, "");
|
||||||
const fields = new Array(numFields).fill(0).map((_, i) => {
|
const fields = new Array(numFields).fill(0).map((x, i) => {
|
||||||
const name = "emailAddress" + i;
|
const name = "emailAddress" + i;
|
||||||
return <Field
|
return <Field
|
||||||
key={name}
|
key={name}
|
||||||
|
|
|
@ -74,7 +74,7 @@ export default class TabbedView extends React.Component<IProps, IState> {
|
||||||
tabLocation: TabLocation.LEFT,
|
tabLocation: TabLocation.LEFT,
|
||||||
};
|
};
|
||||||
|
|
||||||
private _getActiveTabIndex() {
|
private getActiveTabIndex() {
|
||||||
if (!this.state || !this.state.activeTabIndex) return 0;
|
if (!this.state || !this.state.activeTabIndex) return 0;
|
||||||
return this.state.activeTabIndex;
|
return this.state.activeTabIndex;
|
||||||
}
|
}
|
||||||
|
@ -84,7 +84,7 @@ export default class TabbedView extends React.Component<IProps, IState> {
|
||||||
* @param {Tab} tab the tab to show
|
* @param {Tab} tab the tab to show
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
private _setActiveTab(tab: Tab) {
|
private setActiveTab(tab: Tab) {
|
||||||
const idx = this.props.tabs.indexOf(tab);
|
const idx = this.props.tabs.indexOf(tab);
|
||||||
if (idx !== -1) {
|
if (idx !== -1) {
|
||||||
if (this.props.onChange) this.props.onChange(tab.id);
|
if (this.props.onChange) this.props.onChange(tab.id);
|
||||||
|
@ -94,18 +94,18 @@ export default class TabbedView extends React.Component<IProps, IState> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private _renderTabLabel(tab: Tab) {
|
private renderTabLabel(tab: Tab) {
|
||||||
let classes = "mx_TabbedView_tabLabel ";
|
let classes = "mx_TabbedView_tabLabel ";
|
||||||
|
|
||||||
const idx = this.props.tabs.indexOf(tab);
|
const idx = this.props.tabs.indexOf(tab);
|
||||||
if (idx === this._getActiveTabIndex()) classes += "mx_TabbedView_tabLabel_active";
|
if (idx === this.getActiveTabIndex()) classes += "mx_TabbedView_tabLabel_active";
|
||||||
|
|
||||||
let tabIcon = null;
|
let tabIcon = null;
|
||||||
if (tab.icon) {
|
if (tab.icon) {
|
||||||
tabIcon = <span className={`mx_TabbedView_maskedIcon ${tab.icon}`} />;
|
tabIcon = <span className={`mx_TabbedView_maskedIcon ${tab.icon}`} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
const onClickHandler = () => this._setActiveTab(tab);
|
const onClickHandler = () => this.setActiveTab(tab);
|
||||||
|
|
||||||
const label = _t(tab.label);
|
const label = _t(tab.label);
|
||||||
return (
|
return (
|
||||||
|
@ -118,7 +118,7 @@ export default class TabbedView extends React.Component<IProps, IState> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private _renderTabPanel(tab: Tab): React.ReactNode {
|
private renderTabPanel(tab: Tab): React.ReactNode {
|
||||||
return (
|
return (
|
||||||
<div className="mx_TabbedView_tabPanel" key={"mx_tabpanel_" + tab.label}>
|
<div className="mx_TabbedView_tabPanel" key={"mx_tabpanel_" + tab.label}>
|
||||||
<AutoHideScrollbar className='mx_TabbedView_tabPanelContent'>
|
<AutoHideScrollbar className='mx_TabbedView_tabPanelContent'>
|
||||||
|
@ -129,8 +129,8 @@ export default class TabbedView extends React.Component<IProps, IState> {
|
||||||
}
|
}
|
||||||
|
|
||||||
public render(): React.ReactNode {
|
public render(): React.ReactNode {
|
||||||
const labels = this.props.tabs.map(tab => this._renderTabLabel(tab));
|
const labels = this.props.tabs.map(tab => this.renderTabLabel(tab));
|
||||||
const panel = this._renderTabPanel(this.props.tabs[this._getActiveTabIndex()]);
|
const panel = this.renderTabPanel(this.props.tabs[this.getActiveTabIndex()]);
|
||||||
|
|
||||||
const tabbedViewClasses = classNames({
|
const tabbedViewClasses = classNames({
|
||||||
'mx_TabbedView': true,
|
'mx_TabbedView': true,
|
||||||
|
|
|
@ -277,7 +277,7 @@ class TimelinePanel extends React.Component<IProps, IState> {
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: [REACT-WARNING] Move into constructor
|
// TODO: [REACT-WARNING] Move into constructor
|
||||||
// eslint-disable-next-line camelcase
|
// eslint-disable-next-line
|
||||||
UNSAFE_componentWillMount() {
|
UNSAFE_componentWillMount() {
|
||||||
if (this.props.manageReadReceipts) {
|
if (this.props.manageReadReceipts) {
|
||||||
this.updateReadReceiptOnUserActivity();
|
this.updateReadReceiptOnUserActivity();
|
||||||
|
@ -290,7 +290,7 @@ class TimelinePanel extends React.Component<IProps, IState> {
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: [REACT-WARNING] Replace with appropriate lifecycle event
|
// TODO: [REACT-WARNING] Replace with appropriate lifecycle event
|
||||||
// eslint-disable-next-line camelcase
|
// eslint-disable-next-line
|
||||||
UNSAFE_componentWillReceiveProps(newProps) {
|
UNSAFE_componentWillReceiveProps(newProps) {
|
||||||
if (newProps.timelineSet !== this.props.timelineSet) {
|
if (newProps.timelineSet !== this.props.timelineSet) {
|
||||||
// throw new Error("changing timelineSet on a TimelinePanel is not supported");
|
// throw new Error("changing timelineSet on a TimelinePanel is not supported");
|
||||||
|
|
|
@ -37,14 +37,14 @@ export default class ToastContainer extends React.Component<{}, IState> {
|
||||||
// toasts may dismiss themselves in their didMount if they find
|
// toasts may dismiss themselves in their didMount if they find
|
||||||
// they're already irrelevant by the time they're mounted, and
|
// they're already irrelevant by the time they're mounted, and
|
||||||
// our own componentDidMount is too late.
|
// our own componentDidMount is too late.
|
||||||
ToastStore.sharedInstance().on('update', this._onToastStoreUpdate);
|
ToastStore.sharedInstance().on('update', this.onToastStoreUpdate);
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
ToastStore.sharedInstance().removeListener('update', this._onToastStoreUpdate);
|
ToastStore.sharedInstance().removeListener('update', this.onToastStoreUpdate);
|
||||||
}
|
}
|
||||||
|
|
||||||
_onToastStoreUpdate = () => {
|
private onToastStoreUpdate = () => {
|
||||||
this.setState({
|
this.setState({
|
||||||
toasts: ToastStore.sharedInstance().getToasts(),
|
toasts: ToastStore.sharedInstance().getToasts(),
|
||||||
countSeen: ToastStore.sharedInstance().getCountSeen(),
|
countSeen: ToastStore.sharedInstance().getCountSeen(),
|
||||||
|
|
|
@ -101,7 +101,7 @@ export default class ForgotPassword extends React.Component<IProps, IState> {
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: [REACT-WARNING] Replace with appropriate lifecycle event
|
// TODO: [REACT-WARNING] Replace with appropriate lifecycle event
|
||||||
// eslint-disable-next-line camelcase
|
// eslint-disable-next-line
|
||||||
public UNSAFE_componentWillReceiveProps(newProps: IProps): void {
|
public UNSAFE_componentWillReceiveProps(newProps: IProps): void {
|
||||||
if (newProps.serverConfig.hsUrl === this.props.serverConfig.hsUrl &&
|
if (newProps.serverConfig.hsUrl === this.props.serverConfig.hsUrl &&
|
||||||
newProps.serverConfig.isUrl === this.props.serverConfig.isUrl) return;
|
newProps.serverConfig.isUrl === this.props.serverConfig.isUrl) return;
|
||||||
|
|
|
@ -144,7 +144,7 @@ export default class LoginComponent extends React.PureComponent<IProps, IState>
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: [REACT-WARNING] Replace with appropriate lifecycle event
|
// TODO: [REACT-WARNING] Replace with appropriate lifecycle event
|
||||||
// eslint-disable-next-line camelcase
|
// eslint-disable-next-line
|
||||||
UNSAFE_componentWillMount() {
|
UNSAFE_componentWillMount() {
|
||||||
this.initLoginLogic(this.props.serverConfig);
|
this.initLoginLogic(this.props.serverConfig);
|
||||||
}
|
}
|
||||||
|
@ -154,7 +154,7 @@ export default class LoginComponent extends React.PureComponent<IProps, IState>
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: [REACT-WARNING] Replace with appropriate lifecycle event
|
// TODO: [REACT-WARNING] Replace with appropriate lifecycle event
|
||||||
// eslint-disable-next-line camelcase
|
// eslint-disable-next-line
|
||||||
UNSAFE_componentWillReceiveProps(newProps) {
|
UNSAFE_componentWillReceiveProps(newProps) {
|
||||||
if (newProps.serverConfig.hsUrl === this.props.serverConfig.hsUrl &&
|
if (newProps.serverConfig.hsUrl === this.props.serverConfig.hsUrl &&
|
||||||
newProps.serverConfig.isUrl === this.props.serverConfig.isUrl) return;
|
newProps.serverConfig.isUrl === this.props.serverConfig.isUrl) return;
|
||||||
|
|
|
@ -141,7 +141,7 @@ export default class Registration extends React.Component<IProps, IState> {
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: [REACT-WARNING] Replace with appropriate lifecycle event
|
// TODO: [REACT-WARNING] Replace with appropriate lifecycle event
|
||||||
// eslint-disable-next-line camelcase
|
// eslint-disable-next-line
|
||||||
UNSAFE_componentWillReceiveProps(newProps) {
|
UNSAFE_componentWillReceiveProps(newProps) {
|
||||||
if (newProps.serverConfig.hsUrl === this.props.serverConfig.hsUrl &&
|
if (newProps.serverConfig.hsUrl === this.props.serverConfig.hsUrl &&
|
||||||
newProps.serverConfig.isUrl === this.props.serverConfig.isUrl) return;
|
newProps.serverConfig.isUrl === this.props.serverConfig.isUrl) return;
|
||||||
|
|
|
@ -60,8 +60,8 @@ export default class AskInviteAnywayDialog extends React.Component<IProps> {
|
||||||
contentId='mx_Dialog_content'
|
contentId='mx_Dialog_content'
|
||||||
>
|
>
|
||||||
<div id='mx_Dialog_content'>
|
<div id='mx_Dialog_content'>
|
||||||
{/* eslint-disable-next-line */}
|
<p>{ _t("Unable to find profiles for the Matrix IDs listed below - " +
|
||||||
<p>{_t("Unable to find profiles for the Matrix IDs listed below - would you like to invite them anyway?")}</p>
|
"would you like to invite them anyway?") }</p>
|
||||||
<ul>
|
<ul>
|
||||||
{ errorList }
|
{ errorList }
|
||||||
</ul>
|
</ul>
|
||||||
|
|
|
@ -187,7 +187,7 @@ export default class CommunityPrototypeInviteDialog extends React.PureComponent<
|
||||||
emailAddresses.push((
|
emailAddresses.push((
|
||||||
<Field
|
<Field
|
||||||
key={emailAddresses.length}
|
key={emailAddresses.length}
|
||||||
value={""}
|
value=""
|
||||||
onChange={(e) => this.onAddressChange(e, emailAddresses.length)}
|
onChange={(e) => this.onAddressChange(e, emailAddresses.length)}
|
||||||
label={emailAddresses.length > 0 ? _t("Add another email") : _t("Email address")}
|
label={emailAddresses.length > 0 ? _t("Add another email") : _t("Email address")}
|
||||||
placeholder={emailAddresses.length > 0 ? _t("Add another email") : _t("Email address")}
|
placeholder={emailAddresses.length > 0 ? _t("Add another email") : _t("Email address")}
|
||||||
|
|
|
@ -102,7 +102,7 @@ export default class CreateGroupDialog extends React.Component<IProps, IState> {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
_onCancel = () => {
|
private onCancel = () => {
|
||||||
this.props.onFinished(false);
|
this.props.onFinished(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -167,7 +167,7 @@ export default class CreateGroupDialog extends React.Component<IProps, IState> {
|
||||||
</div>
|
</div>
|
||||||
<div className="mx_Dialog_buttons">
|
<div className="mx_Dialog_buttons">
|
||||||
<input type="submit" value={_t('Create')} className="mx_Dialog_primary" />
|
<input type="submit" value={_t('Create')} className="mx_Dialog_primary" />
|
||||||
<button onClick={this._onCancel}>
|
<button onClick={this.onCancel}>
|
||||||
{ _t("Cancel") }
|
{ _t("Cancel") }
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -337,7 +337,7 @@ class FilteredList extends React.PureComponent<IFilteredListProps, IFilteredList
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: [REACT-WARNING] Replace with appropriate lifecycle event
|
// TODO: [REACT-WARNING] Replace with appropriate lifecycle event
|
||||||
UNSAFE_componentWillReceiveProps(nextProps) { // eslint-disable-line camelcase
|
UNSAFE_componentWillReceiveProps(nextProps) { // eslint-disable-line
|
||||||
if (this.props.children === nextProps.children && this.props.query === nextProps.query) return;
|
if (this.props.children === nextProps.children && this.props.query === nextProps.query) return;
|
||||||
this.setState({
|
this.setState({
|
||||||
filteredChildren: FilteredList.filterChildren(nextProps.children, nextProps.query),
|
filteredChildren: FilteredList.filterChildren(nextProps.children, nextProps.query),
|
||||||
|
|
|
@ -40,7 +40,7 @@ interface IState {
|
||||||
busy: boolean;
|
busy: boolean;
|
||||||
err?: string;
|
err?: string;
|
||||||
// If we know it, the nature of the abuse, as specified by MSC3215.
|
// If we know it, the nature of the abuse, as specified by MSC3215.
|
||||||
nature?: EXTENDED_NATURE;
|
nature?: ExtendedNature;
|
||||||
}
|
}
|
||||||
|
|
||||||
const MODERATED_BY_STATE_EVENT_TYPE = [
|
const MODERATED_BY_STATE_EVENT_TYPE = [
|
||||||
|
@ -55,22 +55,22 @@ const MODERATED_BY_STATE_EVENT_TYPE = [
|
||||||
const ABUSE_EVENT_TYPE = "org.matrix.msc3215.abuse.report";
|
const ABUSE_EVENT_TYPE = "org.matrix.msc3215.abuse.report";
|
||||||
|
|
||||||
// Standard abuse natures.
|
// Standard abuse natures.
|
||||||
enum NATURE {
|
enum Nature {
|
||||||
DISAGREEMENT = "org.matrix.msc3215.abuse.nature.disagreement",
|
Disagreement = "org.matrix.msc3215.abuse.nature.disagreement",
|
||||||
TOXIC = "org.matrix.msc3215.abuse.nature.toxic",
|
Toxic = "org.matrix.msc3215.abuse.nature.toxic",
|
||||||
ILLEGAL = "org.matrix.msc3215.abuse.nature.illegal",
|
Illegal = "org.matrix.msc3215.abuse.nature.illegal",
|
||||||
SPAM = "org.matrix.msc3215.abuse.nature.spam",
|
Spam = "org.matrix.msc3215.abuse.nature.spam",
|
||||||
OTHER = "org.matrix.msc3215.abuse.nature.other",
|
Other = "org.matrix.msc3215.abuse.nature.other",
|
||||||
}
|
}
|
||||||
|
|
||||||
enum NON_STANDARD_NATURE {
|
enum NonStandardValue {
|
||||||
// Non-standard abuse nature.
|
// Non-standard abuse nature.
|
||||||
// It should never leave the client - we use it to fallback to
|
// It should never leave the client - we use it to fallback to
|
||||||
// server-wide abuse reporting.
|
// server-wide abuse reporting.
|
||||||
ADMIN = "non-standard.abuse.nature.admin"
|
Admin = "non-standard.abuse.nature.admin"
|
||||||
}
|
}
|
||||||
|
|
||||||
type EXTENDED_NATURE = NATURE | NON_STANDARD_NATURE;
|
type ExtendedNature = Nature | NonStandardValue;
|
||||||
|
|
||||||
type Moderation = {
|
type Moderation = {
|
||||||
// The id of the moderation room.
|
// The id of the moderation room.
|
||||||
|
@ -170,7 +170,7 @@ export default class ReportEventDialog extends React.Component<IProps, IState> {
|
||||||
|
|
||||||
// The user has clicked on a nature.
|
// The user has clicked on a nature.
|
||||||
private onNatureChosen = (e: React.FormEvent<HTMLInputElement>): void => {
|
private onNatureChosen = (e: React.FormEvent<HTMLInputElement>): void => {
|
||||||
this.setState({ nature: e.currentTarget.value as EXTENDED_NATURE });
|
this.setState({ nature: e.currentTarget.value as ExtendedNature });
|
||||||
};
|
};
|
||||||
|
|
||||||
// The user has clicked "cancel".
|
// The user has clicked "cancel".
|
||||||
|
@ -187,7 +187,7 @@ export default class ReportEventDialog extends React.Component<IProps, IState> {
|
||||||
// We need a nature.
|
// We need a nature.
|
||||||
// If the nature is `NATURE.OTHER` or `NON_STANDARD_NATURE.ADMIN`, we also need a `reason`.
|
// If the nature is `NATURE.OTHER` or `NON_STANDARD_NATURE.ADMIN`, we also need a `reason`.
|
||||||
if (!this.state.nature ||
|
if (!this.state.nature ||
|
||||||
((this.state.nature == NATURE.OTHER || this.state.nature == NON_STANDARD_NATURE.ADMIN)
|
((this.state.nature == Nature.Other || this.state.nature == NonStandardValue.Admin)
|
||||||
&& !reason)
|
&& !reason)
|
||||||
) {
|
) {
|
||||||
this.setState({
|
this.setState({
|
||||||
|
@ -214,8 +214,8 @@ export default class ReportEventDialog extends React.Component<IProps, IState> {
|
||||||
try {
|
try {
|
||||||
const client = MatrixClientPeg.get();
|
const client = MatrixClientPeg.get();
|
||||||
const ev = this.props.mxEvent;
|
const ev = this.props.mxEvent;
|
||||||
if (this.moderation && this.state.nature != NON_STANDARD_NATURE.ADMIN) {
|
if (this.moderation && this.state.nature != NonStandardValue.Admin) {
|
||||||
const nature: NATURE = this.state.nature;
|
const nature: Nature = this.state.nature;
|
||||||
|
|
||||||
// Report to moderators through to the dedicated bot,
|
// Report to moderators through to the dedicated bot,
|
||||||
// as configured in the room's state events.
|
// as configured in the room's state events.
|
||||||
|
@ -274,27 +274,27 @@ export default class ReportEventDialog extends React.Component<IProps, IState> {
|
||||||
const homeServerName = SdkConfig.get()["validated_server_config"].hsName;
|
const homeServerName = SdkConfig.get()["validated_server_config"].hsName;
|
||||||
let subtitle;
|
let subtitle;
|
||||||
switch (this.state.nature) {
|
switch (this.state.nature) {
|
||||||
case NATURE.DISAGREEMENT:
|
case Nature.Disagreement:
|
||||||
subtitle = _t("What this user is writing is wrong.\n" +
|
subtitle = _t("What this user is writing is wrong.\n" +
|
||||||
"This will be reported to the room moderators.");
|
"This will be reported to the room moderators.");
|
||||||
break;
|
break;
|
||||||
case NATURE.TOXIC:
|
case Nature.Toxic:
|
||||||
subtitle = _t("This user is displaying toxic behaviour, " +
|
subtitle = _t("This user is displaying toxic behaviour, " +
|
||||||
"for instance by insulting other users or sharing " +
|
"for instance by insulting other users or sharing " +
|
||||||
" adult-only content in a family-friendly room " +
|
" adult-only content in a family-friendly room " +
|
||||||
" or otherwise violating the rules of this room.\n" +
|
" or otherwise violating the rules of this room.\n" +
|
||||||
"This will be reported to the room moderators.");
|
"This will be reported to the room moderators.");
|
||||||
break;
|
break;
|
||||||
case NATURE.ILLEGAL:
|
case Nature.Illegal:
|
||||||
subtitle = _t("This user is displaying illegal behaviour, " +
|
subtitle = _t("This user is displaying illegal behaviour, " +
|
||||||
"for instance by doxing people or threatening violence.\n" +
|
"for instance by doxing people or threatening violence.\n" +
|
||||||
"This will be reported to the room moderators who may escalate this to legal authorities.");
|
"This will be reported to the room moderators who may escalate this to legal authorities.");
|
||||||
break;
|
break;
|
||||||
case NATURE.SPAM:
|
case Nature.Spam:
|
||||||
subtitle = _t("This user is spamming the room with ads, links to ads or to propaganda.\n" +
|
subtitle = _t("This user is spamming the room with ads, links to ads or to propaganda.\n" +
|
||||||
"This will be reported to the room moderators.");
|
"This will be reported to the room moderators.");
|
||||||
break;
|
break;
|
||||||
case NON_STANDARD_NATURE.ADMIN:
|
case NonStandardValue.Admin:
|
||||||
if (client.isRoomEncrypted(this.props.mxEvent.getRoomId())) {
|
if (client.isRoomEncrypted(this.props.mxEvent.getRoomId())) {
|
||||||
subtitle = _t("This room is dedicated to illegal or toxic content " +
|
subtitle = _t("This room is dedicated to illegal or toxic content " +
|
||||||
"or the moderators fail to moderate illegal or toxic content.\n" +
|
"or the moderators fail to moderate illegal or toxic content.\n" +
|
||||||
|
@ -308,7 +308,7 @@ export default class ReportEventDialog extends React.Component<IProps, IState> {
|
||||||
{ homeserver: homeServerName });
|
{ homeserver: homeServerName });
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case NATURE.OTHER:
|
case Nature.Other:
|
||||||
subtitle = _t("Any other reason. Please describe the problem.\n" +
|
subtitle = _t("Any other reason. Please describe the problem.\n" +
|
||||||
"This will be reported to the room moderators.");
|
"This will be reported to the room moderators.");
|
||||||
break;
|
break;
|
||||||
|
@ -327,48 +327,48 @@ export default class ReportEventDialog extends React.Component<IProps, IState> {
|
||||||
<div>
|
<div>
|
||||||
<StyledRadioButton
|
<StyledRadioButton
|
||||||
name="nature"
|
name="nature"
|
||||||
value = { NATURE.DISAGREEMENT }
|
value={Nature.Disagreement}
|
||||||
checked = { this.state.nature == NATURE.DISAGREEMENT }
|
checked={this.state.nature == Nature.Disagreement}
|
||||||
onChange={this.onNatureChosen}
|
onChange={this.onNatureChosen}
|
||||||
>
|
>
|
||||||
{ _t('Disagree') }
|
{ _t('Disagree') }
|
||||||
</StyledRadioButton>
|
</StyledRadioButton>
|
||||||
<StyledRadioButton
|
<StyledRadioButton
|
||||||
name="nature"
|
name="nature"
|
||||||
value = { NATURE.TOXIC }
|
value={Nature.Toxic}
|
||||||
checked = { this.state.nature == NATURE.TOXIC }
|
checked={this.state.nature == Nature.Toxic}
|
||||||
onChange={this.onNatureChosen}
|
onChange={this.onNatureChosen}
|
||||||
>
|
>
|
||||||
{ _t('Toxic Behaviour') }
|
{ _t('Toxic Behaviour') }
|
||||||
</StyledRadioButton>
|
</StyledRadioButton>
|
||||||
<StyledRadioButton
|
<StyledRadioButton
|
||||||
name="nature"
|
name="nature"
|
||||||
value = { NATURE.ILLEGAL }
|
value={Nature.Illegal}
|
||||||
checked = { this.state.nature == NATURE.ILLEGAL }
|
checked={this.state.nature == Nature.Illegal}
|
||||||
onChange={this.onNatureChosen}
|
onChange={this.onNatureChosen}
|
||||||
>
|
>
|
||||||
{ _t('Illegal Content') }
|
{ _t('Illegal Content') }
|
||||||
</StyledRadioButton>
|
</StyledRadioButton>
|
||||||
<StyledRadioButton
|
<StyledRadioButton
|
||||||
name="nature"
|
name="nature"
|
||||||
value = { NATURE.SPAM }
|
value={Nature.Spam}
|
||||||
checked = { this.state.nature == NATURE.SPAM }
|
checked={this.state.nature == Nature.Spam}
|
||||||
onChange={this.onNatureChosen}
|
onChange={this.onNatureChosen}
|
||||||
>
|
>
|
||||||
{ _t('Spam or propaganda') }
|
{ _t('Spam or propaganda') }
|
||||||
</StyledRadioButton>
|
</StyledRadioButton>
|
||||||
<StyledRadioButton
|
<StyledRadioButton
|
||||||
name="nature"
|
name="nature"
|
||||||
value = { NON_STANDARD_NATURE.ADMIN }
|
value={NonStandardValue.Admin}
|
||||||
checked = { this.state.nature == NON_STANDARD_NATURE.ADMIN }
|
checked={this.state.nature == NonStandardValue.Admin}
|
||||||
onChange={this.onNatureChosen}
|
onChange={this.onNatureChosen}
|
||||||
>
|
>
|
||||||
{ _t('Report the entire room') }
|
{ _t('Report the entire room') }
|
||||||
</StyledRadioButton>
|
</StyledRadioButton>
|
||||||
<StyledRadioButton
|
<StyledRadioButton
|
||||||
name="nature"
|
name="nature"
|
||||||
value = { NATURE.OTHER }
|
value={Nature.Other}
|
||||||
checked = { this.state.nature == NATURE.OTHER }
|
checked={this.state.nature == Nature.Other}
|
||||||
onChange={this.onNatureChosen}
|
onChange={this.onNatureChosen}
|
||||||
>
|
>
|
||||||
{ _t('Other') }
|
{ _t('Other') }
|
||||||
|
|
|
@ -81,7 +81,7 @@ export default class UserSettingsDialog extends React.Component<IProps, IState>
|
||||||
this.setState({ mjolnirEnabled: newValue });
|
this.setState({ mjolnirEnabled: newValue });
|
||||||
};
|
};
|
||||||
|
|
||||||
_getTabs() {
|
private getTabs() {
|
||||||
const tabs = [];
|
const tabs = [];
|
||||||
|
|
||||||
tabs.push(new Tab(
|
tabs.push(new Tab(
|
||||||
|
@ -170,7 +170,7 @@ export default class UserSettingsDialog extends React.Component<IProps, IState>
|
||||||
title={_t("Settings")}
|
title={_t("Settings")}
|
||||||
>
|
>
|
||||||
<div className='mx_SettingsDialog_content'>
|
<div className='mx_SettingsDialog_content'>
|
||||||
<TabbedView tabs={this._getTabs()} initialTabId={this.props.initialTabId} />
|
<TabbedView tabs={this.getTabs()} initialTabId={this.props.initialTabId} />
|
||||||
</div>
|
</div>
|
||||||
</BaseDialog>
|
</BaseDialog>
|
||||||
);
|
);
|
||||||
|
|
|
@ -453,13 +453,13 @@ export default class AppTile extends React.Component {
|
||||||
title={_t('Popout widget')}
|
title={_t('Popout widget')}
|
||||||
onClick={this._onPopoutWidgetClick}
|
onClick={this._onPopoutWidgetClick}
|
||||||
/> }
|
/> }
|
||||||
{ <ContextMenuButton
|
<ContextMenuButton
|
||||||
className="mx_AppTileMenuBar_iconButton mx_AppTileMenuBar_iconButton_menu"
|
className="mx_AppTileMenuBar_iconButton mx_AppTileMenuBar_iconButton_menu"
|
||||||
label={_t("Options")}
|
label={_t("Options")}
|
||||||
isExpanded={this.state.menuDisplayed}
|
isExpanded={this.state.menuDisplayed}
|
||||||
inputRef={this._contextMenuButton}
|
inputRef={this._contextMenuButton}
|
||||||
onClick={this._onContextMenuClick}
|
onClick={this._onContextMenuClick}
|
||||||
/> }
|
/>
|
||||||
</span>
|
</span>
|
||||||
</div> }
|
</div> }
|
||||||
{ appTileBody }
|
{ appTileBody }
|
||||||
|
|
|
@ -63,7 +63,7 @@ const EventListSummary: React.FC<IProps> = ({
|
||||||
// If we are only given few events then just pass them through
|
// If we are only given few events then just pass them through
|
||||||
if (events.length < threshold) {
|
if (events.length < threshold) {
|
||||||
return (
|
return (
|
||||||
<li className="mx_EventListSummary" data-scroll-tokens={eventIds}>
|
<li className="mx_EventListSummary" data-scroll-tokens={eventIds} data-expanded={true}>
|
||||||
{ children }
|
{ children }
|
||||||
</li>
|
</li>
|
||||||
);
|
);
|
||||||
|
@ -92,7 +92,7 @@ const EventListSummary: React.FC<IProps> = ({
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<li className="mx_EventListSummary" data-scroll-tokens={eventIds}>
|
<li className="mx_EventListSummary" data-scroll-tokens={eventIds} data-expanded={expanded + ""}>
|
||||||
<AccessibleButton className="mx_EventListSummary_toggle" onClick={toggleExpanded} aria-expanded={expanded}>
|
<AccessibleButton className="mx_EventListSummary_toggle" onClick={toggleExpanded} aria-expanded={expanded}>
|
||||||
{ expanded ? _t('collapse') : _t('expand') }
|
{ expanded ? _t('collapse') : _t('expand') }
|
||||||
</AccessibleButton>
|
</AccessibleButton>
|
||||||
|
@ -101,4 +101,8 @@ const EventListSummary: React.FC<IProps> = ({
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
EventListSummary.defaultProps = {
|
||||||
|
startExpanded: false,
|
||||||
|
};
|
||||||
|
|
||||||
export default EventListSummary;
|
export default EventListSummary;
|
||||||
|
|
|
@ -83,7 +83,7 @@ export default class PersistedElement extends React.Component {
|
||||||
// for this, so we bodge it by listening for document resize and
|
// for this, so we bodge it by listening for document resize and
|
||||||
// the timeline_resize action.
|
// the timeline_resize action.
|
||||||
window.addEventListener('resize', this._repositionChild);
|
window.addEventListener('resize', this._repositionChild);
|
||||||
this._dispatcherRef = dis.register(this._onAction);
|
this.dispatcherRef = dis.register(this._onAction);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -45,7 +45,7 @@ export default class SpellCheckLanguagesDropdown extends React.Component<SpellCh
|
||||||
SpellCheckLanguagesDropdownIState> {
|
SpellCheckLanguagesDropdownIState> {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this._onSearchChange = this._onSearchChange.bind(this);
|
this.onSearchChange = this.onSearchChange.bind(this);
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
searchQuery: '',
|
searchQuery: '',
|
||||||
|
@ -76,10 +76,8 @@ export default class SpellCheckLanguagesDropdown extends React.Component<SpellCh
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_onSearchChange(search) {
|
private onSearchChange(searchQuery: string) {
|
||||||
this.setState({
|
this.setState({ searchQuery });
|
||||||
searchQuery: search,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
@ -117,7 +115,7 @@ export default class SpellCheckLanguagesDropdown extends React.Component<SpellCh
|
||||||
id="mx_LanguageDropdown"
|
id="mx_LanguageDropdown"
|
||||||
className={this.props.className}
|
className={this.props.className}
|
||||||
onOptionChange={this.props.onOptionChange}
|
onOptionChange={this.props.onOptionChange}
|
||||||
onSearchChange={this._onSearchChange}
|
onSearchChange={this.onSearchChange}
|
||||||
searchEnabled={true}
|
searchEnabled={true}
|
||||||
value={value}
|
value={value}
|
||||||
label={_t("Language Dropdown")}>
|
label={_t("Language Dropdown")}>
|
||||||
|
|
|
@ -56,7 +56,7 @@ export default class TextWithTooltip extends React.Component {
|
||||||
{...tooltipProps}
|
{...tooltipProps}
|
||||||
label={tooltip}
|
label={tooltip}
|
||||||
tooltipClassName={tooltipClass}
|
tooltipClassName={tooltipClass}
|
||||||
className={"mx_TextWithTooltip_tooltip"}
|
className="mx_TextWithTooltip_tooltip"
|
||||||
/> }
|
/> }
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
|
|
|
@ -304,13 +304,6 @@ export default class MImageBody extends React.Component<IProps, IState> {
|
||||||
this.downloadImage();
|
this.downloadImage();
|
||||||
this.setState({ showImage: true });
|
this.setState({ showImage: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
this._afterComponentDidMount();
|
|
||||||
}
|
|
||||||
|
|
||||||
// To be overridden by subclasses (e.g. MStickerBody) for further
|
|
||||||
// initialisation after componentDidMount
|
|
||||||
_afterComponentDidMount() {
|
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
|
|
|
@ -16,12 +16,19 @@ limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { forwardRef } from "react";
|
import React, { forwardRef } from "react";
|
||||||
|
import { MatrixEvent } from "matrix-js-sdk/src";
|
||||||
|
|
||||||
export default forwardRef(({ mxEvent }, ref) => {
|
interface IProps {
|
||||||
|
mxEvent: MatrixEvent;
|
||||||
|
children?: React.ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default forwardRef(({ mxEvent, children }: IProps, ref: React.RefObject<HTMLSpanElement>) => {
|
||||||
const text = mxEvent.getContent().body;
|
const text = mxEvent.getContent().body;
|
||||||
return (
|
return (
|
||||||
<span className="mx_UnknownBody" ref={ref}>
|
<span className="mx_UnknownBody" ref={ref}>
|
||||||
{ text }
|
{ text }
|
||||||
|
{ children }
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
});
|
});
|
|
@ -385,7 +385,7 @@ const UserOptionsSection: React.FC<{
|
||||||
}
|
}
|
||||||
|
|
||||||
insertPillButton = (
|
insertPillButton = (
|
||||||
<AccessibleButton onClick={onInsertPillButton} className={"mx_UserInfo_field"}>
|
<AccessibleButton onClick={onInsertPillButton} className="mx_UserInfo_field">
|
||||||
{ _t('Mention') }
|
{ _t('Mention') }
|
||||||
</AccessibleButton>
|
</AccessibleButton>
|
||||||
);
|
);
|
||||||
|
|
|
@ -106,7 +106,7 @@ export default class RelatedGroupSettings extends React.Component {
|
||||||
<EditableItemList
|
<EditableItemList
|
||||||
id="relatedGroups"
|
id="relatedGroups"
|
||||||
items={this.state.newGroupsList}
|
items={this.state.newGroupsList}
|
||||||
className={"mx_RelatedGroupSettings"}
|
className="mx_RelatedGroupSettings"
|
||||||
newItem={this.state.newGroupId}
|
newItem={this.state.newGroupId}
|
||||||
canRemove={this.props.canSetRelatedGroups}
|
canRemove={this.props.canSetRelatedGroups}
|
||||||
canEdit={this.props.canSetRelatedGroups}
|
canEdit={this.props.canSetRelatedGroups}
|
||||||
|
|
|
@ -170,8 +170,6 @@ export function getHandlerTile(ev) {
|
||||||
return eventTileTypes[type];
|
return eventTileTypes[type];
|
||||||
}
|
}
|
||||||
|
|
||||||
const MAX_READ_AVATARS = 5;
|
|
||||||
|
|
||||||
// Our component structure for EventTiles on the timeline is:
|
// Our component structure for EventTiles on the timeline is:
|
||||||
//
|
//
|
||||||
// .-EventTile------------------------------------------------.
|
// .-EventTile------------------------------------------------.
|
||||||
|
@ -297,6 +295,9 @@ interface IProps {
|
||||||
|
|
||||||
// whether or not to always show timestamps
|
// whether or not to always show timestamps
|
||||||
alwaysShowTimestamps?: boolean;
|
alwaysShowTimestamps?: boolean;
|
||||||
|
|
||||||
|
// whether or not to display the sender
|
||||||
|
hideSender?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IState {
|
interface IState {
|
||||||
|
@ -430,7 +431,7 @@ export default class EventTile extends React.Component<IProps, IState> {
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: [REACT-WARNING] Move into constructor
|
// TODO: [REACT-WARNING] Move into constructor
|
||||||
// eslint-disable-next-line camelcase
|
// eslint-disable-next-line
|
||||||
UNSAFE_componentWillMount() {
|
UNSAFE_componentWillMount() {
|
||||||
this.verifyEvent(this.props.mxEvent);
|
this.verifyEvent(this.props.mxEvent);
|
||||||
}
|
}
|
||||||
|
@ -452,7 +453,7 @@ export default class EventTile extends React.Component<IProps, IState> {
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: [REACT-WARNING] Replace with appropriate lifecycle event
|
// TODO: [REACT-WARNING] Replace with appropriate lifecycle event
|
||||||
// eslint-disable-next-line camelcase
|
// eslint-disable-next-line
|
||||||
UNSAFE_componentWillReceiveProps(nextProps) {
|
UNSAFE_componentWillReceiveProps(nextProps) {
|
||||||
// re-check the sender verification as outgoing events progress through
|
// re-check the sender verification as outgoing events progress through
|
||||||
// the send process.
|
// the send process.
|
||||||
|
@ -656,6 +657,10 @@ export default class EventTile extends React.Component<IProps, IState> {
|
||||||
return <SentReceipt messageState={this.props.mxEvent.getAssociatedStatus()} />;
|
return <SentReceipt messageState={this.props.mxEvent.getAssociatedStatus()} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const MAX_READ_AVATARS = this.props.layout == Layout.Bubble
|
||||||
|
? 2
|
||||||
|
: 5;
|
||||||
|
|
||||||
// return early if there are no read receipts
|
// return early if there are no read receipts
|
||||||
if (!this.props.readReceipts || this.props.readReceipts.length === 0) {
|
if (!this.props.readReceipts || this.props.readReceipts.length === 0) {
|
||||||
// We currently must include `mx_EventTile_readAvatars` in the DOM
|
// We currently must include `mx_EventTile_readAvatars` in the DOM
|
||||||
|
@ -951,7 +956,7 @@ export default class EventTile extends React.Component<IProps, IState> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (needsSenderProfile) {
|
if (needsSenderProfile && this.props.hideSender !== true) {
|
||||||
if (!this.props.tileShape) {
|
if (!this.props.tileShape) {
|
||||||
sender = <SenderProfile onClick={this.onSenderProfileClick}
|
sender = <SenderProfile onClick={this.onSenderProfileClick}
|
||||||
mxEvent={this.props.mxEvent}
|
mxEvent={this.props.mxEvent}
|
||||||
|
@ -971,8 +976,12 @@ export default class EventTile extends React.Component<IProps, IState> {
|
||||||
onFocusChange={this.onActionBarFocusChange}
|
onFocusChange={this.onActionBarFocusChange}
|
||||||
/> : undefined;
|
/> : undefined;
|
||||||
|
|
||||||
const showTimestamp = this.props.mxEvent.getTs() &&
|
const showTimestamp = this.props.mxEvent.getTs()
|
||||||
(this.props.alwaysShowTimestamps || this.props.last || this.state.hover || this.state.actionBarFocused);
|
&& (this.props.alwaysShowTimestamps
|
||||||
|
|| this.props.last
|
||||||
|
|| this.state.hover
|
||||||
|
|| this.state.actionBarFocused);
|
||||||
|
|
||||||
const timestamp = showTimestamp ?
|
const timestamp = showTimestamp ?
|
||||||
<MessageTimestamp showTwelveHour={this.props.isTwelveHour} ts={this.props.mxEvent.getTs()} /> : null;
|
<MessageTimestamp showTwelveHour={this.props.isTwelveHour} ts={this.props.mxEvent.getTs()} /> : null;
|
||||||
|
|
||||||
|
@ -1112,6 +1121,8 @@ export default class EventTile extends React.Component<IProps, IState> {
|
||||||
this.props.alwaysShowTimestamps || this.state.hover,
|
this.props.alwaysShowTimestamps || this.state.hover,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const isOwnEvent = this.props.mxEvent?.sender?.userId === MatrixClientPeg.get().getUserId();
|
||||||
|
|
||||||
// tab-index=-1 to allow it to be focusable but do not add tab stop for it, primarily for screen readers
|
// tab-index=-1 to allow it to be focusable but do not add tab stop for it, primarily for screen readers
|
||||||
return (
|
return (
|
||||||
React.createElement(this.props.as || "li", {
|
React.createElement(this.props.as || "li", {
|
||||||
|
@ -1121,6 +1132,9 @@ export default class EventTile extends React.Component<IProps, IState> {
|
||||||
"aria-live": ariaLive,
|
"aria-live": ariaLive,
|
||||||
"aria-atomic": "true",
|
"aria-atomic": "true",
|
||||||
"data-scroll-tokens": scrollToken,
|
"data-scroll-tokens": scrollToken,
|
||||||
|
"data-layout": this.props.layout,
|
||||||
|
"data-self": isOwnEvent,
|
||||||
|
"data-has-reply": !!thread,
|
||||||
"onMouseEnter": () => this.setState({ hover: true }),
|
"onMouseEnter": () => this.setState({ hover: true }),
|
||||||
"onMouseLeave": () => this.setState({ hover: false }),
|
"onMouseLeave": () => this.setState({ hover: false }),
|
||||||
}, <>
|
}, <>
|
||||||
|
@ -1142,9 +1156,9 @@ export default class EventTile extends React.Component<IProps, IState> {
|
||||||
onHeightChanged={this.props.onHeightChanged}
|
onHeightChanged={this.props.onHeightChanged}
|
||||||
/>
|
/>
|
||||||
{ keyRequestInfo }
|
{ keyRequestInfo }
|
||||||
{ reactionsRow }
|
|
||||||
{ actionBar }
|
{ actionBar }
|
||||||
</div>
|
</div>
|
||||||
|
{ reactionsRow }
|
||||||
{ msgOption }
|
{ msgOption }
|
||||||
{ avatar }
|
{ avatar }
|
||||||
</>)
|
</>)
|
||||||
|
|
|
@ -93,7 +93,7 @@ export default class MemberList extends React.Component<IProps, IState> {
|
||||||
this.showPresence = enablePresenceByHsUrl?.[hsUrl] ?? true;
|
this.showPresence = enablePresenceByHsUrl?.[hsUrl] ?? true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line camelcase
|
// eslint-disable-next-line
|
||||||
UNSAFE_componentWillMount() {
|
UNSAFE_componentWillMount() {
|
||||||
const cli = MatrixClientPeg.get();
|
const cli = MatrixClientPeg.get();
|
||||||
this.mounted = true;
|
this.mounted = true;
|
||||||
|
|
|
@ -441,7 +441,7 @@ export default class SendMessageComposer extends React.Component<IProps> {
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: [REACT-WARNING] Move this to constructor
|
// TODO: [REACT-WARNING] Move this to constructor
|
||||||
UNSAFE_componentWillMount() { // eslint-disable-line camelcase
|
UNSAFE_componentWillMount() { // eslint-disable-line
|
||||||
const partCreator = new CommandPartCreator(this.props.room, this.context);
|
const partCreator = new CommandPartCreator(this.props.room, this.context);
|
||||||
const parts = this.restoreStoredEditorState(partCreator) || [];
|
const parts = this.restoreStoredEditorState(partCreator) || [];
|
||||||
this.model = new EditorModel(parts, partCreator);
|
this.model = new EditorModel(parts, partCreator);
|
||||||
|
|
|
@ -64,8 +64,8 @@ export default class WhoIsTypingTile extends React.Component<IProps, IState> {
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate(_, prevState) {
|
componentDidUpdate(_, prevState) {
|
||||||
const wasVisible = this._isVisible(prevState);
|
const wasVisible = WhoIsTypingTile.isVisible(prevState);
|
||||||
const isVisible = this._isVisible(this.state);
|
const isVisible = WhoIsTypingTile.isVisible(this.state);
|
||||||
if (this.props.onShown && !wasVisible && isVisible) {
|
if (this.props.onShown && !wasVisible && isVisible) {
|
||||||
this.props.onShown();
|
this.props.onShown();
|
||||||
} else if (this.props.onHidden && wasVisible && !isVisible) {
|
} else if (this.props.onHidden && wasVisible && !isVisible) {
|
||||||
|
@ -83,12 +83,12 @@ export default class WhoIsTypingTile extends React.Component<IProps, IState> {
|
||||||
Object.values(this.state.delayedStopTypingTimers).forEach((t) => (t as Timer).abort());
|
Object.values(this.state.delayedStopTypingTimers).forEach((t) => (t as Timer).abort());
|
||||||
}
|
}
|
||||||
|
|
||||||
private _isVisible(state: IState): boolean {
|
private static isVisible(state: IState): boolean {
|
||||||
return state.usersTyping.length !== 0 || Object.keys(state.delayedStopTypingTimers).length !== 0;
|
return state.usersTyping.length !== 0 || Object.keys(state.delayedStopTypingTimers).length !== 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public isVisible = (): boolean => {
|
public isVisible = (): boolean => {
|
||||||
return this._isVisible(this.state);
|
return WhoIsTypingTile.isVisible(this.state);
|
||||||
};
|
};
|
||||||
|
|
||||||
private onRoomTimeline = (event: MatrixEvent, room: Room): void => {
|
private onRoomTimeline = (event: MatrixEvent, room: Room): void => {
|
||||||
|
|
|
@ -35,7 +35,7 @@ interface SpellCheckLanguagesIState {
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ExistingSpellCheckLanguage extends React.Component<ExistingSpellCheckLanguageIProps> {
|
export class ExistingSpellCheckLanguage extends React.Component<ExistingSpellCheckLanguageIProps> {
|
||||||
_onRemove = (e) => {
|
private onRemove = (e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
|
@ -46,7 +46,7 @@ export class ExistingSpellCheckLanguage extends React.Component<ExistingSpellChe
|
||||||
return (
|
return (
|
||||||
<div className="mx_ExistingSpellCheckLanguage">
|
<div className="mx_ExistingSpellCheckLanguage">
|
||||||
<span className="mx_ExistingSpellCheckLanguage_language">{ this.props.language }</span>
|
<span className="mx_ExistingSpellCheckLanguage_language">{ this.props.language }</span>
|
||||||
<AccessibleButton onClick={this._onRemove} kind="danger_sm">
|
<AccessibleButton onClick={this.onRemove} kind="danger_sm">
|
||||||
{ _t("Remove") }
|
{ _t("Remove") }
|
||||||
</AccessibleButton>
|
</AccessibleButton>
|
||||||
</div>
|
</div>
|
||||||
|
@ -63,12 +63,12 @@ export default class SpellCheckLanguages extends React.Component<SpellCheckLangu
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
_onRemoved = (language) => {
|
private onRemoved = (language: string) => {
|
||||||
const languages = this.props.languages.filter((e) => e !== language);
|
const languages = this.props.languages.filter((e) => e !== language);
|
||||||
this.props.onLanguagesChange(languages);
|
this.props.onLanguagesChange(languages);
|
||||||
};
|
};
|
||||||
|
|
||||||
_onAddClick = (e) => {
|
private onAddClick = (e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
|
@ -81,18 +81,18 @@ export default class SpellCheckLanguages extends React.Component<SpellCheckLangu
|
||||||
this.props.onLanguagesChange(this.props.languages);
|
this.props.onLanguagesChange(this.props.languages);
|
||||||
};
|
};
|
||||||
|
|
||||||
_onNewLanguageChange = (language: string) => {
|
private onNewLanguageChange = (language: string) => {
|
||||||
if (this.state.newLanguage === language) return;
|
if (this.state.newLanguage === language) return;
|
||||||
this.setState({ newLanguage: language });
|
this.setState({ newLanguage: language });
|
||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const existingSpellCheckLanguages = this.props.languages.map((e) => {
|
const existingSpellCheckLanguages = this.props.languages.map((e) => {
|
||||||
return <ExistingSpellCheckLanguage language={e} onRemoved={this._onRemoved} key={e} />;
|
return <ExistingSpellCheckLanguage language={e} onRemoved={this.onRemoved} key={e} />;
|
||||||
});
|
});
|
||||||
|
|
||||||
const addButton = (
|
const addButton = (
|
||||||
<AccessibleButton onClick={this._onAddClick} kind="primary">
|
<AccessibleButton onClick={this.onAddClick} kind="primary">
|
||||||
{ _t("Add") }
|
{ _t("Add") }
|
||||||
</AccessibleButton>
|
</AccessibleButton>
|
||||||
);
|
);
|
||||||
|
@ -100,11 +100,11 @@ export default class SpellCheckLanguages extends React.Component<SpellCheckLangu
|
||||||
return (
|
return (
|
||||||
<div className="mx_SpellCheckLanguages">
|
<div className="mx_SpellCheckLanguages">
|
||||||
{ existingSpellCheckLanguages }
|
{ existingSpellCheckLanguages }
|
||||||
<form onSubmit={this._onAddClick} noValidate={true}>
|
<form onSubmit={this.onAddClick} noValidate={true}>
|
||||||
<SpellCheckLanguagesDropdown
|
<SpellCheckLanguagesDropdown
|
||||||
className="mx_GeneralUserSettingsTab_spellCheckLanguageInput"
|
className="mx_GeneralUserSettingsTab_spellCheckLanguageInput"
|
||||||
value={this.state.newLanguage}
|
value={this.state.newLanguage}
|
||||||
onOptionChange={this._onNewLanguageChange} />
|
onOptionChange={this.onNewLanguageChange} />
|
||||||
{ addButton }
|
{ addButton }
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -78,7 +78,7 @@ export default class SecurityRoomSettingsTab extends React.Component<IProps, ISt
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: [REACT-WARNING] Move this to constructor
|
// TODO: [REACT-WARNING] Move this to constructor
|
||||||
async UNSAFE_componentWillMount() { // eslint-disable-line camelcase
|
async UNSAFE_componentWillMount() { // eslint-disable-line
|
||||||
MatrixClientPeg.get().on("RoomState.events", this.onStateEvent);
|
MatrixClientPeg.get().on("RoomState.events", this.onStateEvent);
|
||||||
|
|
||||||
const room = MatrixClientPeg.get().getRoom(this.props.roomId);
|
const room = MatrixClientPeg.get().getRoom(this.props.roomId);
|
||||||
|
|
|
@ -37,6 +37,8 @@ import StyledRadioGroup from "../../../elements/StyledRadioGroup";
|
||||||
import { SettingLevel } from "../../../../../settings/SettingLevel";
|
import { SettingLevel } from "../../../../../settings/SettingLevel";
|
||||||
import { UIFeature } from "../../../../../settings/UIFeature";
|
import { UIFeature } from "../../../../../settings/UIFeature";
|
||||||
import { Layout } from "../../../../../settings/Layout";
|
import { Layout } from "../../../../../settings/Layout";
|
||||||
|
import classNames from 'classnames';
|
||||||
|
import StyledRadioButton from '../../../elements/StyledRadioButton';
|
||||||
import { replaceableComponent } from "../../../../../utils/replaceableComponent";
|
import { replaceableComponent } from "../../../../../utils/replaceableComponent";
|
||||||
import { compare } from "../../../../../utils/strings";
|
import { compare } from "../../../../../utils/strings";
|
||||||
|
|
||||||
|
@ -241,6 +243,19 @@ export default class AppearanceUserSettingsTab extends React.Component<IProps, I
|
||||||
this.setState({ customThemeUrl: e.target.value });
|
this.setState({ customThemeUrl: e.target.value });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private onLayoutChange = (e: React.ChangeEvent<HTMLInputElement>): void => {
|
||||||
|
let layout;
|
||||||
|
switch (e.target.value) {
|
||||||
|
case "irc": layout = Layout.IRC; break;
|
||||||
|
case "group": layout = Layout.Group; break;
|
||||||
|
case "bubble": layout = Layout.Bubble; break;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setState({ layout: layout });
|
||||||
|
|
||||||
|
SettingsStore.setValue("layout", null, SettingLevel.DEVICE, layout);
|
||||||
|
};
|
||||||
|
|
||||||
private onIRCLayoutChange = (enabled: boolean) => {
|
private onIRCLayoutChange = (enabled: boolean) => {
|
||||||
if (enabled) {
|
if (enabled) {
|
||||||
this.setState({ layout: Layout.IRC });
|
this.setState({ layout: Layout.IRC });
|
||||||
|
@ -373,6 +388,77 @@ export default class AppearanceUserSettingsTab extends React.Component<IProps, I
|
||||||
</div>;
|
</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private renderLayoutSection = () => {
|
||||||
|
return <div className="mx_SettingsTab_section mx_AppearanceUserSettingsTab_Layout">
|
||||||
|
<span className="mx_SettingsTab_subheading">{ _t("Message layout") }</span>
|
||||||
|
|
||||||
|
<div className="mx_AppearanceUserSettingsTab_Layout_RadioButtons">
|
||||||
|
<div className={classNames("mx_AppearanceUserSettingsTab_Layout_RadioButton", {
|
||||||
|
mx_AppearanceUserSettingsTab_Layout_RadioButton_selected: this.state.layout == Layout.IRC,
|
||||||
|
})}>
|
||||||
|
<EventTilePreview
|
||||||
|
className="mx_AppearanceUserSettingsTab_Layout_RadioButton_preview"
|
||||||
|
message={this.MESSAGE_PREVIEW_TEXT}
|
||||||
|
layout={Layout.IRC}
|
||||||
|
userId={this.state.userId}
|
||||||
|
displayName={this.state.displayName}
|
||||||
|
avatarUrl={this.state.avatarUrl}
|
||||||
|
/>
|
||||||
|
<StyledRadioButton
|
||||||
|
name="layout"
|
||||||
|
value="irc"
|
||||||
|
checked={this.state.layout === Layout.IRC}
|
||||||
|
onChange={this.onLayoutChange}
|
||||||
|
>
|
||||||
|
{ _t("IRC") }
|
||||||
|
</StyledRadioButton>
|
||||||
|
</div>
|
||||||
|
<div className="mx_AppearanceUserSettingsTab_spacer" />
|
||||||
|
<div className={classNames("mx_AppearanceUserSettingsTab_Layout_RadioButton", {
|
||||||
|
mx_AppearanceUserSettingsTab_Layout_RadioButton_selected: this.state.layout == Layout.Group,
|
||||||
|
})}>
|
||||||
|
<EventTilePreview
|
||||||
|
className="mx_AppearanceUserSettingsTab_Layout_RadioButton_preview"
|
||||||
|
message={this.MESSAGE_PREVIEW_TEXT}
|
||||||
|
layout={Layout.Group}
|
||||||
|
userId={this.state.userId}
|
||||||
|
displayName={this.state.displayName}
|
||||||
|
avatarUrl={this.state.avatarUrl}
|
||||||
|
/>
|
||||||
|
<StyledRadioButton
|
||||||
|
name="layout"
|
||||||
|
value="group"
|
||||||
|
checked={this.state.layout == Layout.Group}
|
||||||
|
onChange={this.onLayoutChange}
|
||||||
|
>
|
||||||
|
{ _t("Modern") }
|
||||||
|
</StyledRadioButton>
|
||||||
|
</div>
|
||||||
|
<div className="mx_AppearanceUserSettingsTab_spacer" />
|
||||||
|
<div className={classNames("mx_AppearanceUserSettingsTab_Layout_RadioButton", {
|
||||||
|
mx_AppearanceUserSettingsTab_Layout_RadioButton_selected: this.state.layout === Layout.Bubble,
|
||||||
|
})}>
|
||||||
|
<EventTilePreview
|
||||||
|
className="mx_AppearanceUserSettingsTab_Layout_RadioButton_preview"
|
||||||
|
message={this.MESSAGE_PREVIEW_TEXT}
|
||||||
|
layout={Layout.Bubble}
|
||||||
|
userId={this.state.userId}
|
||||||
|
displayName={this.state.displayName}
|
||||||
|
avatarUrl={this.state.avatarUrl}
|
||||||
|
/>
|
||||||
|
<StyledRadioButton
|
||||||
|
name="layout"
|
||||||
|
value="bubble"
|
||||||
|
checked={this.state.layout == Layout.Bubble}
|
||||||
|
onChange={this.onLayoutChange}
|
||||||
|
>
|
||||||
|
{ _t("Message bubbles") }
|
||||||
|
</StyledRadioButton>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>;
|
||||||
|
};
|
||||||
|
|
||||||
private renderAdvancedSection() {
|
private renderAdvancedSection() {
|
||||||
if (!SettingsStore.getValue(UIFeature.AdvancedSettings)) return null;
|
if (!SettingsStore.getValue(UIFeature.AdvancedSettings)) return null;
|
||||||
|
|
||||||
|
@ -396,14 +482,17 @@ export default class AppearanceUserSettingsTab extends React.Component<IProps, I
|
||||||
name="useCompactLayout"
|
name="useCompactLayout"
|
||||||
level={SettingLevel.DEVICE}
|
level={SettingLevel.DEVICE}
|
||||||
useCheckbox={true}
|
useCheckbox={true}
|
||||||
disabled={this.state.layout == Layout.IRC}
|
disabled={this.state.layout !== Layout.Group}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
{ !SettingsStore.getValue("feature_new_layout_switcher") ?
|
||||||
<StyledCheckbox
|
<StyledCheckbox
|
||||||
checked={this.state.layout == Layout.IRC}
|
checked={this.state.layout == Layout.IRC}
|
||||||
onChange={(ev) => this.onIRCLayoutChange(ev.target.checked)}
|
onChange={(ev) => this.onIRCLayoutChange(ev.target.checked)}
|
||||||
>
|
>
|
||||||
{ _t("Enable experimental, compact IRC style layout") }
|
{ _t("Enable experimental, compact IRC style layout") }
|
||||||
</StyledCheckbox>
|
</StyledCheckbox> : null
|
||||||
|
}
|
||||||
|
|
||||||
<SettingsFlag
|
<SettingsFlag
|
||||||
name="useSystemFont"
|
name="useSystemFont"
|
||||||
|
@ -444,6 +533,7 @@ export default class AppearanceUserSettingsTab extends React.Component<IProps, I
|
||||||
{ _t("Appearance Settings only affect this %(brand)s session.", { brand }) }
|
{ _t("Appearance Settings only affect this %(brand)s session.", { brand }) }
|
||||||
</div>
|
</div>
|
||||||
{ this.renderThemeSection() }
|
{ this.renderThemeSection() }
|
||||||
|
{ SettingsStore.getValue("feature_new_layout_switcher") ? this.renderLayoutSection() : null }
|
||||||
{ this.renderFontSection() }
|
{ this.renderFontSection() }
|
||||||
{ this.renderAdvancedSection() }
|
{ this.renderAdvancedSection() }
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -60,14 +60,14 @@ export default class VerificationRequestToast extends React.PureComponent<IProps
|
||||||
this.setState({ counter });
|
this.setState({ counter });
|
||||||
}, 1000);
|
}, 1000);
|
||||||
}
|
}
|
||||||
request.on("change", this._checkRequestIsPending);
|
request.on("change", this.checkRequestIsPending);
|
||||||
// We should probably have a separate class managing the active verification toasts,
|
// We should probably have a separate class managing the active verification toasts,
|
||||||
// rather than monitoring this in the toast component itself, since we'll get problems
|
// rather than monitoring this in the toast component itself, since we'll get problems
|
||||||
// like the toasdt not going away when the verification is cancelled unless it's the
|
// like the toasdt not going away when the verification is cancelled unless it's the
|
||||||
// one on the top (ie. the one that's mounted).
|
// one on the top (ie. the one that's mounted).
|
||||||
// As a quick & dirty fix, check the toast is still relevant when it mounts (this prevents
|
// As a quick & dirty fix, check the toast is still relevant when it mounts (this prevents
|
||||||
// a toast hanging around after logging in if you did a verification as part of login).
|
// a toast hanging around after logging in if you did a verification as part of login).
|
||||||
this._checkRequestIsPending();
|
this.checkRequestIsPending();
|
||||||
|
|
||||||
if (request.isSelfVerification) {
|
if (request.isSelfVerification) {
|
||||||
const cli = MatrixClientPeg.get();
|
const cli = MatrixClientPeg.get();
|
||||||
|
@ -83,10 +83,10 @@ export default class VerificationRequestToast extends React.PureComponent<IProps
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
clearInterval(this.intervalHandle);
|
clearInterval(this.intervalHandle);
|
||||||
const { request } = this.props;
|
const { request } = this.props;
|
||||||
request.off("change", this._checkRequestIsPending);
|
request.off("change", this.checkRequestIsPending);
|
||||||
}
|
}
|
||||||
|
|
||||||
_checkRequestIsPending = () => {
|
private checkRequestIsPending = () => {
|
||||||
const { request } = this.props;
|
const { request } = this.props;
|
||||||
if (!request.canAccept) {
|
if (!request.canAccept) {
|
||||||
ToastStore.sharedInstance().dismissToast(this.props.toastKey);
|
ToastStore.sharedInstance().dismissToast(this.props.toastKey);
|
||||||
|
|
|
@ -513,7 +513,9 @@ export default class CallView extends React.Component<IProps, IState> {
|
||||||
transferee: transfereeName,
|
transferee: transfereeName,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
a: sub => <AccessibleButton kind="link" onClick={this.onTransferClick}>{sub}</AccessibleButton>,
|
a: sub => <AccessibleButton kind="link" onClick={this.onTransferClick}>
|
||||||
|
{ sub }
|
||||||
|
</AccessibleButton>,
|
||||||
},
|
},
|
||||||
) }
|
) }
|
||||||
</div>;
|
</div>;
|
||||||
|
|
|
@ -144,7 +144,7 @@ export default class IncomingCallBox extends React.Component<IProps, IState> {
|
||||||
</div>
|
</div>
|
||||||
<div className="mx_IncomingCallBox_buttons">
|
<div className="mx_IncomingCallBox_buttons">
|
||||||
<AccessibleButton
|
<AccessibleButton
|
||||||
className={"mx_IncomingCallBox_decline"}
|
className="mx_IncomingCallBox_decline"
|
||||||
onClick={this.onRejectClick}
|
onClick={this.onRejectClick}
|
||||||
kind="danger"
|
kind="danger"
|
||||||
>
|
>
|
||||||
|
@ -152,7 +152,7 @@ export default class IncomingCallBox extends React.Component<IProps, IState> {
|
||||||
</AccessibleButton>
|
</AccessibleButton>
|
||||||
<div className="mx_IncomingCallBox_spacer" />
|
<div className="mx_IncomingCallBox_spacer" />
|
||||||
<AccessibleButton
|
<AccessibleButton
|
||||||
className={"mx_IncomingCallBox_accept"}
|
className="mx_IncomingCallBox_accept"
|
||||||
onClick={this.onAnswerClick}
|
onClick={this.onAnswerClick}
|
||||||
kind="primary"
|
kind="primary"
|
||||||
>
|
>
|
||||||
|
|
|
@ -17,6 +17,7 @@ limitations under the License.
|
||||||
|
|
||||||
import { MatrixClient } from "matrix-js-sdk/src/client";
|
import { MatrixClient } from "matrix-js-sdk/src/client";
|
||||||
import { Room } from "matrix-js-sdk/src/models/room";
|
import { Room } from "matrix-js-sdk/src/models/room";
|
||||||
|
import { RoomMember } from "matrix-js-sdk/src/models/room-member";
|
||||||
import { EventType } from "matrix-js-sdk/src/@types/event";
|
import { EventType } from "matrix-js-sdk/src/@types/event";
|
||||||
|
|
||||||
import { MatrixClientPeg } from './MatrixClientPeg';
|
import { MatrixClientPeg } from './MatrixClientPeg';
|
||||||
|
@ -247,11 +248,11 @@ export function findDMForUser(client: MatrixClient, userId: string): Room {
|
||||||
* NOTE: this assumes you've just created the room and there's not been an opportunity
|
* NOTE: this assumes you've just created the room and there's not been an opportunity
|
||||||
* for other code to run, so we shouldn't miss RoomState.newMember when it comes by.
|
* for other code to run, so we shouldn't miss RoomState.newMember when it comes by.
|
||||||
*/
|
*/
|
||||||
export async function _waitForMember(client: MatrixClient, roomId: string, userId: string, opts = { timeout: 1500 }) {
|
export async function waitForMember(client: MatrixClient, roomId: string, userId: string, opts = { timeout: 1500 }) {
|
||||||
const { timeout } = opts;
|
const { timeout } = opts;
|
||||||
let handler;
|
let handler;
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
handler = function(_event, _roomstate, member) {
|
handler = function(_, __, member: RoomMember) { // eslint-disable-line @typescript-eslint/naming-convention
|
||||||
if (member.userId !== userId) return;
|
if (member.userId !== userId) return;
|
||||||
if (member.roomId !== roomId) return;
|
if (member.roomId !== roomId) return;
|
||||||
resolve(true);
|
resolve(true);
|
||||||
|
@ -324,7 +325,7 @@ export async function ensureDMExists(client: MatrixClient, userId: string): Prom
|
||||||
}
|
}
|
||||||
|
|
||||||
roomId = await createRoom({ encryption, dmUserId: userId, spinner: false, andView: false });
|
roomId = await createRoom({ encryption, dmUserId: userId, spinner: false, andView: false });
|
||||||
await _waitForMember(client, roomId, userId);
|
await waitForMember(client, roomId, userId);
|
||||||
}
|
}
|
||||||
return roomId;
|
return roomId;
|
||||||
}
|
}
|
||||||
|
|
|
@ -274,7 +274,7 @@ abstract class PillPart extends BasePart implements IPillPart {
|
||||||
}
|
}
|
||||||
|
|
||||||
// helper method for subclasses
|
// helper method for subclasses
|
||||||
_setAvatarVars(node: HTMLElement, avatarUrl: string, initialLetter: string) {
|
protected setAvatarVars(node: HTMLElement, avatarUrl: string, initialLetter: string) {
|
||||||
const avatarBackground = `url('${avatarUrl}')`;
|
const avatarBackground = `url('${avatarUrl}')`;
|
||||||
const avatarLetter = `'${initialLetter}'`;
|
const avatarLetter = `'${initialLetter}'`;
|
||||||
// check if the value is changing,
|
// check if the value is changing,
|
||||||
|
@ -354,7 +354,7 @@ class RoomPillPart extends PillPart {
|
||||||
initialLetter = Avatar.getInitialLetter(this.room ? this.room.name : this.resourceId);
|
initialLetter = Avatar.getInitialLetter(this.room ? this.room.name : this.resourceId);
|
||||||
avatarUrl = Avatar.defaultAvatarUrlForString(this.room ? this.room.roomId : this.resourceId);
|
avatarUrl = Avatar.defaultAvatarUrlForString(this.room ? this.room.roomId : this.resourceId);
|
||||||
}
|
}
|
||||||
this._setAvatarVars(node, avatarUrl, initialLetter);
|
this.setAvatarVars(node, avatarUrl, initialLetter);
|
||||||
}
|
}
|
||||||
|
|
||||||
get type(): IPillPart["type"] {
|
get type(): IPillPart["type"] {
|
||||||
|
@ -399,7 +399,7 @@ class UserPillPart extends PillPart {
|
||||||
if (avatarUrl === defaultAvatarUrl) {
|
if (avatarUrl === defaultAvatarUrl) {
|
||||||
initialLetter = Avatar.getInitialLetter(name);
|
initialLetter = Avatar.getInitialLetter(name);
|
||||||
}
|
}
|
||||||
this._setAvatarVars(node, avatarUrl, initialLetter);
|
this.setAvatarVars(node, avatarUrl, initialLetter);
|
||||||
}
|
}
|
||||||
|
|
||||||
get type(): IPillPart["type"] {
|
get type(): IPillPart["type"] {
|
||||||
|
|
|
@ -823,6 +823,7 @@
|
||||||
"Offline encrypted messaging using dehydrated devices": "Offline encrypted messaging using dehydrated devices",
|
"Offline encrypted messaging using dehydrated devices": "Offline encrypted messaging using dehydrated devices",
|
||||||
"Enable advanced debugging for the room list": "Enable advanced debugging for the room list",
|
"Enable advanced debugging for the room list": "Enable advanced debugging for the room list",
|
||||||
"Show info about bridges in room settings": "Show info about bridges in room settings",
|
"Show info about bridges in room settings": "Show info about bridges in room settings",
|
||||||
|
"New layout switcher (with message bubbles)": "New layout switcher (with message bubbles)",
|
||||||
"Font size": "Font size",
|
"Font size": "Font size",
|
||||||
"Use custom size": "Use custom size",
|
"Use custom size": "Use custom size",
|
||||||
"Enable Emoji suggestions while typing": "Enable Emoji suggestions while typing",
|
"Enable Emoji suggestions while typing": "Enable Emoji suggestions while typing",
|
||||||
|
@ -1245,6 +1246,10 @@
|
||||||
"Custom theme URL": "Custom theme URL",
|
"Custom theme URL": "Custom theme URL",
|
||||||
"Add theme": "Add theme",
|
"Add theme": "Add theme",
|
||||||
"Theme": "Theme",
|
"Theme": "Theme",
|
||||||
|
"Message layout": "Message layout",
|
||||||
|
"IRC": "IRC",
|
||||||
|
"Modern": "Modern",
|
||||||
|
"Message bubbles": "Message bubbles",
|
||||||
"Set the name of a font installed on your system & %(brand)s will attempt to use it.": "Set the name of a font installed on your system & %(brand)s will attempt to use it.",
|
"Set the name of a font installed on your system & %(brand)s will attempt to use it.": "Set the name of a font installed on your system & %(brand)s will attempt to use it.",
|
||||||
"Enable experimental, compact IRC style layout": "Enable experimental, compact IRC style layout",
|
"Enable experimental, compact IRC style layout": "Enable experimental, compact IRC style layout",
|
||||||
"Customise your appearance": "Customise your appearance",
|
"Customise your appearance": "Customise your appearance",
|
||||||
|
|
|
@ -67,7 +67,7 @@ export function getUserLanguage(): string {
|
||||||
|
|
||||||
// Function which only purpose is to mark that a string is translatable
|
// Function which only purpose is to mark that a string is translatable
|
||||||
// Does not actually do anything. It's helpful for automatic extraction of translatable strings
|
// Does not actually do anything. It's helpful for automatic extraction of translatable strings
|
||||||
export function _td(s: string): string {
|
export function _td(s: string): string { // eslint-disable-line @typescript-eslint/naming-convention
|
||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -132,6 +132,8 @@ export type TranslatedString = string | React.ReactNode;
|
||||||
*
|
*
|
||||||
* @return a React <span> component if any non-strings were used in substitutions, otherwise a string
|
* @return a React <span> component if any non-strings were used in substitutions, otherwise a string
|
||||||
*/
|
*/
|
||||||
|
// eslint-next-line @typescript-eslint/naming-convention
|
||||||
|
// eslint-nexline @typescript-eslint/naming-convention
|
||||||
export function _t(text: string, variables?: IVariables): string;
|
export function _t(text: string, variables?: IVariables): string;
|
||||||
export function _t(text: string, variables: IVariables, tags: Tags): React.ReactNode;
|
export function _t(text: string, variables: IVariables, tags: Tags): React.ReactNode;
|
||||||
export function _t(text: string, variables?: IVariables, tags?: Tags): TranslatedString {
|
export function _t(text: string, variables?: IVariables, tags?: Tags): TranslatedString {
|
||||||
|
|
|
@ -14,6 +14,7 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
|
||||||
import { MatrixClientPeg } from "../MatrixClientPeg";
|
import { MatrixClientPeg } from "../MatrixClientPeg";
|
||||||
import { ALL_RULE_TYPES, BanList } from "./BanList";
|
import { ALL_RULE_TYPES, BanList } from "./BanList";
|
||||||
import SettingsStore from "../settings/SettingsStore";
|
import SettingsStore from "../settings/SettingsStore";
|
||||||
|
@ -21,19 +22,17 @@ import { _t } from "../languageHandler";
|
||||||
import dis from "../dispatcher/dispatcher";
|
import dis from "../dispatcher/dispatcher";
|
||||||
import { SettingLevel } from "../settings/SettingLevel";
|
import { SettingLevel } from "../settings/SettingLevel";
|
||||||
import { Preset } from "matrix-js-sdk/src/@types/partials";
|
import { Preset } from "matrix-js-sdk/src/@types/partials";
|
||||||
|
import { ActionPayload } from "../dispatcher/payloads";
|
||||||
|
|
||||||
// TODO: Move this and related files to the js-sdk or something once finalized.
|
// TODO: Move this and related files to the js-sdk or something once finalized.
|
||||||
|
|
||||||
export class Mjolnir {
|
export class Mjolnir {
|
||||||
static _instance: Mjolnir = null;
|
private static instance: Mjolnir = null;
|
||||||
|
|
||||||
_lists: BanList[] = [];
|
private _lists: BanList[] = []; // eslint-disable-line @typescript-eslint/naming-convention
|
||||||
_roomIds: string[] = [];
|
private _roomIds: string[] = []; // eslint-disable-line @typescript-eslint/naming-convention
|
||||||
_mjolnirWatchRef = null;
|
private mjolnirWatchRef: string = null;
|
||||||
_dispatcherRef = null;
|
private dispatcherRef: string = null;
|
||||||
|
|
||||||
constructor() {
|
|
||||||
}
|
|
||||||
|
|
||||||
get roomIds(): string[] {
|
get roomIds(): string[] {
|
||||||
return this._roomIds;
|
return this._roomIds;
|
||||||
|
@ -44,16 +43,16 @@ export class Mjolnir {
|
||||||
}
|
}
|
||||||
|
|
||||||
start() {
|
start() {
|
||||||
this._mjolnirWatchRef = SettingsStore.watchSetting("mjolnirRooms", null, this._onListsChanged.bind(this));
|
this.mjolnirWatchRef = SettingsStore.watchSetting("mjolnirRooms", null, this.onListsChanged.bind(this));
|
||||||
|
|
||||||
this._dispatcherRef = dis.register(this._onAction);
|
this.dispatcherRef = dis.register(this.onAction);
|
||||||
dis.dispatch({
|
dis.dispatch({
|
||||||
action: 'do_after_sync_prepared',
|
action: 'do_after_sync_prepared',
|
||||||
deferred_action: { action: 'setup_mjolnir' },
|
deferred_action: { action: 'setup_mjolnir' },
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
_onAction = (payload) => {
|
private onAction = (payload: ActionPayload) => {
|
||||||
if (payload['action'] === 'setup_mjolnir') {
|
if (payload['action'] === 'setup_mjolnir') {
|
||||||
console.log("Setting up Mjolnir: after sync");
|
console.log("Setting up Mjolnir: after sync");
|
||||||
this.setup();
|
this.setup();
|
||||||
|
@ -62,23 +61,23 @@ export class Mjolnir {
|
||||||
|
|
||||||
setup() {
|
setup() {
|
||||||
if (!MatrixClientPeg.get()) return;
|
if (!MatrixClientPeg.get()) return;
|
||||||
this._updateLists(SettingsStore.getValue("mjolnirRooms"));
|
this.updateLists(SettingsStore.getValue("mjolnirRooms"));
|
||||||
MatrixClientPeg.get().on("RoomState.events", this._onEvent);
|
MatrixClientPeg.get().on("RoomState.events", this.onEvent);
|
||||||
}
|
}
|
||||||
|
|
||||||
stop() {
|
stop() {
|
||||||
if (this._mjolnirWatchRef) {
|
if (this.mjolnirWatchRef) {
|
||||||
SettingsStore.unwatchSetting(this._mjolnirWatchRef);
|
SettingsStore.unwatchSetting(this.mjolnirWatchRef);
|
||||||
this._mjolnirWatchRef = null;
|
this.mjolnirWatchRef = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this._dispatcherRef) {
|
if (this.dispatcherRef) {
|
||||||
dis.unregister(this._dispatcherRef);
|
dis.unregister(this.dispatcherRef);
|
||||||
this._dispatcherRef = null;
|
this.dispatcherRef = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!MatrixClientPeg.get()) return;
|
if (!MatrixClientPeg.get()) return;
|
||||||
MatrixClientPeg.get().removeListener("RoomState.events", this._onEvent);
|
MatrixClientPeg.get().removeListener("RoomState.events", this.onEvent);
|
||||||
}
|
}
|
||||||
|
|
||||||
async getOrCreatePersonalList(): Promise<BanList> {
|
async getOrCreatePersonalList(): Promise<BanList> {
|
||||||
|
@ -132,20 +131,20 @@ export class Mjolnir {
|
||||||
this._lists = this._lists.filter(b => b.roomId !== roomId);
|
this._lists = this._lists.filter(b => b.roomId !== roomId);
|
||||||
}
|
}
|
||||||
|
|
||||||
_onEvent = (event) => {
|
private onEvent = (event: MatrixEvent) => {
|
||||||
if (!MatrixClientPeg.get()) return;
|
if (!MatrixClientPeg.get()) return;
|
||||||
if (!this._roomIds.includes(event.getRoomId())) return;
|
if (!this._roomIds.includes(event.getRoomId())) return;
|
||||||
if (!ALL_RULE_TYPES.includes(event.getType())) return;
|
if (!ALL_RULE_TYPES.includes(event.getType())) return;
|
||||||
|
|
||||||
this._updateLists(this._roomIds);
|
this.updateLists(this._roomIds);
|
||||||
};
|
};
|
||||||
|
|
||||||
_onListsChanged(settingName, roomId, atLevel, newValue) {
|
private onListsChanged(settingName: string, roomId: string, atLevel: SettingLevel, newValue: string[]) {
|
||||||
// We know that ban lists are only recorded at one level so we don't need to re-eval them
|
// We know that ban lists are only recorded at one level so we don't need to re-eval them
|
||||||
this._updateLists(newValue);
|
this.updateLists(newValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
_updateLists(listRoomIds: string[]) {
|
private updateLists(listRoomIds: string[]) {
|
||||||
if (!MatrixClientPeg.get()) return;
|
if (!MatrixClientPeg.get()) return;
|
||||||
|
|
||||||
console.log("Updating Mjolnir ban lists to: " + listRoomIds);
|
console.log("Updating Mjolnir ban lists to: " + listRoomIds);
|
||||||
|
@ -182,10 +181,10 @@ export class Mjolnir {
|
||||||
}
|
}
|
||||||
|
|
||||||
static sharedInstance(): Mjolnir {
|
static sharedInstance(): Mjolnir {
|
||||||
if (!Mjolnir._instance) {
|
if (!Mjolnir.instance) {
|
||||||
Mjolnir._instance = new Mjolnir();
|
Mjolnir.instance = new Mjolnir();
|
||||||
}
|
}
|
||||||
return Mjolnir._instance;
|
return Mjolnir.instance;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -203,7 +203,7 @@ export default async function sendBugReport(bugReportEndpoint: string, opts: IOp
|
||||||
const body = await collectBugReport(opts);
|
const body = await collectBugReport(opts);
|
||||||
|
|
||||||
progressCallback(_t("Uploading logs"));
|
progressCallback(_t("Uploading logs"));
|
||||||
await _submitReport(bugReportEndpoint, body, progressCallback);
|
await submitReport(bugReportEndpoint, body, progressCallback);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -289,10 +289,10 @@ export async function submitFeedback(
|
||||||
body.append(k, extraData[k]);
|
body.append(k, extraData[k]);
|
||||||
}
|
}
|
||||||
|
|
||||||
await _submitReport(SdkConfig.get().bug_report_endpoint_url, body, () => {});
|
await submitReport(SdkConfig.get().bug_report_endpoint_url, body, () => {});
|
||||||
}
|
}
|
||||||
|
|
||||||
function _submitReport(endpoint: string, body: FormData, progressCallback: (string) => void) {
|
function submitReport(endpoint: string, body: FormData, progressCallback: (str: string) => void) {
|
||||||
return new Promise<void>((resolve, reject) => {
|
return new Promise<void>((resolve, reject) => {
|
||||||
const req = new XMLHttpRequest();
|
const req = new XMLHttpRequest();
|
||||||
req.open("POST", endpoint);
|
req.open("POST", endpoint);
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
/*
|
/*
|
||||||
Copyright 2021 Šimon Brandner <simon.bra.ag@gmail.com>
|
Copyright 2021 Šimon Brandner <simon.bra.ag@gmail.com>
|
||||||
|
Copyright 2021 Quirin Götz <codeworks@supercable.onl>
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
|
@ -19,7 +20,8 @@ import PropTypes from 'prop-types';
|
||||||
/* TODO: This should be later reworked into something more generic */
|
/* TODO: This should be later reworked into something more generic */
|
||||||
export enum Layout {
|
export enum Layout {
|
||||||
IRC = "irc",
|
IRC = "irc",
|
||||||
Group = "group"
|
Group = "group",
|
||||||
|
Bubble = "bubble",
|
||||||
}
|
}
|
||||||
|
|
||||||
/* We need this because multiple components are still using JavaScript */
|
/* We need this because multiple components are still using JavaScript */
|
||||||
|
|
|
@ -41,6 +41,7 @@ import { Layout } from "./Layout";
|
||||||
import ReducedMotionController from './controllers/ReducedMotionController';
|
import ReducedMotionController from './controllers/ReducedMotionController';
|
||||||
import IncompatibleController from "./controllers/IncompatibleController";
|
import IncompatibleController from "./controllers/IncompatibleController";
|
||||||
import SdkConfig from "../SdkConfig";
|
import SdkConfig from "../SdkConfig";
|
||||||
|
import NewLayoutSwitcherController from './controllers/NewLayoutSwitcherController';
|
||||||
|
|
||||||
// These are just a bunch of helper arrays to avoid copy/pasting a bunch of times
|
// These are just a bunch of helper arrays to avoid copy/pasting a bunch of times
|
||||||
const LEVELS_ROOM_SETTINGS = [
|
const LEVELS_ROOM_SETTINGS = [
|
||||||
|
@ -321,6 +322,13 @@ export const SETTINGS: {[setting: string]: ISetting} = {
|
||||||
displayName: _td("Show info about bridges in room settings"),
|
displayName: _td("Show info about bridges in room settings"),
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
|
"feature_new_layout_switcher": {
|
||||||
|
isFeature: true,
|
||||||
|
supportedLevels: LEVELS_FEATURE,
|
||||||
|
displayName: _td("New layout switcher (with message bubbles)"),
|
||||||
|
default: false,
|
||||||
|
controller: new NewLayoutSwitcherController(),
|
||||||
|
},
|
||||||
"RoomList.backgroundImage": {
|
"RoomList.backgroundImage": {
|
||||||
supportedLevels: LEVELS_ACCOUNT_SETTINGS,
|
supportedLevels: LEVELS_ACCOUNT_SETTINGS,
|
||||||
default: null,
|
default: null,
|
||||||
|
|
26
src/settings/controllers/NewLayoutSwitcherController.ts
Normal file
26
src/settings/controllers/NewLayoutSwitcherController.ts
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
/*
|
||||||
|
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 SettingController from "./SettingController";
|
||||||
|
import { SettingLevel } from "../SettingLevel";
|
||||||
|
import SettingsStore from "../SettingsStore";
|
||||||
|
import { Layout } from "../Layout";
|
||||||
|
|
||||||
|
export default class NewLayoutSwitcherController extends SettingController {
|
||||||
|
public onChange(level: SettingLevel, roomId: string, newValue: any) {
|
||||||
|
// On disabling switch back to Layout.Group if Layout.Bubble
|
||||||
|
if (!newValue && SettingsStore.getValue("layout") == Layout.Bubble) {
|
||||||
|
SettingsStore.setValue("layout", null, SettingLevel.DEVICE, Layout.Group);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -49,7 +49,7 @@ class GroupFilterOrderStore extends Store {
|
||||||
this.__emitChange();
|
this.__emitChange();
|
||||||
}
|
}
|
||||||
|
|
||||||
__onDispatch(payload) {
|
__onDispatch(payload) { // eslint-disable-line @typescript-eslint/naming-convention
|
||||||
switch (payload.action) {
|
switch (payload.action) {
|
||||||
// Initialise state after initial sync
|
// Initialise state after initial sync
|
||||||
case 'view_room': {
|
case 'view_room': {
|
||||||
|
|
|
@ -44,7 +44,7 @@ class LifecycleStore extends Store<ActionPayload> {
|
||||||
this.__emitChange();
|
this.__emitChange();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected __onDispatch(payload: ActionPayload) {
|
protected __onDispatch(payload: ActionPayload) { // eslint-disable-line @typescript-eslint/naming-convention
|
||||||
switch (payload.action) {
|
switch (payload.action) {
|
||||||
case 'do_after_sync_prepared':
|
case 'do_after_sync_prepared':
|
||||||
this.setState({
|
this.setState({
|
||||||
|
|
|
@ -144,7 +144,7 @@ export default class RightPanelStore extends Store<ActionPayload> {
|
||||||
this.__emitChange();
|
this.__emitChange();
|
||||||
}
|
}
|
||||||
|
|
||||||
__onDispatch(payload: ActionPayload) {
|
__onDispatch(payload: ActionPayload) { // eslint-disable-line @typescript-eslint/naming-convention
|
||||||
switch (payload.action) {
|
switch (payload.action) {
|
||||||
case 'view_room':
|
case 'view_room':
|
||||||
if (payload.room_id === this.lastRoomId) break; // skip this transition, probably a permalink
|
if (payload.room_id === this.lastRoomId) break; // skip this transition, probably a permalink
|
||||||
|
|
|
@ -96,7 +96,7 @@ class RoomViewStore extends Store<ActionPayload> {
|
||||||
this.__emitChange();
|
this.__emitChange();
|
||||||
}
|
}
|
||||||
|
|
||||||
__onDispatch(payload) {
|
__onDispatch(payload) { // eslint-disable-line @typescript-eslint/naming-convention
|
||||||
switch (payload.action) {
|
switch (payload.action) {
|
||||||
// view_room:
|
// view_room:
|
||||||
// - room_alias: '#somealias:matrix.org'
|
// - room_alias: '#somealias:matrix.org'
|
||||||
|
|
|
@ -63,7 +63,7 @@ const PREVIEWS = {
|
||||||
const MAX_EVENTS_BACKWARDS = 50;
|
const MAX_EVENTS_BACKWARDS = 50;
|
||||||
|
|
||||||
// type merging ftw
|
// type merging ftw
|
||||||
type TAG_ANY = "im.vector.any";
|
type TAG_ANY = "im.vector.any"; // eslint-disable-line @typescript-eslint/naming-convention
|
||||||
const TAG_ANY: TAG_ANY = "im.vector.any";
|
const TAG_ANY: TAG_ANY = "im.vector.any";
|
||||||
|
|
||||||
interface IState {
|
interface IState {
|
||||||
|
|
|
@ -20,7 +20,7 @@ import { EventType, MsgType } from "matrix-js-sdk/src/@types/event";
|
||||||
import { ElementWidgetCapabilities } from "../stores/widgets/ElementWidgetCapabilities";
|
import { ElementWidgetCapabilities } from "../stores/widgets/ElementWidgetCapabilities";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
|
||||||
type GENERIC_WIDGET_KIND = "generic";
|
type GENERIC_WIDGET_KIND = "generic"; // eslint-disable-line @typescript-eslint/naming-convention
|
||||||
const GENERIC_WIDGET_KIND: GENERIC_WIDGET_KIND = "generic";
|
const GENERIC_WIDGET_KIND: GENERIC_WIDGET_KIND = "generic";
|
||||||
|
|
||||||
interface ISendRecvStaticCapText {
|
interface ISendRecvStaticCapText {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import './skinned-sdk'; // Must be first for skinning to work
|
import './skinned-sdk'; // Must be first for skinning to work
|
||||||
import { _waitForMember, canEncryptToAllUsers } from '../src/createRoom';
|
import { waitForMember, canEncryptToAllUsers } from '../src/createRoom';
|
||||||
import { EventEmitter } from 'events';
|
import { EventEmitter } from 'events';
|
||||||
|
|
||||||
/* Shorter timeout, we've got tests to run */
|
/* Shorter timeout, we've got tests to run */
|
||||||
|
@ -13,7 +13,7 @@ describe("waitForMember", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it("resolves with false if the timeout is reached", (done) => {
|
it("resolves with false if the timeout is reached", (done) => {
|
||||||
_waitForMember(client, "", "", { timeout: 0 }).then((r) => {
|
waitForMember(client, "", "", { timeout: 0 }).then((r) => {
|
||||||
expect(r).toBe(false);
|
expect(r).toBe(false);
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
|
@ -22,7 +22,7 @@ describe("waitForMember", () => {
|
||||||
it("resolves with false if the timeout is reached, even if other RoomState.newMember events fire", (done) => {
|
it("resolves with false if the timeout is reached, even if other RoomState.newMember events fire", (done) => {
|
||||||
const roomId = "!roomId:domain";
|
const roomId = "!roomId:domain";
|
||||||
const userId = "@clientId:domain";
|
const userId = "@clientId:domain";
|
||||||
_waitForMember(client, roomId, userId, { timeout }).then((r) => {
|
waitForMember(client, roomId, userId, { timeout }).then((r) => {
|
||||||
expect(r).toBe(false);
|
expect(r).toBe(false);
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
|
@ -32,7 +32,7 @@ describe("waitForMember", () => {
|
||||||
it("resolves with true if RoomState.newMember fires", (done) => {
|
it("resolves with true if RoomState.newMember fires", (done) => {
|
||||||
const roomId = "!roomId:domain";
|
const roomId = "!roomId:domain";
|
||||||
const userId = "@clientId:domain";
|
const userId = "@clientId:domain";
|
||||||
_waitForMember(client, roomId, userId, { timeout }).then((r) => {
|
waitForMember(client, roomId, userId, { timeout }).then((r) => {
|
||||||
expect(r).toBe(true);
|
expect(r).toBe(true);
|
||||||
expect(client.listeners("RoomState.newMember").length).toBe(0);
|
expect(client.listeners("RoomState.newMember").length).toBe(0);
|
||||||
done();
|
done();
|
||||||
|
|
|
@ -3242,7 +3242,7 @@ eslint-config-google@^0.14.0:
|
||||||
|
|
||||||
"eslint-plugin-matrix-org@github:matrix-org/eslint-plugin-matrix-org#main":
|
"eslint-plugin-matrix-org@github:matrix-org/eslint-plugin-matrix-org#main":
|
||||||
version "0.3.2"
|
version "0.3.2"
|
||||||
resolved "https://codeload.github.com/matrix-org/eslint-plugin-matrix-org/tar.gz/383a1e4a9ef7944c921efda0de2ac9635d45cb5c"
|
resolved "https://codeload.github.com/matrix-org/eslint-plugin-matrix-org/tar.gz/8529f1d77863db6327cf1a1a4fa65d06cc26f91b"
|
||||||
|
|
||||||
eslint-plugin-react-hooks@^4.2.0:
|
eslint-plugin-react-hooks@^4.2.0:
|
||||||
version "4.2.0"
|
version "4.2.0"
|
||||||
|
|
Loading…
Reference in a new issue