Merge remote-tracking branch 'upstream/develop' into feature/call-event-tile
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
This commit is contained in:
commit
dd6379cf4b
297 changed files with 3248 additions and 2557 deletions
149
CHANGELOG.md
149
CHANGELOG.md
|
@ -1,3 +1,152 @@
|
|||
Changes in [3.26.0](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v3.26.0) (2021-07-19)
|
||||
=====================================================================================================
|
||||
[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v3.26.0-rc.1...v3.26.0)
|
||||
|
||||
* Fix 'User' type import
|
||||
[\#6376](https://github.com/matrix-org/matrix-react-sdk/pull/6376)
|
||||
|
||||
Changes in [3.26.0-rc.1](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v3.26.0-rc.1) (2021-07-14)
|
||||
===============================================================================================================
|
||||
[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v3.25.0...v3.26.0-rc.1)
|
||||
|
||||
* Fix voice messages in right panels
|
||||
[\#6370](https://github.com/matrix-org/matrix-react-sdk/pull/6370)
|
||||
* Use TileShape enum more universally
|
||||
[\#6369](https://github.com/matrix-org/matrix-react-sdk/pull/6369)
|
||||
* Translations update from Weblate
|
||||
[\#6373](https://github.com/matrix-org/matrix-react-sdk/pull/6373)
|
||||
* Hide world readable history option in encrypted rooms
|
||||
[\#5947](https://github.com/matrix-org/matrix-react-sdk/pull/5947)
|
||||
* Make the Image View buttons easier to hit
|
||||
[\#6372](https://github.com/matrix-org/matrix-react-sdk/pull/6372)
|
||||
* Reorder buttons in the Image View
|
||||
[\#6368](https://github.com/matrix-org/matrix-react-sdk/pull/6368)
|
||||
* Add VS Code to gitignore
|
||||
[\#6367](https://github.com/matrix-org/matrix-react-sdk/pull/6367)
|
||||
* Fix inviter exploding due to member being null
|
||||
[\#6362](https://github.com/matrix-org/matrix-react-sdk/pull/6362)
|
||||
* Increase sample count in voice message thumbnail
|
||||
[\#6359](https://github.com/matrix-org/matrix-react-sdk/pull/6359)
|
||||
* Improve arraySeed utility
|
||||
[\#6360](https://github.com/matrix-org/matrix-react-sdk/pull/6360)
|
||||
* Convert FontManager to TS and stub it out for tests
|
||||
[\#6358](https://github.com/matrix-org/matrix-react-sdk/pull/6358)
|
||||
* Adjust recording waveform behaviour for voice messages
|
||||
[\#6357](https://github.com/matrix-org/matrix-react-sdk/pull/6357)
|
||||
* Do not honor string power levels
|
||||
[\#6245](https://github.com/matrix-org/matrix-react-sdk/pull/6245)
|
||||
* Add alias and directory customisation points
|
||||
[\#6343](https://github.com/matrix-org/matrix-react-sdk/pull/6343)
|
||||
* Fix multiinviter user already in room and clean up code
|
||||
[\#6354](https://github.com/matrix-org/matrix-react-sdk/pull/6354)
|
||||
* Fix right panel not closing user info when changing rooms
|
||||
[\#6341](https://github.com/matrix-org/matrix-react-sdk/pull/6341)
|
||||
* Quit sticker picker on m.sticker
|
||||
[\#5679](https://github.com/matrix-org/matrix-react-sdk/pull/5679)
|
||||
* Don't autodetect language in inline code blocks
|
||||
[\#6350](https://github.com/matrix-org/matrix-react-sdk/pull/6350)
|
||||
* Make ghost button background transparent
|
||||
[\#6331](https://github.com/matrix-org/matrix-react-sdk/pull/6331)
|
||||
* only consider valid & loaded url previews for show N more prompt
|
||||
[\#6346](https://github.com/matrix-org/matrix-react-sdk/pull/6346)
|
||||
* Extract MXCs from _matrix/media/r0/ URLs for inline images in messages
|
||||
[\#6335](https://github.com/matrix-org/matrix-react-sdk/pull/6335)
|
||||
* Fix small visual regression with the site name on url previews
|
||||
[\#6342](https://github.com/matrix-org/matrix-react-sdk/pull/6342)
|
||||
* Make PIP CallView draggable/movable
|
||||
[\#5952](https://github.com/matrix-org/matrix-react-sdk/pull/5952)
|
||||
* Convert VoiceUserSettingsTab to TS
|
||||
[\#6340](https://github.com/matrix-org/matrix-react-sdk/pull/6340)
|
||||
* Simplify typescript definition for Modernizr
|
||||
[\#6339](https://github.com/matrix-org/matrix-react-sdk/pull/6339)
|
||||
* Remember the last used server for room directory searches
|
||||
[\#6322](https://github.com/matrix-org/matrix-react-sdk/pull/6322)
|
||||
* Focus composer after reacting
|
||||
[\#6332](https://github.com/matrix-org/matrix-react-sdk/pull/6332)
|
||||
* Fix bug which prevented more than one event getting pinned
|
||||
[\#6336](https://github.com/matrix-org/matrix-react-sdk/pull/6336)
|
||||
* Make DeviceListener also update on megolm key in SSSS
|
||||
[\#6337](https://github.com/matrix-org/matrix-react-sdk/pull/6337)
|
||||
* Improve URL previews
|
||||
[\#6326](https://github.com/matrix-org/matrix-react-sdk/pull/6326)
|
||||
* Don't close settings dialog when opening spaces feedback prompt
|
||||
[\#6334](https://github.com/matrix-org/matrix-react-sdk/pull/6334)
|
||||
* Update import location for types
|
||||
[\#6330](https://github.com/matrix-org/matrix-react-sdk/pull/6330)
|
||||
* Improve blurhash rendering performance
|
||||
[\#6329](https://github.com/matrix-org/matrix-react-sdk/pull/6329)
|
||||
* Use a proper color scheme for codeblocks
|
||||
[\#6320](https://github.com/matrix-org/matrix-react-sdk/pull/6320)
|
||||
* Burn `sdk.getComponent()` with 🔥
|
||||
[\#6308](https://github.com/matrix-org/matrix-react-sdk/pull/6308)
|
||||
* Fix instances of the Edit Message Composer's save button being wrongly
|
||||
disabled
|
||||
[\#6307](https://github.com/matrix-org/matrix-react-sdk/pull/6307)
|
||||
* Do not generate a lockfile when running in CI
|
||||
[\#6327](https://github.com/matrix-org/matrix-react-sdk/pull/6327)
|
||||
* Update lockfile with correct dependencies
|
||||
[\#6324](https://github.com/matrix-org/matrix-react-sdk/pull/6324)
|
||||
* Clarify the keys we use when submitting rageshakes
|
||||
[\#6321](https://github.com/matrix-org/matrix-react-sdk/pull/6321)
|
||||
* Fix ImageView context menu
|
||||
[\#6318](https://github.com/matrix-org/matrix-react-sdk/pull/6318)
|
||||
* TypeScript migration
|
||||
[\#6315](https://github.com/matrix-org/matrix-react-sdk/pull/6315)
|
||||
* Move animation to compositor
|
||||
[\#6310](https://github.com/matrix-org/matrix-react-sdk/pull/6310)
|
||||
* Reorganize preferences
|
||||
[\#5742](https://github.com/matrix-org/matrix-react-sdk/pull/5742)
|
||||
* Fix being able to un-rotate images
|
||||
[\#6313](https://github.com/matrix-org/matrix-react-sdk/pull/6313)
|
||||
* Fix icon size in passphrase prompt
|
||||
[\#6312](https://github.com/matrix-org/matrix-react-sdk/pull/6312)
|
||||
* Use sleep & defer from js-sdk instead of duplicating it
|
||||
[\#6305](https://github.com/matrix-org/matrix-react-sdk/pull/6305)
|
||||
* Convert EventTimeline, EventTimelineSet and TimelineWindow to TS
|
||||
[\#6295](https://github.com/matrix-org/matrix-react-sdk/pull/6295)
|
||||
* Comply with new member-delimiter-style rule
|
||||
[\#6306](https://github.com/matrix-org/matrix-react-sdk/pull/6306)
|
||||
* Fix Test Linting
|
||||
[\#6304](https://github.com/matrix-org/matrix-react-sdk/pull/6304)
|
||||
* Convert Markdown to TypeScript
|
||||
[\#6303](https://github.com/matrix-org/matrix-react-sdk/pull/6303)
|
||||
* Convert RoomHeader to TS
|
||||
[\#6302](https://github.com/matrix-org/matrix-react-sdk/pull/6302)
|
||||
* Prevent RoomDirectory from exploding when filterString is wrongly nulled
|
||||
[\#6296](https://github.com/matrix-org/matrix-react-sdk/pull/6296)
|
||||
* Add support for blurhash (MSC2448)
|
||||
[\#5099](https://github.com/matrix-org/matrix-react-sdk/pull/5099)
|
||||
* Remove rateLimitedFunc
|
||||
[\#6300](https://github.com/matrix-org/matrix-react-sdk/pull/6300)
|
||||
* Convert some Key Verification classes to TypeScript
|
||||
[\#6299](https://github.com/matrix-org/matrix-react-sdk/pull/6299)
|
||||
* Typescript conversion of Composer components and more
|
||||
[\#6292](https://github.com/matrix-org/matrix-react-sdk/pull/6292)
|
||||
* Upgrade browserlist target versions
|
||||
[\#6298](https://github.com/matrix-org/matrix-react-sdk/pull/6298)
|
||||
* Fix browser crashing when searching for a malformed HTML tag
|
||||
[\#6297](https://github.com/matrix-org/matrix-react-sdk/pull/6297)
|
||||
* Add custom audio player
|
||||
[\#6264](https://github.com/matrix-org/matrix-react-sdk/pull/6264)
|
||||
* Lint MXC APIs to centralise access
|
||||
[\#6293](https://github.com/matrix-org/matrix-react-sdk/pull/6293)
|
||||
* Remove reminescent references to the tinter
|
||||
[\#6290](https://github.com/matrix-org/matrix-react-sdk/pull/6290)
|
||||
* More js-sdk type consolidation
|
||||
[\#6263](https://github.com/matrix-org/matrix-react-sdk/pull/6263)
|
||||
* Convert MessagePanel, TimelinePanel, ScrollPanel, and more to Typescript
|
||||
[\#6243](https://github.com/matrix-org/matrix-react-sdk/pull/6243)
|
||||
* Migrate to `eslint-plugin-matrix-org`
|
||||
[\#6285](https://github.com/matrix-org/matrix-react-sdk/pull/6285)
|
||||
* Avoid cyclic dependencies by moving watchers out of constructor
|
||||
[\#6287](https://github.com/matrix-org/matrix-react-sdk/pull/6287)
|
||||
* Add spacing between toast buttons with cross browser support in mind
|
||||
[\#6284](https://github.com/matrix-org/matrix-react-sdk/pull/6284)
|
||||
* Deprecate Tinter and TintableSVG
|
||||
[\#6279](https://github.com/matrix-org/matrix-react-sdk/pull/6279)
|
||||
* Migrate FilePanel to TypeScript
|
||||
[\#6283](https://github.com/matrix-org/matrix-react-sdk/pull/6283)
|
||||
|
||||
Changes in [3.25.0](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v3.25.0) (2021-07-05)
|
||||
=====================================================================================================
|
||||
[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v3.25.0-rc.1...v3.25.0)
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "matrix-react-sdk",
|
||||
"version": "3.25.0",
|
||||
"version": "3.26.0",
|
||||
"description": "SDK for matrix.org using React",
|
||||
"author": "matrix.org",
|
||||
"repository": {
|
||||
|
@ -46,6 +46,7 @@
|
|||
"start:build": "babel src -w -s -d lib --verbose --extensions \".ts,.js\"",
|
||||
"lint": "yarn lint:types && yarn lint:js && yarn lint:style",
|
||||
"lint:js": "eslint --max-warnings 0 src test",
|
||||
"lint:js-fix": "eslint --fix src test",
|
||||
"lint:types": "tsc --noEmit --jsx react",
|
||||
"lint:style": "stylelint 'res/css/**/*.scss'",
|
||||
"test": "jest",
|
||||
|
@ -79,7 +80,7 @@
|
|||
"katex": "^0.12.0",
|
||||
"linkifyjs": "^2.1.9",
|
||||
"lodash": "^4.17.20",
|
||||
"matrix-js-sdk": "12.0.1",
|
||||
"matrix-js-sdk": "12.1.0",
|
||||
"matrix-widget-api": "^0.1.0-beta.15",
|
||||
"minimist": "^1.2.5",
|
||||
"opus-recorder": "^8.0.3",
|
||||
|
|
|
@ -202,6 +202,7 @@
|
|||
@import "./views/rooms/_EditMessageComposer.scss";
|
||||
@import "./views/rooms/_EntityTile.scss";
|
||||
@import "./views/rooms/_EventTile.scss";
|
||||
@import "./views/rooms/_EventBubbleTile.scss";
|
||||
@import "./views/rooms/_GroupLayout.scss";
|
||||
@import "./views/rooms/_IRCLayout.scss";
|
||||
@import "./views/rooms/_JumpToBottomButton.scss";
|
||||
|
|
|
@ -27,6 +27,7 @@ limitations under the License.
|
|||
// https://bugzilla.mozilla.org/show_bug.cgi?id=255139
|
||||
display: inline-block;
|
||||
user-select: none;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.mx_BaseAvatar_initial {
|
||||
|
|
|
@ -16,19 +16,16 @@ limitations under the License.
|
|||
|
||||
.mx_ReplyThread {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.mx_ReplyThread_show {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
blockquote.mx_ReplyThread {
|
||||
margin-left: 0;
|
||||
margin-right: 0;
|
||||
margin-bottom: 8px;
|
||||
padding-left: 10px;
|
||||
border-left: 4px solid $button-bg-color;
|
||||
|
||||
.mx_ReplyThread_show {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
&.mx_ReplyThread_color1 {
|
||||
border-left-color: $username-variant1-color;
|
||||
}
|
||||
|
|
|
@ -46,7 +46,7 @@ limitations under the License.
|
|||
width: $font-16px;
|
||||
}
|
||||
|
||||
> input[type=radio] {
|
||||
input[type=radio] {
|
||||
// Remove the OS's representation
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
@ -112,6 +112,12 @@ limitations under the License.
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
.mx_RadioButton_innerLabel {
|
||||
display: flex;
|
||||
position: relative;
|
||||
top: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
.mx_RadioButton_outlined {
|
||||
|
|
|
@ -18,7 +18,6 @@ $timelineImageBorderRadius: 4px;
|
|||
|
||||
.mx_MImageBody {
|
||||
display: block;
|
||||
margin-right: 34px;
|
||||
}
|
||||
|
||||
.mx_MImageBody_thumbnail {
|
||||
|
@ -29,6 +28,10 @@ $timelineImageBorderRadius: 4px;
|
|||
top: 0;
|
||||
border-radius: $timelineImageBorderRadius;
|
||||
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
> canvas {
|
||||
border-radius: $timelineImageBorderRadius;
|
||||
}
|
||||
|
@ -43,17 +46,6 @@ $timelineImageBorderRadius: 4px;
|
|||
position: relative;
|
||||
}
|
||||
|
||||
.mx_MImageBody_thumbnail_spinner {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
}
|
||||
|
||||
// Inner img should be centered around 0, 0
|
||||
.mx_MImageBody_thumbnail_spinner > * {
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
|
||||
.mx_MImageBody_gifLabel {
|
||||
position: absolute;
|
||||
display: block;
|
||||
|
|
|
@ -26,6 +26,7 @@ limitations under the License.
|
|||
height: 24px;
|
||||
vertical-align: middle;
|
||||
margin-left: 4px;
|
||||
margin-right: 4px;
|
||||
|
||||
&::before {
|
||||
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 2020 The Matrix.org Foundation C.I.C.
|
||||
Copyright 2020-2021 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
@ -18,15 +18,14 @@ limitations under the License.
|
|||
$left-gutter: 64px;
|
||||
$hover-select-border: 4px;
|
||||
|
||||
.mx_EventTile {
|
||||
.mx_EventTile:not([data-layout=bubble]) {
|
||||
max-width: 100%;
|
||||
clear: both;
|
||||
padding-top: 18px;
|
||||
font-size: $font-14px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.mx_EventTile.mx_EventTile_info {
|
||||
&.mx_EventTile_info {
|
||||
padding-top: 1px;
|
||||
}
|
||||
|
||||
|
@ -37,12 +36,12 @@ $hover-select-border: 4px;
|
|||
user-select: none;
|
||||
}
|
||||
|
||||
.mx_EventTile.mx_EventTile_info .mx_EventTile_avatar {
|
||||
&.mx_EventTile_info .mx_EventTile_avatar {
|
||||
top: $font-6px;
|
||||
left: $left-gutter;
|
||||
}
|
||||
|
||||
.mx_EventTile_continuation {
|
||||
&.mx_EventTile_continuation {
|
||||
padding-top: 0px !important;
|
||||
|
||||
&.mx_EventTile_isEditing {
|
||||
|
@ -51,11 +50,11 @@ $hover-select-border: 4px;
|
|||
}
|
||||
}
|
||||
|
||||
.mx_EventTile_isEditing {
|
||||
&.mx_EventTile_isEditing {
|
||||
background-color: $header-panel-bg-color;
|
||||
}
|
||||
|
||||
.mx_EventTile .mx_SenderProfile {
|
||||
.mx_SenderProfile {
|
||||
color: $primary-fg-color;
|
||||
font-size: $font-14px;
|
||||
display: inline-block; /* anti-zalgo, with overflow hidden */
|
||||
|
@ -70,7 +69,7 @@ $hover-select-border: 4px;
|
|||
max-width: calc(100% - $left-gutter);
|
||||
}
|
||||
|
||||
.mx_EventTile .mx_SenderProfile .mx_Flair {
|
||||
.mx_SenderProfile .mx_Flair {
|
||||
opacity: 0.7;
|
||||
margin-left: 5px;
|
||||
display: inline-block;
|
||||
|
@ -85,11 +84,11 @@ $hover-select-border: 4px;
|
|||
}
|
||||
}
|
||||
|
||||
.mx_EventTile_isEditing .mx_MessageTimestamp {
|
||||
&.mx_EventTile_isEditing .mx_MessageTimestamp {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.mx_EventTile .mx_MessageTimestamp {
|
||||
.mx_MessageTimestamp {
|
||||
display: block;
|
||||
white-space: nowrap;
|
||||
left: 0px;
|
||||
|
@ -97,7 +96,7 @@ $hover-select-border: 4px;
|
|||
user-select: none;
|
||||
}
|
||||
|
||||
.mx_EventTile_continuation .mx_EventTile_line {
|
||||
&.mx_EventTile_continuation .mx_EventTile_line {
|
||||
clear: both;
|
||||
}
|
||||
|
||||
|
@ -107,70 +106,25 @@ $hover-select-border: 4px;
|
|||
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;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
.mx_EventTile_line {
|
||||
// To avoid bubble events being highlighted
|
||||
background-color: inherit !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.mx_EventTile_reply {
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
/* 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_selected > div > a > .mx_MessageTimestamp {
|
||||
&.mx_EventTile_selected > div > a > .mx_MessageTimestamp {
|
||||
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.
|
||||
* 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;
|
||||
padding-left: calc($left-gutter - $hover-select-border);
|
||||
background-color: $event-selected-color;
|
||||
}
|
||||
|
||||
.mx_EventTile_highlight,
|
||||
.mx_EventTile_highlight .markdown-body {
|
||||
&.mx_EventTile_highlight,
|
||||
&.mx_EventTile_highlight .markdown-body {
|
||||
color: $event-highlight-fg-color;
|
||||
|
||||
.mx_EventTile_line {
|
||||
|
@ -178,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);
|
||||
}
|
||||
|
||||
.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);
|
||||
}
|
||||
|
||||
.mx_EventTile:hover .mx_EventTile_line,
|
||||
.mx_EventTile.mx_EventTile_actionBarFocused .mx_EventTile_line,
|
||||
.mx_EventTile.focus-visible:focus-within .mx_EventTile_line {
|
||||
&.mx_EventTile:hover .mx_EventTile_line,
|
||||
&.mx_EventTile.mx_EventTile_actionBarFocused .mx_EventTile_line,
|
||||
&.mx_EventTile.focus-visible:focus-within .mx_EventTile_line {
|
||||
background-color: $event-selected-color;
|
||||
}
|
||||
|
||||
|
@ -232,7 +186,7 @@ $hover-select-border: 4px;
|
|||
mask-image: url('$(res)/img/element-icons/circle-sending.svg');
|
||||
}
|
||||
|
||||
.mx_EventTile_contextual {
|
||||
&.mx_EventTile_contextual {
|
||||
opacity: 0.4;
|
||||
}
|
||||
|
||||
|
@ -250,40 +204,18 @@ $hover-select-border: 4px;
|
|||
margin-right: 10px;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
.mx_EventTile_line {
|
||||
// To avoid bubble events being highlighted
|
||||
background-color: inherit !important;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.mx_EventTile_msgOption a {
|
||||
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 -
|
||||
but they introduce an implicit overflow-x: auto.
|
||||
so make that explicitly hidden too to avoid random
|
||||
|
@ -321,15 +253,147 @@ $hover-select-border: 4px;
|
|||
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 {
|
||||
position: absolute;
|
||||
top: 6px;
|
||||
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;
|
||||
height: 14px;
|
||||
display: block;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
opacity: 0.2;
|
||||
background-repeat: no-repeat;
|
||||
background-size: contain;
|
||||
|
@ -388,87 +452,6 @@ $hover-select-border: 4px;
|
|||
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 */
|
||||
|
||||
.mx_EventTile_body pre {
|
||||
|
@ -602,6 +585,35 @@ $hover-select-border: 4px;
|
|||
|
||||
/* 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 {
|
||||
color: red;
|
||||
text-align: center;
|
||||
|
@ -622,6 +634,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) {
|
||||
.mx_EventTile_line, .mx_EventTile_reply {
|
||||
padding-left: 0;
|
||||
|
|
|
@ -22,11 +22,9 @@ limitations under the License.
|
|||
max-height: 50vh;
|
||||
overflow: auto;
|
||||
box-shadow: 0px -16px 32px $composer-shadow-color;
|
||||
}
|
||||
|
||||
.mx_ReplyPreview_section {
|
||||
border-bottom: 1px solid $primary-hairline-color;
|
||||
}
|
||||
|
||||
.mx_ReplyPreview_header {
|
||||
margin: 8px;
|
||||
|
@ -52,3 +50,6 @@ limitations under the License.
|
|||
.mx_ReplyPreview_clear {
|
||||
clear: both;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -15,10 +15,9 @@ limitations under the License.
|
|||
*/
|
||||
|
||||
.mx_ReplyTile {
|
||||
padding-top: 2px;
|
||||
padding-bottom: 2px;
|
||||
font-size: $font-14px;
|
||||
position: relative;
|
||||
padding: 2px 0;
|
||||
font-size: $font-14px;
|
||||
line-height: $font-16px;
|
||||
|
||||
&.mx_ReplyTile_audio .mx_MFileBody_info_icon::before {
|
||||
|
@ -38,16 +37,15 @@ limitations under the License.
|
|||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.mx_ReplyTile > a {
|
||||
> a {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
text-decoration: none;
|
||||
color: $primary-fg-color;
|
||||
}
|
||||
|
||||
.mx_ReplyTile .mx_RedactedBody {
|
||||
.mx_RedactedBody {
|
||||
padding: 4px 0 2px 20px;
|
||||
|
||||
&::before {
|
||||
|
@ -58,7 +56,7 @@ limitations under the License.
|
|||
}
|
||||
|
||||
// We do reply size limiting with CSS to avoid duplicating the TextualBody component.
|
||||
.mx_ReplyTile .mx_EventTile_content {
|
||||
.mx_EventTile_content {
|
||||
$reply-lines: 2;
|
||||
$line-height: $font-22px;
|
||||
|
||||
|
@ -72,8 +70,7 @@ limitations under the License.
|
|||
|
||||
.mx_EventTile_body.mx_EventTile_bigEmoji {
|
||||
line-height: $line-height !important;
|
||||
// Override the big emoji override
|
||||
font-size: $font-14px !important;
|
||||
font-size: $font-14px !important; // Override the big emoji override
|
||||
}
|
||||
|
||||
// Hide line numbers
|
||||
|
@ -102,22 +99,21 @@ limitations under the License.
|
|||
}
|
||||
}
|
||||
|
||||
.mx_ReplyTile.mx_ReplyTile_info {
|
||||
&.mx_ReplyTile_info {
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
.mx_ReplyTile .mx_SenderProfile {
|
||||
color: $primary-fg-color;
|
||||
.mx_SenderProfile {
|
||||
font-size: $font-14px;
|
||||
display: inline-block; /* anti-zalgo, with overflow hidden */
|
||||
overflow: hidden;
|
||||
cursor: pointer;
|
||||
padding-left: 0; /* left gutter */
|
||||
padding-bottom: 0;
|
||||
padding-top: 0;
|
||||
margin: 0;
|
||||
line-height: $font-17px;
|
||||
/* the next three lines, along with overflow hidden, truncate long display names */
|
||||
|
||||
display: inline-block; // anti-zalgo, with overflow hidden
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
|
||||
// truncate long display names
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -227,6 +227,13 @@ $groupFilterPanel-background-blur-amount: 30px;
|
|||
|
||||
$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! *****
|
||||
|
||||
@define-mixin mx_DialogButton {
|
||||
|
|
|
@ -347,6 +347,13 @@ $appearance-tab-border-color: $input-darker-bg-color;
|
|||
|
||||
$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! *****
|
||||
|
||||
@define-mixin mx_DialogButton {
|
||||
|
|
|
@ -349,6 +349,13 @@ $groupFilterPanel-background-blur-amount: 20px;
|
|||
|
||||
$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! *****
|
||||
|
||||
@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 { RoomScrollStateStore } from "../stores/RoomScrollStateStore";
|
||||
|
||||
/* eslint-disable @typescript-eslint/naming-convention */
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
matrixChat: ReturnType<Renderer>;
|
||||
|
@ -186,3 +188,5 @@ declare global {
|
|||
}
|
||||
);
|
||||
}
|
||||
|
||||
/* eslint-enable @typescript-eslint/naming-convention */
|
||||
|
|
|
@ -15,6 +15,7 @@ limitations under the License.
|
|||
*/
|
||||
|
||||
import RoomViewStore from './stores/RoomViewStore';
|
||||
import { EventSubscription } from 'fbemitter';
|
||||
|
||||
type Listener = (isActive: boolean) => void;
|
||||
|
||||
|
@ -30,7 +31,7 @@ type Listener = (isActive: boolean) => void;
|
|||
export class ActiveRoomObserver {
|
||||
private listeners: {[key: string]: Listener[]} = {};
|
||||
private _activeRoomId = RoomViewStore.getRoomId();
|
||||
private readonly roomStoreToken: string;
|
||||
private readonly roomStoreToken: EventSubscription;
|
||||
|
||||
constructor() {
|
||||
// TODO: We could self-destruct when the last listener goes away, or at least stop listening.
|
||||
|
|
|
@ -270,7 +270,7 @@ export class Analytics {
|
|||
localStorage.removeItem(LAST_VISIT_TS_KEY);
|
||||
}
|
||||
|
||||
private async _track(data: IData) {
|
||||
private async track(data: IData) {
|
||||
if (this.disabled) return;
|
||||
|
||||
const now = new Date();
|
||||
|
@ -304,7 +304,7 @@ export class Analytics {
|
|||
}
|
||||
|
||||
public ping() {
|
||||
this._track({
|
||||
this.track({
|
||||
ping: "1",
|
||||
});
|
||||
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
|
||||
}
|
||||
|
||||
this._track({
|
||||
this.track({
|
||||
gt_ms: String(generationTimeMs),
|
||||
});
|
||||
}
|
||||
|
||||
public trackEvent(category: string, action: string, name?: string, value?: string) {
|
||||
if (this.disabled) return;
|
||||
this._track({
|
||||
this.track({
|
||||
e_c: category,
|
||||
e_a: action,
|
||||
e_n: name,
|
||||
|
|
|
@ -18,6 +18,7 @@ import { RoomMember } from "matrix-js-sdk/src/models/room-member";
|
|||
import { User } from "matrix-js-sdk/src/models/user";
|
||||
import { Room } from "matrix-js-sdk/src/models/room";
|
||||
import { ResizeMethod } from "matrix-js-sdk/src/@types/partials";
|
||||
import { split } from "lodash";
|
||||
|
||||
import DMRoomMap from './utils/DMRoomMap';
|
||||
import { mediaFromMxc } from "./customisations/Media";
|
||||
|
@ -122,27 +123,13 @@ export function getInitialLetter(name: string): string {
|
|||
return undefined;
|
||||
}
|
||||
|
||||
let idx = 0;
|
||||
const initial = name[0];
|
||||
if ((initial === '@' || initial === '#' || initial === '+') && name[1]) {
|
||||
idx++;
|
||||
name = name.substring(1);
|
||||
}
|
||||
|
||||
// string.codePointAt(0) would do this, but that isn't supported by
|
||||
// some browsers (notably PhantomJS).
|
||||
let chars = 1;
|
||||
const first = name.charCodeAt(idx);
|
||||
|
||||
// check if it’s the start of a surrogate pair
|
||||
if (first >= 0xD800 && first <= 0xDBFF && name[idx+1]) {
|
||||
const second = name.charCodeAt(idx+1);
|
||||
if (second >= 0xDC00 && second <= 0xDFFF) {
|
||||
chars++;
|
||||
}
|
||||
}
|
||||
|
||||
const firstChar = name.substring(idx, idx+chars);
|
||||
return firstChar.toUpperCase();
|
||||
// rely on the grapheme cluster splitter in lodash so that we don't break apart compound emojis
|
||||
return split(name, "", 1)[0].toUpperCase();
|
||||
}
|
||||
|
||||
export function avatarUrlForRoom(room: Room, width: number, height: number, resizeMethod?: ResizeMethod) {
|
||||
|
|
|
@ -33,6 +33,7 @@ import { isSecretStorageBeingAccessed, accessSecretStorage } from "./SecurityMan
|
|||
import { isSecureBackupRequired } from './utils/WellKnownUtils';
|
||||
import { isLoggedIn } from './components/structures/MatrixChat';
|
||||
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
|
||||
import { ActionPayload } from "./dispatcher/payloads";
|
||||
|
||||
const KEY_BACKUP_POLL_INTERVAL = 5 * 60 * 1000;
|
||||
|
||||
|
@ -58,28 +59,28 @@ export default class DeviceListener {
|
|||
}
|
||||
|
||||
start() {
|
||||
MatrixClientPeg.get().on('crypto.willUpdateDevices', this._onWillUpdateDevices);
|
||||
MatrixClientPeg.get().on('crypto.devicesUpdated', this._onDevicesUpdated);
|
||||
MatrixClientPeg.get().on('deviceVerificationChanged', this._onDeviceVerificationChanged);
|
||||
MatrixClientPeg.get().on('userTrustStatusChanged', this._onUserTrustStatusChanged);
|
||||
MatrixClientPeg.get().on('crossSigning.keysChanged', this._onCrossSingingKeysChanged);
|
||||
MatrixClientPeg.get().on('accountData', this._onAccountData);
|
||||
MatrixClientPeg.get().on('sync', this._onSync);
|
||||
MatrixClientPeg.get().on('RoomState.events', this._onRoomStateEvents);
|
||||
this.dispatcherRef = dis.register(this._onAction);
|
||||
this._recheck();
|
||||
MatrixClientPeg.get().on('crypto.willUpdateDevices', this.onWillUpdateDevices);
|
||||
MatrixClientPeg.get().on('crypto.devicesUpdated', this.onDevicesUpdated);
|
||||
MatrixClientPeg.get().on('deviceVerificationChanged', this.onDeviceVerificationChanged);
|
||||
MatrixClientPeg.get().on('userTrustStatusChanged', this.onUserTrustStatusChanged);
|
||||
MatrixClientPeg.get().on('crossSigning.keysChanged', this.onCrossSingingKeysChanged);
|
||||
MatrixClientPeg.get().on('accountData', this.onAccountData);
|
||||
MatrixClientPeg.get().on('sync', this.onSync);
|
||||
MatrixClientPeg.get().on('RoomState.events', this.onRoomStateEvents);
|
||||
this.dispatcherRef = dis.register(this.onAction);
|
||||
this.recheck();
|
||||
}
|
||||
|
||||
stop() {
|
||||
if (MatrixClientPeg.get()) {
|
||||
MatrixClientPeg.get().removeListener('crypto.willUpdateDevices', this._onWillUpdateDevices);
|
||||
MatrixClientPeg.get().removeListener('crypto.devicesUpdated', this._onDevicesUpdated);
|
||||
MatrixClientPeg.get().removeListener('deviceVerificationChanged', this._onDeviceVerificationChanged);
|
||||
MatrixClientPeg.get().removeListener('userTrustStatusChanged', this._onUserTrustStatusChanged);
|
||||
MatrixClientPeg.get().removeListener('crossSigning.keysChanged', this._onCrossSingingKeysChanged);
|
||||
MatrixClientPeg.get().removeListener('accountData', this._onAccountData);
|
||||
MatrixClientPeg.get().removeListener('sync', this._onSync);
|
||||
MatrixClientPeg.get().removeListener('RoomState.events', this._onRoomStateEvents);
|
||||
MatrixClientPeg.get().removeListener('crypto.willUpdateDevices', this.onWillUpdateDevices);
|
||||
MatrixClientPeg.get().removeListener('crypto.devicesUpdated', this.onDevicesUpdated);
|
||||
MatrixClientPeg.get().removeListener('deviceVerificationChanged', this.onDeviceVerificationChanged);
|
||||
MatrixClientPeg.get().removeListener('userTrustStatusChanged', this.onUserTrustStatusChanged);
|
||||
MatrixClientPeg.get().removeListener('crossSigning.keysChanged', this.onCrossSingingKeysChanged);
|
||||
MatrixClientPeg.get().removeListener('accountData', this.onAccountData);
|
||||
MatrixClientPeg.get().removeListener('sync', this.onSync);
|
||||
MatrixClientPeg.get().removeListener('RoomState.events', this.onRoomStateEvents);
|
||||
}
|
||||
if (this.dispatcherRef) {
|
||||
dis.unregister(this.dispatcherRef);
|
||||
|
@ -103,15 +104,15 @@ export default class DeviceListener {
|
|||
this.dismissed.add(d);
|
||||
}
|
||||
|
||||
this._recheck();
|
||||
this.recheck();
|
||||
}
|
||||
|
||||
dismissEncryptionSetup() {
|
||||
this.dismissedThisDeviceToast = true;
|
||||
this._recheck();
|
||||
this.recheck();
|
||||
}
|
||||
|
||||
_ensureDeviceIdsAtStartPopulated() {
|
||||
private ensureDeviceIdsAtStartPopulated() {
|
||||
if (this.ourDeviceIdsAtStart === null) {
|
||||
const cli = MatrixClientPeg.get();
|
||||
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),
|
||||
// then they are all pre-existing devices, so ignore this and set the
|
||||
// devicesAtStart list to the devices that we see after the fetch.
|
||||
if (initialFetch) return;
|
||||
|
||||
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
|
||||
// before we download any new ones.
|
||||
};
|
||||
|
||||
_onDevicesUpdated = (users: string[]) => {
|
||||
private onDevicesUpdated = (users: string[]) => {
|
||||
if (!users.includes(MatrixClientPeg.get().getUserId())) return;
|
||||
this._recheck();
|
||||
this.recheck();
|
||||
};
|
||||
|
||||
_onDeviceVerificationChanged = (userId: string) => {
|
||||
private onDeviceVerificationChanged = (userId: string) => {
|
||||
if (userId !== MatrixClientPeg.get().getUserId()) return;
|
||||
this._recheck();
|
||||
this.recheck();
|
||||
};
|
||||
|
||||
_onUserTrustStatusChanged = (userId: string) => {
|
||||
private onUserTrustStatusChanged = (userId: string) => {
|
||||
if (userId !== MatrixClientPeg.get().getUserId()) return;
|
||||
this._recheck();
|
||||
this.recheck();
|
||||
};
|
||||
|
||||
_onCrossSingingKeysChanged = () => {
|
||||
this._recheck();
|
||||
private onCrossSingingKeysChanged = () => {
|
||||
this.recheck();
|
||||
};
|
||||
|
||||
_onAccountData = (ev) => {
|
||||
private onAccountData = (ev: MatrixEvent) => {
|
||||
// User may have:
|
||||
// * migrated SSSS to symmetric
|
||||
// * uploaded keys to secret storage
|
||||
|
@ -163,32 +164,32 @@ export default class DeviceListener {
|
|||
ev.getType().startsWith('m.cross_signing.') ||
|
||||
ev.getType() === 'm.megolm_backup.v1'
|
||||
) {
|
||||
this._recheck();
|
||||
this.recheck();
|
||||
}
|
||||
};
|
||||
|
||||
_onSync = (state, prevState) => {
|
||||
if (state === 'PREPARED' && prevState === null) this._recheck();
|
||||
private onSync = (state, prevState) => {
|
||||
if (state === 'PREPARED' && prevState === null) this.recheck();
|
||||
};
|
||||
|
||||
_onRoomStateEvents = (ev: MatrixEvent) => {
|
||||
private onRoomStateEvents = (ev: MatrixEvent) => {
|
||||
if (ev.getType() !== "m.room.encryption") {
|
||||
return;
|
||||
}
|
||||
|
||||
// If a room changes to encrypted, re-check as it may be our first
|
||||
// encrypted room. This also catches encrypted room creation as well.
|
||||
this._recheck();
|
||||
this.recheck();
|
||||
};
|
||||
|
||||
_onAction = ({ action }) => {
|
||||
private onAction = ({ action }: ActionPayload) => {
|
||||
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
|
||||
// & cache the result
|
||||
async _getKeyBackupInfo() {
|
||||
private async getKeyBackupInfo() {
|
||||
const now = (new Date()).getTime();
|
||||
if (!this.keyBackupInfo || this.keyBackupFetchedAt < now - KEY_BACKUP_POLL_INTERVAL) {
|
||||
this.keyBackupInfo = await MatrixClientPeg.get().getKeyBackupVersion();
|
||||
|
@ -206,7 +207,7 @@ export default class DeviceListener {
|
|||
return cli && cli.getRooms().some(r => cli.isRoomEncrypted(r.roomId));
|
||||
}
|
||||
|
||||
async _recheck() {
|
||||
private async recheck() {
|
||||
const cli = MatrixClientPeg.get();
|
||||
|
||||
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)
|
||||
showSetupEncryptionToast(SetupKind.VERIFY_THIS_SESSION);
|
||||
} else {
|
||||
const backupInfo = await this._getKeyBackupInfo();
|
||||
const backupInfo = await this.getKeyBackupInfo();
|
||||
if (backupInfo) {
|
||||
// No cross-signing on account but key backup available (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
|
||||
// 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
|
||||
// (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'
|
||||
* Matrix Client object is available easily.
|
||||
*/
|
||||
class _MatrixClientPeg implements IMatrixClientPeg {
|
||||
class MatrixClientPegClass implements IMatrixClientPeg {
|
||||
// These are the default options used when when the
|
||||
// client is started in 'start'. These can be altered
|
||||
// at any time up to after the 'will_start_client'
|
||||
|
@ -300,7 +300,7 @@ class _MatrixClientPeg implements IMatrixClientPeg {
|
|||
}
|
||||
|
||||
if (!window.mxMatrixClientPeg) {
|
||||
window.mxMatrixClientPeg = new _MatrixClientPeg();
|
||||
window.mxMatrixClientPeg = new MatrixClientPegClass();
|
||||
}
|
||||
|
||||
export const MatrixClientPeg = window.mxMatrixClientPeg;
|
||||
|
|
|
@ -522,7 +522,7 @@ export const Commands = [
|
|||
aliases: ['j', 'goto'],
|
||||
args: '<room-address>',
|
||||
description: _td('Joins room with given address'),
|
||||
runFn: function(_, args) {
|
||||
runFn: function(roomId, args) {
|
||||
if (args) {
|
||||
// Note: we support 2 versions of this command. The first is
|
||||
// the public-facing one for most users and the other is a
|
||||
|
@ -1069,7 +1069,7 @@ export const Commands = [
|
|||
command: "msg",
|
||||
description: _td("Sends a message to the given user"),
|
||||
args: "<user-id> <message>",
|
||||
runFn: function(_, args) {
|
||||
runFn: function(roomId, args) {
|
||||
if (args) {
|
||||
// matches the first whitespace delimited group and then the rest of the string
|
||||
const matches = args.match(/^(\S+?)(?: +(.*))?$/s);
|
||||
|
|
|
@ -109,7 +109,7 @@ export default class UserProvider extends AutocompleteProvider {
|
|||
limit = -1,
|
||||
): Promise<ICompletion[]> {
|
||||
// lazy-load user list into matcher
|
||||
if (!this.users) this._makeUsers();
|
||||
if (!this.users) this.makeUsers();
|
||||
|
||||
let completions = [];
|
||||
const { command, range } = this.getCurrentCommand(rawQuery, selection, force);
|
||||
|
@ -147,7 +147,7 @@ export default class UserProvider extends AutocompleteProvider {
|
|||
return _t('Users');
|
||||
}
|
||||
|
||||
_makeUsers() {
|
||||
private makeUsers() {
|
||||
const events = this.room.getLiveTimeline().getEvents();
|
||||
const lastSpoken = {};
|
||||
|
||||
|
|
|
@ -17,8 +17,8 @@ limitations under the License.
|
|||
*/
|
||||
|
||||
import * as React from 'react';
|
||||
import * as PropTypes from 'prop-types';
|
||||
import { MatrixClient } from 'matrix-js-sdk/src/client';
|
||||
import { MatrixEvent } from 'matrix-js-sdk/src/models/event';
|
||||
|
||||
import { Key } from '../../Keyboard';
|
||||
import PageTypes from '../../PageTypes';
|
||||
|
@ -79,6 +79,8 @@ function canElementReceiveInput(el) {
|
|||
|
||||
interface IProps {
|
||||
matrixClient: MatrixClient;
|
||||
// Called with the credentials of a registered user (if they were a ROU that
|
||||
// transitioned to PWLU)
|
||||
onRegistered: (credentials: IMatrixClientCreds) => Promise<MatrixClient>;
|
||||
hideToSRUsers: boolean;
|
||||
resizeNotifier: ResizeNotifier;
|
||||
|
@ -140,18 +142,6 @@ interface IState {
|
|||
class LoggedInView extends React.Component<IProps, IState> {
|
||||
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 _roomView: React.RefObject<any>;
|
||||
protected readonly _resizeContainer: React.RefObject<ResizeHandle>;
|
||||
|
@ -181,10 +171,10 @@ class LoggedInView extends React.Component<IProps, IState> {
|
|||
}
|
||||
|
||||
componentDidMount() {
|
||||
document.addEventListener('keydown', this._onNativeKeyDown, false);
|
||||
document.addEventListener('keydown', this.onNativeKeyDown, false);
|
||||
CallHandler.sharedInstance().addListener(CallHandlerEvent.CallsChanged, this.onCallsChanged);
|
||||
|
||||
this._updateServerNoticeEvents();
|
||||
this.updateServerNoticeEvents();
|
||||
|
||||
this._matrixClient.on("accountData", this.onAccountData);
|
||||
this._matrixClient.on("sync", this.onSync);
|
||||
|
@ -200,13 +190,13 @@ class LoggedInView extends React.Component<IProps, IState> {
|
|||
"useCompactLayout", null, this.onCompactLayoutChanged,
|
||||
);
|
||||
|
||||
this.resizer = this._createResizer();
|
||||
this.resizer = this.createResizer();
|
||||
this.resizer.attach();
|
||||
this._loadResizerPreferences();
|
||||
this.loadResizerPreferences();
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
document.removeEventListener('keydown', this._onNativeKeyDown, false);
|
||||
document.removeEventListener('keydown', this.onNativeKeyDown, false);
|
||||
CallHandler.sharedInstance().removeListener(CallHandlerEvent.CallsChanged, this.onCallsChanged);
|
||||
this._matrixClient.removeListener("accountData", this.onAccountData);
|
||||
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) {
|
||||
return true;
|
||||
}
|
||||
return this._roomView.current.canResetTimeline();
|
||||
};
|
||||
|
||||
_createResizer() {
|
||||
let size;
|
||||
let collapsed;
|
||||
private createResizer() {
|
||||
let panelSize;
|
||||
let panelCollapsed;
|
||||
const collapseConfig: ICollapseConfig = {
|
||||
// TODO decrease this once Spaces launches as it'll no longer need to include the 56px Community Panel
|
||||
toggleSize: 206 - 50,
|
||||
onCollapsed: (_collapsed) => {
|
||||
collapsed = _collapsed;
|
||||
if (_collapsed) {
|
||||
onCollapsed: (collapsed) => {
|
||||
panelCollapsed = collapsed;
|
||||
if (collapsed) {
|
||||
dis.dispatch({ action: "hide_left_panel" });
|
||||
window.localStorage.setItem("mx_lhs_size", '0');
|
||||
} else {
|
||||
dis.dispatch({ action: "show_left_panel" });
|
||||
}
|
||||
},
|
||||
onResized: (_size) => {
|
||||
size = _size;
|
||||
onResized: (size) => {
|
||||
panelSize = size;
|
||||
this.props.resizeNotifier.notifyLeftHandleResized();
|
||||
},
|
||||
onResizeStart: () => {
|
||||
this.props.resizeNotifier.startResizing();
|
||||
},
|
||||
onResizeStop: () => {
|
||||
if (!collapsed) window.localStorage.setItem("mx_lhs_size", '' + size);
|
||||
if (!panelCollapsed) window.localStorage.setItem("mx_lhs_size", '' + panelSize);
|
||||
this.props.resizeNotifier.stopResizing();
|
||||
},
|
||||
isItemCollapsed: domNode => {
|
||||
|
@ -267,7 +257,7 @@ class LoggedInView extends React.Component<IProps, IState> {
|
|||
return resizer;
|
||||
}
|
||||
|
||||
_loadResizerPreferences() {
|
||||
private loadResizerPreferences() {
|
||||
let lhsSize = parseInt(window.localStorage.getItem("mx_lhs_size"), 10);
|
||||
if (isNaN(lhsSize)) {
|
||||
lhsSize = 350;
|
||||
|
@ -275,7 +265,7 @@ class LoggedInView extends React.Component<IProps, IState> {
|
|||
this.resizer.forHandleAt(0).resize(lhsSize);
|
||||
}
|
||||
|
||||
onAccountData = (event) => {
|
||||
private onAccountData = (event: MatrixEvent) => {
|
||||
if (event.getType() === "m.ignored_user_list") {
|
||||
dis.dispatch({ action: "ignore_state_changed" });
|
||||
}
|
||||
|
@ -307,16 +297,16 @@ class LoggedInView extends React.Component<IProps, IState> {
|
|||
}
|
||||
|
||||
if (oldSyncState === 'PREPARED' && syncState === 'SYNCING') {
|
||||
this._updateServerNoticeEvents();
|
||||
this.updateServerNoticeEvents();
|
||||
} else {
|
||||
this._calculateServerLimitToast(this.state.syncErrorData, this.state.usageLimitEventContent);
|
||||
this.calculateServerLimitToast(this.state.syncErrorData, this.state.usageLimitEventContent);
|
||||
}
|
||||
};
|
||||
|
||||
onRoomStateEvents = (ev, state) => {
|
||||
const serverNoticeList = RoomListStore.instance.orderedLists[DefaultTagID.ServerNotice];
|
||||
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";
|
||||
if (error) {
|
||||
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];
|
||||
if (!serverNoticeList) return [];
|
||||
|
||||
|
@ -378,7 +368,7 @@ class LoggedInView extends React.Component<IProps, IState> {
|
|||
);
|
||||
});
|
||||
const usageLimitEventContent = usageLimitEvent && usageLimitEvent.getContent();
|
||||
this._calculateServerLimitToast(this.state.syncErrorData, usageLimitEventContent);
|
||||
this.calculateServerLimitToast(this.state.syncErrorData, usageLimitEventContent);
|
||||
this.setState({
|
||||
usageLimitEventContent,
|
||||
usageLimitEventTs: pinnedEventTs,
|
||||
|
@ -387,7 +377,7 @@ class LoggedInView extends React.Component<IProps, IState> {
|
|||
});
|
||||
};
|
||||
|
||||
_onPaste = (ev) => {
|
||||
private onPaste = (ev) => {
|
||||
let canReceiveInput = false;
|
||||
let element = ev.target;
|
||||
// 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.
|
||||
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
|
||||
// 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.
|
||||
// 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.
|
||||
if (ev.target === document.body) {
|
||||
this._onKeyDown(ev);
|
||||
this.onKeyDown(ev);
|
||||
}
|
||||
};
|
||||
|
||||
_onKeyDown = (ev) => {
|
||||
private onKeyDown = (ev) => {
|
||||
let handled = false;
|
||||
|
||||
const roomAction = getKeyBindingsManager().getRoomAction(ev);
|
||||
|
@ -450,7 +440,7 @@ class LoggedInView extends React.Component<IProps, IState> {
|
|||
case RoomAction.JumpToFirstMessage:
|
||||
case RoomAction.JumpToLatestMessage:
|
||||
// pass the event down to the scroll panel
|
||||
this._onScrollKeyPressed(ev);
|
||||
this.onScrollKeyPressed(ev);
|
||||
handled = true;
|
||||
break;
|
||||
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
|
||||
* @param {Object} ev The key event
|
||||
*/
|
||||
_onScrollKeyPressed = (ev) => {
|
||||
private onScrollKeyPressed = (ev) => {
|
||||
if (this._roomView.current) {
|
||||
this._roomView.current.handleScrollKey(ev);
|
||||
}
|
||||
|
@ -625,8 +615,8 @@ class LoggedInView extends React.Component<IProps, IState> {
|
|||
return (
|
||||
<MatrixClientContext.Provider value={this._matrixClient}>
|
||||
<div
|
||||
onPaste={this._onPaste}
|
||||
onKeyDown={this._onReactKeyDown}
|
||||
onPaste={this.onPaste}
|
||||
onKeyDown={this.onReactKeyDown}
|
||||
className='mx_MatrixChat_wrapper'
|
||||
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
|
||||
// eslint-disable-next-line camelcase
|
||||
// eslint-disable-next-line
|
||||
UNSAFE_componentWillUpdate(props, state) {
|
||||
if (this.shouldTrackPageChange(this.state, state)) {
|
||||
this.startPageChangeTimer();
|
||||
|
@ -1864,13 +1864,6 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
|||
dis.dispatch({ action: 'timeline_resize' });
|
||||
}
|
||||
|
||||
onRoomCreated(roomId: string) {
|
||||
dis.dispatch({
|
||||
action: "view_room",
|
||||
room_id: roomId,
|
||||
});
|
||||
}
|
||||
|
||||
onRegisterClick = () => {
|
||||
this.showScreen("register");
|
||||
};
|
||||
|
@ -2043,7 +2036,6 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
|||
{...this.state}
|
||||
ref={this.loggedInView}
|
||||
matrixClient={MatrixClientPeg.get()}
|
||||
onRoomCreated={this.onRoomCreated}
|
||||
onRegistered={this.onRegistered}
|
||||
currentRoomId={this.state.currentRoomId}
|
||||
/>
|
||||
|
|
|
@ -671,8 +671,10 @@ export default class MessagePanel extends React.Component<IProps, IState> {
|
|||
}
|
||||
|
||||
let willWantDateSeparator = false;
|
||||
let lastInSection = true;
|
||||
if (nextEvent) {
|
||||
willWantDateSeparator = this.wantsDateSeparator(mxEv, nextEvent.getDate() || new Date());
|
||||
lastInSection = willWantDateSeparator || mxEv.getSender() !== nextEvent.getSender();
|
||||
}
|
||||
|
||||
// is this a continuation of the previous message?
|
||||
|
@ -732,7 +734,7 @@ export default class MessagePanel extends React.Component<IProps, IState> {
|
|||
isTwelveHour={this.props.isTwelveHour}
|
||||
permalinkCreator={this.props.permalinkCreator}
|
||||
last={last}
|
||||
lastInSection={willWantDateSeparator}
|
||||
lastInSection={lastInSection}
|
||||
lastSuccessful={isLastSuccessful}
|
||||
isSelectedEvent={highlight}
|
||||
getRelationsForEvent={this.props.getRelationsForEvent}
|
||||
|
@ -741,6 +743,7 @@ export default class MessagePanel extends React.Component<IProps, IState> {
|
|||
enableFlair={this.props.enableFlair}
|
||||
showReadReceipts={this.props.showReadReceipts}
|
||||
callEventGrouper={callEventGrouper}
|
||||
hideSender={this.props.room.getMembers().length <= 2 && this.props.layout === Layout.Bubble}
|
||||
/>
|
||||
</TileErrorBoundary>,
|
||||
);
|
||||
|
|
|
@ -17,6 +17,7 @@ limitations under the License.
|
|||
|
||||
import React from 'react';
|
||||
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 { RoomMember } from "matrix-js-sdk/src/models/room-member";
|
||||
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
|
||||
UNSAFE_componentWillReceiveProps(newProps) { // eslint-disable-line camelcase
|
||||
UNSAFE_componentWillReceiveProps(newProps) { // eslint-disable-line
|
||||
if (newProps.groupId !== this.props.groupId) {
|
||||
this.unregisterGroupStore();
|
||||
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) {
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -814,7 +814,7 @@ export default class RoomDirectory extends React.Component<IProps, IState> {
|
|||
}) : _t("Explore rooms");
|
||||
return (
|
||||
<BaseDialog
|
||||
className={'mx_RoomDirectory_dialog'}
|
||||
className="mx_RoomDirectory_dialog"
|
||||
hasCancel={true}
|
||||
onFinished={this.onFinished}
|
||||
title={title}
|
||||
|
|
|
@ -458,7 +458,7 @@ const SpaceSetupFirstRooms = ({ space, title, description, onFinished }) => {
|
|||
const numFields = 3;
|
||||
const placeholders = [_t("General"), _t("Random"), _t("Support")];
|
||||
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;
|
||||
return <Field
|
||||
key={name}
|
||||
|
@ -625,7 +625,7 @@ const SpaceSetupPrivateInvite = ({ space, onFinished }) => {
|
|||
const numFields = 3;
|
||||
const fieldRefs: RefObject<Field>[] = [useRef(), useRef(), useRef()];
|
||||
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;
|
||||
return <Field
|
||||
key={name}
|
||||
|
|
|
@ -74,7 +74,7 @@ export default class TabbedView extends React.Component<IProps, IState> {
|
|||
tabLocation: TabLocation.LEFT,
|
||||
};
|
||||
|
||||
private _getActiveTabIndex() {
|
||||
private getActiveTabIndex() {
|
||||
if (!this.state || !this.state.activeTabIndex) return 0;
|
||||
return this.state.activeTabIndex;
|
||||
}
|
||||
|
@ -84,7 +84,7 @@ export default class TabbedView extends React.Component<IProps, IState> {
|
|||
* @param {Tab} tab the tab to show
|
||||
* @private
|
||||
*/
|
||||
private _setActiveTab(tab: Tab) {
|
||||
private setActiveTab(tab: Tab) {
|
||||
const idx = this.props.tabs.indexOf(tab);
|
||||
if (idx !== -1) {
|
||||
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 ";
|
||||
|
||||
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;
|
||||
if (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);
|
||||
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 (
|
||||
<div className="mx_TabbedView_tabPanel" key={"mx_tabpanel_" + tab.label}>
|
||||
<AutoHideScrollbar className='mx_TabbedView_tabPanelContent'>
|
||||
|
@ -129,8 +129,8 @@ export default class TabbedView extends React.Component<IProps, IState> {
|
|||
}
|
||||
|
||||
public render(): React.ReactNode {
|
||||
const labels = this.props.tabs.map(tab => this._renderTabLabel(tab));
|
||||
const panel = this._renderTabPanel(this.props.tabs[this._getActiveTabIndex()]);
|
||||
const labels = this.props.tabs.map(tab => this.renderTabLabel(tab));
|
||||
const panel = this.renderTabPanel(this.props.tabs[this.getActiveTabIndex()]);
|
||||
|
||||
const tabbedViewClasses = classNames({
|
||||
'mx_TabbedView': true,
|
||||
|
|
|
@ -277,7 +277,7 @@ class TimelinePanel extends React.Component<IProps, IState> {
|
|||
}
|
||||
|
||||
// TODO: [REACT-WARNING] Move into constructor
|
||||
// eslint-disable-next-line camelcase
|
||||
// eslint-disable-next-line
|
||||
UNSAFE_componentWillMount() {
|
||||
if (this.props.manageReadReceipts) {
|
||||
this.updateReadReceiptOnUserActivity();
|
||||
|
@ -290,7 +290,7 @@ class TimelinePanel extends React.Component<IProps, IState> {
|
|||
}
|
||||
|
||||
// TODO: [REACT-WARNING] Replace with appropriate lifecycle event
|
||||
// eslint-disable-next-line camelcase
|
||||
// eslint-disable-next-line
|
||||
UNSAFE_componentWillReceiveProps(newProps) {
|
||||
if (newProps.timelineSet !== this.props.timelineSet) {
|
||||
// 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
|
||||
// they're already irrelevant by the time they're mounted, and
|
||||
// our own componentDidMount is too late.
|
||||
ToastStore.sharedInstance().on('update', this._onToastStoreUpdate);
|
||||
ToastStore.sharedInstance().on('update', this.onToastStoreUpdate);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
ToastStore.sharedInstance().removeListener('update', this._onToastStoreUpdate);
|
||||
ToastStore.sharedInstance().removeListener('update', this.onToastStoreUpdate);
|
||||
}
|
||||
|
||||
_onToastStoreUpdate = () => {
|
||||
private onToastStoreUpdate = () => {
|
||||
this.setState({
|
||||
toasts: ToastStore.sharedInstance().getToasts(),
|
||||
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
|
||||
// eslint-disable-next-line camelcase
|
||||
// eslint-disable-next-line
|
||||
public UNSAFE_componentWillReceiveProps(newProps: IProps): void {
|
||||
if (newProps.serverConfig.hsUrl === this.props.serverConfig.hsUrl &&
|
||||
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
|
||||
// eslint-disable-next-line camelcase
|
||||
// eslint-disable-next-line
|
||||
UNSAFE_componentWillMount() {
|
||||
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
|
||||
// eslint-disable-next-line camelcase
|
||||
// eslint-disable-next-line
|
||||
UNSAFE_componentWillReceiveProps(newProps) {
|
||||
if (newProps.serverConfig.hsUrl === this.props.serverConfig.hsUrl &&
|
||||
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
|
||||
// eslint-disable-next-line camelcase
|
||||
// eslint-disable-next-line
|
||||
UNSAFE_componentWillReceiveProps(newProps) {
|
||||
if (newProps.serverConfig.hsUrl === this.props.serverConfig.hsUrl &&
|
||||
newProps.serverConfig.isUrl === this.props.serverConfig.isUrl) return;
|
||||
|
|
|
@ -22,6 +22,7 @@ import ImageView from '../elements/ImageView';
|
|||
import { MatrixClientPeg } from '../../../MatrixClientPeg';
|
||||
import Modal from '../../../Modal';
|
||||
import * as Avatar from '../../../Avatar';
|
||||
import DMRoomMap from "../../../utils/DMRoomMap";
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
import { mediaFromMxc } from "../../../customisations/Media";
|
||||
import { IOOBData } from '../../../stores/ThreepidInviteStore';
|
||||
|
@ -131,11 +132,14 @@ export default class RoomAvatar extends React.Component<IProps, IState> {
|
|||
const { room, oobData, viewAvatarOnClick, onClick, ...otherProps } = this.props;
|
||||
|
||||
const roomName = room ? room.name : oobData.name;
|
||||
// If the room is a DM, we use the other user's ID for the color hash
|
||||
// in order to match the room avatar with their avatar
|
||||
const idName = room ? (DMRoomMap.shared().getUserIdForRoomId(room.roomId) ?? room.roomId) : null;
|
||||
|
||||
return (
|
||||
<BaseAvatar {...otherProps}
|
||||
name={roomName}
|
||||
idName={room ? room.roomId : null}
|
||||
idName={idName}
|
||||
urls={this.state.urls}
|
||||
onClick={viewAvatarOnClick && this.state.urls[0] ? this.onRoomAvatarClick : onClick}
|
||||
/>
|
||||
|
|
|
@ -60,8 +60,8 @@ export default class AskInviteAnywayDialog extends React.Component<IProps> {
|
|||
contentId='mx_Dialog_content'
|
||||
>
|
||||
<div id='mx_Dialog_content'>
|
||||
{/* eslint-disable-next-line */}
|
||||
<p>{_t("Unable to find profiles for the Matrix IDs listed below - would you like to invite them anyway?")}</p>
|
||||
<p>{ _t("Unable to find profiles for the Matrix IDs listed below - " +
|
||||
"would you like to invite them anyway?") }</p>
|
||||
<ul>
|
||||
{ errorList }
|
||||
</ul>
|
||||
|
|
|
@ -187,7 +187,7 @@ export default class CommunityPrototypeInviteDialog extends React.PureComponent<
|
|||
emailAddresses.push((
|
||||
<Field
|
||||
key={emailAddresses.length}
|
||||
value={""}
|
||||
value=""
|
||||
onChange={(e) => this.onAddressChange(e, emailAddresses.length)}
|
||||
label={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);
|
||||
};
|
||||
|
||||
|
@ -167,7 +167,7 @@ export default class CreateGroupDialog extends React.Component<IProps, IState> {
|
|||
</div>
|
||||
<div className="mx_Dialog_buttons">
|
||||
<input type="submit" value={_t('Create')} className="mx_Dialog_primary" />
|
||||
<button onClick={this._onCancel}>
|
||||
<button onClick={this.onCancel}>
|
||||
{ _t("Cancel") }
|
||||
</button>
|
||||
</div>
|
||||
|
|
|
@ -337,7 +337,7 @@ class FilteredList extends React.PureComponent<IFilteredListProps, IFilteredList
|
|||
}
|
||||
|
||||
// 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;
|
||||
this.setState({
|
||||
filteredChildren: FilteredList.filterChildren(nextProps.children, nextProps.query),
|
||||
|
|
|
@ -40,7 +40,7 @@ interface IState {
|
|||
busy: boolean;
|
||||
err?: string;
|
||||
// If we know it, the nature of the abuse, as specified by MSC3215.
|
||||
nature?: EXTENDED_NATURE;
|
||||
nature?: ExtendedNature;
|
||||
}
|
||||
|
||||
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";
|
||||
|
||||
// Standard abuse natures.
|
||||
enum NATURE {
|
||||
DISAGREEMENT = "org.matrix.msc3215.abuse.nature.disagreement",
|
||||
TOXIC = "org.matrix.msc3215.abuse.nature.toxic",
|
||||
ILLEGAL = "org.matrix.msc3215.abuse.nature.illegal",
|
||||
SPAM = "org.matrix.msc3215.abuse.nature.spam",
|
||||
OTHER = "org.matrix.msc3215.abuse.nature.other",
|
||||
enum Nature {
|
||||
Disagreement = "org.matrix.msc3215.abuse.nature.disagreement",
|
||||
Toxic = "org.matrix.msc3215.abuse.nature.toxic",
|
||||
Illegal = "org.matrix.msc3215.abuse.nature.illegal",
|
||||
Spam = "org.matrix.msc3215.abuse.nature.spam",
|
||||
Other = "org.matrix.msc3215.abuse.nature.other",
|
||||
}
|
||||
|
||||
enum NON_STANDARD_NATURE {
|
||||
enum NonStandardValue {
|
||||
// Non-standard abuse nature.
|
||||
// It should never leave the client - we use it to fallback to
|
||||
// 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 = {
|
||||
// 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.
|
||||
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".
|
||||
|
@ -187,7 +187,7 @@ export default class ReportEventDialog extends React.Component<IProps, IState> {
|
|||
// We need a nature.
|
||||
// If the nature is `NATURE.OTHER` or `NON_STANDARD_NATURE.ADMIN`, we also need a `reason`.
|
||||
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)
|
||||
) {
|
||||
this.setState({
|
||||
|
@ -214,8 +214,8 @@ export default class ReportEventDialog extends React.Component<IProps, IState> {
|
|||
try {
|
||||
const client = MatrixClientPeg.get();
|
||||
const ev = this.props.mxEvent;
|
||||
if (this.moderation && this.state.nature != NON_STANDARD_NATURE.ADMIN) {
|
||||
const nature: NATURE = this.state.nature;
|
||||
if (this.moderation && this.state.nature != NonStandardValue.Admin) {
|
||||
const nature: Nature = this.state.nature;
|
||||
|
||||
// Report to moderators through to the dedicated bot,
|
||||
// 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;
|
||||
let subtitle;
|
||||
switch (this.state.nature) {
|
||||
case NATURE.DISAGREEMENT:
|
||||
case Nature.Disagreement:
|
||||
subtitle = _t("What this user is writing is wrong.\n" +
|
||||
"This will be reported to the room moderators.");
|
||||
break;
|
||||
case NATURE.TOXIC:
|
||||
case Nature.Toxic:
|
||||
subtitle = _t("This user is displaying toxic behaviour, " +
|
||||
"for instance by insulting other users or sharing " +
|
||||
" adult-only content in a family-friendly room " +
|
||||
" or otherwise violating the rules of this room.\n" +
|
||||
"This will be reported to the room moderators.");
|
||||
break;
|
||||
case NATURE.ILLEGAL:
|
||||
case Nature.Illegal:
|
||||
subtitle = _t("This user is displaying illegal behaviour, " +
|
||||
"for instance by doxing people or threatening violence.\n" +
|
||||
"This will be reported to the room moderators who may escalate this to legal authorities.");
|
||||
break;
|
||||
case NATURE.SPAM:
|
||||
case Nature.Spam:
|
||||
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.");
|
||||
break;
|
||||
case NON_STANDARD_NATURE.ADMIN:
|
||||
case NonStandardValue.Admin:
|
||||
if (client.isRoomEncrypted(this.props.mxEvent.getRoomId())) {
|
||||
subtitle = _t("This room is dedicated to illegal or toxic content " +
|
||||
"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 });
|
||||
}
|
||||
break;
|
||||
case NATURE.OTHER:
|
||||
case Nature.Other:
|
||||
subtitle = _t("Any other reason. Please describe the problem.\n" +
|
||||
"This will be reported to the room moderators.");
|
||||
break;
|
||||
|
@ -327,48 +327,48 @@ export default class ReportEventDialog extends React.Component<IProps, IState> {
|
|||
<div>
|
||||
<StyledRadioButton
|
||||
name="nature"
|
||||
value = { NATURE.DISAGREEMENT }
|
||||
checked = { this.state.nature == NATURE.DISAGREEMENT }
|
||||
value={Nature.Disagreement}
|
||||
checked={this.state.nature == Nature.Disagreement}
|
||||
onChange={this.onNatureChosen}
|
||||
>
|
||||
{ _t('Disagree') }
|
||||
</StyledRadioButton>
|
||||
<StyledRadioButton
|
||||
name="nature"
|
||||
value = { NATURE.TOXIC }
|
||||
checked = { this.state.nature == NATURE.TOXIC }
|
||||
value={Nature.Toxic}
|
||||
checked={this.state.nature == Nature.Toxic}
|
||||
onChange={this.onNatureChosen}
|
||||
>
|
||||
{ _t('Toxic Behaviour') }
|
||||
</StyledRadioButton>
|
||||
<StyledRadioButton
|
||||
name="nature"
|
||||
value = { NATURE.ILLEGAL }
|
||||
checked = { this.state.nature == NATURE.ILLEGAL }
|
||||
value={Nature.Illegal}
|
||||
checked={this.state.nature == Nature.Illegal}
|
||||
onChange={this.onNatureChosen}
|
||||
>
|
||||
{ _t('Illegal Content') }
|
||||
</StyledRadioButton>
|
||||
<StyledRadioButton
|
||||
name="nature"
|
||||
value = { NATURE.SPAM }
|
||||
checked = { this.state.nature == NATURE.SPAM }
|
||||
value={Nature.Spam}
|
||||
checked={this.state.nature == Nature.Spam}
|
||||
onChange={this.onNatureChosen}
|
||||
>
|
||||
{ _t('Spam or propaganda') }
|
||||
</StyledRadioButton>
|
||||
<StyledRadioButton
|
||||
name="nature"
|
||||
value = { NON_STANDARD_NATURE.ADMIN }
|
||||
checked = { this.state.nature == NON_STANDARD_NATURE.ADMIN }
|
||||
value={NonStandardValue.Admin}
|
||||
checked={this.state.nature == NonStandardValue.Admin}
|
||||
onChange={this.onNatureChosen}
|
||||
>
|
||||
{ _t('Report the entire room') }
|
||||
</StyledRadioButton>
|
||||
<StyledRadioButton
|
||||
name="nature"
|
||||
value = { NATURE.OTHER }
|
||||
checked = { this.state.nature == NATURE.OTHER }
|
||||
value={Nature.Other}
|
||||
checked={this.state.nature == Nature.Other}
|
||||
onChange={this.onNatureChosen}
|
||||
>
|
||||
{ _t('Other') }
|
||||
|
|
|
@ -205,13 +205,14 @@ export default class ServerPickerDialog extends React.PureComponent<IProps, ISta
|
|||
className="mx_ServerPickerDialog_otherHomeserverRadio"
|
||||
checked={!this.state.defaultChosen}
|
||||
onChange={this.onOtherChosen}
|
||||
childrenInLabel={false}
|
||||
>
|
||||
<Field
|
||||
type="text"
|
||||
className="mx_ServerPickerDialog_otherHomeserver"
|
||||
label={_t("Other homeserver")}
|
||||
onChange={this.onHomeserverChange}
|
||||
onClick={this.onOtherChosen}
|
||||
onFocus={this.onOtherChosen}
|
||||
ref={this.fieldRef}
|
||||
onValidate={this.onHomeserverValidate}
|
||||
value={this.state.otherHomeserver}
|
||||
|
|
|
@ -81,7 +81,7 @@ export default class UserSettingsDialog extends React.Component<IProps, IState>
|
|||
this.setState({ mjolnirEnabled: newValue });
|
||||
};
|
||||
|
||||
_getTabs() {
|
||||
private getTabs() {
|
||||
const tabs = [];
|
||||
|
||||
tabs.push(new Tab(
|
||||
|
@ -170,7 +170,7 @@ export default class UserSettingsDialog extends React.Component<IProps, IState>
|
|||
title={_t("Settings")}
|
||||
>
|
||||
<div className='mx_SettingsDialog_content'>
|
||||
<TabbedView tabs={this._getTabs()} initialTabId={this.props.initialTabId} />
|
||||
<TabbedView tabs={this.getTabs()} initialTabId={this.props.initialTabId} />
|
||||
</div>
|
||||
</BaseDialog>
|
||||
);
|
||||
|
|
|
@ -453,13 +453,13 @@ export default class AppTile extends React.Component {
|
|||
title={_t('Popout widget')}
|
||||
onClick={this._onPopoutWidgetClick}
|
||||
/> }
|
||||
{ <ContextMenuButton
|
||||
<ContextMenuButton
|
||||
className="mx_AppTileMenuBar_iconButton mx_AppTileMenuBar_iconButton_menu"
|
||||
label={_t("Options")}
|
||||
isExpanded={this.state.menuDisplayed}
|
||||
inputRef={this._contextMenuButton}
|
||||
onClick={this._onContextMenuClick}
|
||||
/> }
|
||||
/>
|
||||
</span>
|
||||
</div> }
|
||||
{ appTileBody }
|
||||
|
|
|
@ -63,7 +63,7 @@ const EventListSummary: React.FC<IProps> = ({
|
|||
// If we are only given few events then just pass them through
|
||||
if (events.length < threshold) {
|
||||
return (
|
||||
<li className="mx_EventListSummary" data-scroll-tokens={eventIds}>
|
||||
<li className="mx_EventListSummary" data-scroll-tokens={eventIds} data-expanded={true}>
|
||||
{ children }
|
||||
</li>
|
||||
);
|
||||
|
@ -92,7 +92,7 @@ const EventListSummary: React.FC<IProps> = ({
|
|||
}
|
||||
|
||||
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}>
|
||||
{ expanded ? _t('collapse') : _t('expand') }
|
||||
</AccessibleButton>
|
||||
|
@ -101,4 +101,8 @@ const EventListSummary: React.FC<IProps> = ({
|
|||
);
|
||||
};
|
||||
|
||||
EventListSummary.defaultProps = {
|
||||
startExpanded: false,
|
||||
};
|
||||
|
||||
export default EventListSummary;
|
||||
|
|
|
@ -488,8 +488,8 @@ export default class ImageView extends React.Component<IProps, IState> {
|
|||
>
|
||||
<img
|
||||
src={this.props.src}
|
||||
title={this.props.name}
|
||||
style={style}
|
||||
alt={this.props.name}
|
||||
ref={this.image}
|
||||
className="mx_ImageView_image"
|
||||
draggable={true}
|
||||
|
|
|
@ -83,7 +83,7 @@ export default class PersistedElement extends React.Component {
|
|||
// for this, so we bodge it by listening for document resize and
|
||||
// the timeline_resize action.
|
||||
window.addEventListener('resize', this._repositionChild);
|
||||
this._dispatcherRef = dis.register(this._onAction);
|
||||
this.dispatcherRef = dis.register(this._onAction);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -14,14 +14,14 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { _t } from '../../../languageHandler';
|
||||
import PropTypes from 'prop-types';
|
||||
import dis from '../../../dispatcher/dispatcher';
|
||||
import { MatrixEvent } from 'matrix-js-sdk/src/models/event';
|
||||
import { makeUserPermalink, RoomPermalinkCreator } from "../../../utils/permalinks/Permalinks";
|
||||
import SettingsStore from "../../../settings/SettingsStore";
|
||||
import { LayoutPropType } from "../../../settings/Layout";
|
||||
import { Layout } from "../../../settings/Layout";
|
||||
import escapeHtml from "escape-html";
|
||||
import MatrixClientContext from "../../../contexts/MatrixClientContext";
|
||||
import { getUserNameColorClass } from "../../../utils/FormattingUtils";
|
||||
|
@ -32,51 +32,54 @@ import { replaceableComponent } from "../../../utils/replaceableComponent";
|
|||
import Spinner from './Spinner';
|
||||
import ReplyTile from "../rooms/ReplyTile";
|
||||
import Pill from './Pill';
|
||||
import { Room } from 'matrix-js-sdk/src/models/room';
|
||||
|
||||
interface IProps {
|
||||
// the latest event in this chain of replies
|
||||
parentEv?: MatrixEvent;
|
||||
// called when the ReplyThread contents has changed, including EventTiles thereof
|
||||
onHeightChanged: () => void;
|
||||
permalinkCreator: RoomPermalinkCreator;
|
||||
// Specifies which layout to use.
|
||||
layout?: Layout;
|
||||
// Whether to always show a timestamp
|
||||
alwaysShowTimestamps?: boolean;
|
||||
}
|
||||
|
||||
interface IState {
|
||||
// The loaded events to be rendered as linear-replies
|
||||
events: MatrixEvent[];
|
||||
// The latest loaded event which has not yet been shown
|
||||
loadedEv: MatrixEvent;
|
||||
// Whether the component is still loading more events
|
||||
loading: boolean;
|
||||
// Whether as error was encountered fetching a replied to event.
|
||||
err: boolean;
|
||||
}
|
||||
|
||||
// This component does no cycle detection, simply because the only way to make such a cycle would be to
|
||||
// craft event_id's, using a homeserver that generates predictable event IDs; even then the impact would
|
||||
// be low as each event being loaded (after the first) is triggered by an explicit user action.
|
||||
@replaceableComponent("views.elements.ReplyThread")
|
||||
export default class ReplyThread extends React.Component {
|
||||
static propTypes = {
|
||||
// the latest event in this chain of replies
|
||||
parentEv: PropTypes.instanceOf(MatrixEvent),
|
||||
// called when the ReplyThread contents has changed, including EventTiles thereof
|
||||
onHeightChanged: PropTypes.func.isRequired,
|
||||
permalinkCreator: PropTypes.instanceOf(RoomPermalinkCreator).isRequired,
|
||||
// Specifies which layout to use.
|
||||
layout: LayoutPropType,
|
||||
// Whether to always show a timestamp
|
||||
alwaysShowTimestamps: PropTypes.bool,
|
||||
};
|
||||
|
||||
export default class ReplyThread extends React.Component<IProps, IState> {
|
||||
static contextType = MatrixClientContext;
|
||||
private unmounted = false;
|
||||
private room: Room;
|
||||
|
||||
constructor(props, context) {
|
||||
super(props, context);
|
||||
|
||||
this.state = {
|
||||
// The loaded events to be rendered as linear-replies
|
||||
events: [],
|
||||
|
||||
// The latest loaded event which has not yet been shown
|
||||
loadedEv: null,
|
||||
// Whether the component is still loading more events
|
||||
loading: true,
|
||||
|
||||
// Whether as error was encountered fetching a replied to event.
|
||||
err: false,
|
||||
};
|
||||
|
||||
this.unmounted = false;
|
||||
this.room = this.context.getRoom(this.props.parentEv.getRoomId());
|
||||
|
||||
this.onQuoteClick = this.onQuoteClick.bind(this);
|
||||
this.canCollapse = this.canCollapse.bind(this);
|
||||
this.collapse = this.collapse.bind(this);
|
||||
}
|
||||
|
||||
static getParentEventId(ev) {
|
||||
public static getParentEventId(ev: MatrixEvent): string {
|
||||
if (!ev || ev.isRedacted()) return;
|
||||
|
||||
// XXX: For newer relations (annotations, replacements, etc.), we now
|
||||
|
@ -92,7 +95,7 @@ export default class ReplyThread extends React.Component {
|
|||
}
|
||||
|
||||
// Part of Replies fallback support
|
||||
static stripPlainReply(body) {
|
||||
public static stripPlainReply(body: string): string {
|
||||
// Removes lines beginning with `> ` until you reach one that doesn't.
|
||||
const lines = body.split('\n');
|
||||
while (lines.length && lines[0].startsWith('> ')) lines.shift();
|
||||
|
@ -102,7 +105,7 @@ export default class ReplyThread extends React.Component {
|
|||
}
|
||||
|
||||
// Part of Replies fallback support
|
||||
static stripHTMLReply(html) {
|
||||
public static stripHTMLReply(html: string): string {
|
||||
// Sanitize the original HTML for inclusion in <mx-reply>. We allow
|
||||
// any HTML, since the original sender could use special tags that we
|
||||
// don't recognize, but want to pass along to any recipients who do
|
||||
|
@ -124,7 +127,10 @@ export default class ReplyThread extends React.Component {
|
|||
}
|
||||
|
||||
// Part of Replies fallback support
|
||||
static getNestedReplyText(ev, permalinkCreator) {
|
||||
public static getNestedReplyText(
|
||||
ev: MatrixEvent,
|
||||
permalinkCreator: RoomPermalinkCreator,
|
||||
): { body: string, html: string } {
|
||||
if (!ev) return null;
|
||||
|
||||
let { body, formatted_body: html } = ev.getContent();
|
||||
|
@ -200,7 +206,7 @@ export default class ReplyThread extends React.Component {
|
|||
return { body, html };
|
||||
}
|
||||
|
||||
static makeReplyMixIn(ev) {
|
||||
public static makeReplyMixIn(ev: MatrixEvent) {
|
||||
if (!ev) return {};
|
||||
return {
|
||||
'm.relates_to': {
|
||||
|
@ -211,10 +217,15 @@ export default class ReplyThread extends React.Component {
|
|||
};
|
||||
}
|
||||
|
||||
static makeThread(parentEv, onHeightChanged, permalinkCreator, ref, layout, alwaysShowTimestamps) {
|
||||
if (!ReplyThread.getParentEventId(parentEv)) {
|
||||
return null;
|
||||
}
|
||||
public static makeThread(
|
||||
parentEv: MatrixEvent,
|
||||
onHeightChanged: () => void,
|
||||
permalinkCreator: RoomPermalinkCreator,
|
||||
ref: React.RefObject<ReplyThread>,
|
||||
layout: Layout,
|
||||
alwaysShowTimestamps: boolean,
|
||||
): JSX.Element {
|
||||
if (!ReplyThread.getParentEventId(parentEv)) return null;
|
||||
return <ReplyThread
|
||||
parentEv={parentEv}
|
||||
onHeightChanged={onHeightChanged}
|
||||
|
@ -237,7 +248,7 @@ export default class ReplyThread extends React.Component {
|
|||
this.unmounted = true;
|
||||
}
|
||||
|
||||
async initialize() {
|
||||
private async initialize(): Promise<void> {
|
||||
const { parentEv } = this.props;
|
||||
// at time of making this component we checked that props.parentEv has a parentEventId
|
||||
const ev = await this.getEvent(ReplyThread.getParentEventId(parentEv));
|
||||
|
@ -256,7 +267,7 @@ export default class ReplyThread extends React.Component {
|
|||
}
|
||||
}
|
||||
|
||||
async getNextEvent(ev) {
|
||||
private async getNextEvent(ev: MatrixEvent): Promise<MatrixEvent> {
|
||||
try {
|
||||
const inReplyToEventId = ReplyThread.getParentEventId(ev);
|
||||
return await this.getEvent(inReplyToEventId);
|
||||
|
@ -265,7 +276,7 @@ export default class ReplyThread extends React.Component {
|
|||
}
|
||||
}
|
||||
|
||||
async getEvent(eventId) {
|
||||
private async getEvent(eventId: string): Promise<MatrixEvent> {
|
||||
if (!eventId) return null;
|
||||
const event = this.room.findEventById(eventId);
|
||||
if (event) return event;
|
||||
|
@ -282,15 +293,15 @@ export default class ReplyThread extends React.Component {
|
|||
return this.room.findEventById(eventId);
|
||||
}
|
||||
|
||||
canCollapse() {
|
||||
public canCollapse = (): boolean => {
|
||||
return this.state.events.length > 1;
|
||||
}
|
||||
};
|
||||
|
||||
collapse() {
|
||||
public collapse = (): void => {
|
||||
this.initialize();
|
||||
}
|
||||
};
|
||||
|
||||
async onQuoteClick() {
|
||||
private onQuoteClick = async (): Promise<void> => {
|
||||
const events = [this.state.loadedEv, ...this.state.events];
|
||||
|
||||
let loadedEv = null;
|
||||
|
@ -304,9 +315,9 @@ export default class ReplyThread extends React.Component {
|
|||
});
|
||||
|
||||
dis.fire(Action.FocusSendMessageComposer);
|
||||
}
|
||||
};
|
||||
|
||||
getReplyThreadColorClass(ev) {
|
||||
private getReplyThreadColorClass(ev: MatrixEvent): string {
|
||||
return getUserNameColorClass(ev.getSender()).replace("Username", "ReplyThread");
|
||||
}
|
||||
|
|
@ -45,7 +45,7 @@ export default class SpellCheckLanguagesDropdown extends React.Component<SpellCh
|
|||
SpellCheckLanguagesDropdownIState> {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this._onSearchChange = this._onSearchChange.bind(this);
|
||||
this.onSearchChange = this.onSearchChange.bind(this);
|
||||
|
||||
this.state = {
|
||||
searchQuery: '',
|
||||
|
@ -76,10 +76,8 @@ export default class SpellCheckLanguagesDropdown extends React.Component<SpellCh
|
|||
}
|
||||
}
|
||||
|
||||
_onSearchChange(search) {
|
||||
this.setState({
|
||||
searchQuery: search,
|
||||
});
|
||||
private onSearchChange(searchQuery: string) {
|
||||
this.setState({ searchQuery });
|
||||
}
|
||||
|
||||
render() {
|
||||
|
@ -117,7 +115,7 @@ export default class SpellCheckLanguagesDropdown extends React.Component<SpellCh
|
|||
id="mx_LanguageDropdown"
|
||||
className={this.props.className}
|
||||
onOptionChange={this.props.onOptionChange}
|
||||
onSearchChange={this._onSearchChange}
|
||||
onSearchChange={this.onSearchChange}
|
||||
searchEnabled={true}
|
||||
value={value}
|
||||
label={_t("Language Dropdown")}>
|
||||
|
|
|
@ -20,6 +20,10 @@ import { replaceableComponent } from "../../../utils/replaceableComponent";
|
|||
|
||||
interface IProps extends React.InputHTMLAttributes<HTMLInputElement> {
|
||||
outlined?: boolean;
|
||||
// If true (default), the children will be contained within a <label> element
|
||||
// If false, they'll be in a div. Putting interactive components that have labels
|
||||
// themselves in labels can cause strange bugs like https://github.com/vector-im/element-web/issues/18031
|
||||
childrenInLabel?: boolean;
|
||||
}
|
||||
|
||||
interface IState {
|
||||
|
@ -29,10 +33,11 @@ interface IState {
|
|||
export default class StyledRadioButton extends React.PureComponent<IProps, IState> {
|
||||
public static readonly defaultProps = {
|
||||
className: '',
|
||||
childrenInLabel: true,
|
||||
};
|
||||
|
||||
public render() {
|
||||
const { children, className, disabled, outlined, ...otherProps } = this.props;
|
||||
const { children, className, disabled, outlined, childrenInLabel, ...otherProps } = this.props;
|
||||
const _className = classnames(
|
||||
'mx_RadioButton',
|
||||
className,
|
||||
|
@ -42,12 +47,27 @@ export default class StyledRadioButton extends React.PureComponent<IProps, IStat
|
|||
"mx_RadioButton_checked": this.props.checked,
|
||||
"mx_RadioButton_outlined": outlined,
|
||||
});
|
||||
return <label className={_className}>
|
||||
|
||||
const radioButton = <React.Fragment>
|
||||
<input type='radio' disabled={disabled} {...otherProps} />
|
||||
{ /* Used to render the radio button circle */ }
|
||||
<div><div /></div>
|
||||
</React.Fragment>;
|
||||
|
||||
if (childrenInLabel) {
|
||||
return <label className={_className}>
|
||||
{ radioButton }
|
||||
<div className="mx_RadioButton_content">{ children }</div>
|
||||
<div className="mx_RadioButton_spacer" />
|
||||
</label>;
|
||||
} else {
|
||||
return <div className={_className}>
|
||||
<label className="mx_RadioButton_innerLabel">
|
||||
{ radioButton }
|
||||
</label>
|
||||
<div className="mx_RadioButton_content">{ children }</div>
|
||||
<div className="mx_RadioButton_spacer" />
|
||||
</div>;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -56,7 +56,7 @@ export default class TextWithTooltip extends React.Component {
|
|||
{...tooltipProps}
|
||||
label={tooltip}
|
||||
tooltipClassName={tooltipClass}
|
||||
className={"mx_TextWithTooltip_tooltip"}
|
||||
className="mx_TextWithTooltip_tooltip"
|
||||
/> }
|
||||
</span>
|
||||
);
|
||||
|
|
|
@ -304,13 +304,6 @@ export default class MImageBody extends React.Component<IProps, IState> {
|
|||
this.downloadImage();
|
||||
this.setState({ showImage: true });
|
||||
}
|
||||
|
||||
this._afterComponentDidMount();
|
||||
}
|
||||
|
||||
// To be overridden by subclasses (e.g. MStickerBody) for further
|
||||
// initialisation after componentDidMount
|
||||
_afterComponentDidMount() {
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
|
@ -440,9 +433,9 @@ export default class MImageBody extends React.Component<IProps, IState> {
|
|||
protected getPlaceholder(width: number, height: number): JSX.Element {
|
||||
const blurhash = this.props.mxEvent.getContent().info[BLURHASH_FIELD];
|
||||
if (blurhash) return <Blurhash hash={blurhash} width={width} height={height} />;
|
||||
return <div className="mx_MImageBody_thumbnail_spinner">
|
||||
return (
|
||||
<InlineSpinner w={32} h={32} />
|
||||
</div>;
|
||||
);
|
||||
}
|
||||
|
||||
// Overidden by MStickerBody
|
||||
|
|
|
@ -16,12 +16,19 @@ limitations under the License.
|
|||
*/
|
||||
|
||||
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;
|
||||
return (
|
||||
<span className="mx_UnknownBody" ref={ref}>
|
||||
{ text }
|
||||
{ children }
|
||||
</span>
|
||||
);
|
||||
});
|
|
@ -385,7 +385,7 @@ const UserOptionsSection: React.FC<{
|
|||
}
|
||||
|
||||
insertPillButton = (
|
||||
<AccessibleButton onClick={onInsertPillButton} className={"mx_UserInfo_field"}>
|
||||
<AccessibleButton onClick={onInsertPillButton} className="mx_UserInfo_field">
|
||||
{ _t('Mention') }
|
||||
</AccessibleButton>
|
||||
);
|
||||
|
|
|
@ -106,7 +106,7 @@ export default class RelatedGroupSettings extends React.Component {
|
|||
<EditableItemList
|
||||
id="relatedGroups"
|
||||
items={this.state.newGroupsList}
|
||||
className={"mx_RelatedGroupSettings"}
|
||||
className="mx_RelatedGroupSettings"
|
||||
newItem={this.state.newGroupId}
|
||||
canRemove={this.props.canSetRelatedGroups}
|
||||
canEdit={this.props.canSetRelatedGroups}
|
||||
|
|
|
@ -168,8 +168,6 @@ export function getHandlerTile(ev) {
|
|||
return eventTileTypes[type];
|
||||
}
|
||||
|
||||
const MAX_READ_AVATARS = 5;
|
||||
|
||||
// Our component structure for EventTiles on the timeline is:
|
||||
//
|
||||
// .-EventTile------------------------------------------------.
|
||||
|
@ -298,6 +296,9 @@ interface IProps {
|
|||
|
||||
// whether or not to always show timestamps
|
||||
alwaysShowTimestamps?: boolean;
|
||||
|
||||
// whether or not to display the sender
|
||||
hideSender?: boolean;
|
||||
}
|
||||
|
||||
interface IState {
|
||||
|
@ -321,7 +322,7 @@ export default class EventTile extends React.Component<IProps, IState> {
|
|||
private suppressReadReceiptAnimation: boolean;
|
||||
private isListeningForReceipts: boolean;
|
||||
private tile = React.createRef();
|
||||
private replyThread = React.createRef();
|
||||
private replyThread = React.createRef<ReplyThread>();
|
||||
|
||||
public readonly ref = createRef<HTMLElement>();
|
||||
|
||||
|
@ -431,7 +432,7 @@ export default class EventTile extends React.Component<IProps, IState> {
|
|||
}
|
||||
|
||||
// TODO: [REACT-WARNING] Move into constructor
|
||||
// eslint-disable-next-line camelcase
|
||||
// eslint-disable-next-line
|
||||
UNSAFE_componentWillMount() {
|
||||
this.verifyEvent(this.props.mxEvent);
|
||||
}
|
||||
|
@ -453,7 +454,7 @@ export default class EventTile extends React.Component<IProps, IState> {
|
|||
}
|
||||
|
||||
// TODO: [REACT-WARNING] Replace with appropriate lifecycle event
|
||||
// eslint-disable-next-line camelcase
|
||||
// eslint-disable-next-line
|
||||
UNSAFE_componentWillReceiveProps(nextProps) {
|
||||
// re-check the sender verification as outgoing events progress through
|
||||
// the send process.
|
||||
|
@ -657,6 +658,10 @@ export default class EventTile extends React.Component<IProps, IState> {
|
|||
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
|
||||
if (!this.props.readReceipts || this.props.readReceipts.length === 0) {
|
||||
// We currently must include `mx_EventTile_readAvatars` in the DOM
|
||||
|
@ -952,7 +957,7 @@ export default class EventTile extends React.Component<IProps, IState> {
|
|||
);
|
||||
}
|
||||
|
||||
if (needsSenderProfile) {
|
||||
if (needsSenderProfile && this.props.hideSender !== true) {
|
||||
if (!this.props.tileShape) {
|
||||
sender = <SenderProfile onClick={this.onSenderProfileClick}
|
||||
mxEvent={this.props.mxEvent}
|
||||
|
@ -972,8 +977,12 @@ export default class EventTile extends React.Component<IProps, IState> {
|
|||
onFocusChange={this.onActionBarFocusChange}
|
||||
/> : undefined;
|
||||
|
||||
const showTimestamp = this.props.mxEvent.getTs() &&
|
||||
(this.props.alwaysShowTimestamps || this.props.last || this.state.hover || this.state.actionBarFocused);
|
||||
const showTimestamp = this.props.mxEvent.getTs()
|
||||
&& (this.props.alwaysShowTimestamps
|
||||
|| this.props.last
|
||||
|| this.state.hover
|
||||
|| this.state.actionBarFocused);
|
||||
|
||||
const timestamp = showTimestamp ?
|
||||
<MessageTimestamp showTwelveHour={this.props.isTwelveHour} ts={this.props.mxEvent.getTs()} /> : null;
|
||||
|
||||
|
@ -1113,6 +1122,8 @@ export default class EventTile extends React.Component<IProps, IState> {
|
|||
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
|
||||
return (
|
||||
React.createElement(this.props.as || "li", {
|
||||
|
@ -1122,6 +1133,9 @@ export default class EventTile extends React.Component<IProps, IState> {
|
|||
"aria-live": ariaLive,
|
||||
"aria-atomic": "true",
|
||||
"data-scroll-tokens": scrollToken,
|
||||
"data-layout": this.props.layout,
|
||||
"data-self": isOwnEvent,
|
||||
"data-has-reply": !!thread,
|
||||
"onMouseEnter": () => this.setState({ hover: true }),
|
||||
"onMouseLeave": () => this.setState({ hover: false }),
|
||||
}, <>
|
||||
|
@ -1144,9 +1158,9 @@ export default class EventTile extends React.Component<IProps, IState> {
|
|||
callEventGrouper={this.props.callEventGrouper}
|
||||
/>
|
||||
{ keyRequestInfo }
|
||||
{ reactionsRow }
|
||||
{ actionBar }
|
||||
</div>
|
||||
{ reactionsRow }
|
||||
{ msgOption }
|
||||
{ avatar }
|
||||
</>)
|
||||
|
|
|
@ -93,7 +93,7 @@ export default class MemberList extends React.Component<IProps, IState> {
|
|||
this.showPresence = enablePresenceByHsUrl?.[hsUrl] ?? true;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line camelcase
|
||||
// eslint-disable-next-line
|
||||
UNSAFE_componentWillMount() {
|
||||
const cli = MatrixClientPeg.get();
|
||||
this.mounted = true;
|
||||
|
|
|
@ -18,10 +18,11 @@ import React from 'react';
|
|||
import dis from '../../../dispatcher/dispatcher';
|
||||
import { _t } from '../../../languageHandler';
|
||||
import RoomViewStore from '../../../stores/RoomViewStore';
|
||||
import PropTypes from "prop-types";
|
||||
import { RoomPermalinkCreator } from "../../../utils/permalinks/Permalinks";
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
import ReplyTile from './ReplyTile';
|
||||
import { MatrixEvent } from 'matrix-js-sdk/src/models/event';
|
||||
import { EventSubscription } from 'fbemitter';
|
||||
|
||||
function cancelQuoting() {
|
||||
dis.dispatch({
|
||||
|
@ -30,41 +31,46 @@ function cancelQuoting() {
|
|||
});
|
||||
}
|
||||
|
||||
interface IProps {
|
||||
permalinkCreator: RoomPermalinkCreator;
|
||||
}
|
||||
|
||||
interface IState {
|
||||
event: MatrixEvent;
|
||||
}
|
||||
|
||||
@replaceableComponent("views.rooms.ReplyPreview")
|
||||
export default class ReplyPreview extends React.Component {
|
||||
static propTypes = {
|
||||
permalinkCreator: PropTypes.instanceOf(RoomPermalinkCreator).isRequired,
|
||||
};
|
||||
export default class ReplyPreview extends React.Component<IProps, IState> {
|
||||
private unmounted = false;
|
||||
private readonly roomStoreToken: EventSubscription;
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.unmounted = false;
|
||||
|
||||
this.state = {
|
||||
event: RoomViewStore.getQuotingEvent(),
|
||||
};
|
||||
|
||||
this._onRoomViewStoreUpdate = this._onRoomViewStoreUpdate.bind(this);
|
||||
this._roomStoreToken = RoomViewStore.addListener(this._onRoomViewStoreUpdate);
|
||||
this.roomStoreToken = RoomViewStore.addListener(this.onRoomViewStoreUpdate);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.unmounted = true;
|
||||
|
||||
// Remove RoomStore listener
|
||||
if (this._roomStoreToken) {
|
||||
this._roomStoreToken.remove();
|
||||
if (this.roomStoreToken) {
|
||||
this.roomStoreToken.remove();
|
||||
}
|
||||
}
|
||||
|
||||
_onRoomViewStoreUpdate() {
|
||||
private onRoomViewStoreUpdate = (): void => {
|
||||
if (this.unmounted) return;
|
||||
|
||||
const event = RoomViewStore.getQuotingEvent();
|
||||
if (this.state.event !== event) {
|
||||
this.setState({ event });
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
if (!this.state.event) return null;
|
|
@ -530,7 +530,7 @@ export default class RoomTile extends React.PureComponent<IProps, IState> {
|
|||
) : null }
|
||||
<IconizedContextMenuOption
|
||||
onClick={this.onCopyRoomClick}
|
||||
label={_t("Copy Link")}
|
||||
label={_t("Copy Room Link")}
|
||||
iconClassName="mx_RoomTile_iconCopyLink"
|
||||
/>
|
||||
<IconizedContextMenuOption
|
||||
|
|
|
@ -441,7 +441,7 @@ export default class SendMessageComposer extends React.Component<IProps> {
|
|||
}
|
||||
|
||||
// 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 parts = this.restoreStoredEditorState(partCreator) || [];
|
||||
this.model = new EditorModel(parts, partCreator);
|
||||
|
|
|
@ -64,8 +64,8 @@ export default class WhoIsTypingTile extends React.Component<IProps, IState> {
|
|||
}
|
||||
|
||||
componentDidUpdate(_, prevState) {
|
||||
const wasVisible = this._isVisible(prevState);
|
||||
const isVisible = this._isVisible(this.state);
|
||||
const wasVisible = WhoIsTypingTile.isVisible(prevState);
|
||||
const isVisible = WhoIsTypingTile.isVisible(this.state);
|
||||
if (this.props.onShown && !wasVisible && isVisible) {
|
||||
this.props.onShown();
|
||||
} 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());
|
||||
}
|
||||
|
||||
private _isVisible(state: IState): boolean {
|
||||
private static isVisible(state: IState): boolean {
|
||||
return state.usersTyping.length !== 0 || Object.keys(state.delayedStopTypingTimers).length !== 0;
|
||||
}
|
||||
|
||||
public isVisible = (): boolean => {
|
||||
return this._isVisible(this.state);
|
||||
return WhoIsTypingTile.isVisible(this.state);
|
||||
};
|
||||
|
||||
private onRoomTimeline = (event: MatrixEvent, room: Room): void => {
|
||||
|
|
|
@ -569,8 +569,8 @@ export default class Notifications extends React.PureComponent<IProps, IState> {
|
|||
|
||||
const rows = this.state.vectorPushRules[category].map(r => <tr key={category + r.ruleId}>
|
||||
<td>{ r.description }</td>
|
||||
<td>{ makeRadio(r, VectorState.On) }</td>
|
||||
<td>{ makeRadio(r, VectorState.Off) }</td>
|
||||
<td>{ makeRadio(r, VectorState.On) }</td>
|
||||
<td>{ makeRadio(r, VectorState.Loud) }</td>
|
||||
</tr>);
|
||||
|
||||
|
@ -594,8 +594,8 @@ export default class Notifications extends React.PureComponent<IProps, IState> {
|
|||
<thead>
|
||||
<tr>
|
||||
<th>{ sectionName }</th>
|
||||
<th>{ _t("On") }</th>
|
||||
<th>{ _t("Off") }</th>
|
||||
<th>{ _t("On") }</th>
|
||||
<th>{ _t("Noisy") }</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
|
|
@ -35,7 +35,7 @@ interface SpellCheckLanguagesIState {
|
|||
}
|
||||
|
||||
export class ExistingSpellCheckLanguage extends React.Component<ExistingSpellCheckLanguageIProps> {
|
||||
_onRemove = (e) => {
|
||||
private onRemove = (e) => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
|
||||
|
@ -46,7 +46,7 @@ export class ExistingSpellCheckLanguage extends React.Component<ExistingSpellChe
|
|||
return (
|
||||
<div className="mx_ExistingSpellCheckLanguage">
|
||||
<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") }
|
||||
</AccessibleButton>
|
||||
</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);
|
||||
this.props.onLanguagesChange(languages);
|
||||
};
|
||||
|
||||
_onAddClick = (e) => {
|
||||
private onAddClick = (e) => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
|
||||
|
@ -81,18 +81,18 @@ export default class SpellCheckLanguages extends React.Component<SpellCheckLangu
|
|||
this.props.onLanguagesChange(this.props.languages);
|
||||
};
|
||||
|
||||
_onNewLanguageChange = (language: string) => {
|
||||
private onNewLanguageChange = (language: string) => {
|
||||
if (this.state.newLanguage === language) return;
|
||||
this.setState({ newLanguage: language });
|
||||
};
|
||||
|
||||
render() {
|
||||
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 = (
|
||||
<AccessibleButton onClick={this._onAddClick} kind="primary">
|
||||
<AccessibleButton onClick={this.onAddClick} kind="primary">
|
||||
{ _t("Add") }
|
||||
</AccessibleButton>
|
||||
);
|
||||
|
@ -100,11 +100,11 @@ export default class SpellCheckLanguages extends React.Component<SpellCheckLangu
|
|||
return (
|
||||
<div className="mx_SpellCheckLanguages">
|
||||
{ existingSpellCheckLanguages }
|
||||
<form onSubmit={this._onAddClick} noValidate={true}>
|
||||
<form onSubmit={this.onAddClick} noValidate={true}>
|
||||
<SpellCheckLanguagesDropdown
|
||||
className="mx_GeneralUserSettingsTab_spellCheckLanguageInput"
|
||||
value={this.state.newLanguage}
|
||||
onOptionChange={this._onNewLanguageChange} />
|
||||
onOptionChange={this.onNewLanguageChange} />
|
||||
{ addButton }
|
||||
</form>
|
||||
</div>
|
||||
|
|
|
@ -78,7 +78,7 @@ export default class SecurityRoomSettingsTab extends React.Component<IProps, ISt
|
|||
}
|
||||
|
||||
// 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);
|
||||
|
||||
const room = MatrixClientPeg.get().getRoom(this.props.roomId);
|
||||
|
|
|
@ -37,6 +37,8 @@ import StyledRadioGroup from "../../../elements/StyledRadioGroup";
|
|||
import { SettingLevel } from "../../../../../settings/SettingLevel";
|
||||
import { UIFeature } from "../../../../../settings/UIFeature";
|
||||
import { Layout } from "../../../../../settings/Layout";
|
||||
import classNames from 'classnames';
|
||||
import StyledRadioButton from '../../../elements/StyledRadioButton';
|
||||
import { replaceableComponent } from "../../../../../utils/replaceableComponent";
|
||||
import { compare } from "../../../../../utils/strings";
|
||||
|
||||
|
@ -241,6 +243,19 @@ export default class AppearanceUserSettingsTab extends React.Component<IProps, I
|
|||
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) => {
|
||||
if (enabled) {
|
||||
this.setState({ layout: Layout.IRC });
|
||||
|
@ -373,6 +388,77 @@ export default class AppearanceUserSettingsTab extends React.Component<IProps, I
|
|||
</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() {
|
||||
if (!SettingsStore.getValue(UIFeature.AdvancedSettings)) return null;
|
||||
|
||||
|
@ -396,14 +482,17 @@ export default class AppearanceUserSettingsTab extends React.Component<IProps, I
|
|||
name="useCompactLayout"
|
||||
level={SettingLevel.DEVICE}
|
||||
useCheckbox={true}
|
||||
disabled={this.state.layout == Layout.IRC}
|
||||
disabled={this.state.layout !== Layout.Group}
|
||||
/>
|
||||
|
||||
{ !SettingsStore.getValue("feature_new_layout_switcher") ?
|
||||
<StyledCheckbox
|
||||
checked={this.state.layout == Layout.IRC}
|
||||
onChange={(ev) => this.onIRCLayoutChange(ev.target.checked)}
|
||||
>
|
||||
{ _t("Enable experimental, compact IRC style layout") }
|
||||
</StyledCheckbox>
|
||||
</StyledCheckbox> : null
|
||||
}
|
||||
|
||||
<SettingsFlag
|
||||
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 }) }
|
||||
</div>
|
||||
{ this.renderThemeSection() }
|
||||
{ SettingsStore.getValue("feature_new_layout_switcher") ? this.renderLayoutSection() : null }
|
||||
{ this.renderFontSection() }
|
||||
{ this.renderAdvancedSection() }
|
||||
</div>
|
||||
|
|
|
@ -60,14 +60,14 @@ export default class VerificationRequestToast extends React.PureComponent<IProps
|
|||
this.setState({ counter });
|
||||
}, 1000);
|
||||
}
|
||||
request.on("change", this._checkRequestIsPending);
|
||||
request.on("change", this.checkRequestIsPending);
|
||||
// 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
|
||||
// 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).
|
||||
// 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).
|
||||
this._checkRequestIsPending();
|
||||
this.checkRequestIsPending();
|
||||
|
||||
if (request.isSelfVerification) {
|
||||
const cli = MatrixClientPeg.get();
|
||||
|
@ -83,10 +83,10 @@ export default class VerificationRequestToast extends React.PureComponent<IProps
|
|||
componentWillUnmount() {
|
||||
clearInterval(this.intervalHandle);
|
||||
const { request } = this.props;
|
||||
request.off("change", this._checkRequestIsPending);
|
||||
request.off("change", this.checkRequestIsPending);
|
||||
}
|
||||
|
||||
_checkRequestIsPending = () => {
|
||||
private checkRequestIsPending = () => {
|
||||
const { request } = this.props;
|
||||
if (!request.canAccept) {
|
||||
ToastStore.sharedInstance().dismissToast(this.props.toastKey);
|
||||
|
|
|
@ -30,6 +30,7 @@ import { replaceableComponent } from "../../../utils/replaceableComponent";
|
|||
import UIStore from '../../../stores/UIStore';
|
||||
import { lerp } from '../../../utils/AnimationUtils';
|
||||
import { MarkedExecution } from '../../../utils/MarkedExecution';
|
||||
import { EventSubscription } from 'fbemitter';
|
||||
|
||||
const PIP_VIEW_WIDTH = 336;
|
||||
const PIP_VIEW_HEIGHT = 232;
|
||||
|
@ -108,7 +109,7 @@ function getPrimarySecondaryCalls(calls: MatrixCall[]): [MatrixCall, MatrixCall[
|
|||
*/
|
||||
@replaceableComponent("views.voip.CallPreview")
|
||||
export default class CallPreview extends React.Component<IProps, IState> {
|
||||
private roomStoreToken: any;
|
||||
private roomStoreToken: EventSubscription;
|
||||
private dispatcherRef: string;
|
||||
private settingsWatcherRef: string;
|
||||
private callViewWrapper = createRef<HTMLDivElement>();
|
||||
|
@ -240,7 +241,7 @@ export default class CallPreview extends React.Component<IProps, IState> {
|
|||
this.scheduledUpdate.mark();
|
||||
};
|
||||
|
||||
private onRoomViewStoreUpdate = (payload) => {
|
||||
private onRoomViewStoreUpdate = () => {
|
||||
if (RoomViewStore.getRoomId() === this.state.roomId) return;
|
||||
|
||||
const roomId = RoomViewStore.getRoomId();
|
||||
|
|
|
@ -513,7 +513,9 @@ export default class CallView extends React.Component<IProps, IState> {
|
|||
transferee: transfereeName,
|
||||
},
|
||||
{
|
||||
a: sub => <AccessibleButton kind="link" onClick={this.onTransferClick}>{sub}</AccessibleButton>,
|
||||
a: sub => <AccessibleButton kind="link" onClick={this.onTransferClick}>
|
||||
{ sub }
|
||||
</AccessibleButton>,
|
||||
},
|
||||
) }
|
||||
</div>;
|
||||
|
|
|
@ -156,7 +156,7 @@ export default class IncomingCallBox extends React.Component<IProps, IState> {
|
|||
</div>
|
||||
<div className="mx_IncomingCallBox_buttons">
|
||||
<AccessibleButton
|
||||
className={"mx_IncomingCallBox_decline"}
|
||||
className="mx_IncomingCallBox_decline"
|
||||
onClick={this.onRejectClick}
|
||||
kind="danger"
|
||||
>
|
||||
|
@ -164,7 +164,7 @@ export default class IncomingCallBox extends React.Component<IProps, IState> {
|
|||
</AccessibleButton>
|
||||
<div className="mx_IncomingCallBox_spacer" />
|
||||
<AccessibleButton
|
||||
className={"mx_IncomingCallBox_accept"}
|
||||
className="mx_IncomingCallBox_accept"
|
||||
onClick={this.onAnswerClick}
|
||||
kind="primary"
|
||||
>
|
||||
|
|
|
@ -17,6 +17,7 @@ limitations under the License.
|
|||
|
||||
import { MatrixClient } from "matrix-js-sdk/src/client";
|
||||
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 { 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
|
||||
* 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;
|
||||
let handler;
|
||||
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.roomId !== roomId) return;
|
||||
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 });
|
||||
await _waitForMember(client, roomId, userId);
|
||||
await waitForMember(client, roomId, userId);
|
||||
}
|
||||
return roomId;
|
||||
}
|
||||
|
|
|
@ -274,7 +274,7 @@ abstract class PillPart extends BasePart implements IPillPart {
|
|||
}
|
||||
|
||||
// helper method for subclasses
|
||||
_setAvatarVars(node: HTMLElement, avatarUrl: string, initialLetter: string) {
|
||||
protected setAvatarVars(node: HTMLElement, avatarUrl: string, initialLetter: string) {
|
||||
const avatarBackground = `url('${avatarUrl}')`;
|
||||
const avatarLetter = `'${initialLetter}'`;
|
||||
// check if the value is changing,
|
||||
|
@ -354,7 +354,7 @@ class RoomPillPart extends PillPart {
|
|||
initialLetter = Avatar.getInitialLetter(this.room ? this.room.name : 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"] {
|
||||
|
@ -399,7 +399,7 @@ class UserPillPart extends PillPart {
|
|||
if (avatarUrl === defaultAvatarUrl) {
|
||||
initialLetter = Avatar.getInitialLetter(name);
|
||||
}
|
||||
this._setAvatarVars(node, avatarUrl, initialLetter);
|
||||
this.setAvatarVars(node, avatarUrl, initialLetter);
|
||||
}
|
||||
|
||||
get type(): IPillPart["type"] {
|
||||
|
|
|
@ -809,6 +809,7 @@
|
|||
"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",
|
||||
"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",
|
||||
"Use custom size": "Use custom size",
|
||||
"Enable Emoji suggestions while typing": "Enable Emoji suggestions while typing",
|
||||
|
@ -1132,8 +1133,8 @@
|
|||
"New keyword": "New keyword",
|
||||
"Global": "Global",
|
||||
"Mentions & keywords": "Mentions & keywords",
|
||||
"On": "On",
|
||||
"Off": "Off",
|
||||
"On": "On",
|
||||
"Noisy": "Noisy",
|
||||
"Notification targets": "Notification targets",
|
||||
"There was an error loading your notification settings.": "There was an error loading your notification settings.",
|
||||
|
@ -1231,6 +1232,10 @@
|
|||
"Custom theme URL": "Custom theme URL",
|
||||
"Add theme": "Add 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.",
|
||||
"Enable experimental, compact IRC style layout": "Enable experimental, compact IRC style layout",
|
||||
"Customise your appearance": "Customise your appearance",
|
||||
|
@ -1643,7 +1648,7 @@
|
|||
"Favourite": "Favourite",
|
||||
"Low Priority": "Low Priority",
|
||||
"Invite People": "Invite People",
|
||||
"Copy Link": "Copy Link",
|
||||
"Copy Room Link": "Copy Room Link",
|
||||
"Leave Room": "Leave Room",
|
||||
"Room options": "Room options",
|
||||
"%(count)s unread messages including mentions.|other": "%(count)s unread messages including mentions.",
|
||||
|
|
|
@ -67,7 +67,7 @@ export function getUserLanguage(): string {
|
|||
|
||||
// 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
|
||||
export function _td(s: string): string {
|
||||
export function _td(s: string): string { // eslint-disable-line @typescript-eslint/naming-convention
|
||||
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
|
||||
*/
|
||||
// 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, tags: Tags): React.ReactNode;
|
||||
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.
|
||||
*/
|
||||
|
||||
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
|
||||
import { MatrixClientPeg } from "../MatrixClientPeg";
|
||||
import { ALL_RULE_TYPES, BanList } from "./BanList";
|
||||
import SettingsStore from "../settings/SettingsStore";
|
||||
|
@ -21,19 +22,17 @@ import { _t } from "../languageHandler";
|
|||
import dis from "../dispatcher/dispatcher";
|
||||
import { SettingLevel } from "../settings/SettingLevel";
|
||||
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.
|
||||
|
||||
export class Mjolnir {
|
||||
static _instance: Mjolnir = null;
|
||||
private static instance: Mjolnir = null;
|
||||
|
||||
_lists: BanList[] = [];
|
||||
_roomIds: string[] = [];
|
||||
_mjolnirWatchRef = null;
|
||||
_dispatcherRef = null;
|
||||
|
||||
constructor() {
|
||||
}
|
||||
private _lists: BanList[] = []; // eslint-disable-line @typescript-eslint/naming-convention
|
||||
private _roomIds: string[] = []; // eslint-disable-line @typescript-eslint/naming-convention
|
||||
private mjolnirWatchRef: string = null;
|
||||
private dispatcherRef: string = null;
|
||||
|
||||
get roomIds(): string[] {
|
||||
return this._roomIds;
|
||||
|
@ -44,16 +43,16 @@ export class Mjolnir {
|
|||
}
|
||||
|
||||
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({
|
||||
action: 'do_after_sync_prepared',
|
||||
deferred_action: { action: 'setup_mjolnir' },
|
||||
});
|
||||
}
|
||||
|
||||
_onAction = (payload) => {
|
||||
private onAction = (payload: ActionPayload) => {
|
||||
if (payload['action'] === 'setup_mjolnir') {
|
||||
console.log("Setting up Mjolnir: after sync");
|
||||
this.setup();
|
||||
|
@ -62,23 +61,23 @@ export class Mjolnir {
|
|||
|
||||
setup() {
|
||||
if (!MatrixClientPeg.get()) return;
|
||||
this._updateLists(SettingsStore.getValue("mjolnirRooms"));
|
||||
MatrixClientPeg.get().on("RoomState.events", this._onEvent);
|
||||
this.updateLists(SettingsStore.getValue("mjolnirRooms"));
|
||||
MatrixClientPeg.get().on("RoomState.events", this.onEvent);
|
||||
}
|
||||
|
||||
stop() {
|
||||
if (this._mjolnirWatchRef) {
|
||||
SettingsStore.unwatchSetting(this._mjolnirWatchRef);
|
||||
this._mjolnirWatchRef = null;
|
||||
if (this.mjolnirWatchRef) {
|
||||
SettingsStore.unwatchSetting(this.mjolnirWatchRef);
|
||||
this.mjolnirWatchRef = null;
|
||||
}
|
||||
|
||||
if (this._dispatcherRef) {
|
||||
dis.unregister(this._dispatcherRef);
|
||||
this._dispatcherRef = null;
|
||||
if (this.dispatcherRef) {
|
||||
dis.unregister(this.dispatcherRef);
|
||||
this.dispatcherRef = null;
|
||||
}
|
||||
|
||||
if (!MatrixClientPeg.get()) return;
|
||||
MatrixClientPeg.get().removeListener("RoomState.events", this._onEvent);
|
||||
MatrixClientPeg.get().removeListener("RoomState.events", this.onEvent);
|
||||
}
|
||||
|
||||
async getOrCreatePersonalList(): Promise<BanList> {
|
||||
|
@ -132,20 +131,20 @@ export class Mjolnir {
|
|||
this._lists = this._lists.filter(b => b.roomId !== roomId);
|
||||
}
|
||||
|
||||
_onEvent = (event) => {
|
||||
private onEvent = (event: MatrixEvent) => {
|
||||
if (!MatrixClientPeg.get()) return;
|
||||
if (!this._roomIds.includes(event.getRoomId())) 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
|
||||
this._updateLists(newValue);
|
||||
this.updateLists(newValue);
|
||||
}
|
||||
|
||||
_updateLists(listRoomIds: string[]) {
|
||||
private updateLists(listRoomIds: string[]) {
|
||||
if (!MatrixClientPeg.get()) return;
|
||||
|
||||
console.log("Updating Mjolnir ban lists to: " + listRoomIds);
|
||||
|
@ -182,10 +181,10 @@ export class Mjolnir {
|
|||
}
|
||||
|
||||
static sharedInstance(): Mjolnir {
|
||||
if (!Mjolnir._instance) {
|
||||
Mjolnir._instance = new Mjolnir();
|
||||
if (!Mjolnir.instance) {
|
||||
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);
|
||||
|
||||
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]);
|
||||
}
|
||||
|
||||
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) => {
|
||||
const req = new XMLHttpRequest();
|
||||
req.open("POST", endpoint);
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
/*
|
||||
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");
|
||||
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 */
|
||||
export enum Layout {
|
||||
IRC = "irc",
|
||||
Group = "group"
|
||||
Group = "group",
|
||||
Bubble = "bubble",
|
||||
}
|
||||
|
||||
/* We need this because multiple components are still using JavaScript */
|
||||
|
|
|
@ -41,6 +41,7 @@ import { Layout } from "./Layout";
|
|||
import ReducedMotionController from './controllers/ReducedMotionController';
|
||||
import IncompatibleController from "./controllers/IncompatibleController";
|
||||
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
|
||||
const LEVELS_ROOM_SETTINGS = [
|
||||
|
@ -321,6 +322,13 @@ export const SETTINGS: {[setting: string]: ISetting} = {
|
|||
displayName: _td("Show info about bridges in room settings"),
|
||||
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": {
|
||||
supportedLevels: LEVELS_ACCOUNT_SETTINGS,
|
||||
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();
|
||||
}
|
||||
|
||||
__onDispatch(payload) {
|
||||
__onDispatch(payload) { // eslint-disable-line @typescript-eslint/naming-convention
|
||||
switch (payload.action) {
|
||||
// Initialise state after initial sync
|
||||
case 'view_room': {
|
||||
|
|
|
@ -44,7 +44,7 @@ class LifecycleStore extends Store<ActionPayload> {
|
|||
this.__emitChange();
|
||||
}
|
||||
|
||||
protected __onDispatch(payload: ActionPayload) {
|
||||
protected __onDispatch(payload: ActionPayload) { // eslint-disable-line @typescript-eslint/naming-convention
|
||||
switch (payload.action) {
|
||||
case 'do_after_sync_prepared':
|
||||
this.setState({
|
||||
|
|
|
@ -144,7 +144,7 @@ export default class RightPanelStore extends Store<ActionPayload> {
|
|||
this.__emitChange();
|
||||
}
|
||||
|
||||
__onDispatch(payload: ActionPayload) {
|
||||
__onDispatch(payload: ActionPayload) { // eslint-disable-line @typescript-eslint/naming-convention
|
||||
switch (payload.action) {
|
||||
case 'view_room':
|
||||
if (payload.room_id === this.lastRoomId) break; // skip this transition, probably a permalink
|
||||
|
|
|
@ -96,7 +96,7 @@ class RoomViewStore extends Store<ActionPayload> {
|
|||
this.__emitChange();
|
||||
}
|
||||
|
||||
__onDispatch(payload) {
|
||||
__onDispatch(payload) { // eslint-disable-line @typescript-eslint/naming-convention
|
||||
switch (payload.action) {
|
||||
// view_room:
|
||||
// - room_alias: '#somealias:matrix.org'
|
||||
|
@ -429,7 +429,7 @@ class RoomViewStore extends Store<ActionPayload> {
|
|||
}
|
||||
}
|
||||
|
||||
let singletonRoomViewStore = null;
|
||||
let singletonRoomViewStore: RoomViewStore = null;
|
||||
if (!singletonRoomViewStore) {
|
||||
singletonRoomViewStore = new RoomViewStore();
|
||||
}
|
||||
|
|
|
@ -63,7 +63,7 @@ const PREVIEWS = {
|
|||
const MAX_EVENTS_BACKWARDS = 50;
|
||||
|
||||
// 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";
|
||||
|
||||
interface IState {
|
||||
|
|
|
@ -124,7 +124,11 @@ export class Algorithm extends EventEmitter {
|
|||
* @param val The new room to sticky.
|
||||
*/
|
||||
public setStickyRoom(val: Room) {
|
||||
try {
|
||||
this.updateStickyRoom(val);
|
||||
} catch (e) {
|
||||
console.warn("Failed to update sticky room", e);
|
||||
}
|
||||
}
|
||||
|
||||
public getTagSorting(tagId: TagID): SortAlgorithm {
|
||||
|
|
|
@ -20,7 +20,7 @@ import { EventType, MsgType } from "matrix-js-sdk/src/@types/event";
|
|||
import { ElementWidgetCapabilities } from "../stores/widgets/ElementWidgetCapabilities";
|
||||
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";
|
||||
|
||||
interface ISendRecvStaticCapText {
|
||||
|
|
|
@ -23,6 +23,7 @@ import { mkEvent, mkStubRoom } from "../../../test-utils";
|
|||
import { MatrixClientPeg } from "../../../../src/MatrixClientPeg";
|
||||
import * as languageHandler from "../../../../src/languageHandler";
|
||||
import * as TestUtils from "../../../test-utils";
|
||||
import DMRoomMap from "../../../../src/utils/DMRoomMap";
|
||||
|
||||
const _TextualBody = sdk.getComponent("views.messages.TextualBody");
|
||||
const TextualBody = TestUtils.wrapInMatrixClientContext(_TextualBody);
|
||||
|
@ -41,6 +42,7 @@ describe("<TextualBody />", () => {
|
|||
isGuest: () => false,
|
||||
mxcUrlToHttp: (s) => s,
|
||||
};
|
||||
DMRoomMap.makeShared();
|
||||
|
||||
const ev = mkEvent({
|
||||
type: "m.room.message",
|
||||
|
@ -66,6 +68,7 @@ describe("<TextualBody />", () => {
|
|||
isGuest: () => false,
|
||||
mxcUrlToHttp: (s) => s,
|
||||
};
|
||||
DMRoomMap.makeShared();
|
||||
|
||||
const ev = mkEvent({
|
||||
type: "m.room.message",
|
||||
|
@ -92,6 +95,7 @@ describe("<TextualBody />", () => {
|
|||
isGuest: () => false,
|
||||
mxcUrlToHttp: (s) => s,
|
||||
};
|
||||
DMRoomMap.makeShared();
|
||||
});
|
||||
|
||||
it("simple message renders as expected", () => {
|
||||
|
@ -146,6 +150,7 @@ describe("<TextualBody />", () => {
|
|||
isGuest: () => false,
|
||||
mxcUrlToHttp: (s) => s,
|
||||
};
|
||||
DMRoomMap.makeShared();
|
||||
});
|
||||
|
||||
it("italics, bold, underline and strikethrough render as expected", () => {
|
||||
|
@ -292,6 +297,7 @@ describe("<TextualBody />", () => {
|
|||
isGuest: () => false,
|
||||
mxcUrlToHttp: (s) => s,
|
||||
};
|
||||
DMRoomMap.makeShared();
|
||||
|
||||
const ev = mkEvent({
|
||||
type: "m.room.message",
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
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';
|
||||
|
||||
/* Shorter timeout, we've got tests to run */
|
||||
|
@ -13,7 +13,7 @@ describe("waitForMember", () => {
|
|||
});
|
||||
|
||||
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);
|
||||
done();
|
||||
});
|
||||
|
@ -22,7 +22,7 @@ describe("waitForMember", () => {
|
|||
it("resolves with false if the timeout is reached, even if other RoomState.newMember events fire", (done) => {
|
||||
const roomId = "!roomId:domain";
|
||||
const userId = "@clientId:domain";
|
||||
_waitForMember(client, roomId, userId, { timeout }).then((r) => {
|
||||
waitForMember(client, roomId, userId, { timeout }).then((r) => {
|
||||
expect(r).toBe(false);
|
||||
done();
|
||||
});
|
||||
|
@ -32,7 +32,7 @@ describe("waitForMember", () => {
|
|||
it("resolves with true if RoomState.newMember fires", (done) => {
|
||||
const roomId = "!roomId:domain";
|
||||
const userId = "@clientId:domain";
|
||||
_waitForMember(client, roomId, userId, { timeout }).then((r) => {
|
||||
waitForMember(client, roomId, userId, { timeout }).then((r) => {
|
||||
expect(r).toBe(true);
|
||||
expect(client.listeners("RoomState.newMember").length).toBe(0);
|
||||
done();
|
||||
|
|
10
yarn.lock
10
yarn.lock
|
@ -3242,7 +3242,7 @@ eslint-config-google@^0.14.0:
|
|||
|
||||
"eslint-plugin-matrix-org@github:matrix-org/eslint-plugin-matrix-org#main":
|
||||
version "0.3.2"
|
||||
resolved "https://codeload.github.com/matrix-org/eslint-plugin-matrix-org/tar.gz/e8197938dca66849ffdac4baca7c05275df12835"
|
||||
resolved "https://codeload.github.com/matrix-org/eslint-plugin-matrix-org/tar.gz/8529f1d77863db6327cf1a1a4fa65d06cc26f91b"
|
||||
|
||||
eslint-plugin-react-hooks@^4.2.0:
|
||||
version "4.2.0"
|
||||
|
@ -5455,10 +5455,10 @@ mathml-tag-names@^2.1.3:
|
|||
resolved "https://registry.yarnpkg.com/mathml-tag-names/-/mathml-tag-names-2.1.3.tgz#4ddadd67308e780cf16a47685878ee27b736a0a3"
|
||||
integrity sha512-APMBEanjybaPzUrfqU0IMU5I0AswKMH7k8OTLs0vvV4KZpExkTkY87nR/zpbuTPj+gARop7aGUbl11pnDfW6xg==
|
||||
|
||||
matrix-js-sdk@12.0.1:
|
||||
version "12.0.1"
|
||||
resolved "https://registry.yarnpkg.com/matrix-js-sdk/-/matrix-js-sdk-12.0.1.tgz#3a63881f743420a4d39474daa39bd0fb90930d43"
|
||||
integrity sha512-HkOWv8QHojceo3kPbC+vAIFUjsRAig6MBvEY35UygS3g2dL0UcJ5Qx09/2wcXtu6dowlDnWsz2HHk62tS2cklA==
|
||||
matrix-js-sdk@12.1.0:
|
||||
version "12.1.0"
|
||||
resolved "https://registry.yarnpkg.com/matrix-js-sdk/-/matrix-js-sdk-12.1.0.tgz#7d159dd9bc03701e45a6b2777f1fa582a7e8b970"
|
||||
integrity sha512-/fSqOjD+mTlMD+/B3s3Ja6BfI46FnTDl43ojzGDUOsHRRmSYUmoONb83qkH5Fjm8cI2q5ZBJMsBfjuZwLVeiZw==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.12.5"
|
||||
another-json "^0.2.0"
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue