From 3df9557df2f886db13185872641b2f763baf41aa Mon Sep 17 00:00:00 2001
From: Ayush PS
Date: Wed, 24 Mar 2021 14:00:09 +0530
Subject: [PATCH 001/164] Dial Pad UI fix
---
res/css/views/voip/_DialPad.scss | 1 +
res/css/views/voip/_DialPadContextMenu.scss | 4 +++-
2 files changed, 4 insertions(+), 1 deletion(-)
diff --git a/res/css/views/voip/_DialPad.scss b/res/css/views/voip/_DialPad.scss
index 0c7bff0ce8..fd7c5f56f6 100644
--- a/res/css/views/voip/_DialPad.scss
+++ b/res/css/views/voip/_DialPad.scss
@@ -30,6 +30,7 @@ limitations under the License.
text-align: center;
vertical-align: middle;
line-height: 40px;
+ color: #15191e;
}
.mx_DialPad_deleteButton, .mx_DialPad_dialButton {
diff --git a/res/css/views/voip/_DialPadContextMenu.scss b/res/css/views/voip/_DialPadContextMenu.scss
index 520f51cf93..c400060b6c 100644
--- a/res/css/views/voip/_DialPadContextMenu.scss
+++ b/res/css/views/voip/_DialPadContextMenu.scss
@@ -27,9 +27,11 @@ limitations under the License.
}
.mx_DialPadContextMenu_dialled {
- height: 1em;
+ height: 1.5em;
font-size: 18px;
font-weight: 600;
+ max-width: 150px;
+ overflow: auto;
}
.mx_DialPadContextMenu_dialPad {
From 1488457c3322b6d5dd6ef7882d5b14d9bf49e20f Mon Sep 17 00:00:00 2001
From: Ayush PS
Date: Thu, 25 Mar 2021 01:31:45 +0530
Subject: [PATCH 002/164] Added the class -button-bg-color for all themes
---
res/css/views/voip/_DialPad.scss | 3 +--
res/themes/dark/css/_dark.scss | 2 ++
res/themes/legacy-dark/css/_legacy-dark.scss | 1 +
res/themes/legacy-light/css/_legacy-light.scss | 2 ++
res/themes/light/css/_light.scss | 2 ++
5 files changed, 8 insertions(+), 2 deletions(-)
diff --git a/res/css/views/voip/_DialPad.scss b/res/css/views/voip/_DialPad.scss
index fd7c5f56f6..483b131bfe 100644
--- a/res/css/views/voip/_DialPad.scss
+++ b/res/css/views/voip/_DialPad.scss
@@ -23,14 +23,13 @@ limitations under the License.
.mx_DialPad_button {
width: 40px;
height: 40px;
- background-color: $theme-button-bg-color;
+ background-color: $dialpad-button-bg-color;
border-radius: 40px;
font-size: 18px;
font-weight: 600;
text-align: center;
vertical-align: middle;
line-height: 40px;
- color: #15191e;
}
.mx_DialPad_deleteButton, .mx_DialPad_dialButton {
diff --git a/res/themes/dark/css/_dark.scss b/res/themes/dark/css/_dark.scss
index 7a751ad9c1..42d592c1e1 100644
--- a/res/themes/dark/css/_dark.scss
+++ b/res/themes/dark/css/_dark.scss
@@ -114,6 +114,8 @@ $voipcall-plinth-color: #21262c;
// ********************
$theme-button-bg-color: #e3e8f0;
+$dialpad-button-bg-color: #545454;
+
$roomlist-button-bg-color: rgba(141, 151, 165, 0.2); // Buttons include the filter box, explore button, and sublist buttons
$roomlist-filter-active-bg-color: $bg-color;
diff --git a/res/themes/legacy-dark/css/_legacy-dark.scss b/res/themes/legacy-dark/css/_legacy-dark.scss
index 764b8f302a..ae98141d06 100644
--- a/res/themes/legacy-dark/css/_legacy-dark.scss
+++ b/res/themes/legacy-dark/css/_legacy-dark.scss
@@ -111,6 +111,7 @@ $voipcall-plinth-color: #f2f5f8;
// ********************
$theme-button-bg-color: #e3e8f0;
+$dialpad-button-bg-color: #545454;
$roomlist-button-bg-color: #1A1D23; // Buttons include the filter box, explore button, and sublist buttons
$roomlist-filter-active-bg-color: $roomlist-button-bg-color;
diff --git a/res/themes/legacy-light/css/_legacy-light.scss b/res/themes/legacy-light/css/_legacy-light.scss
index 9ad154dd93..4313e3c0b6 100644
--- a/res/themes/legacy-light/css/_legacy-light.scss
+++ b/res/themes/legacy-light/css/_legacy-light.scss
@@ -178,6 +178,8 @@ $voipcall-plinth-color: #f2f5f8;
// ********************
$theme-button-bg-color: #e3e8f0;
+$dialpad-button-bg-color: #e3e8f0;
+
$roomlist-button-bg-color: #fff; // Buttons include the filter box, explore button, and sublist buttons
$roomlist-filter-active-bg-color: $roomlist-button-bg-color;
diff --git a/res/themes/light/css/_light.scss b/res/themes/light/css/_light.scss
index 25fbd0201b..81330d07c9 100644
--- a/res/themes/light/css/_light.scss
+++ b/res/themes/light/css/_light.scss
@@ -169,6 +169,8 @@ $voipcall-plinth-color: #f2f5f8;
// ********************
$theme-button-bg-color: #e3e8f0;
+$dialpad-button-bg-color: #e3e8f0;
+
$roomlist-button-bg-color: rgba(141, 151, 165, 0.2); // Buttons include the filter box, explore button, and sublist buttons
$roomlist-filter-active-bg-color: #ffffff;
From 3201ed2f0fe0e378c741d57d9a147d79b267b842 Mon Sep 17 00:00:00 2001
From: Ayush PS
Date: Mon, 26 Apr 2021 01:40:10 +0530
Subject: [PATCH 003/164] Added color scheme for the numbers
---
res/css/views/voip/_DialPadContextMenu.scss | 2 +-
res/themes/dark/css/_dark.scss | 3 ++-
res/themes/legacy-dark/css/_legacy-dark.scss | 3 ++-
3 files changed, 5 insertions(+), 3 deletions(-)
diff --git a/res/css/views/voip/_DialPadContextMenu.scss b/res/css/views/voip/_DialPadContextMenu.scss
index c400060b6c..9879b7da1c 100644
--- a/res/css/views/voip/_DialPadContextMenu.scss
+++ b/res/css/views/voip/_DialPadContextMenu.scss
@@ -30,7 +30,7 @@ limitations under the License.
height: 1.5em;
font-size: 18px;
font-weight: 600;
- max-width: 150px;
+ max-width: 155px;
overflow: auto;
}
diff --git a/res/themes/dark/css/_dark.scss b/res/themes/dark/css/_dark.scss
index 42d592c1e1..b83bd52f76 100644
--- a/res/themes/dark/css/_dark.scss
+++ b/res/themes/dark/css/_dark.scss
@@ -114,7 +114,8 @@ $voipcall-plinth-color: #21262c;
// ********************
$theme-button-bg-color: #e3e8f0;
-$dialpad-button-bg-color: #545454;
+$dialpad-button-bg-color: #6F7882;
+;
$roomlist-button-bg-color: rgba(141, 151, 165, 0.2); // Buttons include the filter box, explore button, and sublist buttons
diff --git a/res/themes/legacy-dark/css/_legacy-dark.scss b/res/themes/legacy-dark/css/_legacy-dark.scss
index ae98141d06..ff85375d35 100644
--- a/res/themes/legacy-dark/css/_legacy-dark.scss
+++ b/res/themes/legacy-dark/css/_legacy-dark.scss
@@ -111,7 +111,8 @@ $voipcall-plinth-color: #f2f5f8;
// ********************
$theme-button-bg-color: #e3e8f0;
-$dialpad-button-bg-color: #545454;
+$dialpad-button-bg-color: #6F7882;
+;
$roomlist-button-bg-color: #1A1D23; // Buttons include the filter box, explore button, and sublist buttons
$roomlist-filter-active-bg-color: $roomlist-button-bg-color;
From 7509481bb9339e038027efa6564df9746b73518a Mon Sep 17 00:00:00 2001
From: Ayush PS
Date: Wed, 28 Apr 2021 02:46:43 +0530
Subject: [PATCH 004/164] Added the LTR support for the dialpad
---
res/css/views/voip/_DialPadContextMenu.scss | 14 ++++++++++++--
.../views/context_menus/DialpadContextMenu.tsx | 13 ++++++++++++-
2 files changed, 24 insertions(+), 3 deletions(-)
diff --git a/res/css/views/voip/_DialPadContextMenu.scss b/res/css/views/voip/_DialPadContextMenu.scss
index 9879b7da1c..c01ce0f2d9 100644
--- a/res/css/views/voip/_DialPadContextMenu.scss
+++ b/res/css/views/voip/_DialPadContextMenu.scss
@@ -30,8 +30,18 @@ limitations under the License.
height: 1.5em;
font-size: 18px;
font-weight: 600;
- max-width: 155px;
- overflow: auto;
+ max-width: 150px;
+ border: none;
+ margin: 0px;
+
+}
+.mx_DialPadContextMenu_dialled input{
+ font-size: 18px;
+ font-weight: 600;
+ overflow: hidden;
+ text-align: left;
+ direction: rtl;
+ background-color: rgb(0, 0, 0, 0);
}
.mx_DialPadContextMenu_dialPad {
diff --git a/src/components/views/context_menus/DialpadContextMenu.tsx b/src/components/views/context_menus/DialpadContextMenu.tsx
index 17abce0c61..0a1d8184f2 100644
--- a/src/components/views/context_menus/DialpadContextMenu.tsx
+++ b/src/components/views/context_menus/DialpadContextMenu.tsx
@@ -18,6 +18,7 @@ import React from 'react';
import { _t } from '../../../languageHandler';
import { ContextMenu, IProps as IContextMenuProps } from '../../structures/ContextMenu';
import { MatrixCall } from 'matrix-js-sdk/src/webrtc/call';
+import Field from "../elements/Field";
import Dialpad from '../voip/DialPad';
import {replaceableComponent} from "../../../utils/replaceableComponent";
@@ -44,13 +45,23 @@ export default class DialpadContextMenu extends React.Component
this.setState({value: this.state.value + digit});
}
+ onChange = (ev) => {
+ this.setState({value: ev.target.value});
+ }
+
+
render() {
return
{_t("Dial pad")}
-
{this.state.value}
+
From b42872daa409d27ad4634d2418fee2bfa7dccfea Mon Sep 17 00:00:00 2001
From: Ayush PS
Date: Sun, 2 May 2021 22:10:15 +0530
Subject: [PATCH 005/164] Fixed the Dial Pad and finalized the changes
---
res/css/views/voip/_DialPadContextMenu.scss | 5 +++--
.../views/context_menus/DialpadContextMenu.tsx | 12 +++++-------
2 files changed, 8 insertions(+), 9 deletions(-)
diff --git a/res/css/views/voip/_DialPadContextMenu.scss b/res/css/views/voip/_DialPadContextMenu.scss
index c01ce0f2d9..31327113cf 100644
--- a/res/css/views/voip/_DialPadContextMenu.scss
+++ b/res/css/views/voip/_DialPadContextMenu.scss
@@ -33,14 +33,15 @@ limitations under the License.
max-width: 150px;
border: none;
margin: 0px;
-
}
-.mx_DialPadContextMenu_dialled input{
+.mx_DialPadContextMenu_dialled input {
font-size: 18px;
font-weight: 600;
overflow: hidden;
+ max-width: 150px;
text-align: left;
direction: rtl;
+ padding: 8px 0px;
background-color: rgb(0, 0, 0, 0);
}
diff --git a/src/components/views/context_menus/DialpadContextMenu.tsx b/src/components/views/context_menus/DialpadContextMenu.tsx
index 0a1d8184f2..8879629055 100644
--- a/src/components/views/context_menus/DialpadContextMenu.tsx
+++ b/src/components/views/context_menus/DialpadContextMenu.tsx
@@ -48,7 +48,7 @@ export default class DialpadContextMenu extends React.Component
onChange = (ev) => {
this.setState({value: ev.target.value});
}
-
+
render() {
return
@@ -56,12 +56,10 @@ export default class DialpadContextMenu extends React.Component
{_t("Dial pad")}
-
+
From 44b143c8c3063be7ca2bf24e6cfdb81be9351c75 Mon Sep 17 00:00:00 2001
From: Robin Townsend
Date: Sat, 8 May 2021 21:17:05 -0400
Subject: [PATCH 006/164] Match requested avatar size to displayed size
Reduces the blurriness of avatars in the EventTilePreview.
Signed-off-by: Robin Townsend
---
src/components/views/elements/EventTilePreview.tsx | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/components/views/elements/EventTilePreview.tsx b/src/components/views/elements/EventTilePreview.tsx
index b15fbbed2b..95f9a97058 100644
--- a/src/components/views/elements/EventTilePreview.tsx
+++ b/src/components/views/elements/EventTilePreview.tsx
@@ -61,7 +61,7 @@ interface IState {
message: string;
}
-const AVATAR_SIZE = 32;
+const AVATAR_SIZE = 30;
@replaceableComponent("views.elements.EventTilePreview")
export default class EventTilePreview extends React.Component {
From e46bc931781095447a1929938a5cb5bdbdb7de4d Mon Sep 17 00:00:00 2001
From: Robin Townsend
Date: Sat, 8 May 2021 21:22:31 -0400
Subject: [PATCH 007/164] Fall back to MXID when no display name is present
MemberAvatar requires a display name, or else it refuses to render.
Signed-off-by: Robin Townsend
---
src/components/views/elements/EventTilePreview.tsx | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/components/views/elements/EventTilePreview.tsx b/src/components/views/elements/EventTilePreview.tsx
index 95f9a97058..6d2ea687de 100644
--- a/src/components/views/elements/EventTilePreview.tsx
+++ b/src/components/views/elements/EventTilePreview.tsx
@@ -101,7 +101,7 @@ export default class EventTilePreview extends React.Component {
// Fake it more
event.sender = {
- name: this.props.displayName,
+ name: this.props.displayName || this.props.userId,
userId: this.props.userId,
getAvatarUrl: (..._) => {
return Avatar.avatarUrlForUser(
From 42ffc5c9e8ffd1673f14268a18c83bed31378984 Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Mon, 24 May 2021 18:08:43 +0100
Subject: [PATCH 008/164] Add support to keyboard shortcuts dialog for [digits]
---
src/accessibility/KeyboardShortcuts.tsx | 3 +++
src/i18n/strings/en_EN.json | 3 ++-
2 files changed, 5 insertions(+), 1 deletion(-)
diff --git a/src/accessibility/KeyboardShortcuts.tsx b/src/accessibility/KeyboardShortcuts.tsx
index 2a3e576e31..1cd5408210 100644
--- a/src/accessibility/KeyboardShortcuts.tsx
+++ b/src/accessibility/KeyboardShortcuts.tsx
@@ -57,6 +57,8 @@ export enum Modifiers {
// Meta-modifier: isMac ? CMD : CONTROL
export const CMD_OR_CTRL = isMac ? Modifiers.COMMAND : Modifiers.CONTROL;
+// Meta-key representing the digits [0-9] often found at the top of standard keyboard layouts
+export const DIGITS = "digits";
interface IKeybind {
modifiers?: Modifiers[];
@@ -319,6 +321,7 @@ const alternateKeyName: Record = {
[Key.SPACE]: _td("Space"),
[Key.HOME]: _td("Home"),
[Key.END]: _td("End"),
+ [DIGITS]: _td("[number]"),
};
const keyIcon: Record = {
[Key.ARROW_UP]: "↑",
diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json
index 7ceb039822..251c70e241 100644
--- a/src/i18n/strings/en_EN.json
+++ b/src/i18n/strings/en_EN.json
@@ -2973,5 +2973,6 @@
"Esc": "Esc",
"Enter": "Enter",
"Space": "Space",
- "End": "End"
+ "End": "End",
+ "[number]": "[number]"
}
From 63ae84a72ed441853bb0c95857afc755eefe8486 Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Mon, 24 May 2021 18:23:04 +0100
Subject: [PATCH 009/164] Wire space switch shortcut via dispatcher to prevent
app load explosion due to skinning
---
src/dispatcher/actions.ts | 5 ++++
src/dispatcher/payloads/SwitchSpacePayload.ts | 27 +++++++++++++++++++
src/stores/SpaceStore.tsx | 7 +++++
3 files changed, 39 insertions(+)
create mode 100644 src/dispatcher/payloads/SwitchSpacePayload.ts
diff --git a/src/dispatcher/actions.ts b/src/dispatcher/actions.ts
index cd32c3743f..79e1edeee9 100644
--- a/src/dispatcher/actions.ts
+++ b/src/dispatcher/actions.ts
@@ -138,4 +138,9 @@ export enum Action {
* Fired when an upload is cancelled by the user. Should be used with UploadCanceledPayload.
*/
UploadCanceled = "upload_canceled",
+
+ /**
+ * Switches space. Should be used with SwitchSpacePayload.
+ */
+ SwitchSpace = "switch_space",
}
diff --git a/src/dispatcher/payloads/SwitchSpacePayload.ts b/src/dispatcher/payloads/SwitchSpacePayload.ts
new file mode 100644
index 0000000000..04eb744334
--- /dev/null
+++ b/src/dispatcher/payloads/SwitchSpacePayload.ts
@@ -0,0 +1,27 @@
+/*
+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 { ActionPayload } from "../payloads";
+import { Action } from "../actions";
+
+export interface SwitchSpacePayload extends ActionPayload {
+ action: Action.SwitchSpace;
+
+ /**
+ * The number of the space to switch to, 1-indexed, 0 is Home.
+ */
+ num: number;
+}
diff --git a/src/stores/SpaceStore.tsx b/src/stores/SpaceStore.tsx
index 40997d30a8..0d09357fc1 100644
--- a/src/stores/SpaceStore.tsx
+++ b/src/stores/SpaceStore.tsx
@@ -33,6 +33,7 @@ import {EnhancedMap, mapDiff} from "../utils/maps";
import {setHasDiff} from "../utils/sets";
import {ISpaceSummaryEvent, ISpaceSummaryRoom} from "../components/structures/SpaceRoomDirectory";
import RoomViewStore from "./RoomViewStore";
+import {Action} from "../dispatcher/actions";
interface IState {}
@@ -565,6 +566,12 @@ export class SpaceStoreClass extends AsyncStoreWithClient {
this.setActiveSpace(null, false);
}
break;
+ case Action.SwitchSpace:
+ if (payload.num === 0) {
+ this.setActiveSpace(null);
+ } else if (this.spacePanelSpaces.length >= payload.num) {
+ this.setActiveSpace(this.spacePanelSpaces[payload.num - 1]);
+ }
}
}
From 0b7d3f007aece9a15d53e53f0f953fa203da8457 Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Tue, 1 Jun 2021 17:30:57 +0100
Subject: [PATCH 010/164] Remove react-beautiful-dnd
---
package.json | 1 -
src/components/structures/GroupFilterPanel.js | 32 ++---
src/components/structures/LoggedInView.tsx | 65 ++-------
src/components/views/elements/DNDTagTile.js | 32 ++---
.../views/groups/GroupPublicityToggle.js | 4 +-
src/components/views/groups/GroupTile.js | 44 +-----
test/components/views/rooms/RoomList-test.js | 5 +-
yarn.lock | 130 +-----------------
8 files changed, 36 insertions(+), 277 deletions(-)
diff --git a/package.json b/package.json
index 13047b69cf..270c86ddba 100644
--- a/package.json
+++ b/package.json
@@ -91,7 +91,6 @@
"qs": "^6.9.6",
"re-resizable": "^6.9.0",
"react": "^16.14.0",
- "react-beautiful-dnd": "^4.0.1",
"react-dom": "^16.14.0",
"react-focus-lock": "^2.5.0",
"react-transition-group": "^4.4.1",
diff --git a/src/components/structures/GroupFilterPanel.js b/src/components/structures/GroupFilterPanel.js
index 2ff91e4976..f1c28d588a 100644
--- a/src/components/structures/GroupFilterPanel.js
+++ b/src/components/structures/GroupFilterPanel.js
@@ -24,7 +24,6 @@ import * as sdk from '../../index';
import dis from '../../dispatcher/dispatcher';
import { _t } from '../../languageHandler';
-import { Droppable } from 'react-beautiful-dnd';
import classNames from 'classnames';
import MatrixClientContext from "../../contexts/MatrixClientContext";
import AutoHideScrollbar from "./AutoHideScrollbar";
@@ -83,7 +82,7 @@ class GroupFilterPanel extends React.Component {
}
};
- onMouseDown = e => {
+ onClick = e => {
// only dispatch if its not a no-op
if (this.state.selectedTags.length > 0) {
dis.dispatch({action: 'deselect_tags'});
@@ -151,28 +150,15 @@ class GroupFilterPanel extends React.Component {
return
-
- { (provided, snapshot) => (
-
- { this.renderGlobalIcon() }
- { tags }
-
- {createButton}
-
- { provided.placeholder }
-
- ) }
-
+
+ { this.renderGlobalIcon() }
+ { tags }
+
+ { createButton }
+
+
;
}
diff --git a/src/components/structures/LoggedInView.tsx b/src/components/structures/LoggedInView.tsx
index ad5c759f0d..f5df99d8c9 100644
--- a/src/components/structures/LoggedInView.tsx
+++ b/src/components/structures/LoggedInView.tsx
@@ -19,7 +19,6 @@ limitations under the License.
import * as React from 'react';
import * as PropTypes from 'prop-types';
import { MatrixClient } from 'matrix-js-sdk/src/client';
-import { DragDropContext } from 'react-beautiful-dnd';
import {Key} from '../../Keyboard';
import PageTypes from '../../PageTypes';
@@ -569,50 +568,6 @@ class LoggedInView extends React.Component {
}
};
- _onDragEnd = (result) => {
- // Dragged to an invalid destination, not onto a droppable
- if (!result.destination) {
- return;
- }
-
- const dest = result.destination.droppableId;
-
- if (dest === 'tag-panel-droppable') {
- // Could be "GroupTile +groupId:domain"
- const draggableId = result.draggableId.split(' ').pop();
-
- // Dispatch synchronously so that the GroupFilterPanel receives an
- // optimistic update from GroupFilterOrderStore before the previous
- // state is shown.
- dis.dispatch(TagOrderActions.moveTag(
- this._matrixClient,
- draggableId,
- result.destination.index,
- ), true);
- } else if (dest.startsWith('room-sub-list-droppable_')) {
- this._onRoomTileEndDrag(result);
- }
- };
-
- _onRoomTileEndDrag = (result) => {
- let newTag = result.destination.droppableId.split('_')[1];
- let prevTag = result.source.droppableId.split('_')[1];
- if (newTag === 'undefined') newTag = undefined;
- if (prevTag === 'undefined') prevTag = undefined;
-
- const roomId = result.draggableId.split('_')[1];
-
- const oldIndex = result.source.index;
- const newIndex = result.destination.index;
-
- dis.dispatch(RoomListActions.tagRoom(
- this._matrixClient,
- this._matrixClient.getRoom(roomId),
- prevTag, newTag,
- oldIndex, newIndex,
- ), true);
- };
-
render() {
const RoomView = sdk.getComponent('structures.RoomView');
const UserView = sdk.getComponent('structures.UserView');
@@ -679,17 +634,15 @@ class LoggedInView extends React.Component {
aria-hidden={this.props.hideToSRUsers}
>
-
-
- { SettingsStore.getValue("feature_spaces") ? : null }
-
-
- { pageElement }
-
-
+
+ { SettingsStore.getValue("feature_spaces") ? : null }
+
+
+ { pageElement }
+
diff --git a/src/components/views/elements/DNDTagTile.js b/src/components/views/elements/DNDTagTile.js
index 67572d4508..eaaa0f183b 100644
--- a/src/components/views/elements/DNDTagTile.js
+++ b/src/components/views/elements/DNDTagTile.js
@@ -18,7 +18,6 @@ limitations under the License.
import TagTile from './TagTile';
import React from 'react';
-import { Draggable } from 'react-beautiful-dnd';
import { ContextMenu, toRightOf, useContextMenu } from "../../structures/ContextMenu";
import * as sdk from '../../../index';
@@ -35,28 +34,13 @@ export default function DNDTagTile(props) {
);
}
- return
-
- {(provided, snapshot) => (
-
-
-
- )}
-
+ return <>
+
{contextMenu}
-
;
+ >;
}
diff --git a/src/components/views/groups/GroupPublicityToggle.js b/src/components/views/groups/GroupPublicityToggle.js
index c06d827550..6bef141cb8 100644
--- a/src/components/views/groups/GroupPublicityToggle.js
+++ b/src/components/views/groups/GroupPublicityToggle.js
@@ -66,9 +66,7 @@ export default class GroupPublicityToggle extends React.Component {
render() {
const GroupTile = sdk.getComponent('groups.GroupTile');
return
-
+
diff --git a/src/components/views/groups/GroupTile.js b/src/components/views/groups/GroupTile.js
index 42a977fb79..ce42662462 100644
--- a/src/components/views/groups/GroupTile.js
+++ b/src/components/views/groups/GroupTile.js
@@ -16,7 +16,6 @@ limitations under the License.
import React from 'react';
import PropTypes from 'prop-types';
-import { Draggable, Droppable } from 'react-beautiful-dnd';
import * as sdk from '../../../index';
import dis from '../../../dispatcher/dispatcher';
import FlairStore from '../../../stores/FlairStore';
@@ -24,8 +23,6 @@ import MatrixClientContext from "../../../contexts/MatrixClientContext";
import {replaceableComponent} from "../../../utils/replaceableComponent";
import {mediaFromMxc} from "../../../customisations/Media";
-function nop() {}
-
@replaceableComponent("views.groups.GroupTile")
class GroupTile extends React.Component {
static propTypes = {
@@ -34,7 +31,6 @@ class GroupTile extends React.Component {
showDescription: PropTypes.bool,
// Height of the group avatar in pixels
avatarHeight: PropTypes.number,
- draggable: PropTypes.bool,
};
static contextType = MatrixClientContext;
@@ -42,7 +38,6 @@ class GroupTile extends React.Component {
static defaultProps = {
showDescription: true,
avatarHeight: 50,
- draggable: true,
};
state = {
@@ -57,7 +52,7 @@ class GroupTile extends React.Component {
});
}
- onMouseDown = e => {
+ onClick = e => {
e.preventDefault();
dis.dispatch({
action: 'view_group',
@@ -78,7 +73,7 @@ class GroupTile extends React.Component {
? mediaFromMxc(profile.avatarUrl).getSquareThumbnailHttp(avatarHeight)
: null;
- let avatarElement = (
+ const avatarElement = (
);
- if (this.props.draggable) {
- const avatarClone = avatarElement;
- avatarElement = (
-
- { (droppableProvided, droppableSnapshot) => (
-
-
- { (provided, snapshot) => (
-
-
- {avatarClone}
-
- { /* Instead of a blank placeholder, use a copy of the avatar itself. */ }
- { provided.placeholder ? avatarClone :
}
-
- ) }
-
-
- ) }
-
- );
- }
- // XXX: Use onMouseDown as a workaround for https://github.com/atlassian/react-beautiful-dnd/issues/273
- // instead of onClick. Otherwise we experience https://github.com/vector-im/element-web/issues/6156
- return
+ return
{ avatarElement }
{ name }
diff --git a/test/components/views/rooms/RoomList-test.js b/test/components/views/rooms/RoomList-test.js
index bfb8e1afd4..6aad6a90fd 100644
--- a/test/components/views/rooms/RoomList-test.js
+++ b/test/components/views/rooms/RoomList-test.js
@@ -6,7 +6,6 @@ import * as TestUtils from '../../../test-utils';
import {MatrixClientPeg} from '../../../../src/MatrixClientPeg';
import sdk from '../../../skinned-sdk';
-import { DragDropContext } from 'react-beautiful-dnd';
import dis from '../../../../src/dispatcher/dispatcher';
import DMRoomMap from '../../../../src/utils/DMRoomMap';
@@ -68,9 +67,7 @@ describe('RoomList', () => {
const RoomList = sdk.getComponent('views.rooms.RoomList');
const WrappedRoomList = TestUtils.wrapInMatrixClientContext(RoomList);
root = ReactDOM.render(
-
- {}} />
- ,
+
{}} />,
parentDiv,
);
ReactTestUtils.findRenderedComponentWithType(root, RoomList);
diff --git a/yarn.lock b/yarn.lock
index 0ff235a660..2c84237730 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1017,7 +1017,7 @@
pirates "^4.0.0"
source-map-support "^0.5.16"
-"@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.12.5", "@babel/runtime@^7.5.5", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7":
+"@babel/runtime@^7.0.0", "@babel/runtime@^7.12.5", "@babel/runtime@^7.5.5", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7":
version "7.12.5"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.12.5.tgz#410e7e487441e1b360c29be715d870d9b985882e"
integrity sha512-plcc+hbExy3McchJCEQG3knOsuh3HH+Prx1P6cLIkET/0dLuQDEnrT+s27Axgc9bqfsmNUNHfscgMUdBpC9xfg==
@@ -2114,14 +2114,6 @@ babel-preset-jest@^26.6.2:
babel-plugin-jest-hoist "^26.6.2"
babel-preset-current-node-syntax "^1.0.0"
-babel-runtime@^6.26.0:
- version "6.26.0"
- resolved "https://registry.yarnpkg.com/babel-runtime/-/babel-runtime-6.26.0.tgz#965c7058668e82b55d7bfe04ff2337bc8b5647fe"
- integrity sha1-llxwWGaOgrVde/4E/yM3vItWR/4=
- dependencies:
- core-js "^2.4.0"
- regenerator-runtime "^0.11.0"
-
bail@^1.0.0:
version "1.0.5"
resolved "https://registry.yarnpkg.com/bail/-/bail-1.0.5.tgz#b6fa133404a392cbc1f8c4bf63f5953351e7a776"
@@ -2645,11 +2637,6 @@ core-js@^1.0.0:
resolved "https://registry.yarnpkg.com/core-js/-/core-js-1.2.7.tgz#652294c14651db28fa93bd2d5ff2983a4f08c636"
integrity sha1-ZSKUwUZR2yj6k70tX/KYOk8IxjY=
-core-js@^2.4.0:
- version "2.6.12"
- resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.12.tgz#d9333dfa7b065e347cc5682219d6f690859cc2ec"
- integrity sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==
-
core-util-is@1.0.2, core-util-is@~1.0.0:
version "1.0.2"
resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7"
@@ -4215,13 +4202,6 @@ highlight.js@^10.5.0:
resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-10.5.0.tgz#3f09fede6a865757378f2d9ebdcbc15ba268f98f"
integrity sha512-xTmvd9HiIHR6L53TMC7TKolEj65zG1XU+Onr8oi86mYa+nLcIbxTTWkpW7CsEwv/vK7u1zb8alZIMLDqqN6KTw==
-hoist-non-react-statics@^3.3.0:
- version "3.3.2"
- resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45"
- integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==
- dependencies:
- react-is "^16.7.0"
-
hosted-git-info@^2.1.4:
version "2.8.9"
resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.9.tgz#dffc0bf9a21c02209090f2aa69429e1414daf3f9"
@@ -4430,13 +4410,6 @@ internal-slot@^1.0.2:
has "^1.0.3"
side-channel "^1.0.2"
-invariant@^2.2.2, invariant@^2.2.4:
- version "2.2.4"
- resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6"
- integrity sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==
- dependencies:
- loose-envify "^1.0.0"
-
ip-regex@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/ip-regex/-/ip-regex-2.1.0.tgz#fa78bf5d2e6913c911ce9f819ee5146bb6d844e9"
@@ -5556,11 +5529,6 @@ locate-path@^5.0.0:
dependencies:
p-locate "^4.1.0"
-lodash-es@^4.2.1:
- version "4.17.20"
- resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.20.tgz#29f6332eefc60e849f869c264bc71126ad61e8f7"
- integrity sha512-JD1COMZsq8maT6mnuz1UMV0jvYD0E0aUsSOdrr1/nAG3dhqQXwRRgeW0cSqH1U43INKcqxaiVIQNOUDld7gRDA==
-
lodash.escape@^4.0.1:
version "4.0.1"
resolved "https://registry.yarnpkg.com/lodash.escape/-/lodash.escape-4.0.1.tgz#c9044690c21e04294beaa517712fded1fa88de98"
@@ -5581,7 +5549,7 @@ lodash.sortby@^4.7.0:
resolved "https://registry.yarnpkg.com/lodash.sortby/-/lodash.sortby-4.7.0.tgz#edd14c824e2cc9c1e0b0a1b42bb5210516a42438"
integrity sha1-7dFMgk4sycHgsKG0K7UhBRakJDg=
-lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.2.1:
+lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20:
version "4.17.21"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
@@ -5749,11 +5717,6 @@ mdurl@~1.0.1:
resolved "https://registry.yarnpkg.com/mdurl/-/mdurl-1.0.1.tgz#fe85b2ec75a59037f2adfec100fd6c601761152e"
integrity sha1-/oWy7HWlkDfyrf7BAP1sYBdhFS4=
-memoize-one@^3.0.1:
- version "3.1.1"
- resolved "https://registry.yarnpkg.com/memoize-one/-/memoize-one-3.1.1.tgz#ef609811e3bc28970eac2884eece64d167830d17"
- integrity sha512-YqVh744GsMlZu6xkhGslPSqSurOv6P+kLN2J3ysBZfagLcL5FdRK/0UpgLoL8hwjjEvvAVkjJZyFP+1T6p1vgA==
-
meow@^9.0.0:
version "9.0.0"
resolved "https://registry.yarnpkg.com/meow/-/meow-9.0.0.tgz#cd9510bc5cac9dee7d03c73ee1f9ad959f4ea364"
@@ -6374,11 +6337,6 @@ path-type@^4.0.0:
resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b"
integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==
-performance-now@^0.2.0:
- version "0.2.0"
- resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-0.2.0.tgz#33ef30c5c77d4ea21c5a53869d91b56d8f2555e5"
- integrity sha1-M+8wxcd9TqIcWlOGnZG1bY8lVeU=
-
performance-now@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b"
@@ -6597,7 +6555,7 @@ prop-types-exact@^1.2.0:
object.assign "^4.1.0"
reflect.ownkeys "^0.2.0"
-prop-types@^15.5.8, prop-types@^15.6.0, prop-types@^15.6.1, prop-types@^15.6.2, prop-types@^15.7.2:
+prop-types@^15.6.2, prop-types@^15.7.2:
version "15.7.2"
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5"
integrity sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==
@@ -6674,12 +6632,7 @@ quick-lru@^4.0.1:
resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-4.0.1.tgz#5b8878f113a58217848c6482026c73e1ba57727f"
integrity sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==
-raf-schd@^2.1.0:
- version "2.1.2"
- resolved "https://registry.yarnpkg.com/raf-schd/-/raf-schd-2.1.2.tgz#ec622b5167f2912089f054dc03ebd5bcf33c8f62"
- integrity sha512-Orl0IEvMtUCgPddgSxtxreK77UiQz4nPYJy9RggVzu4mKsZkQWiAaG1y9HlYWdvm9xtN348xRaT37qkvL/+A+g==
-
-raf@^3.1.0, raf@^3.4.1:
+raf@^3.4.1:
version "3.4.1"
resolved "https://registry.yarnpkg.com/raf/-/raf-3.4.1.tgz#0742e99a4a6552f445d73e3ee0328af0ff1ede39"
integrity sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA==
@@ -6706,22 +6659,6 @@ re-resizable@^6.9.0:
dependencies:
fast-memoize "^2.5.1"
-react-beautiful-dnd@^4.0.1:
- version "4.0.1"
- resolved "https://registry.yarnpkg.com/react-beautiful-dnd/-/react-beautiful-dnd-4.0.1.tgz#3b0a49bf6be75af351176c904f012611dd292b81"
- integrity sha512-d73RMu4QOFCyjUELLWFyY/EuclnfqulI9pECx+2gIuJvV0ycf1uR88o+1x0RSB9ILD70inHMzCBKNkWVbbt+vA==
- dependencies:
- babel-runtime "^6.26.0"
- invariant "^2.2.2"
- memoize-one "^3.0.1"
- prop-types "^15.6.0"
- raf-schd "^2.1.0"
- react-motion "^0.5.2"
- react-redux "^5.0.6"
- redux "^3.7.2"
- redux-thunk "^2.2.0"
- reselect "^3.0.1"
-
react-clientside-effect@^1.2.2:
version "1.2.3"
resolved "https://registry.yarnpkg.com/react-clientside-effect/-/react-clientside-effect-1.2.3.tgz#95c95f520addfb71743608b990bfe01eb002012b"
@@ -6751,7 +6688,7 @@ react-focus-lock@^2.5.0:
use-callback-ref "^1.2.1"
use-sidecar "^1.0.1"
-react-is@^16.13.1, react-is@^16.6.0, react-is@^16.7.0, react-is@^16.8.1, react-is@^16.8.6:
+react-is@^16.13.1, react-is@^16.8.1, react-is@^16.8.6:
version "16.13.1"
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
@@ -6761,33 +6698,6 @@ react-is@^17.0.1:
resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.1.tgz#5b3531bd76a645a4c9fb6e693ed36419e3301339"
integrity sha512-NAnt2iGDXohE5LI7uBnLnqvLQMtzhkiAOLXTmv+qnF9Ky7xAPcX8Up/xWIhxvLVGJvuLiNc4xQLtuqDRzb4fSA==
-react-lifecycles-compat@^3.0.0:
- version "3.0.4"
- resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362"
- integrity sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==
-
-react-motion@^0.5.2:
- version "0.5.2"
- resolved "https://registry.yarnpkg.com/react-motion/-/react-motion-0.5.2.tgz#0dd3a69e411316567927917c6626551ba0607316"
- integrity sha512-9q3YAvHoUiWlP3cK0v+w1N5Z23HXMj4IF4YuvjvWegWqNPfLXsOBE/V7UvQGpXxHFKRQQcNcVQE31g9SB/6qgQ==
- dependencies:
- performance-now "^0.2.0"
- prop-types "^15.5.8"
- raf "^3.1.0"
-
-react-redux@^5.0.6:
- version "5.1.2"
- resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-5.1.2.tgz#b19cf9e21d694422727bf798e934a916c4080f57"
- integrity sha512-Ns1G0XXc8hDyH/OcBHOxNgQx9ayH3SPxBnFCOidGKSle8pKihysQw2rG/PmciUQRoclhVBO8HMhiRmGXnDja9Q==
- dependencies:
- "@babel/runtime" "^7.1.2"
- hoist-non-react-statics "^3.3.0"
- invariant "^2.2.4"
- loose-envify "^1.1.0"
- prop-types "^15.6.1"
- react-is "^16.6.0"
- react-lifecycles-compat "^3.0.0"
-
react-test-renderer@^16.0.0-0, react-test-renderer@^16.14.0:
version "16.14.0"
resolved "https://registry.yarnpkg.com/react-test-renderer/-/react-test-renderer-16.14.0.tgz#e98360087348e260c56d4fe2315e970480c228ae"
@@ -6908,21 +6818,6 @@ redent@^3.0.0:
indent-string "^4.0.0"
strip-indent "^3.0.0"
-redux-thunk@^2.2.0:
- version "2.3.0"
- resolved "https://registry.yarnpkg.com/redux-thunk/-/redux-thunk-2.3.0.tgz#51c2c19a185ed5187aaa9a2d08b666d0d6467622"
- integrity sha512-km6dclyFnmcvxhAcrQV2AkZmPQjzPDjgVlQtR0EQjxZPyJ0BnMf3in1ryuR8A2qU0HldVRfxYXbFSKlI3N7Slw==
-
-redux@^3.7.2:
- version "3.7.2"
- resolved "https://registry.yarnpkg.com/redux/-/redux-3.7.2.tgz#06b73123215901d25d065be342eb026bc1c8537b"
- integrity sha512-pNqnf9q1hI5HHZRBkj3bAngGZW/JMCmexDlOxw4XagXY2o1327nHH54LoTjiPJ0gizoqPDRqWyX/00g0hD6w+A==
- dependencies:
- lodash "^4.2.1"
- lodash-es "^4.2.1"
- loose-envify "^1.1.0"
- symbol-observable "^1.0.3"
-
reflect.ownkeys@^0.2.0:
version "0.2.0"
resolved "https://registry.yarnpkg.com/reflect.ownkeys/-/reflect.ownkeys-0.2.0.tgz#749aceec7f3fdf8b63f927a04809e90c5c0b3460"
@@ -6940,11 +6835,6 @@ regenerate@^1.4.0:
resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.4.2.tgz#b9346d8827e8f5a32f7ba29637d398b69014848a"
integrity sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==
-regenerator-runtime@^0.11.0:
- version "0.11.1"
- resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz#be05ad7f9bf7d22e056f9726cee5017fbf19e2e9"
- integrity sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==
-
regenerator-runtime@^0.13.4:
version "0.13.7"
resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz#cac2dacc8a1ea675feaabaeb8ae833898ae46f55"
@@ -7102,11 +6992,6 @@ require-main-filename@^2.0.0:
resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-2.0.0.tgz#d0b329ecc7cc0f61649f62215be69af54aa8989b"
integrity sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==
-reselect@^3.0.1:
- version "3.0.1"
- resolved "https://registry.yarnpkg.com/reselect/-/reselect-3.0.1.tgz#efdaa98ea7451324d092b2b2163a6a1d7a9a2147"
- integrity sha1-79qpjqdFEyTQkrKyFjpqHXqaIUc=
-
resize-observer-polyfill@^1.5.1:
version "1.5.1"
resolved "https://registry.yarnpkg.com/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz#0e9020dd3d21024458d4ebd27e23e40269810464"
@@ -7813,11 +7698,6 @@ svg-tags@^1.0.0:
resolved "https://registry.yarnpkg.com/svg-tags/-/svg-tags-1.0.0.tgz#58f71cee3bd519b59d4b2a843b6c7de64ac04764"
integrity sha1-WPcc7jvVGbWdSyqEO2x95krAR2Q=
-symbol-observable@^1.0.3:
- version "1.2.0"
- resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.2.0.tgz#c22688aed4eab3cdc2dfeacbb561660560a00804"
- integrity sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ==
-
symbol-tree@^3.2.4:
version "3.2.4"
resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.4.tgz#430637d248ba77e078883951fb9aa0eed7c63fa2"
From bc3c759feb2d3f062d0dbc9489e5741fa7d8af13 Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Wed, 2 Jun 2021 11:33:25 +0100
Subject: [PATCH 011/164] Add temporary mechanism for managing communities
without dnd
---
.../context_menus/_TagTileContextMenu.scss | 9 ++++
src/components/structures/MyGroups.js | 3 +-
.../views/context_menus/TagTileContextMenu.js | 49 ++++++++++++++-----
src/components/views/elements/DNDTagTile.js | 2 +-
src/components/views/groups/GroupTile.js | 23 +++++++++
src/i18n/strings/en_EN.json | 4 +-
6 files changed, 74 insertions(+), 16 deletions(-)
diff --git a/res/css/views/context_menus/_TagTileContextMenu.scss b/res/css/views/context_menus/_TagTileContextMenu.scss
index 8929c8906e..d707f4ce7c 100644
--- a/res/css/views/context_menus/_TagTileContextMenu.scss
+++ b/res/css/views/context_menus/_TagTileContextMenu.scss
@@ -38,6 +38,15 @@ limitations under the License.
mask-image: url('$(res)/img/element-icons/view-community.svg');
}
+.mx_TagTileContextMenu_moveUp::before {
+ transform: rotate(180deg);
+ mask-image: url('$(res)/img/feather-customised/chevron-down.svg');
+}
+
+.mx_TagTileContextMenu_moveDown::before {
+ mask-image: url('$(res)/img/feather-customised/chevron-down.svg');
+}
+
.mx_TagTileContextMenu_hideCommunity::before {
mask-image: url('$(res)/img/element-icons/hide.svg');
}
diff --git a/src/components/structures/MyGroups.js b/src/components/structures/MyGroups.js
index 1fab6c4348..d0a2fbff41 100644
--- a/src/components/structures/MyGroups.js
+++ b/src/components/structures/MyGroups.js
@@ -82,8 +82,7 @@ export default class MyGroups extends React.Component {
{ _t(
- "To set up a filter, drag a community avatar over to the filter panel on " +
- "the far left hand side of the screen. You can click on an avatar in the " +
+ "You can click on an avatar in the " +
"filter panel at any time to see only the rooms and people associated " +
"with that community.",
) }
diff --git a/src/components/views/context_menus/TagTileContextMenu.js b/src/components/views/context_menus/TagTileContextMenu.js
index 8dea62690c..4e381643ba 100644
--- a/src/components/views/context_menus/TagTileContextMenu.js
+++ b/src/components/views/context_menus/TagTileContextMenu.js
@@ -23,45 +23,70 @@ import TagOrderActions from '../../../actions/TagOrderActions';
import {MenuItem} from "../../structures/ContextMenu";
import MatrixClientContext from "../../../contexts/MatrixClientContext";
import {replaceableComponent} from "../../../utils/replaceableComponent";
+import GroupFilterOrderStore from "../../../stores/GroupFilterOrderStore";
@replaceableComponent("views.context_menus.TagTileContextMenu")
export default class TagTileContextMenu extends React.Component {
static propTypes = {
tag: PropTypes.string.isRequired,
+ index: PropTypes.number.isRequired,
/* callback called when the menu is dismissed */
onFinished: PropTypes.func.isRequired,
};
static contextType = MatrixClientContext;
- constructor() {
- super();
-
- this._onViewCommunityClick = this._onViewCommunityClick.bind(this);
- this._onRemoveClick = this._onRemoveClick.bind(this);
- }
-
- _onViewCommunityClick() {
+ _onViewCommunityClick = () => {
dis.dispatch({
action: 'view_group',
group_id: this.props.tag,
});
this.props.onFinished();
- }
+ };
- _onRemoveClick() {
+ _onRemoveClick = () => {
dis.dispatch(TagOrderActions.removeTag(this.context, this.props.tag));
this.props.onFinished();
- }
+ };
+
+ _onMoveUp = () => {
+ dis.dispatch(TagOrderActions.moveTag(this.context, this.props.tag, this.props.index - 1));
+ this.props.onFinished();
+ };
+
+ _onMoveDown = () => {
+ dis.dispatch(TagOrderActions.moveTag(this.context, this.props.tag, this.props.index + 1));
+ this.props.onFinished();
+ };
render() {
+ let moveUp;
+ let moveDown;
+ if (this.props.index > 0) {
+ moveUp = (
+
+ { _t("Move up") }
+
+ );
+ }
+ if (this.props.index < (GroupFilterOrderStore.getOrderedTags() || []).length - 1) {
+ moveDown = (
+
+ { _t("Move down") }
+
+ );
+ }
+
return
{ _t('View Community') }
+ { (moveUp || moveDown) ?
: null }
+ { moveUp }
+ { moveDown }
- { _t('Hide') }
+ { _t("Unpin") }
;
}
diff --git a/src/components/views/elements/DNDTagTile.js b/src/components/views/elements/DNDTagTile.js
index eaaa0f183b..2e88d37882 100644
--- a/src/components/views/elements/DNDTagTile.js
+++ b/src/components/views/elements/DNDTagTile.js
@@ -30,7 +30,7 @@ export default function DNDTagTile(props) {
const TagTileContextMenu = sdk.getComponent('context_menus.TagTileContextMenu');
contextMenu = (
-
+
);
}
diff --git a/src/components/views/groups/GroupTile.js b/src/components/views/groups/GroupTile.js
index ce42662462..dd8366bbe0 100644
--- a/src/components/views/groups/GroupTile.js
+++ b/src/components/views/groups/GroupTile.js
@@ -22,6 +22,9 @@ import FlairStore from '../../../stores/FlairStore';
import MatrixClientContext from "../../../contexts/MatrixClientContext";
import {replaceableComponent} from "../../../utils/replaceableComponent";
import {mediaFromMxc} from "../../../customisations/Media";
+import { _t } from "../../../languageHandler";
+import TagOrderActions from "../../../actions/TagOrderActions";
+import GroupFilterOrderStore from "../../../stores/GroupFilterOrderStore";
@replaceableComponent("views.groups.GroupTile")
class GroupTile extends React.Component {
@@ -60,6 +63,18 @@ class GroupTile extends React.Component {
});
};
+ onPinClick = e => {
+ e.preventDefault();
+ e.stopPropagation();
+ dis.dispatch(TagOrderActions.moveTag(this.context, this.props.groupId, 0));
+ };
+
+ onUnpinClick = e => {
+ e.preventDefault();
+ e.stopPropagation();
+ dis.dispatch(TagOrderActions.removeTag(this.context, this.props.groupId));
+ };
+
render() {
const BaseAvatar = sdk.getComponent('avatars.BaseAvatar');
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
@@ -90,6 +105,14 @@ class GroupTile extends React.Component {
{ name }
{ descElement }
{ this.props.groupId }
+ { !(GroupFilterOrderStore.getOrderedTags() || []).includes(this.props.groupId)
+ ?
+ { _t("Pin") }
+
+ :
+ { _t("Unpin") }
+
+ }
;
}
diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json
index 3d6fcb8643..85647a17e5 100644
--- a/src/i18n/strings/en_EN.json
+++ b/src/i18n/strings/en_EN.json
@@ -2477,6 +2477,8 @@
"Update status": "Update status",
"Set status": "Set status",
"Set a new status...": "Set a new status...",
+ "Move up": "Move up",
+ "Move down": "Move down",
"View Community": "View Community",
"Unable to start audio streaming.": "Unable to start audio streaming.",
"Failed to start livestream": "Failed to start livestream",
@@ -2623,7 +2625,7 @@
"%(count)s messages deleted.|one": "%(count)s message deleted.",
"Your Communities": "Your Communities",
"Did you know: you can use communities to filter your %(brand)s experience!": "Did you know: you can use communities to filter your %(brand)s experience!",
- "To set up a filter, drag a community avatar over to the filter panel on the far left hand side of the screen. You can click on an avatar in the filter panel at any time to see only the rooms and people associated with that community.": "To set up a filter, drag a community avatar over to the filter panel on the far left hand side of the screen. You can click on an avatar in the filter panel at any time to see only the rooms and people associated with that community.",
+ "You can click on an avatar in the filter panel at any time to see only the rooms and people associated with that community.": "You can click on an avatar in the filter panel at any time to see only the rooms and people associated with that community.",
"Error whilst fetching joined communities": "Error whilst fetching joined communities",
"Create a new community": "Create a new community",
"Create a community to group together users and rooms! Build a custom homepage to mark out your space in the Matrix universe.": "Create a community to group together users and rooms! Build a custom homepage to mark out your space in the Matrix universe.",
From 35948374e91d4cf6e3110de4218d043be9ff4db0 Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Wed, 2 Jun 2021 11:56:49 +0100
Subject: [PATCH 012/164] remove unused imports
---
src/components/structures/LoggedInView.tsx | 2 --
1 file changed, 2 deletions(-)
diff --git a/src/components/structures/LoggedInView.tsx b/src/components/structures/LoggedInView.tsx
index f5df99d8c9..388616c55e 100644
--- a/src/components/structures/LoggedInView.tsx
+++ b/src/components/structures/LoggedInView.tsx
@@ -29,8 +29,6 @@ import dis from '../../dispatcher/dispatcher';
import { IMatrixClientCreds } from '../../MatrixClientPeg';
import SettingsStore from "../../settings/SettingsStore";
-import TagOrderActions from '../../actions/TagOrderActions';
-import RoomListActions from '../../actions/RoomListActions';
import ResizeHandle from '../views/elements/ResizeHandle';
import {Resizer, CollapseDistributor} from '../../resizer';
import MatrixClientContext from "../../contexts/MatrixClientContext";
From 079a5c10ad8191d6d3c357400c948253b0738997 Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Wed, 2 Jun 2021 16:43:38 +0100
Subject: [PATCH 013/164] Respect space ordering field in m.tag for top level
spaces
---
.../structures/SpaceRoomDirectory.tsx | 4 +--
src/stores/SpaceStore.tsx | 33 ++++++++++++++++---
2 files changed, 31 insertions(+), 6 deletions(-)
diff --git a/src/components/structures/SpaceRoomDirectory.tsx b/src/components/structures/SpaceRoomDirectory.tsx
index 8d59fe6c68..2b4fb24c1b 100644
--- a/src/components/structures/SpaceRoomDirectory.tsx
+++ b/src/components/structures/SpaceRoomDirectory.tsx
@@ -39,7 +39,7 @@ import {mediaFromMxc} from "../../customisations/Media";
import InfoTooltip from "../views/elements/InfoTooltip";
import TextWithTooltip from "../views/elements/TextWithTooltip";
import {useStateToggle} from "../../hooks/useStateToggle";
-import {getOrder} from "../../stores/SpaceStore";
+import {getChildOrder} from "../../stores/SpaceStore";
import AccessibleTooltipButton from "../views/elements/AccessibleTooltipButton";
import {linkifyElement} from "../../HtmlUtils";
@@ -286,7 +286,7 @@ export const HierarchyLevel = ({
const children = Array.from(relations.get(spaceId)?.values() || []);
const sortedChildren = sortBy(children, ev => {
// XXX: Space Summary API doesn't give the child origin_server_ts but once it does we should use it for sorting
- return getOrder(ev.content.order, null, ev.state_key);
+ return getChildOrder(ev.content.order, null, ev.state_key);
});
const [subspaces, childRooms] = sortedChildren.reduce((result, ev: ISpaceSummaryEvent) => {
const roomId = ev.state_key;
diff --git a/src/stores/SpaceStore.tsx b/src/stores/SpaceStore.tsx
index 40997d30a8..1333fc5d37 100644
--- a/src/stores/SpaceStore.tsx
+++ b/src/stores/SpaceStore.tsx
@@ -33,6 +33,7 @@ import {EnhancedMap, mapDiff} from "../utils/maps";
import {setHasDiff} from "../utils/sets";
import {ISpaceSummaryEvent, ISpaceSummaryRoom} from "../components/structures/SpaceRoomDirectory";
import RoomViewStore from "./RoomViewStore";
+import { arrayHasOrderChange } from "../utils/arrays";
interface IState {}
@@ -60,8 +61,16 @@ const partitionSpacesAndRooms = (arr: Room[]): [Room[], Room[]] => { // [spaces,
}, [[], []]);
};
+const SpaceTagOrderingField = "org.matrix.mscXXXX.space";
+
+const getSpaceTagOrdering = (space: Room): number | undefined => {
+ return space?.getAccountData(EventType.Tag)?.getContent()?.tags?.[SpaceTagOrderingField]?.order;
+};
+
+const sortRootSpaces = (spaces: Room[]): Room[] => sortBy(spaces, [getSpaceTagOrdering, "roomId"]);
+
// For sorting space children using a validated `order`, `m.room.create`'s `origin_server_ts`, `room_id`
-export const getOrder = (order: string, creationTs: number, roomId: string): Array>> => {
+export const getChildOrder = (order: string, creationTs: number, roomId: string): Array>> => {
let validatedOrder: string = null;
if (typeof order === "string" && Array.from(order).every((c: string) => {
@@ -214,7 +223,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient {
const roomId = ev.getStateKey();
const childRoom = this.matrixClient?.getRoom(roomId);
const createTs = childRoom?.currentState.getStateEvents(EventType.RoomCreate, "")?.getTs();
- return getOrder(ev.getContent().order, createTs, roomId);
+ return getChildOrder(ev.getContent().order, createTs, roomId);
}).map(ev => {
return this.matrixClient.getRoom(ev.getStateKey());
}).filter(room => {
@@ -326,7 +335,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient {
// rootSpaces.push(space);
// });
- this.rootSpaces = rootSpaces;
+ this.rootSpaces = sortRootSpaces(rootSpaces);
this.parentMap = backrefs;
// if the currently selected space no longer exists, remove its selection
@@ -338,7 +347,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient {
this.emit(UPDATE_TOP_LEVEL_SPACES, this.spacePanelSpaces);
// build initial state of invited spaces as we would have missed the emitted events about the room at launch
- this._invitedSpaces = new Set(invitedSpaces);
+ this._invitedSpaces = new Set(sortRootSpaces(invitedSpaces));
this.emit(UPDATE_INVITED_SPACES, this.invitedSpaces);
}, 100, {trailing: true, leading: true});
@@ -472,6 +481,20 @@ export class SpaceStoreClass extends AsyncStoreWithClient {
}
};
+ private onRoomAccountData = (ev: MatrixEvent, room: Room, lastEv?: MatrixEvent) => {
+ if (!room.isSpaceRoom() || ev.getType() !== EventType.Tag) return;
+
+ const order = ev.getContent()?.tags?.[SpaceTagOrderingField]?.order;
+ const lastOrder = lastEv?.getContent()?.tags?.[SpaceTagOrderingField]?.order;
+ if (order !== lastOrder) {
+ const rootSpaces = sortRootSpaces(this.rootSpaces);
+ if (arrayHasOrderChange(this.rootSpaces, rootSpaces)) {
+ this.rootSpaces = rootSpaces;
+ this.emit(UPDATE_TOP_LEVEL_SPACES, this.spacePanelSpaces);
+ }
+ }
+ };
+
private onRoomState = (ev: MatrixEvent) => {
const room = this.matrixClient.getRoom(ev.getRoomId());
if (!room) return;
@@ -516,6 +539,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient {
if (this.matrixClient) {
this.matrixClient.removeListener("Room", this.onRoom);
this.matrixClient.removeListener("Room.myMembership", this.onRoom);
+ this.matrixClient.removeListener("Room.accountData", this.onRoomAccountData);
this.matrixClient.removeListener("RoomState.events", this.onRoomState);
}
await this.reset();
@@ -525,6 +549,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient {
if (!SettingsStore.getValue("feature_spaces")) return;
this.matrixClient.on("Room", this.onRoom);
this.matrixClient.on("Room.myMembership", this.onRoom);
+ this.matrixClient.on("Room.accountData", this.onRoomAccountData);
this.matrixClient.on("RoomState.events", this.onRoomState);
await this.onSpaceUpdate(); // trigger an initial update
From 3f12b7280d801ac505caec71d2a2c095ab68d3c9 Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Thu, 3 Jun 2021 08:31:06 +0100
Subject: [PATCH 014/164] Make AutoHideScrollbar pass through all unknown props
---
.../structures/AutoHideScrollbar.tsx | 18 +++++++++++-------
.../structures/IndicatorScrollbar.js | 11 +++++++----
2 files changed, 18 insertions(+), 11 deletions(-)
diff --git a/src/components/structures/AutoHideScrollbar.tsx b/src/components/structures/AutoHideScrollbar.tsx
index 66f998b616..e5fa124fed 100644
--- a/src/components/structures/AutoHideScrollbar.tsx
+++ b/src/components/structures/AutoHideScrollbar.tsx
@@ -15,9 +15,9 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
-import React from "react";
+import React, {HTMLAttributes} from "react";
-interface IProps {
+interface IProps extends HTMLAttributes {
className?: string;
onScroll?: () => void;
onWheel?: () => void;
@@ -52,14 +52,18 @@ export default class AutoHideScrollbar extends React.Component {
}
public render() {
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
+ const { className, onScroll, onWheel, style, tabIndex, wrappedRef, children, ...otherProps } = this.props;
+
return (
- { this.props.children }
+ { children }
);
}
}
diff --git a/src/components/structures/IndicatorScrollbar.js b/src/components/structures/IndicatorScrollbar.js
index 51a3b287f0..25dcaeed39 100644
--- a/src/components/structures/IndicatorScrollbar.js
+++ b/src/components/structures/IndicatorScrollbar.js
@@ -185,21 +185,24 @@ export default class IndicatorScrollbar extends React.Component {
};
render() {
+ // eslint-disable-next-line no-unused-vars
+ const { children, trackHorizontalOverflow, verticalScrollsHorizontally, ...otherProps } = this.props;
+
const leftIndicatorStyle = {left: this.state.leftIndicatorOffset};
const rightIndicatorStyle = {right: this.state.rightIndicatorOffset};
- const leftOverflowIndicator = this.props.trackHorizontalOverflow
+ const leftOverflowIndicator = trackHorizontalOverflow
?
: null;
- const rightOverflowIndicator = this.props.trackHorizontalOverflow
+ const rightOverflowIndicator = trackHorizontalOverflow
?
: null;
return (
{ leftOverflowIndicator }
- { this.props.children }
+ { children }
{ rightOverflowIndicator }
);
}
From e334ce81920723832c9260b0f009df88805e22a1 Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Thu, 3 Jun 2021 08:32:36 +0100
Subject: [PATCH 015/164] First cut of space panel drag-and-drop ordering
---
package.json | 2 +
res/css/structures/_SpacePanel.scss | 7 +-
src/components/views/spaces/SpacePanel.tsx | 136 +++++++++++-------
.../views/spaces/SpaceTreeLevel.tsx | 27 ++--
src/stores/SpaceStore.tsx | 71 +++++++--
yarn.lock | 100 ++++++++++++-
6 files changed, 263 insertions(+), 80 deletions(-)
diff --git a/package.json b/package.json
index 270c86ddba..2d2506e1df 100644
--- a/package.json
+++ b/package.json
@@ -91,6 +91,7 @@
"qs": "^6.9.6",
"re-resizable": "^6.9.0",
"react": "^16.14.0",
+ "react-beautiful-dnd": "^13.1.0",
"react-dom": "^16.14.0",
"react-focus-lock": "^2.5.0",
"react-transition-group": "^4.4.1",
@@ -135,6 +136,7 @@
"@types/parse5": "^6.0.0",
"@types/qrcode": "^1.3.5",
"@types/react": "^16.9",
+ "@types/react-beautiful-dnd": "^13.0.0",
"@types/react-dom": "^16.9.10",
"@types/react-transition-group": "^4.4.0",
"@types/sanitize-html": "^2.3.1",
diff --git a/res/css/structures/_SpacePanel.scss b/res/css/structures/_SpacePanel.scss
index c433ccf275..e64057d16c 100644
--- a/res/css/structures/_SpacePanel.scss
+++ b/res/css/structures/_SpacePanel.scss
@@ -31,7 +31,6 @@ $activeBorderColor: $secondary-fg-color;
// Create another flexbox so the Panel fills the container
display: flex;
flex-direction: column;
- overflow-y: auto;
.mx_SpacePanel_spaceTreeWrapper {
flex: 1;
@@ -69,6 +68,12 @@ $activeBorderColor: $secondary-fg-color;
cursor: pointer;
}
+ .mx_SpaceItem_dragging {
+ .mx_SpaceButton_toggleCollapse {
+ visibility: hidden;
+ }
+ }
+
.mx_SpaceTreeLevel {
display: flex;
flex-direction: column;
diff --git a/src/components/views/spaces/SpacePanel.tsx b/src/components/views/spaces/SpacePanel.tsx
index eb63b21f0e..27f097e9d4 100644
--- a/src/components/views/spaces/SpacePanel.tsx
+++ b/src/components/views/spaces/SpacePanel.tsx
@@ -15,8 +15,9 @@ limitations under the License.
*/
import React, { useEffect, useState } from "react";
+import { DragDropContext, Droppable, Draggable } from "react-beautiful-dnd";
import classNames from "classnames";
-import {Room} from "matrix-js-sdk/src/models/room";
+import { Room } from "matrix-js-sdk/src/models/room";
import {_t} from "../../../languageHandler";
import RoomAvatar from "../avatars/RoomAvatar";
@@ -204,58 +205,89 @@ const SpacePanel = () => {
};
const activeSpaces = activeSpace ? [activeSpace] : [];
- const expandCollapseButtonTitle = isPanelCollapsed ? _t("Expand space panel") : _t("Collapse space panel");
- // TODO drag and drop for re-arranging order
- return
- {({onKeyDownHandler}) => (
-
-
-
-
SpaceStore.instance.setActiveSpace(null)}
- selected={!activeSpace}
- tooltip={_t("All rooms")}
- notificationState={RoomNotificationStateStore.instance.globalState}
- isNarrow={isPanelCollapsed}
+ return (
+ {
+ if (!result.destination) return; // dropped outside the list
+ SpaceStore.instance.moveRootSpace(result.source.index, result.destination.index);
+ }}>
+
+ {({onKeyDownHandler}) => (
+
+
+ {(provided, snapshot) => (
+
+
+ SpaceStore.instance.setActiveSpace(null)}
+ selected={!activeSpace}
+ tooltip={_t("All rooms")}
+ notificationState={RoomNotificationStateStore.instance.globalState}
+ isNarrow={isPanelCollapsed}
+ />
+ { invites.map(s => (
+ setPanelCollapsed(false)}
+ />
+ )) }
+ { spaces.map((s, i) => (
+
+ {(provided, snapshot) => (
+ setPanelCollapsed(false)}
+ />
+ )}
+
+ )) }
+ { provided.placeholder }
+
+ {
+ if (!isPanelCollapsed) setPanelCollapsed(true);
+ openMenu();
+ }}
+ isNarrow={isPanelCollapsed}
+ />
+
+ )}
+
+ setPanelCollapsed(!isPanelCollapsed)}
+ title={isPanelCollapsed ? _t("Expand space panel") : _t("Collapse space panel")}
/>
- { invites.map(s => setPanelCollapsed(false)}
- />) }
- { spaces.map(s => setPanelCollapsed(false)}
- />) }
-
- {
- if (!isPanelCollapsed) setPanelCollapsed(true);
- openMenu();
- }}
- isNarrow={isPanelCollapsed}
- />
-
- setPanelCollapsed(!isPanelCollapsed)}
- title={expandCollapseButtonTitle}
- />
- { contextMenu }
-
- )}
-
+ { contextMenu }
+
+ )}
+
+
+ );
};
export default SpacePanel;
diff --git a/src/components/views/spaces/SpaceTreeLevel.tsx b/src/components/views/spaces/SpaceTreeLevel.tsx
index f34baf256b..7ac863b239 100644
--- a/src/components/views/spaces/SpaceTreeLevel.tsx
+++ b/src/components/views/spaces/SpaceTreeLevel.tsx
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
-import React from "react";
+import React, {InputHTMLAttributes, LegacyRef} from "react";
import classNames from "classnames";
import {Room} from "matrix-js-sdk/src/models/room";
@@ -49,13 +49,14 @@ import {EventType} from "matrix-js-sdk/src/@types/event";
import {StaticNotificationState} from "../../../stores/notifications/StaticNotificationState";
import {NotificationColor} from "../../../stores/notifications/NotificationColor";
-interface IItemProps {
+interface IItemProps extends InputHTMLAttributes {
space?: Room;
activeSpaces: Room[];
isNested?: boolean;
isPanelCollapsed?: boolean;
onExpand?: Function;
parents?: Set;
+ innerRef?: LegacyRef;
}
interface IItemState {
@@ -300,18 +301,18 @@ export class SpaceItem extends React.PureComponent {
}
render() {
- const {space, activeSpaces, isNested} = this.props;
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
+ const { space, activeSpaces, isNested, isPanelCollapsed, onExpand, parents, innerRef,
+ ...otherProps } = this.props;
- const forceCollapsed = this.props.isPanelCollapsed;
- const isNarrow = this.props.isPanelCollapsed;
- const collapsed = this.state.collapsed || forceCollapsed;
+ const collapsed = this.state.collapsed || isPanelCollapsed;
const childSpaces = SpaceStore.instance.getChildSpaces(space.roomId)
- .filter(s => !this.props.parents?.has(s.roomId));
+ .filter(s => !parents?.has(s.roomId));
const isActive = activeSpaces.includes(space);
- const itemClasses = classNames({
+ const itemClasses = classNames(this.props.className, {
"mx_SpaceItem": true,
- "mx_SpaceItem_narrow": isNarrow,
+ "mx_SpaceItem_narrow": isPanelCollapsed,
"collapsed": collapsed,
"hasSubSpaces": childSpaces && childSpaces.length,
});
@@ -320,7 +321,7 @@ export class SpaceItem extends React.PureComponent {
const classes = classNames("mx_SpaceButton", {
mx_SpaceButton_active: isActive,
mx_SpaceButton_hasMenuOpen: !!this.state.contextMenuPosition,
- mx_SpaceButton_narrow: isNarrow,
+ mx_SpaceButton_narrow: isPanelCollapsed,
mx_SpaceButton_invite: isInvite,
});
const notificationState = isInvite
@@ -333,7 +334,7 @@ export class SpaceItem extends React.PureComponent {
spaces={childSpaces}
activeSpaces={activeSpaces}
isNested={true}
- parents={new Set(this.props.parents).add(this.props.space.roomId)}
+ parents={new Set(parents).add(space.roomId)}
/>;
}
@@ -353,7 +354,7 @@ export class SpaceItem extends React.PureComponent {
/> : null;
let button;
- if (isNarrow) {
+ if (isPanelCollapsed) {
button = (
{
}
return (
-
+
{ button }
{ childItems }
diff --git a/src/stores/SpaceStore.tsx b/src/stores/SpaceStore.tsx
index 1333fc5d37..9ef961ce2d 100644
--- a/src/stores/SpaceStore.tsx
+++ b/src/stores/SpaceStore.tsx
@@ -63,12 +63,6 @@ const partitionSpacesAndRooms = (arr: Room[]): [Room[], Room[]] => { // [spaces,
const SpaceTagOrderingField = "org.matrix.mscXXXX.space";
-const getSpaceTagOrdering = (space: Room): number | undefined => {
- return space?.getAccountData(EventType.Tag)?.getContent()?.tags?.[SpaceTagOrderingField]?.order;
-};
-
-const sortRootSpaces = (spaces: Room[]): Room[] => sortBy(spaces, [getSpaceTagOrdering, "roomId"]);
-
// For sorting space children using a validated `order`, `m.room.create`'s `origin_server_ts`, `room_id`
export const getChildOrder = (order: string, creationTs: number, roomId: string): Array>> => {
let validatedOrder: string = null;
@@ -104,6 +98,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient {
private _activeSpace?: Room = null;
private _suggestedRooms: ISuggestedRoom[] = [];
private _invitedSpaces = new Set();
+ private spaceOrderLocalEchoMap = new Map();
public get invitedSpaces(): Room[] {
return Array.from(this._invitedSpaces);
@@ -335,7 +330,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient {
// rootSpaces.push(space);
// });
- this.rootSpaces = sortRootSpaces(rootSpaces);
+ this.rootSpaces = this.sortRootSpaces(rootSpaces);
this.parentMap = backrefs;
// if the currently selected space no longer exists, remove its selection
@@ -347,7 +342,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient {
this.emit(UPDATE_TOP_LEVEL_SPACES, this.spacePanelSpaces);
// build initial state of invited spaces as we would have missed the emitted events about the room at launch
- this._invitedSpaces = new Set(sortRootSpaces(invitedSpaces));
+ this._invitedSpaces = new Set(this.sortRootSpaces(invitedSpaces));
this.emit(UPDATE_INVITED_SPACES, this.invitedSpaces);
}, 100, {trailing: true, leading: true});
@@ -484,17 +479,22 @@ export class SpaceStoreClass extends AsyncStoreWithClient {
private onRoomAccountData = (ev: MatrixEvent, room: Room, lastEv?: MatrixEvent) => {
if (!room.isSpaceRoom() || ev.getType() !== EventType.Tag) return;
+ this.spaceOrderLocalEchoMap.delete(room.roomId); // clear any local echo
const order = ev.getContent()?.tags?.[SpaceTagOrderingField]?.order;
const lastOrder = lastEv?.getContent()?.tags?.[SpaceTagOrderingField]?.order;
if (order !== lastOrder) {
- const rootSpaces = sortRootSpaces(this.rootSpaces);
- if (arrayHasOrderChange(this.rootSpaces, rootSpaces)) {
- this.rootSpaces = rootSpaces;
- this.emit(UPDATE_TOP_LEVEL_SPACES, this.spacePanelSpaces);
- }
+ this.notifyIfOrderChanged();
}
};
+ private notifyIfOrderChanged(): void {
+ const rootSpaces = this.sortRootSpaces(this.rootSpaces);
+ if (arrayHasOrderChange(this.rootSpaces, rootSpaces)) {
+ this.rootSpaces = rootSpaces;
+ this.emit(UPDATE_TOP_LEVEL_SPACES, this.spacePanelSpaces);
+ }
+ }
+
private onRoomState = (ev: MatrixEvent) => {
const room = this.matrixClient.getRoom(ev.getRoomId());
if (!room) return;
@@ -624,6 +624,51 @@ export class SpaceStoreClass extends AsyncStoreWithClient {
}
childSpaces.forEach(s => this.traverseSpace(s.roomId, fn, includeRooms, newPath));
}
+
+ private getSpaceTagOrdering = (space: Room): number | undefined => {
+ if (this.spaceOrderLocalEchoMap.has(space.roomId)) return this.spaceOrderLocalEchoMap.get(space.roomId);
+ return space.tags?.[SpaceTagOrderingField]?.order;
+ };
+
+ private sortRootSpaces(spaces: Room[]): Room[] {
+ return sortBy(spaces, [this.getSpaceTagOrdering, "roomId"]);
+ }
+
+ public moveRootSpace(fromIndex: number, toIndex: number): void {
+ if (
+ fromIndex < 0 || toIndex < 0 ||
+ fromIndex > this.rootSpaces.length || toIndex > this.rootSpaces.length ||
+ fromIndex === toIndex
+ ) {
+ return;
+ }
+ const space = this.rootSpaces[fromIndex];
+ const orders = this.rootSpaces.map(this.getSpaceTagOrdering);
+
+ let prevOrder = orders[toIndex - 1];
+ let nextOrder = orders[toIndex]; // accounts for downwards displacement of existing inhabitant of this index
+
+ if (prevOrder === undefined && nextOrder === undefined) {
+ // TODO WHAT A PAIN
+ }
+
+ prevOrder = prevOrder || 0.0;
+ nextOrder = nextOrder || 1.0;
+
+ if (prevOrder !== nextOrder) {
+ const order = prevOrder + ((nextOrder - prevOrder) / 2);
+ this.spaceOrderLocalEchoMap.set(space.roomId, order);
+ this.matrixClient.setRoomAccountData(space.roomId, EventType.Tag, {
+ tags: {
+ ...space.tags,
+ [SpaceTagOrderingField]: { order },
+ },
+ });
+ this.notifyIfOrderChanged();
+ } else {
+ // TODO REBUILD
+ }
+ }
}
export default class SpaceStore {
diff --git a/yarn.lock b/yarn.lock
index 2c84237730..7e24c220e5 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1024,6 +1024,13 @@
dependencies:
regenerator-runtime "^0.13.4"
+"@babel/runtime@^7.12.1", "@babel/runtime@^7.9.2":
+ version "7.14.0"
+ resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.14.0.tgz#46794bc20b612c5f75e62dd071e24dfd95f1cbe6"
+ integrity sha512-JELkvo/DlpNdJ7dlyw/eY7E0suy5i5GQH+Vlxaq1nsNJ+H7f4Vtv3jMeCEgRhZZQFXTjldYfQgv2qmM6M1v5wA==
+ dependencies:
+ regenerator-runtime "^0.13.4"
+
"@babel/template@^7.10.4", "@babel/template@^7.12.7", "@babel/template@^7.3.3":
version "7.12.7"
resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.12.7.tgz#c817233696018e39fbb6c491d2fb684e05ed43bc"
@@ -1504,6 +1511,14 @@
dependencies:
"@types/node" "*"
+"@types/hoist-non-react-statics@^3.3.0":
+ version "3.3.1"
+ resolved "https://registry.yarnpkg.com/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz#1124aafe5118cb591977aeb1ceaaed1070eb039f"
+ integrity sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==
+ dependencies:
+ "@types/react" "*"
+ hoist-non-react-statics "^3.3.0"
+
"@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0", "@types/istanbul-lib-coverage@^2.0.1":
version "2.0.3"
resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.3.tgz#4ba8ddb720221f432e443bd5f9117fd22cfd4762"
@@ -1620,6 +1635,13 @@
dependencies:
"@types/node" "*"
+"@types/react-beautiful-dnd@^13.0.0":
+ version "13.0.0"
+ resolved "https://registry.yarnpkg.com/@types/react-beautiful-dnd/-/react-beautiful-dnd-13.0.0.tgz#e60d3d965312fcf1516894af92dc3e9249587db4"
+ integrity sha512-by80tJ8aTTDXT256Gl+RfLRtFjYbUWOnZuEigJgNsJrSEGxvFe5eY6k3g4VIvf0M/6+xoLgfYWoWonlOo6Wqdg==
+ dependencies:
+ "@types/react" "*"
+
"@types/react-dom@^16.9.10":
version "16.9.10"
resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-16.9.10.tgz#4485b0bec3d41f856181b717f45fd7831101156f"
@@ -1627,6 +1649,16 @@
dependencies:
"@types/react" "^16"
+"@types/react-redux@^7.1.16":
+ version "7.1.16"
+ resolved "https://registry.yarnpkg.com/@types/react-redux/-/react-redux-7.1.16.tgz#0fbd04c2500c12105494c83d4a3e45c084e3cb21"
+ integrity sha512-f/FKzIrZwZk7YEO9E1yoxIuDNRiDducxkFlkw/GNMGEnK9n4K8wJzlJBghpSuOVDgEUHoDkDF7Gi9lHNQR4siw==
+ dependencies:
+ "@types/hoist-non-react-statics" "^3.3.0"
+ "@types/react" "*"
+ hoist-non-react-statics "^3.3.0"
+ redux "^4.0.0"
+
"@types/react-transition-group@^4.4.0":
version "4.4.0"
resolved "https://registry.yarnpkg.com/@types/react-transition-group/-/react-transition-group-4.4.0.tgz#882839db465df1320e4753e6e9f70ca7e9b4d46d"
@@ -2696,6 +2728,13 @@ cross-spawn@^7.0.0, cross-spawn@^7.0.2:
shebang-command "^2.0.0"
which "^2.0.1"
+css-box-model@^1.2.0:
+ version "1.2.1"
+ resolved "https://registry.yarnpkg.com/css-box-model/-/css-box-model-1.2.1.tgz#59951d3b81fd6b2074a62d49444415b0d2b4d7c1"
+ integrity sha512-a7Vr4Q/kd/aw96bnJG332W9V9LkJO69JRcaCYDUqjp6/z0w6VcZjgAcTbgFxEPfBgdnAwlh3iwu+hLopa+flJw==
+ dependencies:
+ tiny-invariant "^1.0.6"
+
css-select@^4.1.2:
version "4.1.2"
resolved "https://registry.yarnpkg.com/css-select/-/css-select-4.1.2.tgz#8b52b6714ed3a80d8221ec971c543f3b12653286"
@@ -4202,6 +4241,13 @@ highlight.js@^10.5.0:
resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-10.5.0.tgz#3f09fede6a865757378f2d9ebdcbc15ba268f98f"
integrity sha512-xTmvd9HiIHR6L53TMC7TKolEj65zG1XU+Onr8oi86mYa+nLcIbxTTWkpW7CsEwv/vK7u1zb8alZIMLDqqN6KTw==
+hoist-non-react-statics@^3.3.0, hoist-non-react-statics@^3.3.2:
+ version "3.3.2"
+ resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45"
+ integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==
+ dependencies:
+ react-is "^16.7.0"
+
hosted-git-info@^2.1.4:
version "2.8.9"
resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.9.tgz#dffc0bf9a21c02209090f2aa69429e1414daf3f9"
@@ -5717,6 +5763,11 @@ mdurl@~1.0.1:
resolved "https://registry.yarnpkg.com/mdurl/-/mdurl-1.0.1.tgz#fe85b2ec75a59037f2adfec100fd6c601761152e"
integrity sha1-/oWy7HWlkDfyrf7BAP1sYBdhFS4=
+memoize-one@^5.1.1:
+ version "5.2.1"
+ resolved "https://registry.yarnpkg.com/memoize-one/-/memoize-one-5.2.1.tgz#8337aa3c4335581839ec01c3d594090cebe8f00e"
+ integrity sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==
+
meow@^9.0.0:
version "9.0.0"
resolved "https://registry.yarnpkg.com/meow/-/meow-9.0.0.tgz#cd9510bc5cac9dee7d03c73ee1f9ad959f4ea364"
@@ -6632,6 +6683,11 @@ quick-lru@^4.0.1:
resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-4.0.1.tgz#5b8878f113a58217848c6482026c73e1ba57727f"
integrity sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==
+raf-schd@^4.0.2:
+ version "4.0.3"
+ resolved "https://registry.yarnpkg.com/raf-schd/-/raf-schd-4.0.3.tgz#5d6c34ef46f8b2a0e880a8fcdb743efc5bfdbc1a"
+ integrity sha512-tQkJl2GRWh83ui2DiPTJz9wEiMN20syf+5oKfB03yYP7ioZcJwsIK8FjrtLwH1m7C7e+Tt2yYBlrOpdT+dyeIQ==
+
raf@^3.4.1:
version "3.4.1"
resolved "https://registry.yarnpkg.com/raf/-/raf-3.4.1.tgz#0742e99a4a6552f445d73e3ee0328af0ff1ede39"
@@ -6659,6 +6715,19 @@ re-resizable@^6.9.0:
dependencies:
fast-memoize "^2.5.1"
+react-beautiful-dnd@^13.1.0:
+ version "13.1.0"
+ resolved "https://registry.yarnpkg.com/react-beautiful-dnd/-/react-beautiful-dnd-13.1.0.tgz#ec97c81093593526454b0de69852ae433783844d"
+ integrity sha512-aGvblPZTJowOWUNiwd6tNfEpgkX5OxmpqxHKNW/4VmvZTNTbeiq7bA3bn5T+QSF2uibXB0D1DmJsb1aC/+3cUA==
+ dependencies:
+ "@babel/runtime" "^7.9.2"
+ css-box-model "^1.2.0"
+ memoize-one "^5.1.1"
+ raf-schd "^4.0.2"
+ react-redux "^7.2.0"
+ redux "^4.0.4"
+ use-memo-one "^1.1.1"
+
react-clientside-effect@^1.2.2:
version "1.2.3"
resolved "https://registry.yarnpkg.com/react-clientside-effect/-/react-clientside-effect-1.2.3.tgz#95c95f520addfb71743608b990bfe01eb002012b"
@@ -6688,7 +6757,7 @@ react-focus-lock@^2.5.0:
use-callback-ref "^1.2.1"
use-sidecar "^1.0.1"
-react-is@^16.13.1, react-is@^16.8.1, react-is@^16.8.6:
+react-is@^16.13.1, react-is@^16.7.0, react-is@^16.8.1, react-is@^16.8.6:
version "16.13.1"
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
@@ -6698,6 +6767,18 @@ react-is@^17.0.1:
resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.1.tgz#5b3531bd76a645a4c9fb6e693ed36419e3301339"
integrity sha512-NAnt2iGDXohE5LI7uBnLnqvLQMtzhkiAOLXTmv+qnF9Ky7xAPcX8Up/xWIhxvLVGJvuLiNc4xQLtuqDRzb4fSA==
+react-redux@^7.2.0:
+ version "7.2.4"
+ resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-7.2.4.tgz#1ebb474032b72d806de2e0519cd07761e222e225"
+ integrity sha512-hOQ5eOSkEJEXdpIKbnRyl04LhaWabkDPV+Ix97wqQX3T3d2NQ8DUblNXXtNMavc7DpswyQM6xfaN4HQDKNY2JA==
+ dependencies:
+ "@babel/runtime" "^7.12.1"
+ "@types/react-redux" "^7.1.16"
+ hoist-non-react-statics "^3.3.2"
+ loose-envify "^1.4.0"
+ prop-types "^15.7.2"
+ react-is "^16.13.1"
+
react-test-renderer@^16.0.0-0, react-test-renderer@^16.14.0:
version "16.14.0"
resolved "https://registry.yarnpkg.com/react-test-renderer/-/react-test-renderer-16.14.0.tgz#e98360087348e260c56d4fe2315e970480c228ae"
@@ -6818,6 +6899,13 @@ redent@^3.0.0:
indent-string "^4.0.0"
strip-indent "^3.0.0"
+redux@^4.0.0, redux@^4.0.4:
+ version "4.1.0"
+ resolved "https://registry.yarnpkg.com/redux/-/redux-4.1.0.tgz#eb049679f2f523c379f1aff345c8612f294c88d4"
+ integrity sha512-uI2dQN43zqLWCt6B/BMGRMY6db7TTY4qeHHfGeKb3EOhmOKjU3KdWvNLJyqaHRksv/ErdNH7cFZWg9jXtewy4g==
+ dependencies:
+ "@babel/runtime" "^7.9.2"
+
reflect.ownkeys@^0.2.0:
version "0.2.0"
resolved "https://registry.yarnpkg.com/reflect.ownkeys/-/reflect.ownkeys-0.2.0.tgz#749aceec7f3fdf8b63f927a04809e90c5c0b3460"
@@ -7765,6 +7853,11 @@ through@^2.3.6:
resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5"
integrity sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=
+tiny-invariant@^1.0.6:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.1.0.tgz#634c5f8efdc27714b7f386c35e6760991d230875"
+ integrity sha512-ytxQvrb1cPc9WBEI/HSeYYoGD0kWnGEOR8RY6KomWLBVhqz0RgTwVO9dLrGz7dC+nN9llyI7OKAgRq8Vq4ZBSw==
+
tmatch@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/tmatch/-/tmatch-2.0.1.tgz#0c56246f33f30da1b8d3d72895abaf16660f38cf"
@@ -8070,6 +8163,11 @@ use-callback-ref@^1.2.1:
resolved "https://registry.yarnpkg.com/use-callback-ref/-/use-callback-ref-1.2.5.tgz#6115ed242cfbaed5915499c0a9842ca2912f38a5"
integrity sha512-gN3vgMISAgacF7sqsLPByqoePooY3n2emTH59Ur5d/M8eg4WTWu1xp8i8DHjohftIyEx0S08RiYxbffr4j8Peg==
+use-memo-one@^1.1.1:
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/use-memo-one/-/use-memo-one-1.1.2.tgz#0c8203a329f76e040047a35a1197defe342fab20"
+ integrity sha512-u2qFKtxLsia/r8qG0ZKkbytbztzRb317XCkT7yP8wxL0tZ/CzK2G+WWie5vWvpyeP7+YoPIwbJoIHJ4Ba4k0oQ==
+
use-sidecar@^1.0.1:
version "1.0.4"
resolved "https://registry.yarnpkg.com/use-sidecar/-/use-sidecar-1.0.4.tgz#38398c3723727f9f924bed2343dfa3db6aaaee46"
From dbaa394d65c640581f3ef89f52aba13e4801cc3a Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Thu, 3 Jun 2021 08:54:30 +0100
Subject: [PATCH 016/164] i18n
---
src/i18n/strings/en_EN.json | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json
index 85647a17e5..2a5297122f 100644
--- a/src/i18n/strings/en_EN.json
+++ b/src/i18n/strings/en_EN.json
@@ -1018,9 +1018,9 @@
"You can change these anytime.": "You can change these anytime.",
"Creating...": "Creating...",
"Create": "Create",
+ "All rooms": "All rooms",
"Expand space panel": "Expand space panel",
"Collapse space panel": "Collapse space panel",
- "All rooms": "All rooms",
"Click to copy": "Click to copy",
"Copied!": "Copied!",
"Failed to copy": "Failed to copy",
From 43921500d3459896a8a220c870a2b46d49d34303 Mon Sep 17 00:00:00 2001
From: Robin Townsend
Date: Sat, 5 Jun 2021 22:21:10 -0400
Subject: [PATCH 017/164] Revert "Match requested avatar size to displayed
size"
This reverts commit 44b143c8c3063be7ca2bf24e6cfdb81be9351c75.
---
src/components/views/elements/EventTilePreview.tsx | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/components/views/elements/EventTilePreview.tsx b/src/components/views/elements/EventTilePreview.tsx
index 6d2ea687de..77db94b5dd 100644
--- a/src/components/views/elements/EventTilePreview.tsx
+++ b/src/components/views/elements/EventTilePreview.tsx
@@ -61,7 +61,7 @@ interface IState {
message: string;
}
-const AVATAR_SIZE = 30;
+const AVATAR_SIZE = 32;
@replaceableComponent("views.elements.EventTilePreview")
export default class EventTilePreview extends React.Component {
From b2b95257a8571edde2cf1a7ed110eb1a65652252 Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Mon, 7 Jun 2021 08:54:41 +0100
Subject: [PATCH 018/164] Convert RoomAliasField to Typescript
---
src/components/views/elements/Field.tsx | 7 +-
.../{RoomAliasField.js => RoomAliasField.tsx} | 75 ++++++++++---------
src/i18n/strings/en_EN.json | 2 +-
3 files changed, 48 insertions(+), 36 deletions(-)
rename src/components/views/elements/{RoomAliasField.js => RoomAliasField.tsx} (67%)
diff --git a/src/components/views/elements/Field.tsx b/src/components/views/elements/Field.tsx
index 59d9a11596..1373c2df0e 100644
--- a/src/components/views/elements/Field.tsx
+++ b/src/components/views/elements/Field.tsx
@@ -29,6 +29,11 @@ function getId() {
return `${BASE_ID}_${count++}`;
}
+export interface IValidateOpts {
+ focused?: boolean;
+ allowEmpty?: boolean;
+}
+
interface IProps {
// The field's ID, which binds the input and label together. Immutable.
id?: string;
@@ -180,7 +185,7 @@ export default class Field extends React.PureComponent {
}
};
- public async validate({ focused, allowEmpty = true }: {focused?: boolean, allowEmpty?: boolean}) {
+ public async validate({ focused, allowEmpty = true }: IValidateOpts) {
if (!this.props.onValidate) {
return;
}
diff --git a/src/components/views/elements/RoomAliasField.js b/src/components/views/elements/RoomAliasField.tsx
similarity index 67%
rename from src/components/views/elements/RoomAliasField.js
rename to src/components/views/elements/RoomAliasField.tsx
index 813dd8b5cc..7eff529c46 100644
--- a/src/components/views/elements/RoomAliasField.js
+++ b/src/components/views/elements/RoomAliasField.tsx
@@ -1,5 +1,5 @@
/*
-Copyright 2019 New Vector Ltd
+Copyright 2019, 2021 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -13,67 +13,74 @@ 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, { createRef } from "react";
+
import { _t } from '../../../languageHandler';
-import React from 'react';
-import PropTypes from 'prop-types';
-import * as sdk from '../../../index';
import withValidation from './Validation';
-import {MatrixClientPeg} from '../../../MatrixClientPeg';
-import {replaceableComponent} from "../../../utils/replaceableComponent";
+import { MatrixClientPeg } from '../../../MatrixClientPeg';
+import { replaceableComponent } from "../../../utils/replaceableComponent";
+import Field, { IValidateOpts } from "./Field";
+
+interface IProps {
+ domain: string;
+ value: string;
+ label?: string;
+ placeholder?: string;
+ onChange?(value: string): void;
+}
+
+interface IState {
+ isValid: boolean;
+}
// Controlled form component wrapping Field for inputting a room alias scoped to a given domain
@replaceableComponent("views.elements.RoomAliasField")
-export default class RoomAliasField extends React.PureComponent {
- static propTypes = {
- domain: PropTypes.string.isRequired,
- onChange: PropTypes.func,
- value: PropTypes.string.isRequired,
+export default class RoomAliasField extends React.PureComponent {
+ private fieldRef = createRef();
+
+ public state = {
+ isValid: true,
};
- constructor(props) {
- super(props);
- this.state = {isValid: true};
- }
-
- _asFullAlias(localpart) {
+ private asFullAlias(localpart: string): string {
return `#${localpart}:${this.props.domain}`;
}
render() {
- const Field = sdk.getComponent('views.elements.Field');
const poundSign = (# );
const aliasPostfix = ":" + this.props.domain;
const domain = ({aliasPostfix} );
const maxlength = 255 - this.props.domain.length - 2; // 2 for # and :
return (
this._fieldRef = ref}
- onValidate={this._onValidate}
- placeholder={_t("e.g. my-room")}
- onChange={this._onChange}
+ ref={this.fieldRef}
+ onValidate={this.onValidate}
+ placeholder={this.props.placeholder || _t("e.g. my-room")}
+ onChange={this.onChange}
value={this.props.value.substring(1, this.props.value.length - this.props.domain.length - 1)}
maxLength={maxlength}
/>
);
}
- _onChange = (ev) => {
+ private onChange = (ev) => {
if (this.props.onChange) {
- this.props.onChange(this._asFullAlias(ev.target.value));
+ this.props.onChange(this.asFullAlias(ev.target.value));
}
};
- _onValidate = async (fieldState) => {
- const result = await this._validationRules(fieldState);
+ private onValidate = async (fieldState) => {
+ const result = await this.validationRules(fieldState);
this.setState({isValid: result.valid});
return result;
};
- _validationRules = withValidation({
+ private validationRules = withValidation({
rules: [
{
key: "safeLocalpart",
@@ -81,7 +88,7 @@ export default class RoomAliasField extends React.PureComponent {
if (!value) {
return true;
}
- const fullAlias = this._asFullAlias(value);
+ const fullAlias = this.asFullAlias(value);
// XXX: FIXME https://github.com/matrix-org/matrix-doc/issues/668
return !value.includes("#") && !value.includes(":") && !value.includes(",") &&
encodeURI(fullAlias) === fullAlias;
@@ -90,7 +97,7 @@ export default class RoomAliasField extends React.PureComponent {
}, {
key: "required",
test: async ({ value, allowEmpty }) => allowEmpty || !!value,
- invalid: () => _t("Please provide a room address"),
+ invalid: () => _t("Please provide an address"),
}, {
key: "taken",
final: true,
@@ -100,7 +107,7 @@ export default class RoomAliasField extends React.PureComponent {
}
const client = MatrixClientPeg.get();
try {
- await client.getRoomIdForAlias(this._asFullAlias(value));
+ await client.getRoomIdForAlias(this.asFullAlias(value));
// we got a room id, so the alias is taken
return false;
} catch (err) {
@@ -120,11 +127,11 @@ export default class RoomAliasField extends React.PureComponent {
return this.state.isValid;
}
- validate(options) {
- return this._fieldRef.validate(options);
+ validate(options: IValidateOpts) {
+ return this.fieldRef.current?.validate(options);
}
focus() {
- this._fieldRef.focus();
+ this.fieldRef.current?.focus();
}
}
diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json
index 9e85ea28c8..02662aa508 100644
--- a/src/i18n/strings/en_EN.json
+++ b/src/i18n/strings/en_EN.json
@@ -2014,7 +2014,7 @@
"Room address": "Room address",
"e.g. my-room": "e.g. my-room",
"Some characters not allowed": "Some characters not allowed",
- "Please provide a room address": "Please provide a room address",
+ "Please provide an address": "Please provide an address",
"This address is available to use": "This address is available to use",
"This address is already in use": "This address is already in use",
"Server Options": "Server Options",
From 8c34a8461ee64949b670715cafac5948bedbca57 Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Mon, 7 Jun 2021 08:57:39 +0100
Subject: [PATCH 019/164] Add way to specify address during public space
creation
---
res/css/views/spaces/_SpaceBasicSettings.scss | 2 +-
.../views/spaces/SpaceCreateMenu.tsx | 45 +++++++++++++++----
src/i18n/strings/en_EN.json | 2 +
3 files changed, 40 insertions(+), 9 deletions(-)
diff --git a/res/css/views/spaces/_SpaceBasicSettings.scss b/res/css/views/spaces/_SpaceBasicSettings.scss
index 204ccab2b7..e6e06e7181 100644
--- a/res/css/views/spaces/_SpaceBasicSettings.scss
+++ b/res/css/views/spaces/_SpaceBasicSettings.scss
@@ -16,7 +16,7 @@ limitations under the License.
.mx_SpaceBasicSettings {
.mx_Field {
- margin: 32px 0;
+ margin: 24px 0;
}
.mx_SpaceBasicSettings_avatarContainer {
diff --git a/src/components/views/spaces/SpaceCreateMenu.tsx b/src/components/views/spaces/SpaceCreateMenu.tsx
index 0ebf511018..a65b53a045 100644
--- a/src/components/views/spaces/SpaceCreateMenu.tsx
+++ b/src/components/views/spaces/SpaceCreateMenu.tsx
@@ -33,6 +33,7 @@ import {USER_LABS_TAB} from "../dialogs/UserSettingsDialog";
import Field from "../elements/Field";
import withValidation from "../elements/Validation";
import {SpaceFeedbackPrompt} from "../../structures/SpaceRoomView";
+import RoomAliasField from "../elements/RoomAliasField";
const SpaceCreateMenuType = ({ title, description, className, onClick }) => {
return (
@@ -58,6 +59,11 @@ const spaceNameValidator = withValidation({
],
});
+const nameToAlias = (name: string, domain: string): string => {
+ const localpart = name.trim().toLowerCase().replace(/\s+/g, "-").replace(/[^a-z0-9_-]+/gi, "");
+ return `#${localpart}:${domain}`;
+};
+
const SpaceCreateMenu = ({ onFinished }) => {
const cli = useContext(MatrixClientContext);
const [visibility, setVisibility] = useState(null);
@@ -65,6 +71,8 @@ const SpaceCreateMenu = ({ onFinished }) => {
const [name, setName] = useState("");
const spaceNameField = useRef();
+ const [alias, setAlias] = useState("");
+ const spaceAliasField = useRef();
const [avatar, setAvatar] = useState(null);
const [topic, setTopic] = useState("");
@@ -80,6 +88,13 @@ const SpaceCreateMenu = ({ onFinished }) => {
setBusy(false);
return;
}
+ // validate the space name alias field but do not require it
+ if (visibility === Visibility.Public && !await spaceAliasField.current.validate({ allowEmpty: true })) {
+ spaceAliasField.current.focus();
+ spaceAliasField.current.validate({ allowEmpty: true, focused: true });
+ setBusy(false);
+ return;
+ }
const initialState: IStateEvent[] = [
{
@@ -97,12 +112,6 @@ const SpaceCreateMenu = ({ onFinished }) => {
content: { url },
});
}
- if (topic) {
- initialState.push({
- type: EventType.RoomTopic,
- content: { topic },
- });
- }
try {
await createRoom({
@@ -110,7 +119,6 @@ const SpaceCreateMenu = ({ onFinished }) => {
preset: visibility === Visibility.Public ? Preset.PublicChat : Preset.PrivateChat,
name,
creation_content: {
- // Based on MSC1840
[RoomCreateTypeField]: RoomType.Space,
},
initial_state: initialState,
@@ -119,6 +127,8 @@ const SpaceCreateMenu = ({ onFinished }) => {
events_default: 100,
...Visibility.Public ? { invite: 0 } : {},
},
+ room_alias_name: alias ? alias.substr(1, alias.indexOf(":") - 1) : undefined,
+ topic,
},
spinner: false,
encryption: false,
@@ -157,6 +167,7 @@ const SpaceCreateMenu = ({ onFinished }) => {
;
} else {
+ const domain = cli.getDomain();
body =
{
label={_t("Name")}
autoFocus={true}
value={name}
- onChange={ev => setName(ev.target.value)}
+ onChange={ev => {
+ const newName = ev.target.value;
+ if (!alias || alias === nameToAlias(name, domain)) {
+ setAlias(nameToAlias(newName, domain));
+ }
+ setName(newName);
+ }}
ref={spaceNameField}
onValidate={spaceNameValidator}
disabled={busy}
/>
+ { visibility === Visibility.Public
+ ?
+ : null
+ }
+
Date: Mon, 7 Jun 2021 08:59:57 +0100
Subject: [PATCH 020/164] Stash
---
src/stores/SpaceStore.tsx | 62 +++++++++++++++++++++++++--------------
1 file changed, 40 insertions(+), 22 deletions(-)
diff --git a/src/stores/SpaceStore.tsx b/src/stores/SpaceStore.tsx
index 9ef961ce2d..5e09b617a7 100644
--- a/src/stores/SpaceStore.tsx
+++ b/src/stores/SpaceStore.tsx
@@ -61,8 +61,6 @@ const partitionSpacesAndRooms = (arr: Room[]): [Room[], Room[]] => { // [spaces,
}, [[], []]);
};
-const SpaceTagOrderingField = "org.matrix.mscXXXX.space";
-
// For sorting space children using a validated `order`, `m.room.create`'s `origin_server_ts`, `room_id`
export const getChildOrder = (order: string, creationTs: number, roomId: string): Array>> => {
let validatedOrder: string = null;
@@ -98,7 +96,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient {
private _activeSpace?: Room = null;
private _suggestedRooms: ISuggestedRoom[] = [];
private _invitedSpaces = new Set();
- private spaceOrderLocalEchoMap = new Map();
+ private spaceOrderLocalEchoMap = new Map();
public get invitedSpaces(): Room[] {
return Array.from(this._invitedSpaces);
@@ -477,11 +475,11 @@ export class SpaceStoreClass extends AsyncStoreWithClient {
};
private onRoomAccountData = (ev: MatrixEvent, room: Room, lastEv?: MatrixEvent) => {
- if (!room.isSpaceRoom() || ev.getType() !== EventType.Tag) return;
+ if (!room.isSpaceRoom() || ev.getType() !== EventType.SpaceOrder) return;
this.spaceOrderLocalEchoMap.delete(room.roomId); // clear any local echo
- const order = ev.getContent()?.tags?.[SpaceTagOrderingField]?.order;
- const lastOrder = lastEv?.getContent()?.tags?.[SpaceTagOrderingField]?.order;
+ const order = ev.getContent()?.order;
+ const lastOrder = lastEv?.getContent()?.order;
if (order !== lastOrder) {
this.notifyIfOrderChanged();
}
@@ -625,15 +623,21 @@ export class SpaceStoreClass extends AsyncStoreWithClient {
childSpaces.forEach(s => this.traverseSpace(s.roomId, fn, includeRooms, newPath));
}
- private getSpaceTagOrdering = (space: Room): number | undefined => {
+ private getSpaceTagOrdering = (space: Room): string | undefined => {
if (this.spaceOrderLocalEchoMap.has(space.roomId)) return this.spaceOrderLocalEchoMap.get(space.roomId);
- return space.tags?.[SpaceTagOrderingField]?.order;
+ const order = space.getAccountData(EventType.SpaceOrder)?.getContent()?.order;
+ return typeof order === "string" ? order : undefined;
};
private sortRootSpaces(spaces: Room[]): Room[] {
return sortBy(spaces, [this.getSpaceTagOrdering, "roomId"]);
}
+ private setRootSpaceOrder(space: Room, order: string): void {
+ this.spaceOrderLocalEchoMap.set(space.roomId, order);
+ this.matrixClient.setRoomAccountData(space.roomId, EventType.SpaceOrder, { order });
+ }
+
public moveRootSpace(fromIndex: number, toIndex: number): void {
if (
fromIndex < 0 || toIndex < 0 ||
@@ -645,29 +649,43 @@ export class SpaceStoreClass extends AsyncStoreWithClient {
const space = this.rootSpaces[fromIndex];
const orders = this.rootSpaces.map(this.getSpaceTagOrdering);
- let prevOrder = orders[toIndex - 1];
- let nextOrder = orders[toIndex]; // accounts for downwards displacement of existing inhabitant of this index
+ let prevOrder: string;
+ let nextOrder: string;
- if (prevOrder === undefined && nextOrder === undefined) {
- // TODO WHAT A PAIN
+ if (toIndex > fromIndex) {
+ prevOrder = toIndex >= 0 ? orders[toIndex] : "aaaaa";
+ nextOrder = toIndex <= orders.length ? orders[toIndex + 1] : "zzzzz";
+ } else {
+ // accounts for downwards displacement of existing inhabitant of this index
+ prevOrder = toIndex > 0 ? orders[toIndex - 1] : "aaaaa";
+ nextOrder = toIndex < orders.length ? orders[toIndex] : "zzzzz";
}
+ console.log("@@ start", {fromIndex, toIndex, orders, prevOrder, nextOrder});
- prevOrder = prevOrder || 0.0;
- nextOrder = nextOrder || 1.0;
+ if (prevOrder === undefined) {
+ const firstUndefinedIndex = orders.indexOf(undefined);
+ const numUndefined = orders.length - firstUndefinedIndex;
+ const lastOrder = orders[firstUndefinedIndex - 1];
+ console.log("@@ precalc", {firstUndefinedIndex, numUndefined, lastOrder});
+ nextOrder = lastOrder + step;
+ for (let i = firstUndefinedIndex; i < toIndex; i++, nextOrder += step) {
+ console.log("@@ preset", {i, nextOrder});
+ this.setRootSpaceOrder(this.rootSpaces[i], nextOrder);
+ }
+
+ prevOrder = nextOrder;
+ nextOrder += step;
+ }
if (prevOrder !== nextOrder) {
const order = prevOrder + ((nextOrder - prevOrder) / 2);
- this.spaceOrderLocalEchoMap.set(space.roomId, order);
- this.matrixClient.setRoomAccountData(space.roomId, EventType.Tag, {
- tags: {
- ...space.tags,
- [SpaceTagOrderingField]: { order },
- },
- });
- this.notifyIfOrderChanged();
+ console.log("@@ set", {prevOrder, nextOrder, order});
+ this.setRootSpaceOrder(space, order);
} else {
// TODO REBUILD
}
+
+ this.notifyIfOrderChanged();
}
}
From a7eb09af1ebbffd5f700250dc52dd00238e847f1 Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Mon, 7 Jun 2021 15:48:55 +0100
Subject: [PATCH 021/164] Convert EditableItemList & AliasSettings to
Typescript
---
...itableItemList.js => EditableItemList.tsx} | 133 +++++++++--------
.../{AliasSettings.js => AliasSettings.tsx} | 134 ++++++++++--------
2 files changed, 146 insertions(+), 121 deletions(-)
rename src/components/views/elements/{EditableItemList.js => EditableItemList.tsx} (54%)
rename src/components/views/room_settings/{AliasSettings.js => AliasSettings.tsx} (78%)
diff --git a/src/components/views/elements/EditableItemList.js b/src/components/views/elements/EditableItemList.tsx
similarity index 54%
rename from src/components/views/elements/EditableItemList.js
rename to src/components/views/elements/EditableItemList.tsx
index d8ec5af278..89e2e1b8a0 100644
--- a/src/components/views/elements/EditableItemList.js
+++ b/src/components/views/elements/EditableItemList.tsx
@@ -1,5 +1,5 @@
/*
-Copyright 2017, 2019 New Vector Ltd.
+Copyright 2017-2021 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -14,48 +14,48 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
-import React from 'react';
-import PropTypes from 'prop-types';
-import {_t} from '../../../languageHandler';
+import React from "react";
+
+import { _t } from '../../../languageHandler';
import Field from "./Field";
import AccessibleButton from "./AccessibleButton";
-import {replaceableComponent} from "../../../utils/replaceableComponent";
+import { replaceableComponent } from "../../../utils/replaceableComponent";
-export class EditableItem extends React.Component {
- static propTypes = {
- index: PropTypes.number,
- value: PropTypes.string,
- onRemove: PropTypes.func,
+interface IItemProps {
+ index?: number;
+ value?: string;
+ onRemove?(index: number): void;
+}
+
+interface IItemState {
+ verifyRemove: boolean;
+}
+
+export class EditableItem extends React.Component {
+ public state = {
+ verifyRemove: false,
};
- constructor() {
- super();
-
- this.state = {
- verifyRemove: false,
- };
- }
-
- _onRemove = (e) => {
+ private onRemove = (e) => {
e.stopPropagation();
e.preventDefault();
- this.setState({verifyRemove: true});
+ this.setState({ verifyRemove: true });
};
- _onDontRemove = (e) => {
+ private onDontRemove = (e) => {
e.stopPropagation();
e.preventDefault();
- this.setState({verifyRemove: false});
+ this.setState({ verifyRemove: false });
};
- _onActuallyRemove = (e) => {
+ private onActuallyRemove = (e) => {
e.stopPropagation();
e.preventDefault();
if (this.props.onRemove) this.props.onRemove(this.props.index);
- this.setState({verifyRemove: false});
+ this.setState({ verifyRemove: false });
};
render() {
@@ -66,14 +66,14 @@ export class EditableItem extends React.Component {
{_t("Are you sure?")}
{_t("Yes")}
@@ -85,59 +85,68 @@ export class EditableItem extends React.Component {
return (
);
}
}
+interface IProps {
+ id: string;
+ items: string[];
+ itemsLabel?: string;
+ noItemsLabel?: string;
+ placeholder?: string;
+ newItem?: string;
+ canEdit?: boolean;
+ canRemove?: boolean;
+ suggestionsListId?: string;
+ onItemAdded?(item: string): void;
+ onItemRemoved?(index: number): void;
+ onNewItemChanged?(item: string): void;
+}
+
@replaceableComponent("views.elements.EditableItemList")
-export default class EditableItemList extends React.Component {
- static propTypes = {
- id: PropTypes.string.isRequired,
- items: PropTypes.arrayOf(PropTypes.string).isRequired,
- itemsLabel: PropTypes.string,
- noItemsLabel: PropTypes.string,
- placeholder: PropTypes.string,
- newItem: PropTypes.string,
-
- onItemAdded: PropTypes.func,
- onItemRemoved: PropTypes.func,
- onNewItemChanged: PropTypes.func,
-
- canEdit: PropTypes.bool,
- canRemove: PropTypes.bool,
- };
-
- _onItemAdded = (e) => {
+export default class EditableItemList extends React.PureComponent {
+ protected onItemAdded = (e) => {
e.stopPropagation();
e.preventDefault();
if (this.props.onItemAdded) this.props.onItemAdded(this.props.newItem);
};
- _onItemRemoved = (index) => {
+ protected onItemRemoved = (index) => {
if (this.props.onItemRemoved) this.props.onItemRemoved(index);
};
- _onNewItemChanged = (e) => {
+ protected onNewItemChanged = (e) => {
if (this.props.onNewItemChanged) this.props.onNewItemChanged(e.target.value);
};
- _renderNewItemField() {
+ protected renderNewItemField() {
return (
);
@@ -153,19 +162,21 @@ export default class EditableItemList extends React.Component {
key={item}
index={index}
value={item}
- onRemove={this._onItemRemoved}
+ onRemove={this.onItemRemoved}
/>;
});
const editableItemsSection = this.props.canRemove ? editableItems : ;
const label = this.props.items.length > 0 ? this.props.itemsLabel : this.props.noItemsLabel;
- return (
-
- { label }
+ return (
+
+
+ { label }
+
+ { editableItemsSection }
+ { this.props.canEdit ? this.renderNewItemField() :
}
- { editableItemsSection }
- { this.props.canEdit ? this._renderNewItemField() :
}
-
);
+ );
}
}
diff --git a/src/components/views/room_settings/AliasSettings.js b/src/components/views/room_settings/AliasSettings.tsx
similarity index 78%
rename from src/components/views/room_settings/AliasSettings.js
rename to src/components/views/room_settings/AliasSettings.tsx
index 80e0099ab3..d6e79c4ee9 100644
--- a/src/components/views/room_settings/AliasSettings.js
+++ b/src/components/views/room_settings/AliasSettings.tsx
@@ -1,6 +1,5 @@
/*
-Copyright 2016 OpenMarket Ltd
-Copyright 2018, 2019 New Vector Ltd
+Copyright 2016-2021 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -15,59 +14,60 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
+import React, { ChangeEvent, createRef } from "react";
+import { MatrixEvent } from "matrix-js-sdk/src/models/event";
+
import EditableItemList from "../elements/EditableItemList";
-import React, {createRef} from 'react';
-import PropTypes from 'prop-types';
-import {MatrixClientPeg} from "../../../MatrixClientPeg";
-import * as sdk from "../../../index";
+import { MatrixClientPeg } from "../../../MatrixClientPeg";
import { _t } from '../../../languageHandler';
import Field from "../elements/Field";
+import Spinner from "../elements/Spinner";
import ErrorDialog from "../dialogs/ErrorDialog";
import AccessibleButton from "../elements/AccessibleButton";
import Modal from "../../../Modal";
import RoomPublishSetting from "./RoomPublishSetting";
-import {replaceableComponent} from "../../../utils/replaceableComponent";
+import { replaceableComponent } from "../../../utils/replaceableComponent";
+import RoomAliasField from "../elements/RoomAliasField";
-class EditableAliasesList extends EditableItemList {
- constructor(props) {
- super(props);
+interface IEditableAliasesListProps {
+ domain?: string;
+}
- this._aliasField = createRef();
- }
+class EditableAliasesList extends EditableItemList
{
+ private aliasField = createRef();
- _onAliasAdded = async () => {
- await this._aliasField.current.validate({ allowEmpty: false });
+ private onAliasAdded = async () => {
+ await this.aliasField.current.validate({ allowEmpty: false });
- if (this._aliasField.current.isValid) {
+ if (this.aliasField.current.isValid) {
if (this.props.onItemAdded) this.props.onItemAdded(this.props.newItem);
return;
}
- this._aliasField.current.focus();
- this._aliasField.current.validate({ allowEmpty: false, focused: true });
+ this.aliasField.current.focus();
+ this.aliasField.current.validate({ allowEmpty: false, focused: true });
};
- _renderNewItemField() {
+ protected renderNewItemField() {
// if we don't need the RoomAliasField,
- // we don't need to overriden version of _renderNewItemField
+ // we don't need to overriden version of renderNewItemField
if (!this.props.domain) {
- return super._renderNewItemField();
+ return super.renderNewItemField();
}
- const RoomAliasField = sdk.getComponent('views.elements.RoomAliasField');
- const onChange = (alias) => this._onNewItemChanged({target: {value: alias}});
+ const onChange = (alias) => this.onNewItemChanged({target: {value: alias}});
return (
@@ -75,15 +75,27 @@ class EditableAliasesList extends EditableItemList {
}
}
-@replaceableComponent("views.room_settings.AliasSettings")
-export default class AliasSettings extends React.Component {
- static propTypes = {
- roomId: PropTypes.string.isRequired,
- canSetCanonicalAlias: PropTypes.bool.isRequired,
- canSetAliases: PropTypes.bool.isRequired,
- canonicalAliasEvent: PropTypes.object, // MatrixEvent
- };
+interface IProps {
+ roomId: string;
+ canSetCanonicalAlias: boolean;
+ canSetAliases: boolean;
+ canonicalAliasEvent?: MatrixEvent;
+ aliasEvents?: MatrixEvent[];
+}
+interface IState {
+ altAliases: string[];
+ localAliases: string[];
+ canonicalAlias?: string;
+ updatingCanonicalAlias: boolean;
+ localAliasesLoading: boolean;
+ detailsOpen: boolean;
+ newAlias?: string;
+ newAltAlias?: string;
+}
+
+@replaceableComponent("views.room_settings.AliasSettings")
+export default class AliasSettings extends React.Component {
static defaultProps = {
canSetAliases: false,
canSetCanonicalAlias: false,
@@ -122,7 +134,7 @@ export default class AliasSettings extends React.Component {
}
}
- async loadLocalAliases() {
+ private async loadLocalAliases() {
this.setState({ localAliasesLoading: true });
try {
const cli = MatrixClientPeg.get();
@@ -139,7 +151,7 @@ export default class AliasSettings extends React.Component {
}
}
- changeCanonicalAlias(alias) {
+ private changeCanonicalAlias(alias: string) {
if (!this.props.canSetCanonicalAlias) return;
const oldAlias = this.state.canonicalAlias;
@@ -170,7 +182,7 @@ export default class AliasSettings extends React.Component {
});
}
- changeAltAliases(altAliases) {
+ private changeAltAliases(altAliases: string[]) {
if (!this.props.canSetCanonicalAlias) return;
this.setState({
@@ -181,7 +193,7 @@ export default class AliasSettings extends React.Component {
const eventContent = {};
if (this.state.canonicalAlias) {
- eventContent.alias = this.state.canonicalAlias;
+ eventContent["alias"] = this.state.canonicalAlias;
}
if (altAliases) {
eventContent["alt_aliases"] = altAliases;
@@ -202,11 +214,11 @@ export default class AliasSettings extends React.Component {
});
}
- onNewAliasChanged = (value) => {
- this.setState({newAlias: value});
+ private onNewAliasChanged = (value: string) => {
+ this.setState({ newAlias: value });
};
- onLocalAliasAdded = (alias) => {
+ private onLocalAliasAdded = (alias: string) => {
if (!alias || alias.length === 0) return; // ignore attempts to create blank aliases
const localDomain = MatrixClientPeg.get().getDomain();
@@ -232,7 +244,7 @@ export default class AliasSettings extends React.Component {
});
};
- onLocalAliasDeleted = (index) => {
+ private onLocalAliasDeleted = (index: number) => {
const alias = this.state.localAliases[index];
// TODO: In future, we should probably be making sure that the alias actually belongs
// to this room. See https://github.com/vector-im/element-web/issues/7353
@@ -261,7 +273,7 @@ export default class AliasSettings extends React.Component {
});
};
- onLocalAliasesToggled = (event) => {
+ private onLocalAliasesToggled = (event: ChangeEvent) => {
// expanded
if (event.target.open) {
// if local aliases haven't been preloaded yet at component mount
@@ -269,37 +281,37 @@ export default class AliasSettings extends React.Component {
this.loadLocalAliases();
}
}
- this.setState({detailsOpen: event.target.open});
+ this.setState({ detailsOpen: event.currentTarget.open });
};
- onCanonicalAliasChange = (event) => {
+ private onCanonicalAliasChange = (event: ChangeEvent) => {
this.changeCanonicalAlias(event.target.value);
};
- onNewAltAliasChanged = (value) => {
- this.setState({newAltAlias: value});
+ private onNewAltAliasChanged = (value: string) => {
+ this.setState({ newAltAlias: value });
}
- onAltAliasAdded = (alias) => {
+ private onAltAliasAdded = (alias: string) => {
const altAliases = this.state.altAliases.slice();
if (!altAliases.some(a => a.trim() === alias.trim())) {
altAliases.push(alias.trim());
this.changeAltAliases(altAliases);
- this.setState({newAltAlias: ""});
+ this.setState({ newAltAlias: "" });
}
}
- onAltAliasDeleted = (index) => {
+ private onAltAliasDeleted = (index: number) => {
const altAliases = this.state.altAliases.slice();
altAliases.splice(index, 1);
this.changeAltAliases(altAliases);
}
- _getAliases() {
- return this.state.altAliases.concat(this._getLocalNonAltAliases());
+ private getAliases() {
+ return this.state.altAliases.concat(this.getLocalNonAltAliases());
}
- _getLocalNonAltAliases() {
+ private getLocalNonAltAliases() {
const {altAliases} = this.state;
return this.state.localAliases.filter(alias => !altAliases.includes(alias));
}
@@ -320,7 +332,7 @@ export default class AliasSettings extends React.Component {
>
{ _t('not specified') }
{
- this._getAliases().map((alias, i) => {
+ this.getAliases().map((alias, i) => {
if (alias === this.state.canonicalAlias) found = true;
return (
@@ -340,12 +352,10 @@ export default class AliasSettings extends React.Component {
let localAliasesList;
if (this.state.localAliasesLoading) {
- const Spinner = sdk.getComponent("elements.Spinner");
localAliasesList = ;
} else {
localAliasesList = (
- {this._getLocalNonAltAliases().map(alias => {
+ {this.getLocalNonAltAliases().map(alias => {
return ;
})};
- {_t("Local Addresses")}
- {_t("Set addresses for this room so users can find this room through your homeserver (%(localDomain)s)", {localDomain})}
+
+ { _t("Local Addresses") }
+
+
+ { _t("Set addresses for this room so users can find this room " +
+ "through your homeserver (%(localDomain)s)", { localDomain }) }
+
{ this.state.detailsOpen ? _t('Show less') : _t("Show more")}
- {localAliasesList}
+ { localAliasesList }
);
From 4725a9e8fa954b3858f4dd8e8d4212f6a0d61488 Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Mon, 7 Jun 2021 16:42:59 +0100
Subject: [PATCH 022/164] Extract useRoomState hook into hooks directory
---
.../views/right_panel/PinnedMessagesCard.tsx | 19 +--------
src/hooks/useRoomState.ts | 40 +++++++++++++++++++
2 files changed, 41 insertions(+), 18 deletions(-)
create mode 100644 src/hooks/useRoomState.ts
diff --git a/src/components/views/right_panel/PinnedMessagesCard.tsx b/src/components/views/right_panel/PinnedMessagesCard.tsx
index a3f1f2d9df..ad62619593 100644
--- a/src/components/views/right_panel/PinnedMessagesCard.tsx
+++ b/src/components/views/right_panel/PinnedMessagesCard.tsx
@@ -28,6 +28,7 @@ import { useEventEmitter } from "../../../hooks/useEventEmitter";
import PinningUtils from "../../../utils/PinningUtils";
import { useAsyncMemo } from "../../../hooks/useAsyncMemo";
import PinnedEventTile from "../rooms/PinnedEventTile";
+import { useRoomState } from "../../../hooks/useRoomState";
interface IProps {
room: Room;
@@ -75,24 +76,6 @@ export const useReadPinnedEvents = (room: Room): Set => {
return readPinnedEvents;
};
-const useRoomState = (room: Room, mapper: (state: RoomState) => T): T => {
- const [value, setValue] = useState(room ? mapper(room.currentState) : undefined);
-
- const update = useCallback(() => {
- if (!room) return;
- setValue(mapper(room.currentState));
- }, [room, mapper]);
-
- useEventEmitter(room?.currentState, "RoomState.events", update);
- useEffect(() => {
- update();
- return () => {
- setValue(undefined);
- };
- }, [update]);
- return value;
-};
-
const PinnedMessagesCard = ({ room, onClose }: IProps) => {
const cli = useContext(MatrixClientContext);
const canUnpin = useRoomState(room, state => state.mayClientSendStateEvent(EventType.RoomPinnedEvents, cli));
diff --git a/src/hooks/useRoomState.ts b/src/hooks/useRoomState.ts
new file mode 100644
index 0000000000..ded51c3900
--- /dev/null
+++ b/src/hooks/useRoomState.ts
@@ -0,0 +1,40 @@
+/*
+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 { useCallback, useEffect, useState } from "react";
+import { Room } from "matrix-js-sdk/src/models/room";
+import { RoomState } from "matrix-js-sdk/src/models/room-state";
+
+import { useEventEmitter } from "./useEventEmitter";
+
+// Hook to simplify watching Matrix Room state
+export const useRoomState = (room: Room, mapper: (state: RoomState) => T): T => {
+ const [value, setValue] = useState(room ? mapper(room.currentState) : undefined);
+
+ const update = useCallback(() => {
+ if (!room) return;
+ setValue(mapper(room.currentState));
+ }, [room, mapper]);
+
+ useEventEmitter(room?.currentState, "RoomState.events", update);
+ useEffect(() => {
+ update();
+ return () => {
+ setValue(undefined);
+ };
+ }, [update]);
+ return value;
+};
From 5e3ad621892786890692c987d95f74881bebe232 Mon Sep 17 00:00:00 2001
From: Robin Townsend
Date: Mon, 7 Jun 2021 19:03:04 -0400
Subject: [PATCH 023/164] Remove mysterious dot from EventTilePreviews
It was a bullet point, since EventTiles now get created as li by
default :P
Signed-off-by: Robin Townsend
---
src/components/views/elements/EventTilePreview.tsx | 1 +
1 file changed, 1 insertion(+)
diff --git a/src/components/views/elements/EventTilePreview.tsx b/src/components/views/elements/EventTilePreview.tsx
index 77db94b5dd..20d6cbaeb3 100644
--- a/src/components/views/elements/EventTilePreview.tsx
+++ b/src/components/views/elements/EventTilePreview.tsx
@@ -128,6 +128,7 @@ export default class EventTilePreview extends React.Component {
mxEvent={event}
layout={this.props.layout}
enableFlair={SettingsStore.getValue(UIFeature.Flair)}
+ as="div"
/>
;
}
From 9454a4e6c79f0ecfa5ebd91bd7af689e478ca559 Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Tue, 8 Jun 2021 16:28:02 +0100
Subject: [PATCH 024/164] Convert AdvancedRoomSettingsTab to Typescript
---
...ingsTab.js => AdvancedRoomSettingsTab.tsx} | 87 ++++++++++---------
1 file changed, 47 insertions(+), 40 deletions(-)
rename src/components/views/settings/tabs/room/{AdvancedRoomSettingsTab.js => AdvancedRoomSettingsTab.tsx} (73%)
diff --git a/src/components/views/settings/tabs/room/AdvancedRoomSettingsTab.js b/src/components/views/settings/tabs/room/AdvancedRoomSettingsTab.tsx
similarity index 73%
rename from src/components/views/settings/tabs/room/AdvancedRoomSettingsTab.js
rename to src/components/views/settings/tabs/room/AdvancedRoomSettingsTab.tsx
index 28aad65129..f587210095 100644
--- a/src/components/views/settings/tabs/room/AdvancedRoomSettingsTab.js
+++ b/src/components/views/settings/tabs/room/AdvancedRoomSettingsTab.tsx
@@ -1,5 +1,5 @@
/*
-Copyright 2019 New Vector Ltd
+Copyright 2019, 2021 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -15,68 +15,75 @@ limitations under the License.
*/
import React from 'react';
-import PropTypes from 'prop-types';
-import {_t} from "../../../../../languageHandler";
-import {MatrixClientPeg} from "../../../../../MatrixClientPeg";
-import * as sdk from "../../../../..";
+
+import { _t } from "../../../../../languageHandler";
+import { MatrixClientPeg } from "../../../../../MatrixClientPeg";
import AccessibleButton from "../../../elements/AccessibleButton";
+import RoomUpgradeDialog from "../../../dialogs/RoomUpgradeDialog";
+import DevtoolsDialog from "../../../dialogs/DevtoolsDialog";
import Modal from "../../../../../Modal";
import dis from "../../../../../dispatcher/dispatcher";
-import {replaceableComponent} from "../../../../../utils/replaceableComponent";
+import { replaceableComponent } from "../../../../../utils/replaceableComponent";
+
+interface IProps {
+ roomId: string;
+ closeSettingsFn(): void;
+}
+
+interface IRecommendedVersion {
+ version: string;
+ needsUpgrade: boolean;
+ urgent: boolean;
+}
+
+interface IState {
+ upgradeRecommendation?: IRecommendedVersion;
+ oldRoomId?: string;
+ oldEventId?: string;
+ upgraded?: boolean;
+}
@replaceableComponent("views.settings.tabs.room.AdvancedRoomSettingsTab")
-export default class AdvancedRoomSettingsTab extends React.Component {
- static propTypes = {
- roomId: PropTypes.string.isRequired,
- closeSettingsFn: PropTypes.func.isRequired,
- };
-
- constructor(props) {
- super(props);
+export default class AdvancedRoomSettingsTab extends React.Component {
+ constructor(props, context) {
+ super(props, context);
this.state = {
// This is eventually set to the value of room.getRecommendedVersion()
upgradeRecommendation: null,
};
- }
- // TODO: [REACT-WARNING] Move this to constructor
- UNSAFE_componentWillMount() { // eslint-disable-line camelcase
// we handle lack of this object gracefully later, so don't worry about it failing here.
const room = MatrixClientPeg.get().getRoom(this.props.roomId);
room.getRecommendedVersion().then((v) => {
const tombstone = room.currentState.getStateEvents("m.room.tombstone", "");
- const additionalStateChanges = {};
+ const additionalStateChanges: Partial = {};
const createEvent = room.currentState.getStateEvents("m.room.create", "");
const predecessor = createEvent ? createEvent.getContent().predecessor : null;
if (predecessor && predecessor.room_id) {
- additionalStateChanges['oldRoomId'] = predecessor.room_id;
- additionalStateChanges['oldEventId'] = predecessor.event_id;
- additionalStateChanges['hasPreviousRoom'] = true;
+ additionalStateChanges.oldRoomId = predecessor.room_id;
+ additionalStateChanges.oldEventId = predecessor.event_id;
}
-
this.setState({
- upgraded: tombstone && tombstone.getContent().replacement_room,
+ upgraded: !!tombstone?.getContent().replacement_room,
upgradeRecommendation: v,
...additionalStateChanges,
});
});
}
- _upgradeRoom = (e) => {
- const RoomUpgradeDialog = sdk.getComponent('dialogs.RoomUpgradeDialog');
+ private upgradeRoom = (e) => {
const room = MatrixClientPeg.get().getRoom(this.props.roomId);
Modal.createTrackedDialog('Upgrade Room Version', '', RoomUpgradeDialog, {room: room});
};
- _openDevtools = (e) => {
- const DevtoolsDialog = sdk.getComponent('dialogs.DevtoolsDialog');
+ private openDevtools = (e) => {
Modal.createDialog(DevtoolsDialog, {roomId: this.props.roomId});
};
- _onOldRoomClicked = (e) => {
+ private onOldRoomClicked = (e) => {
e.preventDefault();
e.stopPropagation();
@@ -113,7 +120,7 @@ export default class AdvancedRoomSettingsTab extends React.Component {
},
)}
-
+
{_t("Upgrade this room to the recommended room version")}
@@ -121,13 +128,13 @@ export default class AdvancedRoomSettingsTab extends React.Component {
}
let oldRoomLink;
- if (this.state.hasPreviousRoom) {
+ if (this.state.oldRoomId) {
let name = _t("this room");
const room = MatrixClientPeg.get().getRoom(this.props.roomId);
if (room && room.name) name = room.name;
oldRoomLink = (
-
- {_t("View older messages in %(roomName)s.", {roomName: name})}
+
+ { _t("View older messages in %(roomName)s.", { roomName: name }) }
);
}
@@ -139,23 +146,23 @@ export default class AdvancedRoomSettingsTab extends React.Component {
{_t("Room information")}
{_t("Internal room ID:")}
- {this.props.roomId}
+ { this.props.roomId }
- {unfederatableSection}
+ { unfederatableSection }
{_t("Room version")}
{_t("Room version:")}
- {room.getVersion()}
+ { room.getVersion() }
- {oldRoomLink}
- {roomUpgradeButton}
+ { oldRoomLink }
+ { roomUpgradeButton }
-
{_t("Developer options")}
-
- {_t("Open Devtools")}
+ { _t("Developer options") }
+
+ { _t("Open Devtools") }
From 8d4ac90265839a98aeb83277834ef8673fc2b97e Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Tue, 8 Jun 2021 16:28:37 +0100
Subject: [PATCH 025/164] Convert RoomPublishSetting and LabelledToggleSwitch
to Typescript
---
...ggleSwitch.js => LabelledToggleSwitch.tsx} | 47 +++++++++----------
...blishSetting.js => RoomPublishSetting.tsx} | 47 ++++++++++++-------
2 files changed, 51 insertions(+), 43 deletions(-)
rename src/components/views/elements/{LabelledToggleSwitch.js => LabelledToggleSwitch.tsx} (63%)
rename src/components/views/room_settings/{RoomPublishSetting.js => RoomPublishSetting.tsx} (62%)
diff --git a/src/components/views/elements/LabelledToggleSwitch.js b/src/components/views/elements/LabelledToggleSwitch.tsx
similarity index 63%
rename from src/components/views/elements/LabelledToggleSwitch.js
rename to src/components/views/elements/LabelledToggleSwitch.tsx
index ef60eeed7b..957e3dbc97 100644
--- a/src/components/views/elements/LabelledToggleSwitch.js
+++ b/src/components/views/elements/LabelledToggleSwitch.tsx
@@ -1,5 +1,5 @@
/*
-Copyright 2019 New Vector Ltd
+Copyright 2019, 2021 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -14,38 +14,33 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
-import React from 'react';
-import PropTypes from "prop-types";
+import React from "react";
+
import ToggleSwitch from "./ToggleSwitch";
import {replaceableComponent} from "../../../utils/replaceableComponent";
+interface IProps {
+ // The value for the toggle switch
+ value: boolean;
+ // The translated label for the switch
+ label: string;
+ // Whether or not to disable the toggle switch
+ disabled?: boolean;
+ // True to put the toggle in front of the label
+ // Default false.
+ toggleInFront?: boolean;
+ // Additional class names to append to the switch. Optional.
+ className?: string;
+ // The function to call when the value changes
+ onChange(checked: boolean): void;
+}
+
@replaceableComponent("views.elements.LabelledToggleSwitch")
-export default class LabelledToggleSwitch extends React.Component {
- static propTypes = {
- // The value for the toggle switch
- value: PropTypes.bool.isRequired,
-
- // The function to call when the value changes
- onChange: PropTypes.func.isRequired,
-
- // The translated label for the switch
- label: PropTypes.string.isRequired,
-
- // Whether or not to disable the toggle switch
- disabled: PropTypes.bool,
-
- // True to put the toggle in front of the label
- // Default false.
- toggleInFront: PropTypes.bool,
-
- // Additional class names to append to the switch. Optional.
- className: PropTypes.string,
- };
-
+export default class LabelledToggleSwitch extends React.PureComponent {
render() {
// This is a minimal version of a SettingsFlag
- let firstPart = {this.props.label} ;
+ let firstPart = { this.props.label } ;
let secondPart = {
+ public state = {
+ isRoomPublished: false,
+ };
- onRoomPublishChange = (e) => {
+ private onRoomPublishChange = (e) => {
const valueBefore = this.state.isRoomPublished;
const newValue = !valueBefore;
this.setState({isRoomPublished: newValue});
@@ -52,11 +62,14 @@ export default class RoomPublishSetting extends React.PureComponent {
render() {
const client = MatrixClientPeg.get();
- return ( );
+ return (
+
+ );
}
}
From 13a2f779b962e8498ae09d9675a91baeca3cd671 Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Tue, 8 Jun 2021 16:29:06 +0100
Subject: [PATCH 026/164] improve defaults for useRoomState and useStateToggle
hooks
---
src/hooks/useRoomState.ts | 5 ++++-
src/hooks/useStateToggle.ts | 2 +-
2 files changed, 5 insertions(+), 2 deletions(-)
diff --git a/src/hooks/useRoomState.ts b/src/hooks/useRoomState.ts
index ded51c3900..11ac7de49e 100644
--- a/src/hooks/useRoomState.ts
+++ b/src/hooks/useRoomState.ts
@@ -20,8 +20,11 @@ import { RoomState } from "matrix-js-sdk/src/models/room-state";
import { useEventEmitter } from "./useEventEmitter";
+type Mapper = (roomState: RoomState) => T;
+const defaultMapper: Mapper = (roomState: RoomState) => roomState;
+
// Hook to simplify watching Matrix Room state
-export const useRoomState = (room: Room, mapper: (state: RoomState) => T): T => {
+export const useRoomState = (room: Room, mapper: Mapper = defaultMapper): T => {
const [value, setValue] = useState(room ? mapper(room.currentState) : undefined);
const update = useCallback(() => {
diff --git a/src/hooks/useStateToggle.ts b/src/hooks/useStateToggle.ts
index b50a923234..33701c4f16 100644
--- a/src/hooks/useStateToggle.ts
+++ b/src/hooks/useStateToggle.ts
@@ -18,7 +18,7 @@ import {Dispatch, SetStateAction, useState} from "react";
// Hook to simplify toggling of a boolean state value
// Returns value, method to toggle boolean value and method to set the boolean value
-export const useStateToggle = (initialValue: boolean): [boolean, () => void, Dispatch>] => {
+export const useStateToggle = (initialValue = false): [boolean, () => void, Dispatch>] => {
const [value, setValue] = useState(initialValue);
const toggleValue = () => {
setValue(!value);
From 5c85ee1ea03097325680ed4574006c559fcccaa9 Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Tue, 8 Jun 2021 16:30:46 +0100
Subject: [PATCH 027/164] Make AdvancedRoomSettingsTab space-aware
---
.../views/settings/tabs/room/AdvancedRoomSettingsTab.tsx | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/src/components/views/settings/tabs/room/AdvancedRoomSettingsTab.tsx b/src/components/views/settings/tabs/room/AdvancedRoomSettingsTab.tsx
index f587210095..7e7d9cba90 100644
--- a/src/components/views/settings/tabs/room/AdvancedRoomSettingsTab.tsx
+++ b/src/components/views/settings/tabs/room/AdvancedRoomSettingsTab.tsx
@@ -76,7 +76,7 @@ export default class AdvancedRoomSettingsTab extends React.Component {
const room = MatrixClientPeg.get().getRoom(this.props.roomId);
- Modal.createTrackedDialog('Upgrade Room Version', '', RoomUpgradeDialog, {room: room});
+ Modal.createTrackedDialog('Upgrade Room Version', '', RoomUpgradeDialog, { room });
};
private openDevtools = (e) => {
@@ -143,7 +143,9 @@ export default class AdvancedRoomSettingsTab extends React.Component
{_t("Advanced")}
-
{_t("Room information")}
+
+ { room?.isSpaceRoom() ? _t("Space information") : _t("Room information") }
+
{_t("Internal room ID:")}
{ this.props.roomId }
From 78debcc93b99eea96f270cbff8a5266a10a52db5 Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Tue, 8 Jun 2021 16:31:39 +0100
Subject: [PATCH 028/164] Add method to disable StyledRadioGroup and wrap
description in element with a className
---
.../views/elements/StyledRadioGroup.tsx | 17 +++++++++++++----
1 file changed, 13 insertions(+), 4 deletions(-)
diff --git a/src/components/views/elements/StyledRadioGroup.tsx b/src/components/views/elements/StyledRadioGroup.tsx
index 6b9e992f92..744b6f2059 100644
--- a/src/components/views/elements/StyledRadioGroup.tsx
+++ b/src/components/views/elements/StyledRadioGroup.tsx
@@ -34,10 +34,19 @@ interface IProps
{
definitions: IDefinition[];
value?: T; // if not provided no options will be selected
outlined?: boolean;
+ disabled?: boolean;
onChange(newValue: T): void;
}
-function StyledRadioGroup({name, definitions, value, className, outlined, onChange}: IProps) {
+function StyledRadioGroup({
+ name,
+ definitions,
+ value,
+ className,
+ outlined,
+ disabled,
+ onChange,
+}: IProps) {
const _onChange = e => {
onChange(e.target.value);
};
@@ -50,12 +59,12 @@ function StyledRadioGroup({name, definitions, value, className
checked={d.checked !== undefined ? d.checked : d.value === value}
name={name}
value={d.value}
- disabled={d.disabled}
+ disabled={disabled || d.disabled}
outlined={outlined}
>
- {d.label}
+ { d.label }
- {d.description}
+ { d.description ? { d.description } : null }
)}
;
}
From fdecba2fe54a5bfd4e5b9e6974e1fd9467858539 Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Tue, 8 Jun 2021 16:32:43 +0100
Subject: [PATCH 029/164] Make AliasSettings space-aware, remove stale unused
props
---
.../views/room_settings/AliasSettings.tsx | 34 ++++++++++++++-----
.../tabs/room/GeneralRoomSettingsTab.js | 3 +-
2 files changed, 26 insertions(+), 11 deletions(-)
diff --git a/src/components/views/room_settings/AliasSettings.tsx b/src/components/views/room_settings/AliasSettings.tsx
index d6e79c4ee9..6f83d64eaf 100644
--- a/src/components/views/room_settings/AliasSettings.tsx
+++ b/src/components/views/room_settings/AliasSettings.tsx
@@ -80,7 +80,7 @@ interface IProps {
canSetCanonicalAlias: boolean;
canSetAliases: boolean;
canonicalAliasEvent?: MatrixEvent;
- aliasEvents?: MatrixEvent[];
+ hidePublishSetting?: boolean;
}
interface IState {
@@ -99,7 +99,6 @@ export default class AliasSettings extends React.Component {
static defaultProps = {
canSetAliases: false,
canSetCanonicalAlias: false,
- aliasEvents: [],
};
constructor(props) {
@@ -317,7 +316,9 @@ export default class AliasSettings extends React.Component {
}
render() {
- const localDomain = MatrixClientPeg.get().getDomain();
+ const cli = MatrixClientPeg.get();
+ const localDomain = cli.getDomain();
+ const isSpaceRoom = cli.getRoom(this.props.roomId)?.isSpaceRoom();
let found = false;
const canonicalValue = this.state.canonicalAlias || "";
@@ -363,7 +364,9 @@ export default class AliasSettings extends React.Component {
canEdit={this.props.canSetAliases}
onItemAdded={this.onLocalAliasAdded}
onItemRemoved={this.onLocalAliasDeleted}
- noItemsLabel={_t('This room has no local addresses')}
+ noItemsLabel={isSpaceRoom
+ ? _t("This space has no local addresses")
+ : _t("This room has no local addresses")}
placeholder={_t('Local address')}
domain={localDomain}
/>);
@@ -372,10 +375,20 @@ export default class AliasSettings extends React.Component {
return (
{_t("Published Addresses")}
-
{_t("Published addresses can be used by anyone on any server to join your room. " +
- "To publish an address, it needs to be set as a local address first.")}
- {canonicalAliasSection}
-
+
+ { isSpaceRoom
+ ? _t("Published addresses can be used by anyone on any server to join your space.")
+ : _t("Published addresses can be used by anyone on any server to join your room.")}
+
+ { _t("To publish an address, it needs to be set as a local address first.") }
+
+ { canonicalAliasSection }
+ { this.props.hidePublishSetting
+ ? null
+ :
}
{this.getLocalNonAltAliases().map(alias => {
return ;
@@ -399,7 +412,10 @@ export default class AliasSettings extends React.Component {
{ _t("Local Addresses") }
- { _t("Set addresses for this room so users can find this room " +
+ { isSpaceRoom
+ ? _t("Set addresses for this space so users can find this space " +
+ "through your homeserver (%(localDomain)s)", { localDomain })
+ : _t("Set addresses for this room so users can find this room " +
"through your homeserver (%(localDomain)s)", { localDomain }) }
diff --git a/src/components/views/settings/tabs/room/GeneralRoomSettingsTab.js b/src/components/views/settings/tabs/room/GeneralRoomSettingsTab.js
index 139cfd5fbd..10c93c5dca 100644
--- a/src/components/views/settings/tabs/room/GeneralRoomSettingsTab.js
+++ b/src/components/views/settings/tabs/room/GeneralRoomSettingsTab.js
@@ -60,7 +60,6 @@ export default class GeneralRoomSettingsTab extends React.Component {
const canSetAliases = true; // Previously, we arbitrarily only allowed admins to do this
const canSetCanonical = room.currentState.mayClientSendStateEvent("m.room.canonical_alias", client);
const canonicalAliasEv = room.currentState.getStateEvents("m.room.canonical_alias", '');
- const aliasEvents = room.currentState.getStateEvents("m.room.aliases");
const canChangeGroups = room.currentState.mayClientSendStateEvent("m.room.related_groups", client);
const groupsEvent = room.currentState.getStateEvents("m.room.related_groups", "");
@@ -100,7 +99,7 @@ export default class GeneralRoomSettingsTab extends React.Component {
+ canonicalAliasEvent={canonicalAliasEv} />
{_t("Other")}
{ flairSection }
From 856a5682b90fa1e4ac3c4dbf907c1b5cb55bf0f8 Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Tue, 8 Jun 2021 16:33:06 +0100
Subject: [PATCH 030/164] Improve useRoomPowerLevels hook
---
src/components/views/right_panel/UserInfo.tsx | 12 ++++--------
1 file changed, 4 insertions(+), 8 deletions(-)
diff --git a/src/components/views/right_panel/UserInfo.tsx b/src/components/views/right_panel/UserInfo.tsx
index d6c97f9cf2..48336de400 100644
--- a/src/components/views/right_panel/UserInfo.tsx
+++ b/src/components/views/right_panel/UserInfo.tsx
@@ -502,19 +502,15 @@ const isMuted = (member: RoomMember, powerLevelContent: IPowerLevelsContent) =>
return member.powerLevel < levelToSend;
};
+const getPowerLevels = room => room.currentState.getStateEvents(EventType.RoomPowerLevels, "")?.getContent() || {};
+
export const useRoomPowerLevels = (cli: MatrixClient, room: Room) => {
- const [powerLevels, setPowerLevels] = useState({});
+ const [powerLevels, setPowerLevels] = useState(getPowerLevels(room));
const update = useCallback((ev?: MatrixEvent) => {
if (!room) return;
if (ev && ev.getType() !== EventType.RoomPowerLevels) return;
-
- const event = room.currentState.getStateEvents(EventType.RoomPowerLevels, "");
- if (event) {
- setPowerLevels(event.getContent());
- } else {
- setPowerLevels({});
- }
+ setPowerLevels(getPowerLevels(room));
}, [room]);
useEventEmitter(cli, "RoomState.events", update);
From 90bb7c1482c38a234099d023b4bb1a4ae0232f57 Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Tue, 8 Jun 2021 16:33:47 +0100
Subject: [PATCH 031/164] Switch Space Settings for a tabbed view with a bunch
more settings exposed
---
res/css/views/dialogs/_SettingsDialog.scss | 2 +-
.../views/dialogs/_SpaceSettingsDialog.scss | 51 ++++-
res/img/element-icons/eye.svg | 3 +
.../views/dialogs/SpaceSettingsDialog.tsx | 172 +++++------------
.../tabs/room/SecurityRoomSettingsTab.tsx | 6 +-
.../views/spaces/SpaceSettingsGeneralTab.tsx | 143 ++++++++++++++
.../spaces/SpaceSettingsVisibilityTab.tsx | 181 ++++++++++++++++++
src/i18n/strings/en_EN.json | 40 ++--
8 files changed, 456 insertions(+), 142 deletions(-)
create mode 100644 res/img/element-icons/eye.svg
create mode 100644 src/components/views/spaces/SpaceSettingsGeneralTab.tsx
create mode 100644 src/components/views/spaces/SpaceSettingsVisibilityTab.tsx
diff --git a/res/css/views/dialogs/_SettingsDialog.scss b/res/css/views/dialogs/_SettingsDialog.scss
index 6c4ed35c5a..b3b6802c3d 100644
--- a/res/css/views/dialogs/_SettingsDialog.scss
+++ b/res/css/views/dialogs/_SettingsDialog.scss
@@ -15,7 +15,7 @@ limitations under the License.
*/
// Not actually a component but things shared by settings components
-.mx_UserSettingsDialog, .mx_RoomSettingsDialog {
+.mx_UserSettingsDialog, .mx_RoomSettingsDialog, .mx_SpaceSettingsDialog {
width: 90vw;
max-width: 1000px;
// set the height too since tabbed view scrolls itself.
diff --git a/res/css/views/dialogs/_SpaceSettingsDialog.scss b/res/css/views/dialogs/_SpaceSettingsDialog.scss
index 6e5fd9c8c8..fa074fdbe8 100644
--- a/res/css/views/dialogs/_SpaceSettingsDialog.scss
+++ b/res/css/views/dialogs/_SpaceSettingsDialog.scss
@@ -15,7 +15,6 @@ limitations under the License.
*/
.mx_SpaceSettingsDialog {
- width: 480px;
color: $primary-fg-color;
.mx_SpaceSettings_errorText {
@@ -32,8 +31,44 @@ limitations under the License.
margin-left: 16px;
}
- .mx_AccessibleButton_kind_danger {
- margin-top: 28px;
+ .mx_SettingsTab_section {
+ .mx_SettingsTab_section_caption {
+ margin-top: 12px;
+ margin-bottom: 20px;
+ }
+
+ & + .mx_SettingsTab_subheading {
+ border-top: 1px solid $message-body-panel-bg-color;
+ margin-top: 0;
+ padding-top: 24px;
+ }
+
+ .mx_RadioButton {
+ margin-top: 8px;
+ margin-bottom: 4px;
+
+ .mx_RadioButton_content {
+ font-weight: $font-semi-bold;
+ line-height: $font-18px;
+ color: $primary-fg-color;
+ }
+
+ & + span {
+ font-size: $font-15px;
+ line-height: $font-18px;
+ color: $secondary-fg-color;
+ margin-left: 26px;
+ }
+ }
+
+ .mx_SettingsTab_showAdvanced {
+ margin: 16px 0;
+ padding: 0;
+ }
+
+ .mx_SettingsFlag {
+ margin-top: 24px;
+ }
}
.mx_SpaceSettingsDialog_buttons {
@@ -52,4 +87,14 @@ limitations under the License.
.mx_AccessibleButton_hasKind {
padding: 8px 22px;
}
+
+ .mx_TabbedView_tabLabel {
+ .mx_SpaceSettingsDialog_generalIcon::before {
+ mask-image: url('$(res)/img/element-icons/settings.svg');
+ }
+
+ .mx_SpaceSettingsDialog_visibilityIcon::before {
+ mask-image: url('$(res)/img/element-icons/eye.svg');
+ }
+ }
}
diff --git a/res/img/element-icons/eye.svg b/res/img/element-icons/eye.svg
new file mode 100644
index 0000000000..0460a6201d
--- /dev/null
+++ b/res/img/element-icons/eye.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/src/components/views/dialogs/SpaceSettingsDialog.tsx b/src/components/views/dialogs/SpaceSettingsDialog.tsx
index a135b6bc16..1273f06401 100644
--- a/src/components/views/dialogs/SpaceSettingsDialog.tsx
+++ b/src/components/views/dialogs/SpaceSettingsDialog.tsx
@@ -14,24 +14,27 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
-import React, {useState} from 'react';
-import {Room} from "matrix-js-sdk/src/models/room";
-import {MatrixClient} from "matrix-js-sdk/src/client";
-import {EventType} from "matrix-js-sdk/src/@types/event";
+import React, { useMemo } from 'react';
+import { Room } from "matrix-js-sdk/src/models/room";
+import { MatrixClient } from "matrix-js-sdk/src/client";
-import {_t} from '../../../languageHandler';
-import {IDialogProps} from "./IDialogProps";
+import { _t, _td } from '../../../languageHandler';
+import { IDialogProps } from "./IDialogProps";
import BaseDialog from "./BaseDialog";
-import DevtoolsDialog from "./DevtoolsDialog";
-import SpaceBasicSettings from '../spaces/SpaceBasicSettings';
-import {getTopic} from "../elements/RoomTopic";
-import {avatarUrlForRoom} from "../../../Avatar";
-import ToggleSwitch from "../elements/ToggleSwitch";
-import AccessibleButton from "../elements/AccessibleButton";
-import Modal from "../../../Modal";
import defaultDispatcher from "../../../dispatcher/dispatcher";
-import {useDispatcher} from "../../../hooks/useDispatcher";
-import {SpaceFeedbackPrompt} from "../../structures/SpaceRoomView";
+import { useDispatcher } from "../../../hooks/useDispatcher";
+import TabbedView, { Tab } from "../../structures/TabbedView";
+import SpaceSettingsGeneralTab from '../spaces/SpaceSettingsGeneralTab';
+import SpaceSettingsVisibilityTab from "../spaces/SpaceSettingsVisibilityTab";
+import SettingsStore from "../../../settings/SettingsStore";
+import {UIFeature} from "../../../settings/UIFeature";
+import AdvancedRoomSettingsTab from "../settings/tabs/room/AdvancedRoomSettingsTab";
+
+export enum SpaceSettingsTab {
+ General = "SPACE_GENERAL_TAB",
+ Visibility = "SPACE_VISIBILITY_TAB",
+ Advanced = "SPACE_ADVANCED_TAB",
+}
interface IProps extends IDialogProps {
matrixClient: MatrixClient;
@@ -45,63 +48,30 @@ const SpaceSettingsDialog: React.FC = ({ matrixClient: cli, space, onFin
}
});
- const [busy, setBusy] = useState(false);
- const [error, setError] = useState("");
-
- const userId = cli.getUserId();
-
- const [newAvatar, setNewAvatar] = useState(null); // undefined means to remove avatar
- const canSetAvatar = space.currentState.maySendStateEvent(EventType.RoomAvatar, userId);
- const avatarChanged = newAvatar !== null;
-
- const [name, setName] = useState(space.name);
- const canSetName = space.currentState.maySendStateEvent(EventType.RoomName, userId);
- const nameChanged = name !== space.name;
-
- const currentTopic = getTopic(space);
- const [topic, setTopic] = useState(currentTopic);
- const canSetTopic = space.currentState.maySendStateEvent(EventType.RoomTopic, userId);
- const topicChanged = topic !== currentTopic;
-
- const currentJoinRule = space.getJoinRule();
- const [joinRule, setJoinRule] = useState(currentJoinRule);
- const canSetJoinRule = space.currentState.maySendStateEvent(EventType.RoomJoinRules, userId);
- const joinRuleChanged = joinRule !== currentJoinRule;
-
- const onSave = async () => {
- setBusy(true);
- const promises = [];
-
- if (avatarChanged) {
- if (newAvatar) {
- promises.push(cli.sendStateEvent(space.roomId, EventType.RoomAvatar, {
- url: await cli.uploadContent(newAvatar),
- }, ""));
- } else {
- promises.push(cli.sendStateEvent(space.roomId, EventType.RoomAvatar, {}, ""));
- }
- }
-
- if (nameChanged) {
- promises.push(cli.setRoomName(space.roomId, name));
- }
-
- if (topicChanged) {
- promises.push(cli.setRoomTopic(space.roomId, topic));
- }
-
- if (joinRuleChanged) {
- promises.push(cli.sendStateEvent(space.roomId, EventType.RoomJoinRules, { join_rule: joinRule }, ""));
- }
-
- const results = await Promise.allSettled(promises);
- setBusy(false);
- const failures = results.filter(r => r.status === "rejected");
- if (failures.length > 0) {
- console.error("Failed to save space settings: ", failures);
- setError(_t("Failed to save space settings."));
- }
- };
+ const tabs = useMemo(() => {
+ return [
+ new Tab(
+ SpaceSettingsTab.General,
+ _td("General"),
+ "mx_SpaceSettingsDialog_generalIcon",
+ ,
+ ),
+ new Tab(
+ SpaceSettingsTab.Visibility,
+ _td("Visibility"),
+ "mx_SpaceSettingsDialog_visibilityIcon",
+ ,
+ ),
+ SettingsStore.getValue(UIFeature.AdvancedSettings)
+ ? new Tab(
+ SpaceSettingsTab.Advanced,
+ _td("Advanced"),
+ "mx_RoomSettingsDialog_warningIcon",
+ ,
+ )
+ : null,
+ ].filter(Boolean);
+ }, [cli, space, onFinished]);
return = ({ matrixClient: cli, space, onFin
onFinished={onFinished}
fixedWidth={false}
>
-
-
{ _t("Edit settings relating to your space.") }
-
- { error &&
{ error }
}
-
-
onFinished(false)} />
-
-
-
-
- { _t("Make this space private") }
- setJoinRule(checked ? "invite" : "public")}
- disabled={!canSetJoinRule}
- aria-label={_t("Make this space private")}
- />
-
-
- {
- defaultDispatcher.dispatch({
- action: "leave_room",
- room_id: space.roomId,
- });
- }}
- >
- { _t("Leave Space") }
-
-
-
-
Modal.createDialog(DevtoolsDialog, {roomId: space.roomId})}>
- { _t("View dev tools") }
-
-
- { _t("Cancel") }
-
-
- { busy ? _t("Saving...") : _t("Save Changes") }
-
-
+
+
;
};
diff --git a/src/components/views/settings/tabs/room/SecurityRoomSettingsTab.tsx b/src/components/views/settings/tabs/room/SecurityRoomSettingsTab.tsx
index 02bbcfb751..99f525364e 100644
--- a/src/components/views/settings/tabs/room/SecurityRoomSettingsTab.tsx
+++ b/src/components/views/settings/tabs/room/SecurityRoomSettingsTab.tsx
@@ -29,19 +29,19 @@ import {UIFeature} from "../../../../../settings/UIFeature";
import { replaceableComponent } from "../../../../../utils/replaceableComponent";
// Knock and private are reserved keywords which are not yet implemented.
-enum JoinRule {
+export enum JoinRule {
Public = "public",
Knock = "knock",
Invite = "invite",
Private = "private",
}
-enum GuestAccess {
+export enum GuestAccess {
CanJoin = "can_join",
Forbidden = "forbidden",
}
-enum HistoryVisibility {
+export enum HistoryVisibility {
Invited = "invited",
Joined = "joined",
Shared = "shared",
diff --git a/src/components/views/spaces/SpaceSettingsGeneralTab.tsx b/src/components/views/spaces/SpaceSettingsGeneralTab.tsx
new file mode 100644
index 0000000000..db0a180846
--- /dev/null
+++ b/src/components/views/spaces/SpaceSettingsGeneralTab.tsx
@@ -0,0 +1,143 @@
+/*
+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 React, { useState } from "react";
+import { Room } from "matrix-js-sdk/src/models/room";
+import { MatrixClient } from "matrix-js-sdk/src/client";
+import { EventType } from "matrix-js-sdk/src/@types/event";
+
+import { _t } from "../../../languageHandler";
+import AccessibleButton from "../elements/AccessibleButton";
+import { SpaceFeedbackPrompt } from "../../structures/SpaceRoomView";
+import SpaceBasicSettings from "./SpaceBasicSettings";
+import { avatarUrlForRoom } from "../../../Avatar";
+import { IDialogProps } from "../dialogs/IDialogProps";
+import { getTopic } from "../elements/RoomTopic";
+import { defaultDispatcher } from "../../../dispatcher/dispatcher";
+
+interface IProps extends IDialogProps {
+ matrixClient: MatrixClient;
+ space: Room;
+}
+
+const SpaceSettingsGeneralTab = ({ matrixClient: cli, space, onFinished }: IProps) => {
+ const [busy, setBusy] = useState(false);
+ const [error, setError] = useState("");
+
+ const userId = cli.getUserId();
+
+ const [newAvatar, setNewAvatar] = useState(null); // undefined means to remove avatar
+ const canSetAvatar = space.currentState.maySendStateEvent(EventType.RoomAvatar, userId);
+ const avatarChanged = newAvatar !== null;
+
+ const [name, setName] = useState(space.name);
+ const canSetName = space.currentState.maySendStateEvent(EventType.RoomName, userId);
+ const nameChanged = name !== space.name;
+
+ const currentTopic = getTopic(space);
+ const [topic, setTopic] = useState(currentTopic);
+ const canSetTopic = space.currentState.maySendStateEvent(EventType.RoomTopic, userId);
+ const topicChanged = topic !== currentTopic;
+
+ const onCancel = () => {
+ setNewAvatar(null);
+ setName(space.name);
+ setTopic(currentTopic);
+ };
+
+ const onSave = async () => {
+ setBusy(true);
+ const promises = [];
+
+ if (avatarChanged) {
+ if (newAvatar) {
+ promises.push(cli.sendStateEvent(space.roomId, EventType.RoomAvatar, {
+ url: await cli.uploadContent(newAvatar),
+ }, ""));
+ } else {
+ promises.push(cli.sendStateEvent(space.roomId, EventType.RoomAvatar, {}, ""));
+ }
+ }
+
+ if (nameChanged) {
+ promises.push(cli.setRoomName(space.roomId, name));
+ }
+
+ if (topicChanged) {
+ promises.push(cli.setRoomTopic(space.roomId, topic));
+ }
+
+ const results = await Promise.allSettled(promises);
+ setBusy(false);
+ const failures = results.filter(r => r.status === "rejected");
+ if (failures.length > 0) {
+ console.error("Failed to save space settings: ", failures);
+ setError(_t("Failed to save space settings."));
+ }
+ };
+
+ return
+
{_t("General")}
+
+
{ _t("Edit settings relating to your space.") }
+
+ { error &&
{ error }
}
+
+
onFinished(false)} />
+
+
+
+
+
+ { _t("Cancel") }
+
+
+ { busy ? _t("Saving...") : _t("Save Changes") }
+
+
+
+ {_t("Leave Space")}
+
+
{
+ defaultDispatcher.dispatch({
+ action: "leave_room",
+ room_id: space.roomId,
+ });
+ }}
+ >
+ { _t("Leave Space") }
+
+
+ ;
+};
+
+export default SpaceSettingsGeneralTab;
diff --git a/src/components/views/spaces/SpaceSettingsVisibilityTab.tsx b/src/components/views/spaces/SpaceSettingsVisibilityTab.tsx
new file mode 100644
index 0000000000..7fc3514b2d
--- /dev/null
+++ b/src/components/views/spaces/SpaceSettingsVisibilityTab.tsx
@@ -0,0 +1,181 @@
+/*
+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 React, { useState } from "react";
+import { Room } from "matrix-js-sdk/src/models/room";
+import { MatrixClient } from "matrix-js-sdk/src/client";
+import { EventType } from "matrix-js-sdk/src/@types/event";
+
+import { _t } from "../../../languageHandler";
+import AccessibleButton from "../elements/AccessibleButton";
+import AliasSettings from "../room_settings/AliasSettings";
+import { useStateToggle } from "../../../hooks/useStateToggle";
+import LabelledToggleSwitch from "../elements/LabelledToggleSwitch";
+import { GuestAccess, HistoryVisibility, JoinRule } from "../settings/tabs/room/SecurityRoomSettingsTab";
+import StyledRadioGroup from "../elements/StyledRadioGroup";
+
+interface IProps {
+ matrixClient: MatrixClient;
+ space: Room;
+}
+
+enum SpaceVisibility {
+ Unlisted = "unlisted",
+ Private = "private",
+}
+
+const useLocalEcho = (
+ currentFactory: () => T,
+ setterFn: (value: T) => Promise,
+ errorFn: (error: Error) => void,
+): [value: T, handler: (value: T) => void] => {
+ const [value, setValue] = useState(currentFactory);
+ const handler = async (value: T) => {
+ setValue(value);
+ try {
+ await setterFn(value);
+ } catch (e) {
+ setValue(currentFactory());
+ errorFn(e);
+ }
+ };
+
+ return [value, handler];
+};
+
+const SpaceSettingsVisibilityTab = ({ matrixClient: cli, space }: IProps) => {
+ const [busy, setBusy] = useState(false);
+ const [error, setError] = useState("");
+
+ const userId = cli.getUserId();
+
+ const [visibility, setVisibility] = useLocalEcho(
+ () => space.getJoinRule() === JoinRule.Private ? SpaceVisibility.Private : SpaceVisibility.Unlisted,
+ visibility => cli.sendStateEvent(space.roomId, EventType.RoomJoinRules, {
+ join_rule: visibility === SpaceVisibility.Unlisted ? JoinRule.Public : JoinRule.Private,
+ }, ""),
+ () => setError(_t("Failed to update the visibility of this space")),
+ );
+ const [guestAccessEnabled, setGuestAccessEnabled] = useLocalEcho(
+ () => space.currentState.getStateEvents(EventType.RoomGuestAccess, "")
+ ?.getContent()?.guest_access === GuestAccess.CanJoin,
+ guestAccessEnabled => cli.sendStateEvent(space.roomId, EventType.RoomGuestAccess, {
+ guest_access: guestAccessEnabled ? GuestAccess.CanJoin : GuestAccess.Forbidden,
+ }, ""),
+ () => setError(_t("Failed to update the guest access of this space")),
+ );
+ const [historyVisibility, setHistoryVisibility] = useLocalEcho(
+ () => space.currentState.getStateEvents(EventType.RoomHistoryVisibility, "")
+ ?.getContent()?.history_visibility || HistoryVisibility.Shared,
+ historyVisibility => cli.sendStateEvent(space.roomId, EventType.RoomHistoryVisibility, {
+ history_visibility: historyVisibility,
+ }, ""),
+ () => setError(_t("Failed to update the history visibility of this space")),
+ );
+
+ const [showAdvancedSection, toggleAdvancedSection] = useStateToggle();
+
+ const canSetJoinRule = space.currentState.maySendStateEvent(EventType.RoomJoinRules, userId);
+ const canSetGuestAccess = space.currentState.maySendStateEvent(EventType.RoomGuestAccess, userId);
+ const canSetHistoryVisibility = space.currentState.maySendStateEvent(EventType.RoomHistoryVisibility, userId);
+ const canSetCanonical = space.currentState.mayClientSendStateEvent(EventType.RoomCanonicalAlias, cli);
+ const canonicalAliasEv = space.currentState.getStateEvents(EventType.RoomCanonicalAlias, "");
+
+ let advancedSection;
+ if (showAdvancedSection) {
+ advancedSection = <>
+
+ { _t("Hide advanced") }
+
+
+
+
+ { _t("Guests can join a space without having an account.") }
+
+ { _t("This may be useful for public spaces.") }
+
+ >;
+ } else {
+ advancedSection = <>
+
+ { _t("Show advanced") }
+
+ >;
+ }
+
+ return
+
{_t("Visibility")}
+
+ { error &&
{ error }
}
+
+
+
+ { _t("Decide who can view and join %(spaceName)s.", { spaceName: space.name }) }
+
+
+
+
+
+
+ { advancedSection }
+
+
{
+ setHistoryVisibility(checked ? HistoryVisibility.WorldReadable : HistoryVisibility.Shared);
+ }}
+ disabled={!canSetHistoryVisibility}
+ label={_t("Preview Space")}
+ />
+ { _t("Allow people to preview your space before they join.") }
+ { _t("Recommended for public spaces.") }
+
+
+
{_t("Address")}
+
+
;
+};
+
+export default SpaceSettingsVisibilityTab;
diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json
index a744d8e7be..8c04a81807 100644
--- a/src/i18n/strings/en_EN.json
+++ b/src/i18n/strings/en_EN.json
@@ -1029,6 +1029,28 @@
"Share invite link": "Share invite link",
"Invite people": "Invite people",
"Invite with email or username": "Invite with email or username",
+ "Failed to save space settings.": "Failed to save space settings.",
+ "General": "General",
+ "Edit settings relating to your space.": "Edit settings relating to your space.",
+ "Saving...": "Saving...",
+ "Save Changes": "Save Changes",
+ "Leave Space": "Leave Space",
+ "Failed to update the visibility of this space": "Failed to update the visibility of this space",
+ "Failed to update the guest access of this space": "Failed to update the guest access of this space",
+ "Failed to update the history visibility of this space": "Failed to update the history visibility of this space",
+ "Hide advanced": "Hide advanced",
+ "Enable guest access": "Enable guest access",
+ "Guests can join a space without having an account.": "Guests can join a space without having an account.",
+ "This may be useful for public spaces.": "This may be useful for public spaces.",
+ "Show advanced": "Show advanced",
+ "Visibility": "Visibility",
+ "Decide who can view and join %(spaceName)s.": "Decide who can view and join %(spaceName)s.",
+ "anyone with the link can view and join": "anyone with the link can view and join",
+ "Invite only": "Invite only",
+ "only invited people can view and join": "only invited people can view and join",
+ "Preview Space": "Preview Space",
+ "Allow people to preview your space before they join.": "Allow people to preview your space before they join.",
+ "Recommended for public spaces.": "Recommended for public spaces.",
"Settings": "Settings",
"Leave space": "Leave space",
"Create new room": "Create new room",
@@ -1223,8 +1245,6 @@
"Custom theme URL": "Custom theme URL",
"Add theme": "Add theme",
"Theme": "Theme",
- "Hide advanced": "Hide advanced",
- "Show advanced": "Show advanced",
"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",
@@ -1245,7 +1265,6 @@
"Deactivate Account": "Deactivate Account",
"Deactivate account": "Deactivate account",
"Discovery": "Discovery",
- "General": "General",
"Legal": "Legal",
"Credits": "Credits",
"For help with using %(brand)s, click here .": "For help with using %(brand)s, click here .",
@@ -1351,6 +1370,7 @@
"Upgrade this room to the recommended room version": "Upgrade this room to the recommended room version",
"this room": "this room",
"View older messages in %(roomName)s.": "View older messages in %(roomName)s.",
+ "Space information": "Space information",
"Room information": "Room information",
"Internal room ID:": "Internal room ID:",
"Room version": "Room version",
@@ -1675,14 +1695,18 @@
"Error removing address": "Error removing address",
"Main address": "Main address",
"not specified": "not specified",
+ "This space has no local addresses": "This space has no local addresses",
"This room has no local addresses": "This room has no local addresses",
"Local address": "Local address",
"Published Addresses": "Published Addresses",
- "Published addresses can be used by anyone on any server to join your room. To publish an address, it needs to be set as a local address first.": "Published addresses can be used by anyone on any server to join your room. To publish an address, it needs to be set as a local address first.",
+ "Published addresses can be used by anyone on any server to join your space.": "Published addresses can be used by anyone on any server to join your space.",
+ "Published addresses can be used by anyone on any server to join your room.": "Published addresses can be used by anyone on any server to join your room.",
+ "To publish an address, it needs to be set as a local address first.": "To publish an address, it needs to be set as a local address first.",
"Other published addresses:": "Other published addresses:",
"No other published addresses yet, add one below": "No other published addresses yet, add one below",
"New published address (e.g. #alias:server)": "New published address (e.g. #alias:server)",
"Local Addresses": "Local Addresses",
+ "Set addresses for this space so users can find this space through your homeserver (%(localDomain)s)": "Set addresses for this space so users can find this space through your homeserver (%(localDomain)s)",
"Set addresses for this room so users can find this room through your homeserver (%(localDomain)s)": "Set addresses for this room so users can find this room through your homeserver (%(localDomain)s)",
"Show more": "Show more",
"Error updating flair": "Error updating flair",
@@ -2370,14 +2394,8 @@
"Share Room Message": "Share Room Message",
"Link to selected message": "Link to selected message",
"Command Help": "Command Help",
- "Failed to save space settings.": "Failed to save space settings.",
"Space settings": "Space settings",
- "Edit settings relating to your space.": "Edit settings relating to your space.",
- "Make this space private": "Make this space private",
- "Leave Space": "Leave Space",
- "View dev tools": "View dev tools",
- "Saving...": "Saving...",
- "Save Changes": "Save Changes",
+ "Settings - %(spaceName)s": "Settings - %(spaceName)s",
"To help us prevent this in future, please send us logs .": "To help us prevent this in future, please send us logs .",
"Missing session data": "Missing session data",
"Some session data, including encrypted message keys, is missing. Sign out and sign in to fix this, restoring keys from backup.": "Some session data, including encrypted message keys, is missing. Sign out and sign in to fix this, restoring keys from backup.",
From 54241f44c2b50f559862d78b795b3b714ed30746 Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Tue, 8 Jun 2021 16:40:01 +0100
Subject: [PATCH 032/164] delint
---
src/components/views/right_panel/PinnedMessagesCard.tsx | 1 -
.../views/settings/tabs/room/SecurityRoomSettingsTab.tsx | 2 +-
src/components/views/spaces/SpaceSettingsVisibilityTab.tsx | 1 -
3 files changed, 1 insertion(+), 3 deletions(-)
diff --git a/src/components/views/right_panel/PinnedMessagesCard.tsx b/src/components/views/right_panel/PinnedMessagesCard.tsx
index ad62619593..3b1db91689 100644
--- a/src/components/views/right_panel/PinnedMessagesCard.tsx
+++ b/src/components/views/right_panel/PinnedMessagesCard.tsx
@@ -16,7 +16,6 @@ limitations under the License.
import React, {useCallback, useContext, useEffect, useState} from "react";
import { Room } from "matrix-js-sdk/src/models/room";
-import { RoomState } from "matrix-js-sdk/src/models/room-state";
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
import { EventType } from 'matrix-js-sdk/src/@types/event';
diff --git a/src/components/views/settings/tabs/room/SecurityRoomSettingsTab.tsx b/src/components/views/settings/tabs/room/SecurityRoomSettingsTab.tsx
index 99f525364e..bb7e194253 100644
--- a/src/components/views/settings/tabs/room/SecurityRoomSettingsTab.tsx
+++ b/src/components/views/settings/tabs/room/SecurityRoomSettingsTab.tsx
@@ -121,7 +121,7 @@ export default class SecurityRoomSettingsTab extends React.Component {
+ private onEncryptionChange = () => {
Modal.createTrackedDialog('Enable encryption', '', QuestionDialog, {
title: _t('Enable encryption?'),
description: _t(
diff --git a/src/components/views/spaces/SpaceSettingsVisibilityTab.tsx b/src/components/views/spaces/SpaceSettingsVisibilityTab.tsx
index 7fc3514b2d..9eb4c6eb02 100644
--- a/src/components/views/spaces/SpaceSettingsVisibilityTab.tsx
+++ b/src/components/views/spaces/SpaceSettingsVisibilityTab.tsx
@@ -57,7 +57,6 @@ const useLocalEcho = (
};
const SpaceSettingsVisibilityTab = ({ matrixClient: cli, space }: IProps) => {
- const [busy, setBusy] = useState(false);
const [error, setError] = useState("");
const userId = cli.getUserId();
From 21fc386317d8bc41f5bdbf416c17263a4f7e055d Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Thu, 10 Jun 2021 11:40:10 +0100
Subject: [PATCH 033/164] Move over to new lexicographic string sorting
---
src/stores/SpaceStore.tsx | 65 +++++++++++++++----------
src/utils/stringOrderField.ts | 56 ++++++++++++++++++++++
test/utils/stringOrderField-test.ts | 73 +++++++++++++++++++++++++++++
3 files changed, 170 insertions(+), 24 deletions(-)
create mode 100644 src/utils/stringOrderField.ts
create mode 100644 test/utils/stringOrderField-test.ts
diff --git a/src/stores/SpaceStore.tsx b/src/stores/SpaceStore.tsx
index 5e09b617a7..47c735285c 100644
--- a/src/stores/SpaceStore.tsx
+++ b/src/stores/SpaceStore.tsx
@@ -34,6 +34,12 @@ import {setHasDiff} from "../utils/sets";
import {ISpaceSummaryEvent, ISpaceSummaryRoom} from "../components/structures/SpaceRoomDirectory";
import RoomViewStore from "./RoomViewStore";
import { arrayHasOrderChange } from "../utils/arrays";
+import {
+ ALPHABET_END,
+ ALPHABET_START,
+ averageBetweenStrings,
+ midPointsBetweenStrings,
+} from "../utils/stringOrderField";
interface IState {}
@@ -61,18 +67,19 @@ const partitionSpacesAndRooms = (arr: Room[]): [Room[], Room[]] => { // [spaces,
}, [[], []]);
};
-// For sorting space children using a validated `order`, `m.room.create`'s `origin_server_ts`, `room_id`
-export const getChildOrder = (order: string, creationTs: number, roomId: string): Array>> => {
- let validatedOrder: string = null;
-
- if (typeof order === "string" && Array.from(order).every((c: string) => {
+const validOrder = (order: string): string | null => {
+ if (typeof order === "string" && order.length <= 50 && Array.from(order).every((c: string) => {
const charCode = c.charCodeAt(0);
return charCode >= 0x20 && charCode <= 0x7E;
})) {
- validatedOrder = order;
+ return order;
}
+ return undefined;
+};
- return [validatedOrder, creationTs, roomId];
+// For sorting space children using a validated `order`, `m.room.create`'s `origin_server_ts`, `room_id`
+export const getChildOrder = (order: string, creationTs: number, roomId: string): Array>> => {
+ return [validOrder(order), creationTs, roomId];
}
const getRoomFn: FetchRoomFn = (room: Room) => {
@@ -625,8 +632,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient {
private getSpaceTagOrdering = (space: Room): string | undefined => {
if (this.spaceOrderLocalEchoMap.has(space.roomId)) return this.spaceOrderLocalEchoMap.get(space.roomId);
- const order = space.getAccountData(EventType.SpaceOrder)?.getContent()?.order;
- return typeof order === "string" ? order : undefined;
+ return validOrder(space.getAccountData(EventType.SpaceOrder)?.getContent()?.order);
};
private sortRootSpaces(spaces: Room[]): Room[] {
@@ -635,7 +641,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient {
private setRootSpaceOrder(space: Room, order: string): void {
this.spaceOrderLocalEchoMap.set(space.roomId, order);
- this.matrixClient.setRoomAccountData(space.roomId, EventType.SpaceOrder, { order });
+ this.matrixClient.setRoomAccountData(space.roomId, EventType.SpaceOrder, { order }); // TODO retrying, failure
}
public moveRootSpace(fromIndex: number, toIndex: number): void {
@@ -653,32 +659,42 @@ export class SpaceStoreClass extends AsyncStoreWithClient {
let nextOrder: string;
if (toIndex > fromIndex) {
- prevOrder = toIndex >= 0 ? orders[toIndex] : "aaaaa";
- nextOrder = toIndex <= orders.length ? orders[toIndex + 1] : "zzzzz";
+ // moving down
+ prevOrder = orders[toIndex];
+ nextOrder = orders[toIndex + 1];
} else {
// accounts for downwards displacement of existing inhabitant of this index
- prevOrder = toIndex > 0 ? orders[toIndex - 1] : "aaaaa";
- nextOrder = toIndex < orders.length ? orders[toIndex] : "zzzzz";
+ prevOrder = toIndex > 0 ? orders[toIndex - 1] : String.fromCharCode(ALPHABET_START).repeat(5); // TODO
+ nextOrder = orders[toIndex];
}
console.log("@@ start", {fromIndex, toIndex, orders, prevOrder, nextOrder});
if (prevOrder === undefined) {
+ // to be able to move to this toIndex we will first need to insert a bunch of orders for earlier elements
const firstUndefinedIndex = orders.indexOf(undefined);
const numUndefined = orders.length - firstUndefinedIndex;
- const lastOrder = orders[firstUndefinedIndex - 1];
- console.log("@@ precalc", {firstUndefinedIndex, numUndefined, lastOrder});
- nextOrder = lastOrder + step;
- for (let i = firstUndefinedIndex; i < toIndex; i++, nextOrder += step) {
- console.log("@@ preset", {i, nextOrder});
- this.setRootSpaceOrder(this.rootSpaces[i], nextOrder);
- }
+ const lastOrder = orders[firstUndefinedIndex - 1] ?? String.fromCharCode(ALPHABET_START); // TODO
+ nextOrder = String.fromCharCode(ALPHABET_END).repeat(lastOrder.length + 1);
+ const newOrders = midPointsBetweenStrings(lastOrder, nextOrder, numUndefined);
- prevOrder = nextOrder;
- nextOrder += step;
+ if (newOrders.length === numUndefined) {
+ console.log("@@ precalc", {firstUndefinedIndex, numUndefined, lastOrder, newOrders});
+ for (let i = firstUndefinedIndex, j = 0; i <= toIndex; i++, j++) {
+ if (i === toIndex && toIndex < fromIndex) continue;
+ if (i === fromIndex) continue;
+ const newOrder = newOrders[j];
+ console.log("@@ preset", {i, j, newOrder});
+ this.setRootSpaceOrder(this.rootSpaces[i], newOrder);
+ }
+
+ prevOrder = newOrders[newOrders.length - 1];
+ } else {
+ prevOrder = nextOrder; // rebuild
+ }
}
if (prevOrder !== nextOrder) {
- const order = prevOrder + ((nextOrder - prevOrder) / 2);
+ const order = averageBetweenStrings(prevOrder, nextOrder ?? String.fromCharCode(ALPHABET_END).repeat(prevOrder.length + 1));
console.log("@@ set", {prevOrder, nextOrder, order});
this.setRootSpaceOrder(space, order);
} else {
@@ -686,6 +702,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient {
}
this.notifyIfOrderChanged();
+ console.log("@@ done", this.rootSpaces.map(this.getSpaceTagOrdering));
}
}
diff --git a/src/utils/stringOrderField.ts b/src/utils/stringOrderField.ts
new file mode 100644
index 0000000000..fce859ddb8
--- /dev/null
+++ b/src/utils/stringOrderField.ts
@@ -0,0 +1,56 @@
+/*
+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.
+*/
+
+export const ALPHABET_START = 0x20;
+export const ALPHABET_END = 0x7E;
+export const ALPHABET = new Array(1 + ALPHABET_END - ALPHABET_START)
+ .fill(undefined)
+ .map((_, i) => String.fromCharCode(ALPHABET_START + i))
+ .join("");
+
+export const baseToString = (base: number, alphabet = ALPHABET): string => {
+ base = Math.floor(base);
+ if (base < alphabet.length) return alphabet[base];
+ return baseToString(Math.floor(base / alphabet.length), alphabet) + alphabet[base % alphabet.length];
+};
+
+export const stringToBase = (str: string, alphabet = ALPHABET): number => {
+ let result = 0;
+ for (let i = str.length - 1, j = 0; i >= 0; i--, j++) {
+ result += (str.charCodeAt(i) - alphabet.charCodeAt(0)) * (alphabet.length ** j);
+ }
+ return result;
+};
+
+const pad = (str: string, length: number, alphabet = ALPHABET): string => str.padEnd(length, alphabet[0]);
+
+export const averageBetweenStrings = (a: string, b: string, alphabet = ALPHABET): string => {
+ const n = Math.max(a.length, b.length);
+ const aBase = stringToBase(pad(a, n, alphabet), alphabet);
+ const bBase = stringToBase(pad(b, n, alphabet), alphabet);
+ return baseToString((aBase + bBase) / 2, alphabet);
+};
+
+export const midPointsBetweenStrings = (a: string, b: string, count: number, alphabet = ALPHABET): string[] => {
+ const n = Math.max(a.length, b.length);
+ const aBase = stringToBase(pad(a, n, alphabet), alphabet);
+ const bBase = stringToBase(pad(b, n, alphabet), alphabet);
+ const step = (bBase - aBase) / (count + 1);
+ if (step < 1) {
+ return [];
+ }
+ return Array(count).fill(undefined).map((_, i) => baseToString(aBase + step + (i * step), alphabet));
+};
diff --git a/test/utils/stringOrderField-test.ts b/test/utils/stringOrderField-test.ts
new file mode 100644
index 0000000000..5b8c2f3feb
--- /dev/null
+++ b/test/utils/stringOrderField-test.ts
@@ -0,0 +1,73 @@
+/*
+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 {
+ ALPHABET,
+ averageBetweenStrings,
+ baseToString,
+ midPointsBetweenStrings,
+ stringToBase,
+} from "../../src/utils/stringOrderField";
+
+describe("stringOrderField", () => {
+ it("stringToBase", () => {
+ expect(stringToBase(" ")).toBe(0);
+ expect(stringToBase("a")).toBe(65);
+ expect(stringToBase("aa")).toBe(6240);
+ expect(stringToBase("cat")).toBe(610934);
+ expect(stringToBase("doggo")).toBe(5607022724);
+ expect(stringToBase(" ")).toEqual(0);
+ expect(stringToBase("a", "abcdefghijklmnopqrstuvwxyz")).toEqual(0);
+ expect(stringToBase("a")).toEqual(65);
+ expect(stringToBase("c", "abcdefghijklmnopqrstuvwxyz")).toEqual(2);
+ expect(stringToBase("ab")).toEqual(6241);
+ expect(stringToBase("cb", "abcdefghijklmnopqrstuvwxyz")).toEqual(53);
+ });
+
+ it("baseToString", () => {
+ expect(baseToString(10)).toBe(ALPHABET[10]);
+ expect(baseToString(10, "abcdefghijklmnopqrstuvwxyz")).toEqual("k");
+ expect(baseToString(6241)).toEqual("ab");
+ expect(baseToString(53, "abcdefghijklmnopqrstuvwxyz")).toEqual("cb");
+ expect(baseToString(1234)).toBe(",~");
+ });
+
+ it("averageBetweenStrings", () => {
+ [
+ { a: "a", b: "z", output: `m` },
+ { a: "ba", b: "z", output: `n@` },
+ { a: "z", b: "ba", output: `n@` },
+ { a: "# ", b: "$8888", output: `#[[[[` },
+ { a: "cat", b: "doggo", output: `d9>Cw` },
+ { a: "cat", b: "doggo", output: "cumqh", alphabet: "abcdefghijklmnopqrstuvwxyz" },
+ { a: "aa", b: "zz", output: "mz", alphabet: "abcdefghijklmnopqrstuvwxyz" },
+ { a: "a", b: "z", output: "m", alphabet: "abcdefghijklmnopqrstuvwxyz" },
+ { a: "AA", b: "zz", output: "^." },
+ { a: "A", b: "z", output: "]" },
+ ].forEach((c) => {
+ // assert that the output string falls lexicographically between `a` and `b`
+ expect([c.a, c.b, c.output].sort()[1]).toBe(c.output);
+ expect(averageBetweenStrings(c.a, c.b, c.alphabet)).toBe(c.output);
+ });
+ });
+
+ it("midPointsBetweenStrings", () => {
+ expect(midPointsBetweenStrings("a", "e", 3)).toStrictEqual(["b", "c", "d"]);
+ expect(midPointsBetweenStrings("a", "e", 0)).toStrictEqual([]);
+ expect(midPointsBetweenStrings("a", "e", 4)).toStrictEqual([]);
+ });
+});
+
From a4fa2779d4a40bd8a2eeeb65d96fa61cacd836e5 Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Fri, 11 Jun 2021 10:33:00 +0100
Subject: [PATCH 034/164] Iterate lexicographic ordering implementation
---
src/stores/SpaceStore.tsx | 69 ++++---------------------
src/utils/arrays.ts | 17 ++++++-
src/utils/stringOrderField.ts | 79 +++++++++++++++++++++++++++++
test/utils/stringOrderField-test.ts | 76 +++++++++++++++++++++++++++
4 files changed, 180 insertions(+), 61 deletions(-)
diff --git a/src/stores/SpaceStore.tsx b/src/stores/SpaceStore.tsx
index 47c735285c..d0ec573306 100644
--- a/src/stores/SpaceStore.tsx
+++ b/src/stores/SpaceStore.tsx
@@ -34,12 +34,7 @@ import {setHasDiff} from "../utils/sets";
import {ISpaceSummaryEvent, ISpaceSummaryRoom} from "../components/structures/SpaceRoomDirectory";
import RoomViewStore from "./RoomViewStore";
import { arrayHasOrderChange } from "../utils/arrays";
-import {
- ALPHABET_END,
- ALPHABET_START,
- averageBetweenStrings,
- midPointsBetweenStrings,
-} from "../utils/stringOrderField";
+import { reorderLexicographically } from "../utils/stringOrderField";
interface IState {}
@@ -645,64 +640,18 @@ export class SpaceStoreClass extends AsyncStoreWithClient {
}
public moveRootSpace(fromIndex: number, toIndex: number): void {
- if (
- fromIndex < 0 || toIndex < 0 ||
- fromIndex > this.rootSpaces.length || toIndex > this.rootSpaces.length ||
- fromIndex === toIndex
- ) {
- return;
- }
- const space = this.rootSpaces[fromIndex];
- const orders = this.rootSpaces.map(this.getSpaceTagOrdering);
+ const currentOrders = this.rootSpaces.map(this.getSpaceTagOrdering);
+ const changes = reorderLexicographically(currentOrders, fromIndex, toIndex);
- let prevOrder: string;
- let nextOrder: string;
+ changes.forEach(({ index, order }) => {
+ this.setRootSpaceOrder(this.rootSpaces[index], order);
+ });
- if (toIndex > fromIndex) {
- // moving down
- prevOrder = orders[toIndex];
- nextOrder = orders[toIndex + 1];
+ if (changes.length) {
+ this.notifyIfOrderChanged();
} else {
- // accounts for downwards displacement of existing inhabitant of this index
- prevOrder = toIndex > 0 ? orders[toIndex - 1] : String.fromCharCode(ALPHABET_START).repeat(5); // TODO
- nextOrder = orders[toIndex];
+ // TODO
}
- console.log("@@ start", {fromIndex, toIndex, orders, prevOrder, nextOrder});
-
- if (prevOrder === undefined) {
- // to be able to move to this toIndex we will first need to insert a bunch of orders for earlier elements
- const firstUndefinedIndex = orders.indexOf(undefined);
- const numUndefined = orders.length - firstUndefinedIndex;
- const lastOrder = orders[firstUndefinedIndex - 1] ?? String.fromCharCode(ALPHABET_START); // TODO
- nextOrder = String.fromCharCode(ALPHABET_END).repeat(lastOrder.length + 1);
- const newOrders = midPointsBetweenStrings(lastOrder, nextOrder, numUndefined);
-
- if (newOrders.length === numUndefined) {
- console.log("@@ precalc", {firstUndefinedIndex, numUndefined, lastOrder, newOrders});
- for (let i = firstUndefinedIndex, j = 0; i <= toIndex; i++, j++) {
- if (i === toIndex && toIndex < fromIndex) continue;
- if (i === fromIndex) continue;
- const newOrder = newOrders[j];
- console.log("@@ preset", {i, j, newOrder});
- this.setRootSpaceOrder(this.rootSpaces[i], newOrder);
- }
-
- prevOrder = newOrders[newOrders.length - 1];
- } else {
- prevOrder = nextOrder; // rebuild
- }
- }
-
- if (prevOrder !== nextOrder) {
- const order = averageBetweenStrings(prevOrder, nextOrder ?? String.fromCharCode(ALPHABET_END).repeat(prevOrder.length + 1));
- console.log("@@ set", {prevOrder, nextOrder, order});
- this.setRootSpaceOrder(space, order);
- } else {
- // TODO REBUILD
- }
-
- this.notifyIfOrderChanged();
- console.log("@@ done", this.rootSpaces.map(this.getSpaceTagOrdering));
}
}
diff --git a/src/utils/arrays.ts b/src/utils/arrays.ts
index e527f43c29..d319631d93 100644
--- a/src/utils/arrays.ts
+++ b/src/utils/arrays.ts
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
-import {percentageOf, percentageWithin} from "./numbers";
+import { percentageOf, percentageWithin } from "./numbers";
/**
* Quickly resample an array to have less/more data points. If an input which is larger
@@ -223,6 +223,21 @@ export function arrayMerge(...a: T[][]): T[] {
}, new Set()));
}
+/**
+ * Moves a single element from fromIndex to toIndex.
+ * @param list the list from which to construct the new list.
+ * @param fromIndex the index of the element to move.
+ * @param toIndex the index of where to put the element.
+ * @returns A new array with the requested value moved.
+ */
+export function reorder(list: T[], fromIndex: number, toIndex: number): T[] {
+ const result = Array.from(list);
+ const [removed] = result.splice(fromIndex, 1);
+ result.splice(toIndex, 0, removed);
+
+ return result;
+}
+
/**
* Helper functions to perform LINQ-like queries on arrays.
*/
diff --git a/src/utils/stringOrderField.ts b/src/utils/stringOrderField.ts
index fce859ddb8..ab65a46cb2 100644
--- a/src/utils/stringOrderField.ts
+++ b/src/utils/stringOrderField.ts
@@ -14,6 +14,8 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
+import { reorder } from "./arrays";
+
export const ALPHABET_START = 0x20;
export const ALPHABET_END = 0x7E;
export const ALPHABET = new Array(1 + ALPHABET_END - ALPHABET_START)
@@ -54,3 +56,80 @@ export const midPointsBetweenStrings = (a: string, b: string, count: number, alp
}
return Array(count).fill(undefined).map((_, i) => baseToString(aBase + step + (i * step), alphabet));
};
+
+interface IEntry {
+ index: number;
+ order: string;
+}
+
+export const reorderLexicographically = (
+ orders: Array,
+ fromIndex: number,
+ toIndex: number,
+): IEntry[] => {
+ if (
+ fromIndex < 0 || toIndex < 0 ||
+ fromIndex > orders.length || toIndex > orders.length ||
+ fromIndex === toIndex
+ ) {
+ return [];
+ }
+
+ const ordersWithIndices: IEntry[] = orders.map((order, index) => ({ index, order }));
+ const newOrder = reorder(ordersWithIndices, fromIndex, toIndex);
+
+ const isMoveTowardsRight = toIndex > fromIndex;
+ const orderToLeftUndefined = newOrder[toIndex - 1]?.order === undefined;
+
+ let leftBoundIdx = toIndex;
+ let rightBoundIdx = toIndex;
+
+ const canDisplaceLeft = isMoveTowardsRight || orderToLeftUndefined || true; // TODO
+ if (canDisplaceLeft) {
+ const nextBase = newOrder[toIndex + 1]?.order !== undefined
+ ? stringToBase(newOrder[toIndex + 1].order)
+ : Number.MAX_VALUE;
+ for (let i = toIndex - 1, j = 0; i >= 0; i--, j++) {
+ if (newOrder[i]?.order !== undefined && nextBase - stringToBase(newOrder[i].order) > j) break;
+ leftBoundIdx = i;
+ }
+ }
+
+ const canDisplaceRight = !orderToLeftUndefined;
+ // TODO check if there is enough space on the right hand side at all,
+ // I guess find the last set order and then compare it to prevBase + $requiredGap
+ if (canDisplaceRight) {
+ const prevBase = newOrder[toIndex - 1]?.order !== undefined
+ ? stringToBase(newOrder[toIndex - 1]?.order)
+ : Number.MIN_VALUE;
+ for (let i = toIndex + 1, j = 0; i < newOrder.length; i++, j++) {
+ if (newOrder[i]?.order === undefined || stringToBase(newOrder[i].order) - prevBase > j) break; // TODO verify
+ rightBoundIdx = i;
+ }
+ }
+
+ const leftDiff = toIndex - leftBoundIdx;
+ const rightDiff = rightBoundIdx - toIndex;
+
+ if (orderToLeftUndefined || leftDiff < rightDiff) {
+ rightBoundIdx = toIndex;
+ } else {
+ leftBoundIdx = toIndex;
+ }
+
+ const prevOrder = newOrder[leftBoundIdx - 1]?.order
+ ?? String.fromCharCode(ALPHABET_START).repeat(5); // TODO
+ const nextOrder = newOrder[rightBoundIdx + 1]?.order
+ ?? String.fromCharCode(ALPHABET_END).repeat(prevOrder.length + 1); // TODO
+
+ const changes = midPointsBetweenStrings(prevOrder, nextOrder, 1 + rightBoundIdx - leftBoundIdx);
+ // TODO If we exceed maxLen then reorder EVERYTHING
+
+ console.log("@@ test", { prevOrder, nextOrder, changes, leftBoundIdx, rightBoundIdx, orders, fromIndex, toIndex, newOrder, orderToLeftUndefined });
+
+ return changes.map((order, i) => {
+ const index = newOrder[leftBoundIdx + i].index;
+
+ return { index, order };
+ });
+};
diff --git a/test/utils/stringOrderField-test.ts b/test/utils/stringOrderField-test.ts
index 5b8c2f3feb..8e3ae06b79 100644
--- a/test/utils/stringOrderField-test.ts
+++ b/test/utils/stringOrderField-test.ts
@@ -14,14 +14,36 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
+import { sortBy } from "lodash";
+
import {
ALPHABET,
averageBetweenStrings,
baseToString,
midPointsBetweenStrings,
+ reorderLexicographically,
stringToBase,
} from "../../src/utils/stringOrderField";
+const moveLexicographicallyTest = (
+ orders: Array,
+ fromIndex: number,
+ toIndex: number,
+ expectedIndices: number[],
+): void => {
+ const ops = reorderLexicographically(orders, fromIndex, toIndex);
+ expect(ops.map(o => o.index).sort()).toStrictEqual(expectedIndices.sort());
+
+ const zipped: Array<[number, string | undefined]> = orders.map((o, i) => [i, o]);
+ ops.forEach(({ index, order }) => {
+ zipped[index][1] = order;
+ });
+
+ const newOrders = sortBy(zipped, i => i[1]);
+ console.log("@@ moveLexicographicallyTest", {orders, zipped, newOrders, fromIndex, toIndex, ops});
+ expect(newOrders[toIndex][0]).toBe(fromIndex);
+};
+
describe("stringOrderField", () => {
it("stringToBase", () => {
expect(stringToBase(" ")).toBe(0);
@@ -35,6 +57,9 @@ describe("stringOrderField", () => {
expect(stringToBase("c", "abcdefghijklmnopqrstuvwxyz")).toEqual(2);
expect(stringToBase("ab")).toEqual(6241);
expect(stringToBase("cb", "abcdefghijklmnopqrstuvwxyz")).toEqual(53);
+ expect(stringToBase("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA")).toEqual(4.5115969857961825e+78);
+ expect(stringToBase("~".repeat(50))).toEqual(7.694497527671333e+98);
+ // expect(typeof stringToBase("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA")).toEqual("bigint");
});
it("baseToString", () => {
@@ -57,11 +82,18 @@ describe("stringOrderField", () => {
{ a: "a", b: "z", output: "m", alphabet: "abcdefghijklmnopqrstuvwxyz" },
{ a: "AA", b: "zz", output: "^." },
{ a: "A", b: "z", output: "]" },
+ {
+ a: "A".repeat(50),
+ b: "Z".repeat(50),
+ output: "M}M}M}N ba`54Qpt\\\\Z+kNA#O(9}z>@2jJm]%Y^$m<8lRzz/2[Y",
+ },
].forEach((c) => {
// assert that the output string falls lexicographically between `a` and `b`
expect([c.a, c.b, c.output].sort()[1]).toBe(c.output);
expect(averageBetweenStrings(c.a, c.b, c.alphabet)).toBe(c.output);
});
+
+ expect(averageBetweenStrings("Q#!x+k", "V6yr>L")).toBe("S\\Mu5,");
});
it("midPointsBetweenStrings", () => {
@@ -69,5 +101,49 @@ describe("stringOrderField", () => {
expect(midPointsBetweenStrings("a", "e", 0)).toStrictEqual([]);
expect(midPointsBetweenStrings("a", "e", 4)).toStrictEqual([]);
});
+
+ it("moveLexicographically left", () => {
+ moveLexicographicallyTest(["a", "c", "e", "g", "i"], 2, 1, [2]);
+ });
+
+ it("moveLexicographically right", () => {
+ moveLexicographicallyTest(["a", "c", "e", "g", "i"], 1, 2, [1]);
+ });
+
+ it("moveLexicographically all undefined", () => {
+ moveLexicographicallyTest(
+ [undefined, undefined, undefined, undefined, undefined, undefined],
+ 4,
+ 1,
+ [0, 4],
+ );
+ });
+
+ it("moveLexicographically all undefined to end", () => {
+ moveLexicographicallyTest(
+ [undefined, undefined, undefined, undefined, undefined, undefined],
+ 1,
+ 4,
+ [0, 1, 2, 3, 4],
+ );
+ });
+
+ it("moveLexicographically some undefined move left", () => {
+ moveLexicographicallyTest(
+ ["a", "c", "e", undefined, undefined, undefined],
+ 5,
+ 2,
+ [5],
+ );
+ });
+
+ it("moveLexicographically some undefined move left close", () => {
+ moveLexicographicallyTest(
+ ["a", "a", "e", undefined, undefined, undefined],
+ 5,
+ 1,
+ [1, 5],
+ );
+ });
});
From 3d4411390f0f041241768dad7524f9e2cb465983 Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Fri, 11 Jun 2021 16:28:07 +0100
Subject: [PATCH 035/164] write a shedload more tests
---
src/utils/stringOrderField.ts | 46 ++++---
test/utils/stringOrderField-test.ts | 191 ++++++++++++++++++++++------
2 files changed, 181 insertions(+), 56 deletions(-)
diff --git a/src/utils/stringOrderField.ts b/src/utils/stringOrderField.ts
index ab65a46cb2..8c5d7260e7 100644
--- a/src/utils/stringOrderField.ts
+++ b/src/utils/stringOrderField.ts
@@ -39,21 +39,32 @@ export const stringToBase = (str: string, alphabet = ALPHABET): number => {
const pad = (str: string, length: number, alphabet = ALPHABET): string => str.padEnd(length, alphabet[0]);
-export const averageBetweenStrings = (a: string, b: string, alphabet = ALPHABET): string => {
- const n = Math.max(a.length, b.length);
- const aBase = stringToBase(pad(a, n, alphabet), alphabet);
- const bBase = stringToBase(pad(b, n, alphabet), alphabet);
- return baseToString((aBase + bBase) / 2, alphabet);
-};
-
-export const midPointsBetweenStrings = (a: string, b: string, count: number, alphabet = ALPHABET): string[] => {
- const n = Math.max(a.length, b.length);
- const aBase = stringToBase(pad(a, n, alphabet), alphabet);
- const bBase = stringToBase(pad(b, n, alphabet), alphabet);
- const step = (bBase - aBase) / (count + 1);
- if (step < 1) {
+export const midPointsBetweenStrings = (
+ a: string,
+ b: string,
+ count: number,
+ maxLen: number,
+ alphabet = ALPHABET,
+): string[] => {
+ const n = Math.min(maxLen, Math.max(a.length, b.length));
+ const aPadded = pad(a, n, alphabet);
+ const bPadded = pad(b, n, alphabet);
+ const aBase = stringToBase(aPadded, alphabet);
+ const bBase = stringToBase(bPadded, alphabet);
+ if (bBase - aBase - 1 < count) {
+ if (n < maxLen) {
+ // this recurses once at most due to the new limit of n+1
+ return midPointsBetweenStrings(
+ pad(aPadded, n + 1, alphabet),
+ pad(bPadded, n + 1, alphabet),
+ count,
+ n + 1,
+ alphabet,
+ );
+ }
return [];
}
+ const step = (bBase - aBase) / (count + 1);
return Array(count).fill(undefined).map((_, i) => baseToString(aBase + step + (i * step), alphabet));
};
@@ -66,6 +77,7 @@ export const reorderLexicographically = (
orders: Array,
fromIndex: number,
toIndex: number,
+ maxLen = 50,
): IEntry[] => {
if (
fromIndex < 0 || toIndex < 0 ||
@@ -89,7 +101,7 @@ export const reorderLexicographically = (
const nextBase = newOrder[toIndex + 1]?.order !== undefined
? stringToBase(newOrder[toIndex + 1].order)
: Number.MAX_VALUE;
- for (let i = toIndex - 1, j = 0; i >= 0; i--, j++) {
+ for (let i = toIndex - 1, j = 1; i >= 0; i--, j++) {
if (newOrder[i]?.order !== undefined && nextBase - stringToBase(newOrder[i].order) > j) break;
leftBoundIdx = i;
}
@@ -102,7 +114,7 @@ export const reorderLexicographically = (
const prevBase = newOrder[toIndex - 1]?.order !== undefined
? stringToBase(newOrder[toIndex - 1]?.order)
: Number.MIN_VALUE;
- for (let i = toIndex + 1, j = 0; i < newOrder.length; i++, j++) {
+ for (let i = toIndex + 1, j = 1; i < newOrder.length; i++, j++) {
if (newOrder[i]?.order === undefined || stringToBase(newOrder[i].order) - prevBase > j) break; // TODO verify
rightBoundIdx = i;
}
@@ -122,10 +134,10 @@ export const reorderLexicographically = (
const nextOrder = newOrder[rightBoundIdx + 1]?.order
?? String.fromCharCode(ALPHABET_END).repeat(prevOrder.length + 1); // TODO
- const changes = midPointsBetweenStrings(prevOrder, nextOrder, 1 + rightBoundIdx - leftBoundIdx);
+ const changes = midPointsBetweenStrings(prevOrder, nextOrder, 1 + rightBoundIdx - leftBoundIdx, maxLen);
// TODO If we exceed maxLen then reorder EVERYTHING
- console.log("@@ test", { prevOrder, nextOrder, changes, leftBoundIdx, rightBoundIdx, orders, fromIndex, toIndex, newOrder, orderToLeftUndefined });
+ console.log("@@ test", { prevOrder, nextOrder, changes, leftBoundIdx, rightBoundIdx, orders, fromIndex, toIndex, newOrder, orderToLeftUndefined, leftDiff, rightDiff });
return changes.map((order, i) => {
const index = newOrder[leftBoundIdx + i].index;
diff --git a/test/utils/stringOrderField-test.ts b/test/utils/stringOrderField-test.ts
index 8e3ae06b79..335db028a8 100644
--- a/test/utils/stringOrderField-test.ts
+++ b/test/utils/stringOrderField-test.ts
@@ -18,7 +18,6 @@ import { sortBy } from "lodash";
import {
ALPHABET,
- averageBetweenStrings,
baseToString,
midPointsBetweenStrings,
reorderLexicographically,
@@ -29,10 +28,10 @@ const moveLexicographicallyTest = (
orders: Array,
fromIndex: number,
toIndex: number,
- expectedIndices: number[],
+ expectedChanges: number,
+ maxLength?: number,
): void => {
- const ops = reorderLexicographically(orders, fromIndex, toIndex);
- expect(ops.map(o => o.index).sort()).toStrictEqual(expectedIndices.sort());
+ const ops = reorderLexicographically(orders, fromIndex, toIndex, maxLength);
const zipped: Array<[number, string | undefined]> = orders.map((o, i) => [i, o]);
ops.forEach(({ index, order }) => {
@@ -42,6 +41,7 @@ const moveLexicographicallyTest = (
const newOrders = sortBy(zipped, i => i[1]);
console.log("@@ moveLexicographicallyTest", {orders, zipped, newOrders, fromIndex, toIndex, ops});
expect(newOrders[toIndex][0]).toBe(fromIndex);
+ expect(ops).toHaveLength(expectedChanges);
};
describe("stringOrderField", () => {
@@ -70,44 +70,20 @@ describe("stringOrderField", () => {
expect(baseToString(1234)).toBe(",~");
});
- it("averageBetweenStrings", () => {
- [
- { a: "a", b: "z", output: `m` },
- { a: "ba", b: "z", output: `n@` },
- { a: "z", b: "ba", output: `n@` },
- { a: "# ", b: "$8888", output: `#[[[[` },
- { a: "cat", b: "doggo", output: `d9>Cw` },
- { a: "cat", b: "doggo", output: "cumqh", alphabet: "abcdefghijklmnopqrstuvwxyz" },
- { a: "aa", b: "zz", output: "mz", alphabet: "abcdefghijklmnopqrstuvwxyz" },
- { a: "a", b: "z", output: "m", alphabet: "abcdefghijklmnopqrstuvwxyz" },
- { a: "AA", b: "zz", output: "^." },
- { a: "A", b: "z", output: "]" },
- {
- a: "A".repeat(50),
- b: "Z".repeat(50),
- output: "M}M}M}N ba`54Qpt\\\\Z+kNA#O(9}z>@2jJm]%Y^$m<8lRzz/2[Y",
- },
- ].forEach((c) => {
- // assert that the output string falls lexicographically between `a` and `b`
- expect([c.a, c.b, c.output].sort()[1]).toBe(c.output);
- expect(averageBetweenStrings(c.a, c.b, c.alphabet)).toBe(c.output);
- });
-
- expect(averageBetweenStrings("Q#!x+k", "V6yr>L")).toBe("S\\Mu5,");
- });
-
it("midPointsBetweenStrings", () => {
- expect(midPointsBetweenStrings("a", "e", 3)).toStrictEqual(["b", "c", "d"]);
- expect(midPointsBetweenStrings("a", "e", 0)).toStrictEqual([]);
- expect(midPointsBetweenStrings("a", "e", 4)).toStrictEqual([]);
+ const midpoints = ["a", ...midPointsBetweenStrings("a", "e", 3, 1), "e"].sort();
+ expect(midpoints[0]).toBe("a");
+ expect(midpoints[4]).toBe("e");
+ expect(midPointsBetweenStrings("a", "e", 0, 1)).toStrictEqual([]);
+ expect(midPointsBetweenStrings("a", "e", 4, 1)).toStrictEqual([]);
});
it("moveLexicographically left", () => {
- moveLexicographicallyTest(["a", "c", "e", "g", "i"], 2, 1, [2]);
+ moveLexicographicallyTest(["a", "c", "e", "g", "i"], 2, 1, 1);
});
it("moveLexicographically right", () => {
- moveLexicographicallyTest(["a", "c", "e", "g", "i"], 1, 2, [1]);
+ moveLexicographicallyTest(["a", "c", "e", "g", "i"], 1, 2, 1);
});
it("moveLexicographically all undefined", () => {
@@ -115,7 +91,7 @@ describe("stringOrderField", () => {
[undefined, undefined, undefined, undefined, undefined, undefined],
4,
1,
- [0, 4],
+ 2,
);
});
@@ -124,7 +100,7 @@ describe("stringOrderField", () => {
[undefined, undefined, undefined, undefined, undefined, undefined],
1,
4,
- [0, 1, 2, 3, 4],
+ 5,
);
});
@@ -133,7 +109,7 @@ describe("stringOrderField", () => {
["a", "c", "e", undefined, undefined, undefined],
5,
2,
- [5],
+ 1,
);
});
@@ -142,7 +118,144 @@ describe("stringOrderField", () => {
["a", "a", "e", undefined, undefined, undefined],
5,
1,
- [1, 5],
+ 2,
+ );
+ });
+
+ it("test A moving to the start when all is undefined", () => {
+ moveLexicographicallyTest(
+ [undefined, undefined, undefined, undefined],
+ 2,
+ 0,
+ 1,
+ );
+ });
+
+ it("test B moving to the end when all is undefined", () => {
+ moveLexicographicallyTest(
+ [undefined, undefined, undefined, undefined],
+ 1,
+ 3,
+ 4,
+ );
+ });
+
+ it("test C moving left when all is undefined", () => {
+ moveLexicographicallyTest(
+ [undefined, undefined, undefined, undefined, undefined, undefined],
+ 4,
+ 1,
+ 2,
+ );
+ });
+
+ it("test D moving right when all is undefined", () => {
+ moveLexicographicallyTest(
+ [undefined, undefined, undefined, undefined],
+ 1,
+ 2,
+ 3,
+ );
+ });
+
+ it("test E moving more right when all is undefined", () => {
+ moveLexicographicallyTest(
+ [undefined, undefined, undefined, undefined, undefined, /**/ undefined, undefined],
+ 1,
+ 4,
+ 5,
+ );
+ });
+
+ it("test F moving left when right is undefined", () => {
+ moveLexicographicallyTest(
+ ["20", undefined, undefined, undefined, undefined, undefined],
+ 4,
+ 2,
+ 2,
+ );
+ });
+
+ it("test G moving right when right is undefined", () => {
+ moveLexicographicallyTest(
+ ["50", undefined, undefined, undefined, undefined, /**/ undefined, undefined],
+ 1,
+ 4,
+ 4,
+ );
+ });
+
+ it("test H moving left when right is defined", () => {
+ moveLexicographicallyTest(
+ ["10", "20", "30", "40", undefined, undefined],
+ 3,
+ 1,
+ 1,
+ );
+ });
+
+ it("test I moving right when right is defined", () => {
+ moveLexicographicallyTest(
+ ["10", "20", "30", "40", "50", undefined],
+ 1,
+ 3,
+ 1,
+ );
+ });
+
+ it("test J moving left when all is defined", () => {
+ moveLexicographicallyTest(
+ ["11", "13", "15", "17", "19"],
+ 2,
+ 1,
+ 1,
+ );
+ });
+
+ it("test K moving right when all is defined", () => {
+ moveLexicographicallyTest(
+ ["11", "13", "15", "17", "19"],
+ 1,
+ 2,
+ 1,
+ );
+ });
+
+ it("test L moving left into no left space", () => {
+ moveLexicographicallyTest(
+ ["11", "12", "13", "14", "19"],
+ 3,
+ 1,
+ 2,
+ 2,
+ );
+ });
+
+ it("test M moving right into no right space", () => {
+ moveLexicographicallyTest(
+ ["15", "16", "17", "18", "19"],
+ 1,
+ 3,
+ 2,
+ 2,
+ );
+ });
+
+ it("test N moving right into no left space", () => {
+ moveLexicographicallyTest(
+ ["11", "12", "13", "14", "15", "16", undefined],
+ 1,
+ 3,
+ 3,
+ );
+ });
+
+ it("test O moving left into no right space", () => {
+ moveLexicographicallyTest(
+ ["15", "16", "17", "18", "19"],
+ 4,
+ 3,
+ 2,
);
});
});
From bd71bcca5a83380796ee63400c546edd43499fd5 Mon Sep 17 00:00:00 2001
From: Steffen Kolmer
Date: Fri, 11 Jun 2021 17:38:16 +0200
Subject: [PATCH 036/164] Allow modal widget buttons to be disabled when the
modal opens
---
src/components/views/dialogs/ModalWidgetDialog.tsx | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/src/components/views/dialogs/ModalWidgetDialog.tsx b/src/components/views/dialogs/ModalWidgetDialog.tsx
index 0c474b160c..df2ed6b335 100644
--- a/src/components/views/dialogs/ModalWidgetDialog.tsx
+++ b/src/components/views/dialogs/ModalWidgetDialog.tsx
@@ -63,7 +63,8 @@ export default class ModalWidgetDialog extends React.PureComponent = React.createRef();
state: IState = {
- disabledButtonIds: [],
+ disabledButtonIds: (this.props.widgetDefinition.buttons || []).filter(b => b.disabled)
+ .map(b => b.id),
};
constructor(props) {
From 5ae1b1444f4927f923356709d6eadd8c8c30604b Mon Sep 17 00:00:00 2001
From: Aaron Raimist
Date: Fri, 11 Jun 2021 23:24:05 -0500
Subject: [PATCH 037/164] Open local addresses section by default when there
are no existing local addresses
Signed-off-by: Aaron Raimist
---
src/components/views/room_settings/AliasSettings.js | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/src/components/views/room_settings/AliasSettings.js b/src/components/views/room_settings/AliasSettings.js
index 80e0099ab3..e493cba96f 100644
--- a/src/components/views/room_settings/AliasSettings.js
+++ b/src/components/views/room_settings/AliasSettings.js
@@ -134,6 +134,10 @@ export default class AliasSettings extends React.Component {
}
}
this.setState({ localAliases });
+
+ if (localAliases.length === 0) {
+ this.setState({ detailsOpen: true });
+ }
} finally {
this.setState({ localAliasesLoading: false });
}
@@ -388,7 +392,7 @@ export default class AliasSettings extends React.Component {
/>
{_t("Local Addresses")}
{_t("Set addresses for this room so users can find this room through your homeserver (%(localDomain)s)", {localDomain})}
-
+
{ this.state.detailsOpen ? _t('Show less') : _t("Show more")}
{localAliasesList}
From b8458c0ae323f80525211bdc048a8f01f5f36763 Mon Sep 17 00:00:00 2001
From: Aaron Raimist
Date: Fri, 11 Jun 2021 23:58:16 -0500
Subject: [PATCH 038/164] fix test maybe
Signed-off-by: Aaron Raimist
---
test/end-to-end-tests/src/usecases/room-settings.js | 2 --
1 file changed, 2 deletions(-)
diff --git a/test/end-to-end-tests/src/usecases/room-settings.js b/test/end-to-end-tests/src/usecases/room-settings.js
index abd4488db2..654c461296 100644
--- a/test/end-to-end-tests/src/usecases/room-settings.js
+++ b/test/end-to-end-tests/src/usecases/room-settings.js
@@ -140,8 +140,6 @@ async function changeRoomSettings(session, settings) {
if (settings.alias) {
session.log.step(`sets alias to ${settings.alias}`);
- const summary = await session.query(".mx_RoomSettingsDialog .mx_AliasSettings summary");
- await summary.click();
const aliasField = await session.query(".mx_RoomSettingsDialog .mx_AliasSettings details input[type=text]");
await session.replaceInputText(aliasField, settings.alias.substring(1, settings.alias.lastIndexOf(":")));
const addButton = await session.query(".mx_RoomSettingsDialog .mx_AliasSettings details .mx_AccessibleButton");
From 4af2675e235ec1cee07d4a1c760653b94274b58c Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Mon, 14 Jun 2021 14:37:05 +0100
Subject: [PATCH 039/164] stash bigint support
---
src/utils/stringOrderField.ts | 77 +++++++++------
test/utils/stringOrderField-test.ts | 147 +++++++++++++++++++++-------
2 files changed, 160 insertions(+), 64 deletions(-)
diff --git a/src/utils/stringOrderField.ts b/src/utils/stringOrderField.ts
index 8c5d7260e7..d837dd4cbf 100644
--- a/src/utils/stringOrderField.ts
+++ b/src/utils/stringOrderField.ts
@@ -23,16 +23,17 @@ export const ALPHABET = new Array(1 + ALPHABET_END - ALPHABET_START)
.map((_, i) => String.fromCharCode(ALPHABET_START + i))
.join("");
-export const baseToString = (base: number, alphabet = ALPHABET): string => {
- base = Math.floor(base);
- if (base < alphabet.length) return alphabet[base];
- return baseToString(Math.floor(base / alphabet.length), alphabet) + alphabet[base % alphabet.length];
+export const baseToString = (base: bigint, alphabet = ALPHABET): string => {
+ const len = BigInt(alphabet.length);
+ if (base < len) return alphabet[Number(base)];
+ return baseToString(base / len, alphabet) + alphabet[Number(base % len)];
};
-export const stringToBase = (str: string, alphabet = ALPHABET): number => {
- let result = 0;
- for (let i = str.length - 1, j = 0; i >= 0; i--, j++) {
- result += (str.charCodeAt(i) - alphabet.charCodeAt(0)) * (alphabet.length ** j);
+export const stringToBase = (str: string, alphabet = ALPHABET): bigint => {
+ let result = BigInt(0);
+ const len = BigInt(alphabet.length);
+ for (let i = str.length - 1, j = BigInt(0); i >= 0; i--, j++) {
+ result += BigInt(str.charCodeAt(i) - alphabet.charCodeAt(0)) * (len ** j);
}
return result;
};
@@ -51,7 +52,7 @@ export const midPointsBetweenStrings = (
const bPadded = pad(b, n, alphabet);
const aBase = stringToBase(aPadded, alphabet);
const bBase = stringToBase(bPadded, alphabet);
- if (bBase - aBase - 1 < count) {
+ if (bBase - aBase - BigInt(1) < count) {
if (n < maxLen) {
// this recurses once at most due to the new limit of n+1
return midPointsBetweenStrings(
@@ -64,8 +65,9 @@ export const midPointsBetweenStrings = (
}
return [];
}
- const step = (bBase - aBase) / (count + 1);
- return Array(count).fill(undefined).map((_, i) => baseToString(aBase + step + (i * step), alphabet));
+ const step = (bBase - aBase) / BigInt(count + 1);
+ const start = BigInt(aBase + step);
+ return Array(count).fill(undefined).map((_, i) => baseToString(start + (BigInt(i) * step), alphabet));
};
interface IEntry {
@@ -79,6 +81,7 @@ export const reorderLexicographically = (
toIndex: number,
maxLen = 50,
): IEntry[] => {
+ // sanity check inputs
if (
fromIndex < 0 || toIndex < 0 ||
fromIndex > orders.length || toIndex > orders.length ||
@@ -87,41 +90,56 @@ export const reorderLexicographically = (
return [];
}
+ // zip orders with their indices to simplify later index wrangling
const ordersWithIndices: IEntry[] = orders.map((order, index) => ({ index, order }));
+ // apply the fundamental order update to the zipped array
const newOrder = reorder(ordersWithIndices, fromIndex, toIndex);
- const isMoveTowardsRight = toIndex > fromIndex;
const orderToLeftUndefined = newOrder[toIndex - 1]?.order === undefined;
let leftBoundIdx = toIndex;
let rightBoundIdx = toIndex;
- const canDisplaceLeft = isMoveTowardsRight || orderToLeftUndefined || true; // TODO
- if (canDisplaceLeft) {
- const nextBase = newOrder[toIndex + 1]?.order !== undefined
- ? stringToBase(newOrder[toIndex + 1].order)
- : Number.MAX_VALUE;
- for (let i = toIndex - 1, j = 1; i >= 0; i--, j++) {
- if (newOrder[i]?.order !== undefined && nextBase - stringToBase(newOrder[i].order) > j) break;
- leftBoundIdx = i;
- }
+ let canMoveLeft = true;
+ const nextBase = newOrder[toIndex + 1]?.order !== undefined
+ ? stringToBase(newOrder[toIndex + 1].order)
+ : BigInt(Number.MAX_VALUE);
+
+ for (let i = toIndex - 1, j = 1; i >= 0; i--, j++) {
+ if (newOrder[i]?.order !== undefined && nextBase - stringToBase(newOrder[i].order) > j) break;
+ leftBoundIdx = i;
+ }
+
+ if (leftBoundIdx === 0 &&
+ newOrder[0].order !== undefined &&
+ nextBase - stringToBase(newOrder[0].order) < toIndex
+ ) {
+ canMoveLeft = false;
}
const canDisplaceRight = !orderToLeftUndefined;
- // TODO check if there is enough space on the right hand side at all,
- // I guess find the last set order and then compare it to prevBase + $requiredGap
+ let canMoveRight = canDisplaceRight;
if (canDisplaceRight) {
const prevBase = newOrder[toIndex - 1]?.order !== undefined
? stringToBase(newOrder[toIndex - 1]?.order)
- : Number.MIN_VALUE;
+ : BigInt(Number.MIN_VALUE);
+
for (let i = toIndex + 1, j = 1; i < newOrder.length; i++, j++) {
if (newOrder[i]?.order === undefined || stringToBase(newOrder[i].order) - prevBase > j) break; // TODO verify
rightBoundIdx = i;
}
+
+ if (rightBoundIdx === newOrder.length - 1 &&
+ (newOrder[rightBoundIdx]
+ ? stringToBase(newOrder[rightBoundIdx].order)
+ : BigInt(Number.MAX_VALUE)) - prevBase <= (rightBoundIdx - toIndex)
+ ) {
+ canMoveRight = false;
+ }
}
- const leftDiff = toIndex - leftBoundIdx;
- const rightDiff = rightBoundIdx - toIndex;
+ const leftDiff = canMoveLeft ? toIndex - leftBoundIdx : Number.MAX_SAFE_INTEGER;
+ const rightDiff = canMoveRight ? rightBoundIdx - toIndex : Number.MAX_SAFE_INTEGER;
if (orderToLeftUndefined || leftDiff < rightDiff) {
rightBoundIdx = toIndex;
@@ -130,14 +148,13 @@ export const reorderLexicographically = (
}
const prevOrder = newOrder[leftBoundIdx - 1]?.order
- ?? String.fromCharCode(ALPHABET_START).repeat(5); // TODO
+ ?? String.fromCharCode(ALPHABET_START).repeat(5);
const nextOrder = newOrder[rightBoundIdx + 1]?.order
- ?? String.fromCharCode(ALPHABET_END).repeat(prevOrder.length + 1); // TODO
+ ?? String.fromCharCode(ALPHABET_END).repeat(prevOrder.length);
const changes = midPointsBetweenStrings(prevOrder, nextOrder, 1 + rightBoundIdx - leftBoundIdx, maxLen);
- // TODO If we exceed maxLen then reorder EVERYTHING
- console.log("@@ test", { prevOrder, nextOrder, changes, leftBoundIdx, rightBoundIdx, orders, fromIndex, toIndex, newOrder, orderToLeftUndefined, leftDiff, rightDiff });
+ console.log("@@ test", { canMoveLeft, canMoveRight, prevOrder, nextOrder, changes, leftBoundIdx, rightBoundIdx, orders, fromIndex, toIndex, newOrder, orderToLeftUndefined, leftDiff, rightDiff });
return changes.map((order, i) => {
const index = newOrder[leftBoundIdx + i].index;
diff --git a/test/utils/stringOrderField-test.ts b/test/utils/stringOrderField-test.ts
index 335db028a8..9f92774acb 100644
--- a/test/utils/stringOrderField-test.ts
+++ b/test/utils/stringOrderField-test.ts
@@ -46,28 +46,28 @@ const moveLexicographicallyTest = (
describe("stringOrderField", () => {
it("stringToBase", () => {
- expect(stringToBase(" ")).toBe(0);
- expect(stringToBase("a")).toBe(65);
- expect(stringToBase("aa")).toBe(6240);
- expect(stringToBase("cat")).toBe(610934);
- expect(stringToBase("doggo")).toBe(5607022724);
- expect(stringToBase(" ")).toEqual(0);
- expect(stringToBase("a", "abcdefghijklmnopqrstuvwxyz")).toEqual(0);
- expect(stringToBase("a")).toEqual(65);
- expect(stringToBase("c", "abcdefghijklmnopqrstuvwxyz")).toEqual(2);
- expect(stringToBase("ab")).toEqual(6241);
- expect(stringToBase("cb", "abcdefghijklmnopqrstuvwxyz")).toEqual(53);
- expect(stringToBase("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA")).toEqual(4.5115969857961825e+78);
- expect(stringToBase("~".repeat(50))).toEqual(7.694497527671333e+98);
+ expect(Number(stringToBase(" "))).toBe(0);
+ expect(Number(stringToBase("a"))).toBe(65);
+ expect(Number(stringToBase("aa"))).toBe(6240);
+ expect(Number(stringToBase("cat"))).toBe(610934);
+ expect(Number(stringToBase("doggo"))).toBe(5607022724);
+ expect(Number(stringToBase(" "))).toEqual(0);
+ expect(Number(stringToBase("a", "abcdefghijklmnopqrstuvwxyz"))).toEqual(0);
+ expect(Number(stringToBase("a"))).toEqual(65);
+ expect(Number(stringToBase("c", "abcdefghijklmnopqrstuvwxyz"))).toEqual(2);
+ expect(Number(stringToBase("ab"))).toEqual(6241);
+ expect(Number(stringToBase("cb", "abcdefghijklmnopqrstuvwxyz"))).toEqual(53);
+ expect(Number(stringToBase("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"))).toEqual(4.511596985796182e+78);
+ expect(Number(stringToBase("~".repeat(50)))).toEqual(7.694497527671333e+98);
// expect(typeof stringToBase("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA")).toEqual("bigint");
});
it("baseToString", () => {
- expect(baseToString(10)).toBe(ALPHABET[10]);
- expect(baseToString(10, "abcdefghijklmnopqrstuvwxyz")).toEqual("k");
- expect(baseToString(6241)).toEqual("ab");
- expect(baseToString(53, "abcdefghijklmnopqrstuvwxyz")).toEqual("cb");
- expect(baseToString(1234)).toBe(",~");
+ expect(baseToString(BigInt(10))).toBe(ALPHABET[10]);
+ expect(baseToString(BigInt(10), "abcdefghijklmnopqrstuvwxyz")).toEqual("k");
+ expect(baseToString(BigInt(6241))).toEqual("ab");
+ expect(baseToString(BigInt(53), "abcdefghijklmnopqrstuvwxyz")).toEqual("cb");
+ expect(baseToString(BigInt(1234))).toBe(",~");
});
it("midPointsBetweenStrings", () => {
@@ -122,7 +122,7 @@ describe("stringOrderField", () => {
);
});
- it("test A moving to the start when all is undefined", () => {
+ it("test moving to the start when all is undefined", () => {
moveLexicographicallyTest(
[undefined, undefined, undefined, undefined],
2,
@@ -131,7 +131,7 @@ describe("stringOrderField", () => {
);
});
- it("test B moving to the end when all is undefined", () => {
+ it("test moving to the end when all is undefined", () => {
moveLexicographicallyTest(
[undefined, undefined, undefined, undefined],
1,
@@ -140,7 +140,7 @@ describe("stringOrderField", () => {
);
});
- it("test C moving left when all is undefined", () => {
+ it("test moving left when all is undefined", () => {
moveLexicographicallyTest(
[undefined, undefined, undefined, undefined, undefined, undefined],
4,
@@ -149,7 +149,7 @@ describe("stringOrderField", () => {
);
});
- it("test D moving right when all is undefined", () => {
+ it("test moving right when all is undefined", () => {
moveLexicographicallyTest(
[undefined, undefined, undefined, undefined],
1,
@@ -158,7 +158,7 @@ describe("stringOrderField", () => {
);
});
- it("test E moving more right when all is undefined", () => {
+ it("test moving more right when all is undefined", () => {
moveLexicographicallyTest(
[undefined, undefined, undefined, undefined, undefined, /**/ undefined, undefined],
1,
@@ -167,7 +167,7 @@ describe("stringOrderField", () => {
);
});
- it("test F moving left when right is undefined", () => {
+ it("test moving left when right is undefined", () => {
moveLexicographicallyTest(
["20", undefined, undefined, undefined, undefined, undefined],
4,
@@ -176,7 +176,7 @@ describe("stringOrderField", () => {
);
});
- it("test G moving right when right is undefined", () => {
+ it("test moving right when right is undefined", () => {
moveLexicographicallyTest(
["50", undefined, undefined, undefined, undefined, /**/ undefined, undefined],
1,
@@ -185,7 +185,7 @@ describe("stringOrderField", () => {
);
});
- it("test H moving left when right is defined", () => {
+ it("test moving left when right is defined", () => {
moveLexicographicallyTest(
["10", "20", "30", "40", undefined, undefined],
3,
@@ -194,7 +194,7 @@ describe("stringOrderField", () => {
);
});
- it("test I moving right when right is defined", () => {
+ it("test moving right when right is defined", () => {
moveLexicographicallyTest(
["10", "20", "30", "40", "50", undefined],
1,
@@ -203,7 +203,7 @@ describe("stringOrderField", () => {
);
});
- it("test J moving left when all is defined", () => {
+ it("test moving left when all is defined", () => {
moveLexicographicallyTest(
["11", "13", "15", "17", "19"],
2,
@@ -212,7 +212,7 @@ describe("stringOrderField", () => {
);
});
- it("test K moving right when all is defined", () => {
+ it("test moving right when all is defined", () => {
moveLexicographicallyTest(
["11", "13", "15", "17", "19"],
1,
@@ -221,7 +221,7 @@ describe("stringOrderField", () => {
);
});
- it("test L moving left into no left space", () => {
+ it.skip("test moving left into no left space", () => {
moveLexicographicallyTest(
["11", "12", "13", "14", "19"],
3,
@@ -231,17 +231,17 @@ describe("stringOrderField", () => {
);
});
- it("test M moving right into no right space", () => {
+ it("test moving right into no right space", () => {
moveLexicographicallyTest(
["15", "16", "17", "18", "19"],
1,
3,
- 2,
+ 3,
2,
);
});
- it("test N moving right into no left space", () => {
+ it("test moving right into no left space", () => {
moveLexicographicallyTest(
["11", "12", "13", "14", "15", "16", undefined],
1,
@@ -250,13 +250,92 @@ describe("stringOrderField", () => {
);
});
- it("test O moving left into no right space", () => {
+ it("test moving left into no right space", () => {
moveLexicographicallyTest(
["15", "16", "17", "18", "19"],
4,
3,
+ 4,
2,
);
});
+
+ it("test moving left into no left space", () => {
+ moveLexicographicallyTest(
+ [
+ ALPHABET.charAt(0),
+ ALPHABET.charAt(1),
+ ALPHABET.charAt(2),
+ ALPHABET.charAt(3),
+ ALPHABET.charAt(4),
+ ALPHABET.charAt(5),
+ ],
+ 5,
+ 1,
+ 5,
+ 1,
+ );
+ });
+
+ it("test moving right into no right space", () => {
+ moveLexicographicallyTest(
+ [
+ ALPHABET.charAt(ALPHABET.length - 5),
+ ALPHABET.charAt(ALPHABET.length - 4),
+ ALPHABET.charAt(ALPHABET.length - 3),
+ ALPHABET.charAt(ALPHABET.length - 2),
+ ALPHABET.charAt(ALPHABET.length - 1),
+ ],
+ 1,
+ 3,
+ 3,
+ 1,
+ );
+ });
+
+ it("test moving right into no left space", () => {
+ moveLexicographicallyTest(
+ ["0", "1", "2", "3", "4", "5"],
+ 1,
+ 3,
+ 3,
+ 1,
+ );
+ });
+
+ it("test moving left into no right space", () => {
+ moveLexicographicallyTest(
+ [
+ ALPHABET.charAt(ALPHABET.length - 5),
+ ALPHABET.charAt(ALPHABET.length - 4),
+ ALPHABET.charAt(ALPHABET.length - 3),
+ ALPHABET.charAt(ALPHABET.length - 2),
+ ALPHABET.charAt(ALPHABET.length - 1),
+ ],
+ 4,
+ 3,
+ 4,
+ 1,
+ );
+ });
+
+ const prev = (str: string) => baseToString(stringToBase(str) - BigInt(1));
+ const next = (str: string) => baseToString(stringToBase(str) + BigInt(1));
+
+ it("baseN calculation is correctly consecutive", () => {
+ const str = "this-is-a-test";
+ expect(next(prev(str))).toBe(str);
+ });
+
+ it("rolls over sanely", () => {
+ const maxSpaceValue = "~".repeat(50);
+ const fiftyFirstChar = "!" + " ".repeat(50);
+ expect(next(maxSpaceValue)).toBe(fiftyFirstChar);
+ expect(prev(fiftyFirstChar)).toBe(maxSpaceValue);
+ expect(stringToBase(ALPHABET[0])).toEqual(BigInt(0));
+ expect(stringToBase(ALPHABET[1])).toEqual(BigInt(1));
+ expect(ALPHABET[ALPHABET.length - 1]).toBe("~");
+ expect(ALPHABET[0]).toBe(" ");
+ });
});
From 8fd72fcf795659d5d40e25e066dec0e68ad7efbb Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Mon, 14 Jun 2021 21:28:32 +0100
Subject: [PATCH 040/164] Iterate algorithm, base it on new js-sdk string lib
---
src/utils/stringOrderField.ts | 77 ++++++++----------
test/utils/stringOrderField-test.ts | 121 +++++++++++++++-------------
2 files changed, 98 insertions(+), 100 deletions(-)
diff --git a/src/utils/stringOrderField.ts b/src/utils/stringOrderField.ts
index d837dd4cbf..e09f7fbea4 100644
--- a/src/utils/stringOrderField.ts
+++ b/src/utils/stringOrderField.ts
@@ -14,6 +14,8 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
+import { alphabetPad, baseToString, stringToBase } from "matrix-js-sdk/src/utils";
+
import { reorder } from "./arrays";
export const ALPHABET_START = 0x20;
@@ -23,23 +25,6 @@ export const ALPHABET = new Array(1 + ALPHABET_END - ALPHABET_START)
.map((_, i) => String.fromCharCode(ALPHABET_START + i))
.join("");
-export const baseToString = (base: bigint, alphabet = ALPHABET): string => {
- const len = BigInt(alphabet.length);
- if (base < len) return alphabet[Number(base)];
- return baseToString(base / len, alphabet) + alphabet[Number(base % len)];
-};
-
-export const stringToBase = (str: string, alphabet = ALPHABET): bigint => {
- let result = BigInt(0);
- const len = BigInt(alphabet.length);
- for (let i = str.length - 1, j = BigInt(0); i >= 0; i--, j++) {
- result += BigInt(str.charCodeAt(i) - alphabet.charCodeAt(0)) * (len ** j);
- }
- return result;
-};
-
-const pad = (str: string, length: number, alphabet = ALPHABET): string => str.padEnd(length, alphabet[0]);
-
export const midPointsBetweenStrings = (
a: string,
b: string,
@@ -47,26 +32,28 @@ export const midPointsBetweenStrings = (
maxLen: number,
alphabet = ALPHABET,
): string[] => {
- const n = Math.min(maxLen, Math.max(a.length, b.length));
- const aPadded = pad(a, n, alphabet);
- const bPadded = pad(b, n, alphabet);
- const aBase = stringToBase(aPadded, alphabet);
- const bBase = stringToBase(bPadded, alphabet);
- if (bBase - aBase - BigInt(1) < count) {
- if (n < maxLen) {
+ const padN = Math.min(Math.max(a.length, b.length), maxLen);
+ const padA = alphabetPad(a, padN, alphabet);
+ const padB = alphabetPad(b, padN, alphabet);
+ const baseA = stringToBase(padA, alphabet);
+ const baseB = stringToBase(padB, alphabet);
+
+ if (baseB - baseA - BigInt(1) < count) {
+ if (padN < maxLen) {
// this recurses once at most due to the new limit of n+1
return midPointsBetweenStrings(
- pad(aPadded, n + 1, alphabet),
- pad(bPadded, n + 1, alphabet),
+ alphabetPad(padA, padN + 1, alphabet),
+ alphabetPad(padB, padN + 1, alphabet),
count,
- n + 1,
+ padN + 1,
alphabet,
);
}
return [];
}
- const step = (bBase - aBase) / BigInt(count + 1);
- const start = BigInt(aBase + step);
+
+ const step = (baseB - baseA) / BigInt(count + 1);
+ const start = BigInt(baseA + step);
return Array(count).fill(undefined).map((_, i) => baseToString(start + (BigInt(i) * step), alphabet));
};
@@ -95,6 +82,7 @@ export const reorderLexicographically = (
// apply the fundamental order update to the zipped array
const newOrder = reorder(ordersWithIndices, fromIndex, toIndex);
+ // check if we have to fill undefined orders to complete placement
const orderToLeftUndefined = newOrder[toIndex - 1]?.order === undefined;
let leftBoundIdx = toIndex;
@@ -105,14 +93,19 @@ export const reorderLexicographically = (
? stringToBase(newOrder[toIndex + 1].order)
: BigInt(Number.MAX_VALUE);
+ // check how far left we would have to mutate to fit in that direction
for (let i = toIndex - 1, j = 1; i >= 0; i--, j++) {
if (newOrder[i]?.order !== undefined && nextBase - stringToBase(newOrder[i].order) > j) break;
leftBoundIdx = i;
}
+ // verify the left move would be sufficient
+ const firstOrderBase = newOrder[0].order === undefined ? undefined : stringToBase(newOrder[0].order);
+ const bigToIndex = BigInt(toIndex);
if (leftBoundIdx === 0 &&
- newOrder[0].order !== undefined &&
- nextBase - stringToBase(newOrder[0].order) < toIndex
+ firstOrderBase !== undefined &&
+ nextBase - firstOrderBase <= bigToIndex &&
+ firstOrderBase <= bigToIndex
) {
canMoveLeft = false;
}
@@ -124,11 +117,13 @@ export const reorderLexicographically = (
? stringToBase(newOrder[toIndex - 1]?.order)
: BigInt(Number.MIN_VALUE);
+ // check how far right we would have to mutate to fit in that direction
for (let i = toIndex + 1, j = 1; i < newOrder.length; i++, j++) {
- if (newOrder[i]?.order === undefined || stringToBase(newOrder[i].order) - prevBase > j) break; // TODO verify
+ if (newOrder[i]?.order === undefined || stringToBase(newOrder[i].order) - prevBase > j) break;
rightBoundIdx = i;
}
+ // verify the right move would be sufficient
if (rightBoundIdx === newOrder.length - 1 &&
(newOrder[rightBoundIdx]
? stringToBase(newOrder[rightBoundIdx].order)
@@ -138,27 +133,23 @@ export const reorderLexicographically = (
}
}
+ // pick the cheaper direction
const leftDiff = canMoveLeft ? toIndex - leftBoundIdx : Number.MAX_SAFE_INTEGER;
const rightDiff = canMoveRight ? rightBoundIdx - toIndex : Number.MAX_SAFE_INTEGER;
-
if (orderToLeftUndefined || leftDiff < rightDiff) {
rightBoundIdx = toIndex;
} else {
leftBoundIdx = toIndex;
}
- const prevOrder = newOrder[leftBoundIdx - 1]?.order
- ?? String.fromCharCode(ALPHABET_START).repeat(5);
+ const prevOrder = newOrder[leftBoundIdx - 1]?.order ?? "";
const nextOrder = newOrder[rightBoundIdx + 1]?.order
- ?? String.fromCharCode(ALPHABET_END).repeat(prevOrder.length);
+ ?? String.fromCharCode(ALPHABET_END).repeat(prevOrder.length || 1);
const changes = midPointsBetweenStrings(prevOrder, nextOrder, 1 + rightBoundIdx - leftBoundIdx, maxLen);
- console.log("@@ test", { canMoveLeft, canMoveRight, prevOrder, nextOrder, changes, leftBoundIdx, rightBoundIdx, orders, fromIndex, toIndex, newOrder, orderToLeftUndefined, leftDiff, rightDiff });
-
- return changes.map((order, i) => {
- const index = newOrder[leftBoundIdx + i].index;
-
- return { index, order };
- });
+ return changes.map((order, i) => ({
+ index: newOrder[leftBoundIdx + i].index,
+ order,
+ }));
};
diff --git a/test/utils/stringOrderField-test.ts b/test/utils/stringOrderField-test.ts
index 9f92774acb..d5671ebe76 100644
--- a/test/utils/stringOrderField-test.ts
+++ b/test/utils/stringOrderField-test.ts
@@ -15,13 +15,12 @@ limitations under the License.
*/
import { sortBy } from "lodash";
+import { stringToBase, baseToString, averageBetweenStrings } from "matrix-js-sdk/src/utils";
import {
ALPHABET,
- baseToString,
midPointsBetweenStrings,
reorderLexicographically,
- stringToBase,
} from "../../src/utils/stringOrderField";
const moveLexicographicallyTest = (
@@ -39,43 +38,58 @@ const moveLexicographicallyTest = (
});
const newOrders = sortBy(zipped, i => i[1]);
- console.log("@@ moveLexicographicallyTest", {orders, zipped, newOrders, fromIndex, toIndex, ops});
expect(newOrders[toIndex][0]).toBe(fromIndex);
expect(ops).toHaveLength(expectedChanges);
};
describe("stringOrderField", () => {
it("stringToBase", () => {
- expect(Number(stringToBase(" "))).toBe(0);
- expect(Number(stringToBase("a"))).toBe(65);
- expect(Number(stringToBase("aa"))).toBe(6240);
- expect(Number(stringToBase("cat"))).toBe(610934);
- expect(Number(stringToBase("doggo"))).toBe(5607022724);
- expect(Number(stringToBase(" "))).toEqual(0);
- expect(Number(stringToBase("a", "abcdefghijklmnopqrstuvwxyz"))).toEqual(0);
- expect(Number(stringToBase("a"))).toEqual(65);
- expect(Number(stringToBase("c", "abcdefghijklmnopqrstuvwxyz"))).toEqual(2);
- expect(Number(stringToBase("ab"))).toEqual(6241);
- expect(Number(stringToBase("cb", "abcdefghijklmnopqrstuvwxyz"))).toEqual(53);
- expect(Number(stringToBase("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"))).toEqual(4.511596985796182e+78);
- expect(Number(stringToBase("~".repeat(50)))).toEqual(7.694497527671333e+98);
- // expect(typeof stringToBase("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA")).toEqual("bigint");
+ expect(Number(stringToBase(""))).toBe(0);
+ expect(Number(stringToBase(" "))).toBe(1);
+ expect(Number(stringToBase("a"))).toBe(66);
+ expect(Number(stringToBase(" !"))).toBe(97);
+ expect(Number(stringToBase("aa"))).toBe(6336);
+ expect(Number(stringToBase("cat"))).toBe(620055);
+ expect(Number(stringToBase("doggo"))).toBe(5689339845);
+ expect(Number(stringToBase("a", "abcdefghijklmnopqrstuvwxyz"))).toEqual(1);
+ expect(Number(stringToBase("a"))).toEqual(66);
+ expect(Number(stringToBase("c", "abcdefghijklmnopqrstuvwxyz"))).toEqual(3);
+ expect(Number(stringToBase("ab"))).toEqual(6337);
+ expect(Number(stringToBase("cb", "abcdefghijklmnopqrstuvwxyz"))).toEqual(80);
+ expect(Number(stringToBase("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"))).toEqual(4.648312045971824e+78);
+ expect(Number(stringToBase("~".repeat(50)))).toEqual(7.776353884348688e+98);
+ expect(Number(stringToBase(" "))).toEqual(7820126496);
+ expect(Number(stringToBase(" "))).toEqual(96);
+ expect(Number(stringToBase(" !"))).toEqual(97);
+ expect(Number(stringToBase("S:J\\~"))).toEqual(4258975590);
+ expect(Number(stringToBase("!'Tu:}"))).toEqual(16173443434);
});
it("baseToString", () => {
- expect(baseToString(BigInt(10))).toBe(ALPHABET[10]);
- expect(baseToString(BigInt(10), "abcdefghijklmnopqrstuvwxyz")).toEqual("k");
- expect(baseToString(BigInt(6241))).toEqual("ab");
- expect(baseToString(BigInt(53), "abcdefghijklmnopqrstuvwxyz")).toEqual("cb");
- expect(baseToString(BigInt(1234))).toBe(",~");
+ expect(baseToString(BigInt(10))).toBe(ALPHABET[9]);
+ expect(baseToString(BigInt(10), "abcdefghijklmnopqrstuvwxyz")).toEqual("j");
+ expect(baseToString(BigInt(6241))).toEqual("`a");
+ expect(baseToString(BigInt(53), "abcdefghijklmnopqrstuvwxyz")).toEqual("ba");
+ expect(baseToString(BigInt(1234))).toBe("+}");
+ expect(baseToString(BigInt(0))).toBe(""); // TODO
+ expect(baseToString(BigInt(1))).toBe(" ");
+ expect(baseToString(BigInt(95))).toBe("~");
+ expect(baseToString(BigInt(96))).toBe(" ");
+ expect(baseToString(BigInt(97))).toBe(" !");
+ expect(baseToString(BigInt(98))).toBe(' "');
+ expect(baseToString(BigInt(1))).toBe(" ");
});
it("midPointsBetweenStrings", () => {
+ expect(averageBetweenStrings("!!", "##")).toBe('""');
const midpoints = ["a", ...midPointsBetweenStrings("a", "e", 3, 1), "e"].sort();
expect(midpoints[0]).toBe("a");
expect(midpoints[4]).toBe("e");
expect(midPointsBetweenStrings("a", "e", 0, 1)).toStrictEqual([]);
expect(midPointsBetweenStrings("a", "e", 4, 1)).toStrictEqual([]);
+ expect(midPointsBetweenStrings(" ", "!'Tu:}", 1, 50)).toStrictEqual([" S:J\\~"]);
+ expect(averageBetweenStrings(" ", "!!")).toBe(" P");
+ expect(averageBetweenStrings("! ", "!!")).toBe("! ");
});
it("moveLexicographically left", () => {
@@ -221,7 +235,7 @@ describe("stringOrderField", () => {
);
});
- it.skip("test moving left into no left space", () => {
+ it("test moving left into no left space", () => {
moveLexicographicallyTest(
["11", "12", "13", "14", "19"],
3,
@@ -229,41 +243,11 @@ describe("stringOrderField", () => {
2,
2,
);
- });
- it("test moving right into no right space", () => {
- moveLexicographicallyTest(
- ["15", "16", "17", "18", "19"],
- 1,
- 3,
- 3,
- 2,
- );
- });
-
- it("test moving right into no left space", () => {
- moveLexicographicallyTest(
- ["11", "12", "13", "14", "15", "16", undefined],
- 1,
- 3,
- 3,
- );
- });
-
- it("test moving left into no right space", () => {
- moveLexicographicallyTest(
- ["15", "16", "17", "18", "19"],
- 4,
- 3,
- 4,
- 2,
- );
- });
-
- it("test moving left into no left space", () => {
moveLexicographicallyTest(
[
ALPHABET.charAt(0),
+ // Target
ALPHABET.charAt(1),
ALPHABET.charAt(2),
ALPHABET.charAt(3),
@@ -278,6 +262,14 @@ describe("stringOrderField", () => {
});
it("test moving right into no right space", () => {
+ moveLexicographicallyTest(
+ ["15", "16", "17", "18", "19"],
+ 1,
+ 3,
+ 3,
+ 2,
+ );
+
moveLexicographicallyTest(
[
ALPHABET.charAt(ALPHABET.length - 5),
@@ -294,6 +286,13 @@ describe("stringOrderField", () => {
});
it("test moving right into no left space", () => {
+ moveLexicographicallyTest(
+ ["11", "12", "13", "14", "15", "16", undefined],
+ 1,
+ 3,
+ 3,
+ );
+
moveLexicographicallyTest(
["0", "1", "2", "3", "4", "5"],
1,
@@ -304,6 +303,14 @@ describe("stringOrderField", () => {
});
it("test moving left into no right space", () => {
+ moveLexicographicallyTest(
+ ["15", "16", "17", "18", "19"],
+ 4,
+ 3,
+ 4,
+ 2,
+ );
+
moveLexicographicallyTest(
[
ALPHABET.charAt(ALPHABET.length - 5),
@@ -329,11 +336,11 @@ describe("stringOrderField", () => {
it("rolls over sanely", () => {
const maxSpaceValue = "~".repeat(50);
- const fiftyFirstChar = "!" + " ".repeat(50);
+ const fiftyFirstChar = " ".repeat(51);
expect(next(maxSpaceValue)).toBe(fiftyFirstChar);
expect(prev(fiftyFirstChar)).toBe(maxSpaceValue);
- expect(stringToBase(ALPHABET[0])).toEqual(BigInt(0));
- expect(stringToBase(ALPHABET[1])).toEqual(BigInt(1));
+ expect(Number(stringToBase(ALPHABET[0]))).toEqual(1);
+ expect(Number(stringToBase(ALPHABET[1]))).toEqual(2);
expect(ALPHABET[ALPHABET.length - 1]).toBe("~");
expect(ALPHABET[0]).toBe(" ");
});
From 2879b9086c1ace24fc3307e6af5b404fff0059c2 Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Mon, 14 Jun 2021 21:32:11 +0100
Subject: [PATCH 041/164] Use alphabet from js-sdk
---
src/utils/stringOrderField.ts | 13 ++------
test/utils/stringOrderField-test.ts | 50 +++++++++++++----------------
2 files changed, 26 insertions(+), 37 deletions(-)
diff --git a/src/utils/stringOrderField.ts b/src/utils/stringOrderField.ts
index e09f7fbea4..4336583b9d 100644
--- a/src/utils/stringOrderField.ts
+++ b/src/utils/stringOrderField.ts
@@ -14,23 +14,16 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
-import { alphabetPad, baseToString, stringToBase } from "matrix-js-sdk/src/utils";
+import { alphabetPad, baseToString, stringToBase, DEFAULT_ALPHABET } from "matrix-js-sdk/src/utils";
import { reorder } from "./arrays";
-export const ALPHABET_START = 0x20;
-export const ALPHABET_END = 0x7E;
-export const ALPHABET = new Array(1 + ALPHABET_END - ALPHABET_START)
- .fill(undefined)
- .map((_, i) => String.fromCharCode(ALPHABET_START + i))
- .join("");
-
export const midPointsBetweenStrings = (
a: string,
b: string,
count: number,
maxLen: number,
- alphabet = ALPHABET,
+ alphabet = DEFAULT_ALPHABET,
): string[] => {
const padN = Math.min(Math.max(a.length, b.length), maxLen);
const padA = alphabetPad(a, padN, alphabet);
@@ -144,7 +137,7 @@ export const reorderLexicographically = (
const prevOrder = newOrder[leftBoundIdx - 1]?.order ?? "";
const nextOrder = newOrder[rightBoundIdx + 1]?.order
- ?? String.fromCharCode(ALPHABET_END).repeat(prevOrder.length || 1);
+ ?? DEFAULT_ALPHABET.charAt(DEFAULT_ALPHABET.length - 1).repeat(prevOrder.length || 1);
const changes = midPointsBetweenStrings(prevOrder, nextOrder, 1 + rightBoundIdx - leftBoundIdx, maxLen);
diff --git a/test/utils/stringOrderField-test.ts b/test/utils/stringOrderField-test.ts
index d5671ebe76..a8bc00eeb9 100644
--- a/test/utils/stringOrderField-test.ts
+++ b/test/utils/stringOrderField-test.ts
@@ -15,13 +15,9 @@ limitations under the License.
*/
import { sortBy } from "lodash";
-import { stringToBase, baseToString, averageBetweenStrings } from "matrix-js-sdk/src/utils";
+import { stringToBase, baseToString, averageBetweenStrings, DEFAULT_ALPHABET } from "matrix-js-sdk/src/utils";
-import {
- ALPHABET,
- midPointsBetweenStrings,
- reorderLexicographically,
-} from "../../src/utils/stringOrderField";
+import { midPointsBetweenStrings, reorderLexicographically } from "../../src/utils/stringOrderField";
const moveLexicographicallyTest = (
orders: Array,
@@ -66,7 +62,7 @@ describe("stringOrderField", () => {
});
it("baseToString", () => {
- expect(baseToString(BigInt(10))).toBe(ALPHABET[9]);
+ expect(baseToString(BigInt(10))).toBe(DEFAULT_ALPHABET[9]);
expect(baseToString(BigInt(10), "abcdefghijklmnopqrstuvwxyz")).toEqual("j");
expect(baseToString(BigInt(6241))).toEqual("`a");
expect(baseToString(BigInt(53), "abcdefghijklmnopqrstuvwxyz")).toEqual("ba");
@@ -246,13 +242,13 @@ describe("stringOrderField", () => {
moveLexicographicallyTest(
[
- ALPHABET.charAt(0),
+ DEFAULT_ALPHABET.charAt(0),
// Target
- ALPHABET.charAt(1),
- ALPHABET.charAt(2),
- ALPHABET.charAt(3),
- ALPHABET.charAt(4),
- ALPHABET.charAt(5),
+ DEFAULT_ALPHABET.charAt(1),
+ DEFAULT_ALPHABET.charAt(2),
+ DEFAULT_ALPHABET.charAt(3),
+ DEFAULT_ALPHABET.charAt(4),
+ DEFAULT_ALPHABET.charAt(5),
],
5,
1,
@@ -272,11 +268,11 @@ describe("stringOrderField", () => {
moveLexicographicallyTest(
[
- ALPHABET.charAt(ALPHABET.length - 5),
- ALPHABET.charAt(ALPHABET.length - 4),
- ALPHABET.charAt(ALPHABET.length - 3),
- ALPHABET.charAt(ALPHABET.length - 2),
- ALPHABET.charAt(ALPHABET.length - 1),
+ DEFAULT_ALPHABET.charAt(DEFAULT_ALPHABET.length - 5),
+ DEFAULT_ALPHABET.charAt(DEFAULT_ALPHABET.length - 4),
+ DEFAULT_ALPHABET.charAt(DEFAULT_ALPHABET.length - 3),
+ DEFAULT_ALPHABET.charAt(DEFAULT_ALPHABET.length - 2),
+ DEFAULT_ALPHABET.charAt(DEFAULT_ALPHABET.length - 1),
],
1,
3,
@@ -313,11 +309,11 @@ describe("stringOrderField", () => {
moveLexicographicallyTest(
[
- ALPHABET.charAt(ALPHABET.length - 5),
- ALPHABET.charAt(ALPHABET.length - 4),
- ALPHABET.charAt(ALPHABET.length - 3),
- ALPHABET.charAt(ALPHABET.length - 2),
- ALPHABET.charAt(ALPHABET.length - 1),
+ DEFAULT_ALPHABET.charAt(DEFAULT_ALPHABET.length - 5),
+ DEFAULT_ALPHABET.charAt(DEFAULT_ALPHABET.length - 4),
+ DEFAULT_ALPHABET.charAt(DEFAULT_ALPHABET.length - 3),
+ DEFAULT_ALPHABET.charAt(DEFAULT_ALPHABET.length - 2),
+ DEFAULT_ALPHABET.charAt(DEFAULT_ALPHABET.length - 1),
],
4,
3,
@@ -339,10 +335,10 @@ describe("stringOrderField", () => {
const fiftyFirstChar = " ".repeat(51);
expect(next(maxSpaceValue)).toBe(fiftyFirstChar);
expect(prev(fiftyFirstChar)).toBe(maxSpaceValue);
- expect(Number(stringToBase(ALPHABET[0]))).toEqual(1);
- expect(Number(stringToBase(ALPHABET[1]))).toEqual(2);
- expect(ALPHABET[ALPHABET.length - 1]).toBe("~");
- expect(ALPHABET[0]).toBe(" ");
+ expect(Number(stringToBase(DEFAULT_ALPHABET[0]))).toEqual(1);
+ expect(Number(stringToBase(DEFAULT_ALPHABET[1]))).toEqual(2);
+ expect(DEFAULT_ALPHABET[DEFAULT_ALPHABET.length - 1]).toBe("~");
+ expect(DEFAULT_ALPHABET[0]).toBe(" ");
});
});
From b9f86d54c3712bcf438038376a01fb9a90c96e9f Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Mon, 14 Jun 2021 22:07:25 +0100
Subject: [PATCH 042/164] Update yarn.lock
---
yarn.lock | 9 +++++++--
1 file changed, 7 insertions(+), 2 deletions(-)
diff --git a/yarn.lock b/yarn.lock
index 8e6b4fa732..289d33088f 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -6659,7 +6659,7 @@ prompts@^2.0.1:
kleur "^3.0.3"
sisteransi "^1.0.5"
-prop-types@^15.5.8, prop-types@^15.6.0, prop-types@^15.6.1, prop-types@^15.6.2, prop-types@^15.7.0, prop-types@^15.7.2:
+prop-types@^15.6.2, prop-types@^15.7.0, prop-types@^15.7.2:
version "15.7.2"
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5"
integrity sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==
@@ -6809,7 +6809,12 @@ react-focus-lock@^2.5.0:
use-callback-ref "^1.2.1"
use-sidecar "^1.0.1"
-react-is@^16.13.1, react-is@^16.7.0, react-is@^16.8.1, react-is@^16.8.6:
+"react-is@^16.12.0 || ^17.0.0", react-is@^17.0.0, react-is@^17.0.2:
+ version "17.0.2"
+ resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0"
+ integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==
+
+react-is@^16.13.1, react-is@^16.7.0, react-is@^16.8.1:
version "16.13.1"
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
From a63d9220d2474ffd592c728371107bcfda1ae070 Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Tue, 15 Jun 2021 08:26:46 +0100
Subject: [PATCH 043/164] Clear outstanding TODOs
---
src/stores/SpaceStore.tsx | 17 ++++++++++-------
test/utils/stringOrderField-test.ts | 2 +-
2 files changed, 11 insertions(+), 8 deletions(-)
diff --git a/src/stores/SpaceStore.tsx b/src/stores/SpaceStore.tsx
index d0ec573306..9ffb4eb776 100644
--- a/src/stores/SpaceStore.tsx
+++ b/src/stores/SpaceStore.tsx
@@ -634,9 +634,16 @@ export class SpaceStoreClass extends AsyncStoreWithClient {
return sortBy(spaces, [this.getSpaceTagOrdering, "roomId"]);
}
- private setRootSpaceOrder(space: Room, order: string): void {
+ private async setRootSpaceOrder(space: Room, order: string): Promise {
this.spaceOrderLocalEchoMap.set(space.roomId, order);
- this.matrixClient.setRoomAccountData(space.roomId, EventType.SpaceOrder, { order }); // TODO retrying, failure
+ try {
+ await this.matrixClient.setRoomAccountData(space.roomId, EventType.SpaceOrder, { order });
+ } catch (e) {
+ console.log("Failed to set root space order", e);
+ if (this.spaceOrderLocalEchoMap.get(space.roomId) === order) {
+ this.spaceOrderLocalEchoMap.delete(space.roomId);
+ }
+ }
}
public moveRootSpace(fromIndex: number, toIndex: number): void {
@@ -647,11 +654,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient {
this.setRootSpaceOrder(this.rootSpaces[index], order);
});
- if (changes.length) {
- this.notifyIfOrderChanged();
- } else {
- // TODO
- }
+ this.notifyIfOrderChanged();
}
}
diff --git a/test/utils/stringOrderField-test.ts b/test/utils/stringOrderField-test.ts
index a8bc00eeb9..a523872023 100644
--- a/test/utils/stringOrderField-test.ts
+++ b/test/utils/stringOrderField-test.ts
@@ -67,7 +67,7 @@ describe("stringOrderField", () => {
expect(baseToString(BigInt(6241))).toEqual("`a");
expect(baseToString(BigInt(53), "abcdefghijklmnopqrstuvwxyz")).toEqual("ba");
expect(baseToString(BigInt(1234))).toBe("+}");
- expect(baseToString(BigInt(0))).toBe(""); // TODO
+ expect(baseToString(BigInt(0))).toBe("");
expect(baseToString(BigInt(1))).toBe(" ");
expect(baseToString(BigInt(95))).toBe("~");
expect(baseToString(BigInt(96))).toBe(" ");
From 5130d5e111bf32ced37df78096b85467496b9ee2 Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Tue, 15 Jun 2021 12:26:09 +0100
Subject: [PATCH 044/164] Hide addresses section for private spaces
---
.../spaces/SpaceSettingsVisibilityTab.tsx | 27 ++++++++++++-------
1 file changed, 17 insertions(+), 10 deletions(-)
diff --git a/src/components/views/spaces/SpaceSettingsVisibilityTab.tsx b/src/components/views/spaces/SpaceSettingsVisibilityTab.tsx
index 9eb4c6eb02..2f80ad97a6 100644
--- a/src/components/views/spaces/SpaceSettingsVisibilityTab.tsx
+++ b/src/components/views/spaces/SpaceSettingsVisibilityTab.tsx
@@ -120,6 +120,22 @@ const SpaceSettingsVisibilityTab = ({ matrixClient: cli, space }: IProps) => {
>;
}
+ let addressesSection;
+ if (visibility !== SpaceVisibility.Private) {
+ addressesSection = <>
+ {_t("Address")}
+
+ >;
+ }
+
return
{_t("Visibility")}
@@ -164,16 +180,7 @@ const SpaceSettingsVisibilityTab = ({ matrixClient: cli, space }: IProps) => {
{ _t("Recommended for public spaces.") }
- {_t("Address")}
-
+ { addressesSection }
;
};
From 5098a304c9850c139aa73e8e594648b622a4710c Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Tue, 15 Jun 2021 12:33:46 +0100
Subject: [PATCH 045/164] delint
---
src/components/views/dialogs/RoomSettingsDialog.tsx | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/src/components/views/dialogs/RoomSettingsDialog.tsx b/src/components/views/dialogs/RoomSettingsDialog.tsx
index 1a664951c5..303f17c342 100644
--- a/src/components/views/dialogs/RoomSettingsDialog.tsx
+++ b/src/components/views/dialogs/RoomSettingsDialog.tsx
@@ -108,7 +108,10 @@ export default class RoomSettingsDialog extends React.Component {
ROOM_ADVANCED_TAB,
_td("Advanced"),
"mx_RoomSettingsDialog_warningIcon",
- ,
+ this.props.onFinished(true)}
+ />,
));
}
From b3912dc5b8b499528a2316af790b317a8d5df4ee Mon Sep 17 00:00:00 2001
From: RiotRobot
Date: Tue, 15 Jun 2021 16:16:42 +0100
Subject: [PATCH 046/164] Upgrade matrix-js-sdk to 12.0.0-rc.1
---
package.json | 4 ++--
yarn.lock | 7 ++++---
2 files changed, 6 insertions(+), 5 deletions(-)
diff --git a/package.json b/package.json
index d8c26098ca..7ac73a60e4 100644
--- a/package.json
+++ b/package.json
@@ -78,7 +78,7 @@
"katex": "^0.12.0",
"linkifyjs": "^2.1.9",
"lodash": "^4.17.20",
- "matrix-js-sdk": "github:matrix-org/matrix-js-sdk#develop",
+ "matrix-js-sdk": "12.0.0-rc.1",
"matrix-widget-api": "^0.1.0-beta.14",
"minimist": "^1.2.5",
"opus-recorder": "^8.0.3",
@@ -139,12 +139,12 @@
"@types/zxcvbn": "^4.4.0",
"@typescript-eslint/eslint-plugin": "^4.14.0",
"@typescript-eslint/parser": "^4.14.0",
+ "@wojtekmaj/enzyme-adapter-react-17": "^0.6.1",
"babel-eslint": "^10.1.0",
"babel-jest": "^26.6.3",
"chokidar": "^3.5.1",
"concurrently": "^5.3.0",
"enzyme": "^3.11.0",
- "@wojtekmaj/enzyme-adapter-react-17": "^0.6.1",
"eslint": "7.18.0",
"eslint-config-matrix-org": "^0.2.0",
"eslint-plugin-babel": "^5.3.1",
diff --git a/yarn.lock b/yarn.lock
index 7c232d2aa1..14cd11d769 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -5711,9 +5711,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@github:matrix-org/matrix-js-sdk#develop":
- version "11.2.0"
- resolved "https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/35ecbed29d16982deff27a8c37b05167738225a2"
+matrix-js-sdk@12.0.0-rc.1:
+ version "12.0.0-rc.1"
+ resolved "https://registry.yarnpkg.com/matrix-js-sdk/-/matrix-js-sdk-12.0.0-rc.1.tgz#b94a72f0549f3000763efb8c7b6fa1f8808e56f6"
+ integrity sha512-bzozc4w9dF6Dl8xXXLXMpe3FqL/ncczKdB9Y8dL1mPaujVrmLWAai+BYmC9/c4SIw+1zUap9P5W16ej3z7prig==
dependencies:
"@babel/runtime" "^7.12.5"
another-json "^0.2.0"
From cdb9d3a41be4c3252781a50a1913b6db4223bb7d Mon Sep 17 00:00:00 2001
From: RiotRobot
Date: Tue, 15 Jun 2021 16:22:33 +0100
Subject: [PATCH 047/164] Prepare changelog for v3.24.0-rc.1
---
CHANGELOG.md | 118 +++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 118 insertions(+)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 94c9530941..a14a0f308e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,121 @@
+Changes in [3.24.0-rc.1](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v3.24.0-rc.1) (2021-06-15)
+===============================================================================================================
+[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v3.23.0...v3.24.0-rc.1)
+
+ * Upgrade to JS SDK 12.0.0-rc.1
+ * Translations update from Weblate
+ [\#6192](https://github.com/matrix-org/matrix-react-sdk/pull/6192)
+ * Disable comment-on-alert for PR coming from a fork
+ [\#6189](https://github.com/matrix-org/matrix-react-sdk/pull/6189)
+ * Add JS benchmark tracking in CI
+ [\#6177](https://github.com/matrix-org/matrix-react-sdk/pull/6177)
+ * Upgrade matrix-react-test-utils for React 17 peer deps
+ [\#6187](https://github.com/matrix-org/matrix-react-sdk/pull/6187)
+ * Fix display name overlaps on the IRC layout
+ [\#6186](https://github.com/matrix-org/matrix-react-sdk/pull/6186)
+ * Small fixes to the spaces experience
+ [\#6184](https://github.com/matrix-org/matrix-react-sdk/pull/6184)
+ * Add footer and privacy note to the start dm dialog
+ [\#6111](https://github.com/matrix-org/matrix-react-sdk/pull/6111)
+ * Format mxids when disambiguation needed
+ [\#5880](https://github.com/matrix-org/matrix-react-sdk/pull/5880)
+ * Move various createRoom types to the js-sdk
+ [\#6183](https://github.com/matrix-org/matrix-react-sdk/pull/6183)
+ * Fix HTML tag for Event Tile when not rendered in a list
+ [\#6175](https://github.com/matrix-org/matrix-react-sdk/pull/6175)
+ * Remove legacy polyfills and unused dependencies
+ [\#6176](https://github.com/matrix-org/matrix-react-sdk/pull/6176)
+ * Fix buggy hovering/selecting of event tiles
+ [\#6173](https://github.com/matrix-org/matrix-react-sdk/pull/6173)
+ * Add room intro warning when e2ee is not enabled
+ [\#5929](https://github.com/matrix-org/matrix-react-sdk/pull/5929)
+ * Migrate end to end tests to GitHub actions
+ [\#6156](https://github.com/matrix-org/matrix-react-sdk/pull/6156)
+ * Fix expanding last collapsed sticky session when zoomed in
+ [\#6171](https://github.com/matrix-org/matrix-react-sdk/pull/6171)
+ * ⚛️ Upgrade to React@17
+ [\#6165](https://github.com/matrix-org/matrix-react-sdk/pull/6165)
+ * Revert refreshStickyHeaders optimisations
+ [\#6168](https://github.com/matrix-org/matrix-react-sdk/pull/6168)
+ * Add logging for which rooms calls are in
+ [\#6170](https://github.com/matrix-org/matrix-react-sdk/pull/6170)
+ * Restore read receipt animation from event to event
+ [\#6169](https://github.com/matrix-org/matrix-react-sdk/pull/6169)
+ * Restore copy button icon when sharing permalink
+ [\#6166](https://github.com/matrix-org/matrix-react-sdk/pull/6166)
+ * Restore Page Up/Down key bindings when focusing the composer
+ [\#6167](https://github.com/matrix-org/matrix-react-sdk/pull/6167)
+ * Timeline rendering optimizations
+ [\#6143](https://github.com/matrix-org/matrix-react-sdk/pull/6143)
+ * Bump css-what from 5.0.0 to 5.0.1
+ [\#6164](https://github.com/matrix-org/matrix-react-sdk/pull/6164)
+ * Bump ws from 6.2.1 to 6.2.2 in /test/end-to-end-tests
+ [\#6145](https://github.com/matrix-org/matrix-react-sdk/pull/6145)
+ * Bump trim-newlines from 3.0.0 to 3.0.1
+ [\#6163](https://github.com/matrix-org/matrix-react-sdk/pull/6163)
+ * Fix upgrade to element home button in top left menu
+ [\#6162](https://github.com/matrix-org/matrix-react-sdk/pull/6162)
+ * Fix unpinning of pinned messages and panel empty state
+ [\#6140](https://github.com/matrix-org/matrix-react-sdk/pull/6140)
+ * Better handling for widgets that fail to load
+ [\#6161](https://github.com/matrix-org/matrix-react-sdk/pull/6161)
+ * Improved forwarding UI
+ [\#5999](https://github.com/matrix-org/matrix-react-sdk/pull/5999)
+ * Fixes for sharing room links
+ [\#6118](https://github.com/matrix-org/matrix-react-sdk/pull/6118)
+ * Fix setting watchers
+ [\#6160](https://github.com/matrix-org/matrix-react-sdk/pull/6160)
+ * Fix Stickerpicker context menu
+ [\#6152](https://github.com/matrix-org/matrix-react-sdk/pull/6152)
+ * Add warning to private space creation flow
+ [\#6155](https://github.com/matrix-org/matrix-react-sdk/pull/6155)
+ * Add prop to alwaysShowTimestamps on TimelinePanel
+ [\#6159](https://github.com/matrix-org/matrix-react-sdk/pull/6159)
+ * Fix notif panel timestamp padding
+ [\#6157](https://github.com/matrix-org/matrix-react-sdk/pull/6157)
+ * Fixes and refactoring for the ImageView
+ [\#6149](https://github.com/matrix-org/matrix-react-sdk/pull/6149)
+ * Fix timestamps
+ [\#6148](https://github.com/matrix-org/matrix-react-sdk/pull/6148)
+ * Make it easier to pan images in the lightbox
+ [\#6147](https://github.com/matrix-org/matrix-react-sdk/pull/6147)
+ * Fix scroll token for EventTile and EventListSummary node type
+ [\#6154](https://github.com/matrix-org/matrix-react-sdk/pull/6154)
+ * Convert bunch of things to Typescript
+ [\#6153](https://github.com/matrix-org/matrix-react-sdk/pull/6153)
+ * Lint the typescript tests
+ [\#6142](https://github.com/matrix-org/matrix-react-sdk/pull/6142)
+ * Fix jumping to bottom without a highlighted event
+ [\#6146](https://github.com/matrix-org/matrix-react-sdk/pull/6146)
+ * Repair event status position in timeline
+ [\#6141](https://github.com/matrix-org/matrix-react-sdk/pull/6141)
+ * Adapt for js-sdk MatrixClient conversion to TS
+ [\#6132](https://github.com/matrix-org/matrix-react-sdk/pull/6132)
+ * Improve pinned messages in Labs
+ [\#6096](https://github.com/matrix-org/matrix-react-sdk/pull/6096)
+ * Map phone number lookup results to their native rooms
+ [\#6136](https://github.com/matrix-org/matrix-react-sdk/pull/6136)
+ * Fix mx_Event containment rules and empty read avatar row
+ [\#6138](https://github.com/matrix-org/matrix-react-sdk/pull/6138)
+ * Improve switch room rendering
+ [\#6079](https://github.com/matrix-org/matrix-react-sdk/pull/6079)
+ * Add CSS containment rules for shorter reflow operations
+ [\#6127](https://github.com/matrix-org/matrix-react-sdk/pull/6127)
+ * ignore hash/fragment when de-duplicating links for url previews
+ [\#6135](https://github.com/matrix-org/matrix-react-sdk/pull/6135)
+ * Clicking jump to bottom resets room hash
+ [\#5823](https://github.com/matrix-org/matrix-react-sdk/pull/5823)
+ * Use passive option for scroll handlers
+ [\#6113](https://github.com/matrix-org/matrix-react-sdk/pull/6113)
+ * Optimise memberSort performance for large list
+ [\#6130](https://github.com/matrix-org/matrix-react-sdk/pull/6130)
+ * Tweak event border radius to match action bar
+ [\#6133](https://github.com/matrix-org/matrix-react-sdk/pull/6133)
+ * Log when we ignore a second call in a room
+ [\#6131](https://github.com/matrix-org/matrix-react-sdk/pull/6131)
+ * Performance monitoring measurements
+ [\#6041](https://github.com/matrix-org/matrix-react-sdk/pull/6041)
+
Changes in [3.23.0](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v3.23.0) (2021-06-07)
=====================================================================================================
[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v3.23.0-rc.1...v3.23.0)
From 2eb7d35ea16ce593d7e8fd666327d5a2b2bc495c Mon Sep 17 00:00:00 2001
From: RiotRobot
Date: Tue, 15 Jun 2021 16:22:34 +0100
Subject: [PATCH 048/164] v3.24.0-rc.1
---
package.json | 7 ++++---
1 file changed, 4 insertions(+), 3 deletions(-)
diff --git a/package.json b/package.json
index 7ac73a60e4..644793e265 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "matrix-react-sdk",
- "version": "3.23.0",
+ "version": "3.24.0-rc.1",
"description": "SDK for matrix.org using React",
"author": "matrix.org",
"repository": {
@@ -25,7 +25,7 @@
"bin": {
"reskindex": "scripts/reskindex.js"
},
- "main": "./src/index.js",
+ "main": "./lib/index.js",
"matrix_src_main": "./src/index.js",
"matrix_lib_main": "./lib/index.js",
"matrix_lib_typings": "./lib/index.d.ts",
@@ -197,5 +197,6 @@
"coverageReporters": [
"text"
]
- }
+ },
+ "typings": "./lib/index.d.ts"
}
From ea46df0d4841c8c92ed876bae7f2faa98e14da99 Mon Sep 17 00:00:00 2001
From: Travis Ralston
Date: Mon, 7 Jun 2021 20:19:16 -0600
Subject: [PATCH 049/164] Partially restore immutable event objects at the
rendering layer
This is primarily to fix some extremely rare edge cases in local echo, but also restores the accuracy of some comments in the stack regarding immutable event objects (which were made mutable many years ago).
This shouldn't have any impact on the daily usage of the app, only adding a measured 0ms of latency to the stack.
---
src/components/views/messages/TextualBody.js | 1 +
src/components/views/rooms/EventTile.tsx | 124 +++++++++++--------
2 files changed, 76 insertions(+), 49 deletions(-)
diff --git a/src/components/views/messages/TextualBody.js b/src/components/views/messages/TextualBody.js
index 3adfea6ee6..00e7d3d301 100644
--- a/src/components/views/messages/TextualBody.js
+++ b/src/components/views/messages/TextualBody.js
@@ -262,6 +262,7 @@ export default class TextualBody extends React.Component {
// exploit that events are immutable :)
return (nextProps.mxEvent.getId() !== this.props.mxEvent.getId() ||
+ nextProps.mxEvent !== this.props.mxEvent ||
nextProps.highlights !== this.props.highlights ||
nextProps.replacingEventId !== this.props.replacingEventId ||
nextProps.highlightLink !== this.props.highlightLink ||
diff --git a/src/components/views/rooms/EventTile.tsx b/src/components/views/rooms/EventTile.tsx
index 85b9cac2c4..d1b596a709 100644
--- a/src/components/views/rooms/EventTile.tsx
+++ b/src/components/views/rooms/EventTile.tsx
@@ -298,6 +298,9 @@ interface IState {
// The Relations model from the JS SDK for reactions to `mxEvent`
reactions: Relations;
+ // Our snapshotted/local copy of the props.mxEvent, for local echo reasons
+ mxEvent: MatrixEvent;
+
hover: boolean;
}
@@ -332,6 +335,8 @@ export default class EventTile extends React.Component {
// The Relations model from the JS SDK for reactions to `mxEvent`
reactions: this.getReactions(),
+ mxEvent: this.mxEvent.getSnapshotCopy(), // snapshot up front to verify it all works
+
hover: false,
};
@@ -348,6 +353,10 @@ export default class EventTile extends React.Component {
this.ref = React.createRef();
}
+ private get mxEvent(): MatrixEvent {
+ return this.state?.mxEvent ?? this.props.mxEvent;
+ }
+
/**
* When true, the tile qualifies for some sort of special read receipt. This could be a 'sending'
* or 'sent' receipt, for example.
@@ -356,16 +365,16 @@ export default class EventTile extends React.Component {
private get isEligibleForSpecialReceipt() {
// First, if there are other read receipts then just short-circuit this.
if (this.props.readReceipts && this.props.readReceipts.length > 0) return false;
- if (!this.props.mxEvent) return false;
+ if (!this.mxEvent) return false;
// Sanity check (should never happen, but we shouldn't explode if it does)
- const room = this.context.getRoom(this.props.mxEvent.getRoomId());
+ const room = this.context.getRoom(this.mxEvent.getRoomId());
if (!room) return false;
// Quickly check to see if the event was sent by us. If it wasn't, it won't qualify for
// special read receipts.
const myUserId = MatrixClientPeg.get().getUserId();
- if (this.props.mxEvent.getSender() !== myUserId) return false;
+ if (this.mxEvent.getSender() !== myUserId) return false;
// Finally, determine if the type is relevant to the user. This notably excludes state
// events and pretty much anything that can't be sent by the composer as a message. For
@@ -376,7 +385,7 @@ export default class EventTile extends React.Component {
EventType.RoomMessage,
EventType.RoomMessageEncrypted,
];
- if (!simpleSendableEvents.includes(this.props.mxEvent.getType())) return false;
+ if (!simpleSendableEvents.includes(this.mxEvent.getType())) return false;
// Default case
return true;
@@ -418,7 +427,7 @@ export default class EventTile extends React.Component {
// TODO: [REACT-WARNING] Move into constructor
// eslint-disable-next-line camelcase
UNSAFE_componentWillMount() {
- this.verifyEvent(this.props.mxEvent);
+ this.verifyEvent(this.mxEvent);
}
componentDidMount() {
@@ -448,11 +457,21 @@ export default class EventTile extends React.Component {
}
shouldComponentUpdate(nextProps, nextState) {
+ // If the echo changed meaningfully, update.
+ if (!this.state.mxEvent?.isEquivalentTo(nextProps.mxEvent)) {
+ return true;
+ }
+
if (objectHasDiff(this.state, nextState)) {
return true;
}
- return !this.propsEqual(this.props, nextProps);
+ if (!this.propsEqual(this.props, nextProps)) {
+ return true;
+ }
+
+ // Always assume there's no significant change.
+ return false;
}
componentWillUnmount() {
@@ -473,11 +492,18 @@ export default class EventTile extends React.Component {
this.context.on("Room.receipt", this.onRoomReceipt);
this.isListeningForReceipts = true;
}
+
+ // Update the state again if the snapshot needs updating. Note that this will fire
+ // a second state update to re-render child components, which ultimately calls didUpdate
+ // again, so we break that loop with a reference check first (faster than comparing events).
+ if (this.state.mxEvent === prevState.mxEvent && !this.state?.mxEvent.isEquivalentTo(this.props.mxEvent)) {
+ this.setState({mxEvent: this.props.mxEvent.getSnapshotCopy()});
+ }
}
private onRoomReceipt = (ev, room) => {
// ignore events for other rooms
- const tileRoom = MatrixClientPeg.get().getRoom(this.props.mxEvent.getRoomId());
+ const tileRoom = MatrixClientPeg.get().getRoom(this.mxEvent.getRoomId());
if (room !== tileRoom) return;
if (!this.shouldShowSentReceipt && !this.shouldShowSendingReceipt && !this.isListeningForReceipts) {
@@ -501,19 +527,19 @@ export default class EventTile extends React.Component {
// we need to re-verify the sending device.
// (we call onHeightChanged in verifyEvent to handle the case where decryption
// has caused a change in size of the event tile)
- this.verifyEvent(this.props.mxEvent);
+ this.verifyEvent(this.mxEvent);
this.forceUpdate();
};
private onDeviceVerificationChanged = (userId, device) => {
- if (userId === this.props.mxEvent.getSender()) {
- this.verifyEvent(this.props.mxEvent);
+ if (userId === this.mxEvent.getSender()) {
+ this.verifyEvent(this.mxEvent);
}
};
private onUserVerificationChanged = (userId, _trustStatus) => {
- if (userId === this.props.mxEvent.getSender()) {
- this.verifyEvent(this.props.mxEvent);
+ if (userId === this.mxEvent.getSender()) {
+ this.verifyEvent(this.mxEvent);
}
};
@@ -620,11 +646,11 @@ export default class EventTile extends React.Component {
}
shouldHighlight() {
- const actions = this.context.getPushActionsForEvent(this.props.mxEvent.replacingEvent() || this.props.mxEvent);
+ const actions = this.context.getPushActionsForEvent(this.mxEvent.replacingEvent() || this.mxEvent);
if (!actions || !actions.tweaks) { return false; }
// don't show self-highlights from another of our clients
- if (this.props.mxEvent.getSender() === this.context.credentials.userId) {
+ if (this.mxEvent.getSender() === this.context.credentials.userId) {
return false;
}
@@ -639,7 +665,7 @@ export default class EventTile extends React.Component {
getReadAvatars() {
if (this.shouldShowSentReceipt || this.shouldShowSendingReceipt) {
- return ;
+ return ;
}
// return early if there are no read receipts
@@ -726,7 +752,7 @@ export default class EventTile extends React.Component {
}
onSenderProfileClick = event => {
- const mxEvent = this.props.mxEvent;
+ const mxEvent = this.mxEvent;
dis.dispatch({
action: 'insert_mention',
user_id: mxEvent.getSender(),
@@ -743,7 +769,7 @@ export default class EventTile extends React.Component {
// Cancel any outgoing key request for this event and resend it. If a response
// is received for the request with the required keys, the event could be
// decrypted successfully.
- this.context.cancelAndResendEventRoomKeyRequest(this.props.mxEvent);
+ this.context.cancelAndResendEventRoomKeyRequest(this.mxEvent);
};
onPermalinkClicked = e => {
@@ -752,14 +778,14 @@ export default class EventTile extends React.Component {
e.preventDefault();
dis.dispatch({
action: 'view_room',
- event_id: this.props.mxEvent.getId(),
+ event_id: this.mxEvent.getId(),
highlighted: true,
- room_id: this.props.mxEvent.getRoomId(),
+ room_id: this.mxEvent.getRoomId(),
});
};
private renderE2EPadlock() {
- const ev = this.props.mxEvent;
+ const ev = this.mxEvent;
// event could not be decrypted
if (ev.getContent().msgtype === 'm.bad.encrypted') {
@@ -818,7 +844,7 @@ export default class EventTile extends React.Component {
) {
return null;
}
- const eventId = this.props.mxEvent.getId();
+ const eventId = this.mxEvent.getId();
return this.props.getRelationsForEvent(eventId, "m.annotation", "m.reaction");
};
@@ -837,13 +863,13 @@ export default class EventTile extends React.Component {
const SenderProfile = sdk.getComponent('messages.SenderProfile');
const MemberAvatar = sdk.getComponent('avatars.MemberAvatar');
- //console.info("EventTile showUrlPreview for %s is %s", this.props.mxEvent.getId(), this.props.showUrlPreview);
+ //console.info("EventTile showUrlPreview for %s is %s", this.mxEvent.getId(), this.props.showUrlPreview);
- const content = this.props.mxEvent.getContent();
+ const content = this.mxEvent.getContent();
const msgtype = content.msgtype;
- const eventType = this.props.mxEvent.getType();
+ const eventType = this.mxEvent.getType();
- let tileHandler = getHandlerTile(this.props.mxEvent);
+ let tileHandler = getHandlerTile(this.mxEvent);
// Info messages are basically information about commands processed on a room
const isBubbleMessage = eventType.startsWith("m.key.verification") ||
@@ -860,7 +886,7 @@ export default class EventTile extends React.Component {
// source tile when there's no regular tile for an event and also for
// replace relations (which otherwise would display as a confusing
// duplicate of the thing they are replacing).
- if (SettingsStore.getValue("showHiddenEventsInTimeline") && !haveTileForEvent(this.props.mxEvent)) {
+ if (SettingsStore.getValue("showHiddenEventsInTimeline") && !haveTileForEvent(this.mxEvent)) {
tileHandler = "messages.ViewSourceEvent";
// Reuse info message avatar and sender profile styling
isInfoMessage = true;
@@ -879,8 +905,8 @@ export default class EventTile extends React.Component {
const EventTileType = sdk.getComponent(tileHandler);
const isSending = (['sending', 'queued', 'encrypting'].indexOf(this.props.eventSendStatus) !== -1);
- const isRedacted = isMessageEvent(this.props.mxEvent) && this.props.isRedacted;
- const isEncryptionFailure = this.props.mxEvent.isDecryptionFailure();
+ const isRedacted = isMessageEvent(this.mxEvent) && this.props.isRedacted;
+ const isEncryptionFailure = this.mxEvent.isDecryptionFailure();
const isEditing = !!this.props.editState;
const classes = classNames({
@@ -910,14 +936,14 @@ export default class EventTile extends React.Component {
let permalink = "#";
if (this.props.permalinkCreator) {
- permalink = this.props.permalinkCreator.forEvent(this.props.mxEvent.getId());
+ permalink = this.props.permalinkCreator.forEvent(this.mxEvent.getId());
}
// we can't use local echoes as scroll tokens, because their event IDs change.
// Local echos have a send "status".
- const scrollToken = this.props.mxEvent.status
+ const scrollToken = this.mxEvent.status
? undefined
- : this.props.mxEvent.getId();
+ : this.mxEvent.getId();
let avatar;
let sender;
@@ -947,15 +973,15 @@ export default class EventTile extends React.Component {
needsSenderProfile = true;
}
- if (this.props.mxEvent.sender && avatarSize) {
+ if (this.mxEvent.sender && avatarSize) {
let member;
// set member to receiver (target) if it is a 3PID invite
// so that the correct avatar is shown as the text is
// `$target accepted the invitation for $email`
- if (this.props.mxEvent.getContent().third_party_invite) {
- member = this.props.mxEvent.target;
+ if (this.mxEvent.getContent().third_party_invite) {
+ member = this.mxEvent.target;
} else {
- member = this.props.mxEvent.sender;
+ member = this.mxEvent.sender;
}
avatar = (
@@ -970,17 +996,17 @@ export default class EventTile extends React.Component
{
if (needsSenderProfile) {
if (!this.props.tileShape || this.props.tileShape === 'reply' || this.props.tileShape === 'reply_preview') {
sender = ;
} else {
- sender = ;
+ sender = ;
}
}
const MessageActionBar = sdk.getComponent('messages.MessageActionBar');
const actionBar = !isEditing ? {
onFocusChange={this.onActionBarFocusChange}
/> : undefined;
- const showTimestamp = this.props.mxEvent.getTs() &&
+ const showTimestamp = this.mxEvent.getTs() &&
(this.props.alwaysShowTimestamps || this.props.last || this.state.hover || this.state.actionBarFocused);
const timestamp = showTimestamp ?
- : null;
+ : null;
const keyRequestHelpText =
@@ -1031,7 +1057,7 @@ export default class EventTile extends React.Component
{
if (!isRedacted) {
const ReactionsRow = sdk.getComponent('messages.ReactionsRow');
reactionsRow = ;
}
@@ -1039,7 +1065,7 @@ export default class EventTile extends React.Component {
const linkedTimestamp =
{ timestamp }
;
@@ -1058,7 +1084,7 @@ export default class EventTile extends React.Component {
switch (this.props.tileShape) {
case 'notif': {
- const room = this.context.getRoom(this.props.mxEvent.getRoomId());
+ const room = this.context.getRoom(this.mxEvent.getRoomId());
return React.createElement(this.props.as || "li", {
"className": classes,
"aria-live": ariaLive,
@@ -1080,7 +1106,7 @@ export default class EventTile extends React.Component {
,
{
}, [
{
let thread;
if (this.props.tileShape === 'reply_preview') {
thread = ReplyThread.makeThread(
- this.props.mxEvent,
+ this.mxEvent,
this.props.onHeightChanged,
this.props.permalinkCreator,
this.replyThread,
@@ -1148,7 +1174,7 @@ export default class EventTile extends React.Component {
{ groupPadlock }
{ thread }
{
}
default: {
const thread = ReplyThread.makeThread(
- this.props.mxEvent,
+ this.mxEvent,
this.props.onHeightChanged,
this.props.permalinkCreator,
this.replyThread,
@@ -1188,7 +1214,7 @@ export default class EventTile extends React.Component {
{ groupPadlock }
{ thread }
Date: Tue, 15 Jun 2021 17:24:56 -0600
Subject: [PATCH 050/164] Update MSC number references for voice messages
as per https://github.com/matrix-org/matrix-doc/pull/3245
---
src/components/views/messages/MVoiceOrAudioBody.tsx | 4 +++-
.../views/rooms/VoiceRecordComposerTile.tsx | 11 ++++-------
2 files changed, 7 insertions(+), 8 deletions(-)
diff --git a/src/components/views/messages/MVoiceOrAudioBody.tsx b/src/components/views/messages/MVoiceOrAudioBody.tsx
index 0cebcf3440..6d26ef3dcb 100644
--- a/src/components/views/messages/MVoiceOrAudioBody.tsx
+++ b/src/components/views/messages/MVoiceOrAudioBody.tsx
@@ -28,7 +28,9 @@ interface IProps {
@replaceableComponent("views.messages.MVoiceOrAudioBody")
export default class MVoiceOrAudioBody extends React.PureComponent {
public render() {
- const isVoiceMessage = !!this.props.mxEvent.getContent()['org.matrix.msc2516.voice'];
+ // MSC2516 is a legacy identifier. See https://github.com/matrix-org/matrix-doc/pull/3245
+ const isVoiceMessage = !!this.props.mxEvent.getContent()['org.matrix.msc2516.voice']
+ || !!this.props.mxEvent.getContent()['org.matrix.msc3245.voice'];
const voiceMessagesEnabled = SettingsStore.getValue("feature_voice_messages");
if (isVoiceMessage && voiceMessagesEnabled) {
return ;
diff --git a/src/components/views/rooms/VoiceRecordComposerTile.tsx b/src/components/views/rooms/VoiceRecordComposerTile.tsx
index 2102071bf3..20d8c9c5d4 100644
--- a/src/components/views/rooms/VoiceRecordComposerTile.tsx
+++ b/src/components/views/rooms/VoiceRecordComposerTile.tsx
@@ -77,7 +77,8 @@ export default class VoiceRecordComposerTile extends React.PureComponent Math.round(v * 1024)),
},
- "org.matrix.msc2516.voice": {}, // No content, this is a rendering hint
+ "org.matrix.msc3245.voice": {}, // No content, this is a rendering hint
});
await this.disposeRecording();
}
From b4fbc791bb5a7cde05390bb3a077a7065365f0a8 Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Wed, 16 Jun 2021 09:01:13 +0100
Subject: [PATCH 051/164] Add experimental options to the Spaces beta
---
res/css/views/beta/_BetaCard.scss | 8 +-
src/components/views/beta/BetaCard.tsx | 11 +-
.../views/dialogs/BetaFeedbackDialog.tsx | 7 +-
src/components/views/spaces/SpacePanel.tsx | 10 +-
src/i18n/strings/en_EN.json | 6 +-
src/rageshake/submit-rageshake.ts | 12 +-
src/settings/Settings.tsx | 26 ++-
src/stores/SpaceStore.tsx | 148 ++++++++++++++----
src/stores/room-list/SpaceWatcher.ts | 15 +-
.../room-list/filters/SpaceFilterCondition.ts | 8 +-
10 files changed, 203 insertions(+), 48 deletions(-)
diff --git a/res/css/views/beta/_BetaCard.scss b/res/css/views/beta/_BetaCard.scss
index 3463a653fc..fd87b1c824 100644
--- a/res/css/views/beta/_BetaCard.scss
+++ b/res/css/views/beta/_BetaCard.scss
@@ -42,7 +42,7 @@ limitations under the License.
margin-bottom: 20px;
}
- .mx_AccessibleButton {
+ .mx_BetaCard_buttons .mx_AccessibleButton {
display: block;
margin: 12px 0;
padding: 7px 40px;
@@ -55,6 +55,12 @@ limitations under the License.
color: $secondary-fg-color;
margin-top: 20px;
}
+
+ .mx_BetaCard_relatedSettings {
+ summary + .mx_SettingsFlag {
+ margin-top: 4px;
+ }
+ }
}
> img {
diff --git a/src/components/views/beta/BetaCard.tsx b/src/components/views/beta/BetaCard.tsx
index 821c448f4f..56770c3385 100644
--- a/src/components/views/beta/BetaCard.tsx
+++ b/src/components/views/beta/BetaCard.tsx
@@ -25,6 +25,7 @@ import TextWithTooltip from "../elements/TextWithTooltip";
import Modal from "../../../Modal";
import BetaFeedbackDialog from "../dialogs/BetaFeedbackDialog";
import SdkConfig from "../../../SdkConfig";
+import SettingsFlag from "../elements/SettingsFlag";
interface IProps {
title?: string;
@@ -66,7 +67,7 @@ const BetaCard = ({ title: titleOverride, featureId }: IProps) => {
const info = SettingsStore.getBetaInfo(featureId);
if (!info) return null; // Beta is invalid/disabled
- const { title, caption, disclaimer, image, feedbackLabel, feedbackSubheading } = info;
+ const { title, caption, disclaimer, image, feedbackLabel, feedbackSubheading, extraSettings } = info;
const value = SettingsStore.getValue(featureId);
let feedbackButton;
@@ -88,7 +89,7 @@ const BetaCard = ({ title: titleOverride, featureId }: IProps) => {
{ _t(caption) }
-
+
{ feedbackButton }
SettingsStore.setValue(featureId, null, SettingLevel.DEVICE, !value)}
@@ -100,6 +101,12 @@ const BetaCard = ({ title: titleOverride, featureId }: IProps) => {
{ disclaimer &&
{ disclaimer(value) }
}
+ { extraSettings &&
+ { _t("Experimental options") }
+ { extraSettings.map(key => (
+
+ )) }
+ }
;
diff --git a/src/components/views/dialogs/BetaFeedbackDialog.tsx b/src/components/views/dialogs/BetaFeedbackDialog.tsx
index 1ae50dd66f..635f743c76 100644
--- a/src/components/views/dialogs/BetaFeedbackDialog.tsx
+++ b/src/components/views/dialogs/BetaFeedbackDialog.tsx
@@ -44,7 +44,12 @@ const BetaFeedbackDialog: React.FC = ({featureId, onFinished}) => {
const sendFeedback = async (ok: boolean) => {
if (!ok) return onFinished(false);
- submitFeedback(SdkConfig.get().bug_report_endpoint_url, info.feedbackLabel, comment, canContact);
+ const extraData = SettingsStore.getBetaInfo(featureId)?.extraSettings.reduce((o, k) => {
+ o[k] = SettingsStore.getValue(k);
+ return o;
+ }, {});
+
+ submitFeedback(SdkConfig.get().bug_report_endpoint_url, info.feedbackLabel, comment, canContact, extraData);
onFinished(true);
Modal.createTrackedDialog("Beta Dialog Sent", featureId, InfoDialog, {
diff --git a/src/components/views/spaces/SpacePanel.tsx b/src/components/views/spaces/SpacePanel.tsx
index eb63b21f0e..fbda34a03c 100644
--- a/src/components/views/spaces/SpacePanel.tsx
+++ b/src/components/views/spaces/SpacePanel.tsx
@@ -26,6 +26,7 @@ import {SpaceItem} from "./SpaceTreeLevel";
import AccessibleTooltipButton from "../elements/AccessibleTooltipButton";
import {useEventEmitter} from "../../../hooks/useEventEmitter";
import SpaceStore, {
+ HOME_SPACE,
UPDATE_INVITED_SPACES,
UPDATE_SELECTED_SPACE,
UPDATE_TOP_LEVEL_SPACES,
@@ -40,6 +41,7 @@ import {
import {Key} from "../../../Keyboard";
import {RoomNotificationStateStore} from "../../../stores/notifications/RoomNotificationStateStore";
import {NotificationState} from "../../../stores/notifications/NotificationState";
+import SettingsStore from "../../../settings/SettingsStore";
interface IButtonProps {
space?: Room;
@@ -205,6 +207,10 @@ const SpacePanel = () => {
const activeSpaces = activeSpace ? [activeSpace] : [];
const expandCollapseButtonTitle = isPanelCollapsed ? _t("Expand space panel") : _t("Collapse space panel");
+
+ const homeNotificationState = SettingsStore.getValue("feature_spaces.all_rooms")
+ ? RoomNotificationStateStore.instance.globalState : SpaceStore.instance.getNotificationState(HOME_SPACE);
+
// TODO drag and drop for re-arranging order
return
{({onKeyDownHandler}) => (
@@ -218,8 +224,8 @@ const SpacePanel = () => {
className="mx_SpaceButton_home"
onClick={() => SpaceStore.instance.setActiveSpace(null)}
selected={!activeSpace}
- tooltip={_t("All rooms")}
- notificationState={RoomNotificationStateStore.instance.globalState}
+ tooltip={SettingsStore.getValue("feature_spaces.all_rooms") ? _t("All rooms") : _t("Home")}
+ notificationState={homeNotificationState}
isNarrow={isPanelCollapsed}
/>
{ invites.map(s => = {},
+) {
let version = "UNKNOWN";
try {
version = await PlatformPeg.get().getAppVersion();
@@ -279,6 +285,10 @@ export async function submitFeedback(endpoint: string, label: string, comment: s
body.append("platform", PlatformPeg.get().getHumanReadableName());
body.append("user_id", MatrixClientPeg.get()?.getUserId());
+ for (const k in extraData) {
+ body.append(k, extraData[k]);
+ }
+
await _submitReport(SdkConfig.get().bug_report_endpoint_url, body, () => {});
}
diff --git a/src/settings/Settings.tsx b/src/settings/Settings.tsx
index 155d039572..a291cd1fba 100644
--- a/src/settings/Settings.tsx
+++ b/src/settings/Settings.tsx
@@ -1,6 +1,6 @@
/*
Copyright 2017 Travis Ralston
-Copyright 2018, 2019, 2020 The Matrix.org Foundation C.I.C.
+Copyright 2018 - 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.
@@ -127,6 +127,7 @@ export interface ISetting {
image: string; // require(...)
feedbackSubheading?: string;
feedbackLabel?: string;
+ extraSettings?: string[];
};
}
@@ -167,8 +168,31 @@ export const SETTINGS: {[setting: string]: ISetting} = {
feedbackSubheading: _td("Your feedback will help make spaces better. " +
"The more detail you can go into, the better."),
feedbackLabel: "spaces-feedback",
+ extraSettings: [
+ "feature_spaces.all_rooms",
+ "feature_spaces.space_member_dms",
+ "feature_spaces.space_dm_badges",
+ ],
},
},
+ "feature_spaces.all_rooms": {
+ displayName: _td("Use an all rooms space instead of a home space."),
+ supportedLevels: LEVELS_FEATURE,
+ default: true,
+ controller: new ReloadOnChangeController(),
+ },
+ "feature_spaces.space_member_dms": {
+ displayName: _td("Show DMs for joined/invited members in the space."),
+ supportedLevels: LEVELS_FEATURE,
+ default: true,
+ controller: new ReloadOnChangeController(),
+ },
+ "feature_spaces.space_dm_badges": {
+ displayName: _td("Show notification badges for DMs in spaces."),
+ supportedLevels: LEVELS_FEATURE,
+ default: false,
+ controller: new ReloadOnChangeController(),
+ },
"feature_dnd": {
isFeature: true,
displayName: _td("Show options to enable 'Do not disturb' mode"),
diff --git a/src/stores/SpaceStore.tsx b/src/stores/SpaceStore.tsx
index 40997d30a8..31c53e897d 100644
--- a/src/stores/SpaceStore.tsx
+++ b/src/stores/SpaceStore.tsx
@@ -14,36 +14,41 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
-import {ListIteratee, Many, sortBy, throttle} from "lodash";
-import {EventType, RoomType} from "matrix-js-sdk/src/@types/event";
-import {Room} from "matrix-js-sdk/src/models/room";
-import {MatrixEvent} from "matrix-js-sdk/src/models/event";
+import { ListIteratee, Many, sortBy, throttle } from "lodash";
+import { EventType, RoomType } from "matrix-js-sdk/src/@types/event";
+import { Room } from "matrix-js-sdk/src/models/room";
+import { MatrixEvent } from "matrix-js-sdk/src/models/event";
-import {AsyncStoreWithClient} from "./AsyncStoreWithClient";
+import { AsyncStoreWithClient } from "./AsyncStoreWithClient";
import defaultDispatcher from "../dispatcher/dispatcher";
-import {ActionPayload} from "../dispatcher/payloads";
+import { ActionPayload } from "../dispatcher/payloads";
import RoomListStore from "./room-list/RoomListStore";
import SettingsStore from "../settings/SettingsStore";
import DMRoomMap from "../utils/DMRoomMap";
-import {FetchRoomFn} from "./notifications/ListNotificationState";
-import {SpaceNotificationState} from "./notifications/SpaceNotificationState";
-import {RoomNotificationStateStore} from "./notifications/RoomNotificationStateStore";
-import {DefaultTagID} from "./room-list/models";
-import {EnhancedMap, mapDiff} from "../utils/maps";
-import {setHasDiff} from "../utils/sets";
-import {ISpaceSummaryEvent, ISpaceSummaryRoom} from "../components/structures/SpaceRoomDirectory";
+import { FetchRoomFn } from "./notifications/ListNotificationState";
+import { SpaceNotificationState } from "./notifications/SpaceNotificationState";
+import { RoomNotificationStateStore } from "./notifications/RoomNotificationStateStore";
+import { DefaultTagID } from "./room-list/models";
+import { EnhancedMap, mapDiff } from "../utils/maps";
+import { setHasDiff } from "../utils/sets";
+import { ISpaceSummaryEvent, ISpaceSummaryRoom } from "../components/structures/SpaceRoomDirectory";
import RoomViewStore from "./RoomViewStore";
+import { arrayHasDiff } from "../utils/arrays";
+import { objectDiff } from "../utils/objects";
+
+type SpaceKey = string | symbol;
interface IState {}
const ACTIVE_SPACE_LS_KEY = "mx_active_space";
+export const HOME_SPACE = Symbol("home-space");
export const SUGGESTED_ROOMS = Symbol("suggested-rooms");
export const UPDATE_TOP_LEVEL_SPACES = Symbol("top-level-spaces");
export const UPDATE_INVITED_SPACES = Symbol("invited-spaces");
export const UPDATE_SELECTED_SPACE = Symbol("selected-space");
-// Space Room ID will be emitted when a Space's children change
+// Space Room ID/HOME_SPACE will be emitted when a Space's children change
export interface ISuggestedRoom extends ISpaceSummaryRoom {
viaServers: string[];
@@ -51,7 +56,8 @@ export interface ISuggestedRoom extends ISpaceSummaryRoom {
const MAX_SUGGESTED_ROOMS = 20;
-const getSpaceContextKey = (space?: Room) => `mx_space_context_${space?.roomId || "ALL_ROOMS"}`;
+const homeSpaceKey = SettingsStore.getValue("feature_spaces.all_rooms") ? "ALL_ROOMS" : "HOME_SPACE";
+const getSpaceContextKey = (space?: Room) => `mx_space_context_${space?.roomId || homeSpaceKey}`;
const partitionSpacesAndRooms = (arr: Room[]): [Room[], Room[]] => { // [spaces, rooms]
return arr.reduce((result, room: Room) => {
@@ -85,13 +91,15 @@ export class SpaceStoreClass extends AsyncStoreWithClient {
// The spaces representing the roots of the various tree-like hierarchies
private rootSpaces: Room[] = [];
+ // The list of rooms not present in any currently joined spaces
+ private orphanedRooms = new Set();
// Map from room ID to set of spaces which list it as a child
private parentMap = new EnhancedMap>();
- // Map from spaceId to SpaceNotificationState instance representing that space
- private notificationStateMap = new Map();
+ // Map from SpaceKey to SpaceNotificationState instance representing that space
+ private notificationStateMap = new Map();
// Map from space key to Set of room IDs that should be shown as part of that space's filter
- private spaceFilteredRooms = new Map>();
- // The space currently selected in the Space Panel - if null then All Rooms is selected
+ private spaceFilteredRooms = new Map>();
+ // The space currently selected in the Space Panel - if null then Home is selected
private _activeSpace?: Room = null;
private _suggestedRooms: ISuggestedRoom[] = [];
private _invitedSpaces = new Set();
@@ -251,7 +259,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient {
}
public getSpaceFilteredRoomIds = (space: Room | null): Set => {
- if (!space) {
+ if (!space && SettingsStore.getValue("feature_spaces.all_rooms")) {
return new Set(this.matrixClient.getVisibleRooms().map(r => r.roomId));
}
return this.spaceFilteredRooms.get(space.roomId) || new Set();
@@ -285,7 +293,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient {
});
});
- const [rootSpaces] = partitionSpacesAndRooms(Array.from(unseenChildren));
+ const [rootSpaces, orphanedRooms] = partitionSpacesAndRooms(Array.from(unseenChildren));
// somewhat algorithm to handle full-cycles
const detachedNodes = new Set(spaces);
@@ -326,6 +334,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient {
// rootSpaces.push(space);
// });
+ this.orphanedRooms = new Set(orphanedRooms);
this.rootSpaces = rootSpaces;
this.parentMap = backrefs;
@@ -342,10 +351,30 @@ export class SpaceStoreClass extends AsyncStoreWithClient {
this.emit(UPDATE_INVITED_SPACES, this.invitedSpaces);
}, 100, {trailing: true, leading: true});
- onSpaceUpdate = () => {
+ private onSpaceUpdate = () => {
this.rebuild();
}
+ private showInHomeSpace = (room: Room) => {
+ if (SettingsStore.getValue("feature_spaces.all_rooms")) return true;
+ if (room.isSpaceRoom()) return false;
+ return !this.parentMap.get(room.roomId)?.size // put all orphaned rooms in the Home Space
+ || DMRoomMap.shared().getUserIdForRoomId(room.roomId) // put all DMs in the Home Space
+ || RoomListStore.instance.getTagsForRoom(room).includes(DefaultTagID.Favourite) // show all favourites
+ };
+
+ // Update a given room due to its tag changing (e.g DM-ness or Fav-ness)
+ // This can only change whether it shows up in the HOME_SPACE or not
+ private onRoomUpdate = (room: Room) => {
+ if (this.showInHomeSpace(room)) {
+ this.spaceFilteredRooms.get(HOME_SPACE)?.add(room.roomId);
+ this.emit(HOME_SPACE);
+ } else if (!this.orphanedRooms.has(room.roomId)) {
+ this.spaceFilteredRooms.get(HOME_SPACE)?.delete(room.roomId);
+ this.emit(HOME_SPACE);
+ }
+ };
+
private onSpaceMembersChange = (ev: MatrixEvent) => {
// skip this update if we do not have a DM with this user
if (DMRoomMap.shared().getDMRoomsForUserId(ev.getStateKey()).length < 1) return;
@@ -359,6 +388,18 @@ export class SpaceStoreClass extends AsyncStoreWithClient {
const oldFilteredRooms = this.spaceFilteredRooms;
this.spaceFilteredRooms = new Map();
+ if (!SettingsStore.getValue("feature_spaces.all_rooms")) {
+ // put all room invites in the Home Space
+ const invites = visibleRooms.filter(r => !r.isSpaceRoom() && r.getMyMembership() === "invite");
+ this.spaceFilteredRooms.set(HOME_SPACE, new Set(invites.map(room => room.roomId)));
+
+ visibleRooms.forEach(room => {
+ if (this.showInHomeSpace(room)) {
+ this.spaceFilteredRooms.get(HOME_SPACE).add(room.roomId);
+ }
+ });
+ }
+
this.rootSpaces.forEach(s => {
// traverse each space tree in DFS to build up the supersets as you go up,
// reusing results from like subtrees.
@@ -374,13 +415,15 @@ export class SpaceStoreClass extends AsyncStoreWithClient {
const roomIds = new Set(childRooms.map(r => r.roomId));
const space = this.matrixClient?.getRoom(spaceId);
- // Add relevant DMs
- space?.getMembers().forEach(member => {
- if (member.membership !== "join" && member.membership !== "invite") return;
- DMRoomMap.shared().getDMRoomsForUserId(member.userId).forEach(roomId => {
- roomIds.add(roomId);
+ if (SettingsStore.getValue("feature_spaces.space_member_dms")) {
+ // Add relevant DMs
+ space?.getMembers().forEach(member => {
+ if (member.membership !== "join" && member.membership !== "invite") return;
+ DMRoomMap.shared().getDMRoomsForUserId(member.userId).forEach(roomId => {
+ roomIds.add(roomId);
+ });
});
- });
+ }
const newPath = new Set(parentPath).add(spaceId);
childSpaces.forEach(childSpace => {
@@ -406,6 +449,8 @@ export class SpaceStoreClass extends AsyncStoreWithClient {
// Update NotificationStates
this.getNotificationState(s)?.setRooms(visibleRooms.filter(room => {
if (roomIds.has(room.roomId)) {
+ if (s !== HOME_SPACE && SettingsStore.getValue("feature_spaces.space_dm_badges")) return true;
+
return !DMRoomMap.shared().getUserIdForRoomId(room.roomId)
|| RoomListStore.instance.getTagsForRoom(room).includes(DefaultTagID.Favourite);
}
@@ -489,6 +534,8 @@ export class SpaceStoreClass extends AsyncStoreWithClient {
// TODO confirm this after implementing parenting behaviour
if (room.isSpaceRoom()) {
this.onSpaceUpdate();
+ } else if (!SettingsStore.getValue("feature_spaces.all_rooms")) {
+ this.onRoomUpdate(room);
}
this.emit(room.roomId);
break;
@@ -501,8 +548,38 @@ export class SpaceStoreClass extends AsyncStoreWithClient {
}
};
+ private onRoomAccountData = (ev: MatrixEvent, room: Room, lastEvent?: MatrixEvent) => {
+ if (ev.getType() === EventType.Tag && !room.isSpaceRoom()) {
+ // If the room was in favourites and now isn't or the opposite then update its position in the trees
+ const oldTags = lastEvent?.getContent()?.tags || {};
+ const newTags = ev.getContent()?.tags || {};
+ if (!!oldTags[DefaultTagID.Favourite] !== !!newTags[DefaultTagID.Favourite]) {
+ this.onRoomUpdate(room);
+ }
+ }
+ }
+
+ private onAccountData = (ev: MatrixEvent, lastEvent: MatrixEvent) => {
+ if (ev.getType() === EventType.Direct) {
+ const lastContent = lastEvent.getContent();
+ const content = ev.getContent();
+
+ const diff = objectDiff>(lastContent, content);
+ // filter out keys which changed by reference only by checking whether the sets differ
+ const changed = diff.changed.filter(k => arrayHasDiff(lastContent[k], content[k]));
+ // DM tag changes, refresh relevant rooms
+ new Set([...diff.added, ...diff.removed, ...changed]).forEach(roomId => {
+ const room = this.matrixClient?.getRoom(roomId);
+ if (room) {
+ this.onRoomUpdate(room);
+ }
+ });
+ }
+ };
+
protected async reset() {
this.rootSpaces = [];
+ this.orphanedRooms = new Set();
this.parentMap = new EnhancedMap();
this.notificationStateMap = new Map();
this.spaceFilteredRooms = new Map();
@@ -517,6 +594,10 @@ export class SpaceStoreClass extends AsyncStoreWithClient {
this.matrixClient.removeListener("Room", this.onRoom);
this.matrixClient.removeListener("Room.myMembership", this.onRoom);
this.matrixClient.removeListener("RoomState.events", this.onRoomState);
+ if (!SettingsStore.getValue("feature_spaces.all_rooms")) {
+ this.matrixClient.removeListener("Room.accountData", this.onRoomAccountData);
+ this.matrixClient.removeListener("accountData", this.onAccountData);
+ }
}
await this.reset();
}
@@ -526,6 +607,10 @@ export class SpaceStoreClass extends AsyncStoreWithClient {
this.matrixClient.on("Room", this.onRoom);
this.matrixClient.on("Room.myMembership", this.onRoom);
this.matrixClient.on("RoomState.events", this.onRoomState);
+ if (!SettingsStore.getValue("feature_spaces.all_rooms")) {
+ this.matrixClient.on("Room.accountData", this.onRoomAccountData);
+ this.matrixClient.on("accountData", this.onAccountData);
+ }
await this.onSpaceUpdate(); // trigger an initial update
@@ -550,7 +635,10 @@ export class SpaceStoreClass extends AsyncStoreWithClient {
// Don't context switch when navigating to the space room
// as it will cause you to end up in the wrong room
this.setActiveSpace(room, false);
- } else if (this.activeSpace && !this.getSpaceFilteredRoomIds(this.activeSpace).has(roomId)) {
+ } else if (
+ (!SettingsStore.getValue("feature_spaces.all_rooms") || this.activeSpace) &&
+ !this.getSpaceFilteredRoomIds(this.activeSpace).has(roomId)
+ ) {
this.switchToRelatedSpace(roomId);
}
@@ -568,7 +656,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient {
}
}
- public getNotificationState(key: string): SpaceNotificationState {
+ public getNotificationState(key: SpaceKey): SpaceNotificationState {
if (this.notificationStateMap.has(key)) {
return this.notificationStateMap.get(key);
}
diff --git a/src/stores/room-list/SpaceWatcher.ts b/src/stores/room-list/SpaceWatcher.ts
index 0b1b78bc75..a989e9c147 100644
--- a/src/stores/room-list/SpaceWatcher.ts
+++ b/src/stores/room-list/SpaceWatcher.ts
@@ -19,6 +19,7 @@ import { Room } from "matrix-js-sdk/src/models/room";
import { RoomListStoreClass } from "./RoomListStore";
import { SpaceFilterCondition } from "./filters/SpaceFilterCondition";
import SpaceStore, { UPDATE_SELECTED_SPACE } from "../SpaceStore";
+import SettingsStore from "../../settings/SettingsStore";
/**
* Watches for changes in spaces to manage the filter on the provided RoomListStore
@@ -28,6 +29,10 @@ export class SpaceWatcher {
private activeSpace: Room = SpaceStore.instance.activeSpace;
constructor(private store: RoomListStoreClass) {
+ if (!SettingsStore.getValue("feature_spaces.all_rooms")) {
+ this.updateFilter();
+ store.addFilter(this.filter);
+ }
SpaceStore.instance.on(UPDATE_SELECTED_SPACE, this.onSelectedSpaceUpdated);
}
@@ -35,7 +40,7 @@ export class SpaceWatcher {
this.activeSpace = activeSpace;
if (this.filter) {
- if (activeSpace) {
+ if (activeSpace || !SettingsStore.getValue("feature_spaces.all_rooms")) {
this.updateFilter();
} else {
this.store.removeFilter(this.filter);
@@ -49,9 +54,11 @@ export class SpaceWatcher {
};
private updateFilter = () => {
- SpaceStore.instance.traverseSpace(this.activeSpace.roomId, roomId => {
- this.store.matrixClient?.getRoom(roomId)?.loadMembersIfNeeded();
- });
+ if (this.activeSpace) {
+ SpaceStore.instance.traverseSpace(this.activeSpace.roomId, roomId => {
+ this.store.matrixClient?.getRoom(roomId)?.loadMembersIfNeeded();
+ });
+ }
this.filter.updateSpace(this.activeSpace);
};
}
diff --git a/src/stores/room-list/filters/SpaceFilterCondition.ts b/src/stores/room-list/filters/SpaceFilterCondition.ts
index 6a06bee0d8..0d1886c38f 100644
--- a/src/stores/room-list/filters/SpaceFilterCondition.ts
+++ b/src/stores/room-list/filters/SpaceFilterCondition.ts
@@ -19,7 +19,7 @@ import { Room } from "matrix-js-sdk/src/models/room";
import { FILTER_CHANGED, FilterKind, IFilterCondition } from "./IFilterCondition";
import { IDestroyable } from "../../../utils/IDestroyable";
-import SpaceStore from "../../SpaceStore";
+import SpaceStore, { HOME_SPACE } from "../../SpaceStore";
import { setHasDiff } from "../../../utils/sets";
/**
@@ -55,12 +55,10 @@ export class SpaceFilterCondition extends EventEmitter implements IFilterConditi
}
};
- private getSpaceEventKey = (space: Room) => space.roomId;
+ private getSpaceEventKey = (space: Room | null) => space ? space.roomId : HOME_SPACE;
public updateSpace(space: Room) {
- if (this.space) {
- SpaceStore.instance.off(this.getSpaceEventKey(this.space), this.onStoreUpdate);
- }
+ SpaceStore.instance.off(this.getSpaceEventKey(this.space), this.onStoreUpdate);
SpaceStore.instance.on(this.getSpaceEventKey(this.space = space), this.onStoreUpdate);
this.onStoreUpdate(); // initial update from the change to the space
}
From a75fb98fbc2de2b39e6f4beb44e889a7d61b3ba2 Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Wed, 16 Jun 2021 09:07:55 +0100
Subject: [PATCH 052/164] Fix some NPEs
---
src/stores/SpaceStore.tsx | 2 +-
src/stores/room-list/SpaceWatcher.ts | 1 +
2 files changed, 2 insertions(+), 1 deletion(-)
diff --git a/src/stores/SpaceStore.tsx b/src/stores/SpaceStore.tsx
index 31c53e897d..2b5a25e707 100644
--- a/src/stores/SpaceStore.tsx
+++ b/src/stores/SpaceStore.tsx
@@ -262,7 +262,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient {
if (!space && SettingsStore.getValue("feature_spaces.all_rooms")) {
return new Set(this.matrixClient.getVisibleRooms().map(r => r.roomId));
}
- return this.spaceFilteredRooms.get(space.roomId) || new Set();
+ return this.spaceFilteredRooms.get(space?.roomId || HOME_SPACE) || new Set();
};
private rebuild = throttle(() => {
diff --git a/src/stores/room-list/SpaceWatcher.ts b/src/stores/room-list/SpaceWatcher.ts
index a989e9c147..a1f7786578 100644
--- a/src/stores/room-list/SpaceWatcher.ts
+++ b/src/stores/room-list/SpaceWatcher.ts
@@ -30,6 +30,7 @@ export class SpaceWatcher {
constructor(private store: RoomListStoreClass) {
if (!SettingsStore.getValue("feature_spaces.all_rooms")) {
+ this.filter = new SpaceFilterCondition();
this.updateFilter();
store.addFilter(this.filter);
}
From 9e20e5dfc44c33eb896234d05b60e8804493d9b3 Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Wed, 16 Jun 2021 09:14:07 +0100
Subject: [PATCH 053/164] mock new settings in tests
---
test/stores/SpaceStore-test.ts | 11 +++++++++--
1 file changed, 9 insertions(+), 2 deletions(-)
diff --git a/test/stores/SpaceStore-test.ts b/test/stores/SpaceStore-test.ts
index 01bd528b87..4cbd9f43c8 100644
--- a/test/stores/SpaceStore-test.ts
+++ b/test/stores/SpaceStore-test.ts
@@ -123,8 +123,15 @@ describe("SpaceStore", () => {
jest.runAllTimers();
client.getVisibleRooms.mockReturnValue(rooms = []);
getValue.mockImplementation(settingName => {
- if (settingName === "feature_spaces") {
- return true;
+ switch (settingName) {
+ case "feature_spaces":
+ return true;
+ case "feature_spaces.all_rooms":
+ return true;
+ case "feature_spaces.space_member_dms":
+ return true;
+ case "feature_spaces.space_dm_badges":
+ return false;
}
});
});
From cee294f5a7378737bc0cbaf9707250eeea5a195d Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Wed, 16 Jun 2021 09:23:06 +0100
Subject: [PATCH 054/164] iterate PR
---
.../structures/AutoHideScrollbar.tsx | 2 +-
.../structures/SpaceRoomDirectory.tsx | 28 +++++++++----------
.../views/spaces/SpaceTreeLevel.tsx | 28 +++++++++----------
src/stores/SpaceStore.tsx | 5 ++--
src/utils/arrays.ts | 2 +-
src/utils/stringOrderField.ts | 4 +--
6 files changed, 34 insertions(+), 35 deletions(-)
diff --git a/src/components/structures/AutoHideScrollbar.tsx b/src/components/structures/AutoHideScrollbar.tsx
index e5fa124fed..8650224fb3 100644
--- a/src/components/structures/AutoHideScrollbar.tsx
+++ b/src/components/structures/AutoHideScrollbar.tsx
@@ -15,7 +15,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
-import React, {HTMLAttributes} from "react";
+import React, { HTMLAttributes } from "react";
interface IProps extends HTMLAttributes {
className?: string;
diff --git a/src/components/structures/SpaceRoomDirectory.tsx b/src/components/structures/SpaceRoomDirectory.tsx
index 2b4fb24c1b..497f525a00 100644
--- a/src/components/structures/SpaceRoomDirectory.tsx
+++ b/src/components/structures/SpaceRoomDirectory.tsx
@@ -14,34 +14,34 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
-import React, {ReactNode, useMemo, useState} from "react";
-import {Room} from "matrix-js-sdk/src/models/room";
-import {MatrixClient} from "matrix-js-sdk/src/client";
-import {EventType, RoomType} from "matrix-js-sdk/src/@types/event";
+import React, { ReactNode, useMemo, useState } from "react";
+import { Room } from "matrix-js-sdk/src/models/room";
+import { MatrixClient } from "matrix-js-sdk/src/client";
+import { EventType, RoomType } from "matrix-js-sdk/src/@types/event";
import classNames from "classnames";
-import {sortBy} from "lodash";
+import { sortBy } from "lodash";
-import {MatrixClientPeg} from "../../MatrixClientPeg";
+import { MatrixClientPeg } from "../../MatrixClientPeg";
import dis from "../../dispatcher/dispatcher";
-import {_t} from "../../languageHandler";
-import AccessibleButton, {ButtonEvent} from "../views/elements/AccessibleButton";
+import { _t } from "../../languageHandler";
+import AccessibleButton, { ButtonEvent } from "../views/elements/AccessibleButton";
import BaseDialog from "../views/dialogs/BaseDialog";
import Spinner from "../views/elements/Spinner";
import SearchBox from "./SearchBox";
import RoomAvatar from "../views/avatars/RoomAvatar";
import RoomName from "../views/elements/RoomName";
-import {useAsyncMemo} from "../../hooks/useAsyncMemo";
-import {EnhancedMap} from "../../utils/maps";
+import { useAsyncMemo } from "../../hooks/useAsyncMemo";
+import { EnhancedMap } from "../../utils/maps";
import StyledCheckbox from "../views/elements/StyledCheckbox";
import AutoHideScrollbar from "./AutoHideScrollbar";
import BaseAvatar from "../views/avatars/BaseAvatar";
-import {mediaFromMxc} from "../../customisations/Media";
+import { mediaFromMxc } from "../../customisations/Media";
import InfoTooltip from "../views/elements/InfoTooltip";
import TextWithTooltip from "../views/elements/TextWithTooltip";
-import {useStateToggle} from "../../hooks/useStateToggle";
-import {getChildOrder} from "../../stores/SpaceStore";
+import { useStateToggle } from "../../hooks/useStateToggle";
+import { getChildOrder } from "../../stores/SpaceStore";
import AccessibleTooltipButton from "../views/elements/AccessibleTooltipButton";
-import {linkifyElement} from "../../HtmlUtils";
+import { linkifyElement } from "../../HtmlUtils";
interface IHierarchyProps {
space: Room;
diff --git a/src/components/views/spaces/SpaceTreeLevel.tsx b/src/components/views/spaces/SpaceTreeLevel.tsx
index 7ac863b239..416b4cc6f1 100644
--- a/src/components/views/spaces/SpaceTreeLevel.tsx
+++ b/src/components/views/spaces/SpaceTreeLevel.tsx
@@ -14,23 +14,23 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
-import React, {InputHTMLAttributes, LegacyRef} from "react";
+import React, { InputHTMLAttributes, LegacyRef } from "react";
import classNames from "classnames";
-import {Room} from "matrix-js-sdk/src/models/room";
+import { Room } from "matrix-js-sdk/src/models/room";
import RoomAvatar from "../avatars/RoomAvatar";
import SpaceStore from "../../../stores/SpaceStore";
import SpaceTreeLevelLayoutStore from "../../../stores/SpaceTreeLevelLayoutStore";
import NotificationBadge from "../rooms/NotificationBadge";
-import {RovingAccessibleButton} from "../../../accessibility/roving/RovingAccessibleButton";
-import {RovingAccessibleTooltipButton} from "../../../accessibility/roving/RovingAccessibleTooltipButton";
+import { RovingAccessibleButton } from "../../../accessibility/roving/RovingAccessibleButton";
+import { RovingAccessibleTooltipButton } from "../../../accessibility/roving/RovingAccessibleTooltipButton";
import IconizedContextMenu, {
IconizedContextMenuOption,
IconizedContextMenuOptionList,
} from "../context_menus/IconizedContextMenu";
-import {_t} from "../../../languageHandler";
-import {ContextMenuTooltipButton} from "../../../accessibility/context_menu/ContextMenuTooltipButton";
-import {toRightOf} from "../../structures/ContextMenu";
+import { _t } from "../../../languageHandler";
+import { ContextMenuTooltipButton } from "../../../accessibility/context_menu/ContextMenuTooltipButton";
+import { toRightOf } from "../../structures/ContextMenu";
import {
shouldShowSpaceSettings,
showAddExistingRooms,
@@ -39,15 +39,15 @@ import {
showSpaceSettings,
} from "../../../utils/space";
import MatrixClientContext from "../../../contexts/MatrixClientContext";
-import AccessibleButton, {ButtonEvent} from "../elements/AccessibleButton";
+import AccessibleButton, { ButtonEvent } from "../elements/AccessibleButton";
import defaultDispatcher from "../../../dispatcher/dispatcher";
-import {Action} from "../../../dispatcher/actions";
+import { Action } from "../../../dispatcher/actions";
import RoomViewStore from "../../../stores/RoomViewStore";
-import {SetRightPanelPhasePayload} from "../../../dispatcher/payloads/SetRightPanelPhasePayload";
-import {RightPanelPhases} from "../../../stores/RightPanelStorePhases";
-import {EventType} from "matrix-js-sdk/src/@types/event";
-import {StaticNotificationState} from "../../../stores/notifications/StaticNotificationState";
-import {NotificationColor} from "../../../stores/notifications/NotificationColor";
+import { SetRightPanelPhasePayload } from "../../../dispatcher/payloads/SetRightPanelPhasePayload";
+import { RightPanelPhases } from "../../../stores/RightPanelStorePhases";
+import { EventType } from "matrix-js-sdk/src/@types/event";
+import { StaticNotificationState } from "../../../stores/notifications/StaticNotificationState";
+import { NotificationColor } from "../../../stores/notifications/NotificationColor";
interface IItemProps extends InputHTMLAttributes {
space?: Room;
diff --git a/src/stores/SpaceStore.tsx b/src/stores/SpaceStore.tsx
index 9ffb4eb776..b0099b3306 100644
--- a/src/stores/SpaceStore.tsx
+++ b/src/stores/SpaceStore.tsx
@@ -62,14 +62,13 @@ const partitionSpacesAndRooms = (arr: Room[]): [Room[], Room[]] => { // [spaces,
}, [[], []]);
};
-const validOrder = (order: string): string | null => {
+const validOrder = (order: string): string | undefined => {
if (typeof order === "string" && order.length <= 50 && Array.from(order).every((c: string) => {
const charCode = c.charCodeAt(0);
return charCode >= 0x20 && charCode <= 0x7E;
})) {
return order;
}
- return undefined;
};
// For sorting space children using a validated `order`, `m.room.create`'s `origin_server_ts`, `room_id`
@@ -639,7 +638,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient {
try {
await this.matrixClient.setRoomAccountData(space.roomId, EventType.SpaceOrder, { order });
} catch (e) {
- console.log("Failed to set root space order", e);
+ console.warn("Failed to set root space order", e);
if (this.spaceOrderLocalEchoMap.get(space.roomId) === order) {
this.spaceOrderLocalEchoMap.delete(space.roomId);
}
diff --git a/src/utils/arrays.ts b/src/utils/arrays.ts
index d319631d93..148861e5d3 100644
--- a/src/utils/arrays.ts
+++ b/src/utils/arrays.ts
@@ -230,7 +230,7 @@ export function arrayMerge(...a: T[][]): T[] {
* @param toIndex the index of where to put the element.
* @returns A new array with the requested value moved.
*/
-export function reorder(list: T[], fromIndex: number, toIndex: number): T[] {
+export function moveElement(list: T[], fromIndex: number, toIndex: number): T[] {
const result = Array.from(list);
const [removed] = result.splice(fromIndex, 1);
result.splice(toIndex, 0, removed);
diff --git a/src/utils/stringOrderField.ts b/src/utils/stringOrderField.ts
index 4336583b9d..b312b85b08 100644
--- a/src/utils/stringOrderField.ts
+++ b/src/utils/stringOrderField.ts
@@ -16,7 +16,7 @@ limitations under the License.
import { alphabetPad, baseToString, stringToBase, DEFAULT_ALPHABET } from "matrix-js-sdk/src/utils";
-import { reorder } from "./arrays";
+import { moveElement } from "./arrays";
export const midPointsBetweenStrings = (
a: string,
@@ -73,7 +73,7 @@ export const reorderLexicographically = (
// zip orders with their indices to simplify later index wrangling
const ordersWithIndices: IEntry[] = orders.map((order, index) => ({ index, order }));
// apply the fundamental order update to the zipped array
- const newOrder = reorder(ordersWithIndices, fromIndex, toIndex);
+ const newOrder = moveElement(ordersWithIndices, fromIndex, toIndex);
// check if we have to fill undefined orders to complete placement
const orderToLeftUndefined = newOrder[toIndex - 1]?.order === undefined;
From bceee7978edc0a972f67cc612a250d5f0f717dfd Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Wed, 16 Jun 2021 09:30:47 +0100
Subject: [PATCH 055/164] improve naming of tests
---
test/utils/stringOrderField-test.ts | 495 +++++++++++++---------------
1 file changed, 221 insertions(+), 274 deletions(-)
diff --git a/test/utils/stringOrderField-test.ts b/test/utils/stringOrderField-test.ts
index a523872023..ece3043d86 100644
--- a/test/utils/stringOrderField-test.ts
+++ b/test/utils/stringOrderField-test.ts
@@ -39,306 +39,253 @@ const moveLexicographicallyTest = (
};
describe("stringOrderField", () => {
- it("stringToBase", () => {
- expect(Number(stringToBase(""))).toBe(0);
- expect(Number(stringToBase(" "))).toBe(1);
- expect(Number(stringToBase("a"))).toBe(66);
- expect(Number(stringToBase(" !"))).toBe(97);
- expect(Number(stringToBase("aa"))).toBe(6336);
- expect(Number(stringToBase("cat"))).toBe(620055);
- expect(Number(stringToBase("doggo"))).toBe(5689339845);
- expect(Number(stringToBase("a", "abcdefghijklmnopqrstuvwxyz"))).toEqual(1);
- expect(Number(stringToBase("a"))).toEqual(66);
- expect(Number(stringToBase("c", "abcdefghijklmnopqrstuvwxyz"))).toEqual(3);
- expect(Number(stringToBase("ab"))).toEqual(6337);
- expect(Number(stringToBase("cb", "abcdefghijklmnopqrstuvwxyz"))).toEqual(80);
- expect(Number(stringToBase("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"))).toEqual(4.648312045971824e+78);
- expect(Number(stringToBase("~".repeat(50)))).toEqual(7.776353884348688e+98);
- expect(Number(stringToBase(" "))).toEqual(7820126496);
- expect(Number(stringToBase(" "))).toEqual(96);
- expect(Number(stringToBase(" !"))).toEqual(97);
- expect(Number(stringToBase("S:J\\~"))).toEqual(4258975590);
- expect(Number(stringToBase("!'Tu:}"))).toEqual(16173443434);
+ describe("midPointsBetweenStrings", () => {
+ it("should work", () => {
+ expect(averageBetweenStrings("!!", "##")).toBe('""');
+ const midpoints = ["a", ...midPointsBetweenStrings("a", "e", 3, 1), "e"].sort();
+ expect(midpoints[0]).toBe("a");
+ expect(midpoints[4]).toBe("e");
+ expect(midPointsBetweenStrings(" ", "!'Tu:}", 1, 50)).toStrictEqual([" S:J\\~"]);
+ });
+
+ it("should return empty array when the request is not possible", () => {
+ expect(midPointsBetweenStrings("a", "e", 0, 1)).toStrictEqual([]);
+ expect(midPointsBetweenStrings("a", "e", 4, 1)).toStrictEqual([]);
+ });
});
- it("baseToString", () => {
- expect(baseToString(BigInt(10))).toBe(DEFAULT_ALPHABET[9]);
- expect(baseToString(BigInt(10), "abcdefghijklmnopqrstuvwxyz")).toEqual("j");
- expect(baseToString(BigInt(6241))).toEqual("`a");
- expect(baseToString(BigInt(53), "abcdefghijklmnopqrstuvwxyz")).toEqual("ba");
- expect(baseToString(BigInt(1234))).toBe("+}");
- expect(baseToString(BigInt(0))).toBe("");
- expect(baseToString(BigInt(1))).toBe(" ");
- expect(baseToString(BigInt(95))).toBe("~");
- expect(baseToString(BigInt(96))).toBe(" ");
- expect(baseToString(BigInt(97))).toBe(" !");
- expect(baseToString(BigInt(98))).toBe(' "');
- expect(baseToString(BigInt(1))).toBe(" ");
- });
+ describe("reorderLexicographically", () => {
+ it("should work when moving left", () => {
+ moveLexicographicallyTest(["a", "c", "e", "g", "i"], 2, 1, 1);
+ });
- it("midPointsBetweenStrings", () => {
- expect(averageBetweenStrings("!!", "##")).toBe('""');
- const midpoints = ["a", ...midPointsBetweenStrings("a", "e", 3, 1), "e"].sort();
- expect(midpoints[0]).toBe("a");
- expect(midpoints[4]).toBe("e");
- expect(midPointsBetweenStrings("a", "e", 0, 1)).toStrictEqual([]);
- expect(midPointsBetweenStrings("a", "e", 4, 1)).toStrictEqual([]);
- expect(midPointsBetweenStrings(" ", "!'Tu:}", 1, 50)).toStrictEqual([" S:J\\~"]);
- expect(averageBetweenStrings(" ", "!!")).toBe(" P");
- expect(averageBetweenStrings("! ", "!!")).toBe("! ");
- });
+ it("should work when moving right", () => {
+ moveLexicographicallyTest(["a", "c", "e", "g", "i"], 1, 2, 1);
+ });
- it("moveLexicographically left", () => {
- moveLexicographicallyTest(["a", "c", "e", "g", "i"], 2, 1, 1);
- });
+ it("should work when all orders are undefined", () => {
+ moveLexicographicallyTest(
+ [undefined, undefined, undefined, undefined, undefined, undefined],
+ 4,
+ 1,
+ 2,
+ );
+ });
- it("moveLexicographically right", () => {
- moveLexicographicallyTest(["a", "c", "e", "g", "i"], 1, 2, 1);
- });
+ it("should work when moving to end and all orders are undefined", () => {
+ moveLexicographicallyTest(
+ [undefined, undefined, undefined, undefined, undefined, undefined],
+ 1,
+ 4,
+ 5,
+ );
+ });
- it("moveLexicographically all undefined", () => {
- moveLexicographicallyTest(
- [undefined, undefined, undefined, undefined, undefined, undefined],
- 4,
- 1,
- 2,
- );
- });
+ it("should work when moving left and some orders are undefined", () => {
+ moveLexicographicallyTest(
+ ["a", "c", "e", undefined, undefined, undefined],
+ 5,
+ 2,
+ 1,
+ );
- it("moveLexicographically all undefined to end", () => {
- moveLexicographicallyTest(
- [undefined, undefined, undefined, undefined, undefined, undefined],
- 1,
- 4,
- 5,
- );
- });
+ moveLexicographicallyTest(
+ ["a", "a", "e", undefined, undefined, undefined],
+ 5,
+ 1,
+ 2,
+ );
+ });
- it("moveLexicographically some undefined move left", () => {
- moveLexicographicallyTest(
- ["a", "c", "e", undefined, undefined, undefined],
- 5,
- 2,
- 1,
- );
- });
+ it("should work moving to the start when all is undefined", () => {
+ moveLexicographicallyTest(
+ [undefined, undefined, undefined, undefined],
+ 2,
+ 0,
+ 1,
+ );
+ });
- it("moveLexicographically some undefined move left close", () => {
- moveLexicographicallyTest(
- ["a", "a", "e", undefined, undefined, undefined],
- 5,
- 1,
- 2,
- );
- });
+ it("should work moving to the end when all is undefined", () => {
+ moveLexicographicallyTest(
+ [undefined, undefined, undefined, undefined],
+ 1,
+ 3,
+ 4,
+ );
+ });
- it("test moving to the start when all is undefined", () => {
- moveLexicographicallyTest(
- [undefined, undefined, undefined, undefined],
- 2,
- 0,
- 1,
- );
- });
+ it("should work moving left when all is undefined", () => {
+ moveLexicographicallyTest(
+ [undefined, undefined, undefined, undefined, undefined, undefined],
+ 4,
+ 1,
+ 2,
+ );
+ });
- it("test moving to the end when all is undefined", () => {
- moveLexicographicallyTest(
- [undefined, undefined, undefined, undefined],
- 1,
- 3,
- 4,
- );
- });
+ it("should work moving right when all is undefined", () => {
+ moveLexicographicallyTest(
+ [undefined, undefined, undefined, undefined],
+ 1,
+ 2,
+ 3,
+ );
+ });
- it("test moving left when all is undefined", () => {
- moveLexicographicallyTest(
- [undefined, undefined, undefined, undefined, undefined, undefined],
- 4,
- 1,
- 2,
- );
- });
+ it("should work moving more right when all is undefined", () => {
+ moveLexicographicallyTest(
+ [undefined, undefined, undefined, undefined, undefined, /**/ undefined, undefined],
+ 1,
+ 4,
+ 5,
+ );
+ });
- it("test moving right when all is undefined", () => {
- moveLexicographicallyTest(
- [undefined, undefined, undefined, undefined],
- 1,
- 2,
- 3,
- );
- });
+ it("should work moving left when right is undefined", () => {
+ moveLexicographicallyTest(
+ ["20", undefined, undefined, undefined, undefined, undefined],
+ 4,
+ 2,
+ 2,
+ );
+ });
- it("test moving more right when all is undefined", () => {
- moveLexicographicallyTest(
- [undefined, undefined, undefined, undefined, undefined, /**/ undefined, undefined],
- 1,
- 4,
- 5,
- );
- });
+ it("should work moving right when right is undefined", () => {
+ moveLexicographicallyTest(
+ ["50", undefined, undefined, undefined, undefined, /**/ undefined, undefined],
+ 1,
+ 4,
+ 4,
+ );
+ });
- it("test moving left when right is undefined", () => {
- moveLexicographicallyTest(
- ["20", undefined, undefined, undefined, undefined, undefined],
- 4,
- 2,
- 2,
- );
- });
+ it("should work moving left when right is defined", () => {
+ moveLexicographicallyTest(
+ ["10", "20", "30", "40", undefined, undefined],
+ 3,
+ 1,
+ 1,
+ );
+ });
- it("test moving right when right is undefined", () => {
- moveLexicographicallyTest(
- ["50", undefined, undefined, undefined, undefined, /**/ undefined, undefined],
- 1,
- 4,
- 4,
- );
- });
+ it("should work moving right when right is defined", () => {
+ moveLexicographicallyTest(
+ ["10", "20", "30", "40", "50", undefined],
+ 1,
+ 3,
+ 1,
+ );
+ });
- it("test moving left when right is defined", () => {
- moveLexicographicallyTest(
- ["10", "20", "30", "40", undefined, undefined],
- 3,
- 1,
- 1,
- );
- });
+ it("should work moving left when all is defined", () => {
+ moveLexicographicallyTest(
+ ["11", "13", "15", "17", "19"],
+ 2,
+ 1,
+ 1,
+ );
+ });
- it("test moving right when right is defined", () => {
- moveLexicographicallyTest(
- ["10", "20", "30", "40", "50", undefined],
- 1,
- 3,
- 1,
- );
- });
+ it("should work moving right when all is defined", () => {
+ moveLexicographicallyTest(
+ ["11", "13", "15", "17", "19"],
+ 1,
+ 2,
+ 1,
+ );
+ });
- it("test moving left when all is defined", () => {
- moveLexicographicallyTest(
- ["11", "13", "15", "17", "19"],
- 2,
- 1,
- 1,
- );
- });
+ it("should work moving left into no left space", () => {
+ moveLexicographicallyTest(
+ ["11", "12", "13", "14", "19"],
+ 3,
+ 1,
+ 2,
+ 2,
+ );
- it("test moving right when all is defined", () => {
- moveLexicographicallyTest(
- ["11", "13", "15", "17", "19"],
- 1,
- 2,
- 1,
- );
- });
+ moveLexicographicallyTest(
+ [
+ DEFAULT_ALPHABET.charAt(0),
+ // Target
+ DEFAULT_ALPHABET.charAt(1),
+ DEFAULT_ALPHABET.charAt(2),
+ DEFAULT_ALPHABET.charAt(3),
+ DEFAULT_ALPHABET.charAt(4),
+ DEFAULT_ALPHABET.charAt(5),
+ ],
+ 5,
+ 1,
+ 5,
+ 1,
+ );
+ });
- it("test moving left into no left space", () => {
- moveLexicographicallyTest(
- ["11", "12", "13", "14", "19"],
- 3,
- 1,
- 2,
- 2,
- );
+ it("should work moving right into no right space", () => {
+ moveLexicographicallyTest(
+ ["15", "16", "17", "18", "19"],
+ 1,
+ 3,
+ 3,
+ 2,
+ );
- moveLexicographicallyTest(
- [
- DEFAULT_ALPHABET.charAt(0),
- // Target
- DEFAULT_ALPHABET.charAt(1),
- DEFAULT_ALPHABET.charAt(2),
- DEFAULT_ALPHABET.charAt(3),
- DEFAULT_ALPHABET.charAt(4),
- DEFAULT_ALPHABET.charAt(5),
- ],
- 5,
- 1,
- 5,
- 1,
- );
- });
+ moveLexicographicallyTest(
+ [
+ DEFAULT_ALPHABET.charAt(DEFAULT_ALPHABET.length - 5),
+ DEFAULT_ALPHABET.charAt(DEFAULT_ALPHABET.length - 4),
+ DEFAULT_ALPHABET.charAt(DEFAULT_ALPHABET.length - 3),
+ DEFAULT_ALPHABET.charAt(DEFAULT_ALPHABET.length - 2),
+ DEFAULT_ALPHABET.charAt(DEFAULT_ALPHABET.length - 1),
+ ],
+ 1,
+ 3,
+ 3,
+ 1,
+ );
+ });
- it("test moving right into no right space", () => {
- moveLexicographicallyTest(
- ["15", "16", "17", "18", "19"],
- 1,
- 3,
- 3,
- 2,
- );
+ it("should work moving right into no left space", () => {
+ moveLexicographicallyTest(
+ ["11", "12", "13", "14", "15", "16", undefined],
+ 1,
+ 3,
+ 3,
+ );
- moveLexicographicallyTest(
- [
- DEFAULT_ALPHABET.charAt(DEFAULT_ALPHABET.length - 5),
- DEFAULT_ALPHABET.charAt(DEFAULT_ALPHABET.length - 4),
- DEFAULT_ALPHABET.charAt(DEFAULT_ALPHABET.length - 3),
- DEFAULT_ALPHABET.charAt(DEFAULT_ALPHABET.length - 2),
- DEFAULT_ALPHABET.charAt(DEFAULT_ALPHABET.length - 1),
- ],
- 1,
- 3,
- 3,
- 1,
- );
- });
+ moveLexicographicallyTest(
+ ["0", "1", "2", "3", "4", "5"],
+ 1,
+ 3,
+ 3,
+ 1,
+ );
+ });
- it("test moving right into no left space", () => {
- moveLexicographicallyTest(
- ["11", "12", "13", "14", "15", "16", undefined],
- 1,
- 3,
- 3,
- );
+ it("should work moving left into no right space", () => {
+ moveLexicographicallyTest(
+ ["15", "16", "17", "18", "19"],
+ 4,
+ 3,
+ 4,
+ 2,
+ );
- moveLexicographicallyTest(
- ["0", "1", "2", "3", "4", "5"],
- 1,
- 3,
- 3,
- 1,
- );
- });
-
- it("test moving left into no right space", () => {
- moveLexicographicallyTest(
- ["15", "16", "17", "18", "19"],
- 4,
- 3,
- 4,
- 2,
- );
-
- moveLexicographicallyTest(
- [
- DEFAULT_ALPHABET.charAt(DEFAULT_ALPHABET.length - 5),
- DEFAULT_ALPHABET.charAt(DEFAULT_ALPHABET.length - 4),
- DEFAULT_ALPHABET.charAt(DEFAULT_ALPHABET.length - 3),
- DEFAULT_ALPHABET.charAt(DEFAULT_ALPHABET.length - 2),
- DEFAULT_ALPHABET.charAt(DEFAULT_ALPHABET.length - 1),
- ],
- 4,
- 3,
- 4,
- 1,
- );
- });
-
- const prev = (str: string) => baseToString(stringToBase(str) - BigInt(1));
- const next = (str: string) => baseToString(stringToBase(str) + BigInt(1));
-
- it("baseN calculation is correctly consecutive", () => {
- const str = "this-is-a-test";
- expect(next(prev(str))).toBe(str);
- });
-
- it("rolls over sanely", () => {
- const maxSpaceValue = "~".repeat(50);
- const fiftyFirstChar = " ".repeat(51);
- expect(next(maxSpaceValue)).toBe(fiftyFirstChar);
- expect(prev(fiftyFirstChar)).toBe(maxSpaceValue);
- expect(Number(stringToBase(DEFAULT_ALPHABET[0]))).toEqual(1);
- expect(Number(stringToBase(DEFAULT_ALPHABET[1]))).toEqual(2);
- expect(DEFAULT_ALPHABET[DEFAULT_ALPHABET.length - 1]).toBe("~");
- expect(DEFAULT_ALPHABET[0]).toBe(" ");
+ moveLexicographicallyTest(
+ [
+ DEFAULT_ALPHABET.charAt(DEFAULT_ALPHABET.length - 5),
+ DEFAULT_ALPHABET.charAt(DEFAULT_ALPHABET.length - 4),
+ DEFAULT_ALPHABET.charAt(DEFAULT_ALPHABET.length - 3),
+ DEFAULT_ALPHABET.charAt(DEFAULT_ALPHABET.length - 2),
+ DEFAULT_ALPHABET.charAt(DEFAULT_ALPHABET.length - 1),
+ ],
+ 4,
+ 3,
+ 4,
+ 1,
+ );
+ });
});
});
From d4e376201f986223c16932229598c7e45a032c82 Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Wed, 16 Jun 2021 09:44:37 +0100
Subject: [PATCH 056/164] Break down the SpacePanel component
---
src/components/views/spaces/SpacePanel.tsx | 134 ++++++++++++---------
1 file changed, 76 insertions(+), 58 deletions(-)
diff --git a/src/components/views/spaces/SpacePanel.tsx b/src/components/views/spaces/SpacePanel.tsx
index 27f097e9d4..2e3bfd157a 100644
--- a/src/components/views/spaces/SpacePanel.tsx
+++ b/src/components/views/spaces/SpacePanel.tsx
@@ -14,18 +14,18 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
-import React, { useEffect, useState } from "react";
+import React, { Dispatch, ReactNode, SetStateAction, useEffect, useState } from "react";
import { DragDropContext, Droppable, Draggable } from "react-beautiful-dnd";
import classNames from "classnames";
import { Room } from "matrix-js-sdk/src/models/room";
-import {_t} from "../../../languageHandler";
+import { _t } from "../../../languageHandler";
import RoomAvatar from "../avatars/RoomAvatar";
-import {useContextMenu} from "../../structures/ContextMenu";
+import { useContextMenu } from "../../structures/ContextMenu";
import SpaceCreateMenu from "./SpaceCreateMenu";
-import {SpaceItem} from "./SpaceTreeLevel";
+import { SpaceItem } from "./SpaceTreeLevel";
import AccessibleTooltipButton from "../elements/AccessibleTooltipButton";
-import {useEventEmitter} from "../../../hooks/useEventEmitter";
+import { useEventEmitter } from "../../../hooks/useEventEmitter";
import SpaceStore, {
UPDATE_INVITED_SPACES,
UPDATE_SELECTED_SPACE,
@@ -38,9 +38,9 @@ import {
RovingAccessibleTooltipButton,
RovingTabIndexProvider,
} from "../../../accessibility/RovingTabIndex";
-import {Key} from "../../../Keyboard";
-import {RoomNotificationStateStore} from "../../../stores/notifications/RoomNotificationStateStore";
-import {NotificationState} from "../../../stores/notifications/NotificationState";
+import { Key } from "../../../Keyboard";
+import { RoomNotificationStateStore } from "../../../stores/notifications/RoomNotificationStateStore";
+import { NotificationState } from "../../../stores/notifications/NotificationState";
interface IButtonProps {
space?: Room;
@@ -121,11 +121,62 @@ const useSpaces = (): [Room[], Room[], Room | null] => {
return [invites, spaces, activeSpace];
};
+interface IInnerSpacePanelProps {
+ children?: ReactNode;
+ isPanelCollapsed: boolean;
+ setPanelCollapsed: Dispatch>;
+}
+
+// Optimisation based on https://github.com/atlassian/react-beautiful-dnd/blob/master/docs/api/droppable.md#recommended-droppable--performance-optimisation
+const InnerSpacePanel = React.memo(({ children, isPanelCollapsed, setPanelCollapsed }) => {
+ const [invites, spaces, activeSpace] = useSpaces();
+ const activeSpaces = activeSpace ? [activeSpace] : [];
+
+ return
+ SpaceStore.instance.setActiveSpace(null)}
+ selected={!activeSpace}
+ tooltip={_t("All rooms")}
+ notificationState={RoomNotificationStateStore.instance.globalState}
+ isNarrow={isPanelCollapsed}
+ />
+ { invites.map(s => (
+ setPanelCollapsed(false)}
+ />
+ )) }
+ { spaces.map((s, i) => (
+
+ {(provided, snapshot) => (
+ setPanelCollapsed(false)}
+ />
+ )}
+
+ )) }
+ { children }
+
;
+});
+
const SpacePanel = () => {
// We don't need the handle as we position the menu in a constant location
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const [menuDisplayed, handle, openMenu, closeMenu] = useContextMenu();
- const [invites, spaces, activeSpace] = useSpaces();
const [isPanelCollapsed, setPanelCollapsed] = useState(true);
useEffect(() => {
@@ -134,10 +185,6 @@ const SpacePanel = () => {
}
}, [isPanelCollapsed]); // eslint-disable-line react-hooks/exhaustive-deps
- const newClasses = classNames("mx_SpaceButton_new", {
- mx_SpaceButton_newCancel: menuDisplayed,
- });
-
let contextMenu = null;
if (menuDisplayed) {
contextMenu = ;
@@ -204,7 +251,11 @@ const SpacePanel = () => {
}
};
- const activeSpaces = activeSpace ? [activeSpace] : [];
+ const onNewClick = menuDisplayed ? closeMenu : () => {
+ if (!isPanelCollapsed) setPanelCollapsed(true);
+ openMenu();
+ };
+
return (
{
if (!result.destination) return; // dropped outside the list
@@ -226,59 +277,26 @@ const SpacePanel = () => {
pointerEvents: "none",
} : undefined}
>
-
- SpaceStore.instance.setActiveSpace(null)}
- selected={!activeSpace}
- tooltip={_t("All rooms")}
- notificationState={RoomNotificationStateStore.instance.globalState}
- isNarrow={isPanelCollapsed}
- />
- { invites.map(s => (
- setPanelCollapsed(false)}
- />
- )) }
- { spaces.map((s, i) => (
-
- {(provided, snapshot) => (
- setPanelCollapsed(false)}
- />
- )}
-
- )) }
+
{ provided.placeholder }
-
+
+
{
- if (!isPanelCollapsed) setPanelCollapsed(true);
- openMenu();
- }}
+ onClick={onNewClick}
isNarrow={isPanelCollapsed}
/>
)}
setPanelCollapsed(!isPanelCollapsed)}
title={isPanelCollapsed ? _t("Expand space panel") : _t("Collapse space panel")}
/>
From 27e27b7a871f6eeb4fb37f9ee2d9dc2560442535 Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Wed, 16 Jun 2021 10:18:32 +0100
Subject: [PATCH 057/164] Convert MultiInviter, RoomInvite and UserAddress to
Typescript
---
src/{RoomInvite.js => RoomInvite.tsx} | 44 +++---
src/{UserAddress.js => UserAddress.ts} | 35 +++--
.../{MultiInviter.js => MultiInviter.ts} | 143 +++++++++++-------
3 files changed, 127 insertions(+), 95 deletions(-)
rename src/{RoomInvite.js => RoomInvite.tsx} (76%)
rename src/{UserAddress.js => UserAddress.ts} (69%)
rename src/utils/{MultiInviter.js => MultiInviter.ts} (66%)
diff --git a/src/RoomInvite.js b/src/RoomInvite.tsx
similarity index 76%
rename from src/RoomInvite.js
rename to src/RoomInvite.tsx
index aa758ecbdc..7c75b5d46b 100644
--- a/src/RoomInvite.js
+++ b/src/RoomInvite.tsx
@@ -1,7 +1,5 @@
/*
-Copyright 2016 OpenMarket Ltd
-Copyright 2017, 2018 New Vector Ltd
-Copyright 2020 The Matrix.org Foundation C.I.C.
+Copyright 2016 - 2021 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -16,15 +14,18 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
-import React from 'react';
-import {MatrixClientPeg} from './MatrixClientPeg';
-import MultiInviter from './utils/MultiInviter';
+import React from "react";
+import { Room } from "matrix-js-sdk/src/models/room";
+import { MatrixEvent } from "matrix-js-sdk/src/models/event";
+
+import { MatrixClientPeg } from './MatrixClientPeg';
+import MultiInviter, { CompletionStates } from './utils/MultiInviter';
import Modal from './Modal';
import * as sdk from './';
import { _t } from './languageHandler';
-import InviteDialog, {KIND_DM, KIND_INVITE} from "./components/views/dialogs/InviteDialog";
+import InviteDialog, { KIND_DM, KIND_INVITE } from "./components/views/dialogs/InviteDialog";
import CommunityPrototypeInviteDialog from "./components/views/dialogs/CommunityPrototypeInviteDialog";
-import {CommunityPrototypeStore} from "./stores/CommunityPrototypeStore";
+import { CommunityPrototypeStore } from "./stores/CommunityPrototypeStore";
/**
* Invites multiple addresses to a room
@@ -32,15 +33,18 @@ import {CommunityPrototypeStore} from "./stores/CommunityPrototypeStore";
* no option to cancel.
*
* @param {string} roomId The ID of the room to invite to
- * @param {string[]} addrs Array of strings of addresses to invite. May be matrix IDs or 3pids.
+ * @param {string[]} addresses Array of strings of addresses to invite. May be matrix IDs or 3pids.
* @returns {Promise} Promise
*/
-export function inviteMultipleToRoom(roomId, addrs) {
+export function inviteMultipleToRoom(
+ roomId: string,
+ addresses: string[],
+): Promise<{ states: CompletionStates, inviter: MultiInviter }> {
const inviter = new MultiInviter(roomId);
- return inviter.invite(addrs).then(states => Promise.resolve({states, inviter}));
+ return inviter.invite(addresses).then(states => Promise.resolve({ states, inviter }));
}
-export function showStartChatInviteDialog(initialText) {
+export function showStartChatInviteDialog(initialText = ""): void {
// This dialog handles the room creation internally - we don't need to worry about it.
const InviteDialog = sdk.getComponent("dialogs.InviteDialog");
Modal.createTrackedDialog(
@@ -49,7 +53,7 @@ export function showStartChatInviteDialog(initialText) {
);
}
-export function showRoomInviteDialog(roomId, initialText = "") {
+export function showRoomInviteDialog(roomId: string, initialText = ""): void {
// This dialog handles the room creation internally - we don't need to worry about it.
Modal.createTrackedDialog(
"Invite Users", "", InviteDialog, {
@@ -61,14 +65,14 @@ export function showRoomInviteDialog(roomId, initialText = "") {
);
}
-export function showCommunityRoomInviteDialog(roomId, communityName) {
+export function showCommunityRoomInviteDialog(roomId: string, communityName: string): void {
Modal.createTrackedDialog(
'Invite Users to Community', '', CommunityPrototypeInviteDialog, {communityName, roomId},
/*className=*/null, /*isPriority=*/false, /*isStatic=*/true,
);
}
-export function showCommunityInviteDialog(communityId) {
+export function showCommunityInviteDialog(communityId: string): void {
const chat = CommunityPrototypeStore.instance.getGeneralChat(communityId);
if (chat) {
const name = CommunityPrototypeStore.instance.getCommunityName(communityId);
@@ -83,7 +87,7 @@ export function showCommunityInviteDialog(communityId) {
* @param {MatrixEvent} event The event to check
* @returns {boolean} True if valid, false otherwise
*/
-export function isValid3pidInvite(event) {
+export function isValid3pidInvite(event: MatrixEvent): boolean {
if (!event || event.getType() !== "m.room.third_party_invite") return false;
// any events without these keys are not valid 3pid invites, so we ignore them
@@ -96,7 +100,7 @@ export function isValid3pidInvite(event) {
return true;
}
-export function inviteUsersToRoom(roomId, userIds) {
+export function inviteUsersToRoom(roomId: string, userIds: string[]): Promise {
return inviteMultipleToRoom(roomId, userIds).then((result) => {
const room = MatrixClientPeg.get().getRoom(roomId);
showAnyInviteErrors(result.states, room, result.inviter);
@@ -110,9 +114,9 @@ export function inviteUsersToRoom(roomId, userIds) {
});
}
-export function showAnyInviteErrors(addrs, room, inviter) {
+export function showAnyInviteErrors(states: CompletionStates, room: Room, inviter: MultiInviter): boolean {
// Show user any errors
- const failedUsers = Object.keys(addrs).filter(a => addrs[a] === 'error');
+ const failedUsers = Object.keys(states).filter(a => states[a] === 'error');
if (failedUsers.length === 1 && inviter.fatal) {
// Just get the first message because there was a fatal problem on the first
// user. This usually means that no other users were attempted, making it
@@ -126,7 +130,7 @@ export function showAnyInviteErrors(addrs, room, inviter) {
} else {
const errorList = [];
for (const addr of failedUsers) {
- if (addrs[addr] === "error") {
+ if (states[addr] === "error") {
const reason = inviter.getErrorText(addr);
errorList.push(addr + ": " + reason);
}
diff --git a/src/UserAddress.js b/src/UserAddress.ts
similarity index 69%
rename from src/UserAddress.js
rename to src/UserAddress.ts
index e7501a0d91..a2c546deb7 100644
--- a/src/UserAddress.js
+++ b/src/UserAddress.ts
@@ -1,5 +1,5 @@
/*
-Copyright 2017 New Vector Ltd
+Copyright 2017 - 2021 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -14,15 +14,19 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
-const emailRegex = /^\S+@\S+\.\S+$/;
+import PropTypes from "prop-types";
+const emailRegex = /^\S+@\S+\.\S+$/;
const mxUserIdRegex = /^@\S+:\S+$/;
const mxRoomIdRegex = /^!\S+:\S+$/;
-import PropTypes from 'prop-types';
-export const addressTypes = [
- 'mx-user-id', 'mx-room-id', 'email',
-];
+export const addressTypes = ['mx-user-id', 'mx-room-id', 'email'];
+
+export enum AddressType {
+ Email = "email",
+ MatrixUserId = "mx-user-id",
+ MatrixRoomId = "mx-room-id",
+}
// PropType definition for an object describing
// an address that can be invited to a room (which
@@ -40,18 +44,13 @@ export const UserAddressType = PropTypes.shape({
isKnown: PropTypes.bool,
});
-export function getAddressType(inputText) {
- const isEmailAddress = emailRegex.test(inputText);
- const isUserId = mxUserIdRegex.test(inputText);
- const isRoomId = mxRoomIdRegex.test(inputText);
-
- // sanity check the input for user IDs
- if (isEmailAddress) {
- return 'email';
- } else if (isUserId) {
- return 'mx-user-id';
- } else if (isRoomId) {
- return 'mx-room-id';
+export function getAddressType(inputText: string): AddressType | null {
+ if (emailRegex.test(inputText)) {
+ return AddressType.Email;
+ } else if (mxUserIdRegex.test(inputText)) {
+ return AddressType.MatrixUserId;
+ } else if (mxRoomIdRegex.test(inputText)) {
+ return AddressType.MatrixRoomId;
} else {
return null;
}
diff --git a/src/utils/MultiInviter.js b/src/utils/MultiInviter.ts
similarity index 66%
rename from src/utils/MultiInviter.js
rename to src/utils/MultiInviter.ts
index 78f956b91b..f6a994484e 100644
--- a/src/utils/MultiInviter.js
+++ b/src/utils/MultiInviter.ts
@@ -1,6 +1,5 @@
/*
-Copyright 2016 OpenMarket Ltd
-Copyright 2017, 2018 New Vector Ltd
+Copyright 2016 - 2021 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -15,23 +14,51 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
-import {MatrixClientPeg} from '../MatrixClientPeg';
-import {getAddressType} from '../UserAddress';
+import { MatrixError } from "matrix-js-sdk/src/http-api";
+
+import { MatrixClientPeg } from '../MatrixClientPeg';
+import { AddressType, getAddressType } from '../UserAddress';
import GroupStore from '../stores/GroupStore';
-import {_t} from "../languageHandler";
-import * as sdk from "../index";
+import { _t } from "../languageHandler";
import Modal from "../Modal";
import SettingsStore from "../settings/SettingsStore";
-import {defer} from "./promise";
+import { defer, IDeferred } from "./promise";
+import AskInviteAnywayDialog from "../components/views/dialogs/AskInviteAnywayDialog";
+
+export enum InviteState {
+ Invited = "invited",
+ Error = "error",
+}
+
+interface IError {
+ errorText: string;
+ errcode: string;
+}
+
+const UNKNOWN_PROFILE_ERRORS = ['M_NOT_FOUND', 'M_USER_NOT_FOUND', 'M_PROFILE_UNDISCLOSED', 'M_PROFILE_NOT_FOUND'];
+
+export type CompletionStates = Record;
/**
* Invites multiple addresses to a room or group, handling rate limiting from the server
*/
export default class MultiInviter {
+ private readonly roomId?: string;
+ private readonly groupId?: string;
+
+ private canceled = false;
+ private addresses: string[] = [];
+ private busy = false;
+ private _fatal = false;
+ private completionStates: CompletionStates = {}; // State of each address (invited or error)
+ private errors: Record = {}; // { address: {errorText, errcode} }
+ private deferred: IDeferred = null;
+ private reason: string = null;
+
/**
* @param {string} targetId The ID of the room or group to invite to
*/
- constructor(targetId) {
+ constructor(targetId: string) {
if (targetId[0] === '+') {
this.roomId = null;
this.groupId = targetId;
@@ -39,41 +66,38 @@ export default class MultiInviter {
this.roomId = targetId;
this.groupId = null;
}
+ }
- this.canceled = false;
- this.addrs = [];
- this.busy = false;
- this.completionStates = {}; // State of each address (invited or error)
- this.errors = {}; // { address: {errorText, errcode} }
- this.deferred = null;
+ public get fatal() {
+ return this._fatal;
}
/**
* Invite users to this room. This may only be called once per
* instance of the class.
*
- * @param {array} addrs Array of addresses to invite
+ * @param {array} addresses Array of addresses to invite
* @param {string} reason Reason for inviting (optional)
* @returns {Promise} Resolved when all invitations in the queue are complete
*/
- invite(addrs, reason) {
- if (this.addrs.length > 0) {
+ public invite(addresses, reason?: string): Promise {
+ if (this.addresses.length > 0) {
throw new Error("Already inviting/invited");
}
- this.addrs.push(...addrs);
+ this.addresses.push(...addresses);
this.reason = reason;
- for (const addr of this.addrs) {
+ for (const addr of this.addresses) {
if (getAddressType(addr) === null) {
- this.completionStates[addr] = 'error';
+ this.completionStates[addr] = InviteState.Error;
this.errors[addr] = {
errcode: 'M_INVALID',
errorText: _t('Unrecognised address'),
};
}
}
- this.deferred = defer();
- this._inviteMore(0);
+ this.deferred = defer();
+ this.inviteMore(0);
return this.deferred.promise;
}
@@ -81,33 +105,36 @@ export default class MultiInviter {
/**
* Stops inviting. Causes promises returned by invite() to be rejected.
*/
- cancel() {
+ public cancel(): void {
if (!this.busy) return;
- this._canceled = true;
+ this.canceled = true;
this.deferred.reject(new Error('canceled'));
}
- getCompletionState(addr) {
+ public getCompletionState(addr: string): InviteState {
return this.completionStates[addr];
}
- getErrorText(addr) {
+ public getErrorText(addr: string): string {
return this.errors[addr] ? this.errors[addr].errorText : null;
}
- async _inviteToRoom(roomId, addr, ignoreProfile) {
+ private async inviteToRoom(roomId: string, addr: string, ignoreProfile = false): Promise<{}> {
const addrType = getAddressType(addr);
- if (addrType === 'email') {
+ if (addrType === AddressType.Email) {
return MatrixClientPeg.get().inviteByEmail(roomId, addr);
- } else if (addrType === 'mx-user-id') {
+ } else if (addrType === AddressType.MatrixUserId) {
const room = MatrixClientPeg.get().getRoom(roomId);
if (!room) throw new Error("Room not found");
const member = room.getMember(addr);
if (member && ['join', 'invite'].includes(member.membership)) {
- throw {errcode: "RIOT.ALREADY_IN_ROOM", error: "Member already invited"};
+ throw new new MatrixError({
+ errcode: "RIOT.ALREADY_IN_ROOM",
+ error: "Member already invited",
+ });
}
if (!ignoreProfile && SettingsStore.getValue("promptBeforeInviteUnknownUsers", this.roomId)) {
@@ -124,28 +151,28 @@ export default class MultiInviter {
}
}
- _doInvite(address, ignoreProfile) {
- return new Promise((resolve, reject) => {
+ private doInvite(address: string, ignoreProfile = false): Promise {
+ return new Promise((resolve, reject) => {
console.log(`Inviting ${address}`);
let doInvite;
if (this.groupId !== null) {
doInvite = GroupStore.inviteUserToGroup(this.groupId, address);
} else {
- doInvite = this._inviteToRoom(this.roomId, address, ignoreProfile);
+ doInvite = this.inviteToRoom(this.roomId, address, ignoreProfile);
}
doInvite.then(() => {
- if (this._canceled) {
+ if (this.canceled) {
return;
}
- this.completionStates[address] = 'invited';
+ this.completionStates[address] = InviteState.Invited;
delete this.errors[address];
resolve();
}).catch((err) => {
- if (this._canceled) {
+ if (this.canceled) {
return;
}
@@ -161,7 +188,7 @@ export default class MultiInviter {
} else if (err.errcode === 'M_LIMIT_EXCEEDED') {
// we're being throttled so wait a bit & try again
setTimeout(() => {
- this._doInvite(address, ignoreProfile).then(resolve, reject);
+ this.doInvite(address, ignoreProfile).then(resolve, reject);
}, 5000);
return;
} else if (['M_NOT_FOUND', 'M_USER_NOT_FOUND'].includes(err.errcode)) {
@@ -171,7 +198,7 @@ export default class MultiInviter {
} else if (err.errcode === 'M_PROFILE_NOT_FOUND' && !ignoreProfile) {
// Invite without the profile check
console.warn(`User ${address} does not have a profile - inviting anyways automatically`);
- this._doInvite(address, true).then(resolve, reject);
+ this.doInvite(address, true).then(resolve, reject);
} else if (err.errcode === "M_BAD_STATE") {
errorText = _t("The user must be unbanned before they can be invited.");
} else if (err.errcode === "M_UNSUPPORTED_ROOM_VERSION") {
@@ -180,14 +207,14 @@ export default class MultiInviter {
errorText = _t('Unknown server error');
}
- this.completionStates[address] = 'error';
- this.errors[address] = {errorText, errcode: err.errcode};
+ this.completionStates[address] = InviteState.Error;
+ this.errors[address] = { errorText, errcode: err.errcode };
this.busy = !fatal;
- this.fatal = fatal;
+ this._fatal = fatal;
if (fatal) {
- reject();
+ reject(err);
} else {
resolve();
}
@@ -195,22 +222,22 @@ export default class MultiInviter {
});
}
- _inviteMore(nextIndex, ignoreProfile) {
- if (this._canceled) {
+ private inviteMore(nextIndex: number, ignoreProfile = false): void {
+ if (this.canceled) {
return;
}
- if (nextIndex === this.addrs.length) {
+ if (nextIndex === this.addresses.length) {
this.busy = false;
if (Object.keys(this.errors).length > 0 && !this.groupId) {
// There were problems inviting some people - see if we can invite them
// without caring if they exist or not.
- const unknownProfileErrors = ['M_NOT_FOUND', 'M_USER_NOT_FOUND', 'M_PROFILE_UNDISCLOSED', 'M_PROFILE_NOT_FOUND'];
- const unknownProfileUsers = Object.keys(this.errors).filter(a => unknownProfileErrors.includes(this.errors[a].errcode));
+ const unknownProfileUsers = Object.keys(this.errors)
+ .filter(a => UNKNOWN_PROFILE_ERRORS.includes(this.errors[a].errcode));
if (unknownProfileUsers.length > 0) {
const inviteUnknowns = () => {
- const promises = unknownProfileUsers.map(u => this._doInvite(u, true));
+ const promises = unknownProfileUsers.map(u => this.doInvite(u, true));
Promise.all(promises).then(() => this.deferred.resolve(this.completionStates));
};
@@ -219,15 +246,17 @@ export default class MultiInviter {
return;
}
- const AskInviteAnywayDialog = sdk.getComponent("dialogs.AskInviteAnywayDialog");
console.log("Showing failed to invite dialog...");
Modal.createTrackedDialog('Failed to invite', '', AskInviteAnywayDialog, {
- unknownProfileUsers: unknownProfileUsers.map(u => {return {userId: u, errorText: this.errors[u].errorText};}),
+ unknownProfileUsers: unknownProfileUsers.map(u => ({
+ userId: u,
+ errorText: this.errors[u].errorText,
+ })),
onInviteAnyways: () => inviteUnknowns(),
onGiveUp: () => {
// Fake all the completion states because we already warned the user
for (const addr of unknownProfileUsers) {
- this.completionStates[addr] = 'invited';
+ this.completionStates[addr] = InviteState.Invited;
}
this.deferred.resolve(this.completionStates);
},
@@ -239,25 +268,25 @@ export default class MultiInviter {
return;
}
- const addr = this.addrs[nextIndex];
+ const addr = this.addresses[nextIndex];
// don't try to invite it if it's an invalid address
// (it will already be marked as an error though,
// so no need to do so again)
if (getAddressType(addr) === null) {
- this._inviteMore(nextIndex + 1);
+ this.inviteMore(nextIndex + 1);
return;
}
// don't re-invite (there's no way in the UI to do this, but
// for sanity's sake)
- if (this.completionStates[addr] === 'invited') {
- this._inviteMore(nextIndex + 1);
+ if (this.completionStates[addr] === InviteState.Invited) {
+ this.inviteMore(nextIndex + 1);
return;
}
- this._doInvite(addr, ignoreProfile).then(() => {
- this._inviteMore(nextIndex + 1, ignoreProfile);
+ this.doInvite(addr, ignoreProfile).then(() => {
+ this.inviteMore(nextIndex + 1, ignoreProfile);
}).catch(() => this.deferred.resolve(this.completionStates));
}
}
From 47ddd33d2121ed8e74962c2ef02b1271837d6680 Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Wed, 16 Jun 2021 10:24:51 +0100
Subject: [PATCH 058/164] Remove explicit `.js` imports
---
src/components/structures/MatrixChat.tsx | 2 +-
src/components/views/dialogs/AddressPickerDialog.js | 2 +-
src/components/views/elements/AddressTile.js | 6 +++---
src/components/views/messages/SenderProfile.tsx | 2 +-
4 files changed, 6 insertions(+), 6 deletions(-)
diff --git a/src/components/structures/MatrixChat.tsx b/src/components/structures/MatrixChat.tsx
index 0af2d3d635..2cb0fbf3f6 100644
--- a/src/components/structures/MatrixChat.tsx
+++ b/src/components/structures/MatrixChat.tsx
@@ -48,7 +48,7 @@ import createRoom, {IOpts} from "../../createRoom";
import {_t, _td, getCurrentLanguage} from '../../languageHandler';
import SettingsStore from "../../settings/SettingsStore";
import ThemeController from "../../settings/controllers/ThemeController";
-import { startAnyRegistrationFlow } from "../../Registration.js";
+import { startAnyRegistrationFlow } from "../../Registration";
import { messageForSyncError } from '../../utils/ErrorUtils';
import ResizeNotifier from "../../utils/ResizeNotifier";
import AutoDiscoveryUtils, { ValidatedServerConfig } from "../../utils/AutoDiscoveryUtils";
diff --git a/src/components/views/dialogs/AddressPickerDialog.js b/src/components/views/dialogs/AddressPickerDialog.js
index 929d688e47..77c69abc4e 100644
--- a/src/components/views/dialogs/AddressPickerDialog.js
+++ b/src/components/views/dialogs/AddressPickerDialog.js
@@ -24,7 +24,7 @@ import { _t, _td } from '../../../languageHandler';
import * as sdk from '../../../index';
import {MatrixClientPeg} from '../../../MatrixClientPeg';
import dis from '../../../dispatcher/dispatcher';
-import { addressTypes, getAddressType } from '../../../UserAddress.js';
+import { addressTypes, getAddressType } from '../../../UserAddress';
import GroupStore from '../../../stores/GroupStore';
import * as Email from '../../../email';
import IdentityAuthClient from '../../../IdentityAuthClient';
diff --git a/src/components/views/elements/AddressTile.js b/src/components/views/elements/AddressTile.js
index df66d10a71..f8fa294b71 100644
--- a/src/components/views/elements/AddressTile.js
+++ b/src/components/views/elements/AddressTile.js
@@ -20,9 +20,9 @@ import PropTypes from 'prop-types';
import classNames from 'classnames';
import * as sdk from "../../../index";
import { _t } from '../../../languageHandler';
-import { UserAddressType } from '../../../UserAddress.js';
-import {replaceableComponent} from "../../../utils/replaceableComponent";
-import {mediaFromMxc} from "../../../customisations/Media";
+import { UserAddressType } from '../../../UserAddress';
+import { replaceableComponent } from "../../../utils/replaceableComponent";
+import { mediaFromMxc } from "../../../customisations/Media";
@replaceableComponent("views.elements.AddressTile")
export default class AddressTile extends React.Component {
diff --git a/src/components/views/messages/SenderProfile.tsx b/src/components/views/messages/SenderProfile.tsx
index 805f842fbc..de1549dffa 100644
--- a/src/components/views/messages/SenderProfile.tsx
+++ b/src/components/views/messages/SenderProfile.tsx
@@ -15,7 +15,7 @@
*/
import React from 'react';
-import Flair from '../elements/Flair.js';
+import Flair from '../elements/Flair';
import FlairStore from '../../../stores/FlairStore';
import {getUserNameColorClass} from '../../../utils/FormattingUtils';
import MatrixClientContext from "../../../contexts/MatrixClientContext";
From 384bb3af2e6a43fb28f3c17a8cb7d2612121ba4a Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Wed, 16 Jun 2021 10:37:34 +0100
Subject: [PATCH 059/164] Fix layout regression in the invite dialog for few
results
---
res/css/views/dialogs/_InviteDialog.scss | 1 +
1 file changed, 1 insertion(+)
diff --git a/res/css/views/dialogs/_InviteDialog.scss b/res/css/views/dialogs/_InviteDialog.scss
index 2e48b5d8e9..175b1cc556 100644
--- a/res/css/views/dialogs/_InviteDialog.scss
+++ b/res/css/views/dialogs/_InviteDialog.scss
@@ -294,6 +294,7 @@ limitations under the License.
flex-direction: column;
.mx_InviteDialog_content {
+ height: 100%;
overflow: hidden;
}
}
From 590ce5674055f8094c69c7ec3d490e40747dcc5d Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Wed, 16 Jun 2021 11:48:14 +0100
Subject: [PATCH 060/164] Use MultiInviter error messages in InviteDialog for
room invites
---
src/RoomInvite.tsx | 10 +++++---
src/components/views/dialogs/InviteDialog.tsx | 25 ++++++++-----------
2 files changed, 16 insertions(+), 19 deletions(-)
diff --git a/src/RoomInvite.tsx b/src/RoomInvite.tsx
index 7c75b5d46b..16141a87e8 100644
--- a/src/RoomInvite.tsx
+++ b/src/RoomInvite.tsx
@@ -27,6 +27,11 @@ import InviteDialog, { KIND_DM, KIND_INVITE } from "./components/views/dialogs/I
import CommunityPrototypeInviteDialog from "./components/views/dialogs/CommunityPrototypeInviteDialog";
import { CommunityPrototypeStore } from "./stores/CommunityPrototypeStore";
+export interface IInviteResult {
+ states: CompletionStates;
+ inviter: MultiInviter;
+}
+
/**
* Invites multiple addresses to a room
* Simpler interface to utils/MultiInviter but with
@@ -36,10 +41,7 @@ import { CommunityPrototypeStore } from "./stores/CommunityPrototypeStore";
* @param {string[]} addresses Array of strings of addresses to invite. May be matrix IDs or 3pids.
* @returns {Promise} Promise
*/
-export function inviteMultipleToRoom(
- roomId: string,
- addresses: string[],
-): Promise<{ states: CompletionStates, inviter: MultiInviter }> {
+export function inviteMultipleToRoom(roomId: string, addresses: string[]): Promise {
const inviter = new MultiInviter(roomId);
return inviter.invite(addresses).then(states => Promise.resolve({ states, inviter }));
}
diff --git a/src/components/views/dialogs/InviteDialog.tsx b/src/components/views/dialogs/InviteDialog.tsx
index 778744b783..f50f2f23d6 100644
--- a/src/components/views/dialogs/InviteDialog.tsx
+++ b/src/components/views/dialogs/InviteDialog.tsx
@@ -34,7 +34,12 @@ import {humanizeTime} from "../../../utils/humanize";
import createRoom, {
canEncryptToAllUsers, ensureDMExists, findDMForUser, privateShouldBeEncrypted,
} from "../../../createRoom";
-import {inviteMultipleToRoom, showCommunityInviteDialog} from "../../../RoomInvite";
+import {
+ IInviteResult,
+ inviteMultipleToRoom,
+ showAnyInviteErrors,
+ showCommunityInviteDialog,
+} from "../../../RoomInvite";
import {Key} from "../../../Keyboard";
import {Action} from "../../../dispatcher/actions";
import {DefaultTagID} from "../../../stores/room-list/models";
@@ -601,19 +606,9 @@ export default class InviteDialog extends React.PureComponent ({userId: m.member.userId, user: m.member}));
}
- private shouldAbortAfterInviteError(result): boolean {
- const failedUsers = Object.keys(result.states).filter(a => result.states[a] === 'error');
- if (failedUsers.length > 0) {
- console.log("Failed to invite users: ", result);
- this.setState({
- busy: false,
- errorText: _t("Failed to invite the following users to chat: %(csvUsers)s", {
- csvUsers: failedUsers.join(", "),
- }),
- });
- return true; // abort
- }
- return false;
+ private shouldAbortAfterInviteError(result: IInviteResult, room: Room): boolean {
+ this.setState({ busy: false });
+ return !showAnyInviteErrors(result.states, room, result.inviter);
}
private convertFilter(): Member[] {
@@ -731,7 +726,7 @@ export default class InviteDialog extends React.PureComponent
Date: Wed, 16 Jun 2021 12:12:00 +0100
Subject: [PATCH 061/164] remove unused imports
---
test/utils/stringOrderField-test.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/test/utils/stringOrderField-test.ts b/test/utils/stringOrderField-test.ts
index ece3043d86..331627dfc0 100644
--- a/test/utils/stringOrderField-test.ts
+++ b/test/utils/stringOrderField-test.ts
@@ -15,7 +15,7 @@ limitations under the License.
*/
import { sortBy } from "lodash";
-import { stringToBase, baseToString, averageBetweenStrings, DEFAULT_ALPHABET } from "matrix-js-sdk/src/utils";
+import { averageBetweenStrings, DEFAULT_ALPHABET } from "matrix-js-sdk/src/utils";
import { midPointsBetweenStrings, reorderLexicographically } from "../../src/utils/stringOrderField";
From 100de336a1aaf944a2a5b03b3eb016703394457a Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Wed, 16 Jun 2021 20:12:31 +0100
Subject: [PATCH 062/164] i18n
---
src/i18n/strings/en_EN.json | 1 -
1 file changed, 1 deletion(-)
diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json
index 17d6f64c46..57bf5fa739 100644
--- a/src/i18n/strings/en_EN.json
+++ b/src/i18n/strings/en_EN.json
@@ -2241,7 +2241,6 @@
"Confirm to continue": "Confirm to continue",
"Click the button below to confirm your identity.": "Click the button below to confirm your identity.",
"Invite by email": "Invite by email",
- "Failed to invite the following users to chat: %(csvUsers)s": "Failed to invite the following users to chat: %(csvUsers)s",
"We couldn't create your DM.": "We couldn't create your DM.",
"Something went wrong trying to invite the users.": "Something went wrong trying to invite the users.",
"We couldn't invite those users. Please check the users you want to invite and try again.": "We couldn't invite those users. Please check the users you want to invite and try again.",
From 79bf7bee560856be766493cd1a6dda8043bbe932 Mon Sep 17 00:00:00 2001
From: Robin Townsend
Date: Wed, 16 Jun 2021 18:23:44 -0400
Subject: [PATCH 063/164] Fix EventTilePreview display names
Because of 91df392a2a79383fa9a8a35cc9e4def6d3d4caab, we now need to
additionally set rawDisplayName to properly fake our display name for an
event.
Signed-off-by: Robin Townsend
---
src/components/views/elements/EventTilePreview.tsx | 1 +
1 file changed, 1 insertion(+)
diff --git a/src/components/views/elements/EventTilePreview.tsx b/src/components/views/elements/EventTilePreview.tsx
index 20d6cbaeb3..d39557c9bb 100644
--- a/src/components/views/elements/EventTilePreview.tsx
+++ b/src/components/views/elements/EventTilePreview.tsx
@@ -102,6 +102,7 @@ export default class EventTilePreview extends React.Component {
// Fake it more
event.sender = {
name: this.props.displayName || this.props.userId,
+ rawDisplayName: this.props.displayName,
userId: this.props.userId,
getAvatarUrl: (..._) => {
return Avatar.avatarUrlForUser(
From 7c6161d83ad60df8e332656ca0389ce90797ec3e Mon Sep 17 00:00:00 2001
From: "J. Ryan Stinnett"
Date: Wed, 16 Jun 2021 18:00:06 +0100
Subject: [PATCH 064/164] Stop requesting null next replies from the server
A recent change (47e007e08f9bedaf47cf59a63c9bd04219195d76) introduced a
regression where we failed to check whether a reply thread has a next reply.
This meant that we would end up sending `/context/undefined` requests to the
server for every reply thread on every room view.
Fixes https://github.com/vector-im/element-web/issues/17563
Regressed by https://github.com/matrix-org/matrix-react-sdk/pull/6079
---
src/components/views/elements/ReplyThread.js | 1 +
1 file changed, 1 insertion(+)
diff --git a/src/components/views/elements/ReplyThread.js b/src/components/views/elements/ReplyThread.js
index 81ed360b17..a9b24a306b 100644
--- a/src/components/views/elements/ReplyThread.js
+++ b/src/components/views/elements/ReplyThread.js
@@ -297,6 +297,7 @@ export default class ReplyThread extends React.Component {
}
async getEvent(eventId) {
+ if (!eventId) return null;
const event = this.room.findEventById(eventId);
if (event) return event;
From 0367b5bcced808ce75e19f0a7669f3ab5b61524c Mon Sep 17 00:00:00 2001
From: Germain Souquet
Date: Thu, 17 Jun 2021 08:45:09 +0100
Subject: [PATCH 065/164] remove stray bullet point in reply preview
---
src/components/views/elements/ReplyThread.js | 1 +
1 file changed, 1 insertion(+)
diff --git a/src/components/views/elements/ReplyThread.js b/src/components/views/elements/ReplyThread.js
index 81ed360b17..0f6aef37eb 100644
--- a/src/components/views/elements/ReplyThread.js
+++ b/src/components/views/elements/ReplyThread.js
@@ -392,6 +392,7 @@ export default class ReplyThread extends React.Component {
alwaysShowTimestamps={this.props.alwaysShowTimestamps}
enableFlair={SettingsStore.getValue(UIFeature.Flair)}
replacingEventId={ev.replacingEventId()}
+ as="div"
/>
;
});
From 1597b2a971bb3e0194c4a8cf8bb5530923f17438 Mon Sep 17 00:00:00 2001
From: Germain Souquet
Date: Wed, 16 Jun 2021 10:01:23 +0100
Subject: [PATCH 066/164] Keep composer reply when scrolling away from a
highlighted event
---
src/components/structures/RoomView.tsx | 1 +
1 file changed, 1 insertion(+)
diff --git a/src/components/structures/RoomView.tsx b/src/components/structures/RoomView.tsx
index fe90d2f873..c0ce6ba4c9 100644
--- a/src/components/structures/RoomView.tsx
+++ b/src/components/structures/RoomView.tsx
@@ -701,6 +701,7 @@ export default class RoomView extends React.Component {
room_id: this.state.room.roomId,
event_id: this.state.initialEventId,
highlighted: false,
+ replyingToEvent: this.state.replyToEvent,
});
}
}
From 7d90612371a31f699d879366262f84fd1082bd9b Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Thu, 17 Jun 2021 16:22:40 +0100
Subject: [PATCH 067/164] Iterate PR
---
res/css/views/beta/_BetaCard.scss | 93 +++++++++++--------
src/components/views/beta/BetaCard.tsx | 49 +++++-----
.../views/elements/SettingsFlag.tsx | 10 +-
src/i18n/strings/en_EN.json | 8 +-
src/settings/Settings.tsx | 11 ++-
src/settings/SettingsStore.ts | 10 ++
6 files changed, 107 insertions(+), 74 deletions(-)
diff --git a/res/css/views/beta/_BetaCard.scss b/res/css/views/beta/_BetaCard.scss
index fd87b1c824..1a8241b65f 100644
--- a/res/css/views/beta/_BetaCard.scss
+++ b/res/css/views/beta/_BetaCard.scss
@@ -19,55 +19,68 @@ limitations under the License.
padding: 24px;
background-color: $settings-profile-placeholder-bg-color;
border-radius: 8px;
- display: flex;
box-sizing: border-box;
- > div {
- .mx_BetaCard_title {
- font-weight: $font-semi-bold;
- font-size: $font-18px;
- line-height: $font-22px;
- color: $primary-fg-color;
- margin: 4px 0 14px;
+ .mx_BetaCard_columns {
+ display: flex;
- .mx_BetaCard_betaPill {
- margin-left: 12px;
+ > div {
+ .mx_BetaCard_title {
+ font-weight: $font-semi-bold;
+ font-size: $font-18px;
+ line-height: $font-22px;
+ color: $primary-fg-color;
+ margin: 4px 0 14px;
+
+ .mx_BetaCard_betaPill {
+ margin-left: 12px;
+ }
+ }
+
+ .mx_BetaCard_caption {
+ font-size: $font-15px;
+ line-height: $font-20px;
+ color: $secondary-fg-color;
+ margin-bottom: 20px;
+ }
+
+ .mx_BetaCard_buttons .mx_AccessibleButton {
+ display: block;
+ margin: 12px 0;
+ padding: 7px 40px;
+ width: auto;
+ }
+
+ .mx_BetaCard_disclaimer {
+ font-size: $font-12px;
+ line-height: $font-15px;
+ color: $secondary-fg-color;
+ margin-top: 20px;
}
}
- .mx_BetaCard_caption {
- font-size: $font-15px;
- line-height: $font-20px;
- color: $secondary-fg-color;
- margin-bottom: 20px;
- }
-
- .mx_BetaCard_buttons .mx_AccessibleButton {
- display: block;
- margin: 12px 0;
- padding: 7px 40px;
- width: auto;
- }
-
- .mx_BetaCard_disclaimer {
- font-size: $font-12px;
- line-height: $font-15px;
- color: $secondary-fg-color;
- margin-top: 20px;
- }
-
- .mx_BetaCard_relatedSettings {
- summary + .mx_SettingsFlag {
- margin-top: 4px;
- }
+ > img {
+ margin: auto 0 auto 20px;
+ width: 300px;
+ object-fit: contain;
+ height: 100%;
}
}
- > img {
- margin: auto 0 auto 20px;
- width: 300px;
- object-fit: contain;
- height: 100%;
+ .mx_BetaCard_relatedSettings {
+ .mx_SettingsFlag {
+ margin: 16px 0 0;
+ font-size: $font-15px;
+ line-height: $font-24px;
+ color: $primary-fg-color;
+
+ .mx_SettingsFlag_microcopy {
+ margin-top: 4px;
+ font-size: $font-12px;
+ line-height: $font-15px;
+ color: $secondary-fg-color;
+ }
+ }
}
}
diff --git a/src/components/views/beta/BetaCard.tsx b/src/components/views/beta/BetaCard.tsx
index 56770c3385..aa4fe49f63 100644
--- a/src/components/views/beta/BetaCard.tsx
+++ b/src/components/views/beta/BetaCard.tsx
@@ -83,32 +83,33 @@ const BetaCard = ({ title: titleOverride, featureId }: IProps) => {
}
return
-
-
- { titleOverride || _t(title) }
-
-
-
{ _t(caption) }
-
- { feedbackButton }
-
SettingsStore.setValue(featureId, null, SettingLevel.DEVICE, !value)}
- kind={feedbackButton ? "primary_outline" : "primary"}
- >
- { value ? _t("Leave the beta") : _t("Join the beta") }
-
+
+
+
+ { titleOverride || _t(title) }
+
+
+
{ _t(caption) }
+
+ { feedbackButton }
+
SettingsStore.setValue(featureId, null, SettingLevel.DEVICE, !value)}
+ kind={feedbackButton ? "primary_outline" : "primary"}
+ >
+ { value ? _t("Leave the beta") : _t("Join the beta") }
+
+
+ { disclaimer &&
+ { disclaimer(value) }
+
}
- { disclaimer &&
- { disclaimer(value) }
-
}
- { extraSettings &&
- { _t("Experimental options") }
- { extraSettings.map(key => (
-
- )) }
- }
+
-
+ { extraSettings &&
+ { extraSettings.map(key => (
+
+ )) }
+
}
;
};
diff --git a/src/components/views/elements/SettingsFlag.tsx b/src/components/views/elements/SettingsFlag.tsx
index 4f885ab47d..24a21e1a33 100644
--- a/src/components/views/elements/SettingsFlag.tsx
+++ b/src/components/views/elements/SettingsFlag.tsx
@@ -77,9 +77,10 @@ export default class SettingsFlag extends React.Component
{
public render() {
const canChange = SettingsStore.canSetValue(this.props.name, this.props.roomId, this.props.level);
- let label = this.props.label;
- if (!label) label = SettingsStore.getDisplayName(this.props.name, this.props.level);
- else label = _t(label);
+ const label = this.props.label
+ ? _t(this.props.label)
+ : SettingsStore.getDisplayName(this.props.name, this.props.level);
+ const description = SettingsStore.getDescription(this.props.name);
if (this.props.useCheckbox) {
return {
disabled={this.props.disabled || !canChange}
aria-label={label}
/>
+ { description &&
+ { description }
+
}
);
}
diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json
index 044e3a3079..179b58b617 100644
--- a/src/i18n/strings/en_EN.json
+++ b/src/i18n/strings/en_EN.json
@@ -793,9 +793,10 @@
"You can leave the beta any time from settings or tapping on a beta badge, like the one above.": "You can leave the beta any time from settings or tapping on a beta badge, like the one above.",
"Beta available for web, desktop and Android. Some features may be unavailable on your homeserver.": "Beta available for web, desktop and Android. Some features may be unavailable on your homeserver.",
"Your feedback will help make spaces better. The more detail you can go into, the better.": "Your feedback will help make spaces better. The more detail you can go into, the better.",
- "Use an all rooms space instead of a home space.": "Use an all rooms space instead of a home space.",
- "Show DMs for joined/invited members in the space.": "Show DMs for joined/invited members in the space.",
- "Show notification badges for DMs in spaces.": "Show notification badges for DMs in spaces.",
+ "Show all rooms in Home": "Show all rooms in Home",
+ "Show people in spaces": "Show people in spaces",
+ "If disabled, you can still add Direct Messages to Personal Spaces. If enabled, you'll automatically see everyone who is a member of the Space.": "If disabled, you can still add Direct Messages to Personal Spaces. If enabled, you'll automatically see everyone who is a member of the Space.",
+ "Show notification badges for DMs in Spaces.": "Show notification badges for DMs in Spaces.",
"Show options to enable 'Do not disturb' mode": "Show options to enable 'Do not disturb' mode",
"Send and receive voice messages": "Send and receive voice messages",
"Render LaTeX maths in messages": "Render LaTeX maths in messages",
@@ -2510,7 +2511,6 @@
"Beta": "Beta",
"Leave the beta": "Leave the beta",
"Join the beta": "Join the beta",
- "Experimental options": "Experimental options",
"Avatar": "Avatar",
"This room is public": "This room is public",
"Away": "Away",
diff --git a/src/settings/Settings.tsx b/src/settings/Settings.tsx
index a291cd1fba..af026f4103 100644
--- a/src/settings/Settings.tsx
+++ b/src/settings/Settings.tsx
@@ -94,6 +94,9 @@ export interface ISetting {
[level: SettingLevel]: string;
};
+ // Optional description which will be shown as microCopy under SettingsFlags
+ description?: string;
+
// The supported levels are required. Preferably, use the preset arrays
// at the top of this file to define this rather than a custom array.
supportedLevels?: SettingLevel[];
@@ -176,19 +179,21 @@ export const SETTINGS: {[setting: string]: ISetting} = {
},
},
"feature_spaces.all_rooms": {
- displayName: _td("Use an all rooms space instead of a home space."),
+ displayName: _td("Show all rooms in Home"),
supportedLevels: LEVELS_FEATURE,
default: true,
controller: new ReloadOnChangeController(),
},
"feature_spaces.space_member_dms": {
- displayName: _td("Show DMs for joined/invited members in the space."),
+ displayName: _td("Show people in spaces"),
+ description: _td("If disabled, you can still add Direct Messages to Personal Spaces. " +
+ "If enabled, you'll automatically see everyone who is a member of the Space."),
supportedLevels: LEVELS_FEATURE,
default: true,
controller: new ReloadOnChangeController(),
},
"feature_spaces.space_dm_badges": {
- displayName: _td("Show notification badges for DMs in spaces."),
+ displayName: _td("Show notification badges for DMs in Spaces."),
supportedLevels: LEVELS_FEATURE,
default: false,
controller: new ReloadOnChangeController(),
diff --git a/src/settings/SettingsStore.ts b/src/settings/SettingsStore.ts
index e1e300e185..44f3d5d838 100644
--- a/src/settings/SettingsStore.ts
+++ b/src/settings/SettingsStore.ts
@@ -248,6 +248,16 @@ export default class SettingsStore {
return _t(displayName as string);
}
+ /**
+ * Gets the translated description for a given setting
+ * @param {string} settingName The setting to look up.
+ * @return {String} The description for the setting, or null if not found.
+ */
+ public static getDescription(settingName: string) {
+ if (!SETTINGS[settingName]?.description) return null;
+ return _t(SETTINGS[settingName].description);
+ }
+
/**
* Determines if a setting is also a feature.
* @param {string} settingName The setting to look up.
From a687391b98d638e69983e5d814bcb73bfe52a381 Mon Sep 17 00:00:00 2001
From: Travis Ralston
Date: Thu, 17 Jun 2021 14:21:01 -0600
Subject: [PATCH 068/164] Switch order
---
src/components/views/messages/TextualBody.js | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/components/views/messages/TextualBody.js b/src/components/views/messages/TextualBody.js
index 00e7d3d301..cb6a4f14b6 100644
--- a/src/components/views/messages/TextualBody.js
+++ b/src/components/views/messages/TextualBody.js
@@ -261,8 +261,8 @@ export default class TextualBody extends React.Component {
//console.info("shouldComponentUpdate: ShowUrlPreview for %s is %s", this.props.mxEvent.getId(), this.props.showUrlPreview);
// exploit that events are immutable :)
- return (nextProps.mxEvent.getId() !== this.props.mxEvent.getId() ||
- nextProps.mxEvent !== this.props.mxEvent ||
+ return (nextProps.mxEvent !== this.props.mxEvent ||
+ nextProps.mxEvent.getId() !== this.props.mxEvent.getId() ||
nextProps.highlights !== this.props.highlights ||
nextProps.replacingEventId !== this.props.replacingEventId ||
nextProps.highlightLink !== this.props.highlightLink ||
From 98e0200b4a17fc80b1865ae25bdfec80de0bc161 Mon Sep 17 00:00:00 2001
From: Travis Ralston
Date: Thu, 17 Jun 2021 14:21:50 -0600
Subject: [PATCH 069/164] Function name
---
src/components/views/rooms/EventTile.tsx | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/components/views/rooms/EventTile.tsx b/src/components/views/rooms/EventTile.tsx
index d1b596a709..4dd8fff636 100644
--- a/src/components/views/rooms/EventTile.tsx
+++ b/src/components/views/rooms/EventTile.tsx
@@ -335,7 +335,7 @@ export default class EventTile extends React.Component {
// The Relations model from the JS SDK for reactions to `mxEvent`
reactions: this.getReactions(),
- mxEvent: this.mxEvent.getSnapshotCopy(), // snapshot up front to verify it all works
+ mxEvent: this.mxEvent.toSnapshot(), // snapshot up front to verify it all works
hover: false,
};
@@ -497,7 +497,7 @@ export default class EventTile extends React.Component {
// a second state update to re-render child components, which ultimately calls didUpdate
// again, so we break that loop with a reference check first (faster than comparing events).
if (this.state.mxEvent === prevState.mxEvent && !this.state?.mxEvent.isEquivalentTo(this.props.mxEvent)) {
- this.setState({mxEvent: this.props.mxEvent.getSnapshotCopy()});
+ this.setState({mxEvent: this.props.mxEvent.toSnapshot()});
}
}
From 9cce5ef10f116a13167a04ae42954adc8569c1f3 Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Fri, 18 Jun 2021 15:31:12 +0100
Subject: [PATCH 070/164] Consolidate types with js-sdk changes
---
src/Avatar.ts | 3 +--
src/components/structures/RoomView.tsx | 8 ++------
2 files changed, 3 insertions(+), 8 deletions(-)
diff --git a/src/Avatar.ts b/src/Avatar.ts
index 8ea0b0c9fa..4c4bd1c265 100644
--- a/src/Avatar.ts
+++ b/src/Avatar.ts
@@ -17,13 +17,12 @@ limitations under the License.
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 DMRoomMap from './utils/DMRoomMap';
import { mediaFromMxc } from "./customisations/Media";
import SettingsStore from "./settings/SettingsStore";
-export type ResizeMethod = "crop" | "scale";
-
// Not to be used for BaseAvatar urls as that has similar default avatar fallback already
export function avatarUrlForMember(
member: RoomMember,
diff --git a/src/components/structures/RoomView.tsx b/src/components/structures/RoomView.tsx
index 1e3adcb518..d9f2d5231b 100644
--- a/src/components/structures/RoomView.tsx
+++ b/src/components/structures/RoomView.tsx
@@ -23,7 +23,7 @@ limitations under the License.
import React, { createRef } from 'react';
import classNames from 'classnames';
-import { Room } from "matrix-js-sdk/src/models/room";
+import { IRecommendedVersion, Room } from "matrix-js-sdk/src/models/room";
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
import { SearchResult } from "matrix-js-sdk/src/models/search-result";
import { EventSubscription } from "fbemitter";
@@ -172,11 +172,7 @@ export interface IState {
// We load this later by asking the js-sdk to suggest a version for us.
// This object is the result of Room#getRecommendedVersion()
- upgradeRecommendation?: {
- version: string;
- needsUpgrade: boolean;
- urgent: boolean;
- };
+ upgradeRecommendation?: IRecommendedVersion;
canReact: boolean;
canReply: boolean;
layout: Layout;
From 058cbbbd0c6c2534e390d000844ccde74683f690 Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Fri, 18 Jun 2021 16:13:55 +0100
Subject: [PATCH 071/164] Fix imports
---
src/autocomplete/Autocompleter.ts | 7 ++--
src/autocomplete/NotifProvider.tsx | 3 +-
src/autocomplete/RoomProvider.tsx | 12 +++---
src/components/views/avatars/BaseAvatar.tsx | 9 +++--
src/components/views/avatars/GroupAvatar.tsx | 7 ++--
src/components/views/avatars/MemberAvatar.tsx | 10 ++---
src/components/views/avatars/RoomAvatar.tsx | 12 +++---
src/components/views/elements/RoomName.tsx | 8 ++--
.../views/rooms/WhoIsTypingTile.tsx | 2 +-
src/customisations/Media.ts | 9 +++--
src/utils/ShieldUtils.ts | 39 ++++++++++---------
11 files changed, 62 insertions(+), 56 deletions(-)
diff --git a/src/autocomplete/Autocompleter.ts b/src/autocomplete/Autocompleter.ts
index 5409825f45..ea8eddbb8d 100644
--- a/src/autocomplete/Autocompleter.ts
+++ b/src/autocomplete/Autocompleter.ts
@@ -15,8 +15,9 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
-import {ReactElement} from 'react';
-import Room from 'matrix-js-sdk/src/models/room';
+import { ReactElement } from 'react';
+import { Room } from 'matrix-js-sdk/src/models/room';
+
import CommandProvider from './CommandProvider';
import CommunityProvider from './CommunityProvider';
import DuckDuckGoProvider from './DuckDuckGoProvider';
@@ -24,7 +25,7 @@ import RoomProvider from './RoomProvider';
import UserProvider from './UserProvider';
import EmojiProvider from './EmojiProvider';
import NotifProvider from './NotifProvider';
-import {timeout} from "../utils/promise";
+import { timeout } from "../utils/promise";
import AutocompleteProvider, {ICommand} from "./AutocompleteProvider";
import SettingsStore from "../settings/SettingsStore";
import SpaceProvider from "./SpaceProvider";
diff --git a/src/autocomplete/NotifProvider.tsx b/src/autocomplete/NotifProvider.tsx
index 0bc7ead097..827f4aa885 100644
--- a/src/autocomplete/NotifProvider.tsx
+++ b/src/autocomplete/NotifProvider.tsx
@@ -15,7 +15,8 @@ limitations under the License.
*/
import React from 'react';
-import Room from "matrix-js-sdk/src/models/room";
+import { Room } from "matrix-js-sdk/src/models/room";
+
import AutocompleteProvider from './AutocompleteProvider';
import { _t } from '../languageHandler';
import {MatrixClientPeg} from '../MatrixClientPeg';
diff --git a/src/autocomplete/RoomProvider.tsx b/src/autocomplete/RoomProvider.tsx
index ad55b19101..a82a757a78 100644
--- a/src/autocomplete/RoomProvider.tsx
+++ b/src/autocomplete/RoomProvider.tsx
@@ -17,16 +17,16 @@ limitations under the License.
*/
import React from "react";
-import {uniqBy, sortBy} from "lodash";
-import Room from "matrix-js-sdk/src/models/room";
+import { uniqBy, sortBy } from "lodash";
+import { Room } from "matrix-js-sdk/src/models/room";
import { _t } from '../languageHandler';
import AutocompleteProvider from './AutocompleteProvider';
-import {MatrixClientPeg} from '../MatrixClientPeg';
+import { MatrixClientPeg } from '../MatrixClientPeg';
import QueryMatcher from './QueryMatcher';
-import {PillCompletion} from './Components';
-import {makeRoomPermalink} from "../utils/permalinks/Permalinks";
-import {ICompletion, ISelectionRange} from "./Autocompleter";
+import { PillCompletion } from './Components';
+import { makeRoomPermalink } from "../utils/permalinks/Permalinks";
+import { ICompletion, ISelectionRange } from "./Autocompleter";
import RoomAvatar from '../components/views/avatars/RoomAvatar';
import SettingsStore from "../settings/SettingsStore";
diff --git a/src/components/views/avatars/BaseAvatar.tsx b/src/components/views/avatars/BaseAvatar.tsx
index 6949c14636..f98f8c88a1 100644
--- a/src/components/views/avatars/BaseAvatar.tsx
+++ b/src/components/views/avatars/BaseAvatar.tsx
@@ -17,16 +17,17 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
-import React, {useCallback, useContext, useEffect, useState} from 'react';
+import React, { useCallback, useContext, useEffect, useState } from 'react';
import classNames from 'classnames';
+import { ResizeMethod } from 'matrix-js-sdk/src/@types/partials';
+
import * as AvatarLogic from '../../../Avatar';
import SettingsStore from "../../../settings/SettingsStore";
import AccessibleButton from '../elements/AccessibleButton';
import RoomContext from "../../../contexts/RoomContext";
import MatrixClientContext from "../../../contexts/MatrixClientContext";
-import {useEventEmitter} from "../../../hooks/useEventEmitter";
-import {toPx} from "../../../utils/units";
-import {ResizeMethod} from "../../../Avatar";
+import { useEventEmitter } from "../../../hooks/useEventEmitter";
+import { toPx } from "../../../utils/units";
import { _t } from '../../../languageHandler';
interface IProps {
diff --git a/src/components/views/avatars/GroupAvatar.tsx b/src/components/views/avatars/GroupAvatar.tsx
index 3734ba9504..13dbbfec09 100644
--- a/src/components/views/avatars/GroupAvatar.tsx
+++ b/src/components/views/avatars/GroupAvatar.tsx
@@ -15,10 +15,11 @@ limitations under the License.
*/
import React from 'react';
+import { ResizeMethod } from 'matrix-js-sdk/src/@types/partials';
+
import BaseAvatar from './BaseAvatar';
-import {replaceableComponent} from "../../../utils/replaceableComponent";
-import {mediaFromMxc} from "../../../customisations/Media";
-import {ResizeMethod} from "../../../Avatar";
+import { replaceableComponent } from "../../../utils/replaceableComponent";
+import { mediaFromMxc } from "../../../customisations/Media";
export interface IProps {
groupId?: string;
diff --git a/src/components/views/avatars/MemberAvatar.tsx b/src/components/views/avatars/MemberAvatar.tsx
index 3205ca372c..862563a8b4 100644
--- a/src/components/views/avatars/MemberAvatar.tsx
+++ b/src/components/views/avatars/MemberAvatar.tsx
@@ -16,14 +16,14 @@ limitations under the License.
*/
import React from 'react';
-import {RoomMember} from "matrix-js-sdk/src/models/room-member";
+import { RoomMember } from "matrix-js-sdk/src/models/room-member";
+import { ResizeMethod } from 'matrix-js-sdk/src/@types/partials';
import dis from "../../../dispatcher/dispatcher";
-import {Action} from "../../../dispatcher/actions";
+import { Action } from "../../../dispatcher/actions";
import BaseAvatar from "./BaseAvatar";
-import {replaceableComponent} from "../../../utils/replaceableComponent";
-import {mediaFromMxc} from "../../../customisations/Media";
-import {ResizeMethod} from "../../../Avatar";
+import { replaceableComponent } from "../../../utils/replaceableComponent";
+import { mediaFromMxc } from "../../../customisations/Media";
interface IProps extends Omit, "name" | "idName" | "url"> {
member: RoomMember;
diff --git a/src/components/views/avatars/RoomAvatar.tsx b/src/components/views/avatars/RoomAvatar.tsx
index 4693d907ba..bd820509c5 100644
--- a/src/components/views/avatars/RoomAvatar.tsx
+++ b/src/components/views/avatars/RoomAvatar.tsx
@@ -13,17 +13,17 @@ 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, {ComponentProps} from 'react';
-import Room from 'matrix-js-sdk/src/models/room';
+import React, { ComponentProps } from 'react';
+import { Room } from 'matrix-js-sdk/src/models/room';
+import { ResizeMethod } from 'matrix-js-sdk/src/@types/partials';
import BaseAvatar from './BaseAvatar';
import ImageView from '../elements/ImageView';
-import {MatrixClientPeg} from '../../../MatrixClientPeg';
+import { MatrixClientPeg } from '../../../MatrixClientPeg';
import Modal from '../../../Modal';
import * as Avatar from '../../../Avatar';
-import {ResizeMethod} from "../../../Avatar";
-import {replaceableComponent} from "../../../utils/replaceableComponent";
-import {mediaFromMxc} from "../../../customisations/Media";
+import { replaceableComponent } from "../../../utils/replaceableComponent";
+import { mediaFromMxc } from "../../../customisations/Media";
interface IProps extends Omit, "name" | "idName" | "url" | "onClick"> {
// Room may be left unset here, but if it is,
diff --git a/src/components/views/elements/RoomName.tsx b/src/components/views/elements/RoomName.tsx
index 9178155d19..cdd83aedc2 100644
--- a/src/components/views/elements/RoomName.tsx
+++ b/src/components/views/elements/RoomName.tsx
@@ -14,10 +14,10 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
-import {useEffect, useState} from "react";
-import {Room} from "matrix-js-sdk/src/models/room";
+import React, { useEffect, useState } from "react";
+import { Room } from "matrix-js-sdk/src/models/room";
-import {useEventEmitter} from "../../../hooks/useEventEmitter";
+import { useEventEmitter } from "../../../hooks/useEventEmitter";
interface IProps {
room: Room;
@@ -34,7 +34,7 @@ const RoomName = ({ room, children }: IProps): JSX.Element => {
}, [room]);
if (children) return children(name);
- return name || "";
+ return <>{ name || "" }>;
};
export default RoomName;
diff --git a/src/components/views/rooms/WhoIsTypingTile.tsx b/src/components/views/rooms/WhoIsTypingTile.tsx
index 3a1d2051b4..93078ff645 100644
--- a/src/components/views/rooms/WhoIsTypingTile.tsx
+++ b/src/components/views/rooms/WhoIsTypingTile.tsx
@@ -16,7 +16,7 @@ limitations under the License.
*/
import React from 'react';
-import Room from "matrix-js-sdk/src/models/room";
+import { Room } from "matrix-js-sdk/src/models/room";
import { RoomMember } from "matrix-js-sdk/src/models/room-member";
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
diff --git a/src/customisations/Media.ts b/src/customisations/Media.ts
index f9d957b60c..37e91fc54b 100644
--- a/src/customisations/Media.ts
+++ b/src/customisations/Media.ts
@@ -14,10 +14,11 @@
* limitations under the License.
*/
-import {MatrixClientPeg} from "../MatrixClientPeg";
-import {IMediaEventContent, IPreparedMedia, prepEventContentAsMedia} from "./models/IMediaEventContent";
-import {ResizeMethod} from "../Avatar";
-import {MatrixClient} from "matrix-js-sdk/src/client";
+import { MatrixClient } from "matrix-js-sdk/src/client";
+import { ResizeMethod } from "matrix-js-sdk/src/@types/partials";
+
+import { MatrixClientPeg } from "../MatrixClientPeg";
+import { IMediaEventContent, IPreparedMedia, prepEventContentAsMedia } from "./models/IMediaEventContent";
// Populate this class with the details of your customisations when copying it.
diff --git a/src/utils/ShieldUtils.ts b/src/utils/ShieldUtils.ts
index 5fe653fed0..c855b81bf8 100644
--- a/src/utils/ShieldUtils.ts
+++ b/src/utils/ShieldUtils.ts
@@ -1,30 +1,31 @@
+/*
+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 { MatrixClient } from "matrix-js-sdk/src/client";
+import { Room } from "matrix-js-sdk/src/models/room";
+
import DMRoomMap from './DMRoomMap';
-/* For now, a cut-down type spec for the client */
-interface Client {
- getUserId: () => string;
- checkUserTrust: (userId: string) => {
- isCrossSigningVerified: () => boolean
- wasCrossSigningVerified: () => boolean
- };
- getStoredDevicesForUser: (userId: string) => [{ deviceId: string }];
- checkDeviceTrust: (userId: string, deviceId: string) => {
- isVerified: () => boolean
- };
-}
-
-interface Room {
- getEncryptionTargetMembers: () => Promise<[{userId: string}]>;
- roomId: string;
-}
-
export enum E2EStatus {
Warning = "warning",
Verified = "verified",
Normal = "normal"
}
-export async function shieldStatusForRoom(client: Client, room: Room): Promise {
+export async function shieldStatusForRoom(client: MatrixClient, room: Room): Promise {
const members = (await room.getEncryptionTargetMembers()).map(({userId}) => userId);
const inDMMap = !!DMRoomMap.shared().getUserIdForRoomId(room.roomId);
From 3b7c92fd9ee02961566eb52471f5883cbe12db8a Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Fri, 18 Jun 2021 16:18:42 +0100
Subject: [PATCH 072/164] Use new js-sdk types properly
---
src/components/structures/RoomView.tsx | 4 ++--
src/mjolnir/BanList.ts | 2 +-
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/src/components/structures/RoomView.tsx b/src/components/structures/RoomView.tsx
index d9f2d5231b..bfc7a1972d 100644
--- a/src/components/structures/RoomView.tsx
+++ b/src/components/structures/RoomView.tsx
@@ -23,7 +23,7 @@ limitations under the License.
import React, { createRef } from 'react';
import classNames from 'classnames';
-import { IRecommendedVersion, Room } from "matrix-js-sdk/src/models/room";
+import { IRecommendedVersion, NotificationCountType, Room } from "matrix-js-sdk/src/models/room";
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
import { SearchResult } from "matrix-js-sdk/src/models/search-result";
import { EventSubscription } from "fbemitter";
@@ -2054,7 +2054,7 @@ export default class RoomView extends React.Component {
if (!this.state.atEndOfLiveTimeline && !this.state.searchResults) {
const JumpToBottomButton = sdk.getComponent('rooms.JumpToBottomButton');
jumpToBottom = ( 0}
+ highlight={this.state.room.getUnreadNotificationCount(NotificationCountType.Highlight) > 0}
numUnreadMessages={this.state.numUnreadMessages}
onScrollToBottomClick={this.jumpToLiveTimeline}
roomId={this.state.roomId}
diff --git a/src/mjolnir/BanList.ts b/src/mjolnir/BanList.ts
index 21cd5d4cf7..89eec89500 100644
--- a/src/mjolnir/BanList.ts
+++ b/src/mjolnir/BanList.ts
@@ -92,7 +92,7 @@ export class BanList {
if (!room) return;
for (const eventType of ALL_RULE_TYPES) {
- const events = room.currentState.getStateEvents(eventType, undefined);
+ const events = room.currentState.getStateEvents(eventType);
for (const ev of events) {
if (!ev.getStateKey()) continue;
From 0ae4e7b11de934dcd28645f55f73646722b507ee Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Fri, 18 Jun 2021 16:21:46 +0100
Subject: [PATCH 073/164] Fix typescript types
---
src/SlashCommands.tsx | 7 +++---
.../views/right_panel/EncryptionPanel.tsx | 23 ++++++++++---------
src/components/views/right_panel/UserInfo.tsx | 4 ++--
src/components/views/rooms/NewRoomIntro.tsx | 21 +++++++++--------
src/dispatcher/payloads/ViewUserPayload.ts | 3 ++-
src/stores/RoomViewStore.tsx | 2 +-
.../room-list/filters/SpaceFilterCondition.ts | 2 +-
src/utils/WidgetUtils.ts | 23 ++++++++++---------
8 files changed, 44 insertions(+), 41 deletions(-)
diff --git a/src/SlashCommands.tsx b/src/SlashCommands.tsx
index 9700c57d67..bd2133f3d9 100644
--- a/src/SlashCommands.tsx
+++ b/src/SlashCommands.tsx
@@ -17,8 +17,8 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
-
import * as React from 'react';
+import { User } from "matrix-js-sdk/src/models/user";
import * as ContentHelpers from 'matrix-js-sdk/src/content-helpers';
import {MatrixClientPeg} from './MatrixClientPeg';
@@ -1019,9 +1019,8 @@ export const Commands = [
const member = MatrixClientPeg.get().getRoom(roomId).getMember(userId);
dis.dispatch({
action: Action.ViewUser,
- // XXX: We should be using a real member object and not assuming what the
- // receiver wants.
- member: member || {userId},
+ // XXX: We should be using a real member object and not assuming what the receiver wants.
+ member: member || { userId } as User,
});
return success();
},
diff --git a/src/components/views/right_panel/EncryptionPanel.tsx b/src/components/views/right_panel/EncryptionPanel.tsx
index c237a4ade6..3a26427246 100644
--- a/src/components/views/right_panel/EncryptionPanel.tsx
+++ b/src/components/views/right_panel/EncryptionPanel.tsx
@@ -14,28 +14,29 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
-import React, {useCallback, useEffect, useState} from "react";
+import React, { useCallback, useEffect, useState } from "react";
+import { VerificationRequest } from "matrix-js-sdk/src/crypto/verification/request/VerificationRequest";
+import { RoomMember } from "matrix-js-sdk/src/models/room-member";
+import { User } from "matrix-js-sdk/src/models/user";
+import { PHASE_REQUESTED, PHASE_UNSENT } from "matrix-js-sdk/src/crypto/verification/request/VerificationRequest";
import EncryptionInfo from "./EncryptionInfo";
import VerificationPanel from "./VerificationPanel";
-import {MatrixClientPeg} from "../../../MatrixClientPeg";
-import {ensureDMExists} from "../../../createRoom";
-import {useEventEmitter} from "../../../hooks/useEventEmitter";
+import { MatrixClientPeg } from "../../../MatrixClientPeg";
+import { ensureDMExists } from "../../../createRoom";
+import { useEventEmitter } from "../../../hooks/useEventEmitter";
import Modal from "../../../Modal";
-import {PHASE_REQUESTED, PHASE_UNSENT} from "matrix-js-sdk/src/crypto/verification/request/VerificationRequest";
import * as sdk from "../../../index";
-import {_t} from "../../../languageHandler";
-import {VerificationRequest} from "matrix-js-sdk/src/crypto/verification/request/VerificationRequest";
-import {RoomMember} from "matrix-js-sdk/src/models/room-member";
+import { _t } from "../../../languageHandler";
import dis from "../../../dispatcher/dispatcher";
-import {Action} from "../../../dispatcher/actions";
-import {RightPanelPhases} from "../../../stores/RightPanelStorePhases";
+import { Action } from "../../../dispatcher/actions";
+import { RightPanelPhases } from "../../../stores/RightPanelStorePhases";
// cancellation codes which constitute a key mismatch
const MISMATCHES = ["m.key_mismatch", "m.user_error", "m.mismatched_sas"];
interface IProps {
- member: RoomMember;
+ member: RoomMember | User;
onClose: () => void;
verificationRequest: VerificationRequest;
verificationRequestPromise: Promise;
diff --git a/src/components/views/right_panel/UserInfo.tsx b/src/components/views/right_panel/UserInfo.tsx
index 0b37fb9dd6..8833cb6862 100644
--- a/src/components/views/right_panel/UserInfo.tsx
+++ b/src/components/views/right_panel/UserInfo.tsx
@@ -1594,7 +1594,7 @@ const UserInfo: React.FC = ({
content = (
@@ -1605,7 +1605,7 @@ const UserInfo: React.FC = ({
content = (
}
- member={member}
+ member={member as User | RoomMember}
onClose={onEncryptionPanelClose}
isRoomEncrypted={isRoomEncrypted}
/>
diff --git a/src/components/views/rooms/NewRoomIntro.tsx b/src/components/views/rooms/NewRoomIntro.tsx
index 3bf9a9db33..2b2958e3f3 100644
--- a/src/components/views/rooms/NewRoomIntro.tsx
+++ b/src/components/views/rooms/NewRoomIntro.tsx
@@ -14,25 +14,26 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
-import React, {useContext} from "react";
-import {EventType} from "matrix-js-sdk/src/@types/event";
+import React, { useContext } from "react";
+import { EventType } from "matrix-js-sdk/src/@types/event";
+import { MatrixClient } from "matrix-js-sdk/src/client";
+import { Room } from "matrix-js-sdk/src/models/room";
+import { User } from "matrix-js-sdk/src/models/user";
import MatrixClientContext from "../../../contexts/MatrixClientContext";
import RoomContext from "../../../contexts/RoomContext";
import DMRoomMap from "../../../utils/DMRoomMap";
-import {_t} from "../../../languageHandler";
+import { _t } from "../../../languageHandler";
import AccessibleButton from "../elements/AccessibleButton";
-import MiniAvatarUploader, {AVATAR_SIZE} from "../elements/MiniAvatarUploader";
+import MiniAvatarUploader, { AVATAR_SIZE } from "../elements/MiniAvatarUploader";
import RoomAvatar from "../avatars/RoomAvatar";
import defaultDispatcher from "../../../dispatcher/dispatcher";
-import {ViewUserPayload} from "../../../dispatcher/payloads/ViewUserPayload";
-import {Action} from "../../../dispatcher/actions";
+import { ViewUserPayload } from "../../../dispatcher/payloads/ViewUserPayload";
+import { Action } from "../../../dispatcher/actions";
import dis from "../../../dispatcher/dispatcher";
import SpaceStore from "../../../stores/SpaceStore";
-import {showSpaceInvite} from "../../../utils/space";
-
+import { showSpaceInvite } from "../../../utils/space";
import { privateShouldBeEncrypted } from "../../../createRoom";
-
import EventTileBubble from "../messages/EventTileBubble";
import { ROOM_SECURITY_TAB } from "../dialogs/RoomSettingsDialog";
@@ -61,7 +62,7 @@ const NewRoomIntro = () => {
defaultDispatcher.dispatch({
action: Action.ViewUser,
// XXX: We should be using a real member object and not assuming what the receiver wants.
- member: member || {userId: dmPartner},
+ member: member || { userId: dmPartner } as User,
});
}} />
diff --git a/src/dispatcher/payloads/ViewUserPayload.ts b/src/dispatcher/payloads/ViewUserPayload.ts
index c2838d0dbb..c4d73aea6a 100644
--- a/src/dispatcher/payloads/ViewUserPayload.ts
+++ b/src/dispatcher/payloads/ViewUserPayload.ts
@@ -15,6 +15,7 @@ limitations under the License.
*/
import { RoomMember } from "matrix-js-sdk/src/models/room-member";
+import { User } from "matrix-js-sdk/src/models/user";
import { ActionPayload } from "../payloads";
import { Action } from "../actions";
@@ -25,5 +26,5 @@ export interface ViewUserPayload extends ActionPayload {
* The member to view. May be null or falsy to indicate that no member
* should be shown (hide whichever relevant components).
*/
- member?: RoomMember;
+ member?: RoomMember | User;
}
diff --git a/src/stores/RoomViewStore.tsx b/src/stores/RoomViewStore.tsx
index cc3eafffcd..87978df471 100644
--- a/src/stores/RoomViewStore.tsx
+++ b/src/stores/RoomViewStore.tsx
@@ -276,7 +276,7 @@ class RoomViewStore extends Store {
const address = this.state.roomAlias || this.state.roomId;
const viaServers = this.state.viaServers || [];
try {
- await retry(() => cli.joinRoom(address, {
+ await retry(() => cli.joinRoom(address, {
viaServers,
...payload.opts,
}), NUM_JOIN_RETRY, (err) => {
diff --git a/src/stores/room-list/filters/SpaceFilterCondition.ts b/src/stores/room-list/filters/SpaceFilterCondition.ts
index 6a06bee0d8..79e258927d 100644
--- a/src/stores/room-list/filters/SpaceFilterCondition.ts
+++ b/src/stores/room-list/filters/SpaceFilterCondition.ts
@@ -29,7 +29,7 @@ import { setHasDiff } from "../../../utils/sets";
* + All DMs
*/
export class SpaceFilterCondition extends EventEmitter implements IFilterCondition, IDestroyable {
- private roomIds = new Set();
+ private roomIds = new Set();
private space: Room = null;
public get kind(): FilterKind {
diff --git a/src/utils/WidgetUtils.ts b/src/utils/WidgetUtils.ts
index 7ff0529363..926278a20a 100644
--- a/src/utils/WidgetUtils.ts
+++ b/src/utils/WidgetUtils.ts
@@ -16,19 +16,20 @@ limitations under the License.
*/
import * as url from "url";
+import { Capability, IWidget, IWidgetData, MatrixCapabilities } from "matrix-widget-api";
+import { Room } from "matrix-js-sdk/src/models/room";
+import { MatrixEvent } from "matrix-js-sdk/src/models/event";
-import {MatrixClientPeg} from '../MatrixClientPeg';
+import { MatrixClientPeg } from '../MatrixClientPeg';
import SdkConfig from "../SdkConfig";
import dis from '../dispatcher/dispatcher';
import WidgetEchoStore from '../stores/WidgetEchoStore';
import SettingsStore from "../settings/SettingsStore";
-import {IntegrationManagers} from "../integrations/IntegrationManagers";
-import {Room} from "matrix-js-sdk/src/models/room";
-import {WidgetType} from "../widgets/WidgetType";
-import {objectClone} from "./objects";
-import {_t} from "../languageHandler";
-import {Capability, IWidget, IWidgetData, MatrixCapabilities} from "matrix-widget-api";
-import {IApp} from "../stores/WidgetStore";
+import { IntegrationManagers } from "../integrations/IntegrationManagers";
+import { WidgetType } from "../widgets/WidgetType";
+import { objectClone } from "./objects";
+import { _t } from "../languageHandler";
+import { IApp } from "../stores/WidgetStore";
// How long we wait for the state event echo to come back from the server
// before waitFor[Room/User]Widget rejects its promise
@@ -377,9 +378,9 @@ export default class WidgetUtils {
return widgets.filter(w => w.content && w.content.type === "m.integration_manager");
}
- static getRoomWidgetsOfType(room: Room, type: WidgetType): IWidgetEvent[] {
- const widgets = WidgetUtils.getRoomWidgets(room);
- return (widgets || []).filter(w => {
+ static getRoomWidgetsOfType(room: Room, type: WidgetType): MatrixEvent[] {
+ const widgets = WidgetUtils.getRoomWidgets(room) || [];
+ return widgets.filter(w => {
const content = w.getContent();
return content.url && type.matches(content.type);
});
From 233e2aa425266791f99c99aeb4f77f827c986ff9 Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Fri, 18 Jun 2021 16:22:31 +0100
Subject: [PATCH 074/164] Fix bugs identified by the typescripting
---
src/components/views/rooms/AuxPanel.tsx | 12 ++++++------
src/components/views/rooms/RoomTile.tsx | 4 ++--
src/stores/CommunityPrototypeStore.ts | 13 ++++++++++---
src/stores/SpaceStore.tsx | 12 +++++++++---
4 files changed, 27 insertions(+), 14 deletions(-)
diff --git a/src/components/views/rooms/AuxPanel.tsx b/src/components/views/rooms/AuxPanel.tsx
index 74609cca13..f6cc0f4d45 100644
--- a/src/components/views/rooms/AuxPanel.tsx
+++ b/src/components/views/rooms/AuxPanel.tsx
@@ -15,10 +15,12 @@ limitations under the License.
*/
import React from 'react';
-import { MatrixClientPeg } from "../../../MatrixClientPeg";
-import { Room } from 'matrix-js-sdk/src/models/room'
-import AppsDrawer from './AppsDrawer';
import classNames from 'classnames';
+import { lexicographicCompare } from 'matrix-js-sdk/src/utils';
+import { Room } from 'matrix-js-sdk/src/models/room'
+
+import { MatrixClientPeg } from "../../../MatrixClientPeg";
+import AppsDrawer from './AppsDrawer';
import RateLimitedFunc from '../../../ratelimitedfunc';
import SettingsStore from "../../../settings/SettingsStore";
import AutoHideScrollbar from "../../structures/AutoHideScrollbar";
@@ -106,9 +108,7 @@ export default class AuxPanel extends React.Component {
if (this.props.room && SettingsStore.getValue("feature_state_counters")) {
const stateEvs = this.props.room.currentState.getStateEvents('re.jki.counter');
- stateEvs.sort((a, b) => {
- return a.getStateKey() < b.getStateKey();
- });
+ stateEvs.sort((a, b) => lexicographicCompare(a.getStateKey(), b.getStateKey()));
for (const ev of stateEvs) {
const title = ev.getContent().title;
diff --git a/src/components/views/rooms/RoomTile.tsx b/src/components/views/rooms/RoomTile.tsx
index aae182eca4..310ff29010 100644
--- a/src/components/views/rooms/RoomTile.tsx
+++ b/src/components/views/rooms/RoomTile.tsx
@@ -119,7 +119,7 @@ export default class RoomTile extends React.PureComponent {
};
private onLocalEchoUpdated = (ev: MatrixEvent, room: Room) => {
- if (!room?.roomId === this.props.room.roomId) return;
+ if (room?.roomId !== this.props.room.roomId) return;
this.setState({hasUnsentEvents: this.countUnsentEvents() > 0});
};
@@ -316,7 +316,7 @@ export default class RoomTile extends React.PureComponent {
0,
));
} else {
- console.warn(`Unexpected tag ${tagId} applied to ${this.props.room.room_id}`);
+ console.warn(`Unexpected tag ${tagId} applied to ${this.props.room.roomId}`);
}
if ((ev as React.KeyboardEvent).key === Key.ENTER) {
diff --git a/src/stores/CommunityPrototypeStore.ts b/src/stores/CommunityPrototypeStore.ts
index 023845c9ee..a6f4574a58 100644
--- a/src/stores/CommunityPrototypeStore.ts
+++ b/src/stores/CommunityPrototypeStore.ts
@@ -107,8 +107,9 @@ export class CommunityPrototypeStore extends AsyncStoreWithClient {
const pl = generalChat.currentState.getStateEvents("m.room.power_levels", "");
if (!pl) return this.isAdminOf(communityId);
+ const plContent = pl.getContent();
- const invitePl = isNullOrUndefined(pl.invite) ? 50 : Number(pl.invite);
+ const invitePl = isNullOrUndefined(plContent.invite) ? 50 : Number(plContent.invite);
return invitePl <= myMember.powerLevel;
}
@@ -159,10 +160,16 @@ export class CommunityPrototypeStore extends AsyncStoreWithClient {
if (SettingsStore.getValue("feature_communities_v2_prototypes")) {
const data = this.matrixClient.getAccountData("im.vector.group_info." + roomId);
if (data && data.getContent()) {
- return {displayName: data.getContent().name, avatarMxc: data.getContent().avatar_url};
+ return {
+ displayName: data.getContent().name,
+ avatarMxc: data.getContent().avatar_url,
+ };
}
}
- return {displayName: room.name, avatarMxc: room.avatar_url};
+ return {
+ displayName: room.name,
+ avatarMxc: room.getMxcAvatarUrl(),
+ };
}
protected async onReady(): Promise {
diff --git a/src/stores/SpaceStore.tsx b/src/stores/SpaceStore.tsx
index 40997d30a8..9463949aff 100644
--- a/src/stores/SpaceStore.tsx
+++ b/src/stores/SpaceStore.tsx
@@ -133,7 +133,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient {
// if the space being selected is an invite then always view that invite
// else if the last viewed room in this space is joined then view that
// else view space home or home depending on what is being clicked on
- if (space?.getMyMembership !== "invite" &&
+ if (space?.getMyMembership() !== "invite" &&
this.matrixClient?.getRoom(roomId)?.getMyMembership() === "join"
) {
defaultDispatcher.dispatch({
@@ -423,8 +423,14 @@ export class SpaceStoreClass extends AsyncStoreWithClient {
parent = this.rootSpaces.find(s => this.spaceFilteredRooms.get(s.roomId)?.has(roomId));
}
if (!parent) {
- const parents = Array.from(this.parentMap.get(roomId) || []);
- parent = parents.find(p => this.matrixClient.getRoom(p));
+ const parentIds = Array.from(this.parentMap.get(roomId) || []);
+ for (const parentId of parentIds) {
+ const room = this.matrixClient.getRoom(parentId);
+ if (room) {
+ parent = room;
+ break;
+ }
+ }
}
// don't trigger a context switch when we are switching a space to match the chosen room
From ab94b284b8325f5da9cf7e662a6f92288b887149 Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Fri, 18 Jun 2021 16:29:10 +0100
Subject: [PATCH 075/164] Updates around the use of private fields out of class
---
src/components/views/rooms/NewRoomIntro.tsx | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/src/components/views/rooms/NewRoomIntro.tsx b/src/components/views/rooms/NewRoomIntro.tsx
index 2b2958e3f3..cae86846d9 100644
--- a/src/components/views/rooms/NewRoomIntro.tsx
+++ b/src/components/views/rooms/NewRoomIntro.tsx
@@ -37,8 +37,8 @@ import { privateShouldBeEncrypted } from "../../../createRoom";
import EventTileBubble from "../messages/EventTileBubble";
import { ROOM_SECURITY_TAB } from "../dialogs/RoomSettingsDialog";
-function hasExpectedEncryptionSettings(room): boolean {
- const isEncrypted: boolean = room._client?.isRoomEncrypted(room.roomId);
+function hasExpectedEncryptionSettings(matrixClient: MatrixClient, room: Room): boolean {
+ const isEncrypted: boolean = matrixClient.isRoomEncrypted(room.roomId);
const isPublic: boolean = room.getJoinRule() === "public";
return isPublic || !privateShouldBeEncrypted() || isEncrypted;
}
@@ -195,7 +195,7 @@ const NewRoomIntro = () => {
return
- { !hasExpectedEncryptionSettings(room) && (
+ { !hasExpectedEncryptionSettings(cli, room) && (
Date: Fri, 18 Jun 2021 17:29:12 +0200
Subject: [PATCH 076/164] Add PR template
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Signed-off-by: Šimon Brandner
---
.github/PULL_REQUEST_TEMPLATE.md | 3 +++
1 file changed, 3 insertions(+)
create mode 100644 .github/PULL_REQUEST_TEMPLATE.md
diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
new file mode 100644
index 0000000000..c9d11f02c8
--- /dev/null
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -0,0 +1,3 @@
+
+
+
From 77a4d345bda4791bfec1b8fee643e2b29441a1c2 Mon Sep 17 00:00:00 2001
From: David Teller
Date: Fri, 18 Jun 2021 18:09:02 +0200
Subject: [PATCH 077/164] Submitting abuse reports to moderators (#6213)
This patch is part of MSC3215. It implements `feature_report_to_moderator`
to let end-users send report to room moderators instead of homeserver
administrators.
This only works if the room has been setup for moderation, something that
does not have a UX yet.
Signed-off-by: David Teller
---
.../views/dialogs/ReportEventDialog.js | 149 ------
.../views/dialogs/ReportEventDialog.tsx | 445 ++++++++++++++++++
src/i18n/strings/en_EN.json | 18 +-
src/settings/Settings.tsx | 7 +
4 files changed, 468 insertions(+), 151 deletions(-)
delete mode 100644 src/components/views/dialogs/ReportEventDialog.js
create mode 100644 src/components/views/dialogs/ReportEventDialog.tsx
diff --git a/src/components/views/dialogs/ReportEventDialog.js b/src/components/views/dialogs/ReportEventDialog.js
deleted file mode 100644
index 5454b97287..0000000000
--- a/src/components/views/dialogs/ReportEventDialog.js
+++ /dev/null
@@ -1,149 +0,0 @@
-/*
-Copyright 2019 Michael Telatynski <7t3chguy@gmail.com>
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-import React, {PureComponent} from 'react';
-import * as sdk from '../../../index';
-import { _t } from '../../../languageHandler';
-import PropTypes from "prop-types";
-import {MatrixEvent} from "matrix-js-sdk/src/models/event";
-import {MatrixClientPeg} from "../../../MatrixClientPeg";
-import SdkConfig from '../../../SdkConfig';
-import Markdown from '../../../Markdown';
-import {replaceableComponent} from "../../../utils/replaceableComponent";
-
-/*
- * A dialog for reporting an event.
- */
-@replaceableComponent("views.dialogs.ReportEventDialog")
-export default class ReportEventDialog extends PureComponent {
- static propTypes = {
- mxEvent: PropTypes.instanceOf(MatrixEvent).isRequired,
- onFinished: PropTypes.func.isRequired,
- };
-
- constructor(props) {
- super(props);
-
- this.state = {
- reason: "",
- busy: false,
- err: null,
- };
- }
-
- _onReasonChange = ({target: {value: reason}}) => {
- this.setState({ reason });
- };
-
- _onCancel = () => {
- this.props.onFinished(false);
- };
-
- _onSubmit = async () => {
- if (!this.state.reason || !this.state.reason.trim()) {
- this.setState({
- err: _t("Please fill why you're reporting."),
- });
- return;
- }
-
- this.setState({
- busy: true,
- err: null,
- });
-
- try {
- const ev = this.props.mxEvent;
- await MatrixClientPeg.get().reportEvent(ev.getRoomId(), ev.getId(), -100, this.state.reason.trim());
- this.props.onFinished(true);
- } catch (e) {
- this.setState({
- busy: false,
- err: e.message,
- });
- }
- };
-
- render() {
- const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
- const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
- const Loader = sdk.getComponent('elements.Spinner');
- const Field = sdk.getComponent('elements.Field');
-
- let error = null;
- if (this.state.err) {
- error =
- {this.state.err}
-
;
- }
-
- let progress = null;
- if (this.state.busy) {
- progress = (
-
-
-
- );
- }
-
- const adminMessageMD =
- SdkConfig.get().reportEvent &&
- SdkConfig.get().reportEvent.adminMessageMD;
- let adminMessage;
- if (adminMessageMD) {
- const html = new Markdown(adminMessageMD).toHTML({ externalLinks: true });
- adminMessage =
;
- }
-
- return (
-
-
-
- {
- _t("Reporting this message will send its unique 'event ID' to the administrator of " +
- "your homeserver. If messages in this room are encrypted, your homeserver " +
- "administrator will not be able to read the message text or view any files or images.")
- }
-
- {adminMessage}
-
- {progress}
- {error}
-
-
-
- );
- }
-}
diff --git a/src/components/views/dialogs/ReportEventDialog.tsx b/src/components/views/dialogs/ReportEventDialog.tsx
new file mode 100644
index 0000000000..8271239f7f
--- /dev/null
+++ b/src/components/views/dialogs/ReportEventDialog.tsx
@@ -0,0 +1,445 @@
+/*
+Copyright 2019 Michael Telatynski <7t3chguy@gmail.com>
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+import React from 'react';
+import * as sdk from '../../../index';
+import { _t } from '../../../languageHandler';
+import { ensureDMExists } from "../../../createRoom";
+import { IDialogProps } from "./IDialogProps";
+import {MatrixEvent} from "matrix-js-sdk/src/models/event";
+import {MatrixClientPeg} from "../../../MatrixClientPeg";
+import SdkConfig from '../../../SdkConfig';
+import Markdown from '../../../Markdown';
+import {replaceableComponent} from "../../../utils/replaceableComponent";
+import SettingsStore from "../../../settings/SettingsStore";
+import StyledRadioButton from "../elements/StyledRadioButton";
+
+interface IProps extends IDialogProps {
+ mxEvent: MatrixEvent;
+}
+
+interface IState {
+ // A free-form text describing the abuse.
+ reason: string;
+ busy: boolean;
+ err?: string;
+ // If we know it, the nature of the abuse, as specified by MSC3215.
+ nature?: EXTENDED_NATURE;
+}
+
+
+const MODERATED_BY_STATE_EVENT_TYPE = [
+ "org.matrix.msc3215.room.moderation.moderated_by",
+ /**
+ * Unprefixed state event. Not ready for prime time.
+ *
+ * "m.room.moderation.moderated_by"
+ */
+];
+
+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 NON_STANDARD_NATURE {
+ // 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"
+}
+
+type EXTENDED_NATURE = NATURE | NON_STANDARD_NATURE;
+
+type Moderation = {
+ // The id of the moderation room.
+ moderationRoomId: string;
+ // The id of the bot in charge of forwarding abuse reports to the moderation room.
+ moderationBotUserId: string;
+}
+/*
+ * A dialog for reporting an event.
+ *
+ * The actual content of the dialog will depend on two things:
+ *
+ * 1. Is `feature_report_to_moderators` enabled?
+ * 2. Does the room support moderation as per MSC3215, i.e. is there
+ * a well-formed state event `m.room.moderation.moderated_by`
+ * /`org.matrix.msc3215.room.moderation.moderated_by`?
+ */
+@replaceableComponent("views.dialogs.ReportEventDialog")
+export default class ReportEventDialog extends React.Component {
+ // If the room supports moderation, the moderation information.
+ private moderation?: Moderation;
+
+ constructor(props: IProps) {
+ super(props);
+
+ let moderatedByRoomId = null;
+ let moderatedByUserId = null;
+
+ if (SettingsStore.getValue("feature_report_to_moderators")) {
+ // The client supports reporting to moderators.
+ // Does the room support it, too?
+
+ // Extract state events to determine whether we should display
+ const client = MatrixClientPeg.get();
+ const room = client.getRoom(props.mxEvent.getRoomId());
+
+ for (const stateEventType of MODERATED_BY_STATE_EVENT_TYPE) {
+ const stateEvent = room.currentState.getStateEvents(stateEventType, stateEventType);
+ if (!stateEvent) {
+ continue;
+ }
+ if (Array.isArray(stateEvent)) {
+ // Internal error.
+ throw new TypeError(`getStateEvents(${stateEventType}, ${stateEventType}) ` +
+ "should return at most one state event");
+ }
+ const event = stateEvent.event;
+ if (!("content" in event) || typeof event["content"] != "object") {
+ // The room is improperly configured.
+ // Display this debug message for the sake of moderators.
+ console.debug("Moderation error", "state event", stateEventType,
+ "should have an object field `content`, got", event);
+ continue;
+ }
+ const content = event["content"];
+ if (!("room_id" in content) || typeof content["room_id"] != "string") {
+ // The room is improperly configured.
+ // Display this debug message for the sake of moderators.
+ console.debug("Moderation error", "state event", stateEventType,
+ "should have a string field `content.room_id`, got", event);
+ continue;
+ }
+ if (!("user_id" in content) || typeof content["user_id"] != "string") {
+ // The room is improperly configured.
+ // Display this debug message for the sake of moderators.
+ console.debug("Moderation error", "state event", stateEventType,
+ "should have a string field `content.user_id`, got", event);
+ continue;
+ }
+ moderatedByRoomId = content["room_id"];
+ moderatedByUserId = content["user_id"];
+ }
+
+ if (moderatedByRoomId && moderatedByUserId) {
+ // The room supports moderation.
+ this.moderation = {
+ moderationRoomId: moderatedByRoomId,
+ moderationBotUserId: moderatedByUserId,
+ };
+ }
+ }
+
+ this.state = {
+ // A free-form text describing the abuse.
+ reason: "",
+ busy: false,
+ err: null,
+ // If specified, the nature of the abuse, as specified by MSC3215.
+ nature: null,
+ };
+ }
+
+ // The user has written down a freeform description of the abuse.
+ private onReasonChange = ({target: {value: reason}}): void => {
+ this.setState({ reason });
+ };
+
+ // The user has clicked on a nature.
+ private onNatureChosen = (e: React.FormEvent): void => {
+ this.setState({ nature: e.currentTarget.value as EXTENDED_NATURE});
+ };
+
+ // The user has clicked "cancel".
+ private onCancel = (): void => {
+ this.props.onFinished(false);
+ };
+
+ // The user has clicked "submit".
+ private onSubmit = async () => {
+ let reason = this.state.reason || "";
+ reason = reason.trim();
+ if (this.moderation) {
+ // This room supports moderation.
+ // 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)
+ && !reason)
+ ) {
+ this.setState({
+ err: _t("Please fill why you're reporting."),
+ });
+ return;
+ }
+ } else {
+ // This room does not support moderation.
+ // We need a `reason`.
+ if (!reason) {
+ this.setState({
+ err: _t("Please fill why you're reporting."),
+ });
+ return;
+ }
+ }
+
+ this.setState({
+ busy: true,
+ err: null,
+ });
+
+ 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;
+
+ // Report to moderators through to the dedicated bot,
+ // as configured in the room's state events.
+ const dmRoomId = await ensureDMExists(client, this.moderation.moderationBotUserId);
+ await client.sendEvent(dmRoomId, ABUSE_EVENT_TYPE, {
+ event_id: ev.getId(),
+ room_id: ev.getRoomId(),
+ moderated_by_id: this.moderation.moderationRoomId,
+ nature,
+ reporter: client.getUserId(),
+ comment: this.state.reason.trim(),
+ });
+ } else {
+ // Report to homeserver admin through the dedicated Matrix API.
+ await client.reportEvent(ev.getRoomId(), ev.getId(), -100, this.state.reason.trim());
+ }
+ this.props.onFinished(true);
+ } catch (e) {
+ this.setState({
+ busy: false,
+ err: e.message,
+ });
+ }
+ };
+
+ render() {
+ const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
+ const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
+ const Loader = sdk.getComponent('elements.Spinner');
+ const Field = sdk.getComponent('elements.Field');
+
+ let error = null;
+ if (this.state.err) {
+ error =
+ {this.state.err}
+
;
+ }
+
+ let progress = null;
+ if (this.state.busy) {
+ progress = (
+
+
+
+ );
+ }
+
+ const adminMessageMD =
+ SdkConfig.get().reportEvent &&
+ SdkConfig.get().reportEvent.adminMessageMD;
+ let adminMessage;
+ if (adminMessageMD) {
+ const html = new Markdown(adminMessageMD).toHTML({ externalLinks: true });
+ adminMessage =
;
+ }
+
+ if (this.moderation) {
+ // Display report-to-moderator dialog.
+ // We let the user pick a nature.
+ const client = MatrixClientPeg.get();
+ const homeServerName = SdkConfig.get()["validated_server_config"].hsName;
+ let subtitle;
+ switch (this.state.nature) {
+ 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:
+ 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:
+ 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:
+ 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:
+ 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" +
+ "This will be reported to the administrators of %(homeserver)s. " +
+ "The administrators will NOT be able to read the encrypted content of this room.",
+ { homeserver: homeServerName });
+ } else {
+ subtitle = _t("This room is dedicated to illegal or toxic content " +
+ "or the moderators fail to moderate illegal or toxic content.\n" +
+ " This will be reported to the administrators of %(homeserver)s.",
+ { homeserver: homeServerName });
+ }
+ break;
+ case NATURE.OTHER:
+ subtitle = _t("Any other reason. Please describe the problem.\n" +
+ "This will be reported to the room moderators.");
+ break;
+ default:
+ subtitle = _t("Please pick a nature and describe what makes this message abusive.");
+ break;
+ }
+
+ return (
+
+
+
+ {_t('Disagree')}
+
+
+ {_t('Toxic Behaviour')}
+
+
+ {_t('Illegal Content')}
+
+
+ {_t('Spam or propaganda')}
+
+
+ {_t('Report the entire room')}
+
+
+ {_t('Other')}
+
+
+ {subtitle}
+
+
+ {progress}
+ {error}
+
+
+
+ );
+ }
+ // Report to homeserver admin.
+ // Currently, the API does not support natures.
+ return (
+
+
+
+ {
+ _t("Reporting this message will send its unique 'event ID' to the administrator of " +
+ "your homeserver. If messages in this room are encrypted, your homeserver " +
+ "administrator will not be able to read the message text or view any files " +
+ "or images.")
+ }
+
+ {adminMessage}
+
+ {progress}
+ {error}
+
+
+
+ );
+ }
+}
diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json
index 8c4262fe44..b88dc79da5 100644
--- a/src/i18n/strings/en_EN.json
+++ b/src/i18n/strings/en_EN.json
@@ -784,6 +784,7 @@
"%(senderName)s: %(reaction)s": "%(senderName)s: %(reaction)s",
"%(senderName)s: %(stickerName)s": "%(senderName)s: %(stickerName)s",
"Change notification settings": "Change notification settings",
+ "Report to moderators prototype. In rooms that support moderation, the `report` button will let you report abuse to room moderators": "Report to moderators prototype. In rooms that support moderation, the `report` button will let you report abuse to room moderators",
"Spaces prototype. Incompatible with Communities, Communities v2 and Custom Tags. Requires compatible homeserver for some features.": "Spaces prototype. Incompatible with Communities, Communities v2 and Custom Tags. Requires compatible homeserver for some features.",
"Spaces": "Spaces",
"Spaces are a new way to group rooms and people.": "Spaces are a new way to group rooms and people.",
@@ -2318,9 +2319,23 @@
"Just a heads up, if you don't add an email and forget your password, you could permanently lose access to your account .": "Just a heads up, if you don't add an email and forget your password, you could permanently lose access to your account .",
"Email (optional)": "Email (optional)",
"Please fill why you're reporting.": "Please fill why you're reporting.",
+ "What this user is writing is wrong.\nThis will be reported to the room moderators.": "What this user is writing is wrong.\nThis will be reported to the room moderators.",
+ "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.\nThis will be reported to the room moderators.": "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.\nThis will be reported to the room moderators.",
+ "This user is displaying illegal behaviour, for instance by doxing people or threatening violence.\nThis will be reported to the room moderators who may escalate this to legal authorities.": "This user is displaying illegal behaviour, for instance by doxing people or threatening violence.\nThis will be reported to the room moderators who may escalate this to legal authorities.",
+ "This user is spamming the room with ads, links to ads or to propaganda.\nThis will be reported to the room moderators.": "This user is spamming the room with ads, links to ads or to propaganda.\nThis will be reported to the room moderators.",
+ "This room is dedicated to illegal or toxic content or the moderators fail to moderate illegal or toxic content.\nThis will be reported to the administrators of %(homeserver)s. The administrators will NOT be able to read the encrypted content of this room.": "This room is dedicated to illegal or toxic content or the moderators fail to moderate illegal or toxic content.\nThis will be reported to the administrators of %(homeserver)s. The administrators will NOT be able to read the encrypted content of this room.",
+ "This room is dedicated to illegal or toxic content or the moderators fail to moderate illegal or toxic content.\n This will be reported to the administrators of %(homeserver)s.": "This room is dedicated to illegal or toxic content or the moderators fail to moderate illegal or toxic content.\n This will be reported to the administrators of %(homeserver)s.",
+ "Any other reason. Please describe the problem.\nThis will be reported to the room moderators.": "Any other reason. Please describe the problem.\nThis will be reported to the room moderators.",
+ "Please pick a nature and describe what makes this message abusive.": "Please pick a nature and describe what makes this message abusive.",
+ "Report Content": "Report Content",
+ "Disagree": "Disagree",
+ "Toxic Behaviour": "Toxic Behaviour",
+ "Illegal Content": "Illegal Content",
+ "Spam or propaganda": "Spam or propaganda",
+ "Report the entire room": "Report the entire room",
+ "Send report": "Send report",
"Report Content to Your Homeserver Administrator": "Report Content to Your Homeserver Administrator",
"Reporting this message will send its unique 'event ID' to the administrator of your homeserver. If messages in this room are encrypted, your homeserver administrator will not be able to read the message text or view any files or images.": "Reporting this message will send its unique 'event ID' to the administrator of your homeserver. If messages in this room are encrypted, your homeserver administrator will not be able to read the message text or view any files or images.",
- "Send report": "Send report",
"Room Settings - %(roomName)s": "Room Settings - %(roomName)s",
"Failed to upgrade room": "Failed to upgrade room",
"The room upgrade could not be completed": "The room upgrade could not be completed",
@@ -2487,7 +2502,6 @@
"Share Message": "Share Message",
"Source URL": "Source URL",
"Collapse Reply Thread": "Collapse Reply Thread",
- "Report Content": "Report Content",
"Clear status": "Clear status",
"Update status": "Update status",
"Set status": "Set status",
diff --git a/src/settings/Settings.tsx b/src/settings/Settings.tsx
index 155d039572..97f1beb979 100644
--- a/src/settings/Settings.tsx
+++ b/src/settings/Settings.tsx
@@ -131,6 +131,13 @@ export interface ISetting {
}
export const SETTINGS: {[setting: string]: ISetting} = {
+ "feature_report_to_moderators": {
+ isFeature: true,
+ displayName: _td("Report to moderators prototype. " +
+ "In rooms that support moderation, the `report` button will let you report abuse to room moderators"),
+ supportedLevels: LEVELS_FEATURE,
+ default: false,
+ },
"feature_spaces": {
isFeature: true,
displayName: _td("Spaces prototype. Incompatible with Communities, Communities v2 and Custom Tags. " +
From 8a3dc1bbdf6e5482e54b8bf159911a1b273a2e18 Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Fri, 18 Jun 2021 17:56:18 +0100
Subject: [PATCH 078/164] fix tests
---
test/components/structures/MessagePanel-test.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/test/components/structures/MessagePanel-test.js b/test/components/structures/MessagePanel-test.js
index 8f0242eb30..d32970a278 100644
--- a/test/components/structures/MessagePanel-test.js
+++ b/test/components/structures/MessagePanel-test.js
@@ -42,7 +42,7 @@ import DMRoomMap from "../../../src/utils/DMRoomMap";
configure({ adapter: new Adapter() });
let client;
-const room = new Matrix.Room();
+const room = new Matrix.Room("!roomId:server_name");
// wrap MessagePanel with a component which provides the MatrixClient in the context.
class WrappedMessagePanel extends React.Component {
From 958d4df957f065d321c08e04e73428c5020a6455 Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Fri, 18 Jun 2021 18:32:45 +0100
Subject: [PATCH 079/164] Naive attempt at improving our end-to-end tests in
Github Actions
---
.github/workflows/develop.yml | 7 +++++--
...d-tests.sh => prepare-end-to-end-tests.sh} | 11 +----------
scripts/ci/run-end-to-end-tests.sh | 19 +++++++++++++++++++
3 files changed, 25 insertions(+), 12 deletions(-)
rename scripts/ci/{end-to-end-tests.sh => prepare-end-to-end-tests.sh} (65%)
create mode 100755 scripts/ci/run-end-to-end-tests.sh
diff --git a/.github/workflows/develop.yml b/.github/workflows/develop.yml
index 6410bd28fa..3c3807e33b 100644
--- a/.github/workflows/develop.yml
+++ b/.github/workflows/develop.yml
@@ -11,10 +11,13 @@ jobs:
steps:
- name: Checkout code
uses: actions/checkout@v2
- - name: End-to-End tests
- run: ./scripts/ci/end-to-end-tests.sh
+ - name: Prepare End-to-End tests
+ run: ./scripts/ci/prepare-end-to-end-tests.sh
+ - name: Run End-to-End tests
+ run: ./scripts/ci/run-end-to-end-tests.sh
- name: Archive logs
uses: actions/upload-artifact@v2
+ if: ${{ always() }}
with:
path: |
test/end-to-end-tests/logs/**/*
diff --git a/scripts/ci/end-to-end-tests.sh b/scripts/ci/prepare-end-to-end-tests.sh
similarity index 65%
rename from scripts/ci/end-to-end-tests.sh
rename to scripts/ci/prepare-end-to-end-tests.sh
index edb8870d8e..147e1f6445 100755
--- a/scripts/ci/end-to-end-tests.sh
+++ b/scripts/ci/prepare-end-to-end-tests.sh
@@ -1,8 +1,4 @@
#!/bin/bash
-#
-# script which is run by the CI build (after `yarn test`).
-#
-# clones element-web develop and runs the tests against our version of react-sdk.
set -ev
@@ -19,7 +15,7 @@ cd element-web
element_web_dir=`pwd`
CI_PACKAGE=true yarn build
cd ..
-# run end to end tests
+# prepare end to end tests
pushd test/end-to-end-tests
ln -s $element_web_dir element/element-web
# PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true ./install.sh
@@ -28,9 +24,4 @@ echo "--- Install synapse & other dependencies"
./install.sh
# install static webserver to server symlinked local copy of element
./element/install-webserver.sh
-rm -r logs || true
-mkdir logs
-echo "+++ Running end-to-end tests"
-TESTS_STARTED=1
-./run.sh --no-sandbox --log-directory logs/
popd
diff --git a/scripts/ci/run-end-to-end-tests.sh b/scripts/ci/run-end-to-end-tests.sh
new file mode 100755
index 0000000000..3c99391fc7
--- /dev/null
+++ b/scripts/ci/run-end-to-end-tests.sh
@@ -0,0 +1,19 @@
+#!/bin/bash
+
+set -ev
+
+handle_error() {
+ EXIT_CODE=$?
+ exit $EXIT_CODE
+}
+
+trap 'handle_error' ERR
+
+# run end to end tests
+pushd test/end-to-end-tests
+rm -r logs || true
+mkdir logs
+echo "--- Running end-to-end tests"
+TESTS_STARTED=1
+./run.sh --no-sandbox --log-directory logs/
+popd
From 4ff25c5978484289d085466d0e60f3cbb23fabd9 Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Fri, 18 Jun 2021 19:16:39 +0100
Subject: [PATCH 080/164] Add jq to e2e tests Dockerfile
---
scripts/ci/Dockerfile | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/scripts/ci/Dockerfile b/scripts/ci/Dockerfile
index 3fdd0d7bf6..1d1425c865 100644
--- a/scripts/ci/Dockerfile
+++ b/scripts/ci/Dockerfile
@@ -3,6 +3,6 @@
# docker push vectorim/element-web-ci-e2etests-env:latest
FROM node:14-buster
RUN apt-get update
-RUN apt-get -y install build-essential python3-dev libffi-dev python-pip python-setuptools sqlite3 libssl-dev python-virtualenv libjpeg-dev libxslt1-dev uuid-runtime
+RUN apt-get -y install jq build-essential python3-dev libffi-dev python-pip python-setuptools sqlite3 libssl-dev python-virtualenv libjpeg-dev libxslt1-dev uuid-runtime
# dependencies for chrome (installed by puppeteer)
RUN apt-get -y install gconf-service libasound2 libatk1.0-0 libatk-bridge2.0-0 libc6 libcairo2 libcups2 libdbus-1-3 libexpat1 libfontconfig1 libgcc1 libgconf-2-4 libgdk-pixbuf2.0-0 libglib2.0-0 libgtk-3-0 libnspr4 libpango-1.0-0 libpangocairo-1.0-0 libstdc++6 libx11-6 libx11-xcb1 libxcb1 libxcomposite1 libxcursor1 libxdamage1 libxext6 libxfixes3 libxi6 libxrandr2 libxrender1 libxss1 libxtst6 ca-certificates fonts-liberation libappindicator1 libnss3 lsb-release xdg-utils wget
From be10e77704b1635dc514b7b98201b7ee11b24d10 Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Sat, 19 Jun 2021 15:37:06 +0100
Subject: [PATCH 081/164] Improve typing of Event Index Manager / Seshat
---
src/indexing/BaseEventIndexManager.ts | 78 +++++++++++++--------------
src/indexing/EventIndex.ts | 16 +++---
2 files changed, 47 insertions(+), 47 deletions(-)
diff --git a/src/indexing/BaseEventIndexManager.ts b/src/indexing/BaseEventIndexManager.ts
index debcb213ca..9478b2987b 100644
--- a/src/indexing/BaseEventIndexManager.ts
+++ b/src/indexing/BaseEventIndexManager.ts
@@ -17,7 +17,7 @@ limitations under the License.
// The following interfaces take their names and member names from seshat and the spec
/* eslint-disable camelcase */
-export interface MatrixEvent {
+export interface IMatrixEvent {
type: string;
sender: string;
content: {};
@@ -27,37 +27,37 @@ export interface MatrixEvent {
roomId: string;
}
-export interface MatrixProfile {
+export interface IMatrixProfile {
avatar_url: string;
displayname: string;
}
-export interface CrawlerCheckpoint {
+export interface ICrawlerCheckpoint {
roomId: string;
token: string;
fullCrawl?: boolean;
direction: string;
}
-export interface ResultContext {
- events_before: [MatrixEvent];
- events_after: [MatrixEvent];
- profile_info: Map;
+export interface IResultContext {
+ events_before: [IMatrixEvent];
+ events_after: [IMatrixEvent];
+ profile_info: Map;
}
-export interface ResultsElement {
+export interface IResultsElement {
rank: number;
- result: MatrixEvent;
- context: ResultContext;
+ result: IMatrixEvent;
+ context: IResultContext;
}
-export interface SearchResult {
+export interface ISearchResult {
count: number;
- results: [ResultsElement];
+ results: [IResultsElement];
highlights: [string];
}
-export interface SearchArgs {
+export interface ISearchArgs {
search_term: string;
before_limit: number;
after_limit: number;
@@ -65,19 +65,19 @@ export interface SearchArgs {
room_id?: string;
}
-export interface EventAndProfile {
- event: MatrixEvent;
- profile: MatrixProfile;
+export interface IEventAndProfile {
+ event: IMatrixEvent;
+ profile: IMatrixProfile;
}
-export interface LoadArgs {
+export interface ILoadArgs {
roomId: string;
limit: number;
fromEvent?: string;
direction?: string;
}
-export interface IndexStats {
+export interface IIndexStats {
size: number;
eventCount: number;
roomCount: number;
@@ -119,13 +119,13 @@ export default abstract class BaseEventIndexManager {
* Queue up an event to be added to the index.
*
* @param {MatrixEvent} ev The event that should be added to the index.
- * @param {MatrixProfile} profile The profile of the event sender at the
+ * @param {IMatrixProfile} profile The profile of the event sender at the
* time of the event receival.
*
* @return {Promise} A promise that will resolve when the was queued up for
* addition.
*/
- async addEventToIndex(ev: MatrixEvent, profile: MatrixProfile): Promise {
+ async addEventToIndex(ev: IMatrixEvent, profile: IMatrixProfile): Promise {
throw new Error("Unimplemented");
}
@@ -160,10 +160,10 @@ export default abstract class BaseEventIndexManager {
/**
* Get statistical information of the index.
*
- * @return {Promise} A promise that will resolve to the index
+ * @return {Promise} A promise that will resolve to the index
* statistics.
*/
- async getStats(): Promise {
+ async getStats(): Promise {
throw new Error("Unimplemented");
}
@@ -203,13 +203,13 @@ export default abstract class BaseEventIndexManager {
/**
* Search the event index using the given term for matching events.
*
- * @param {SearchArgs} searchArgs The search configuration for the search,
+ * @param {ISearchArgs} searchArgs The search configuration for the search,
* sets the search term and determines the search result contents.
*
- * @return {Promise<[SearchResult]>} A promise that will resolve to an array
+ * @return {Promise<[ISearchResult]>} A promise that will resolve to an array
* of search results once the search is done.
*/
- async searchEventIndex(searchArgs: SearchArgs): Promise {
+ async searchEventIndex(searchArgs: ISearchArgs): Promise {
throw new Error("Unimplemented");
}
@@ -218,12 +218,12 @@ export default abstract class BaseEventIndexManager {
*
* This is used to add a batch of events to the index.
*
- * @param {[EventAndProfile]} events The list of events and profiles that
+ * @param {[IEventAndProfile]} events The list of events and profiles that
* should be added to the event index.
- * @param {[CrawlerCheckpoint]} checkpoint A new crawler checkpoint that
+ * @param {[ICrawlerCheckpoint]} checkpoint A new crawler checkpoint that
* should be stored in the index which should be used to continue crawling
* the room.
- * @param {[CrawlerCheckpoint]} oldCheckpoint The checkpoint that was used
+ * @param {[ICrawlerCheckpoint]} oldCheckpoint The checkpoint that was used
* to fetch the current batch of events. This checkpoint will be removed
* from the index.
*
@@ -231,9 +231,9 @@ export default abstract class BaseEventIndexManager {
* were already added to the index, false otherwise.
*/
async addHistoricEvents(
- events: [EventAndProfile],
- checkpoint: CrawlerCheckpoint | null,
- oldCheckpoint: CrawlerCheckpoint | null,
+ events: IEventAndProfile[],
+ checkpoint: ICrawlerCheckpoint | null,
+ oldCheckpoint: ICrawlerCheckpoint | null,
): Promise {
throw new Error("Unimplemented");
}
@@ -241,36 +241,36 @@ export default abstract class BaseEventIndexManager {
/**
* Add a new crawler checkpoint to the index.
*
- * @param {CrawlerCheckpoint} checkpoint The checkpoint that should be added
+ * @param {ICrawlerCheckpoint} checkpoint The checkpoint that should be added
* to the index.
*
* @return {Promise} A promise that will resolve once the checkpoint has
* been stored.
*/
- async addCrawlerCheckpoint(checkpoint: CrawlerCheckpoint): Promise {
+ async addCrawlerCheckpoint(checkpoint: ICrawlerCheckpoint): Promise {
throw new Error("Unimplemented");
}
/**
* Add a new crawler checkpoint to the index.
*
- * @param {CrawlerCheckpoint} checkpoint The checkpoint that should be
+ * @param {ICrawlerCheckpoint} checkpoint The checkpoint that should be
* removed from the index.
*
* @return {Promise} A promise that will resolve once the checkpoint has
* been removed.
*/
- async removeCrawlerCheckpoint(checkpoint: CrawlerCheckpoint): Promise {
+ async removeCrawlerCheckpoint(checkpoint: ICrawlerCheckpoint): Promise {
throw new Error("Unimplemented");
}
/**
* Load the stored checkpoints from the index.
*
- * @return {Promise<[CrawlerCheckpoint]>} A promise that will resolve to an
+ * @return {Promise<[ICrawlerCheckpoint]>} A promise that will resolve to an
* array of crawler checkpoints once they have been loaded from the index.
*/
- async loadCheckpoints(): Promise<[CrawlerCheckpoint]> {
+ async loadCheckpoints(): Promise {
throw new Error("Unimplemented");
}
@@ -286,11 +286,11 @@ export default abstract class BaseEventIndexManager {
* @param {string} args.direction The direction to which we should continue
* loading events from. This is used only if fromEvent is used as well.
*
- * @return {Promise<[EventAndProfile]>} A promise that will resolve to an
+ * @return {Promise<[IEventAndProfile]>} A promise that will resolve to an
* array of Matrix events that contain mxc URLs accompanied with the
* historic profile of the sender.
*/
- async loadFileEvents(args: LoadArgs): Promise<[EventAndProfile]> {
+ async loadFileEvents(args: ILoadArgs): Promise {
throw new Error("Unimplemented");
}
diff --git a/src/indexing/EventIndex.ts b/src/indexing/EventIndex.ts
index c36f96f368..978a2ac813 100644
--- a/src/indexing/EventIndex.ts
+++ b/src/indexing/EventIndex.ts
@@ -28,7 +28,7 @@ import { MatrixClientPeg } from "../MatrixClientPeg";
import { sleep } from "../utils/promise";
import SettingsStore from "../settings/SettingsStore";
import { SettingLevel } from "../settings/SettingLevel";
-import {CrawlerCheckpoint, LoadArgs, SearchArgs} from "./BaseEventIndexManager";
+import { ICrawlerCheckpoint, ILoadArgs, ISearchArgs } from "./BaseEventIndexManager";
// The time in ms that the crawler will wait loop iterations if there
// have not been any checkpoints to consume in the last iteration.
@@ -45,9 +45,9 @@ interface ICrawler {
* Event indexing class that wraps the platform specific event indexing.
*/
export default class EventIndex extends EventEmitter {
- private crawlerCheckpoints: CrawlerCheckpoint[] = [];
+ private crawlerCheckpoints: ICrawlerCheckpoint[] = [];
private crawler: ICrawler = null;
- private currentCheckpoint: CrawlerCheckpoint = null;
+ private currentCheckpoint: ICrawlerCheckpoint = null;
public async init() {
const indexManager = PlatformPeg.get().getEventIndexingManager();
@@ -111,14 +111,14 @@ export default class EventIndex extends EventEmitter {
const timeline = room.getLiveTimeline();
const token = timeline.getPaginationToken("b");
- const backCheckpoint: CrawlerCheckpoint = {
+ const backCheckpoint: ICrawlerCheckpoint = {
roomId: room.roomId,
token: token,
direction: "b",
fullCrawl: true,
};
- const forwardCheckpoint: CrawlerCheckpoint = {
+ const forwardCheckpoint: ICrawlerCheckpoint = {
roomId: room.roomId,
token: token,
direction: "f",
@@ -668,13 +668,13 @@ export default class EventIndex extends EventEmitter {
/**
* Search the event index using the given term for matching events.
*
- * @param {SearchArgs} searchArgs The search configuration for the search,
+ * @param {ISearchArgs} searchArgs The search configuration for the search,
* sets the search term and determines the search result contents.
*
* @return {Promise<[SearchResult]>} A promise that will resolve to an array
* of search results once the search is done.
*/
- public async search(searchArgs: SearchArgs) {
+ public async search(searchArgs: ISearchArgs) {
const indexManager = PlatformPeg.get().getEventIndexingManager();
return indexManager.searchEventIndex(searchArgs);
}
@@ -709,7 +709,7 @@ export default class EventIndex extends EventEmitter {
const client = MatrixClientPeg.get();
const indexManager = PlatformPeg.get().getEventIndexingManager();
- const loadArgs: LoadArgs = {
+ const loadArgs: ILoadArgs = {
roomId: room.roomId,
limit: limit,
};
From a2a515841138a3c545b6a44477df3e9ef3992b93 Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Sat, 19 Jun 2021 15:37:48 +0100
Subject: [PATCH 082/164] Fix View Source accessing renamed private field on
MatrixEvent
---
src/components/structures/ViewSource.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/components/structures/ViewSource.js b/src/components/structures/ViewSource.js
index 6fe99dd464..b69a92dd61 100644
--- a/src/components/structures/ViewSource.js
+++ b/src/components/structures/ViewSource.js
@@ -55,7 +55,7 @@ export default class ViewSource extends React.Component {
viewSourceContent() {
const mxEvent = this.props.mxEvent.replacingEvent() || this.props.mxEvent; // show the replacing event, not the original, if it is an edit
const isEncrypted = mxEvent.isEncrypted();
- const decryptedEventSource = mxEvent._clearEvent; // FIXME: _clearEvent is private
+ const decryptedEventSource = mxEvent.clearEvent; // FIXME: clearEvent is private
const originalEventSource = mxEvent.event;
if (isEncrypted) {
From 2c5ddea1d9687bfe7ce406741e824074be56ddfb Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Sat, 19 Jun 2021 15:57:07 +0100
Subject: [PATCH 083/164] Fix ConfirmUserActionDialog returning an input field
rather than text
---
src/components/views/dialogs/ConfirmUserActionDialog.tsx | 8 ++------
1 file changed, 2 insertions(+), 6 deletions(-)
diff --git a/src/components/views/dialogs/ConfirmUserActionDialog.tsx b/src/components/views/dialogs/ConfirmUserActionDialog.tsx
index 05f8c63ace..5cdb4c664b 100644
--- a/src/components/views/dialogs/ConfirmUserActionDialog.tsx
+++ b/src/components/views/dialogs/ConfirmUserActionDialog.tsx
@@ -38,7 +38,7 @@ interface IProps {
// be the string entered.
askReason?: boolean;
danger?: boolean;
- onFinished: (success: boolean, reason?: HTMLInputElement) => void;
+ onFinished: (success: boolean, reason?: string) => void;
}
/*
@@ -59,11 +59,7 @@ export default class ConfirmUserActionDialog extends React.Component {
};
public onOk = (): void => {
- let reason;
- if (this.reasonField) {
- reason = this.reasonField.current;
- }
- this.props.onFinished(true, reason);
+ this.props.onFinished(true, this.reasonField.current?.value);
};
public onCancel = (): void => {
From 8e2a7cc3f6badace1d1cb0dacb90d449689ac8cd Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Sat, 19 Jun 2021 19:41:45 +0100
Subject: [PATCH 084/164] Convert crypto index to TS
---
src/rageshake/submit-rageshake.ts | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/rageshake/submit-rageshake.ts b/src/rageshake/submit-rageshake.ts
index 08d8ccfd13..64d7405f17 100644
--- a/src/rageshake/submit-rageshake.ts
+++ b/src/rageshake/submit-rageshake.ts
@@ -86,8 +86,8 @@ async function collectBugReport(opts: IOpts = {}, gzipLogs = true) {
body.append('cross_signing_key', client.getCrossSigningId());
// add cross-signing status information
- const crossSigning = client.crypto._crossSigningInfo;
- const secretStorage = client.crypto._secretStorage;
+ const crossSigning = client.crypto.crossSigningInfo;
+ const secretStorage = client.crypto.secretStorage;
body.append("cross_signing_ready", String(await client.isCrossSigningReady()));
body.append("cross_signing_supported_by_hs",
From 9344adb2d2e9419fa5238e6a03cf863380c621fc Mon Sep 17 00:00:00 2001
From: Travis Ralston
Date: Sat, 19 Jun 2021 13:38:19 -0600
Subject: [PATCH 085/164] Revert "Partially restore immutable event objects at
the rendering layer"
---
src/components/views/messages/TextualBody.js | 3 +-
src/components/views/rooms/EventTile.tsx | 124 ++++++++-----------
2 files changed, 50 insertions(+), 77 deletions(-)
diff --git a/src/components/views/messages/TextualBody.js b/src/components/views/messages/TextualBody.js
index c67001cc87..ebc4ce7ce8 100644
--- a/src/components/views/messages/TextualBody.js
+++ b/src/components/views/messages/TextualBody.js
@@ -263,8 +263,7 @@ export default class TextualBody extends React.Component {
//console.info("shouldComponentUpdate: ShowUrlPreview for %s is %s", this.props.mxEvent.getId(), this.props.showUrlPreview);
// exploit that events are immutable :)
- return (nextProps.mxEvent !== this.props.mxEvent ||
- nextProps.mxEvent.getId() !== this.props.mxEvent.getId() ||
+ return (nextProps.mxEvent.getId() !== this.props.mxEvent.getId() ||
nextProps.highlights !== this.props.highlights ||
nextProps.replacingEventId !== this.props.replacingEventId ||
nextProps.highlightLink !== this.props.highlightLink ||
diff --git a/src/components/views/rooms/EventTile.tsx b/src/components/views/rooms/EventTile.tsx
index 07c6427992..0099bf73fb 100644
--- a/src/components/views/rooms/EventTile.tsx
+++ b/src/components/views/rooms/EventTile.tsx
@@ -300,9 +300,6 @@ interface IState {
// The Relations model from the JS SDK for reactions to `mxEvent`
reactions: Relations;
- // Our snapshotted/local copy of the props.mxEvent, for local echo reasons
- mxEvent: MatrixEvent;
-
hover: boolean;
}
@@ -337,8 +334,6 @@ export default class EventTile extends React.Component {
// The Relations model from the JS SDK for reactions to `mxEvent`
reactions: this.getReactions(),
- mxEvent: this.mxEvent.toSnapshot(), // snapshot up front to verify it all works
-
hover: false,
};
@@ -355,10 +350,6 @@ export default class EventTile extends React.Component {
this.ref = React.createRef();
}
- private get mxEvent(): MatrixEvent {
- return this.state?.mxEvent ?? this.props.mxEvent;
- }
-
/**
* When true, the tile qualifies for some sort of special read receipt. This could be a 'sending'
* or 'sent' receipt, for example.
@@ -367,16 +358,16 @@ export default class EventTile extends React.Component {
private get isEligibleForSpecialReceipt() {
// First, if there are other read receipts then just short-circuit this.
if (this.props.readReceipts && this.props.readReceipts.length > 0) return false;
- if (!this.mxEvent) return false;
+ if (!this.props.mxEvent) return false;
// Sanity check (should never happen, but we shouldn't explode if it does)
- const room = this.context.getRoom(this.mxEvent.getRoomId());
+ const room = this.context.getRoom(this.props.mxEvent.getRoomId());
if (!room) return false;
// Quickly check to see if the event was sent by us. If it wasn't, it won't qualify for
// special read receipts.
const myUserId = MatrixClientPeg.get().getUserId();
- if (this.mxEvent.getSender() !== myUserId) return false;
+ if (this.props.mxEvent.getSender() !== myUserId) return false;
// Finally, determine if the type is relevant to the user. This notably excludes state
// events and pretty much anything that can't be sent by the composer as a message. For
@@ -387,7 +378,7 @@ export default class EventTile extends React.Component {
EventType.RoomMessage,
EventType.RoomMessageEncrypted,
];
- if (!simpleSendableEvents.includes(this.mxEvent.getType() as EventType)) return false;
+ if (!simpleSendableEvents.includes(this.props.mxEvent.getType() as EventType)) return false;
// Default case
return true;
@@ -429,7 +420,7 @@ export default class EventTile extends React.Component {
// TODO: [REACT-WARNING] Move into constructor
// eslint-disable-next-line camelcase
UNSAFE_componentWillMount() {
- this.verifyEvent(this.mxEvent);
+ this.verifyEvent(this.props.mxEvent);
}
componentDidMount() {
@@ -459,21 +450,11 @@ export default class EventTile extends React.Component {
}
shouldComponentUpdate(nextProps, nextState) {
- // If the echo changed meaningfully, update.
- if (!this.state.mxEvent?.isEquivalentTo(nextProps.mxEvent)) {
- return true;
- }
-
if (objectHasDiff(this.state, nextState)) {
return true;
}
- if (!this.propsEqual(this.props, nextProps)) {
- return true;
- }
-
- // Always assume there's no significant change.
- return false;
+ return !this.propsEqual(this.props, nextProps);
}
componentWillUnmount() {
@@ -494,18 +475,11 @@ export default class EventTile extends React.Component {
this.context.on("Room.receipt", this.onRoomReceipt);
this.isListeningForReceipts = true;
}
-
- // Update the state again if the snapshot needs updating. Note that this will fire
- // a second state update to re-render child components, which ultimately calls didUpdate
- // again, so we break that loop with a reference check first (faster than comparing events).
- if (this.state.mxEvent === prevState.mxEvent && !this.state?.mxEvent.isEquivalentTo(this.props.mxEvent)) {
- this.setState({mxEvent: this.props.mxEvent.toSnapshot()});
- }
}
private onRoomReceipt = (ev, room) => {
// ignore events for other rooms
- const tileRoom = MatrixClientPeg.get().getRoom(this.mxEvent.getRoomId());
+ const tileRoom = MatrixClientPeg.get().getRoom(this.props.mxEvent.getRoomId());
if (room !== tileRoom) return;
if (!this.shouldShowSentReceipt && !this.shouldShowSendingReceipt && !this.isListeningForReceipts) {
@@ -529,19 +503,19 @@ export default class EventTile extends React.Component {
// we need to re-verify the sending device.
// (we call onHeightChanged in verifyEvent to handle the case where decryption
// has caused a change in size of the event tile)
- this.verifyEvent(this.mxEvent);
+ this.verifyEvent(this.props.mxEvent);
this.forceUpdate();
};
private onDeviceVerificationChanged = (userId, device) => {
- if (userId === this.mxEvent.getSender()) {
- this.verifyEvent(this.mxEvent);
+ if (userId === this.props.mxEvent.getSender()) {
+ this.verifyEvent(this.props.mxEvent);
}
};
private onUserVerificationChanged = (userId, _trustStatus) => {
- if (userId === this.mxEvent.getSender()) {
- this.verifyEvent(this.mxEvent);
+ if (userId === this.props.mxEvent.getSender()) {
+ this.verifyEvent(this.props.mxEvent);
}
};
@@ -648,11 +622,11 @@ export default class EventTile extends React.Component {
}
shouldHighlight() {
- const actions = this.context.getPushActionsForEvent(this.mxEvent.replacingEvent() || this.mxEvent);
+ const actions = this.context.getPushActionsForEvent(this.props.mxEvent.replacingEvent() || this.props.mxEvent);
if (!actions || !actions.tweaks) { return false; }
// don't show self-highlights from another of our clients
- if (this.mxEvent.getSender() === this.context.credentials.userId) {
+ if (this.props.mxEvent.getSender() === this.context.credentials.userId) {
return false;
}
@@ -667,7 +641,7 @@ export default class EventTile extends React.Component {
getReadAvatars() {
if (this.shouldShowSentReceipt || this.shouldShowSendingReceipt) {
- return ;
+ return ;
}
// return early if there are no read receipts
@@ -754,7 +728,7 @@ export default class EventTile extends React.Component {
}
onSenderProfileClick = event => {
- const mxEvent = this.mxEvent;
+ const mxEvent = this.props.mxEvent;
dis.dispatch({
action: Action.ComposerInsert,
userId: mxEvent.getSender(),
@@ -771,7 +745,7 @@ export default class EventTile extends React.Component {
// Cancel any outgoing key request for this event and resend it. If a response
// is received for the request with the required keys, the event could be
// decrypted successfully.
- this.context.cancelAndResendEventRoomKeyRequest(this.mxEvent);
+ this.context.cancelAndResendEventRoomKeyRequest(this.props.mxEvent);
};
onPermalinkClicked = e => {
@@ -780,14 +754,14 @@ export default class EventTile extends React.Component {
e.preventDefault();
dis.dispatch({
action: 'view_room',
- event_id: this.mxEvent.getId(),
+ event_id: this.props.mxEvent.getId(),
highlighted: true,
- room_id: this.mxEvent.getRoomId(),
+ room_id: this.props.mxEvent.getRoomId(),
});
};
private renderE2EPadlock() {
- const ev = this.mxEvent;
+ const ev = this.props.mxEvent;
// event could not be decrypted
if (ev.getContent().msgtype === 'm.bad.encrypted') {
@@ -846,7 +820,7 @@ export default class EventTile extends React.Component {
) {
return null;
}
- const eventId = this.mxEvent.getId();
+ const eventId = this.props.mxEvent.getId();
return this.props.getRelationsForEvent(eventId, "m.annotation", "m.reaction");
};
@@ -865,13 +839,13 @@ export default class EventTile extends React.Component {
const SenderProfile = sdk.getComponent('messages.SenderProfile');
const MemberAvatar = sdk.getComponent('avatars.MemberAvatar');
- //console.info("EventTile showUrlPreview for %s is %s", this.mxEvent.getId(), this.props.showUrlPreview);
+ //console.info("EventTile showUrlPreview for %s is %s", this.props.mxEvent.getId(), this.props.showUrlPreview);
- const content = this.mxEvent.getContent();
+ const content = this.props.mxEvent.getContent();
const msgtype = content.msgtype;
- const eventType = this.mxEvent.getType();
+ const eventType = this.props.mxEvent.getType();
- let tileHandler = getHandlerTile(this.mxEvent);
+ let tileHandler = getHandlerTile(this.props.mxEvent);
// Info messages are basically information about commands processed on a room
const isBubbleMessage = eventType.startsWith("m.key.verification") ||
@@ -888,7 +862,7 @@ export default class EventTile extends React.Component {
// source tile when there's no regular tile for an event and also for
// replace relations (which otherwise would display as a confusing
// duplicate of the thing they are replacing).
- if (SettingsStore.getValue("showHiddenEventsInTimeline") && !haveTileForEvent(this.mxEvent)) {
+ if (SettingsStore.getValue("showHiddenEventsInTimeline") && !haveTileForEvent(this.props.mxEvent)) {
tileHandler = "messages.ViewSourceEvent";
// Reuse info message avatar and sender profile styling
isInfoMessage = true;
@@ -907,8 +881,8 @@ export default class EventTile extends React.Component {
const EventTileType = sdk.getComponent(tileHandler);
const isSending = (['sending', 'queued', 'encrypting'].indexOf(this.props.eventSendStatus) !== -1);
- const isRedacted = isMessageEvent(this.mxEvent) && this.props.isRedacted;
- const isEncryptionFailure = this.mxEvent.isDecryptionFailure();
+ const isRedacted = isMessageEvent(this.props.mxEvent) && this.props.isRedacted;
+ const isEncryptionFailure = this.props.mxEvent.isDecryptionFailure();
const isEditing = !!this.props.editState;
const classes = classNames({
@@ -938,14 +912,14 @@ export default class EventTile extends React.Component {
let permalink = "#";
if (this.props.permalinkCreator) {
- permalink = this.props.permalinkCreator.forEvent(this.mxEvent.getId());
+ permalink = this.props.permalinkCreator.forEvent(this.props.mxEvent.getId());
}
// we can't use local echoes as scroll tokens, because their event IDs change.
// Local echos have a send "status".
- const scrollToken = this.mxEvent.status
+ const scrollToken = this.props.mxEvent.status
? undefined
- : this.mxEvent.getId();
+ : this.props.mxEvent.getId();
let avatar;
let sender;
@@ -975,15 +949,15 @@ export default class EventTile extends React.Component {
needsSenderProfile = true;
}
- if (this.mxEvent.sender && avatarSize) {
+ if (this.props.mxEvent.sender && avatarSize) {
let member;
// set member to receiver (target) if it is a 3PID invite
// so that the correct avatar is shown as the text is
// `$target accepted the invitation for $email`
- if (this.mxEvent.getContent().third_party_invite) {
- member = this.mxEvent.target;
+ if (this.props.mxEvent.getContent().third_party_invite) {
+ member = this.props.mxEvent.target;
} else {
- member = this.mxEvent.sender;
+ member = this.props.mxEvent.sender;
}
avatar = (
@@ -998,17 +972,17 @@ export default class EventTile extends React.Component
{
if (needsSenderProfile) {
if (!this.props.tileShape || this.props.tileShape === 'reply' || this.props.tileShape === 'reply_preview') {
sender = ;
} else {
- sender = ;
+ sender = ;
}
}
const MessageActionBar = sdk.getComponent('messages.MessageActionBar');
const actionBar = !isEditing ? {
onFocusChange={this.onActionBarFocusChange}
/> : undefined;
- const showTimestamp = this.mxEvent.getTs() &&
+ const showTimestamp = this.props.mxEvent.getTs() &&
(this.props.alwaysShowTimestamps || this.props.last || this.state.hover || this.state.actionBarFocused);
const timestamp = showTimestamp ?
- : null;
+ : null;
const keyRequestHelpText =
@@ -1059,7 +1033,7 @@ export default class EventTile extends React.Component
{
if (!isRedacted) {
const ReactionsRow = sdk.getComponent('messages.ReactionsRow');
reactionsRow = ;
}
@@ -1067,7 +1041,7 @@ export default class EventTile extends React.Component {
const linkedTimestamp =
{ timestamp }
;
@@ -1086,7 +1060,7 @@ export default class EventTile extends React.Component {
switch (this.props.tileShape) {
case 'notif': {
- const room = this.context.getRoom(this.mxEvent.getRoomId());
+ const room = this.context.getRoom(this.props.mxEvent.getRoomId());
return React.createElement(this.props.as || "li", {
"className": classes,
"aria-live": ariaLive,
@@ -1108,7 +1082,7 @@ export default class EventTile extends React.Component {
,
{
}, [
{
let thread;
if (this.props.tileShape === 'reply_preview') {
thread = ReplyThread.makeThread(
- this.mxEvent,
+ this.props.mxEvent,
this.props.onHeightChanged,
this.props.permalinkCreator,
this.replyThread,
@@ -1176,7 +1150,7 @@ export default class EventTile extends React.Component {
{ groupPadlock }
{ thread }
{
}
default: {
const thread = ReplyThread.makeThread(
- this.mxEvent,
+ this.props.mxEvent,
this.props.onHeightChanged,
this.props.permalinkCreator,
this.replyThread,
@@ -1216,7 +1190,7 @@ export default class EventTile extends React.Component {
{ groupPadlock }
{ thread }
Date: Sun, 20 Jun 2021 10:29:08 +0200
Subject: [PATCH 086/164] Remove sorting by index as it is already done here:
https://github.com/matrix-org/matrix-react-sdk/blob/e9ea3cad76173c5e5b8f4d7d618a3d8a17548102/src/autocomplete/QueryMatcher.ts#L120
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Signed-off-by: Šimon Brandner
---
src/autocomplete/RoomProvider.tsx | 10 ----------
1 file changed, 10 deletions(-)
diff --git a/src/autocomplete/RoomProvider.tsx b/src/autocomplete/RoomProvider.tsx
index ad55b19101..04dc9720e6 100644
--- a/src/autocomplete/RoomProvider.tsx
+++ b/src/autocomplete/RoomProvider.tsx
@@ -32,15 +32,6 @@ import SettingsStore from "../settings/SettingsStore";
const ROOM_REGEX = /\B#\S*/g;
-function score(query: string, space: string) {
- const index = space.indexOf(query);
- if (index === -1) {
- return Infinity;
- } else {
- return index;
- }
-}
-
function matcherObject(room: Room, displayedAlias: string, matchName = "") {
return {
room,
@@ -106,7 +97,6 @@ export default class RoomProvider extends AutocompleteProvider {
const matchedString = command[0];
completions = this.matcher.match(matchedString, limit);
completions = sortBy(completions, [
- (c) => score(matchedString, c.displayedAlias),
(c) => c.displayedAlias.length,
]);
completions = uniqBy(completions, (match) => match.room);
From 7bf230e66502679c3f9894e8e642b215e48fd30d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=C5=A0imon=20Brandner?=
Date: Sun, 20 Jun 2021 10:41:36 +0200
Subject: [PATCH 087/164] Prefer canonical aliases over non-canonical ones
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Signed-off-by: Šimon Brandner
---
src/autocomplete/RoomProvider.tsx | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/src/autocomplete/RoomProvider.tsx b/src/autocomplete/RoomProvider.tsx
index 04dc9720e6..f784a57821 100644
--- a/src/autocomplete/RoomProvider.tsx
+++ b/src/autocomplete/RoomProvider.tsx
@@ -32,6 +32,11 @@ import SettingsStore from "../settings/SettingsStore";
const ROOM_REGEX = /\B#\S*/g;
+// Prefer canonical aliases over non-canonical ones
+function canonicalScore(displayedAlias: string, room: Room): number {
+ return displayedAlias === room.getCanonicalAlias() ? 0 : 1;
+}
+
function matcherObject(room: Room, displayedAlias: string, matchName = "") {
return {
room,
@@ -97,6 +102,7 @@ export default class RoomProvider extends AutocompleteProvider {
const matchedString = command[0];
completions = this.matcher.match(matchedString, limit);
completions = sortBy(completions, [
+ (c) => canonicalScore(c.displayedAlias, c.room),
(c) => c.displayedAlias.length,
]);
completions = uniqBy(completions, (match) => match.room);
From 6c64f564e4ccba567564edfaeb9417fcefa9e05b Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Fri, 18 Jun 2021 18:32:45 +0100
Subject: [PATCH 088/164] Naive attempt at improving our end-to-end tests in
Github Actions
---
.github/workflows/develop.yml | 7 +++++--
...d-tests.sh => prepare-end-to-end-tests.sh} | 11 +----------
scripts/ci/run-end-to-end-tests.sh | 19 +++++++++++++++++++
3 files changed, 25 insertions(+), 12 deletions(-)
rename scripts/ci/{end-to-end-tests.sh => prepare-end-to-end-tests.sh} (65%)
create mode 100755 scripts/ci/run-end-to-end-tests.sh
diff --git a/.github/workflows/develop.yml b/.github/workflows/develop.yml
index 6410bd28fa..3c3807e33b 100644
--- a/.github/workflows/develop.yml
+++ b/.github/workflows/develop.yml
@@ -11,10 +11,13 @@ jobs:
steps:
- name: Checkout code
uses: actions/checkout@v2
- - name: End-to-End tests
- run: ./scripts/ci/end-to-end-tests.sh
+ - name: Prepare End-to-End tests
+ run: ./scripts/ci/prepare-end-to-end-tests.sh
+ - name: Run End-to-End tests
+ run: ./scripts/ci/run-end-to-end-tests.sh
- name: Archive logs
uses: actions/upload-artifact@v2
+ if: ${{ always() }}
with:
path: |
test/end-to-end-tests/logs/**/*
diff --git a/scripts/ci/end-to-end-tests.sh b/scripts/ci/prepare-end-to-end-tests.sh
similarity index 65%
rename from scripts/ci/end-to-end-tests.sh
rename to scripts/ci/prepare-end-to-end-tests.sh
index edb8870d8e..147e1f6445 100755
--- a/scripts/ci/end-to-end-tests.sh
+++ b/scripts/ci/prepare-end-to-end-tests.sh
@@ -1,8 +1,4 @@
#!/bin/bash
-#
-# script which is run by the CI build (after `yarn test`).
-#
-# clones element-web develop and runs the tests against our version of react-sdk.
set -ev
@@ -19,7 +15,7 @@ cd element-web
element_web_dir=`pwd`
CI_PACKAGE=true yarn build
cd ..
-# run end to end tests
+# prepare end to end tests
pushd test/end-to-end-tests
ln -s $element_web_dir element/element-web
# PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true ./install.sh
@@ -28,9 +24,4 @@ echo "--- Install synapse & other dependencies"
./install.sh
# install static webserver to server symlinked local copy of element
./element/install-webserver.sh
-rm -r logs || true
-mkdir logs
-echo "+++ Running end-to-end tests"
-TESTS_STARTED=1
-./run.sh --no-sandbox --log-directory logs/
popd
diff --git a/scripts/ci/run-end-to-end-tests.sh b/scripts/ci/run-end-to-end-tests.sh
new file mode 100755
index 0000000000..3c99391fc7
--- /dev/null
+++ b/scripts/ci/run-end-to-end-tests.sh
@@ -0,0 +1,19 @@
+#!/bin/bash
+
+set -ev
+
+handle_error() {
+ EXIT_CODE=$?
+ exit $EXIT_CODE
+}
+
+trap 'handle_error' ERR
+
+# run end to end tests
+pushd test/end-to-end-tests
+rm -r logs || true
+mkdir logs
+echo "--- Running end-to-end tests"
+TESTS_STARTED=1
+./run.sh --no-sandbox --log-directory logs/
+popd
From e79b7d7adb7b8f5214d0b498cd555957ccc5f1ba Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Sat, 19 Jun 2021 15:37:48 +0100
Subject: [PATCH 089/164] Fix View Source accessing renamed private field on
MatrixEvent
---
src/components/structures/ViewSource.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/components/structures/ViewSource.js b/src/components/structures/ViewSource.js
index 6fe99dd464..b69a92dd61 100644
--- a/src/components/structures/ViewSource.js
+++ b/src/components/structures/ViewSource.js
@@ -55,7 +55,7 @@ export default class ViewSource extends React.Component {
viewSourceContent() {
const mxEvent = this.props.mxEvent.replacingEvent() || this.props.mxEvent; // show the replacing event, not the original, if it is an edit
const isEncrypted = mxEvent.isEncrypted();
- const decryptedEventSource = mxEvent._clearEvent; // FIXME: _clearEvent is private
+ const decryptedEventSource = mxEvent.clearEvent; // FIXME: clearEvent is private
const originalEventSource = mxEvent.event;
if (isEncrypted) {
From 2d9e97a3e13bb481431f812178c47f6c75e395c6 Mon Sep 17 00:00:00 2001
From: Germain Souquet
Date: Mon, 21 Jun 2021 09:47:46 +0100
Subject: [PATCH 090/164] Fix branch matching to work with GitHub Actions and
BuildKite
---
scripts/fetchdep.sh | 21 +++++++++++++++------
1 file changed, 15 insertions(+), 6 deletions(-)
diff --git a/scripts/fetchdep.sh b/scripts/fetchdep.sh
index fe1f49c361..9844fdc9db 100755
--- a/scripts/fetchdep.sh
+++ b/scripts/fetchdep.sh
@@ -22,15 +22,18 @@ clone() {
}
# Try the PR author's branch in case it exists on the deps as well.
-# First we check if BUILDKITE_BRANCH is defined,
+# First we check if GITHUB_HEAD_REF is defined,
+# Then we check if BUILDKITE_BRANCH is defined,
# if it isn't we can assume this is a Netlify build
-if [ -z ${BUILDKITE_BRANCH+x} ]; then
+if [ -n ${GITHUB_HEAD_REF+x} ]; then
+ head=$GITHUB_HEAD_REF
+elif [ -n ${BUILDKITE_BRANCH+x} ]; then
+ head=$BUILDKITE_BRANCH
+else
# Netlify doesn't give us info about the fork so we have to get it from GitHub API
apiEndpoint="https://api.github.com/repos/matrix-org/matrix-react-sdk/pulls/"
apiEndpoint+=$REVIEW_ID
head=$(curl $apiEndpoint | jq -r '.head.label')
-else
- head=$BUILDKITE_BRANCH
fi
# If head is set, it will contain either:
@@ -39,12 +42,18 @@ fi
# We can split on `:` into an array to check.
BRANCH_ARRAY=(${head//:/ })
if [[ "${#BRANCH_ARRAY[@]}" == "1" ]]; then
- clone $deforg $defrepo $BUILDKITE_BRANCH
+ clone $deforg $defrepo $head
elif [[ "${#BRANCH_ARRAY[@]}" == "2" ]]; then
clone ${BRANCH_ARRAY[0]} $defrepo ${BRANCH_ARRAY[1]}
fi
+
# Try the target branch of the push or PR.
-clone $deforg $defrepo $BUILDKITE_PULL_REQUEST_BASE_BRANCH
+if [ -n ${GITHUB_BASE_REF+x} ]; then
+ clone $deforg $defrepo $GITHUB_BASE_REF
+elif [ -n ${BUILDKITE_PULL_REQUEST_BASE_BRANCH+x} ]; then
+ clone $deforg $defrepo $BUILDKITE_PULL_REQUEST_BASE_BRANCH
+fi
+
# Try HEAD which is the branch name in Netlify (not BRANCH which is pull/xxxx/head for PR builds)
clone $deforg $defrepo $HEAD
# Use the default branch as the last resort.
From 9756a99220a34a1a4bae9c0b15c14871ef161818 Mon Sep 17 00:00:00 2001
From: Germain Souquet
Date: Mon, 21 Jun 2021 12:14:30 +0100
Subject: [PATCH 091/164] Migrate TruncatedList to TypeScript
---
.../{TruncatedList.js => TruncatedList.tsx} | 50 +++++++++----------
1 file changed, 24 insertions(+), 26 deletions(-)
rename src/components/views/elements/{TruncatedList.js => TruncatedList.tsx} (65%)
diff --git a/src/components/views/elements/TruncatedList.js b/src/components/views/elements/TruncatedList.tsx
similarity index 65%
rename from src/components/views/elements/TruncatedList.js
rename to src/components/views/elements/TruncatedList.tsx
index 0509775545..395caa9222 100644
--- a/src/components/views/elements/TruncatedList.js
+++ b/src/components/views/elements/TruncatedList.tsx
@@ -16,31 +16,29 @@ limitations under the License.
*/
import React from 'react';
-import PropTypes from 'prop-types';
import { _t } from '../../../languageHandler';
import {replaceableComponent} from "../../../utils/replaceableComponent";
-@replaceableComponent("views.elements.TruncatedList")
-export default class TruncatedList extends React.Component {
- static propTypes = {
- // The number of elements to show before truncating. If negative, no truncation is done.
- truncateAt: PropTypes.number,
- // The className to apply to the wrapping div
- className: PropTypes.string,
- // A function that returns the children to be rendered into the element.
- // function getChildren(start: number, end: number): Array
- // The start element is included, the end is not (as in `slice`).
- // If omitted, the React child elements will be used. This parameter can be used
- // to avoid creating unnecessary React elements.
- getChildren: PropTypes.func,
- // A function that should return the total number of child element available.
- // Required if getChildren is supplied.
- getChildCount: PropTypes.func,
- // A function which will be invoked when an overflow element is required.
- // This will be inserted after the children.
- createOverflowElement: PropTypes.func,
- };
+interface IProps {
+ // The number of elements to show before truncating. If negative, no truncation is done.
+ truncateAt?: number;
+ // The className to apply to the wrapping div
+ className?: string;
+ // A function that returns the children to be rendered into the element.
+ // The start element is included, the end is not (as in `slice`).
+ // If omitted, the React child elements will be used. This parameter can be used
+ // to avoid creating unnecessary React elements.
+ getChildren?: (start: number, end: number) => Array;
+ // A function that should return the total number of child element available.
+ // Required if getChildren is supplied.
+ getChildCount?: () => number;
+ // A function which will be invoked when an overflow element is required.
+ // This will be inserted after the children.
+ createOverflowElement?: (overflowCount: number, totalCount: number) => React.ReactNode;
+}
+@replaceableComponent("views.elements.TruncatedList")
+export default class TruncatedList extends React.Component {
static defaultProps ={
truncateAt: 2,
createOverflowElement(overflowCount, totalCount) {
@@ -50,7 +48,7 @@ export default class TruncatedList extends React.Component {
},
};
- _getChildren(start, end) {
+ private getChildren(start: number, end: number): Array {
if (this.props.getChildren && this.props.getChildCount) {
return this.props.getChildren(start, end);
} else {
@@ -63,7 +61,7 @@ export default class TruncatedList extends React.Component {
}
}
- _getChildCount() {
+ private getChildCount(): number {
if (this.props.getChildren && this.props.getChildCount) {
return this.props.getChildCount();
} else {
@@ -73,10 +71,10 @@ export default class TruncatedList extends React.Component {
}
}
- render() {
+ public render() {
let overflowNode = null;
- const totalChildren = this._getChildCount();
+ const totalChildren = this.getChildCount();
let upperBound = totalChildren;
if (this.props.truncateAt >= 0) {
const overflowCount = totalChildren - this.props.truncateAt;
@@ -87,7 +85,7 @@ export default class TruncatedList extends React.Component {
upperBound = this.props.truncateAt;
}
}
- const childNodes = this._getChildren(0, upperBound);
+ const childNodes = this.getChildren(0, upperBound);
return (
From d2595dcd61876d9f1def0b5e197918588692c07b Mon Sep 17 00:00:00 2001
From: Germain Souquet
Date: Mon, 21 Jun 2021 12:29:59 +0100
Subject: [PATCH 092/164] use TruncatedList to improve ForwardDialog rendering
time
---
.../views/dialogs/ForwardDialog.tsx | 37 ++++++++++++++-----
1 file changed, 28 insertions(+), 9 deletions(-)
diff --git a/src/components/views/dialogs/ForwardDialog.tsx b/src/components/views/dialogs/ForwardDialog.tsx
index a83f3f177c..b04fd9ef76 100644
--- a/src/components/views/dialogs/ForwardDialog.tsx
+++ b/src/components/views/dialogs/ForwardDialog.tsx
@@ -39,6 +39,9 @@ import NotificationBadge from "../rooms/NotificationBadge";
import {RoomPermalinkCreator} from "../../../utils/permalinks/Permalinks";
import {sortRooms} from "../../../stores/room-list/algorithms/tag-sorting/RecentAlgorithm";
import QueryMatcher from "../../../autocomplete/QueryMatcher";
+import TruncatedList from "../elements/TruncatedList";
+import EntityTile from "../rooms/EntityTile";
+import BaseAvatar from "../avatars/BaseAvatar";
const AVATAR_SIZE = 30;
@@ -195,6 +198,17 @@ const ForwardDialog: React.FC = ({ matrixClient: cli, event, permalinkCr
}).match(lcQuery);
}
+ const [truncateAt, setTruncateAt] = useState(20);
+ function overflowTile(overflowCount, totalCount) {
+ const text = _t("and %(count)s others...", { count: overflowCount });
+ return (
+
+ } name={text} presenceState="online" suppressOnHover={true}
+ onClick={() => setTruncateAt(totalCount)} />
+ );
+ }
+
return = ({ matrixClient: cli, event, permalinkCr
{ rooms.length > 0 ? (
- { rooms.map(room =>
- ,
- ) }
+ rooms.slice(start, end).map(room =>
+ ,
+ )}
+ getChildCount={() => rooms.length}
+ />
) :
{ _t("No results") }
From ca123d3c4daaf18e8ab0dcd9ced019fcd6762f37 Mon Sep 17 00:00:00 2001
From: Germain Souquet
Date: Mon, 21 Jun 2021 14:05:56 +0100
Subject: [PATCH 093/164] Migrate MKeyVerificationRequest to TypeScript
---
...Request.js => MKeyVerificationRequest.tsx} | 64 +++++++++----------
1 file changed, 29 insertions(+), 35 deletions(-)
rename src/components/views/messages/{MKeyVerificationRequest.js => MKeyVerificationRequest.tsx} (77%)
diff --git a/src/components/views/messages/MKeyVerificationRequest.js b/src/components/views/messages/MKeyVerificationRequest.tsx
similarity index 77%
rename from src/components/views/messages/MKeyVerificationRequest.js
rename to src/components/views/messages/MKeyVerificationRequest.tsx
index 988606a766..df35c47706 100644
--- a/src/components/views/messages/MKeyVerificationRequest.js
+++ b/src/components/views/messages/MKeyVerificationRequest.tsx
@@ -15,41 +15,40 @@ limitations under the License.
*/
import React from 'react';
-import PropTypes from 'prop-types';
-import {MatrixClientPeg} from '../../../MatrixClientPeg';
+import { MatrixEvent } from 'matrix-js-sdk/src';
+import { MatrixClientPeg } from '../../../MatrixClientPeg';
import * as sdk from '../../../index';
import { _t } from '../../../languageHandler';
-import {getNameForEventRoom, userLabelForEventRoom}
+import { getNameForEventRoom, userLabelForEventRoom }
from '../../../utils/KeyVerificationStateObserver';
import dis from "../../../dispatcher/dispatcher";
-import {RightPanelPhases} from "../../../stores/RightPanelStorePhases";
-import {Action} from "../../../dispatcher/actions";
+import { RightPanelPhases } from "../../../stores/RightPanelStorePhases";
+import { Action } from "../../../dispatcher/actions";
import EventTileBubble from "./EventTileBubble";
-import {replaceableComponent} from "../../../utils/replaceableComponent";
+import { replaceableComponent } from "../../../utils/replaceableComponent";
+
+interface IProps {
+ mxEvent: MatrixEvent
+}
@replaceableComponent("views.messages.MKeyVerificationRequest")
-export default class MKeyVerificationRequest extends React.Component {
- constructor(props) {
- super(props);
- this.state = {};
- }
-
- componentDidMount() {
+export default class MKeyVerificationRequest extends React.Component {
+ public componentDidMount() {
const request = this.props.mxEvent.verificationRequest;
if (request) {
- request.on("change", this._onRequestChanged);
+ request.on("change", this.onRequestChanged);
}
}
- componentWillUnmount() {
+ public componentWillUnmount() {
const request = this.props.mxEvent.verificationRequest;
if (request) {
- request.off("change", this._onRequestChanged);
+ request.off("change", this.onRequestChanged);
}
}
- _openRequest = () => {
- const {verificationRequest} = this.props.mxEvent;
+ private openRequest = () => {
+ const { verificationRequest } = this.props.mxEvent;
const member = MatrixClientPeg.get().getUser(verificationRequest.otherUserId);
dis.dispatch({
action: Action.SetRightPanelPhase,
@@ -58,15 +57,15 @@ export default class MKeyVerificationRequest extends React.Component {
});
};
- _onRequestChanged = () => {
+ private onRequestChanged = () => {
this.forceUpdate();
};
- _onAcceptClicked = async () => {
+ private onAcceptClicked = async () => {
const request = this.props.mxEvent.verificationRequest;
if (request) {
try {
- this._openRequest();
+ this.openRequest();
await request.accept();
} catch (err) {
console.error(err.message);
@@ -74,7 +73,7 @@ export default class MKeyVerificationRequest extends React.Component {
}
};
- _onRejectClicked = async () => {
+ private onRejectClicked = async () => {
const request = this.props.mxEvent.verificationRequest;
if (request) {
try {
@@ -85,7 +84,7 @@ export default class MKeyVerificationRequest extends React.Component {
}
};
- _acceptedLabel(userId) {
+ private acceptedLabel(userId: string) {
const client = MatrixClientPeg.get();
const myUserId = client.getUserId();
if (userId === myUserId) {
@@ -95,7 +94,7 @@ export default class MKeyVerificationRequest extends React.Component {
}
}
- _cancelledLabel(userId) {
+ private cancelledLabel(userId: string) {
const client = MatrixClientPeg.get();
const myUserId = client.getUserId();
const {cancellationCode} = this.props.mxEvent.verificationRequest;
@@ -115,7 +114,7 @@ export default class MKeyVerificationRequest extends React.Component {
}
}
- render() {
+ public render() {
const AccessibleButton = sdk.getComponent("elements.AccessibleButton");
const FormButton = sdk.getComponent("elements.FormButton");
@@ -134,11 +133,11 @@ export default class MKeyVerificationRequest extends React.Component {
let stateLabel;
const accepted = request.ready || request.started || request.done;
if (accepted) {
- stateLabel = (
- {this._acceptedLabel(request.receivingUserId)}
+ stateLabel = (
+ {this.acceptedLabel(request.receivingUserId)}
);
} else if (request.cancelled) {
- stateLabel = this._cancelledLabel(request.cancellingUserId);
+ stateLabel = this.cancelledLabel(request.cancellingUserId);
} else if (request.accepting) {
stateLabel = _t("Accepting …");
} else if (request.declining) {
@@ -153,8 +152,8 @@ export default class MKeyVerificationRequest extends React.Component {
subtitle = userLabelForEventRoom(request.requestingUserId, mxEvent.getRoomId());
if (request.canAccept) {
stateNode = (
-
-
+
+
);
}
} else { // request sent by us
@@ -174,8 +173,3 @@ export default class MKeyVerificationRequest extends React.Component {
return null;
}
}
-
-MKeyVerificationRequest.propTypes = {
- /* the MatrixEvent to show */
- mxEvent: PropTypes.object.isRequired,
-};
From adb42b792782b5151773089bc5243c6563718a8c Mon Sep 17 00:00:00 2001
From: Germain Souquet
Date: Mon, 21 Jun 2021 14:16:37 +0100
Subject: [PATCH 094/164] Deprecate FormButton to use AccessibleButton
everywhere
---
res/css/_components.scss | 1 -
res/css/structures/_ToastContainer.scss | 3 +-
res/css/views/elements/_FormButton.scss | 42 -------------------
res/css/views/right_panel/_UserInfo.scss | 10 -----
.../views/right_panel/_VerificationPanel.scss | 2 +-
res/css/views/spaces/_SpaceBasicSettings.scss | 2 +-
src/components/views/elements/FormButton.js | 28 -------------
.../messages/MKeyVerificationRequest.tsx | 9 ++--
.../views/right_panel/VerificationPanel.tsx | 20 +++------
src/components/views/toasts/GenericToast.tsx | 10 +++--
src/components/views/voip/IncomingCallBox.tsx | 16 +++----
11 files changed, 31 insertions(+), 112 deletions(-)
delete mode 100644 res/css/views/elements/_FormButton.scss
delete mode 100644 src/components/views/elements/FormButton.js
diff --git a/res/css/_components.scss b/res/css/_components.scss
index 56403ea190..ec3af8655e 100644
--- a/res/css/_components.scss
+++ b/res/css/_components.scss
@@ -123,7 +123,6 @@
@import "./views/elements/_EventListSummary.scss";
@import "./views/elements/_FacePile.scss";
@import "./views/elements/_Field.scss";
-@import "./views/elements/_FormButton.scss";
@import "./views/elements/_ImageView.scss";
@import "./views/elements/_InfoTooltip.scss";
@import "./views/elements/_InlineSpinner.scss";
diff --git a/res/css/structures/_ToastContainer.scss b/res/css/structures/_ToastContainer.scss
index 09f834a6e3..14e4c01389 100644
--- a/res/css/structures/_ToastContainer.scss
+++ b/res/css/structures/_ToastContainer.scss
@@ -134,8 +134,9 @@ limitations under the License.
.mx_Toast_buttons {
float: right;
display: flex;
+ gap: 5px;
- .mx_FormButton {
+ .mx_AccessibleButton {
min-width: 96px;
box-sizing: border-box;
}
diff --git a/res/css/views/elements/_FormButton.scss b/res/css/views/elements/_FormButton.scss
deleted file mode 100644
index eda201ff03..0000000000
--- a/res/css/views/elements/_FormButton.scss
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
-Copyright 2019 The Matrix.org Foundation C.I.C.
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-.mx_FormButton {
- line-height: $font-16px;
- padding: 5px 15px;
- font-size: $font-12px;
- height: min-content;
-
- &:not(:last-child) {
- margin-right: 8px;
- }
-
- &.mx_AccessibleButton_kind_primary {
- color: $accent-color;
- background-color: $accent-bg-color;
- }
-
- &.mx_AccessibleButton_kind_danger {
- color: $notice-primary-color;
- background-color: $notice-primary-bg-color;
- }
-
- &.mx_AccessibleButton_kind_secondary {
- color: $secondary-fg-color;
- border: 1px solid $secondary-fg-color;
- background-color: unset;
- }
-}
diff --git a/res/css/views/right_panel/_UserInfo.scss b/res/css/views/right_panel/_UserInfo.scss
index 87420ae4e7..6632ccddf9 100644
--- a/res/css/views/right_panel/_UserInfo.scss
+++ b/res/css/views/right_panel/_UserInfo.scss
@@ -259,16 +259,6 @@ limitations under the License.
.mx_AccessibleButton.mx_AccessibleButton_hasKind {
padding: 8px 18px;
-
- &.mx_AccessibleButton_kind_primary {
- color: $accent-color;
- background-color: $accent-bg-color;
- }
-
- &.mx_AccessibleButton_kind_danger {
- color: $notice-primary-color;
- background-color: $notice-primary-bg-color;
- }
}
.mx_VerificationShowSas .mx_AccessibleButton,
diff --git a/res/css/views/right_panel/_VerificationPanel.scss b/res/css/views/right_panel/_VerificationPanel.scss
index a8466a1626..12148b09de 100644
--- a/res/css/views/right_panel/_VerificationPanel.scss
+++ b/res/css/views/right_panel/_VerificationPanel.scss
@@ -58,7 +58,7 @@ limitations under the License.
}
.mx_VerificationPanel_reciprocate_section {
- .mx_FormButton {
+ .mx_AccessibleButton {
width: 100%;
box-sizing: border-box;
padding: 10px;
diff --git a/res/css/views/spaces/_SpaceBasicSettings.scss b/res/css/views/spaces/_SpaceBasicSettings.scss
index 204ccab2b7..32454b9530 100644
--- a/res/css/views/spaces/_SpaceBasicSettings.scss
+++ b/res/css/views/spaces/_SpaceBasicSettings.scss
@@ -73,7 +73,7 @@ limitations under the License.
}
}
- .mx_FormButton {
+ .mx_AccessibleButton {
padding: 8px 22px;
margin-left: auto;
display: block;
diff --git a/src/components/views/elements/FormButton.js b/src/components/views/elements/FormButton.js
deleted file mode 100644
index f6b4c986f5..0000000000
--- a/src/components/views/elements/FormButton.js
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
-Copyright 2019 The Matrix.org Foundation C.I.C.
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-import React from 'react';
-import AccessibleButton from "./AccessibleButton";
-
-export default function FormButton(props) {
- const {className, label, kind, ...restProps} = props;
- const newClassName = (className || "") + " mx_FormButton";
- const allProps = Object.assign({}, restProps,
- {className: newClassName, kind: kind || "primary", children: [label]});
- return React.createElement(AccessibleButton, allProps);
-}
-
-FormButton.propTypes = AccessibleButton.propTypes;
diff --git a/src/components/views/messages/MKeyVerificationRequest.tsx b/src/components/views/messages/MKeyVerificationRequest.tsx
index df35c47706..69467cfa50 100644
--- a/src/components/views/messages/MKeyVerificationRequest.tsx
+++ b/src/components/views/messages/MKeyVerificationRequest.tsx
@@ -116,7 +116,6 @@ export default class MKeyVerificationRequest extends React.Component {
public render() {
const AccessibleButton = sdk.getComponent("elements.AccessibleButton");
- const FormButton = sdk.getComponent("elements.FormButton");
const {mxEvent} = this.props;
const request = mxEvent.verificationRequest;
@@ -152,8 +151,12 @@ export default class MKeyVerificationRequest extends React.Component {
subtitle = userLabelForEventRoom(request.requestingUserId, mxEvent.getRoomId());
if (request.canAccept) {
stateNode = (
-
-
+
+ {_t("Decline")}
+
+
+ {_t("Accept")}
+
);
}
} else { // request sent by us
diff --git a/src/components/views/right_panel/VerificationPanel.tsx b/src/components/views/right_panel/VerificationPanel.tsx
index edfe0e3483..ce39141391 100644
--- a/src/components/views/right_panel/VerificationPanel.tsx
+++ b/src/components/views/right_panel/VerificationPanel.tsx
@@ -195,14 +195,7 @@ export default class VerificationPanel extends React.PureComponent
{description}
-
{_t("No")}
-
+
{_t("Yes")}
+ onClick={this.onReciprocateYesClick} />
;
} else {
diff --git a/src/components/views/toasts/GenericToast.tsx b/src/components/views/toasts/GenericToast.tsx
index 209babbf9d..ae01e8bfb7 100644
--- a/src/components/views/toasts/GenericToast.tsx
+++ b/src/components/views/toasts/GenericToast.tsx
@@ -15,8 +15,8 @@ limitations under the License.
*/
import React, {ReactNode} from "react";
+import AccessibleButton from "../elements/AccessibleButton";
-import FormButton from "../elements/FormButton";
import {XOR} from "../../../@types/common";
export interface IProps {
@@ -50,8 +50,12 @@ const GenericToast: React.FC> = ({
{detailContent}
- {onReject && rejectLabel &&
}
-
+ {onReject && rejectLabel &&
+ {rejectLabel}
+ }
+
+ {acceptLabel}
+
;
};
diff --git a/src/components/views/voip/IncomingCallBox.tsx b/src/components/views/voip/IncomingCallBox.tsx
index a0660318bc..10b102832d 100644
--- a/src/components/views/voip/IncomingCallBox.tsx
+++ b/src/components/views/voip/IncomingCallBox.tsx
@@ -23,7 +23,7 @@ import { _t } from '../../../languageHandler';
import { ActionPayload } from '../../../dispatcher/payloads';
import CallHandler, { AudioID } from '../../../CallHandler';
import RoomAvatar from '../avatars/RoomAvatar';
-import FormButton from '../elements/FormButton';
+import AccesibleButton from '../elements/AccessibleButton';
import { CallState } from 'matrix-js-sdk/src/webrtc/call';
import {replaceableComponent} from "../../../utils/replaceableComponent";
import AccessibleTooltipButton from '../elements/AccessibleTooltipButton';
@@ -143,19 +143,21 @@ export default class IncomingCallBox extends React.Component {
/>
-
+ >
+ {_t("Decline")}
+
-
+ >
+ {_t("Accept")}
+
;
}
From 7f635c68c519e0df9ba20e34c8f278ea33b20720 Mon Sep 17 00:00:00 2001
From: Germain Souquet
Date: Mon, 21 Jun 2021 14:50:21 +0100
Subject: [PATCH 095/164] Migrate SearchBar to TypeScript
---
src/components/structures/RoomView.tsx | 9 +--
.../rooms/{SearchBar.js => SearchBar.tsx} | 69 ++++++++++++++-----
2 files changed, 55 insertions(+), 23 deletions(-)
rename src/components/views/rooms/{SearchBar.js => SearchBar.tsx} (55%)
diff --git a/src/components/structures/RoomView.tsx b/src/components/structures/RoomView.tsx
index 1e3adcb518..c1dcb81e08 100644
--- a/src/components/structures/RoomView.tsx
+++ b/src/components/structures/RoomView.tsx
@@ -60,7 +60,7 @@ import ScrollPanel from "./ScrollPanel";
import TimelinePanel from "./TimelinePanel";
import ErrorBoundary from "../views/elements/ErrorBoundary";
import RoomPreviewBar from "../views/rooms/RoomPreviewBar";
-import SearchBar from "../views/rooms/SearchBar";
+import SearchBar, { SearchScope } from "../views/rooms/SearchBar";
import RoomUpgradeWarningBar from "../views/rooms/RoomUpgradeWarningBar";
import AuxPanel from "../views/rooms/AuxPanel";
import RoomHeader from "../views/rooms/RoomHeader";
@@ -82,6 +82,7 @@ import SpaceRoomView from "./SpaceRoomView";
import { IOpts } from "../../createRoom";
import { replaceableComponent } from "../../utils/replaceableComponent";
import UIStore from "../../stores/UIStore";
+import Search from '../views/emojipicker/Search';
const DEBUG = false;
let debuglog = function(msg: string) {};
@@ -139,7 +140,7 @@ export interface IState {
draggingFile: boolean;
searching: boolean;
searchTerm?: string;
- searchScope?: "All" | "Room";
+ searchScope?: SearchScope;
searchResults?: XOR<{}, {
count: number;
highlights: string[];
@@ -1267,7 +1268,7 @@ export default class RoomView extends React.Component {
});
}
- private onSearch = (term: string, scope) => {
+ private onSearch = (term: string, scope: SearchScope) => {
this.setState({
searchTerm: term,
searchScope: scope,
@@ -1288,7 +1289,7 @@ export default class RoomView extends React.Component {
this.searchId = new Date().getTime();
let roomId;
- if (scope === "Room") roomId = this.state.room.roomId;
+ if (scope === SearchScope.Room) roomId = this.state.room.roomId;
debuglog("sending search request");
const searchPromise = eventSearch(term, roomId);
diff --git a/src/components/views/rooms/SearchBar.js b/src/components/views/rooms/SearchBar.tsx
similarity index 55%
rename from src/components/views/rooms/SearchBar.js
rename to src/components/views/rooms/SearchBar.tsx
index 029516c932..de99305d81 100644
--- a/src/components/views/rooms/SearchBar.js
+++ b/src/components/views/rooms/SearchBar.tsx
@@ -15,7 +15,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
-import React, {createRef} from 'react';
+import React, {createRef, RefObject} from 'react';
import AccessibleButton from "../elements/AccessibleButton";
import classNames from "classnames";
import { _t } from '../../../languageHandler';
@@ -23,27 +23,42 @@ import {Key} from "../../../Keyboard";
import DesktopBuildsNotice, {WarningKind} from "../elements/DesktopBuildsNotice";
import {replaceableComponent} from "../../../utils/replaceableComponent";
+interface IProps {
+ onCancelClick: () => void;
+ onSearch: (query: string, scope: string) => void;
+ searchInProgress?: boolean;
+ isRoomEncrypted?: boolean;
+}
+
+interface IState {
+ scope: SearchScope;
+}
+
+export enum SearchScope {
+ Room = "Room",
+ All = "All",
+}
+
@replaceableComponent("views.rooms.SearchBar")
-export default class SearchBar extends React.Component {
- constructor(props) {
+export default class SearchBar extends React.Component {
+ private searchTerm: RefObject = createRef();
+
+ constructor(props: IProps) {
super(props);
-
- this._search_term = createRef();
-
this.state = {
- scope: 'Room',
+ scope: SearchScope.Room,
};
}
- onThisRoomClick = () => {
- this.setState({ scope: 'Room' }, () => this._searchIfQuery());
+ public onThisRoomClick = () => {
+ this.setState({ scope: SearchScope.Room }, () => this._searchIfQuery());
};
- onAllRoomsClick = () => {
- this.setState({ scope: 'All' }, () => this._searchIfQuery());
+ public onAllRoomsClick = () => {
+ this.setState({ scope: SearchScope.All }, () => this._searchIfQuery());
};
- onSearchChange = (e) => {
+ public onSearchChange = (e: React.KeyboardEvent) => {
switch (e.key) {
case Key.ENTER:
this.onSearch();
@@ -55,13 +70,13 @@ export default class SearchBar extends React.Component {
};
_searchIfQuery() {
- if (this._search_term.current.value) {
+ if (this.searchTerm.current.value) {
this.onSearch();
}
}
onSearch = () => {
- this.props.onSearch(this._search_term.current.value, this.state.scope);
+ this.props.onSearch(this.searchTerm.current.value, this.state.scope);
};
render() {
@@ -69,25 +84,41 @@ export default class SearchBar extends React.Component {
mx_SearchBar_searching: this.props.searchInProgress,
});
const thisRoomClasses = classNames("mx_SearchBar_button", {
- mx_SearchBar_unselected: this.state.scope !== 'Room',
+ mx_SearchBar_unselected: this.state.scope !== SearchScope.Room,
});
const allRoomsClasses = classNames("mx_SearchBar_button", {
- mx_SearchBar_unselected: this.state.scope !== 'All',
+ mx_SearchBar_unselected: this.state.scope !== SearchScope.All,
});
return (
<>
-
+
{_t("This Room")}
-
+
{_t("All Rooms")}
From 7825c30bf773185c967c61ef3cb2a448bdf8f791 Mon Sep 17 00:00:00 2001
From: Germain Souquet
Date: Mon, 21 Jun 2021 15:41:47 +0100
Subject: [PATCH 096/164] Improve event index initialisation failure message in
search bar for supported platforms
---
.../views/elements/DesktopBuildsNotice.tsx | 19 ++++++++++++++++++-
src/i18n/strings/en_EN.json | 1 +
2 files changed, 19 insertions(+), 1 deletion(-)
diff --git a/src/components/views/elements/DesktopBuildsNotice.tsx b/src/components/views/elements/DesktopBuildsNotice.tsx
index fd1c7848aa..e5e94d4bd4 100644
--- a/src/components/views/elements/DesktopBuildsNotice.tsx
+++ b/src/components/views/elements/DesktopBuildsNotice.tsx
@@ -14,10 +14,15 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
+import React from "react";
import EventIndexPeg from "../../../indexing/EventIndexPeg";
import { _t } from "../../../languageHandler";
import SdkConfig from "../../../SdkConfig";
-import React from "react";
+
+import dis from "../../../dispatcher/dispatcher";
+import { Action } from "../../../dispatcher/actions";
+import { UserTab } from "../dialogs/UserSettingsDialog";
+
export enum WarningKind {
Files,
@@ -33,6 +38,18 @@ export default function DesktopBuildsNotice({isRoomEncrypted, kind}: IProps) {
if (!isRoomEncrypted) return null;
if (EventIndexPeg.get()) return null;
+ if (EventIndexPeg.error) {
+ return _t("Message search initialisation failed, check your settings for more information", {}, {
+ a: sub => ( {
+ evt.preventDefault();
+ dis.dispatch({
+ action: Action.ViewUserSettings,
+ initialTabId: UserTab.Security,
+ });
+ }}>{sub} ),
+ });
+ }
+
const {desktopBuilds, brand} = SdkConfig.get();
let text = null;
diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json
index b88dc79da5..37f6416460 100644
--- a/src/i18n/strings/en_EN.json
+++ b/src/i18n/strings/en_EN.json
@@ -1933,6 +1933,7 @@
"Error loading Widget": "Error loading Widget",
"Error - Mixed content": "Error - Mixed content",
"Popout widget": "Popout widget",
+ "Message search initialisation failed, check your settings for more information": "Message search initialisation failed, check your settings for more information",
"Use the Desktop app to see all encrypted files": "Use the Desktop app to see all encrypted files",
"Use the Desktop app to search encrypted messages": "Use the Desktop app to search encrypted messages",
"This version of %(brand)s does not support viewing some encrypted files": "This version of %(brand)s does not support viewing some encrypted files",
From 88d25ad6af058d098f147553e7289606cf57a38d Mon Sep 17 00:00:00 2001
From: Germain Souquet
Date: Mon, 21 Jun 2021 15:44:09 +0100
Subject: [PATCH 097/164] Fix typo in default import name
---
src/components/views/voip/IncomingCallBox.tsx | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/src/components/views/voip/IncomingCallBox.tsx b/src/components/views/voip/IncomingCallBox.tsx
index 10b102832d..cd1a3afd10 100644
--- a/src/components/views/voip/IncomingCallBox.tsx
+++ b/src/components/views/voip/IncomingCallBox.tsx
@@ -23,7 +23,7 @@ import { _t } from '../../../languageHandler';
import { ActionPayload } from '../../../dispatcher/payloads';
import CallHandler, { AudioID } from '../../../CallHandler';
import RoomAvatar from '../avatars/RoomAvatar';
-import AccesibleButton from '../elements/AccessibleButton';
+import AccessibleButton from '../elements/AccessibleButton';
import { CallState } from 'matrix-js-sdk/src/webrtc/call';
import {replaceableComponent} from "../../../utils/replaceableComponent";
import AccessibleTooltipButton from '../elements/AccessibleTooltipButton';
@@ -143,21 +143,21 @@ export default class IncomingCallBox extends React.Component {
/>
-
{_t("Decline")}
-
+
-
{_t("Accept")}
-
+
;
}
From ca5f8f97bb80d4cdeaee584b7e9ba0c83207a2b2 Mon Sep 17 00:00:00 2001
From: Germain Souquet
Date: Mon, 21 Jun 2021 16:18:13 +0100
Subject: [PATCH 098/164] Branch matching support for forked repository on
GitHub actions
---
scripts/fetchdep.sh | 10 ++++++++--
1 file changed, 8 insertions(+), 2 deletions(-)
diff --git a/scripts/fetchdep.sh b/scripts/fetchdep.sh
index 9844fdc9db..02af402951 100755
--- a/scripts/fetchdep.sh
+++ b/scripts/fetchdep.sh
@@ -36,13 +36,19 @@ else
head=$(curl $apiEndpoint | jq -r '.head.label')
fi
-# If head is set, it will contain either:
+# If head is set, it will contain on BuilKite either:
# * "branch" when the author's branch and target branch are in the same repo
# * "fork:branch" when the author's branch is in their fork or if this is a Netlify build
# We can split on `:` into an array to check.
+# For GitHub Actions we need to inspect GITHUB_REPOSITORY and GITHUB_ACTOR
+# to determine whether the branch is from a fork or not
BRANCH_ARRAY=(${head//:/ })
if [[ "${#BRANCH_ARRAY[@]}" == "1" ]]; then
- clone $deforg $defrepo $head
+ if [[ "$GITHUB_REPOSITORY" = "$deforg/$defrepo" ]]; then
+ clone $deforg $defrepo $head
+ else
+ clone $GITHUB_ACTOR $defrepo $head
+ fi
elif [[ "${#BRANCH_ARRAY[@]}" == "2" ]]; then
clone ${BRANCH_ARRAY[0]} $defrepo ${BRANCH_ARRAY[1]}
fi
From 174a43f1ef901168f69f535515eedb8e6c7f864c Mon Sep 17 00:00:00 2001
From: RiotRobot
Date: Mon, 21 Jun 2021 16:37:49 +0100
Subject: [PATCH 099/164] Upgrade matrix-js-sdk to 12.0.0
---
package.json | 2 +-
yarn.lock | 8 ++++----
2 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/package.json b/package.json
index 644793e265..4a4a326a89 100644
--- a/package.json
+++ b/package.json
@@ -78,7 +78,7 @@
"katex": "^0.12.0",
"linkifyjs": "^2.1.9",
"lodash": "^4.17.20",
- "matrix-js-sdk": "12.0.0-rc.1",
+ "matrix-js-sdk": "12.0.0",
"matrix-widget-api": "^0.1.0-beta.14",
"minimist": "^1.2.5",
"opus-recorder": "^8.0.3",
diff --git a/yarn.lock b/yarn.lock
index 14cd11d769..82ee9a070e 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -5711,10 +5711,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.0-rc.1:
- version "12.0.0-rc.1"
- resolved "https://registry.yarnpkg.com/matrix-js-sdk/-/matrix-js-sdk-12.0.0-rc.1.tgz#b94a72f0549f3000763efb8c7b6fa1f8808e56f6"
- integrity sha512-bzozc4w9dF6Dl8xXXLXMpe3FqL/ncczKdB9Y8dL1mPaujVrmLWAai+BYmC9/c4SIw+1zUap9P5W16ej3z7prig==
+matrix-js-sdk@12.0.0:
+ version "12.0.0"
+ resolved "https://registry.yarnpkg.com/matrix-js-sdk/-/matrix-js-sdk-12.0.0.tgz#8ee7cc37661476341d0c792a1a12bc78b19f9fdd"
+ integrity sha512-DHeq87Sx9Dv37FYyvZkmA1VYsQUNaVgc3QzMUkFwoHt1T4EZzgyYpdsp3uYruJzUW0ACvVJcwFdrU4e1VS97dQ==
dependencies:
"@babel/runtime" "^7.12.5"
another-json "^0.2.0"
From ddbf0fa77fd798495ce3c5940e4c4f44a607d766 Mon Sep 17 00:00:00 2001
From: RiotRobot
Date: Mon, 21 Jun 2021 16:46:20 +0100
Subject: [PATCH 100/164] Prepare changelog for v3.24.0
---
CHANGELOG.md | 12 ++++++++++++
1 file changed, 12 insertions(+)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index a14a0f308e..0f979b4802 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,15 @@
+Changes in [3.24.0](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v3.24.0) (2021-06-21)
+=====================================================================================================
+[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v3.24.0-rc.1...v3.24.0)
+
+ * Upgrade to JS SDK 12.0.0
+ * [Release] Keep composer reply when scrolling away from a highlighted event
+ [\#6211](https://github.com/matrix-org/matrix-react-sdk/pull/6211)
+ * [Release] Remove stray bullet point in reply preview
+ [\#6210](https://github.com/matrix-org/matrix-react-sdk/pull/6210)
+ * [Release] Stop requesting null next replies from the server
+ [\#6209](https://github.com/matrix-org/matrix-react-sdk/pull/6209)
+
Changes in [3.24.0-rc.1](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v3.24.0-rc.1) (2021-06-15)
===============================================================================================================
[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v3.23.0...v3.24.0-rc.1)
From d89710defe12a133cfbc9fee664996dcc17761e2 Mon Sep 17 00:00:00 2001
From: RiotRobot
Date: Mon, 21 Jun 2021 16:53:20 +0100
Subject: [PATCH 101/164] v3.24.0
---
package.json | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/package.json b/package.json
index 4a4a326a89..d592d426a6 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "matrix-react-sdk",
- "version": "3.24.0-rc.1",
+ "version": "3.24.0",
"description": "SDK for matrix.org using React",
"author": "matrix.org",
"repository": {
From 54c3832b5b9dde5981eb1934f599e7b75eac00e8 Mon Sep 17 00:00:00 2001
From: RiotRobot
Date: Mon, 21 Jun 2021 16:54:41 +0100
Subject: [PATCH 102/164] Resetting package fields for development
---
package.json | 5 ++---
1 file changed, 2 insertions(+), 3 deletions(-)
diff --git a/package.json b/package.json
index d592d426a6..f232d4301b 100644
--- a/package.json
+++ b/package.json
@@ -25,7 +25,7 @@
"bin": {
"reskindex": "scripts/reskindex.js"
},
- "main": "./lib/index.js",
+ "main": "./src/index.js",
"matrix_src_main": "./src/index.js",
"matrix_lib_main": "./lib/index.js",
"matrix_lib_typings": "./lib/index.d.ts",
@@ -197,6 +197,5 @@
"coverageReporters": [
"text"
]
- },
- "typings": "./lib/index.d.ts"
+ }
}
From 903f898beeb1e1ddbc3de960197b1b5cc16e36d3 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=C5=A0imon=20Brandner?=
Date: Mon, 21 Jun 2021 18:28:30 +0200
Subject: [PATCH 103/164] Remove ComposerInsertPayload as this is a JS file
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Signed-off-by: Šimon Brandner
---
src/components/views/context_menus/MessageContextMenu.js | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/src/components/views/context_menus/MessageContextMenu.js b/src/components/views/context_menus/MessageContextMenu.js
index 2fb2ac4d0e..5a1da1376d 100644
--- a/src/components/views/context_menus/MessageContextMenu.js
+++ b/src/components/views/context_menus/MessageContextMenu.js
@@ -33,7 +33,6 @@ import { EventType } from "matrix-js-sdk/src/@types/event";
import { replaceableComponent } from "../../../utils/replaceableComponent";
import { ReadPinsEventId } from "../right_panel/PinnedMessagesCard";
import ForwardDialog from "../dialogs/ForwardDialog";
-import { ComposerInsertPayload } from "../../../dispatcher/payloads/ComposerInsertPayload";
import { Action } from "../../../dispatcher/actions";
export function canCancel(eventStatus) {
@@ -201,7 +200,7 @@ export default class MessageContextMenu extends React.Component {
};
onQuoteClick = () => {
- dis.dispatch({
+ dis.dispatch({
action: Action.ComposerInsert,
event: this.props.mxEvent,
});
From dda4c8ec4c9fc5e00c5d9cc5525433acbf8539d9 Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Mon, 21 Jun 2021 21:04:29 +0100
Subject: [PATCH 104/164] Move Promise::allSettled typing from react-sdk to
js-sdk
---
src/@types/global.d.ts | 13 -------------
1 file changed, 13 deletions(-)
diff --git a/src/@types/global.d.ts b/src/@types/global.d.ts
index 0c6b63dd33..7eff341095 100644
--- a/src/@types/global.d.ts
+++ b/src/@types/global.d.ts
@@ -113,19 +113,6 @@ declare global {
usageDetails?: {[key: string]: number};
}
- export interface ISettledFulfilled {
- status: "fulfilled";
- value: T;
- }
- export interface ISettledRejected {
- status: "rejected";
- reason: any;
- }
-
- interface PromiseConstructor {
- allSettled(promises: Promise[]): Promise | ISettledRejected>>;
- }
-
interface HTMLAudioElement {
type?: string;
// sinkId & setSinkId are experimental and typescript doesn't know about them
From de4065719475d6f9d6214406ffbbbfd2a393f1c5 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=C5=A0imon=20Brandner?=
Date: Tue, 22 Jun 2021 09:12:41 +0200
Subject: [PATCH 105/164] Don't show room if we don't click on buttons
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Signed-off-by: Šimon Brandner
---
src/components/structures/RoomDirectory.tsx | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/src/components/structures/RoomDirectory.tsx b/src/components/structures/RoomDirectory.tsx
index 1e0605f263..eb7208f98d 100644
--- a/src/components/structures/RoomDirectory.tsx
+++ b/src/components/structures/RoomDirectory.tsx
@@ -337,11 +337,10 @@ export default class RoomDirectory extends React.Component {
}
private onRoomClicked = (room: IRoom, ev: ButtonEvent) => {
+ // If room was shift-clicked, remove it from the room directory
if (ev.shiftKey && !this.state.selectedCommunityId) {
ev.preventDefault();
this.removeFromDirectory(room);
- } else {
- this.showRoom(room);
}
};
From a59deeb49165429acc91032280bf8205d2b013e7 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=C5=A0imon=20Brandner?=
Date: Tue, 22 Jun 2021 09:16:45 +0200
Subject: [PATCH 106/164] Add onClick handlers
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Signed-off-by: Šimon Brandner
---
src/components/structures/RoomDirectory.tsx | 19 +++++++++++++++----
1 file changed, 15 insertions(+), 4 deletions(-)
diff --git a/src/components/structures/RoomDirectory.tsx b/src/components/structures/RoomDirectory.tsx
index eb7208f98d..731e2387cc 100644
--- a/src/components/structures/RoomDirectory.tsx
+++ b/src/components/structures/RoomDirectory.tsx
@@ -589,12 +589,23 @@ export default class RoomDirectory extends React.Component {
onMouseDown={(ev) => {ev.preventDefault();}}
className="mx_RoomDirectory_roomDescription"
>
- { name }
- { ev.stopPropagation(); } }
+
this.onRoomClicked(room, ev)}
+ >
+ { name }
+
+
this.onRoomClicked(room, ev)}
dangerouslySetInnerHTML={{ __html: topic }}
/>
-
{ getDisplayAliasForRoom(room) }
+
this.onRoomClicked(room, ev)}
+ >
+ { getDisplayAliasForRoom(room) }
+
,
this.onRoomClicked(room, ev)}
From deb075777d4d59cefff26c98c7149651eb729540 Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Tue, 22 Jun 2021 08:17:09 +0100
Subject: [PATCH 107/164] Upgrade @types/react and @types/react-dom
---
package.json | 4 ++--
yarn.lock | 26 ++++++++++++++++++++------
2 files changed, 22 insertions(+), 8 deletions(-)
diff --git a/package.json b/package.json
index f232d4301b..8ebb90f342 100644
--- a/package.json
+++ b/package.json
@@ -132,8 +132,8 @@
"@types/pako": "^1.0.1",
"@types/parse5": "^6.0.0",
"@types/qrcode": "^1.3.5",
- "@types/react": "^16.9",
- "@types/react-dom": "^16.9.10",
+ "@types/react": "^17.0.2",
+ "@types/react-dom": "^17.0.2",
"@types/react-transition-group": "^4.4.0",
"@types/sanitize-html": "^2.3.1",
"@types/zxcvbn": "^4.4.0",
diff --git a/yarn.lock b/yarn.lock
index 952d08d0f6..4f17b63337 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1620,12 +1620,12 @@
dependencies:
"@types/node" "*"
-"@types/react-dom@^16.9.10":
- version "16.9.10"
- resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-16.9.10.tgz#4485b0bec3d41f856181b717f45fd7831101156f"
- integrity sha512-ItatOrnXDMAYpv6G8UCk2VhbYVTjZT9aorLtA/OzDN9XJ2GKcfam68jutoAcILdRjsRUO8qb7AmyObF77Q8QFw==
+"@types/react-dom@^17.0.2":
+ version "17.0.8"
+ resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-17.0.8.tgz#3180de6d79bf53762001ad854e3ce49f36dd71fc"
+ integrity sha512-0ohAiJAx1DAUEcY9UopnfwCE9sSMDGnY/oXjWMax6g3RpzmTt2GMyMVAXcbn0mo8XAff0SbQJl2/SBU+hjSZ1A==
dependencies:
- "@types/react" "^16"
+ "@types/react" "*"
"@types/react-transition-group@^4.4.0":
version "4.4.0"
@@ -1634,7 +1634,7 @@
dependencies:
"@types/react" "*"
-"@types/react@*", "@types/react@^16", "@types/react@^16.14", "@types/react@^16.9":
+"@types/react@*", "@types/react@^16.14":
version "16.14.2"
resolved "https://registry.yarnpkg.com/@types/react/-/react-16.14.2.tgz#85dcc0947d0645349923c04ccef6018a1ab7538c"
integrity sha512-BzzcAlyDxXl2nANlabtT4thtvbbnhee8hMmH/CcJrISDBVcJS1iOsP1f0OAgSdGE0MsY9tqcrb9YoZcOFv9dbQ==
@@ -1642,6 +1642,15 @@
"@types/prop-types" "*"
csstype "^3.0.2"
+"@types/react@^17.0.2":
+ version "17.0.11"
+ resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.11.tgz#67fcd0ddbf5a0b083a0f94e926c7d63f3b836451"
+ integrity sha512-yFRQbD+whVonItSk7ZzP/L+gPTJVBkL/7shLEF+i9GC/1cV3JmUxEQz6+9ylhUpWSDuqo1N9qEvqS6vTj4USUA==
+ dependencies:
+ "@types/prop-types" "*"
+ "@types/scheduler" "*"
+ csstype "^3.0.2"
+
"@types/sanitize-html@^2.3.1":
version "2.3.1"
resolved "https://registry.yarnpkg.com/@types/sanitize-html/-/sanitize-html-2.3.1.tgz#094d696b83b7394b016e96342bbffa6a028795ce"
@@ -1649,6 +1658,11 @@
dependencies:
htmlparser2 "^6.0.0"
+"@types/scheduler@*":
+ version "0.16.1"
+ resolved "https://registry.yarnpkg.com/@types/scheduler/-/scheduler-0.16.1.tgz#18845205e86ff0038517aab7a18a62a6b9f71275"
+ integrity sha512-EaCxbanVeyxDRTQBkdLb3Bvl/HK7PBK6UJjsSixB0iHKoWxE5uu2Q/DgtpOhPIojN0Zl1whvOd7PoHs2P0s5eA==
+
"@types/stack-utils@^1.0.1":
version "1.0.1"
resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-1.0.1.tgz#0a851d3bd96498fa25c33ab7278ed3bd65f06c3e"
From 066ef18db2eb56561bb5736b58c95b41426cb244 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=C5=A0imon=20Brandner?=
Date: Tue, 22 Jun 2021 09:24:42 +0200
Subject: [PATCH 108/164] Replace onClick by onMouseDown
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Signed-off-by: Šimon Brandner
---
src/components/structures/RoomDirectory.tsx | 45 ++++++++++-----------
1 file changed, 21 insertions(+), 24 deletions(-)
diff --git a/src/components/structures/RoomDirectory.tsx b/src/components/structures/RoomDirectory.tsx
index 731e2387cc..62944a9d98 100644
--- a/src/components/structures/RoomDirectory.tsx
+++ b/src/components/structures/RoomDirectory.tsx
@@ -567,11 +567,11 @@ export default class RoomDirectory extends React.Component {
let avatarUrl = null;
if (room.avatar_url) avatarUrl = mediaFromMxc(room.avatar_url).getSquareThumbnailHttp(32);
+ // We use onMouseDown instead of onClick, so that we can avoid text getting selected
return [
- this.onRoomClicked(room, ev)}
- // cancel onMouseDown otherwise shift-clicking highlights text
- onMouseDown={(ev) => {ev.preventDefault();}}
+
this.onRoomClicked(room, ev)}
className="mx_RoomDirectory_roomAvatar"
>
{
url={avatarUrl}
/>
,
-
this.onRoomClicked(room, ev)}
- // cancel onMouseDown otherwise shift-clicking highlights text
- onMouseDown={(ev) => {ev.preventDefault();}}
+
this.onRoomClicked(room, ev)}
className="mx_RoomDirectory_roomDescription"
>
this.onRoomClicked(room, ev)}
+ onMouseDown={(ev) => this.onRoomClicked(room, ev)}
>
{ name }
this.onRoomClicked(room, ev)}
+ onMouseDown={(ev) => this.onRoomClicked(room, ev)}
dangerouslySetInnerHTML={{ __html: topic }}
/>
this.onRoomClicked(room, ev)}
+ onMouseDown={(ev) => this.onRoomClicked(room, ev)}
>
{ getDisplayAliasForRoom(room) }
,
-
this.onRoomClicked(room, ev)}
- // cancel onMouseDown otherwise shift-clicking highlights text
- onMouseDown={(ev) => {ev.preventDefault();}}
+
this.onRoomClicked(room, ev)}
className="mx_RoomDirectory_roomMemberCount"
>
{ room.num_joined_members }
,
-
this.onRoomClicked(room, ev)}
+
this.onRoomClicked(room, ev)}
// cancel onMouseDown otherwise shift-clicking highlights text
- onMouseDown={(ev) => {ev.preventDefault();}}
className="mx_RoomDirectory_preview"
>
- {previewButton}
+ { previewButton }
,
-
this.onRoomClicked(room, ev)}
- // cancel onMouseDown otherwise shift-clicking highlights text
- onMouseDown={(ev) => {ev.preventDefault();}}
+
this.onRoomClicked(room, ev)}
className="mx_RoomDirectory_join"
>
- {joinOrViewButton}
+ { joinOrViewButton }
,
];
}
From 8090d2b583f33f4baf5bf8b8dbb2ff1c6cdfa1de Mon Sep 17 00:00:00 2001
From: Germain Souquet
Date: Tue, 22 Jun 2021 09:31:15 +0100
Subject: [PATCH 109/164] Fix branch matching for BuildKite
---
scripts/fetchdep.sh | 31 +++++++++++++++++++------------
1 file changed, 19 insertions(+), 12 deletions(-)
diff --git a/scripts/fetchdep.sh b/scripts/fetchdep.sh
index 02af402951..246add7e31 100755
--- a/scripts/fetchdep.sh
+++ b/scripts/fetchdep.sh
@@ -25,18 +25,20 @@ clone() {
# First we check if GITHUB_HEAD_REF is defined,
# Then we check if BUILDKITE_BRANCH is defined,
# if it isn't we can assume this is a Netlify build
-if [ -n ${GITHUB_HEAD_REF+x} ]; then
- head=$GITHUB_HEAD_REF
-elif [ -n ${BUILDKITE_BRANCH+x} ]; then
- head=$BUILDKITE_BRANCH
+if [ -z ${BUILDKITE_BRANCH+x} ]; then
+ if [ -z ${GITHUB_HEAD_REF+x} ]; then
+ # Netlify doesn't give us info about the fork so we have to get it from GitHub API
+ apiEndpoint="https://api.github.com/repos/matrix-org/matrix-react-sdk/pulls/"
+ apiEndpoint+=$REVIEW_ID
+ head=$(curl $apiEndpoint | jq -r '.head.label')
+ else
+ head=$GITHUB_HEAD_REF
+ fi
else
- # Netlify doesn't give us info about the fork so we have to get it from GitHub API
- apiEndpoint="https://api.github.com/repos/matrix-org/matrix-react-sdk/pulls/"
- apiEndpoint+=$REVIEW_ID
- head=$(curl $apiEndpoint | jq -r '.head.label')
+ head=$BUILDKITE_BRANCH
fi
-# If head is set, it will contain on BuilKite either:
+# If head is set, it will contain on BuildKite either:
# * "branch" when the author's branch and target branch are in the same repo
# * "fork:branch" when the author's branch is in their fork or if this is a Netlify build
# We can split on `:` into an array to check.
@@ -44,11 +46,16 @@ fi
# to determine whether the branch is from a fork or not
BRANCH_ARRAY=(${head//:/ })
if [[ "${#BRANCH_ARRAY[@]}" == "1" ]]; then
- if [[ "$GITHUB_REPOSITORY" = "$deforg/$defrepo" ]]; then
- clone $deforg $defrepo $head
+ if [ -z ${BUILDKITE_BRANCH+x} ]; then
+ if [[ "$GITHUB_REPOSITORY" == "$deforg"* ]]; then
+ clone $deforg $defrepo $GITHUB_HEAD_REF
+ else
+ clone $GITHUB_ACTOR $defrepo $GITHUB_HEAD_REF
+ fi
else
- clone $GITHUB_ACTOR $defrepo $head
+ clone $deforg $defrepo $BUILDKITE_BRANCH
fi
+
elif [[ "${#BRANCH_ARRAY[@]}" == "2" ]]; then
clone ${BRANCH_ARRAY[0]} $defrepo ${BRANCH_ARRAY[1]}
fi
From 3c725692704a3cecfe721c011ec4b076e816f491 Mon Sep 17 00:00:00 2001
From: Germain Souquet
Date: Tue, 22 Jun 2021 09:53:58 +0100
Subject: [PATCH 110/164] Fix modal opening race condition
Co-authored-by: Michael Telatynski <7t3chguy@gmail.com>
React 17 is hitting a race condition when a modal is closing and is trying to open another one within the same tick.
A proper long term fix would be using React.createPortal to avoid manually mounting and unmounting new React roots
---
src/Modal.tsx | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/Modal.tsx b/src/Modal.tsx
index ce11c571b6..2f2d5a2d52 100644
--- a/src/Modal.tsx
+++ b/src/Modal.tsx
@@ -385,7 +385,7 @@ export class ModalManager {
);
- ReactDOM.render(dialog, ModalManager.getOrCreateContainer());
+ setImmediate(() => ReactDOM.render(dialog, ModalManager.getOrCreateContainer()));
} else {
// This is safe to call repeatedly if we happen to do that
ReactDOM.unmountComponentAtNode(ModalManager.getOrCreateContainer());
From e9d87478e2c6a4b48658de90fb70862f74ca52bb Mon Sep 17 00:00:00 2001
From: Germain
Date: Tue, 22 Jun 2021 10:06:31 +0100
Subject: [PATCH 111/164] Spaces before/after curlies
Co-authored-by: Michael Telatynski <7t3chguy@gmail.com>
---
src/components/views/toasts/GenericToast.tsx | 4 ++--
src/components/views/voip/IncomingCallBox.tsx | 5 ++---
2 files changed, 4 insertions(+), 5 deletions(-)
diff --git a/src/components/views/toasts/GenericToast.tsx b/src/components/views/toasts/GenericToast.tsx
index ae01e8bfb7..45b65ae1fb 100644
--- a/src/components/views/toasts/GenericToast.tsx
+++ b/src/components/views/toasts/GenericToast.tsx
@@ -51,10 +51,10 @@ const GenericToast: React.FC> = ({
{onReject && rejectLabel &&
- {rejectLabel}
+ { rejectLabel }
}
- {acceptLabel}
+ { acceptLabel }
;
diff --git a/src/components/views/voip/IncomingCallBox.tsx b/src/components/views/voip/IncomingCallBox.tsx
index cd1a3afd10..c09043da24 100644
--- a/src/components/views/voip/IncomingCallBox.tsx
+++ b/src/components/views/voip/IncomingCallBox.tsx
@@ -148,7 +148,7 @@ export default class IncomingCallBox extends React.Component
{
onClick={this.onRejectClick}
kind="danger"
>
- {_t("Decline")}
+ { _t("Decline") }
{
onClick={this.onAnswerClick}
kind="primary"
>
- {_t("Accept")}
+ { _t("Accept") }
;
}
}
-
From db9ffe9b3ebb4c07b645808c3048fe76df9ab8c3 Mon Sep 17 00:00:00 2001
From: Germain Souquet
Date: Tue, 22 Jun 2021 10:18:09 +0100
Subject: [PATCH 112/164] Fix AccessibleButton label for VerificationRequest
---
.../views/right_panel/VerificationPanel.tsx | 14 ++++++++++----
1 file changed, 10 insertions(+), 4 deletions(-)
diff --git a/src/components/views/right_panel/VerificationPanel.tsx b/src/components/views/right_panel/VerificationPanel.tsx
index ce39141391..d3f2ba8cbf 100644
--- a/src/components/views/right_panel/VerificationPanel.tsx
+++ b/src/components/views/right_panel/VerificationPanel.tsx
@@ -209,13 +209,19 @@ export default class VerificationPanel extends React.PureComponent
+ onClick={this.onReciprocateNoClick}
+ >
+ { _t("No") }
+
+ onClick={this.onReciprocateYesClick}
+ >
+ { _t("Yes") }
+
;
} else {
From 3d3c4284555ab95a9bc798844c16db517b0f111b Mon Sep 17 00:00:00 2001
From: Germain Souquet
Date: Tue, 22 Jun 2021 10:26:49 +0100
Subject: [PATCH 113/164] Fix DesktopBuildsNotice return type
---
src/components/structures/RoomView.tsx | 1 -
.../views/elements/DesktopBuildsNotice.tsx | 23 +++++++++++--------
src/components/views/rooms/SearchBar.tsx | 20 ++++++++--------
3 files changed, 23 insertions(+), 21 deletions(-)
diff --git a/src/components/structures/RoomView.tsx b/src/components/structures/RoomView.tsx
index c1dcb81e08..a4338e832a 100644
--- a/src/components/structures/RoomView.tsx
+++ b/src/components/structures/RoomView.tsx
@@ -82,7 +82,6 @@ import SpaceRoomView from "./SpaceRoomView";
import { IOpts } from "../../createRoom";
import { replaceableComponent } from "../../utils/replaceableComponent";
import UIStore from "../../stores/UIStore";
-import Search from '../views/emojipicker/Search';
const DEBUG = false;
let debuglog = function(msg: string) {};
diff --git a/src/components/views/elements/DesktopBuildsNotice.tsx b/src/components/views/elements/DesktopBuildsNotice.tsx
index e5e94d4bd4..426554f31e 100644
--- a/src/components/views/elements/DesktopBuildsNotice.tsx
+++ b/src/components/views/elements/DesktopBuildsNotice.tsx
@@ -18,7 +18,6 @@ import React from "react";
import EventIndexPeg from "../../../indexing/EventIndexPeg";
import { _t } from "../../../languageHandler";
import SdkConfig from "../../../SdkConfig";
-
import dis from "../../../dispatcher/dispatcher";
import { Action } from "../../../dispatcher/actions";
import { UserTab } from "../dialogs/UserSettingsDialog";
@@ -39,15 +38,19 @@ export default function DesktopBuildsNotice({isRoomEncrypted, kind}: IProps) {
if (EventIndexPeg.get()) return null;
if (EventIndexPeg.error) {
- return _t("Message search initialisation failed, check your settings for more information", {}, {
- a: sub => ( {
- evt.preventDefault();
- dis.dispatch({
- action: Action.ViewUserSettings,
- initialTabId: UserTab.Security,
- });
- }}>{sub} ),
- });
+ return <>
+ {_t("Message search initialisation failed, check your settings for more information", {}, {
+ a: sub => ( {
+ evt.preventDefault();
+ dis.dispatch({
+ action: Action.ViewUserSettings,
+ initialTabId: UserTab.Security,
+ });
+ }}>
+ {sub}
+ ),
+ })}
+ >;
}
const {desktopBuilds, brand} = SdkConfig.get();
diff --git a/src/components/views/rooms/SearchBar.tsx b/src/components/views/rooms/SearchBar.tsx
index de99305d81..47994f5251 100644
--- a/src/components/views/rooms/SearchBar.tsx
+++ b/src/components/views/rooms/SearchBar.tsx
@@ -15,13 +15,13 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
-import React, {createRef, RefObject} from 'react';
+import React, { createRef, RefObject } from 'react';
import AccessibleButton from "../elements/AccessibleButton";
import classNames from "classnames";
import { _t } from '../../../languageHandler';
import {Key} from "../../../Keyboard";
import DesktopBuildsNotice, {WarningKind} from "../elements/DesktopBuildsNotice";
-import {replaceableComponent} from "../../../utils/replaceableComponent";
+import { replaceableComponent } from "../../../utils/replaceableComponent";
interface IProps {
onCancelClick: () => void;
@@ -50,15 +50,15 @@ export default class SearchBar extends React.Component {
};
}
- public onThisRoomClick = () => {
- this.setState({ scope: SearchScope.Room }, () => this._searchIfQuery());
+ private onThisRoomClick = () => {
+ this.setState({ scope: SearchScope.Room }, () => this.searchIfQuery());
};
- public onAllRoomsClick = () => {
- this.setState({ scope: SearchScope.All }, () => this._searchIfQuery());
+ private onAllRoomsClick = () => {
+ this.setState({ scope: SearchScope.All }, () => this.searchIfQuery());
};
- public onSearchChange = (e: React.KeyboardEvent) => {
+ private onSearchChange = (e: React.KeyboardEvent) => {
switch (e.key) {
case Key.ENTER:
this.onSearch();
@@ -69,17 +69,17 @@ export default class SearchBar extends React.Component {
}
};
- _searchIfQuery() {
+ private searchIfQuery(): void {
if (this.searchTerm.current.value) {
this.onSearch();
}
}
- onSearch = () => {
+ private onSearch = (): void => {
this.props.onSearch(this.searchTerm.current.value, this.state.scope);
};
- render() {
+ public render() {
const searchButtonClasses = classNames("mx_SearchBar_searchButton", {
mx_SearchBar_searching: this.props.searchInProgress,
});
From dd58c9f413abd9a4c654e19c675f54864f24143b Mon Sep 17 00:00:00 2001
From: Germain Souquet
Date: Tue, 22 Jun 2021 10:52:33 +0100
Subject: [PATCH 114/164] Add TruncatedList in AddExistingToSpaceDialog
---
.../dialogs/AddExistingToSpaceDialog.tsx | 39 ++++++++++++++-----
1 file changed, 29 insertions(+), 10 deletions(-)
diff --git a/src/components/views/dialogs/AddExistingToSpaceDialog.tsx b/src/components/views/dialogs/AddExistingToSpaceDialog.tsx
index 822ffc2827..8997e4a5f8 100644
--- a/src/components/views/dialogs/AddExistingToSpaceDialog.tsx
+++ b/src/components/views/dialogs/AddExistingToSpaceDialog.tsx
@@ -39,6 +39,9 @@ import ProgressBar from "../elements/ProgressBar";
import {SpaceFeedbackPrompt} from "../../structures/SpaceRoomView";
import DecoratedRoomAvatar from "../avatars/DecoratedRoomAvatar";
import QueryMatcher from "../../../autocomplete/QueryMatcher";
+import TruncatedList from "../elements/TruncatedList";
+import EntityTile from "../rooms/EntityTile";
+import BaseAvatar from "../avatars/BaseAvatar";
interface IProps extends IDialogProps {
matrixClient: MatrixClient;
@@ -204,6 +207,17 @@ export const AddExistingToSpace: React.FC = ({
setSelectedToAdd(new Set(selectedToAdd));
} : null;
+ const [truncateAt, setTruncateAt] = useState(20);
+ function overflowTile(overflowCount, totalCount) {
+ const text = _t("and %(count)s others...", { count: overflowCount });
+ return (
+
+ } name={text} presenceState="online" suppressOnHover={true}
+ onClick={() => setTruncateAt(totalCount)} />
+ );
+ }
+
return
= ({
{ rooms.length > 0 ? (
{ _t("Rooms") }
- { rooms.map(room => {
- return {
- onChange(checked, room);
- } : null}
- />;
- }) }
+ rooms.slice(start, end).map(room =>
+ {
+ onChange(checked, room);
+ } : null}
+ />,
+ )}
+ getChildCount={() => rooms.length}
+ />
) : undefined }
From 66b3feb802b913b9500ee38c8807f2535eb4ec99 Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Tue, 22 Jun 2021 11:50:00 +0100
Subject: [PATCH 115/164] Fix keyboard accessibility of the space panel
---
.../views/elements/AccessibleButton.tsx | 6 +
.../views/spaces/SpaceTreeLevel.tsx | 145 +++++++++++-------
2 files changed, 98 insertions(+), 53 deletions(-)
diff --git a/src/components/views/elements/AccessibleButton.tsx b/src/components/views/elements/AccessibleButton.tsx
index e634057a21..05bcca24b2 100644
--- a/src/components/views/elements/AccessibleButton.tsx
+++ b/src/components/views/elements/AccessibleButton.tsx
@@ -62,6 +62,8 @@ export default function AccessibleButton({
disabled,
inputRef,
className,
+ onKeyDown,
+ onKeyUp,
...restProps
}: IProps) {
const newProps: IAccessibleButtonProps = restProps;
@@ -83,6 +85,8 @@ export default function AccessibleButton({
if (e.key === Key.SPACE) {
e.stopPropagation();
e.preventDefault();
+ } else {
+ onKeyDown?.(e);
}
};
newProps.onKeyUp = (e) => {
@@ -94,6 +98,8 @@ export default function AccessibleButton({
if (e.key === Key.ENTER) {
e.stopPropagation();
e.preventDefault();
+ } else {
+ onKeyUp?.(e);
}
};
}
diff --git a/src/components/views/spaces/SpaceTreeLevel.tsx b/src/components/views/spaces/SpaceTreeLevel.tsx
index f34baf256b..b3577e436a 100644
--- a/src/components/views/spaces/SpaceTreeLevel.tsx
+++ b/src/components/views/spaces/SpaceTreeLevel.tsx
@@ -14,23 +14,22 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
-import React from "react";
+import React, { createRef } from "react";
import classNames from "classnames";
-import {Room} from "matrix-js-sdk/src/models/room";
+import { Room } from "matrix-js-sdk/src/models/room";
import RoomAvatar from "../avatars/RoomAvatar";
import SpaceStore from "../../../stores/SpaceStore";
import SpaceTreeLevelLayoutStore from "../../../stores/SpaceTreeLevelLayoutStore";
import NotificationBadge from "../rooms/NotificationBadge";
-import {RovingAccessibleButton} from "../../../accessibility/roving/RovingAccessibleButton";
-import {RovingAccessibleTooltipButton} from "../../../accessibility/roving/RovingAccessibleTooltipButton";
+import { RovingAccessibleTooltipButton } from "../../../accessibility/roving/RovingAccessibleTooltipButton";
import IconizedContextMenu, {
IconizedContextMenuOption,
IconizedContextMenuOptionList,
} from "../context_menus/IconizedContextMenu";
-import {_t} from "../../../languageHandler";
-import {ContextMenuTooltipButton} from "../../../accessibility/context_menu/ContextMenuTooltipButton";
-import {toRightOf} from "../../structures/ContextMenu";
+import { _t } from "../../../languageHandler";
+import { ContextMenuTooltipButton } from "../../../accessibility/context_menu/ContextMenuTooltipButton";
+import { toRightOf } from "../../structures/ContextMenu";
import {
shouldShowSpaceSettings,
showAddExistingRooms,
@@ -39,15 +38,16 @@ import {
showSpaceSettings,
} from "../../../utils/space";
import MatrixClientContext from "../../../contexts/MatrixClientContext";
-import AccessibleButton, {ButtonEvent} from "../elements/AccessibleButton";
+import AccessibleButton, { ButtonEvent } from "../elements/AccessibleButton";
import defaultDispatcher from "../../../dispatcher/dispatcher";
-import {Action} from "../../../dispatcher/actions";
+import { Action } from "../../../dispatcher/actions";
import RoomViewStore from "../../../stores/RoomViewStore";
-import {SetRightPanelPhasePayload} from "../../../dispatcher/payloads/SetRightPanelPhasePayload";
-import {RightPanelPhases} from "../../../stores/RightPanelStorePhases";
-import {EventType} from "matrix-js-sdk/src/@types/event";
-import {StaticNotificationState} from "../../../stores/notifications/StaticNotificationState";
-import {NotificationColor} from "../../../stores/notifications/NotificationColor";
+import { SetRightPanelPhasePayload } from "../../../dispatcher/payloads/SetRightPanelPhasePayload";
+import { RightPanelPhases } from "../../../stores/RightPanelStorePhases";
+import { EventType } from "matrix-js-sdk/src/@types/event";
+import { StaticNotificationState } from "../../../stores/notifications/StaticNotificationState";
+import { NotificationColor } from "../../../stores/notifications/NotificationColor";
+import { getKeyBindingsManager, RoomListAction } from "../../../KeyBindingsManager";
interface IItemProps {
space?: Room;
@@ -61,11 +61,14 @@ interface IItemProps {
interface IItemState {
collapsed: boolean;
contextMenuPosition: Pick;
+ childSpaces: Room[];
}
export class SpaceItem extends React.PureComponent {
static contextType = MatrixClientContext;
+ private buttonRef = createRef();
+
constructor(props) {
super(props);
@@ -78,14 +81,36 @@ export class SpaceItem extends React.PureComponent {
this.state = {
collapsed: collapsed,
contextMenuPosition: null,
+ childSpaces: this.childSpaces,
};
+
+ SpaceStore.instance.on(this.props.space.roomId, this.onSpaceUpdate);
}
- private toggleCollapse(evt) {
- if (this.props.onExpand && this.state.collapsed) {
+ componentWillUnmount() {
+ SpaceStore.instance.off(this.props.space.roomId, this.onSpaceUpdate);
+ }
+
+ private onSpaceUpdate = () => {
+ this.setState({
+ childSpaces: this.childSpaces,
+ });
+ };
+
+ private get childSpaces() {
+ return SpaceStore.instance.getChildSpaces(this.props.space.roomId)
+ .filter(s => !this.props.parents?.has(s.roomId));
+ }
+
+ private get isCollapsed() {
+ return this.state.collapsed || this.props.isPanelCollapsed;
+ }
+
+ private toggleCollapse = evt => {
+ if (this.props.onExpand && this.isCollapsed) {
this.props.onExpand();
}
- const newCollapsedState = !this.state.collapsed;
+ const newCollapsedState = !this.isCollapsed;
SpaceTreeLevelLayoutStore.instance.setSpaceCollapsedState(
this.props.space.roomId,
@@ -96,7 +121,7 @@ export class SpaceItem extends React.PureComponent {
// don't bubble up so encapsulating button for space
// doesn't get triggered
evt.stopPropagation();
- }
+ };
private onContextMenu = (ev: React.MouseEvent) => {
if (this.props.space.getMyMembership() !== "join") return;
@@ -111,6 +136,43 @@ export class SpaceItem extends React.PureComponent {
});
}
+ private onKeyDown = (ev: React.KeyboardEvent) => {
+ let handled = true;
+ const action = getKeyBindingsManager().getRoomListAction(ev);
+ const hasChildren = this.state.childSpaces?.length;
+ switch (action) {
+ case RoomListAction.CollapseSection:
+ if (hasChildren && !this.isCollapsed) {
+ this.toggleCollapse(ev);
+ } else {
+ const parentItem = this.buttonRef?.current?.parentElement?.parentElement;
+ const parentButton = parentItem?.previousElementSibling as HTMLElement;
+ parentButton?.focus();
+ }
+ break;
+
+ case RoomListAction.ExpandSection:
+ if (hasChildren) {
+ if (this.isCollapsed) {
+ this.toggleCollapse(ev);
+ } else {
+ const childLevel = this.buttonRef?.current?.nextElementSibling;
+ const firstSpaceItemChild = childLevel?.querySelector(".mx_SpaceItem");
+ firstSpaceItemChild?.querySelector(".mx_SpaceButton")?.focus();
+ }
+ }
+ break;
+
+ default:
+ handled = false;
+ }
+
+ if (handled) {
+ ev.stopPropagation();
+ ev.preventDefault();
+ }
+ };
+
private onClick = (ev: React.MouseEvent) => {
ev.preventDefault();
ev.stopPropagation();
@@ -302,18 +364,15 @@ export class SpaceItem extends React.PureComponent {
render() {
const {space, activeSpaces, isNested} = this.props;
- const forceCollapsed = this.props.isPanelCollapsed;
const isNarrow = this.props.isPanelCollapsed;
- const collapsed = this.state.collapsed || forceCollapsed;
+ const collapsed = this.isCollapsed;
- const childSpaces = SpaceStore.instance.getChildSpaces(space.roomId)
- .filter(s => !this.props.parents?.has(s.roomId));
const isActive = activeSpaces.includes(space);
const itemClasses = classNames({
"mx_SpaceItem": true,
"mx_SpaceItem_narrow": isNarrow,
"collapsed": collapsed,
- "hasSubSpaces": childSpaces && childSpaces.length,
+ "hasSubSpaces": this.state.childSpaces?.length,
});
const isInvite = space.getMyMembership() === "invite";
@@ -328,9 +387,9 @@ export class SpaceItem extends React.PureComponent {
: SpaceStore.instance.getNotificationState(space.roomId);
let childItems;
- if (childSpaces && !collapsed) {
+ if (this.state.childSpaces?.length && !collapsed) {
childItems = {
const avatarSize = isNested ? 24 : 32;
- const toggleCollapseButton = childSpaces && childSpaces.length ?
+ const toggleCollapseButton = this.state.childSpaces?.length ?
this.toggleCollapse(evt)}
+ onClick={this.toggleCollapse}
/> : null;
- let button;
- if (isNarrow) {
- button = (
+ return (
+
{ toggleCollapseButton }
+ { !isNarrow && { space.name } }
{ notifBadge }
{ this.renderContextMenu() }
- );
- } else {
- button = (
-
- { toggleCollapseButton }
-
-
- { space.name }
- { notifBadge }
- { this.renderContextMenu() }
-
-
- );
- }
- return (
-
- { button }
{ childItems }
);
From 1f0fdb95cd2f52731cc1ca2253a249a00fe8a83c Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Tue, 22 Jun 2021 11:59:04 +0100
Subject: [PATCH 116/164] Improve accessibility of subspaces in the space panel
---
src/components/views/spaces/SpaceTreeLevel.tsx | 3 +++
src/i18n/strings/en_EN.json | 2 ++
2 files changed, 5 insertions(+)
diff --git a/src/components/views/spaces/SpaceTreeLevel.tsx b/src/components/views/spaces/SpaceTreeLevel.tsx
index b3577e436a..cbc1cab86b 100644
--- a/src/components/views/spaces/SpaceTreeLevel.tsx
+++ b/src/components/views/spaces/SpaceTreeLevel.tsx
@@ -409,6 +409,8 @@ export class SpaceItem extends React.PureComponent {
: null;
return (
@@ -420,6 +422,7 @@ export class SpaceItem extends React.PureComponent {
onContextMenu={this.onContextMenu}
forceHide={!isNarrow || !!this.state.contextMenuPosition}
role="treeitem"
+ aria-expanded={!collapsed}
inputRef={this.buttonRef}
onKeyDown={this.onKeyDown}
>
diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json
index b88dc79da5..a2fb93dc17 100644
--- a/src/i18n/strings/en_EN.json
+++ b/src/i18n/strings/en_EN.json
@@ -1038,6 +1038,8 @@
"Manage & explore rooms": "Manage & explore rooms",
"Explore rooms": "Explore rooms",
"Space options": "Space options",
+ "Expand": "Expand",
+ "Collapse": "Collapse",
"Remove": "Remove",
"This bridge was provisioned by .": "This bridge was provisioned by .",
"This bridge is managed by .": "This bridge is managed by .",
From a7daf558bb8f911eaedbf96cd7b8564869624e92 Mon Sep 17 00:00:00 2001
From: Germain
Date: Tue, 22 Jun 2021 13:03:55 +0100
Subject: [PATCH 117/164] Use proper capitalisation for Buildkite
Co-authored-by: J. Ryan Stinnett
---
scripts/fetchdep.sh | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/scripts/fetchdep.sh b/scripts/fetchdep.sh
index 246add7e31..c7d8daeda5 100755
--- a/scripts/fetchdep.sh
+++ b/scripts/fetchdep.sh
@@ -38,7 +38,7 @@ else
head=$BUILDKITE_BRANCH
fi
-# If head is set, it will contain on BuildKite either:
+# If head is set, it will contain on Buildkite either:
# * "branch" when the author's branch and target branch are in the same repo
# * "fork:branch" when the author's branch is in their fork or if this is a Netlify build
# We can split on `:` into an array to check.
From 660f3900f8a1369a1dabea3d8e273a5c4861e673 Mon Sep 17 00:00:00 2001
From: Germain Souquet
Date: Tue, 22 Jun 2021 14:11:41 +0100
Subject: [PATCH 118/164] Change if statement syntax to use positive
expressions
---
scripts/fetchdep.sh | 25 ++++++++++++-------------
1 file changed, 12 insertions(+), 13 deletions(-)
diff --git a/scripts/fetchdep.sh b/scripts/fetchdep.sh
index c7d8daeda5..55f068e49d 100755
--- a/scripts/fetchdep.sh
+++ b/scripts/fetchdep.sh
@@ -25,17 +25,15 @@ clone() {
# First we check if GITHUB_HEAD_REF is defined,
# Then we check if BUILDKITE_BRANCH is defined,
# if it isn't we can assume this is a Netlify build
-if [ -z ${BUILDKITE_BRANCH+x} ]; then
- if [ -z ${GITHUB_HEAD_REF+x} ]; then
- # Netlify doesn't give us info about the fork so we have to get it from GitHub API
- apiEndpoint="https://api.github.com/repos/matrix-org/matrix-react-sdk/pulls/"
- apiEndpoint+=$REVIEW_ID
- head=$(curl $apiEndpoint | jq -r '.head.label')
- else
- head=$GITHUB_HEAD_REF
- fi
-else
+if [ -n "$BUILDKITE_BRANCH" ]; then
head=$BUILDKITE_BRANCH
+elif [ -n "$GITHUB_HEAD_REF" ]; then
+ head=$GITHUB_HEAD_REF
+else
+ # Netlify doesn't give us info about the fork so we have to get it from GitHub API
+ apiEndpoint="https://api.github.com/repos/matrix-org/matrix-react-sdk/pulls/"
+ apiEndpoint+=$REVIEW_ID
+ head=$(curl $apiEndpoint | jq -r '.head.label')
fi
# If head is set, it will contain on Buildkite either:
@@ -46,7 +44,8 @@ fi
# to determine whether the branch is from a fork or not
BRANCH_ARRAY=(${head//:/ })
if [[ "${#BRANCH_ARRAY[@]}" == "1" ]]; then
- if [ -z ${BUILDKITE_BRANCH+x} ]; then
+
+ if [ -n "$GITHUB_HEAD_REF" ]; then
if [[ "$GITHUB_REPOSITORY" == "$deforg"* ]]; then
clone $deforg $defrepo $GITHUB_HEAD_REF
else
@@ -61,9 +60,9 @@ elif [[ "${#BRANCH_ARRAY[@]}" == "2" ]]; then
fi
# Try the target branch of the push or PR.
-if [ -n ${GITHUB_BASE_REF+x} ]; then
+if [ -n $GITHUB_BASE_REF ]; then
clone $deforg $defrepo $GITHUB_BASE_REF
-elif [ -n ${BUILDKITE_PULL_REQUEST_BASE_BRANCH+x} ]; then
+elif [ -n $BUILDKITE_PULL_REQUEST_BASE_BRANCH ]; then
clone $deforg $defrepo $BUILDKITE_PULL_REQUEST_BASE_BRANCH
fi
From c42f0fd2e4774c60283e33c0233f93993604184f Mon Sep 17 00:00:00 2001
From: Germain Souquet
Date: Tue, 22 Jun 2021 14:17:11 +0100
Subject: [PATCH 119/164] split GITHUB_REPOSITORY rather than using
GITHUB_ACTOR
---
scripts/fetchdep.sh | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/scripts/fetchdep.sh b/scripts/fetchdep.sh
index 55f068e49d..7d893a6039 100755
--- a/scripts/fetchdep.sh
+++ b/scripts/fetchdep.sh
@@ -49,7 +49,8 @@ if [[ "${#BRANCH_ARRAY[@]}" == "1" ]]; then
if [[ "$GITHUB_REPOSITORY" == "$deforg"* ]]; then
clone $deforg $defrepo $GITHUB_HEAD_REF
else
- clone $GITHUB_ACTOR $defrepo $GITHUB_HEAD_REF
+ REPO_ARRAY=(${GITHUB_REPOSITORY//\// })
+ clone $REPO_ARRAY[0] $defrepo $GITHUB_HEAD_REF
fi
else
clone $deforg $defrepo $BUILDKITE_BRANCH
From ded738ce8c820cffb9b2a49f659e5febf130e4f1 Mon Sep 17 00:00:00 2001
From: Germain
Date: Tue, 22 Jun 2021 14:57:44 +0100
Subject: [PATCH 120/164] Add spaces around curlies
Co-authored-by: Michael Telatynski <7t3chguy@gmail.com>
---
src/components/views/rooms/SearchBar.tsx | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/src/components/views/rooms/SearchBar.tsx b/src/components/views/rooms/SearchBar.tsx
index 47994f5251..d71bb8da73 100644
--- a/src/components/views/rooms/SearchBar.tsx
+++ b/src/components/views/rooms/SearchBar.tsx
@@ -19,8 +19,8 @@ import React, { createRef, RefObject } from 'react';
import AccessibleButton from "../elements/AccessibleButton";
import classNames from "classnames";
import { _t } from '../../../languageHandler';
-import {Key} from "../../../Keyboard";
-import DesktopBuildsNotice, {WarningKind} from "../elements/DesktopBuildsNotice";
+import { Key } from "../../../Keyboard";
+import DesktopBuildsNotice, { WarningKind } from "../elements/DesktopBuildsNotice";
import { replaceableComponent } from "../../../utils/replaceableComponent";
interface IProps {
@@ -95,7 +95,7 @@ export default class SearchBar extends React.Component {
{
{_t("This Room")}
Date: Tue, 22 Jun 2021 15:29:53 +0100
Subject: [PATCH 121/164] remove spurious full stop
---
src/i18n/strings/en_EN.json | 2 +-
src/settings/Settings.tsx | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json
index 179b58b617..a9a0d15ac4 100644
--- a/src/i18n/strings/en_EN.json
+++ b/src/i18n/strings/en_EN.json
@@ -796,7 +796,7 @@
"Show all rooms in Home": "Show all rooms in Home",
"Show people in spaces": "Show people in spaces",
"If disabled, you can still add Direct Messages to Personal Spaces. If enabled, you'll automatically see everyone who is a member of the Space.": "If disabled, you can still add Direct Messages to Personal Spaces. If enabled, you'll automatically see everyone who is a member of the Space.",
- "Show notification badges for DMs in Spaces.": "Show notification badges for DMs in Spaces.",
+ "Show notification badges for DMs in Spaces": "Show notification badges for DMs in Spaces",
"Show options to enable 'Do not disturb' mode": "Show options to enable 'Do not disturb' mode",
"Send and receive voice messages": "Send and receive voice messages",
"Render LaTeX maths in messages": "Render LaTeX maths in messages",
diff --git a/src/settings/Settings.tsx b/src/settings/Settings.tsx
index af026f4103..3937b7d821 100644
--- a/src/settings/Settings.tsx
+++ b/src/settings/Settings.tsx
@@ -193,7 +193,7 @@ export const SETTINGS: {[setting: string]: ISetting} = {
controller: new ReloadOnChangeController(),
},
"feature_spaces.space_dm_badges": {
- displayName: _td("Show notification badges for DMs in Spaces."),
+ displayName: _td("Show notification badges for DMs in Spaces"),
supportedLevels: LEVELS_FEATURE,
default: false,
controller: new ReloadOnChangeController(),
From fca2feaae8ad83cdef7eb1d426873e33bc3e9c00 Mon Sep 17 00:00:00 2001
From: Germain Souquet
Date: Tue, 22 Jun 2021 16:09:33 +0100
Subject: [PATCH 122/164] make github env variable check first as it is new
home for ci
---
scripts/fetchdep.sh | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/scripts/fetchdep.sh b/scripts/fetchdep.sh
index 7d893a6039..0b15db6a23 100755
--- a/scripts/fetchdep.sh
+++ b/scripts/fetchdep.sh
@@ -25,10 +25,10 @@ clone() {
# First we check if GITHUB_HEAD_REF is defined,
# Then we check if BUILDKITE_BRANCH is defined,
# if it isn't we can assume this is a Netlify build
-if [ -n "$BUILDKITE_BRANCH" ]; then
- head=$BUILDKITE_BRANCH
-elif [ -n "$GITHUB_HEAD_REF" ]; then
+if [ -n "$GITHUB_HEAD_REF" ]; then
head=$GITHUB_HEAD_REF
+elif [ -n "$BUILDKITE_BRANCH" ]; then
+ head=$BUILDKITE_BRANCH
else
# Netlify doesn't give us info about the fork so we have to get it from GitHub API
apiEndpoint="https://api.github.com/repos/matrix-org/matrix-react-sdk/pulls/"
From b092686453604cb37df602e6fcc796418688c022 Mon Sep 17 00:00:00 2001
From: Germain Souquet
Date: Tue, 22 Jun 2021 16:14:01 +0100
Subject: [PATCH 123/164] improve comment grammar
---
scripts/fetchdep.sh | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/scripts/fetchdep.sh b/scripts/fetchdep.sh
index 0b15db6a23..0990af70ce 100755
--- a/scripts/fetchdep.sh
+++ b/scripts/fetchdep.sh
@@ -24,7 +24,7 @@ clone() {
# Try the PR author's branch in case it exists on the deps as well.
# First we check if GITHUB_HEAD_REF is defined,
# Then we check if BUILDKITE_BRANCH is defined,
-# if it isn't we can assume this is a Netlify build
+# if they aren't we can assume this is a Netlify build
if [ -n "$GITHUB_HEAD_REF" ]; then
head=$GITHUB_HEAD_REF
elif [ -n "$BUILDKITE_BRANCH" ]; then
From 6d92953311ed49567a0dad80e2ff81df9cbcd417 Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Tue, 22 Jun 2021 17:22:38 +0100
Subject: [PATCH 124/164] Convert bunch of utils to TS
---
...tateTransfer.js => EditorStateTransfer.ts} | 30 ++++----
src/utils/{ErrorUtils.js => ErrorUtils.tsx} | 16 +++--
src/utils/{EventUtils.js => EventUtils.ts} | 17 +++--
...yServerUtils.js => IdentityServerUtils.ts} | 11 +--
...ssageDiffUtils.js => MessageDiffUtils.tsx} | 68 ++++++++++---------
.../{PinningUtils.js => PinningUtils.ts} | 4 +-
src/utils/{Receipt.js => Receipt.ts} | 6 +-
.../{ResizeNotifier.js => ResizeNotifier.ts} | 45 ++++++------
...eMatrixClient.js => createMatrixClient.ts} | 21 +++---
9 files changed, 120 insertions(+), 98 deletions(-)
rename src/utils/{EditorStateTransfer.js => EditorStateTransfer.ts} (58%)
rename src/utils/{ErrorUtils.js => ErrorUtils.tsx} (84%)
rename src/utils/{EventUtils.js => EventUtils.ts} (85%)
rename src/utils/{IdentityServerUtils.js => IdentityServerUtils.ts} (82%)
rename src/utils/{MessageDiffUtils.js => MessageDiffUtils.tsx} (84%)
rename src/utils/{PinningUtils.js => PinningUtils.ts} (89%)
rename src/utils/{Receipt.js => Receipt.ts} (83%)
rename src/utils/{ResizeNotifier.js => ResizeNotifier.ts} (62%)
rename src/utils/{createMatrixClient.js => createMatrixClient.ts} (76%)
diff --git a/src/utils/EditorStateTransfer.js b/src/utils/EditorStateTransfer.ts
similarity index 58%
rename from src/utils/EditorStateTransfer.js
rename to src/utils/EditorStateTransfer.ts
index c7782a9ea8..42e1a316d6 100644
--- a/src/utils/EditorStateTransfer.js
+++ b/src/utils/EditorStateTransfer.ts
@@ -1,5 +1,5 @@
/*
-Copyright 2019 The Matrix.org Foundation C.I.C.
+Copyright 2019 - 2021 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -14,36 +14,40 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
+import { MatrixEvent } from "matrix-js-sdk/src/models/event";
+
+import { SerializedPart } from "../editor/parts";
+import { Caret } from "../editor/caret";
+
/**
* Used while editing, to pass the event, and to preserve editor state
* from one editor instance to another when remounting the editor
* upon receiving the remote echo for an unsent event.
*/
export default class EditorStateTransfer {
- constructor(event) {
- this._event = event;
- this._serializedParts = null;
- this.caret = null;
- }
+ private serializedParts: SerializedPart[] = null;
+ private caret: Caret = null;
- setEditorState(caret, serializedParts) {
- this._caret = caret;
- this._serializedParts = serializedParts;
+ constructor(private readonly event: MatrixEvent) {}
+
+ setEditorState(caret: Caret, serializedParts: SerializedPart[]) {
+ this.caret = caret;
+ this.serializedParts = serializedParts;
}
hasEditorState() {
- return !!this._serializedParts;
+ return !!this.serializedParts;
}
getSerializedParts() {
- return this._serializedParts;
+ return this.serializedParts;
}
getCaret() {
- return this._caret;
+ return this.caret;
}
getEvent() {
- return this._event;
+ return this.event;
}
}
diff --git a/src/utils/ErrorUtils.js b/src/utils/ErrorUtils.tsx
similarity index 84%
rename from src/utils/ErrorUtils.js
rename to src/utils/ErrorUtils.tsx
index b5bd5b0af0..c39ee21f09 100644
--- a/src/utils/ErrorUtils.js
+++ b/src/utils/ErrorUtils.tsx
@@ -1,5 +1,5 @@
/*
-Copyright 2018 New Vector Ltd
+Copyright 2018 - 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.
@@ -14,7 +14,10 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
-import { _t, _td } from '../languageHandler';
+import React, { ReactNode } from "react";
+import { MatrixError } from "matrix-js-sdk/src/http-api";
+
+import { _t, _td, Tags, TranslatedString } from '../languageHandler';
/**
* Produce a translated error message for a
@@ -30,7 +33,12 @@ import { _t, _td } from '../languageHandler';
* for any tags in the strings apart from 'a'
* @returns {*} Translated string or react component
*/
-export function messageForResourceLimitError(limitType, adminContact, strings, extraTranslations) {
+export function messageForResourceLimitError(
+ limitType: string,
+ adminContact: string,
+ strings: Record,
+ extraTranslations?: Tags,
+): TranslatedString {
let errString = strings[limitType];
if (errString === undefined) errString = strings[''];
@@ -49,7 +57,7 @@ export function messageForResourceLimitError(limitType, adminContact, strings, e
}
}
-export function messageForSyncError(err) {
+export function messageForSyncError(err: MatrixError | Error): ReactNode {
if (err.errcode === 'M_RESOURCE_LIMIT_EXCEEDED') {
const limitError = messageForResourceLimitError(
err.data.limit_type,
diff --git a/src/utils/EventUtils.js b/src/utils/EventUtils.ts
similarity index 85%
rename from src/utils/EventUtils.js
rename to src/utils/EventUtils.ts
index be21896417..3d9c60d9cd 100644
--- a/src/utils/EventUtils.js
+++ b/src/utils/EventUtils.ts
@@ -1,5 +1,5 @@
/*
-Copyright 2019 New Vector Ltd
+Copyright 2019 - 2021 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -14,9 +14,12 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
-import { EventStatus } from 'matrix-js-sdk/src/models/event';
-import {MatrixClientPeg} from '../MatrixClientPeg';
+import { Room } from 'matrix-js-sdk/src/models/room';
+import { MatrixEvent, EventStatus } from 'matrix-js-sdk/src/models/event';
+
+import { MatrixClientPeg } from '../MatrixClientPeg';
import shouldHideEvent from "../shouldHideEvent";
+
/**
* Returns whether an event should allow actions like reply, reactions, edit, etc.
* which effectively checks whether it's a regular message that has been sent and that we
@@ -25,7 +28,7 @@ import shouldHideEvent from "../shouldHideEvent";
* @param {MatrixEvent} mxEvent The event to check
* @returns {boolean} true if actionable
*/
-export function isContentActionable(mxEvent) {
+export function isContentActionable(mxEvent: MatrixEvent): boolean {
const { status: eventStatus } = mxEvent;
// status is SENT before remote-echo, null after
@@ -45,7 +48,7 @@ export function isContentActionable(mxEvent) {
return false;
}
-export function canEditContent(mxEvent) {
+export function canEditContent(mxEvent: MatrixEvent): boolean {
if (mxEvent.status === EventStatus.CANCELLED || mxEvent.getType() !== "m.room.message" || mxEvent.isRedacted()) {
return false;
}
@@ -56,7 +59,7 @@ export function canEditContent(mxEvent) {
mxEvent.getSender() === MatrixClientPeg.get().getUserId();
}
-export function canEditOwnEvent(mxEvent) {
+export function canEditOwnEvent(mxEvent: MatrixEvent): boolean {
// for now we only allow editing
// your own events. So this just call through
// In the future though, moderators will be able to
@@ -67,7 +70,7 @@ export function canEditOwnEvent(mxEvent) {
}
const MAX_JUMP_DISTANCE = 100;
-export function findEditableEvent(room, isForward, fromEventId = undefined) {
+export function findEditableEvent(room: Room, isForward: boolean, fromEventId: string = undefined): MatrixEvent {
const liveTimeline = room.getLiveTimeline();
const events = liveTimeline.getEvents().concat(room.getPendingEvents());
const maxIdx = events.length - 1;
diff --git a/src/utils/IdentityServerUtils.js b/src/utils/IdentityServerUtils.ts
similarity index 82%
rename from src/utils/IdentityServerUtils.js
rename to src/utils/IdentityServerUtils.ts
index 5ece308954..2476adca19 100644
--- a/src/utils/IdentityServerUtils.js
+++ b/src/utils/IdentityServerUtils.ts
@@ -1,5 +1,5 @@
/*
-Copyright 2019 The Matrix.org Foundation C.I.C.
+Copyright 2019 - 2021 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -15,14 +15,15 @@ limitations under the License.
*/
import { SERVICE_TYPES } from 'matrix-js-sdk/src/service-types';
+
import SdkConfig from '../SdkConfig';
import {MatrixClientPeg} from '../MatrixClientPeg';
-export function getDefaultIdentityServerUrl() {
+export function getDefaultIdentityServerUrl(): string {
return SdkConfig.get()['validated_server_config']['isUrl'];
}
-export function useDefaultIdentityServer() {
+export function useDefaultIdentityServer(): void {
const url = getDefaultIdentityServerUrl();
// Account data change will update localstorage, client, etc through dispatcher
MatrixClientPeg.get().setAccountData("m.identity_server", {
@@ -30,7 +31,7 @@ export function useDefaultIdentityServer() {
});
}
-export async function doesIdentityServerHaveTerms(fullUrl) {
+export async function doesIdentityServerHaveTerms(fullUrl: string): Promise {
let terms;
try {
terms = await MatrixClientPeg.get().getTerms(SERVICE_TYPES.IS, fullUrl);
@@ -46,7 +47,7 @@ export async function doesIdentityServerHaveTerms(fullUrl) {
return terms && terms["policies"] && (Object.keys(terms["policies"]).length > 0);
}
-export function doesAccountDataHaveIdentityServer() {
+export function doesAccountDataHaveIdentityServer(): boolean {
const event = MatrixClientPeg.get().getAccountData("m.identity_server");
return event && event.getContent() && event.getContent()['base_url'];
}
diff --git a/src/utils/MessageDiffUtils.js b/src/utils/MessageDiffUtils.tsx
similarity index 84%
rename from src/utils/MessageDiffUtils.js
rename to src/utils/MessageDiffUtils.tsx
index 7398173fdd..b5d5e31432 100644
--- a/src/utils/MessageDiffUtils.js
+++ b/src/utils/MessageDiffUtils.tsx
@@ -1,5 +1,5 @@
/*
-Copyright 2019 The Matrix.org Foundation C.I.C.
+Copyright 2019 - 2021 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -14,31 +14,33 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
-import React from 'react';
+import React, { ReactNode } from 'react';
import classNames from 'classnames';
-import DiffMatchPatch from 'diff-match-patch';
-import {DiffDOM} from "diff-dom";
-import { checkBlockNode, bodyToHtml } from "../HtmlUtils";
+import { diff_match_patch as DiffMatchPatch } from 'diff-match-patch';
+import { Action, DiffDOM, IDiff } from "diff-dom";
+import { IContent } from "matrix-js-sdk/src/models/event";
+
+import { bodyToHtml, checkBlockNode, IOptsReturnString } from "../HtmlUtils";
const decodeEntities = (function() {
let textarea = null;
- return function(string) {
+ return function(str: string): string {
if (!textarea) {
textarea = document.createElement("textarea");
}
- textarea.innerHTML = string;
+ textarea.innerHTML = str;
return textarea.value;
};
})();
-function textToHtml(text) {
+function textToHtml(text: string): string {
const container = document.createElement("div");
container.textContent = text;
return container.innerHTML;
}
-function getSanitizedHtmlBody(content) {
- const opts = {
+function getSanitizedHtmlBody(content: IContent): string {
+ const opts: IOptsReturnString = {
stripReplyFallback: true,
returnString: true,
};
@@ -57,21 +59,21 @@ function getSanitizedHtmlBody(content) {
}
}
-function wrapInsertion(child) {
+function wrapInsertion(child: Node): HTMLElement {
const wrapper = document.createElement(checkBlockNode(child) ? "div" : "span");
wrapper.className = "mx_EditHistoryMessage_insertion";
wrapper.appendChild(child);
return wrapper;
}
-function wrapDeletion(child) {
+function wrapDeletion(child: Node): HTMLElement {
const wrapper = document.createElement(checkBlockNode(child) ? "div" : "span");
wrapper.className = "mx_EditHistoryMessage_deletion";
wrapper.appendChild(child);
return wrapper;
}
-function findRefNodes(root, route, isAddition) {
+function findRefNodes(root: Node, route: number[], isAddition = false) {
let refNode = root;
let refParentNode;
const end = isAddition ? route.length - 1 : route.length;
@@ -79,7 +81,7 @@ function findRefNodes(root, route, isAddition) {
refParentNode = refNode;
refNode = refNode.childNodes[route[i]];
}
- return {refNode, refParentNode};
+ return { refNode, refParentNode };
}
function diffTreeToDOM(desc) {
@@ -101,7 +103,7 @@ function diffTreeToDOM(desc) {
}
}
-function insertBefore(parent, nextSibling, child) {
+function insertBefore(parent: Node, nextSibling: Node | null, child: Node): void {
if (nextSibling) {
parent.insertBefore(child, nextSibling);
} else {
@@ -109,7 +111,7 @@ function insertBefore(parent, nextSibling, child) {
}
}
-function isRouteOfNextSibling(route1, route2) {
+function isRouteOfNextSibling(route1: number[], route2: number[]): boolean {
// routes are arrays with indices,
// to be interpreted as a path in the dom tree
@@ -127,7 +129,7 @@ function isRouteOfNextSibling(route1, route2) {
return route2[lastD1Idx] >= route1[lastD1Idx];
}
-function adjustRoutes(diff, remainingDiffs) {
+function adjustRoutes(diff: IDiff, remainingDiffs: IDiff[]): void {
if (diff.action === "removeTextElement" || diff.action === "removeElement") {
// as removed text is not removed from the html, but marked as deleted,
// we need to readjust indices that assume the current node has been removed.
@@ -140,14 +142,14 @@ function adjustRoutes(diff, remainingDiffs) {
}
}
-function stringAsTextNode(string) {
+function stringAsTextNode(string: string): Text {
return document.createTextNode(decodeEntities(string));
}
-function renderDifferenceInDOM(originalRootNode, diff, diffMathPatch) {
+function renderDifferenceInDOM(originalRootNode: Node, diff: IDiff, diffMathPatch: DiffMatchPatch): void {
const {refNode, refParentNode} = findRefNodes(originalRootNode, diff.route);
switch (diff.action) {
- case "replaceElement": {
+ case Action.ReplaceElement: {
const container = document.createElement("span");
const delNode = wrapDeletion(diffTreeToDOM(diff.oldValue));
const insNode = wrapInsertion(diffTreeToDOM(diff.newValue));
@@ -156,22 +158,22 @@ function renderDifferenceInDOM(originalRootNode, diff, diffMathPatch) {
refNode.parentNode.replaceChild(container, refNode);
break;
}
- case "removeTextElement": {
+ case Action.RemoveTextElement: {
const delNode = wrapDeletion(stringAsTextNode(diff.value));
refNode.parentNode.replaceChild(delNode, refNode);
break;
}
- case "removeElement": {
+ case Action.RemoveElement: {
const delNode = wrapDeletion(diffTreeToDOM(diff.element));
refNode.parentNode.replaceChild(delNode, refNode);
break;
}
- case "modifyTextElement": {
+ case Action.ModifyTextElement: {
const textDiffs = diffMathPatch.diff_main(diff.oldValue, diff.newValue);
diffMathPatch.diff_cleanupSemantic(textDiffs);
const container = document.createElement("span");
for (const [modifier, text] of textDiffs) {
- let textDiffNode = stringAsTextNode(text);
+ let textDiffNode: Node = stringAsTextNode(text);
if (modifier < 0) {
textDiffNode = wrapDeletion(textDiffNode);
} else if (modifier > 0) {
@@ -182,12 +184,12 @@ function renderDifferenceInDOM(originalRootNode, diff, diffMathPatch) {
refNode.parentNode.replaceChild(container, refNode);
break;
}
- case "addElement": {
+ case Action.AddElement: {
const insNode = wrapInsertion(diffTreeToDOM(diff.element));
insertBefore(refParentNode, refNode, insNode);
break;
}
- case "addTextElement": {
+ case Action.AddTextElement: {
// XXX: sometimes diffDOM says insert a newline when there shouldn't be one
// but we must insert the node anyway so that we don't break the route child IDs.
// See https://github.com/fiduswriter/diffDOM/issues/100
@@ -197,11 +199,11 @@ function renderDifferenceInDOM(originalRootNode, diff, diffMathPatch) {
}
// e.g. when changing a the href of a link,
// show the link with old href as removed and with the new href as added
- case "removeAttribute":
- case "addAttribute":
- case "modifyAttribute": {
+ case Action.RemoveAttribute:
+ case Action.AddAttribute:
+ case Action.ModifyAttribute: {
const delNode = wrapDeletion(refNode.cloneNode(true));
- const updatedNode = refNode.cloneNode(true);
+ const updatedNode = refNode.cloneNode(true) as HTMLElement;
if (diff.action === "addAttribute" || diff.action === "modifyAttribute") {
updatedNode.setAttribute(diff.name, diff.newValue);
} else {
@@ -220,12 +222,12 @@ function renderDifferenceInDOM(originalRootNode, diff, diffMathPatch) {
}
}
-function routeIsEqual(r1, r2) {
+function routeIsEqual(r1: number[], r2: number[]): boolean {
return r1.length === r2.length && !r1.some((e, i) => e !== r2[i]);
}
// workaround for https://github.com/fiduswriter/diffDOM/issues/90
-function filterCancelingOutDiffs(originalDiffActions) {
+function filterCancelingOutDiffs(originalDiffActions: IDiff[]): IDiff[] {
const diffActions = originalDiffActions.slice();
for (let i = 0; i < diffActions.length; ++i) {
@@ -252,7 +254,7 @@ function filterCancelingOutDiffs(originalDiffActions) {
* @param {object} editContent the content for the edit message
* @return {object} a react element similar to what `bodyToHtml` returns
*/
-export function editBodyDiffToHtml(originalContent, editContent) {
+export function editBodyDiffToHtml(originalContent: IContent, editContent: IContent): ReactNode {
// wrap the body in a div, DiffDOM needs a root element
const originalBody = `${getSanitizedHtmlBody(originalContent)}
`;
const editBody = `${getSanitizedHtmlBody(editContent)}
`;
diff --git a/src/utils/PinningUtils.js b/src/utils/PinningUtils.ts
similarity index 89%
rename from src/utils/PinningUtils.js
rename to src/utils/PinningUtils.ts
index 90d26cc988..ec1eeccf1f 100644
--- a/src/utils/PinningUtils.js
+++ b/src/utils/PinningUtils.ts
@@ -14,13 +14,15 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
+import { MatrixEvent } from "matrix-js-sdk/src/models/event";
+
export default class PinningUtils {
/**
* Determines if the given event may be pinned.
* @param {MatrixEvent} event The event to check.
* @return {boolean} True if the event may be pinned, false otherwise.
*/
- static isPinnable(event) {
+ static isPinnable(event: MatrixEvent): boolean {
if (!event) return false;
if (event.getType() !== "m.room.message") return false;
if (event.isRedacted()) return false;
diff --git a/src/utils/Receipt.js b/src/utils/Receipt.ts
similarity index 83%
rename from src/utils/Receipt.js
rename to src/utils/Receipt.ts
index d88c67fb18..2a626decc4 100644
--- a/src/utils/Receipt.js
+++ b/src/utils/Receipt.ts
@@ -1,5 +1,5 @@
/*
-Copyright 2016 OpenMarket Ltd
+Copyright 2016 - 2021 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -14,6 +14,8 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
+import { MatrixEvent } from "matrix-js-sdk/src/models/event";
+
/**
* Given MatrixEvent containing receipts, return the first
* read receipt from the given user ID, or null if no such
@@ -23,7 +25,7 @@ limitations under the License.
* @param {string} userId A user ID
* @returns {Object} Read receipt
*/
-export function findReadReceiptFromUserId(receiptEvent, userId) {
+export function findReadReceiptFromUserId(receiptEvent: MatrixEvent, userId: string): object | null {
const receiptKeys = Object.keys(receiptEvent.getContent());
for (let i = 0; i < receiptKeys.length; ++i) {
const rcpt = receiptEvent.getContent()[receiptKeys[i]];
diff --git a/src/utils/ResizeNotifier.js b/src/utils/ResizeNotifier.ts
similarity index 62%
rename from src/utils/ResizeNotifier.js
rename to src/utils/ResizeNotifier.ts
index 4d46d10f6c..8bb7f52e57 100644
--- a/src/utils/ResizeNotifier.js
+++ b/src/utils/ResizeNotifier.ts
@@ -1,5 +1,5 @@
/*
-Copyright 2019 New Vector Ltd
+Copyright 2019 - 2021 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -22,59 +22,58 @@ limitations under the License.
* Fires when the middle panel has been resized by a pixel.
* @event module:utils~ResizeNotifier#"middlePanelResizedNoisy"
*/
+
import { EventEmitter } from "events";
import { throttle } from "lodash";
export default class ResizeNotifier extends EventEmitter {
- constructor() {
- super();
- // with default options, will call fn once at first call, and then every x ms
- // if there was another call in that timespan
- this._throttledMiddlePanel = throttle(() => this.emit("middlePanelResized"), 200);
- this._isResizing = false;
- }
+ private _isResizing = false;
- get isResizing() {
+ // with default options, will call fn once at first call, and then every x ms
+ // if there was another call in that timespan
+ private throttledMiddlePanel = throttle(() => this.emit("middlePanelResized"), 200);
+
+ public get isResizing() {
return this._isResizing;
}
- startResizing() {
+ public startResizing() {
this._isResizing = true;
this.emit("isResizing", true);
}
- stopResizing() {
+ public stopResizing() {
this._isResizing = false;
this.emit("isResizing", false);
}
- _noisyMiddlePanel() {
+ private noisyMiddlePanel() {
this.emit("middlePanelResizedNoisy");
}
- _updateMiddlePanel() {
- this._throttledMiddlePanel();
- this._noisyMiddlePanel();
+ private updateMiddlePanel() {
+ this.throttledMiddlePanel();
+ this.noisyMiddlePanel();
}
// can be called in quick succession
- notifyLeftHandleResized() {
+ public notifyLeftHandleResized() {
// don't emit event for own region
- this._updateMiddlePanel();
+ this.updateMiddlePanel();
}
// can be called in quick succession
- notifyRightHandleResized() {
- this._updateMiddlePanel();
+ public notifyRightHandleResized() {
+ this.updateMiddlePanel();
}
- notifyTimelineHeightChanged() {
- this._updateMiddlePanel();
+ public notifyTimelineHeightChanged() {
+ this.updateMiddlePanel();
}
// can be called in quick succession
- notifyWindowResized() {
- this._updateMiddlePanel();
+ public notifyWindowResized() {
+ this.updateMiddlePanel();
}
}
diff --git a/src/utils/createMatrixClient.js b/src/utils/createMatrixClient.ts
similarity index 76%
rename from src/utils/createMatrixClient.js
rename to src/utils/createMatrixClient.ts
index f5e196d846..caaf75616d 100644
--- a/src/utils/createMatrixClient.js
+++ b/src/utils/createMatrixClient.ts
@@ -1,5 +1,5 @@
/*
-Copyright 2017 Vector Creations Ltd
+Copyright 2017 - 2021 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -14,10 +14,10 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
-import {createClient} from "matrix-js-sdk/src/matrix";
-import {IndexedDBCryptoStore} from "matrix-js-sdk/src/crypto/store/indexeddb-crypto-store";
-import {WebStorageSessionStore} from "matrix-js-sdk/src/store/session/webstorage";
-import {IndexedDBStore} from "matrix-js-sdk/src/store/indexeddb";
+import { createClient, ICreateClientOpts } from "matrix-js-sdk/src/matrix";
+import { IndexedDBCryptoStore } from "matrix-js-sdk/src/crypto/store/indexeddb-crypto-store";
+import { WebStorageSessionStore } from "matrix-js-sdk/src/store/session/webstorage";
+import { IndexedDBStore } from "matrix-js-sdk/src/store/indexeddb";
const localStorage = window.localStorage;
@@ -41,8 +41,8 @@ try {
*
* @returns {MatrixClient} the newly-created MatrixClient
*/
-export default function createMatrixClient(opts) {
- const storeOpts = {
+export default function createMatrixClient(opts: ICreateClientOpts) {
+ const storeOpts: Partial = {
useAuthorizationHeader: true,
};
@@ -65,9 +65,10 @@ export default function createMatrixClient(opts) {
);
}
- opts = Object.assign(storeOpts, opts);
-
- return createClient(opts);
+ return createClient({
+ ...storeOpts,
+ ...opts,
+ });
}
createMatrixClient.indexedDbWorkerScript = null;
From a839d0f3963eb00f22db2f205c4d285d34125a3a Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Tue, 22 Jun 2021 17:23:13 +0100
Subject: [PATCH 125/164] More typescript conversion
---
package.json | 1 +
src/@types/diff-dom.ts | 50 +++++++++++++++++
...Tracker.js => DecryptionFailureTracker.ts} | 53 ++++++++++---------
src/HtmlUtils.tsx | 50 +++++++++--------
src/{Rooms.js => Rooms.ts} | 16 +++---
src/{Unread.js => Unread.ts} | 47 +++++++++-------
src/components/structures/MatrixChat.tsx | 2 +-
src/components/views/rooms/AuxPanel.tsx | 2 +-
src/components/views/rooms/EventTile.tsx | 27 +++++-----
src/components/views/rooms/RoomList.tsx | 2 +-
src/components/views/rooms/RoomSublist.tsx | 2 +-
src/languageHandler.tsx | 2 +-
yarn.lock | 5 ++
13 files changed, 166 insertions(+), 93 deletions(-)
create mode 100644 src/@types/diff-dom.ts
rename src/{DecryptionFailureTracker.js => DecryptionFailureTracker.ts} (80%)
rename src/{Rooms.js => Rooms.ts} (89%)
rename src/{Unread.js => Unread.ts} (75%)
diff --git a/package.json b/package.json
index f232d4301b..756604269d 100644
--- a/package.json
+++ b/package.json
@@ -123,6 +123,7 @@
"@sinonjs/fake-timers": "^7.0.2",
"@types/classnames": "^2.2.11",
"@types/counterpart": "^0.18.1",
+ "@types/diff-match-patch": "^1.0.5",
"@types/flux": "^3.1.9",
"@types/jest": "^26.0.20",
"@types/linkifyjs": "^2.1.3",
diff --git a/src/@types/diff-dom.ts b/src/@types/diff-dom.ts
new file mode 100644
index 0000000000..884ee6126d
--- /dev/null
+++ b/src/@types/diff-dom.ts
@@ -0,0 +1,50 @@
+/*
+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.
+*/
+
+declare module "diff-dom" {
+ enum Action {
+ AddElement = "addElement",
+ AddTextElement = "addTextElement",
+ RemoveTextElement = "removeTextElement",
+ RemoveElement = "removeElement",
+ ReplaceElement = "replaceElement",
+ ModifyTextElement = "modifyTextElement",
+ AddAttribute = "addAttribute",
+ RemoveAttribute = "removeAttribute",
+ ModifyAttribute = "modifyAttribute",
+ }
+
+ export interface IDiff {
+ action: Action;
+ name: string;
+ text?: string;
+ route: number[];
+ value: string;
+ element: unknown;
+ oldValue: string;
+ newValue: string;
+ }
+
+ interface IOpts {
+ }
+
+ export class DiffDOM {
+ public constructor(opts?: IOpts);
+ public apply(tree: unknown, diffs: IDiff[]): unknown;
+ public undo(tree: unknown, diffs: IDiff[]): unknown;
+ public diff(a: HTMLElement | string, b: HTMLElement | string): IDiff[];
+ }
+}
diff --git a/src/DecryptionFailureTracker.js b/src/DecryptionFailureTracker.ts
similarity index 80%
rename from src/DecryptionFailureTracker.js
rename to src/DecryptionFailureTracker.ts
index b02a5e937b..960d844e9e 100644
--- a/src/DecryptionFailureTracker.js
+++ b/src/DecryptionFailureTracker.ts
@@ -1,5 +1,5 @@
/*
-Copyright 2018 New Vector Ltd
+Copyright 2018 - 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.
@@ -14,34 +14,40 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
+import { MatrixError } from "matrix-js-sdk/src/http-api";
+import { MatrixEvent } from "matrix-js-sdk/src/models/event";
+
export class DecryptionFailure {
- constructor(failedEventId, errorCode) {
- this.failedEventId = failedEventId;
- this.errorCode = errorCode;
+ public readonly ts: number;
+
+ constructor(public readonly failedEventId: string, public readonly errorCode: string) {
this.ts = Date.now();
}
}
+type Fn = (count: number, trackedErrCode: string) => void;
+type ErrCodeMapFn = (errcode: string) => string;
+
export class DecryptionFailureTracker {
// Array of items of type DecryptionFailure. Every `CHECK_INTERVAL_MS`, this list
// is checked for failures that happened > `GRACE_PERIOD_MS` ago. Those that did
// are accumulated in `failureCounts`.
- failures = [];
+ public failures: DecryptionFailure[] = [];
// A histogram of the number of failures that will be tracked at the next tracking
// interval, split by failure error code.
- failureCounts = {
+ public failureCounts: Record = {
// [errorCode]: 42
};
// Event IDs of failures that were tracked previously
- trackedEventHashMap = {
+ public trackedEventHashMap: Record = {
// [eventId]: true
};
// Set to an interval ID when `start` is called
- checkInterval = null;
- trackInterval = null;
+ public checkInterval: NodeJS.Timeout = null;
+ public trackInterval: NodeJS.Timeout = null;
// Spread the load on `Analytics` by tracking at a low frequency, `TRACK_INTERVAL_MS`.
static TRACK_INTERVAL_MS = 60000;
@@ -67,7 +73,7 @@ export class DecryptionFailureTracker {
* @param {function?} errorCodeMapFn The function used to map error codes to the
* trackedErrorCode. If not provided, the `.code` of errors will be used.
*/
- constructor(fn, errorCodeMapFn) {
+ constructor(private readonly fn: Fn, private readonly errorCodeMapFn?: ErrCodeMapFn) {
if (!fn || typeof fn !== 'function') {
throw new Error('DecryptionFailureTracker requires tracking function');
}
@@ -75,9 +81,6 @@ export class DecryptionFailureTracker {
if (errorCodeMapFn && typeof errorCodeMapFn !== 'function') {
throw new Error('DecryptionFailureTracker second constructor argument should be a function');
}
-
- this._trackDecryptionFailure = fn;
- this._mapErrorCode = errorCodeMapFn;
}
// loadTrackedEventHashMap() {
@@ -88,7 +91,7 @@ export class DecryptionFailureTracker {
// localStorage.setItem('mx-decryption-failure-event-id-hashes', JSON.stringify(this.trackedEventHashMap));
// }
- eventDecrypted(e, err) {
+ public eventDecrypted(e: MatrixEvent, err: MatrixError | Error): void {
if (err) {
this.addDecryptionFailure(new DecryptionFailure(e.getId(), err.code));
} else {
@@ -97,18 +100,18 @@ export class DecryptionFailureTracker {
}
}
- addDecryptionFailure(failure) {
+ public addDecryptionFailure(failure: DecryptionFailure): void {
this.failures.push(failure);
}
- removeDecryptionFailuresForEvent(e) {
+ public removeDecryptionFailuresForEvent(e: MatrixEvent): void {
this.failures = this.failures.filter((f) => f.failedEventId !== e.getId());
}
/**
* Start checking for and tracking failures.
*/
- start() {
+ public start(): void {
this.checkInterval = setInterval(
() => this.checkFailures(Date.now()),
DecryptionFailureTracker.CHECK_INTERVAL_MS,
@@ -123,7 +126,7 @@ export class DecryptionFailureTracker {
/**
* Clear state and stop checking for and tracking failures.
*/
- stop() {
+ public stop(): void {
clearInterval(this.checkInterval);
clearInterval(this.trackInterval);
@@ -132,11 +135,11 @@ export class DecryptionFailureTracker {
}
/**
- * Mark failures that occured before nowTs - GRACE_PERIOD_MS as failures that should be
+ * Mark failures that occurred before nowTs - GRACE_PERIOD_MS as failures that should be
* tracked. Only mark one failure per event ID.
* @param {number} nowTs the timestamp that represents the time now.
*/
- checkFailures(nowTs) {
+ public checkFailures(nowTs: number): void {
const failuresGivenGrace = [];
const failuresNotReady = [];
while (this.failures.length > 0) {
@@ -175,10 +178,10 @@ export class DecryptionFailureTracker {
const dedupedFailures = dedupedFailuresMap.values();
- this._aggregateFailures(dedupedFailures);
+ this.aggregateFailures(dedupedFailures);
}
- _aggregateFailures(failures) {
+ private aggregateFailures(failures: DecryptionFailure[]): void {
for (const failure of failures) {
const errorCode = failure.errorCode;
this.failureCounts[errorCode] = (this.failureCounts[errorCode] || 0) + 1;
@@ -189,12 +192,12 @@ export class DecryptionFailureTracker {
* If there are failures that should be tracked, call the given trackDecryptionFailure
* function with the number of failures that should be tracked.
*/
- trackFailures() {
+ public trackFailures(): void {
for (const errorCode of Object.keys(this.failureCounts)) {
if (this.failureCounts[errorCode] > 0) {
- const trackedErrorCode = this._mapErrorCode ? this._mapErrorCode(errorCode) : errorCode;
+ const trackedErrorCode = this.errorCodeMapFn ? this.errorCodeMapFn(errorCode) : errorCode;
- this._trackDecryptionFailure(this.failureCounts[errorCode], trackedErrorCode);
+ this.fn(this.failureCounts[errorCode], trackedErrorCode);
this.failureCounts[errorCode] = 0;
}
}
diff --git a/src/HtmlUtils.tsx b/src/HtmlUtils.tsx
index ef5ac383e3..5803029030 100644
--- a/src/HtmlUtils.tsx
+++ b/src/HtmlUtils.tsx
@@ -17,11 +17,10 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
-import React from 'react';
+import React, { ReactNode } from 'react';
import sanitizeHtml from 'sanitize-html';
-import { IExtendedSanitizeOptions } from './@types/sanitize-html';
+import cheerio from 'cheerio';
import * as linkify from 'linkifyjs';
-import linkifyMatrix from './linkify-matrix';
import _linkifyElement from 'linkifyjs/element';
import _linkifyString from 'linkifyjs/string';
import classNames from 'classnames';
@@ -29,13 +28,15 @@ import EMOJIBASE_REGEX from 'emojibase-regex';
import url from 'url';
import katex from 'katex';
import { AllHtmlEntities } from 'html-entities';
-import SettingsStore from './settings/SettingsStore';
-import cheerio from 'cheerio';
+import { IContent } from 'matrix-js-sdk/src/models/event';
-import {tryTransformPermalinkToLocalHref} from "./utils/permalinks/Permalinks";
-import {SHORTCODE_TO_EMOJI, getEmojiFromUnicode} from "./emoji";
+import { IExtendedSanitizeOptions } from './@types/sanitize-html';
+import linkifyMatrix from './linkify-matrix';
+import SettingsStore from './settings/SettingsStore';
+import { tryTransformPermalinkToLocalHref } from "./utils/permalinks/Permalinks";
+import { SHORTCODE_TO_EMOJI, getEmojiFromUnicode } from "./emoji";
import ReplyThread from "./components/views/elements/ReplyThread";
-import {mediaFromMxc} from "./customisations/Media";
+import { mediaFromMxc } from "./customisations/Media";
linkifyMatrix(linkify);
@@ -66,7 +67,7 @@ export const PERMITTED_URL_SCHEMES = ['http', 'https', 'ftp', 'mailto', 'magnet'
* need emojification.
* unicodeToImage uses this function.
*/
-function mightContainEmoji(str: string) {
+function mightContainEmoji(str: string): boolean {
return SURROGATE_PAIR_PATTERN.test(str) || SYMBOL_PATTERN.test(str);
}
@@ -76,7 +77,7 @@ function mightContainEmoji(str: string) {
* @param {String} char The emoji character
* @return {String} The shortcode (such as :thumbup:)
*/
-export function unicodeToShortcode(char: string) {
+export function unicodeToShortcode(char: string): string {
const data = getEmojiFromUnicode(char);
return (data && data.shortcodes ? `:${data.shortcodes[0]}:` : '');
}
@@ -87,7 +88,7 @@ export function unicodeToShortcode(char: string) {
* @param {String} shortcode The shortcode (such as :thumbup:)
* @return {String} The emoji character; null if none exists
*/
-export function shortcodeToUnicode(shortcode: string) {
+export function shortcodeToUnicode(shortcode: string): string {
shortcode = shortcode.slice(1, shortcode.length - 1);
const data = SHORTCODE_TO_EMOJI.get(shortcode);
return data ? data.unicode : null;
@@ -124,13 +125,13 @@ export function processHtmlForSending(html: string): string {
* Given an untrusted HTML string, return a React node with an sanitized version
* of that HTML.
*/
-export function sanitizedHtmlNode(insaneHtml: string) {
+export function sanitizedHtmlNode(insaneHtml: string): ReactNode {
const saneHtml = sanitizeHtml(insaneHtml, sanitizeHtmlParams);
return
;
}
-export function getHtmlText(insaneHtml: string) {
+export function getHtmlText(insaneHtml: string): string {
return sanitizeHtml(insaneHtml, {
allowedTags: [],
allowedAttributes: {},
@@ -148,7 +149,7 @@ export function getHtmlText(insaneHtml: string) {
* other places we need to sanitise URLs.
* @return true if permitted, otherwise false
*/
-export function isUrlPermitted(inputUrl: string) {
+export function isUrlPermitted(inputUrl: string): boolean {
try {
const parsed = url.parse(inputUrl);
if (!parsed.protocol) return false;
@@ -351,13 +352,6 @@ class HtmlHighlighter extends BaseHighlighter {
}
}
-interface IContent {
- format?: string;
- // eslint-disable-next-line camelcase
- formatted_body?: string;
- body: string;
-}
-
interface IOpts {
highlightLink?: string;
disableBigEmoji?: boolean;
@@ -367,6 +361,14 @@ interface IOpts {
ref?: React.Ref;
}
+export interface IOptsReturnNode extends IOpts {
+ returnString: false;
+}
+
+export interface IOptsReturnString extends IOpts {
+ returnString: true;
+}
+
/* turn a matrix event body into html
*
* content: 'content' of the MatrixEvent
@@ -380,6 +382,8 @@ interface IOpts {
* opts.forComposerQuote: optional param to lessen the url rewriting done by sanitization, for quoting into composer
* opts.ref: React ref to attach to any React components returned (not compatible with opts.returnString)
*/
+export function bodyToHtml(content: IContent, highlights: string[], opts: IOptsReturnString): string;
+export function bodyToHtml(content: IContent, highlights: string[], opts: IOptsReturnNode): ReactNode;
export function bodyToHtml(content: IContent, highlights: string[], opts: IOpts = {}) {
const isHtmlMessage = content.format === "org.matrix.custom.html" && content.formatted_body;
let bodyHasEmoji = false;
@@ -523,7 +527,7 @@ export function linkifyElement(element: HTMLElement, options = linkifyMatrix.opt
* @param {object} [options] Options for linkifyString. Default: linkifyMatrix.options
* @returns {string}
*/
-export function linkifyAndSanitizeHtml(dirtyHtml: string, options = linkifyMatrix.options) {
+export function linkifyAndSanitizeHtml(dirtyHtml: string, options = linkifyMatrix.options): string {
return sanitizeHtml(linkifyString(dirtyHtml, options), sanitizeHtmlParams);
}
@@ -534,7 +538,7 @@ export function linkifyAndSanitizeHtml(dirtyHtml: string, options = linkifyMatri
* @param {Node} node
* @returns {bool}
*/
-export function checkBlockNode(node: Node) {
+export function checkBlockNode(node: Node): boolean {
switch (node.nodeName) {
case "H1":
case "H2":
diff --git a/src/Rooms.js b/src/Rooms.ts
similarity index 89%
rename from src/Rooms.js
rename to src/Rooms.ts
index 955498faaa..19d1c9ee05 100644
--- a/src/Rooms.js
+++ b/src/Rooms.ts
@@ -1,5 +1,5 @@
/*
-Copyright 2015, 2016 OpenMarket Ltd
+Copyright 2015 - 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.
@@ -14,7 +14,9 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
-import {MatrixClientPeg} from './MatrixClientPeg';
+import { Room } from "matrix-js-sdk/src/models/room";
+
+import { MatrixClientPeg } from './MatrixClientPeg';
/**
* Given a room object, return the alias we should use for it,
@@ -25,11 +27,11 @@ import {MatrixClientPeg} from './MatrixClientPeg';
* @param {Object} room The room object
* @returns {string} A display alias for the given room
*/
-export function getDisplayAliasForRoom(room) {
+export function getDisplayAliasForRoom(room: Room): string {
return room.getCanonicalAlias() || room.getAltAliases()[0];
}
-export function looksLikeDirectMessageRoom(room, myUserId) {
+export function looksLikeDirectMessageRoom(room: Room, myUserId: string): boolean {
const myMembership = room.getMyMembership();
const me = room.getMember(myUserId);
@@ -48,7 +50,7 @@ export function looksLikeDirectMessageRoom(room, myUserId) {
return false;
}
-export function guessAndSetDMRoom(room, isDirect) {
+export function guessAndSetDMRoom(room: Room, isDirect: boolean): Promise {
let newTarget;
if (isDirect) {
const guessedUserId = guessDMRoomTargetId(
@@ -70,7 +72,7 @@ export function guessAndSetDMRoom(room, isDirect) {
this room as a DM room
* @returns {object} A promise
*/
-export function setDMRoom(roomId, userId) {
+export function setDMRoom(roomId: string, userId: string): Promise {
if (MatrixClientPeg.get().isGuest()) {
return Promise.resolve();
}
@@ -114,7 +116,7 @@ export function setDMRoom(roomId, userId) {
* @param {string} myUserId User ID of the current user
* @returns {string} User ID of the user that the room is probably a DM with
*/
-function guessDMRoomTargetId(room, myUserId) {
+function guessDMRoomTargetId(room: Room, myUserId: string): string {
let oldestTs;
let oldestUser;
diff --git a/src/Unread.js b/src/Unread.ts
similarity index 75%
rename from src/Unread.js
rename to src/Unread.ts
index 25c425aa9a..b733f4175a 100644
--- a/src/Unread.js
+++ b/src/Unread.ts
@@ -1,5 +1,5 @@
/*
-Copyright 2015, 2016 OpenMarket Ltd
+Copyright 2015 - 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.
@@ -14,9 +14,13 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
-import {MatrixClientPeg} from "./MatrixClientPeg";
+import { Room } from "matrix-js-sdk/src/models/room";
+import { MatrixEvent } from "matrix-js-sdk/src/models/event";
+import { EventType, MsgType } from "matrix-js-sdk/src/@types/event";
+
+import { MatrixClientPeg } from "./MatrixClientPeg";
import shouldHideEvent from './shouldHideEvent';
-import {haveTileForEvent} from "./components/views/rooms/EventTile";
+import { haveTileForEvent } from "./components/views/rooms/EventTile";
/**
* Returns true iff this event arriving in a room should affect the room's
@@ -25,28 +29,33 @@ import {haveTileForEvent} from "./components/views/rooms/EventTile";
* @param {Object} ev The event
* @returns {boolean} True if the given event should affect the unread message count
*/
-export function eventTriggersUnreadCount(ev) {
+export function eventTriggersUnreadCount(ev: MatrixEvent): boolean {
if (ev.sender && ev.sender.userId == MatrixClientPeg.get().credentials.userId) {
return false;
- } else if (ev.getType() == 'm.room.member') {
- return false;
- } else if (ev.getType() == 'm.room.third_party_invite') {
- return false;
- } else if (ev.getType() == 'm.call.answer' || ev.getType() == 'm.call.hangup') {
- return false;
- } else if (ev.getType() == 'm.room.message' && ev.getContent().msgtype == 'm.notify') {
- return false;
- } else if (ev.getType() == 'm.room.aliases' || ev.getType() == 'm.room.canonical_alias') {
- return false;
- } else if (ev.getType() == 'm.room.server_acl') {
- return false;
- } else if (ev.isRedacted()) {
- return false;
}
+
+ switch (ev.getType()) {
+ case EventType.RoomMember:
+ case EventType.RoomThirdPartyInvite:
+ case EventType.CallAnswer:
+ case EventType.CallHangup:
+ case EventType.RoomAliases:
+ case EventType.RoomCanonicalAlias:
+ case EventType.RoomServerAcl:
+ return false;
+
+ case EventType.RoomMessage:
+ if (ev.getContent().msgtype === MsgType.Notice) {
+ return false;
+ }
+ break;
+ }
+
+ if (ev.isRedacted()) return false;
return haveTileForEvent(ev);
}
-export function doesRoomHaveUnreadMessages(room) {
+export function doesRoomHaveUnreadMessages(room: Room): boolean {
const myUserId = MatrixClientPeg.get().getUserId();
// get the most recent read receipt sent by our account.
diff --git a/src/components/structures/MatrixChat.tsx b/src/components/structures/MatrixChat.tsx
index 0af2d3d635..27d628fecf 100644
--- a/src/components/structures/MatrixChat.tsx
+++ b/src/components/structures/MatrixChat.tsx
@@ -1461,7 +1461,7 @@ export default class MatrixChat extends React.PureComponent {
});
const dft = new DecryptionFailureTracker((total, errorCode) => {
- Analytics.trackEvent('E2E', 'Decryption failure', errorCode, total);
+ Analytics.trackEvent('E2E', 'Decryption failure', errorCode, String(total));
CountlyAnalytics.instance.track("decryption_failure", { errorCode }, null, { sum: total });
}, (errorCode) => {
// Map JS-SDK error codes to tracker codes for aggregation
diff --git a/src/components/views/rooms/AuxPanel.tsx b/src/components/views/rooms/AuxPanel.tsx
index 74609cca13..cb01df05d1 100644
--- a/src/components/views/rooms/AuxPanel.tsx
+++ b/src/components/views/rooms/AuxPanel.tsx
@@ -23,7 +23,7 @@ import RateLimitedFunc from '../../../ratelimitedfunc';
import SettingsStore from "../../../settings/SettingsStore";
import AutoHideScrollbar from "../../structures/AutoHideScrollbar";
import { UIFeature } from "../../../settings/UIFeature";
-import { ResizeNotifier } from "../../../utils/ResizeNotifier";
+import ResizeNotifier from "../../../utils/ResizeNotifier";
import CallViewForRoom from '../voip/CallViewForRoom';
import { objectHasDiff } from "../../../utils/objects";
import { replaceableComponent } from "../../../utils/replaceableComponent";
diff --git a/src/components/views/rooms/EventTile.tsx b/src/components/views/rooms/EventTile.tsx
index 0099bf73fb..3d674efe04 100644
--- a/src/components/views/rooms/EventTile.tsx
+++ b/src/components/views/rooms/EventTile.tsx
@@ -17,7 +17,6 @@ limitations under the License.
import React from 'react';
import classNames from "classnames";
-
import { EventType } from "matrix-js-sdk/src/@types/event";
import { EventStatus, MatrixEvent } from "matrix-js-sdk/src/models/event";
import { Relations } from "matrix-js-sdk/src/models/relations";
@@ -29,24 +28,24 @@ import { hasText } from "../../../TextForEvent";
import * as sdk from "../../../index";
import dis from '../../../dispatcher/dispatcher';
import SettingsStore from "../../../settings/SettingsStore";
-import {Layout} from "../../../settings/Layout";
-import {formatTime} from "../../../DateUtils";
-import {MatrixClientPeg} from '../../../MatrixClientPeg';
-import {ALL_RULE_TYPES} from "../../../mjolnir/BanList";
+import { Layout } from "../../../settings/Layout";
+import { formatTime } from "../../../DateUtils";
+import { MatrixClientPeg } from '../../../MatrixClientPeg';
+import { ALL_RULE_TYPES } from "../../../mjolnir/BanList";
import MatrixClientContext from "../../../contexts/MatrixClientContext";
-import {E2E_STATE} from "./E2EIcon";
-import {toRem} from "../../../utils/units";
-import {WidgetType} from "../../../widgets/WidgetType";
+import { E2E_STATE } from "./E2EIcon";
+import { toRem } from "../../../utils/units";
+import { WidgetType } from "../../../widgets/WidgetType";
import RoomAvatar from "../avatars/RoomAvatar";
-import {WIDGET_LAYOUT_EVENT_TYPE} from "../../../stores/widgets/WidgetLayoutStore";
-import {objectHasDiff} from "../../../utils/objects";
-import {replaceableComponent} from "../../../utils/replaceableComponent";
+import { WIDGET_LAYOUT_EVENT_TYPE } from "../../../stores/widgets/WidgetLayoutStore";
+import { objectHasDiff } from "../../../utils/objects";
+import { replaceableComponent } from "../../../utils/replaceableComponent";
import Tooltip from "../elements/Tooltip";
-import { EditorStateTransfer } from "../../../utils/EditorStateTransfer";
+import EditorStateTransfer from "../../../utils/EditorStateTransfer";
import { RoomPermalinkCreator } from '../../../utils/permalinks/Permalinks';
-import {StaticNotificationState} from "../../../stores/notifications/StaticNotificationState";
+import { StaticNotificationState } from "../../../stores/notifications/StaticNotificationState";
import NotificationBadge from "./NotificationBadge";
-import {ComposerInsertPayload} from "../../../dispatcher/payloads/ComposerInsertPayload";
+import { ComposerInsertPayload } from "../../../dispatcher/payloads/ComposerInsertPayload";
import { Action } from '../../../dispatcher/actions';
const eventTileTypes = {
diff --git a/src/components/views/rooms/RoomList.tsx b/src/components/views/rooms/RoomList.tsx
index 5a1c3a24b3..d277a69907 100644
--- a/src/components/views/rooms/RoomList.tsx
+++ b/src/components/views/rooms/RoomList.tsx
@@ -22,7 +22,7 @@ import { EventType } from "matrix-js-sdk/src/@types/event";
import { _t, _td } from "../../../languageHandler";
import { RovingTabIndexProvider } from "../../../accessibility/RovingTabIndex";
-import { ResizeNotifier } from "../../../utils/ResizeNotifier";
+import ResizeNotifier from "../../../utils/ResizeNotifier";
import RoomListStore, { LISTS_UPDATE_EVENT } from "../../../stores/room-list/RoomListStore";
import RoomViewStore from "../../../stores/RoomViewStore";
import { ITagMap } from "../../../stores/room-list/algorithms/models";
diff --git a/src/components/views/rooms/RoomSublist.tsx b/src/components/views/rooms/RoomSublist.tsx
index ba8bbffbcc..61166b4230 100644
--- a/src/components/views/rooms/RoomSublist.tsx
+++ b/src/components/views/rooms/RoomSublist.tsx
@@ -45,7 +45,7 @@ import { ActionPayload } from "../../../dispatcher/payloads";
import { Enable, Resizable } from "re-resizable";
import { Direction } from "re-resizable/lib/resizer";
import { polyfillTouchEvent } from "../../../@types/polyfill";
-import { ResizeNotifier } from "../../../utils/ResizeNotifier";
+import ResizeNotifier from "../../../utils/ResizeNotifier";
import { RoomNotificationStateStore } from "../../../stores/notifications/RoomNotificationStateStore";
import RoomListLayoutStore from "../../../stores/room-list/RoomListLayoutStore";
import { arrayFastClone, arrayHasOrderChange } from "../../../utils/arrays";
diff --git a/src/languageHandler.tsx b/src/languageHandler.tsx
index 16950dc008..cc129fb6b9 100644
--- a/src/languageHandler.tsx
+++ b/src/languageHandler.tsx
@@ -112,7 +112,7 @@ export interface IVariables {
[key: string]: SubstitutionValue;
}
-type Tags = Record;
+export type Tags = Record;
export type TranslatedString = string | React.ReactNode;
diff --git a/yarn.lock b/yarn.lock
index 952d08d0f6..5bd409e612 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1479,6 +1479,11 @@
resolved "https://registry.yarnpkg.com/@types/counterpart/-/counterpart-0.18.1.tgz#b1b784d9e54d9879f0a8cb12f2caedab65430fe8"
integrity sha512-PRuFlBBkvdDOtxlIASzTmkEFar+S66Ek48NVVTWMUjtJAdn5vyMSN8y6IZIoIymGpR36q2nZbIYazBWyFxL+IQ==
+"@types/diff-match-patch@^1.0.32":
+ version "1.0.32"
+ resolved "https://registry.yarnpkg.com/@types/diff-match-patch/-/diff-match-patch-1.0.32.tgz#d9c3b8c914aa8229485351db4865328337a3d09f"
+ integrity sha512-bPYT5ECFiblzsVzyURaNhljBH2Gh1t9LowgUwciMrNAhFewLkHT2H0Mto07Y4/3KCOGZHRQll3CTtQZ0X11D/A==
+
"@types/events@^3.0.0":
version "3.0.0"
resolved "https://registry.yarnpkg.com/@types/events/-/events-3.0.0.tgz#2862f3f58a9a7f7c3e78d79f130dd4d71c25c2a7"
From 4a667942368305799e34ac8b0ea7d569a5207a36 Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Tue, 22 Jun 2021 17:26:19 +0100
Subject: [PATCH 126/164] update copy
---
src/i18n/strings/en_EN.json | 2 +-
src/settings/Settings.tsx | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json
index a9a0d15ac4..17160a6b89 100644
--- a/src/i18n/strings/en_EN.json
+++ b/src/i18n/strings/en_EN.json
@@ -796,7 +796,7 @@
"Show all rooms in Home": "Show all rooms in Home",
"Show people in spaces": "Show people in spaces",
"If disabled, you can still add Direct Messages to Personal Spaces. If enabled, you'll automatically see everyone who is a member of the Space.": "If disabled, you can still add Direct Messages to Personal Spaces. If enabled, you'll automatically see everyone who is a member of the Space.",
- "Show notification badges for DMs in Spaces": "Show notification badges for DMs in Spaces",
+ "Show notification badges for People in Spaces": "Show notification badges for People in Spaces",
"Show options to enable 'Do not disturb' mode": "Show options to enable 'Do not disturb' mode",
"Send and receive voice messages": "Send and receive voice messages",
"Render LaTeX maths in messages": "Render LaTeX maths in messages",
diff --git a/src/settings/Settings.tsx b/src/settings/Settings.tsx
index 3937b7d821..f22882abc4 100644
--- a/src/settings/Settings.tsx
+++ b/src/settings/Settings.tsx
@@ -193,7 +193,7 @@ export const SETTINGS: {[setting: string]: ISetting} = {
controller: new ReloadOnChangeController(),
},
"feature_spaces.space_dm_badges": {
- displayName: _td("Show notification badges for DMs in Spaces"),
+ displayName: _td("Show notification badges for People in Spaces"),
supportedLevels: LEVELS_FEATURE,
default: false,
controller: new ReloadOnChangeController(),
From 28c61cca2798f0c41fea47a468057a4a9ecf2c58 Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Tue, 22 Jun 2021 17:56:52 +0100
Subject: [PATCH 127/164] Remove pinned resolution for @types/react to 16.x
---
package.json | 3 ---
yarn.lock | 10 +---------
2 files changed, 1 insertion(+), 12 deletions(-)
diff --git a/package.json b/package.json
index 8ebb90f342..9ca1224baa 100644
--- a/package.json
+++ b/package.json
@@ -167,9 +167,6 @@
"typescript": "^4.1.3",
"walk": "^2.3.14"
},
- "resolutions": {
- "**/@types/react": "^16.14"
- },
"jest": {
"testEnvironment": "./__test-utils__/environment.js",
"testMatch": [
diff --git a/yarn.lock b/yarn.lock
index 4f17b63337..b19a188014 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1634,15 +1634,7 @@
dependencies:
"@types/react" "*"
-"@types/react@*", "@types/react@^16.14":
- version "16.14.2"
- resolved "https://registry.yarnpkg.com/@types/react/-/react-16.14.2.tgz#85dcc0947d0645349923c04ccef6018a1ab7538c"
- integrity sha512-BzzcAlyDxXl2nANlabtT4thtvbbnhee8hMmH/CcJrISDBVcJS1iOsP1f0OAgSdGE0MsY9tqcrb9YoZcOFv9dbQ==
- dependencies:
- "@types/prop-types" "*"
- csstype "^3.0.2"
-
-"@types/react@^17.0.2":
+"@types/react@*", "@types/react@^17.0.2":
version "17.0.11"
resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.11.tgz#67fcd0ddbf5a0b083a0f94e926c7d63f3b836451"
integrity sha512-yFRQbD+whVonItSk7ZzP/L+gPTJVBkL/7shLEF+i9GC/1cV3JmUxEQz6+9ylhUpWSDuqo1N9qEvqS6vTj4USUA==
From cd04fb76dc3dd2aa4b921e61a367015fd104f7df Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Tue, 22 Jun 2021 18:01:29 +0100
Subject: [PATCH 128/164] Fix type error
---
src/stores/SpaceStore.tsx | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/stores/SpaceStore.tsx b/src/stores/SpaceStore.tsx
index d371086b45..f11589485a 100644
--- a/src/stores/SpaceStore.tsx
+++ b/src/stores/SpaceStore.tsx
@@ -335,7 +335,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient {
// rootSpaces.push(space);
// });
- this.orphanedRooms = new Set(orphanedRooms);
+ this.orphanedRooms = new Set(orphanedRooms.map(r => r.roomId));
this.rootSpaces = rootSpaces;
this.parentMap = backrefs;
From cecf0ce299b7208fc2fe03e54c7c7adbeb06a98d Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Tue, 22 Jun 2021 20:41:26 +0100
Subject: [PATCH 129/164] Convert MessagePanel, TimelinePanel, ScrollPanel, and
more to Typescript
---
src/@types/global.d.ts | 32 +-
.../structures/AutoHideScrollbar.tsx | 6 +-
.../{MessagePanel.js => MessagePanel.tsx} | 723 +++++++++---------
.../structures/NotificationPanel.tsx | 3 +-
src/components/structures/RoomDirectory.tsx | 17 +-
src/components/structures/RoomView.tsx | 7 +-
.../{ScrollPanel.js => ScrollPanel.tsx} | 431 ++++++-----
.../{TimelinePanel.js => TimelinePanel.tsx} | 637 ++++++++-------
.../views/dialogs/ForwardDialog.tsx | 33 +-
.../{ErrorBoundary.js => ErrorBoundary.tsx} | 35 +-
.../views/elements/EventListSummary.tsx | 14 +-
.../views/elements/EventTilePreview.tsx | 11 +-
.../views/elements/MemberEventListSummary.tsx | 14 +-
.../{DateSeparator.js => DateSeparator.tsx} | 26 +-
...ErrorBoundary.js => TileErrorBoundary.tsx} | 28 +-
src/components/views/rooms/EventTile.tsx | 18 +-
16 files changed, 1087 insertions(+), 948 deletions(-)
rename src/components/structures/{MessagePanel.js => MessagePanel.tsx} (64%)
rename src/components/structures/{ScrollPanel.js => ScrollPanel.tsx} (73%)
rename src/components/structures/{TimelinePanel.js => TimelinePanel.tsx} (75%)
rename src/components/views/elements/{ErrorBoundary.js => ErrorBoundary.tsx} (80%)
rename src/components/views/messages/{DateSeparator.js => DateSeparator.tsx} (82%)
rename src/components/views/messages/{TileErrorBoundary.js => TileErrorBoundary.tsx} (77%)
diff --git a/src/@types/global.d.ts b/src/@types/global.d.ts
index 7eff341095..f75c17aaf4 100644
--- a/src/@types/global.d.ts
+++ b/src/@types/global.d.ts
@@ -16,6 +16,7 @@ limitations under the License.
import "matrix-js-sdk/src/@types/global"; // load matrix-js-sdk's type extensions first
import * as ModernizrStatic from "modernizr";
+
import ContentMessages from "../ContentMessages";
import { IMatrixClientPeg } from "../MatrixClientPeg";
import ToastStore from "../stores/ToastStore";
@@ -23,25 +24,25 @@ import DeviceListener from "../DeviceListener";
import { RoomListStoreClass } from "../stores/room-list/RoomListStore";
import { PlatformPeg } from "../PlatformPeg";
import RoomListLayoutStore from "../stores/room-list/RoomListLayoutStore";
-import {IntegrationManagers} from "../integrations/IntegrationManagers";
-import {ModalManager} from "../Modal";
+import { IntegrationManagers } from "../integrations/IntegrationManagers";
+import { ModalManager } from "../Modal";
import SettingsStore from "../settings/SettingsStore";
-import {ActiveRoomObserver} from "../ActiveRoomObserver";
-import {Notifier} from "../Notifier";
-import type {Renderer} from "react-dom";
+import { ActiveRoomObserver } from "../ActiveRoomObserver";
+import { Notifier } from "../Notifier";
+import type { Renderer } from "react-dom";
import RightPanelStore from "../stores/RightPanelStore";
import WidgetStore from "../stores/WidgetStore";
import CallHandler from "../CallHandler";
-import {Analytics} from "../Analytics";
+import { Analytics } from "../Analytics";
import CountlyAnalytics from "../CountlyAnalytics";
import UserActivity from "../UserActivity";
-import {ModalWidgetStore} from "../stores/ModalWidgetStore";
+import { ModalWidgetStore } from "../stores/ModalWidgetStore";
import { WidgetLayoutStore } from "../stores/widgets/WidgetLayoutStore";
import VoipUserMapper from "../VoipUserMapper";
-import {SpaceStoreClass} from "../stores/SpaceStore";
+import { SpaceStoreClass } from "../stores/SpaceStore";
import TypingStore from "../stores/TypingStore";
import { EventIndexPeg } from "../indexing/EventIndexPeg";
-import {VoiceRecordingStore} from "../stores/VoiceRecordingStore";
+import { VoiceRecordingStore } from "../stores/VoiceRecordingStore";
import PerformanceMonitor from "../performance";
import UIStore from "../stores/UIStore";
import { SetupEncryptionStore } from "../stores/SetupEncryptionStore";
@@ -127,11 +128,24 @@ declare global {
setSinkId(outputId: string);
}
+ // Add Chrome-specific `instant` ScrollBehaviour
+ type _ScrollBehavior = ScrollBehavior | "instant";
+
+ interface _ScrollOptions {
+ behavior?: _ScrollBehavior;
+ }
+
+ interface _ScrollIntoViewOptions extends _ScrollOptions {
+ block?: ScrollLogicalPosition;
+ inline?: ScrollLogicalPosition;
+ }
+
interface Element {
// Safari & IE11 only have this prefixed: we used prefixed versions
// previously so let's continue to support them for now
webkitRequestFullScreen(options?: FullscreenOptions): Promise;
msRequestFullscreen(options?: FullscreenOptions): Promise;
+ scrollIntoView(arg?: boolean | _ScrollIntoViewOptions): void;
}
interface Error {
diff --git a/src/components/structures/AutoHideScrollbar.tsx b/src/components/structures/AutoHideScrollbar.tsx
index 66f998b616..3b7fee3a08 100644
--- a/src/components/structures/AutoHideScrollbar.tsx
+++ b/src/components/structures/AutoHideScrollbar.tsx
@@ -15,12 +15,12 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
-import React from "react";
+import React, { WheelEvent } from "react";
interface IProps {
className?: string;
- onScroll?: () => void;
- onWheel?: () => void;
+ onScroll?: (event: Event) => void;
+ onWheel?: (event: WheelEvent) => void;
style?: React.CSSProperties
tabIndex?: number,
wrappedRef?: (ref: HTMLDivElement) => void;
diff --git a/src/components/structures/MessagePanel.js b/src/components/structures/MessagePanel.tsx
similarity index 64%
rename from src/components/structures/MessagePanel.js
rename to src/components/structures/MessagePanel.tsx
index eb9611a6fc..492d9d9a53 100644
--- a/src/components/structures/MessagePanel.js
+++ b/src/components/structures/MessagePanel.tsx
@@ -1,7 +1,5 @@
/*
-Copyright 2016 OpenMarket Ltd
-Copyright 2018 New Vector Ltd
-Copyright 2019 The Matrix.org Foundation C.I.C.
+Copyright 2016 - 2021 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -16,32 +14,46 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
-import React, {createRef} from 'react';
+import React, { createRef, KeyboardEvent, ReactNode, SyntheticEvent, TransitionEvent } from 'react';
import ReactDOM from 'react-dom';
-import PropTypes from 'prop-types';
-import shouldHideEvent from '../../shouldHideEvent';
-import {wantsDateSeparator} from '../../DateUtils';
-import * as sdk from '../../index';
+import { Room } from 'matrix-js-sdk/src/models/room';
+import { EventType } from 'matrix-js-sdk/src/@types/event';
+import { MatrixEvent } from 'matrix-js-sdk/src/models/event';
+import { Relations } from "matrix-js-sdk/src/models/relations";
+import { RoomMember } from 'matrix-js-sdk/src/models/room-member';
-import {MatrixClientPeg} from '../../MatrixClientPeg';
+import shouldHideEvent from '../../shouldHideEvent';
+import { wantsDateSeparator } from '../../DateUtils';
+import { MatrixClientPeg } from '../../MatrixClientPeg';
import SettingsStore from '../../settings/SettingsStore';
import RoomContext from "../../contexts/RoomContext";
-import {Layout, LayoutPropType} from "../../settings/Layout";
-import {_t} from "../../languageHandler";
-import {haveTileForEvent} from "../views/rooms/EventTile";
-import {hasText} from "../../TextForEvent";
+import { Layout } from "../../settings/Layout";
+import { _t } from "../../languageHandler";
+import EventTile, { haveTileForEvent, IReadReceiptProps, TileShape } from "../views/rooms/EventTile";
+import { hasText } from "../../TextForEvent";
import IRCTimelineProfileResizer from "../views/elements/IRCTimelineProfileResizer";
import DMRoomMap from "../../utils/DMRoomMap";
import NewRoomIntro from "../views/rooms/NewRoomIntro";
-import {replaceableComponent} from "../../utils/replaceableComponent";
+import { replaceableComponent } from "../../utils/replaceableComponent";
import defaultDispatcher from '../../dispatcher/dispatcher';
+import WhoIsTypingTile from '../views/rooms/WhoIsTypingTile';
+import ScrollPanel, { IScrollState } from "./ScrollPanel";
+import EventListSummary from '../views/elements/EventListSummary';
+import MemberEventListSummary from '../views/elements/MemberEventListSummary';
+import DateSeparator from '../views/messages/DateSeparator';
+import ErrorBoundary from '../views/elements/ErrorBoundary';
+import ResizeNotifier from "../../utils/ResizeNotifier";
+import Spinner from "../views/elements/Spinner";
+import TileErrorBoundary from '../views/messages/TileErrorBoundary';
+import { RoomPermalinkCreator } from "../../utils/permalinks/Permalinks";
+import EditorStateTransfer from "../../utils/EditorStateTransfer";
const CONTINUATION_MAX_INTERVAL = 5 * 60 * 1000; // 5 minutes
-const continuedTypes = ['m.sticker', 'm.room.message'];
+const continuedTypes = [EventType.Sticker, EventType.RoomMessage];
// check if there is a previous event and it has the same sender as this event
// and the types are the same/is in continuedTypes and the time between them is <= CONTINUATION_MAX_INTERVAL
-function shouldFormContinuation(prevEvent, mxEvent) {
+function shouldFormContinuation(prevEvent: MatrixEvent, mxEvent: MatrixEvent): boolean {
// sanity check inputs
if (!prevEvent || !prevEvent.sender || !mxEvent.sender) return false;
// check if within the max continuation period
@@ -52,8 +64,8 @@ function shouldFormContinuation(prevEvent, mxEvent) {
// Some events should appear as continuations from previous events of different types.
if (mxEvent.getType() !== prevEvent.getType() &&
- (!continuedTypes.includes(mxEvent.getType()) ||
- !continuedTypes.includes(prevEvent.getType()))) return false;
+ (!continuedTypes.includes(mxEvent.getType() as EventType) ||
+ !continuedTypes.includes(prevEvent.getType() as EventType))) return false;
// Check if the sender is the same and hasn't changed their displayname/avatar between these events
if (mxEvent.sender.userId !== prevEvent.sender.userId ||
@@ -66,96 +78,161 @@ function shouldFormContinuation(prevEvent, mxEvent) {
return true;
}
-const isMembershipChange = (e) => e.getType() === 'm.room.member' || e.getType() === 'm.room.third_party_invite';
+const isMembershipChange = (e: MatrixEvent): boolean => {
+ return e.getType() === EventType.RoomMember || e.getType() === EventType.RoomThirdPartyInvite;
+}
+
+interface IProps {
+ // the list of MatrixEvents to display
+ events: MatrixEvent[];
+
+ // true to give the component a 'display: none' style.
+ hidden?: boolean;
+
+ // true to show a spinner at the top of the timeline to indicate
+ // back-pagination in progress
+ backPaginating?: boolean;
+
+ // true to show a spinner at the end of the timeline to indicate
+ // forward-pagination in progress
+ forwardPaginating?: boolean;
+
+ // ID of an event to highlight. If undefined, no event will be highlighted.
+ highlightedEventId?: string;
+
+ // The room these events are all in together, if any.
+ // (The notification panel won't have a room here, for example.)
+ room?: Room;
+
+ // Should we show URL Previews
+ showUrlPreview?: boolean;
+
+ // event after which we should show a read marker
+ readMarkerEventId?: string;
+
+ // whether the read marker should be visible
+ readMarkerVisible?: boolean;
+
+ // the userid of our user. This is used to suppress the read marker
+ // for pending messages.
+ ourUserId?: string;
+
+ // true to suppress the date at the start of the timeline
+ suppressFirstDateSeparator?: boolean;
+
+ // whether to show read receipts
+ showReadReceipts?: boolean;
+
+ // true if updates to the event list should cause the scroll panel to
+ // scroll down when we are at the bottom of the window. See ScrollPanel
+ // for more details.
+ stickyBottom?: boolean;
+
+ // className for the panel
+ className: string;
+
+ // shape parameter to be passed to EventTiles
+ tileShape?: TileShape;
+
+ // show twelve hour timestamps
+ isTwelveHour?: boolean;
+
+ // show timestamps always
+ alwaysShowTimestamps?: boolean;
+
+ // whether to show reactions for an event
+ showReactions?: boolean;
+
+ // which layout to use
+ layout?: Layout;
+
+ // whether or not to show flair at all
+ enableFlair?: boolean;
+
+ resizeNotifier: ResizeNotifier;
+ permalinkCreator?: RoomPermalinkCreator;
+ editState?: EditorStateTransfer;
+
+ // callback which is called when the panel is scrolled.
+ onScroll?(event: Event): void;
+
+ // callback which is called when the user interacts with the room timeline
+ onUserScroll(event: SyntheticEvent): void;
+
+ // callback which is called when more content is needed.
+ onFillRequest?(backwards: boolean): Promise;
+
+ // helper function to access relations for an event
+ onUnfillRequest?(backwards: boolean, scrollToken: string): void;
+
+ getRelationsForEvent?(eventId: string, relationType: string, eventType: string): Relations;
+}
+
+interface IState {
+ ghostReadMarkers: string[];
+ showTypingNotifications: boolean;
+}
+
+interface IReadReceiptForUser {
+ lastShownEventId: string;
+ receipt: IReadReceiptProps;
+}
/* (almost) stateless UI component which builds the event tiles in the room timeline.
*/
@replaceableComponent("structures.MessagePanel")
-export default class MessagePanel extends React.Component {
- static propTypes = {
- // true to give the component a 'display: none' style.
- hidden: PropTypes.bool,
-
- // true to show a spinner at the top of the timeline to indicate
- // back-pagination in progress
- backPaginating: PropTypes.bool,
-
- // true to show a spinner at the end of the timeline to indicate
- // forward-pagination in progress
- forwardPaginating: PropTypes.bool,
-
- // the list of MatrixEvents to display
- events: PropTypes.array.isRequired,
-
- // ID of an event to highlight. If undefined, no event will be highlighted.
- highlightedEventId: PropTypes.string,
-
- // The room these events are all in together, if any.
- // (The notification panel won't have a room here, for example.)
- room: PropTypes.object,
-
- // Should we show URL Previews
- showUrlPreview: PropTypes.bool,
-
- // event after which we should show a read marker
- readMarkerEventId: PropTypes.string,
-
- // whether the read marker should be visible
- readMarkerVisible: PropTypes.bool,
-
- // the userid of our user. This is used to suppress the read marker
- // for pending messages.
- ourUserId: PropTypes.string,
-
- // true to suppress the date at the start of the timeline
- suppressFirstDateSeparator: PropTypes.bool,
-
- // whether to show read receipts
- showReadReceipts: PropTypes.bool,
-
- // true if updates to the event list should cause the scroll panel to
- // scroll down when we are at the bottom of the window. See ScrollPanel
- // for more details.
- stickyBottom: PropTypes.bool,
-
- // callback which is called when the panel is scrolled.
- onScroll: PropTypes.func,
-
- // callback which is called when the user interacts with the room timeline
- onUserScroll: PropTypes.func,
-
- // callback which is called when more content is needed.
- onFillRequest: PropTypes.func,
-
- // className for the panel
- className: PropTypes.string.isRequired,
-
- // shape parameter to be passed to EventTiles
- tileShape: PropTypes.string,
-
- // show twelve hour timestamps
- isTwelveHour: PropTypes.bool,
-
- // show timestamps always
- alwaysShowTimestamps: PropTypes.bool,
-
- // helper function to access relations for an event
- getRelationsForEvent: PropTypes.func,
-
- // whether to show reactions for an event
- showReactions: PropTypes.bool,
-
- // which layout to use
- layout: LayoutPropType,
-
- // whether or not to show flair at all
- enableFlair: PropTypes.bool,
- };
-
+export default class MessagePanel extends React.Component {
static contextType = RoomContext;
- constructor(props) {
- super(props);
+ // opaque readreceipt info for each userId; used by ReadReceiptMarker
+ // to manage its animations
+ private readonly readReceiptMap: Record = {};
+
+ // Track read receipts by event ID. For each _shown_ event ID, we store
+ // the list of read receipts to display:
+ // [
+ // {
+ // userId: string,
+ // member: RoomMember,
+ // ts: number,
+ // },
+ // ]
+ // This is recomputed on each render. It's only stored on the component
+ // for ease of passing the data around since it's computed in one pass
+ // over all events.
+ private readReceiptsByEvent: Record = {};
+
+ // Track read receipts by user ID. For each user ID we've ever shown a
+ // a read receipt for, we store an object:
+ // {
+ // lastShownEventId: string,
+ // receipt: {
+ // userId: string,
+ // member: RoomMember,
+ // ts: number,
+ // },
+ // }
+ // so that we can always keep receipts displayed by reverting back to
+ // the last shown event for that user ID when needed. This may feel like
+ // it duplicates the receipt storage in the room, but at this layer, we
+ // are tracking _shown_ event IDs, which the JS SDK knows nothing about.
+ // This is recomputed on each render, using the data from the previous
+ // render as our fallback for any user IDs we can't match a receipt to a
+ // displayed event in the current render cycle.
+ private readReceiptsByUserId: Record = {};
+
+ private readonly showHiddenEventsInTimeline: boolean;
+ private isMounted = false;
+
+ private readMarkerNode = createRef();
+ private whoIsTyping = createRef();
+ private scrollPanel = createRef();
+
+ private readonly showTypingNotificationsWatcherRef: string;
+ private eventNodes: Record;
+
+ constructor(props, context) {
+ super(props, context);
this.state = {
// previous positions the read marker has been in, so we can
@@ -164,65 +241,21 @@ export default class MessagePanel extends React.Component {
showTypingNotifications: SettingsStore.getValue("showTypingNotifications"),
};
- // opaque readreceipt info for each userId; used by ReadReceiptMarker
- // to manage its animations
- this._readReceiptMap = {};
-
- // Track read receipts by event ID. For each _shown_ event ID, we store
- // the list of read receipts to display:
- // [
- // {
- // userId: string,
- // member: RoomMember,
- // ts: number,
- // },
- // ]
- // This is recomputed on each render. It's only stored on the component
- // for ease of passing the data around since it's computed in one pass
- // over all events.
- this._readReceiptsByEvent = {};
-
- // Track read receipts by user ID. For each user ID we've ever shown a
- // a read receipt for, we store an object:
- // {
- // lastShownEventId: string,
- // receipt: {
- // userId: string,
- // member: RoomMember,
- // ts: number,
- // },
- // }
- // so that we can always keep receipts displayed by reverting back to
- // the last shown event for that user ID when needed. This may feel like
- // it duplicates the receipt storage in the room, but at this layer, we
- // are tracking _shown_ event IDs, which the JS SDK knows nothing about.
- // This is recomputed on each render, using the data from the previous
- // render as our fallback for any user IDs we can't match a receipt to a
- // displayed event in the current render cycle.
- this._readReceiptsByUserId = {};
-
// Cache hidden events setting on mount since Settings is expensive to
// query, and we check this in a hot code path.
- this._showHiddenEventsInTimeline =
- SettingsStore.getValue("showHiddenEventsInTimeline");
+ this.showHiddenEventsInTimeline = SettingsStore.getValue("showHiddenEventsInTimeline");
- this._isMounted = false;
-
- this._readMarkerNode = createRef();
- this._whoIsTyping = createRef();
- this._scrollPanel = createRef();
-
- this._showTypingNotificationsWatcherRef =
+ this.showTypingNotificationsWatcherRef =
SettingsStore.watchSetting("showTypingNotifications", null, this.onShowTypingNotificationsChange);
}
componentDidMount() {
- this._isMounted = true;
+ this.isMounted = true;
}
componentWillUnmount() {
- this._isMounted = false;
- SettingsStore.unwatchSetting(this._showTypingNotificationsWatcherRef);
+ this.isMounted = false;
+ SettingsStore.unwatchSetting(this.showTypingNotificationsWatcherRef);
}
componentDidUpdate(prevProps, prevState) {
@@ -235,14 +268,14 @@ export default class MessagePanel extends React.Component {
}
}
- onShowTypingNotificationsChange = () => {
+ private onShowTypingNotificationsChange = (): void => {
this.setState({
showTypingNotifications: SettingsStore.getValue("showTypingNotifications"),
});
};
/* get the DOM node representing the given event */
- getNodeForEventId(eventId) {
+ public getNodeForEventId(eventId: string): HTMLElement {
if (!this.eventNodes) {
return undefined;
}
@@ -252,8 +285,8 @@ export default class MessagePanel extends React.Component {
/* return true if the content is fully scrolled down right now; else false.
*/
- isAtBottom() {
- return this._scrollPanel.current && this._scrollPanel.current.isAtBottom();
+ public isAtBottom(): boolean {
+ return this.scrollPanel.current?.isAtBottom();
}
/* get the current scroll state. See ScrollPanel.getScrollState for
@@ -261,8 +294,8 @@ export default class MessagePanel extends React.Component {
*
* returns null if we are not mounted.
*/
- getScrollState() {
- return this._scrollPanel.current ? this._scrollPanel.current.getScrollState() : null;
+ public getScrollState(): IScrollState {
+ return this.scrollPanel.current?.getScrollState() ?? null;
}
// returns one of:
@@ -271,15 +304,15 @@ export default class MessagePanel extends React.Component {
// -1: read marker is above the window
// 0: read marker is within the window
// +1: read marker is below the window
- getReadMarkerPosition() {
- const readMarker = this._readMarkerNode.current;
- const messageWrapper = this._scrollPanel.current;
+ public getReadMarkerPosition(): number {
+ const readMarker = this.readMarkerNode.current;
+ const messageWrapper = this.scrollPanel.current;
if (!readMarker || !messageWrapper) {
return null;
}
- const wrapperRect = ReactDOM.findDOMNode(messageWrapper).getBoundingClientRect();
+ const wrapperRect = (ReactDOM.findDOMNode(messageWrapper) as HTMLElement).getBoundingClientRect();
const readMarkerRect = readMarker.getBoundingClientRect();
// the read-marker pretends to have zero height when it is actually
@@ -295,17 +328,17 @@ export default class MessagePanel extends React.Component {
/* jump to the top of the content.
*/
- scrollToTop() {
- if (this._scrollPanel.current) {
- this._scrollPanel.current.scrollToTop();
+ public scrollToTop(): void {
+ if (this.scrollPanel.current) {
+ this.scrollPanel.current.scrollToTop();
}
}
/* jump to the bottom of the content.
*/
- scrollToBottom() {
- if (this._scrollPanel.current) {
- this._scrollPanel.current.scrollToBottom();
+ public scrollToBottom(): void {
+ if (this.scrollPanel.current) {
+ this.scrollPanel.current.scrollToBottom();
}
}
@@ -314,9 +347,9 @@ export default class MessagePanel extends React.Component {
*
* @param {number} mult: -1 to page up, +1 to page down
*/
- scrollRelative(mult) {
- if (this._scrollPanel.current) {
- this._scrollPanel.current.scrollRelative(mult);
+ public scrollRelative(mult: number): void {
+ if (this.scrollPanel.current) {
+ this.scrollPanel.current.scrollRelative(mult);
}
}
@@ -325,9 +358,9 @@ export default class MessagePanel extends React.Component {
*
* @param {KeyboardEvent} ev: the keyboard event to handle
*/
- handleScrollKey(ev) {
- if (this._scrollPanel.current) {
- this._scrollPanel.current.handleScrollKey(ev);
+ public handleScrollKey(ev: KeyboardEvent): void {
+ if (this.scrollPanel.current) {
+ this.scrollPanel.current.handleScrollKey(ev);
}
}
@@ -341,38 +374,41 @@ export default class MessagePanel extends React.Component {
* node (specifically, the bottom of it) will be positioned. If omitted, it
* defaults to 0.
*/
- scrollToEvent(eventId, pixelOffset, offsetBase) {
- if (this._scrollPanel.current) {
- this._scrollPanel.current.scrollToToken(eventId, pixelOffset, offsetBase);
+ public scrollToEvent(eventId: string, pixelOffset: number, offsetBase: number): void {
+ if (this.scrollPanel.current) {
+ this.scrollPanel.current.scrollToToken(eventId, pixelOffset, offsetBase);
}
}
- scrollToEventIfNeeded(eventId) {
+ public scrollToEventIfNeeded(eventId: string): void {
const node = this.eventNodes[eventId];
if (node) {
- node.scrollIntoView({block: "nearest", behavior: "instant"});
+ node.scrollIntoView({
+ block: "nearest",
+ behavior: "instant",
+ });
}
}
/* check the scroll state and send out pagination requests if necessary.
*/
- checkFillState() {
- if (this._scrollPanel.current) {
- this._scrollPanel.current.checkFillState();
+ public checkFillState(): void {
+ if (this.scrollPanel.current) {
+ this.scrollPanel.current.checkFillState();
}
}
- _isUnmounting = () => {
- return !this._isMounted;
+ private isUnmounting = (): boolean => {
+ return !this.isMounted;
};
// TODO: Implement granular (per-room) hide options
- _shouldShowEvent(mxEv) {
+ public shouldShowEvent(mxEv: MatrixEvent): boolean {
if (mxEv.sender && MatrixClientPeg.get().isUserIgnored(mxEv.sender.userId)) {
return false; // ignored = no show (only happens if the ignore happens after an event was received)
}
- if (this._showHiddenEventsInTimeline) {
+ if (this.showHiddenEventsInTimeline) {
return true;
}
@@ -386,7 +422,7 @@ export default class MessagePanel extends React.Component {
return !shouldHideEvent(mxEv, this.context);
}
- _readMarkerForEvent(eventId, isLastEvent) {
+ public readMarkerForEvent(eventId: string, isLastEvent: boolean): ReactNode {
const visible = !isLastEvent && this.props.readMarkerVisible;
if (this.props.readMarkerEventId === eventId) {
@@ -405,7 +441,7 @@ export default class MessagePanel extends React.Component {
return (
@@ -424,8 +460,8 @@ export default class MessagePanel extends React.Component {
// transition (ie. the read markers do but the event tiles do not)
// and TransitionGroup requires that all its children are Transitions.
const hr = ;
@@ -445,7 +481,7 @@ export default class MessagePanel extends React.Component {
return null;
}
- _collectGhostReadMarker = (node) => {
+ private collectGhostReadMarker = (node: HTMLElement): void => {
if (node) {
// now the element has appeared, change the style which will trigger the CSS transition
requestAnimationFrame(() => {
@@ -455,15 +491,15 @@ export default class MessagePanel extends React.Component {
}
};
- _onGhostTransitionEnd = (ev) => {
+ private onGhostTransitionEnd = (ev: TransitionEvent): void => {
// we can now clean up the ghost element
- const finishedEventId = ev.target.dataset.eventid;
+ const finishedEventId = (ev.target as HTMLElement).dataset.eventid;
this.setState({
ghostReadMarkers: this.state.ghostReadMarkers.filter(eid => eid !== finishedEventId),
});
};
- _getNextEventInfo(arr, i) {
+ private getNextEventInfo(arr: MatrixEvent[], i: number): { nextEvent: MatrixEvent, nextTile: MatrixEvent } {
const nextEvent = i < arr.length - 1
? arr[i + 1]
: null;
@@ -472,16 +508,16 @@ export default class MessagePanel extends React.Component {
// when rendering the tile. The shouldShowEvent function is pretty quick at what
// it does, so this should have no significant cost even when a room is used for
// not-chat purposes.
- const nextTile = arr.slice(i + 1).find(e => this._shouldShowEvent(e));
+ const nextTile = arr.slice(i + 1).find(e => this.shouldShowEvent(e));
- return {nextEvent, nextTile};
+ return { nextEvent, nextTile };
}
- get _roomHasPendingEdit() {
+ private get roomHasPendingEdit(): string {
return this.props.room && localStorage.getItem(`mx_edit_room_${this.props.room.roomId}`);
}
- _getEventTiles() {
+ private getEventTiles(): ReactNode[] {
this.eventNodes = {};
let i;
@@ -497,7 +533,7 @@ export default class MessagePanel extends React.Component {
let lastShownNonLocalEchoIndex = -1;
for (i = this.props.events.length-1; i >= 0; i--) {
const mxEv = this.props.events[i];
- if (!this._shouldShowEvent(mxEv)) {
+ if (!this.shouldShowEvent(mxEv)) {
continue;
}
@@ -521,18 +557,18 @@ export default class MessagePanel extends React.Component {
// Note: the EventTile might still render a "sent/sending receipt" independent of
// this information. When not providing read receipt information, the tile is likely
// to assume that sent receipts are to be shown more often.
- this._readReceiptsByEvent = {};
+ this.readReceiptsByEvent = {};
if (this.props.showReadReceipts) {
- this._readReceiptsByEvent = this._getReadReceiptsByShownEvent();
+ this.readReceiptsByEvent = this.getReadReceiptsByShownEvent();
}
- let grouper = null;
+ let grouper: BaseGrouper = null;
for (i = 0; i < this.props.events.length; i++) {
const mxEv = this.props.events[i];
const eventId = mxEv.getId();
const last = (mxEv === lastShownEvent);
- const {nextEvent, nextTile} = this._getNextEventInfo(this.props.events, i);
+ const { nextEvent, nextTile } = this.getNextEventInfo(this.props.events, i);
if (grouper) {
if (grouper.shouldGroup(mxEv)) {
@@ -553,26 +589,25 @@ export default class MessagePanel extends React.Component {
}
}
if (!grouper) {
- const wantTile = this._shouldShowEvent(mxEv);
+ const wantTile = this.shouldShowEvent(mxEv);
const isGrouped = false;
if (wantTile) {
- // make sure we unpack the array returned by _getTilesForEvent,
+ // make sure we unpack the array returned by getTilesForEvent,
// otherwise react will auto-generate keys and we will end up
// replacing all of the DOM elements every time we paginate.
- ret.push(...this._getTilesForEvent(prevEvent, mxEv, last, isGrouped,
- nextEvent, nextTile));
+ ret.push(...this.getTilesForEvent(prevEvent, mxEv, last, isGrouped, nextEvent, nextTile));
prevEvent = mxEv;
}
- const readMarker = this._readMarkerForEvent(eventId, i >= lastShownNonLocalEchoIndex);
+ const readMarker = this.readMarkerForEvent(eventId, i >= lastShownNonLocalEchoIndex);
if (readMarker) ret.push(readMarker);
}
}
- if (!this.props.editState && this._roomHasPendingEdit) {
+ if (!this.props.editState && this.roomHasPendingEdit) {
defaultDispatcher.dispatch({
action: "edit_event",
- event: this.props.room.findEventById(this._roomHasPendingEdit),
+ event: this.props.room.findEventById(this.roomHasPendingEdit),
});
}
@@ -583,10 +618,14 @@ export default class MessagePanel extends React.Component {
return ret;
}
- _getTilesForEvent(prevEvent, mxEv, last, isGrouped=false, nextEvent, nextEventWithTile) {
- const TileErrorBoundary = sdk.getComponent('messages.TileErrorBoundary');
- const EventTile = sdk.getComponent('rooms.EventTile');
- const DateSeparator = sdk.getComponent('messages.DateSeparator');
+ public getTilesForEvent(
+ prevEvent: MatrixEvent,
+ mxEv: MatrixEvent,
+ last = false,
+ isGrouped = false,
+ nextEvent?: MatrixEvent,
+ nextEventWithTile?: MatrixEvent,
+ ): ReactNode[] {
const ret = [];
const isEditing = this.props.editState &&
@@ -601,7 +640,7 @@ export default class MessagePanel extends React.Component {
}
// do we need a date separator since the last event?
- const wantsDateSeparator = this._wantsDateSeparator(prevEvent, eventDate);
+ const wantsDateSeparator = this.wantsDateSeparator(prevEvent, eventDate);
if (wantsDateSeparator && !isGrouped) {
const dateSeparator = ;
ret.push(dateSeparator);
@@ -609,7 +648,7 @@ export default class MessagePanel extends React.Component {
let willWantDateSeparator = false;
if (nextEvent) {
- willWantDateSeparator = this._wantsDateSeparator(mxEv, nextEvent.getDate() || new Date());
+ willWantDateSeparator = this.wantsDateSeparator(mxEv, nextEvent.getDate() || new Date());
}
// is this a continuation of the previous message?
@@ -618,12 +657,12 @@ export default class MessagePanel extends React.Component {
const eventId = mxEv.getId();
const highlight = (eventId === this.props.highlightedEventId);
- const readReceipts = this._readReceiptsByEvent[eventId];
+ const readReceipts = this.readReceiptsByEvent[eventId];
let isLastSuccessful = false;
const isSentState = s => !s || s === 'sent';
const isSent = isSentState(mxEv.getAssociatedStatus());
- const hasNextEvent = nextEvent && this._shouldShowEvent(nextEvent);
+ const hasNextEvent = nextEvent && this.shouldShowEvent(nextEvent);
if (!hasNextEvent && isSent) {
isLastSuccessful = true;
} else if (hasNextEvent && isSent && !isSentState(nextEvent.getAssociatedStatus())) {
@@ -649,18 +688,18 @@ export default class MessagePanel extends React.Component {
{
if (!r.userId || r.type !== "m.read" || r.userId === myUserId) {
return; // ignore non-read receipts and receipts from self.
@@ -721,13 +760,13 @@ export default class MessagePanel extends React.Component {
// Get an object that maps from event ID to a list of read receipts that
// should be shown next to that event. If a hidden event has read receipts,
// they are folded into the receipts of the last shown event.
- _getReadReceiptsByShownEvent() {
+ private getReadReceiptsByShownEvent(): Record {
const receiptsByEvent = {};
const receiptsByUserId = {};
let lastShownEventId;
for (const event of this.props.events) {
- if (this._shouldShowEvent(event)) {
+ if (this.shouldShowEvent(event)) {
lastShownEventId = event.getId();
}
if (!lastShownEventId) {
@@ -735,7 +774,7 @@ export default class MessagePanel extends React.Component {
}
const existingReceipts = receiptsByEvent[lastShownEventId] || [];
- const newReceipts = this._getReadReceiptsForEvent(event);
+ const newReceipts = this.getReadReceiptsForEvent(event);
receiptsByEvent[lastShownEventId] = existingReceipts.concat(newReceipts);
// Record these receipts along with their last shown event ID for
@@ -754,16 +793,16 @@ export default class MessagePanel extends React.Component {
// someone which had one in the last. By looking through our previous
// mapping of receipts by user ID, we can cover recover any receipts
// that would have been lost by using the same event ID from last time.
- for (const userId in this._readReceiptsByUserId) {
+ for (const userId in this.readReceiptsByUserId) {
if (receiptsByUserId[userId]) {
continue;
}
- const { lastShownEventId, receipt } = this._readReceiptsByUserId[userId];
+ const { lastShownEventId, receipt } = this.readReceiptsByUserId[userId];
const existingReceipts = receiptsByEvent[lastShownEventId] || [];
receiptsByEvent[lastShownEventId] = existingReceipts.concat(receipt);
receiptsByUserId[userId] = { lastShownEventId, receipt };
}
- this._readReceiptsByUserId = receiptsByUserId;
+ this.readReceiptsByUserId = receiptsByUserId;
// After grouping receipts by shown events, do another pass to sort each
// receipt list.
@@ -776,21 +815,21 @@ export default class MessagePanel extends React.Component {
return receiptsByEvent;
}
- _collectEventNode = (eventId, node) => {
+ private collectEventNode = (eventId: string, node: EventTile): void => {
this.eventNodes[eventId] = node?.ref?.current;
}
// once dynamic content in the events load, make the scrollPanel check the
// scroll offsets.
- _onHeightChanged = () => {
- const scrollPanel = this._scrollPanel.current;
+ public onHeightChanged = (): void => {
+ const scrollPanel = this.scrollPanel.current;
if (scrollPanel) {
scrollPanel.checkScroll();
}
};
- _onTypingShown = () => {
- const scrollPanel = this._scrollPanel.current;
+ private onTypingShown = (): void => {
+ const scrollPanel = this.scrollPanel.current;
// this will make the timeline grow, so checkScroll
scrollPanel.checkScroll();
if (scrollPanel && scrollPanel.getScrollState().stuckAtBottom) {
@@ -798,8 +837,8 @@ export default class MessagePanel extends React.Component {
}
};
- _onTypingHidden = () => {
- const scrollPanel = this._scrollPanel.current;
+ private onTypingHidden = (): void => {
+ const scrollPanel = this.scrollPanel.current;
if (scrollPanel) {
// as hiding the typing notifications doesn't
// update the scrollPanel, we tell it to apply
@@ -811,12 +850,12 @@ export default class MessagePanel extends React.Component {
}
};
- updateTimelineMinHeight() {
- const scrollPanel = this._scrollPanel.current;
+ public updateTimelineMinHeight(): void {
+ const scrollPanel = this.scrollPanel.current;
if (scrollPanel) {
const isAtBottom = scrollPanel.isAtBottom();
- const whoIsTyping = this._whoIsTyping.current;
+ const whoIsTyping = this.whoIsTyping.current;
const isTypingVisible = whoIsTyping && whoIsTyping.isVisible();
// when messages get added to the timeline,
// but somebody else is still typing,
@@ -828,18 +867,14 @@ export default class MessagePanel extends React.Component {
}
}
- onTimelineReset() {
- const scrollPanel = this._scrollPanel.current;
+ public onTimelineReset(): void {
+ const scrollPanel = this.scrollPanel.current;
if (scrollPanel) {
scrollPanel.clearPreventShrinking();
}
}
render() {
- const ErrorBoundary = sdk.getComponent('elements.ErrorBoundary');
- const ScrollPanel = sdk.getComponent("structures.ScrollPanel");
- const WhoIsTypingTile = sdk.getComponent("rooms.WhoIsTypingTile");
- const Spinner = sdk.getComponent("elements.Spinner");
let topSpinner;
let bottomSpinner;
if (this.props.backPaginating) {
@@ -855,9 +890,9 @@ export default class MessagePanel extends React.Component {
if (this.props.room && !this.props.tileShape && this.state.showTypingNotifications) {
whoIsTyping = (
+ onShown={this.onTypingShown}
+ onHidden={this.onTypingHidden}
+ ref={this.whoIsTyping} />
);
}
@@ -873,11 +908,10 @@ export default class MessagePanel extends React.Component {
return (
{ topSpinner }
- { this._getEventTiles() }
+ { this.getEventTiles() }
{ whoIsTyping }
{ bottomSpinner }
@@ -895,6 +929,31 @@ export default class MessagePanel extends React.Component {
}
}
+abstract class BaseGrouper {
+ static canStartGroup = (panel: MessagePanel, ev: MatrixEvent): boolean => true;
+
+ public events: MatrixEvent[] = [];
+ // events that we include in the group but then eject out and place above the group.
+ public ejectedEvents: MatrixEvent[] = [];
+ public readMarker: ReactNode;
+
+ constructor(
+ public readonly panel: MessagePanel,
+ public readonly event: MatrixEvent,
+ public readonly prevEvent: MatrixEvent,
+ public readonly lastShownEvent: MatrixEvent,
+ public readonly nextEvent?: MatrixEvent,
+ public readonly nextEventTile?: MatrixEvent,
+ ) {
+ this.readMarker = panel.readMarkerForEvent(event.getId(), event === lastShownEvent);
+ }
+
+ public abstract shouldGroup(ev: MatrixEvent): boolean;
+ public abstract add(ev: MatrixEvent): void;
+ public abstract getTiles(): ReactNode[];
+ public abstract getNewPrevEvent(): MatrixEvent;
+}
+
/* Grouper classes determine when events can be grouped together in a summary.
* Groupers should have the following methods:
* - canStartGroup (static): determines if a new group should be started with the
@@ -910,36 +969,21 @@ export default class MessagePanel extends React.Component {
// Wrap initial room creation events into an EventListSummary
// Grouping only events sent by the same user that sent the `m.room.create` and only until
// the first non-state event or membership event which is not regarding the sender of the `m.room.create` event
-class CreationGrouper {
- static canStartGroup = function(panel, ev) {
- return ev.getType() === "m.room.create";
+class CreationGrouper extends BaseGrouper {
+ static canStartGroup = function(panel: MessagePanel, ev: MatrixEvent): boolean {
+ return ev.getType() === EventType.RoomCreate;
};
- constructor(panel, createEvent, prevEvent, lastShownEvent) {
- this.panel = panel;
- this.createEvent = createEvent;
- this.prevEvent = prevEvent;
- this.lastShownEvent = lastShownEvent;
- this.events = [];
- // events that we include in the group but then eject out and place
- // above the group.
- this.ejectedEvents = [];
- this.readMarker = panel._readMarkerForEvent(
- createEvent.getId(),
- createEvent === lastShownEvent,
- );
- }
-
- shouldGroup(ev) {
+ public shouldGroup(ev: MatrixEvent): boolean {
const panel = this.panel;
- const createEvent = this.createEvent;
- if (!panel._shouldShowEvent(ev)) {
+ const createEvent = this.event;
+ if (!panel.shouldShowEvent(ev)) {
return true;
}
- if (panel._wantsDateSeparator(this.createEvent, ev.getDate())) {
+ if (panel.wantsDateSeparator(this.event, ev.getDate())) {
return false;
}
- if (ev.getType() === "m.room.member"
+ if (ev.getType() === EventType.RoomMember
&& (ev.getStateKey() !== createEvent.getSender() || ev.getContent()["membership"] !== "join")) {
return false;
}
@@ -949,37 +993,35 @@ class CreationGrouper {
return false;
}
- add(ev) {
+ public add(ev: MatrixEvent): void {
const panel = this.panel;
- this.readMarker = this.readMarker || panel._readMarkerForEvent(
+ this.readMarker = this.readMarker || panel.readMarkerForEvent(
ev.getId(),
ev === this.lastShownEvent,
);
- if (!panel._shouldShowEvent(ev)) {
+ if (!panel.shouldShowEvent(ev)) {
return;
}
- if (ev.getType() === "m.room.encryption") {
+ if (ev.getType() === EventType.RoomEncryption) {
this.ejectedEvents.push(ev);
} else {
this.events.push(ev);
}
}
- getTiles() {
+ public getTiles(): ReactNode[] {
// If we don't have any events to group, don't even try to group them. The logic
// below assumes that we have a group of events to deal with, but we might not if
// the events we were supposed to group were redacted.
if (!this.events || !this.events.length) return [];
- const DateSeparator = sdk.getComponent('messages.DateSeparator');
- const EventListSummary = sdk.getComponent('views.elements.EventListSummary');
const panel = this.panel;
const ret = [];
const isGrouped = true;
- const createEvent = this.createEvent;
+ const createEvent = this.event;
const lastShownEvent = this.lastShownEvent;
- if (panel._wantsDateSeparator(this.prevEvent, createEvent.getDate())) {
+ if (panel.wantsDateSeparator(this.prevEvent, createEvent.getDate())) {
const ts = createEvent.getTs();
ret.push(
,
@@ -987,13 +1029,13 @@ class CreationGrouper {
}
// If this m.room.create event should be shown (room upgrade) then show it before the summary
- if (panel._shouldShowEvent(createEvent)) {
+ if (panel.shouldShowEvent(createEvent)) {
// pass in the createEvent as prevEvent as well so no extra DateSeparator is rendered
- ret.push(...panel._getTilesForEvent(createEvent, createEvent));
+ ret.push(...panel.getTilesForEvent(createEvent, createEvent));
}
for (const ejected of this.ejectedEvents) {
- ret.push(...panel._getTilesForEvent(
+ ret.push(...panel.getTilesForEvent(
createEvent, ejected, createEvent === lastShownEvent, isGrouped,
));
}
@@ -1003,7 +1045,7 @@ class CreationGrouper {
// of EventListSummary, render each member event as if the previous
// one was itself. This way, the timestamp of the previous event === the
// timestamp of the current event, and no DateSeparator is inserted.
- return panel._getTilesForEvent(e, e, e === lastShownEvent, isGrouped);
+ return panel.getTilesForEvent(e, e, e === lastShownEvent, isGrouped);
}).reduce((a, b) => a.concat(b), []);
// Get sender profile from the latest event in the summary as the m.room.create doesn't contain one
const ev = this.events[this.events.length - 1];
@@ -1023,7 +1065,7 @@ class CreationGrouper {
@@ -1038,62 +1080,59 @@ class CreationGrouper {
return ret;
}
- getNewPrevEvent() {
- return this.createEvent;
+ public getNewPrevEvent(): MatrixEvent {
+ return this.event;
}
}
-class RedactionGrouper {
- static canStartGroup = function(panel, ev) {
- return panel._shouldShowEvent(ev) && ev.isRedacted();
+class RedactionGrouper extends BaseGrouper {
+ static canStartGroup = function(panel: MessagePanel, ev: MatrixEvent): boolean {
+ return panel.shouldShowEvent(ev) && ev.isRedacted();
}
- constructor(panel, ev, prevEvent, lastShownEvent, nextEvent, nextEventTile) {
- this.panel = panel;
- this.readMarker = panel._readMarkerForEvent(
- ev.getId(),
- ev === lastShownEvent,
- );
+ constructor(
+ panel: MessagePanel,
+ ev: MatrixEvent,
+ prevEvent: MatrixEvent,
+ lastShownEvent: MatrixEvent,
+ nextEvent: MatrixEvent,
+ nextEventTile: MatrixEvent,
+ ) {
+ super(panel, ev, prevEvent, lastShownEvent, nextEvent, nextEventTile);
this.events = [ev];
- this.prevEvent = prevEvent;
- this.lastShownEvent = lastShownEvent;
- this.nextEvent = nextEvent;
- this.nextEventTile = nextEventTile;
}
- shouldGroup(ev) {
+ public shouldGroup(ev: MatrixEvent): boolean {
// absorb hidden events so that they do not break up streams of messages & redaction events being grouped
- if (!this.panel._shouldShowEvent(ev)) {
+ if (!this.panel.shouldShowEvent(ev)) {
return true;
}
- if (this.panel._wantsDateSeparator(this.events[0], ev.getDate())) {
+ if (this.panel.wantsDateSeparator(this.events[0], ev.getDate())) {
return false;
}
return ev.isRedacted();
}
- add(ev) {
- this.readMarker = this.readMarker || this.panel._readMarkerForEvent(
+ public add(ev: MatrixEvent): void {
+ this.readMarker = this.readMarker || this.panel.readMarkerForEvent(
ev.getId(),
ev === this.lastShownEvent,
);
- if (!this.panel._shouldShowEvent(ev)) {
+ if (!this.panel.shouldShowEvent(ev)) {
return;
}
this.events.push(ev);
}
- getTiles() {
+ public getTiles(): ReactNode[] {
if (!this.events || !this.events.length) return [];
- const DateSeparator = sdk.getComponent('messages.DateSeparator');
- const EventListSummary = sdk.getComponent('views.elements.EventListSummary');
const isGrouped = true;
const panel = this.panel;
const ret = [];
const lastShownEvent = this.lastShownEvent;
- if (panel._wantsDateSeparator(this.prevEvent, this.events[0].getDate())) {
+ if (panel.wantsDateSeparator(this.prevEvent, this.events[0].getDate())) {
const ts = this.events[0].getTs();
ret.push(
,
@@ -1104,11 +1143,11 @@ class RedactionGrouper {
this.prevEvent ? this.events[0].getId() : "initial"
);
- const senders = new Set();
+ const senders = new Set();
let eventTiles = this.events.map((e, i) => {
senders.add(e.sender);
const prevEvent = i === 0 ? this.prevEvent : this.events[i - 1];
- return panel._getTilesForEvent(
+ return panel.getTilesForEvent(
prevEvent, e, e === lastShownEvent, isGrouped, this.nextEvent, this.nextEventTile);
}).reduce((a, b) => a.concat(b), []);
@@ -1121,7 +1160,7 @@ class RedactionGrouper {
key={key}
threshold={2}
events={this.events}
- onToggle={panel._onHeightChanged} // Update scroll state
+ onToggle={panel.onHeightChanged} // Update scroll state
summaryMembers={Array.from(senders)}
summaryText={_t("%(count)s messages deleted.", { count: eventTiles.length })}
>
@@ -1136,61 +1175,58 @@ class RedactionGrouper {
return ret;
}
- getNewPrevEvent() {
+ public getNewPrevEvent(): MatrixEvent {
return this.events[this.events.length - 1];
}
}
// Wrap consecutive member events in a ListSummary, ignore if redacted
-class MemberGrouper {
- static canStartGroup = function(panel, ev) {
- return panel._shouldShowEvent(ev) && isMembershipChange(ev);
+class MemberGrouper extends BaseGrouper {
+ static canStartGroup = function(panel: MessagePanel, ev: MatrixEvent): boolean {
+ return panel.shouldShowEvent(ev) && isMembershipChange(ev);
}
- constructor(panel, ev, prevEvent, lastShownEvent) {
- this.panel = panel;
- this.readMarker = panel._readMarkerForEvent(
- ev.getId(),
- ev === lastShownEvent,
- );
- this.events = [ev];
- this.prevEvent = prevEvent;
- this.lastShownEvent = lastShownEvent;
+ constructor(
+ public readonly panel: MessagePanel,
+ public readonly event: MatrixEvent,
+ public readonly prevEvent: MatrixEvent,
+ public readonly lastShownEvent: MatrixEvent,
+ ) {
+ super(panel, event, prevEvent, lastShownEvent);
+ this.events = [event];
}
- shouldGroup(ev) {
- if (this.panel._wantsDateSeparator(this.events[0], ev.getDate())) {
+ public shouldGroup(ev: MatrixEvent): boolean {
+ if (this.panel.wantsDateSeparator(this.events[0], ev.getDate())) {
return false;
}
return isMembershipChange(ev);
}
- add(ev) {
- if (ev.getType() === 'm.room.member') {
+ public add(ev: MatrixEvent): void {
+ if (ev.getType() === EventType.RoomMember) {
// We can ignore any events that don't actually have a message to display
if (!hasText(ev)) return;
}
- this.readMarker = this.readMarker || this.panel._readMarkerForEvent(
+ this.readMarker = this.readMarker || this.panel.readMarkerForEvent(
ev.getId(),
ev === this.lastShownEvent,
);
this.events.push(ev);
}
- getTiles() {
+ public getTiles(): ReactNode[] {
// If we don't have any events to group, don't even try to group them. The logic
// below assumes that we have a group of events to deal with, but we might not if
// the events we were supposed to group were redacted.
if (!this.events || !this.events.length) return [];
- const DateSeparator = sdk.getComponent('messages.DateSeparator');
- const MemberEventListSummary = sdk.getComponent('views.elements.MemberEventListSummary');
const isGrouped = true;
const panel = this.panel;
const lastShownEvent = this.lastShownEvent;
const ret = [];
- if (panel._wantsDateSeparator(this.prevEvent, this.events[0].getDate())) {
+ if (panel.wantsDateSeparator(this.prevEvent, this.events[0].getDate())) {
const ts = this.events[0].getTs();
ret.push(