Merge branch 'develop' of github.com:matrix-org/matrix-react-sdk into t3chguy/fix/17605

 Conflicts:
	src/components/views/spaces/SpaceTreeLevel.tsx
This commit is contained in:
Michael Telatynski 2021-06-22 22:49:28 +01:00
commit 9f20d6661d
100 changed files with 1946 additions and 1139 deletions

View file

@ -89,7 +89,7 @@
"qrcode": "^1.4.4", "qrcode": "^1.4.4",
"re-resizable": "^6.9.0", "re-resizable": "^6.9.0",
"react": "^17.0.2", "react": "^17.0.2",
"react-beautiful-dnd": "^4.0.1", "react-beautiful-dnd": "^13.1.0",
"react-dom": "^17.0.2", "react-dom": "^17.0.2",
"react-focus-lock": "^2.5.0", "react-focus-lock": "^2.5.0",
"react-transition-group": "^4.4.1", "react-transition-group": "^4.4.1",
@ -123,6 +123,7 @@
"@sinonjs/fake-timers": "^7.0.2", "@sinonjs/fake-timers": "^7.0.2",
"@types/classnames": "^2.2.11", "@types/classnames": "^2.2.11",
"@types/counterpart": "^0.18.1", "@types/counterpart": "^0.18.1",
"@types/diff-match-patch": "^1.0.5",
"@types/flux": "^3.1.9", "@types/flux": "^3.1.9",
"@types/jest": "^26.0.20", "@types/jest": "^26.0.20",
"@types/linkifyjs": "^2.1.3", "@types/linkifyjs": "^2.1.3",
@ -132,8 +133,9 @@
"@types/pako": "^1.0.1", "@types/pako": "^1.0.1",
"@types/parse5": "^6.0.0", "@types/parse5": "^6.0.0",
"@types/qrcode": "^1.3.5", "@types/qrcode": "^1.3.5",
"@types/react": "^16.9", "@types/react": "^17.0.2",
"@types/react-dom": "^16.9.10", "@types/react-beautiful-dnd": "^13.0.0",
"@types/react-dom": "^17.0.2",
"@types/react-transition-group": "^4.4.0", "@types/react-transition-group": "^4.4.0",
"@types/sanitize-html": "^2.3.1", "@types/sanitize-html": "^2.3.1",
"@types/zxcvbn": "^4.4.0", "@types/zxcvbn": "^4.4.0",
@ -167,9 +169,6 @@
"typescript": "^4.1.3", "typescript": "^4.1.3",
"walk": "^2.3.14" "walk": "^2.3.14"
}, },
"resolutions": {
"**/@types/react": "^16.14"
},
"jest": { "jest": {
"testEnvironment": "./__test-utils__/environment.js", "testEnvironment": "./__test-utils__/environment.js",
"testMatch": [ "testMatch": [

View file

@ -123,7 +123,6 @@
@import "./views/elements/_EventListSummary.scss"; @import "./views/elements/_EventListSummary.scss";
@import "./views/elements/_FacePile.scss"; @import "./views/elements/_FacePile.scss";
@import "./views/elements/_Field.scss"; @import "./views/elements/_Field.scss";
@import "./views/elements/_FormButton.scss";
@import "./views/elements/_ImageView.scss"; @import "./views/elements/_ImageView.scss";
@import "./views/elements/_InfoTooltip.scss"; @import "./views/elements/_InfoTooltip.scss";
@import "./views/elements/_InlineSpinner.scss"; @import "./views/elements/_InlineSpinner.scss";

View file

@ -31,7 +31,6 @@ $activeBorderColor: $secondary-fg-color;
// Create another flexbox so the Panel fills the container // Create another flexbox so the Panel fills the container
display: flex; display: flex;
flex-direction: column; flex-direction: column;
overflow-y: auto;
.mx_SpacePanel_spaceTreeWrapper { .mx_SpacePanel_spaceTreeWrapper {
flex: 1; flex: 1;
@ -69,6 +68,12 @@ $activeBorderColor: $secondary-fg-color;
cursor: pointer; cursor: pointer;
} }
.mx_SpaceItem_dragging {
.mx_SpaceButton_toggleCollapse {
visibility: hidden;
}
}
.mx_SpaceTreeLevel { .mx_SpaceTreeLevel {
display: flex; display: flex;
flex-direction: column; flex-direction: column;

View file

@ -134,8 +134,9 @@ limitations under the License.
.mx_Toast_buttons { .mx_Toast_buttons {
float: right; float: right;
display: flex; display: flex;
gap: 5px;
.mx_FormButton { .mx_AccessibleButton {
min-width: 96px; min-width: 96px;
box-sizing: border-box; box-sizing: border-box;
} }

View file

@ -19,49 +19,68 @@ limitations under the License.
padding: 24px; padding: 24px;
background-color: $settings-profile-placeholder-bg-color; background-color: $settings-profile-placeholder-bg-color;
border-radius: 8px; border-radius: 8px;
display: flex;
box-sizing: border-box; box-sizing: border-box;
> div { .mx_BetaCard_columns {
.mx_BetaCard_title { display: flex;
font-weight: $font-semi-bold;
font-size: $font-18px;
line-height: $font-22px;
color: $primary-fg-color;
margin: 4px 0 14px;
.mx_BetaCard_betaPill { > div {
margin-left: 12px; .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 { > img {
font-size: $font-15px; margin: auto 0 auto 20px;
line-height: $font-20px; width: 300px;
color: $secondary-fg-color; object-fit: contain;
margin-bottom: 20px; height: 100%;
}
.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;
} }
} }
> img { .mx_BetaCard_relatedSettings {
margin: auto 0 auto 20px; .mx_SettingsFlag {
width: 300px; margin: 16px 0 0;
object-fit: contain; font-size: $font-15px;
height: 100%; 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;
}
}
} }
} }

View file

@ -38,6 +38,15 @@ limitations under the License.
mask-image: url('$(res)/img/element-icons/view-community.svg'); 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 { .mx_TagTileContextMenu_hideCommunity::before {
mask-image: url('$(res)/img/element-icons/hide.svg'); mask-image: url('$(res)/img/element-icons/hide.svg');
} }

View file

@ -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;
}
}

View file

@ -259,16 +259,6 @@ limitations under the License.
.mx_AccessibleButton.mx_AccessibleButton_hasKind { .mx_AccessibleButton.mx_AccessibleButton_hasKind {
padding: 8px 18px; 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, .mx_VerificationShowSas .mx_AccessibleButton,

View file

@ -58,7 +58,7 @@ limitations under the License.
} }
.mx_VerificationPanel_reciprocate_section { .mx_VerificationPanel_reciprocate_section {
.mx_FormButton { .mx_AccessibleButton {
width: 100%; width: 100%;
box-sizing: border-box; box-sizing: border-box;
padding: 10px; padding: 10px;

View file

@ -73,7 +73,7 @@ limitations under the License.
} }
} }
.mx_FormButton { .mx_AccessibleButton {
padding: 8px 22px; padding: 8px 22px;
margin-left: auto; margin-left: auto;
display: block; display: block;

View file

@ -24,19 +24,19 @@ clone() {
# Try the PR author's branch in case it exists on the deps as well. # Try the PR author's branch in case it exists on the deps as well.
# First we check if GITHUB_HEAD_REF is defined, # First we check if GITHUB_HEAD_REF is defined,
# Then we check if BUILDKITE_BRANCH 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+x} ]; then if [ -n "$GITHUB_HEAD_REF" ]; then
head=$GITHUB_HEAD_REF head=$GITHUB_HEAD_REF
elif [ -n ${BUILDKITE_BRANCH+x} ]; then elif [ -n "$BUILDKITE_BRANCH" ]; then
head=$BUILDKITE_BRANCH head=$BUILDKITE_BRANCH
else else
# Netlify doesn't give us info about the fork so we have to get it from GitHub API # 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="https://api.github.com/repos/matrix-org/matrix-react-sdk/pulls/"
apiEndpoint+=$REVIEW_ID apiEndpoint+=$REVIEW_ID
head=$(curl $apiEndpoint | jq -r '.head.label') head=$(curl $apiEndpoint | jq -r '.head.label')
fi 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 # * "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 # * "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. # We can split on `:` into an array to check.
@ -44,19 +44,26 @@ fi
# to determine whether the branch is from a fork or not # to determine whether the branch is from a fork or not
BRANCH_ARRAY=(${head//:/ }) BRANCH_ARRAY=(${head//:/ })
if [[ "${#BRANCH_ARRAY[@]}" == "1" ]]; then if [[ "${#BRANCH_ARRAY[@]}" == "1" ]]; then
if [[ "$GITHUB_REPOSITORY" = "$deforg/$defrepo" ]]; then
clone $deforg $defrepo $head if [ -n "$GITHUB_HEAD_REF" ]; then
if [[ "$GITHUB_REPOSITORY" == "$deforg"* ]]; then
clone $deforg $defrepo $GITHUB_HEAD_REF
else
REPO_ARRAY=(${GITHUB_REPOSITORY//\// })
clone $REPO_ARRAY[0] $defrepo $GITHUB_HEAD_REF
fi
else else
clone $GITHUB_ACTOR $defrepo $head clone $deforg $defrepo $BUILDKITE_BRANCH
fi fi
elif [[ "${#BRANCH_ARRAY[@]}" == "2" ]]; then elif [[ "${#BRANCH_ARRAY[@]}" == "2" ]]; then
clone ${BRANCH_ARRAY[0]} $defrepo ${BRANCH_ARRAY[1]} clone ${BRANCH_ARRAY[0]} $defrepo ${BRANCH_ARRAY[1]}
fi fi
# Try the target branch of the push or PR. # 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 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 clone $deforg $defrepo $BUILDKITE_PULL_REQUEST_BASE_BRANCH
fi fi

50
src/@types/diff-dom.ts Normal file
View file

@ -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[];
}
}

View file

@ -113,19 +113,6 @@ declare global {
usageDetails?: {[key: string]: number}; usageDetails?: {[key: string]: number};
} }
export interface ISettledFulfilled<T> {
status: "fulfilled";
value: T;
}
export interface ISettledRejected {
status: "rejected";
reason: any;
}
interface PromiseConstructor {
allSettled<T>(promises: Promise<T>[]): Promise<Array<ISettledFulfilled<T> | ISettledRejected>>;
}
interface HTMLAudioElement { interface HTMLAudioElement {
type?: string; type?: string;
// sinkId & setSinkId are experimental and typescript doesn't know about them // sinkId & setSinkId are experimental and typescript doesn't know about them

View file

@ -17,13 +17,12 @@ limitations under the License.
import { RoomMember } from "matrix-js-sdk/src/models/room-member"; import { RoomMember } from "matrix-js-sdk/src/models/room-member";
import { User } from "matrix-js-sdk/src/models/user"; import { User } from "matrix-js-sdk/src/models/user";
import { Room } from "matrix-js-sdk/src/models/room"; import { Room } from "matrix-js-sdk/src/models/room";
import { ResizeMethod } from "matrix-js-sdk/src/@types/partials";
import DMRoomMap from './utils/DMRoomMap'; import DMRoomMap from './utils/DMRoomMap';
import { mediaFromMxc } from "./customisations/Media"; import { mediaFromMxc } from "./customisations/Media";
import SettingsStore from "./settings/SettingsStore"; 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 // Not to be used for BaseAvatar urls as that has similar default avatar fallback already
export function avatarUrlForMember( export function avatarUrlForMember(
member: RoomMember, member: RoomMember,

View file

@ -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"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
@ -14,34 +14,40 @@ See the License for the specific language governing permissions and
limitations under the License. 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 { export class DecryptionFailure {
constructor(failedEventId, errorCode) { public readonly ts: number;
this.failedEventId = failedEventId;
this.errorCode = errorCode; constructor(public readonly failedEventId: string, public readonly errorCode: string) {
this.ts = Date.now(); this.ts = Date.now();
} }
} }
type TrackingFn = (count: number, trackedErrCode: string) => void;
type ErrCodeMapFn = (errcode: string) => string;
export class DecryptionFailureTracker { export class DecryptionFailureTracker {
// Array of items of type DecryptionFailure. Every `CHECK_INTERVAL_MS`, this list // 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 // is checked for failures that happened > `GRACE_PERIOD_MS` ago. Those that did
// are accumulated in `failureCounts`. // are accumulated in `failureCounts`.
failures = []; public failures: DecryptionFailure[] = [];
// A histogram of the number of failures that will be tracked at the next tracking // A histogram of the number of failures that will be tracked at the next tracking
// interval, split by failure error code. // interval, split by failure error code.
failureCounts = { public failureCounts: Record<string, number> = {
// [errorCode]: 42 // [errorCode]: 42
}; };
// Event IDs of failures that were tracked previously // Event IDs of failures that were tracked previously
trackedEventHashMap = { public trackedEventHashMap: Record<string, boolean> = {
// [eventId]: true // [eventId]: true
}; };
// Set to an interval ID when `start` is called // Set to an interval ID when `start` is called
checkInterval = null; public checkInterval: NodeJS.Timeout = null;
trackInterval = null; public trackInterval: NodeJS.Timeout = null;
// Spread the load on `Analytics` by tracking at a low frequency, `TRACK_INTERVAL_MS`. // Spread the load on `Analytics` by tracking at a low frequency, `TRACK_INTERVAL_MS`.
static TRACK_INTERVAL_MS = 60000; static TRACK_INTERVAL_MS = 60000;
@ -67,7 +73,7 @@ export class DecryptionFailureTracker {
* @param {function?} errorCodeMapFn The function used to map error codes to the * @param {function?} errorCodeMapFn The function used to map error codes to the
* trackedErrorCode. If not provided, the `.code` of errors will be used. * trackedErrorCode. If not provided, the `.code` of errors will be used.
*/ */
constructor(fn, errorCodeMapFn) { constructor(private readonly fn: TrackingFn, private readonly errorCodeMapFn?: ErrCodeMapFn) {
if (!fn || typeof fn !== 'function') { if (!fn || typeof fn !== 'function') {
throw new Error('DecryptionFailureTracker requires tracking function'); throw new Error('DecryptionFailureTracker requires tracking function');
} }
@ -75,9 +81,6 @@ export class DecryptionFailureTracker {
if (errorCodeMapFn && typeof errorCodeMapFn !== 'function') { if (errorCodeMapFn && typeof errorCodeMapFn !== 'function') {
throw new Error('DecryptionFailureTracker second constructor argument should be a function'); throw new Error('DecryptionFailureTracker second constructor argument should be a function');
} }
this._trackDecryptionFailure = fn;
this._mapErrorCode = errorCodeMapFn;
} }
// loadTrackedEventHashMap() { // loadTrackedEventHashMap() {
@ -88,7 +91,7 @@ export class DecryptionFailureTracker {
// localStorage.setItem('mx-decryption-failure-event-id-hashes', JSON.stringify(this.trackedEventHashMap)); // 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) { if (err) {
this.addDecryptionFailure(new DecryptionFailure(e.getId(), err.code)); this.addDecryptionFailure(new DecryptionFailure(e.getId(), err.code));
} else { } else {
@ -97,18 +100,18 @@ export class DecryptionFailureTracker {
} }
} }
addDecryptionFailure(failure) { public addDecryptionFailure(failure: DecryptionFailure): void {
this.failures.push(failure); this.failures.push(failure);
} }
removeDecryptionFailuresForEvent(e) { public removeDecryptionFailuresForEvent(e: MatrixEvent): void {
this.failures = this.failures.filter((f) => f.failedEventId !== e.getId()); this.failures = this.failures.filter((f) => f.failedEventId !== e.getId());
} }
/** /**
* Start checking for and tracking failures. * Start checking for and tracking failures.
*/ */
start() { public start(): void {
this.checkInterval = setInterval( this.checkInterval = setInterval(
() => this.checkFailures(Date.now()), () => this.checkFailures(Date.now()),
DecryptionFailureTracker.CHECK_INTERVAL_MS, DecryptionFailureTracker.CHECK_INTERVAL_MS,
@ -123,7 +126,7 @@ export class DecryptionFailureTracker {
/** /**
* Clear state and stop checking for and tracking failures. * Clear state and stop checking for and tracking failures.
*/ */
stop() { public stop(): void {
clearInterval(this.checkInterval); clearInterval(this.checkInterval);
clearInterval(this.trackInterval); 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. * tracked. Only mark one failure per event ID.
* @param {number} nowTs the timestamp that represents the time now. * @param {number} nowTs the timestamp that represents the time now.
*/ */
checkFailures(nowTs) { public checkFailures(nowTs: number): void {
const failuresGivenGrace = []; const failuresGivenGrace = [];
const failuresNotReady = []; const failuresNotReady = [];
while (this.failures.length > 0) { while (this.failures.length > 0) {
@ -175,10 +178,10 @@ export class DecryptionFailureTracker {
const dedupedFailures = dedupedFailuresMap.values(); const dedupedFailures = dedupedFailuresMap.values();
this._aggregateFailures(dedupedFailures); this.aggregateFailures(dedupedFailures);
} }
_aggregateFailures(failures) { private aggregateFailures(failures: DecryptionFailure[]): void {
for (const failure of failures) { for (const failure of failures) {
const errorCode = failure.errorCode; const errorCode = failure.errorCode;
this.failureCounts[errorCode] = (this.failureCounts[errorCode] || 0) + 1; 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 * If there are failures that should be tracked, call the given trackDecryptionFailure
* function with the number of failures that should be tracked. * function with the number of failures that should be tracked.
*/ */
trackFailures() { public trackFailures(): void {
for (const errorCode of Object.keys(this.failureCounts)) { for (const errorCode of Object.keys(this.failureCounts)) {
if (this.failureCounts[errorCode] > 0) { 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; this.failureCounts[errorCode] = 0;
} }
} }

View file

@ -17,11 +17,10 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import React from 'react'; import React, { ReactNode } from 'react';
import sanitizeHtml from 'sanitize-html'; import sanitizeHtml from 'sanitize-html';
import { IExtendedSanitizeOptions } from './@types/sanitize-html'; import cheerio from 'cheerio';
import * as linkify from 'linkifyjs'; import * as linkify from 'linkifyjs';
import linkifyMatrix from './linkify-matrix';
import _linkifyElement from 'linkifyjs/element'; import _linkifyElement from 'linkifyjs/element';
import _linkifyString from 'linkifyjs/string'; import _linkifyString from 'linkifyjs/string';
import classNames from 'classnames'; import classNames from 'classnames';
@ -29,13 +28,15 @@ import EMOJIBASE_REGEX from 'emojibase-regex';
import url from 'url'; import url from 'url';
import katex from 'katex'; import katex from 'katex';
import { AllHtmlEntities } from 'html-entities'; import { AllHtmlEntities } from 'html-entities';
import SettingsStore from './settings/SettingsStore'; import { IContent } from 'matrix-js-sdk/src/models/event';
import cheerio from 'cheerio';
import {tryTransformPermalinkToLocalHref} from "./utils/permalinks/Permalinks"; import { IExtendedSanitizeOptions } from './@types/sanitize-html';
import {SHORTCODE_TO_EMOJI, getEmojiFromUnicode} from "./emoji"; 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 ReplyThread from "./components/views/elements/ReplyThread";
import {mediaFromMxc} from "./customisations/Media"; import { mediaFromMxc } from "./customisations/Media";
linkifyMatrix(linkify); linkifyMatrix(linkify);
@ -66,7 +67,7 @@ export const PERMITTED_URL_SCHEMES = ['http', 'https', 'ftp', 'mailto', 'magnet'
* need emojification. * need emojification.
* unicodeToImage uses this function. * unicodeToImage uses this function.
*/ */
function mightContainEmoji(str: string) { function mightContainEmoji(str: string): boolean {
return SURROGATE_PAIR_PATTERN.test(str) || SYMBOL_PATTERN.test(str); return SURROGATE_PAIR_PATTERN.test(str) || SYMBOL_PATTERN.test(str);
} }
@ -76,7 +77,7 @@ function mightContainEmoji(str: string) {
* @param {String} char The emoji character * @param {String} char The emoji character
* @return {String} The shortcode (such as :thumbup:) * @return {String} The shortcode (such as :thumbup:)
*/ */
export function unicodeToShortcode(char: string) { export function unicodeToShortcode(char: string): string {
const data = getEmojiFromUnicode(char); const data = getEmojiFromUnicode(char);
return (data && data.shortcodes ? `:${data.shortcodes[0]}:` : ''); return (data && data.shortcodes ? `:${data.shortcodes[0]}:` : '');
} }
@ -87,7 +88,7 @@ export function unicodeToShortcode(char: string) {
* @param {String} shortcode The shortcode (such as :thumbup:) * @param {String} shortcode The shortcode (such as :thumbup:)
* @return {String} The emoji character; null if none exists * @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); shortcode = shortcode.slice(1, shortcode.length - 1);
const data = SHORTCODE_TO_EMOJI.get(shortcode); const data = SHORTCODE_TO_EMOJI.get(shortcode);
return data ? data.unicode : null; 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 * Given an untrusted HTML string, return a React node with an sanitized version
* of that HTML. * of that HTML.
*/ */
export function sanitizedHtmlNode(insaneHtml: string) { export function sanitizedHtmlNode(insaneHtml: string): ReactNode {
const saneHtml = sanitizeHtml(insaneHtml, sanitizeHtmlParams); const saneHtml = sanitizeHtml(insaneHtml, sanitizeHtmlParams);
return <div dangerouslySetInnerHTML={{ __html: saneHtml }} dir="auto" />; return <div dangerouslySetInnerHTML={{ __html: saneHtml }} dir="auto" />;
} }
export function getHtmlText(insaneHtml: string) { export function getHtmlText(insaneHtml: string): string {
return sanitizeHtml(insaneHtml, { return sanitizeHtml(insaneHtml, {
allowedTags: [], allowedTags: [],
allowedAttributes: {}, allowedAttributes: {},
@ -148,7 +149,7 @@ export function getHtmlText(insaneHtml: string) {
* other places we need to sanitise URLs. * other places we need to sanitise URLs.
* @return true if permitted, otherwise false * @return true if permitted, otherwise false
*/ */
export function isUrlPermitted(inputUrl: string) { export function isUrlPermitted(inputUrl: string): boolean {
try { try {
const parsed = url.parse(inputUrl); const parsed = url.parse(inputUrl);
if (!parsed.protocol) return false; if (!parsed.protocol) return false;
@ -351,13 +352,6 @@ class HtmlHighlighter extends BaseHighlighter<string> {
} }
} }
interface IContent {
format?: string;
// eslint-disable-next-line camelcase
formatted_body?: string;
body: string;
}
interface IOpts { interface IOpts {
highlightLink?: string; highlightLink?: string;
disableBigEmoji?: boolean; disableBigEmoji?: boolean;
@ -367,6 +361,14 @@ interface IOpts {
ref?: React.Ref<any>; ref?: React.Ref<any>;
} }
export interface IOptsReturnNode extends IOpts {
returnString: false;
}
export interface IOptsReturnString extends IOpts {
returnString: true;
}
/* turn a matrix event body into html /* turn a matrix event body into html
* *
* content: 'content' of the MatrixEvent * 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.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) * 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 = {}) { export function bodyToHtml(content: IContent, highlights: string[], opts: IOpts = {}) {
const isHtmlMessage = content.format === "org.matrix.custom.html" && content.formatted_body; const isHtmlMessage = content.format === "org.matrix.custom.html" && content.formatted_body;
let bodyHasEmoji = false; let bodyHasEmoji = false;
@ -501,7 +505,7 @@ export function bodyToHtml(content: IContent, highlights: string[], opts: IOpts
* @param {object} [options] Options for linkifyString. Default: linkifyMatrix.options * @param {object} [options] Options for linkifyString. Default: linkifyMatrix.options
* @returns {string} Linkified string * @returns {string} Linkified string
*/ */
export function linkifyString(str: string, options = linkifyMatrix.options) { export function linkifyString(str: string, options = linkifyMatrix.options): string {
return _linkifyString(str, options); return _linkifyString(str, options);
} }
@ -512,7 +516,7 @@ export function linkifyString(str: string, options = linkifyMatrix.options) {
* @param {object} [options] Options for linkifyElement. Default: linkifyMatrix.options * @param {object} [options] Options for linkifyElement. Default: linkifyMatrix.options
* @returns {object} * @returns {object}
*/ */
export function linkifyElement(element: HTMLElement, options = linkifyMatrix.options) { export function linkifyElement(element: HTMLElement, options = linkifyMatrix.options): HTMLElement {
return _linkifyElement(element, options); return _linkifyElement(element, options);
} }
@ -523,7 +527,7 @@ export function linkifyElement(element: HTMLElement, options = linkifyMatrix.opt
* @param {object} [options] Options for linkifyString. Default: linkifyMatrix.options * @param {object} [options] Options for linkifyString. Default: linkifyMatrix.options
* @returns {string} * @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); return sanitizeHtml(linkifyString(dirtyHtml, options), sanitizeHtmlParams);
} }
@ -534,7 +538,7 @@ export function linkifyAndSanitizeHtml(dirtyHtml: string, options = linkifyMatri
* @param {Node} node * @param {Node} node
* @returns {bool} * @returns {bool}
*/ */
export function checkBlockNode(node: Node) { export function checkBlockNode(node: Node): boolean {
switch (node.nodeName) { switch (node.nodeName) {
case "H1": case "H1":
case "H2": case "H2":

View file

@ -385,7 +385,7 @@ export class ModalManager {
</div> </div>
); );
ReactDOM.render(dialog, ModalManager.getOrCreateContainer()); setImmediate(() => ReactDOM.render(dialog, ModalManager.getOrCreateContainer()));
} else { } else {
// This is safe to call repeatedly if we happen to do that // This is safe to call repeatedly if we happen to do that
ReactDOM.unmountComponentAtNode(ModalManager.getOrCreateContainer()); ReactDOM.unmountComponentAtNode(ModalManager.getOrCreateContainer());

View file

@ -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"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
@ -14,7 +14,9 @@ See the License for the specific language governing permissions and
limitations under the License. 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, * 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 * @param {Object} room The room object
* @returns {string} A display alias for the given room * @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]; return room.getCanonicalAlias() || room.getAltAliases()[0];
} }
export function looksLikeDirectMessageRoom(room, myUserId) { export function looksLikeDirectMessageRoom(room: Room, myUserId: string): boolean {
const myMembership = room.getMyMembership(); const myMembership = room.getMyMembership();
const me = room.getMember(myUserId); const me = room.getMember(myUserId);
@ -48,7 +50,7 @@ export function looksLikeDirectMessageRoom(room, myUserId) {
return false; return false;
} }
export function guessAndSetDMRoom(room, isDirect) { export function guessAndSetDMRoom(room: Room, isDirect: boolean): Promise<void> {
let newTarget; let newTarget;
if (isDirect) { if (isDirect) {
const guessedUserId = guessDMRoomTargetId( const guessedUserId = guessDMRoomTargetId(
@ -70,7 +72,7 @@ export function guessAndSetDMRoom(room, isDirect) {
this room as a DM room this room as a DM room
* @returns {object} A promise * @returns {object} A promise
*/ */
export function setDMRoom(roomId, userId) { export function setDMRoom(roomId: string, userId: string): Promise<void> {
if (MatrixClientPeg.get().isGuest()) { if (MatrixClientPeg.get().isGuest()) {
return Promise.resolve(); return Promise.resolve();
} }
@ -114,7 +116,7 @@ export function setDMRoom(roomId, userId) {
* @param {string} myUserId User ID of the current user * @param {string} myUserId User ID of the current user
* @returns {string} User ID of the user that the room is probably a DM with * @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 oldestTs;
let oldestUser; let oldestUser;

View file

@ -17,8 +17,8 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import * as React from 'react'; 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 * as ContentHelpers from 'matrix-js-sdk/src/content-helpers';
import {MatrixClientPeg} from './MatrixClientPeg'; import {MatrixClientPeg} from './MatrixClientPeg';
@ -1019,9 +1019,8 @@ export const Commands = [
const member = MatrixClientPeg.get().getRoom(roomId).getMember(userId); const member = MatrixClientPeg.get().getRoom(roomId).getMember(userId);
dis.dispatch<ViewUserPayload>({ dis.dispatch<ViewUserPayload>({
action: Action.ViewUser, action: Action.ViewUser,
// XXX: We should be using a real member object and not assuming what the // XXX: We should be using a real member object and not assuming what the receiver wants.
// receiver wants. member: member || { userId } as User,
member: member || {userId},
}); });
return success(); return success();
}, },

View file

@ -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"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
@ -14,9 +14,13 @@ See the License for the specific language governing permissions and
limitations under the License. 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 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 * 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 * @param {Object} ev The event
* @returns {boolean} True if the given event should affect the unread message count * @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) { if (ev.sender && ev.sender.userId == MatrixClientPeg.get().credentials.userId) {
return false; 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); return haveTileForEvent(ev);
} }
export function doesRoomHaveUnreadMessages(room) { export function doesRoomHaveUnreadMessages(room: Room): boolean {
const myUserId = MatrixClientPeg.get().getUserId(); const myUserId = MatrixClientPeg.get().getUserId();
// get the most recent read receipt sent by our account. // get the most recent read receipt sent by our account.

View file

@ -57,6 +57,8 @@ export enum Modifiers {
// Meta-modifier: isMac ? CMD : CONTROL // Meta-modifier: isMac ? CMD : CONTROL
export const CMD_OR_CTRL = isMac ? Modifiers.COMMAND : Modifiers.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 { interface IKeybind {
modifiers?: Modifiers[]; modifiers?: Modifiers[];
@ -319,6 +321,7 @@ const alternateKeyName: Record<string, string> = {
[Key.SPACE]: _td("Space"), [Key.SPACE]: _td("Space"),
[Key.HOME]: _td("Home"), [Key.HOME]: _td("Home"),
[Key.END]: _td("End"), [Key.END]: _td("End"),
[DIGITS]: _td("[number]"),
}; };
const keyIcon: Record<string, string> = { const keyIcon: Record<string, string> = {
[Key.ARROW_UP]: "↑", [Key.ARROW_UP]: "↑",

View file

@ -15,8 +15,9 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import {ReactElement} from 'react'; import { ReactElement } from 'react';
import Room from 'matrix-js-sdk/src/models/room'; import { Room } from 'matrix-js-sdk/src/models/room';
import CommandProvider from './CommandProvider'; import CommandProvider from './CommandProvider';
import CommunityProvider from './CommunityProvider'; import CommunityProvider from './CommunityProvider';
import DuckDuckGoProvider from './DuckDuckGoProvider'; import DuckDuckGoProvider from './DuckDuckGoProvider';
@ -24,7 +25,7 @@ import RoomProvider from './RoomProvider';
import UserProvider from './UserProvider'; import UserProvider from './UserProvider';
import EmojiProvider from './EmojiProvider'; import EmojiProvider from './EmojiProvider';
import NotifProvider from './NotifProvider'; import NotifProvider from './NotifProvider';
import {timeout} from "../utils/promise"; import { timeout } from "../utils/promise";
import AutocompleteProvider, {ICommand} from "./AutocompleteProvider"; import AutocompleteProvider, {ICommand} from "./AutocompleteProvider";
import SettingsStore from "../settings/SettingsStore"; import SettingsStore from "../settings/SettingsStore";
import SpaceProvider from "./SpaceProvider"; import SpaceProvider from "./SpaceProvider";

View file

@ -15,7 +15,8 @@ limitations under the License.
*/ */
import React from 'react'; import React from 'react';
import Room from "matrix-js-sdk/src/models/room"; import { Room } from "matrix-js-sdk/src/models/room";
import AutocompleteProvider from './AutocompleteProvider'; import AutocompleteProvider from './AutocompleteProvider';
import { _t } from '../languageHandler'; import { _t } from '../languageHandler';
import {MatrixClientPeg} from '../MatrixClientPeg'; import {MatrixClientPeg} from '../MatrixClientPeg';

View file

@ -17,16 +17,16 @@ limitations under the License.
*/ */
import React from "react"; import React from "react";
import {uniqBy, sortBy} from "lodash"; import { uniqBy, sortBy } from "lodash";
import Room from "matrix-js-sdk/src/models/room"; import { Room } from "matrix-js-sdk/src/models/room";
import { _t } from '../languageHandler'; import { _t } from '../languageHandler';
import AutocompleteProvider from './AutocompleteProvider'; import AutocompleteProvider from './AutocompleteProvider';
import {MatrixClientPeg} from '../MatrixClientPeg'; import { MatrixClientPeg } from '../MatrixClientPeg';
import QueryMatcher from './QueryMatcher'; import QueryMatcher from './QueryMatcher';
import {PillCompletion} from './Components'; import { PillCompletion } from './Components';
import {makeRoomPermalink} from "../utils/permalinks/Permalinks"; import { makeRoomPermalink } from "../utils/permalinks/Permalinks";
import {ICompletion, ISelectionRange} from "./Autocompleter"; import { ICompletion, ISelectionRange } from "./Autocompleter";
import RoomAvatar from '../components/views/avatars/RoomAvatar'; import RoomAvatar from '../components/views/avatars/RoomAvatar';
import SettingsStore from "../settings/SettingsStore"; import SettingsStore from "../settings/SettingsStore";

View file

@ -15,9 +15,9 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import React from "react"; import React, { HTMLAttributes } from "react";
interface IProps { interface IProps extends HTMLAttributes<HTMLDivElement> {
className?: string; className?: string;
onScroll?: () => void; onScroll?: () => void;
onWheel?: () => void; onWheel?: () => void;
@ -52,14 +52,18 @@ export default class AutoHideScrollbar extends React.Component<IProps> {
} }
public render() { public render() {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { className, onScroll, onWheel, style, tabIndex, wrappedRef, children, ...otherProps } = this.props;
return (<div return (<div
{...otherProps}
ref={this.containerRef} ref={this.containerRef}
style={this.props.style} style={style}
className={["mx_AutoHideScrollbar", this.props.className].join(" ")} className={["mx_AutoHideScrollbar", className].join(" ")}
onWheel={this.props.onWheel} onWheel={onWheel}
tabIndex={this.props.tabIndex} tabIndex={tabIndex}
> >
{ this.props.children } { children }
</div>); </div>);
} }
} }

View file

@ -24,7 +24,6 @@ import * as sdk from '../../index';
import dis from '../../dispatcher/dispatcher'; import dis from '../../dispatcher/dispatcher';
import { _t } from '../../languageHandler'; import { _t } from '../../languageHandler';
import { Droppable } from 'react-beautiful-dnd';
import classNames from 'classnames'; import classNames from 'classnames';
import MatrixClientContext from "../../contexts/MatrixClientContext"; import MatrixClientContext from "../../contexts/MatrixClientContext";
import AutoHideScrollbar from "./AutoHideScrollbar"; 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 // only dispatch if its not a no-op
if (this.state.selectedTags.length > 0) { if (this.state.selectedTags.length > 0) {
dis.dispatch({action: 'deselect_tags'}); dis.dispatch({action: 'deselect_tags'});
@ -151,28 +150,15 @@ class GroupFilterPanel extends React.Component {
return <div className={classes} onClick={this.onClearFilterClick}> return <div className={classes} onClick={this.onClearFilterClick}>
<AutoHideScrollbar <AutoHideScrollbar
className="mx_GroupFilterPanel_scroller" className="mx_GroupFilterPanel_scroller"
// XXX: Use onMouseDown as a workaround for https://github.com/atlassian/react-beautiful-dnd/issues/273 onClick={this.onClick}
// instead of onClick. Otherwise we experience https://github.com/vector-im/element-web/issues/6253
onMouseDown={this.onMouseDown}
> >
<Droppable <div className="mx_GroupFilterPanel_tagTileContainer">
droppableId="tag-panel-droppable" { this.renderGlobalIcon() }
type="draggable-TagTile" { tags }
> <div>
{ (provided, snapshot) => ( { createButton }
<div </div>
className="mx_GroupFilterPanel_tagTileContainer" </div>
ref={provided.innerRef}
>
{ this.renderGlobalIcon() }
{ tags }
<div>
{createButton}
</div>
{ provided.placeholder }
</div>
) }
</Droppable>
</AutoHideScrollbar> </AutoHideScrollbar>
</div>; </div>;
} }

View file

@ -185,21 +185,24 @@ export default class IndicatorScrollbar extends React.Component {
}; };
render() { render() {
// eslint-disable-next-line no-unused-vars
const { children, trackHorizontalOverflow, verticalScrollsHorizontally, ...otherProps } = this.props;
const leftIndicatorStyle = {left: this.state.leftIndicatorOffset}; const leftIndicatorStyle = {left: this.state.leftIndicatorOffset};
const rightIndicatorStyle = {right: this.state.rightIndicatorOffset}; const rightIndicatorStyle = {right: this.state.rightIndicatorOffset};
const leftOverflowIndicator = this.props.trackHorizontalOverflow const leftOverflowIndicator = trackHorizontalOverflow
? <div className="mx_IndicatorScrollbar_leftOverflowIndicator" style={leftIndicatorStyle} /> : null; ? <div className="mx_IndicatorScrollbar_leftOverflowIndicator" style={leftIndicatorStyle} /> : null;
const rightOverflowIndicator = this.props.trackHorizontalOverflow const rightOverflowIndicator = trackHorizontalOverflow
? <div className="mx_IndicatorScrollbar_rightOverflowIndicator" style={rightIndicatorStyle} /> : null; ? <div className="mx_IndicatorScrollbar_rightOverflowIndicator" style={rightIndicatorStyle} /> : null;
return (<AutoHideScrollbar return (<AutoHideScrollbar
ref={this._collectScrollerComponent} ref={this._collectScrollerComponent}
wrappedRef={this._collectScroller} wrappedRef={this._collectScroller}
onWheel={this.onMouseWheel} onWheel={this.onMouseWheel}
{...this.props} {...otherProps}
> >
{ leftOverflowIndicator } { leftOverflowIndicator }
{ this.props.children } { children }
{ rightOverflowIndicator } { rightOverflowIndicator }
</AutoHideScrollbar>); </AutoHideScrollbar>);
} }

View file

@ -19,7 +19,6 @@ limitations under the License.
import * as React from 'react'; import * as React from 'react';
import * as PropTypes from 'prop-types'; import * as PropTypes from 'prop-types';
import { MatrixClient } from 'matrix-js-sdk/src/client'; import { MatrixClient } from 'matrix-js-sdk/src/client';
import { DragDropContext } from 'react-beautiful-dnd';
import {Key} from '../../Keyboard'; import {Key} from '../../Keyboard';
import PageTypes from '../../PageTypes'; import PageTypes from '../../PageTypes';
@ -30,8 +29,6 @@ import dis from '../../dispatcher/dispatcher';
import { IMatrixClientCreds } from '../../MatrixClientPeg'; import { IMatrixClientCreds } from '../../MatrixClientPeg';
import SettingsStore from "../../settings/SettingsStore"; import SettingsStore from "../../settings/SettingsStore";
import TagOrderActions from '../../actions/TagOrderActions';
import RoomListActions from '../../actions/RoomListActions';
import ResizeHandle from '../views/elements/ResizeHandle'; import ResizeHandle from '../views/elements/ResizeHandle';
import {Resizer, CollapseDistributor} from '../../resizer'; import {Resizer, CollapseDistributor} from '../../resizer';
import MatrixClientContext from "../../contexts/MatrixClientContext"; import MatrixClientContext from "../../contexts/MatrixClientContext";
@ -569,50 +566,6 @@ class LoggedInView extends React.Component<IProps, IState> {
} }
}; };
_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() { render() {
const RoomView = sdk.getComponent('structures.RoomView'); const RoomView = sdk.getComponent('structures.RoomView');
const UserView = sdk.getComponent('structures.UserView'); const UserView = sdk.getComponent('structures.UserView');
@ -679,17 +632,15 @@ class LoggedInView extends React.Component<IProps, IState> {
aria-hidden={this.props.hideToSRUsers} aria-hidden={this.props.hideToSRUsers}
> >
<ToastContainer /> <ToastContainer />
<DragDropContext onDragEnd={this._onDragEnd}> <div ref={this._resizeContainer} className={bodyClasses}>
<div ref={this._resizeContainer} className={bodyClasses}> { SettingsStore.getValue("feature_spaces") ? <SpacePanel /> : null }
{ SettingsStore.getValue("feature_spaces") ? <SpacePanel /> : null } <LeftPanel
<LeftPanel isMinimized={this.props.collapseLhs || false}
isMinimized={this.props.collapseLhs || false} resizeNotifier={this.props.resizeNotifier}
resizeNotifier={this.props.resizeNotifier} />
/> <ResizeHandle />
<ResizeHandle /> { pageElement }
{ pageElement } </div>
</div>
</DragDropContext>
</div> </div>
<CallContainer /> <CallContainer />
<NonUrgentToastContainer /> <NonUrgentToastContainer />

View file

@ -1461,7 +1461,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
}); });
const dft = new DecryptionFailureTracker((total, errorCode) => { 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 }); CountlyAnalytics.instance.track("decryption_failure", { errorCode }, null, { sum: total });
}, (errorCode) => { }, (errorCode) => {
// Map JS-SDK error codes to tracker codes for aggregation // Map JS-SDK error codes to tracker codes for aggregation

View file

@ -82,8 +82,7 @@ export default class MyGroups extends React.Component {
</p> </p>
<p> <p>
{ _t( { _t(
"To set up a filter, drag a community avatar over to the filter panel on " + "You can click on an avatar in the " +
"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 " + "filter panel at any time to see only the rooms and people associated " +
"with that community.", "with that community.",
) } ) }

View file

@ -23,7 +23,7 @@ limitations under the License.
import React, { createRef } from 'react'; import React, { createRef } from 'react';
import classNames from 'classnames'; import classNames from 'classnames';
import { 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 { MatrixEvent } from "matrix-js-sdk/src/models/event";
import { SearchResult } from "matrix-js-sdk/src/models/search-result"; import { SearchResult } from "matrix-js-sdk/src/models/search-result";
import { EventSubscription } from "fbemitter"; import { EventSubscription } from "fbemitter";
@ -60,7 +60,7 @@ import ScrollPanel from "./ScrollPanel";
import TimelinePanel from "./TimelinePanel"; import TimelinePanel from "./TimelinePanel";
import ErrorBoundary from "../views/elements/ErrorBoundary"; import ErrorBoundary from "../views/elements/ErrorBoundary";
import RoomPreviewBar from "../views/rooms/RoomPreviewBar"; 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 RoomUpgradeWarningBar from "../views/rooms/RoomUpgradeWarningBar";
import AuxPanel from "../views/rooms/AuxPanel"; import AuxPanel from "../views/rooms/AuxPanel";
import RoomHeader from "../views/rooms/RoomHeader"; import RoomHeader from "../views/rooms/RoomHeader";
@ -139,7 +139,7 @@ export interface IState {
draggingFile: boolean; draggingFile: boolean;
searching: boolean; searching: boolean;
searchTerm?: string; searchTerm?: string;
searchScope?: "All" | "Room"; searchScope?: SearchScope;
searchResults?: XOR<{}, { searchResults?: XOR<{}, {
count: number; count: number;
highlights: string[]; highlights: string[];
@ -172,11 +172,7 @@ export interface IState {
// We load this later by asking the js-sdk to suggest a version for us. // We load this later by asking the js-sdk to suggest a version for us.
// This object is the result of Room#getRecommendedVersion() // This object is the result of Room#getRecommendedVersion()
upgradeRecommendation?: { upgradeRecommendation?: IRecommendedVersion;
version: string;
needsUpgrade: boolean;
urgent: boolean;
};
canReact: boolean; canReact: boolean;
canReply: boolean; canReply: boolean;
layout: Layout; layout: Layout;
@ -1267,7 +1263,7 @@ export default class RoomView extends React.Component<IProps, IState> {
}); });
} }
private onSearch = (term: string, scope) => { private onSearch = (term: string, scope: SearchScope) => {
this.setState({ this.setState({
searchTerm: term, searchTerm: term,
searchScope: scope, searchScope: scope,
@ -1288,7 +1284,7 @@ export default class RoomView extends React.Component<IProps, IState> {
this.searchId = new Date().getTime(); this.searchId = new Date().getTime();
let roomId; let roomId;
if (scope === "Room") roomId = this.state.room.roomId; if (scope === SearchScope.Room) roomId = this.state.room.roomId;
debuglog("sending search request"); debuglog("sending search request");
const searchPromise = eventSearch(term, roomId); const searchPromise = eventSearch(term, roomId);
@ -2058,7 +2054,7 @@ export default class RoomView extends React.Component<IProps, IState> {
if (!this.state.atEndOfLiveTimeline && !this.state.searchResults) { if (!this.state.atEndOfLiveTimeline && !this.state.searchResults) {
const JumpToBottomButton = sdk.getComponent('rooms.JumpToBottomButton'); const JumpToBottomButton = sdk.getComponent('rooms.JumpToBottomButton');
jumpToBottom = (<JumpToBottomButton jumpToBottom = (<JumpToBottomButton
highlight={this.state.room.getUnreadNotificationCount('highlight') > 0} highlight={this.state.room.getUnreadNotificationCount(NotificationCountType.Highlight) > 0}
numUnreadMessages={this.state.numUnreadMessages} numUnreadMessages={this.state.numUnreadMessages}
onScrollToBottomClick={this.jumpToLiveTimeline} onScrollToBottomClick={this.jumpToLiveTimeline}
roomId={this.state.roomId} roomId={this.state.roomId}

View file

@ -14,34 +14,34 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import React, {ReactNode, useMemo, useState} from "react"; import React, { ReactNode, useMemo, useState } from "react";
import {Room} from "matrix-js-sdk/src/models/room"; import { Room } from "matrix-js-sdk/src/models/room";
import {MatrixClient} from "matrix-js-sdk/src/client"; import { MatrixClient } from "matrix-js-sdk/src/client";
import {EventType, RoomType} from "matrix-js-sdk/src/@types/event"; import { EventType, RoomType } from "matrix-js-sdk/src/@types/event";
import classNames from "classnames"; import 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 dis from "../../dispatcher/dispatcher";
import {_t} from "../../languageHandler"; import { _t } from "../../languageHandler";
import AccessibleButton, {ButtonEvent} from "../views/elements/AccessibleButton"; import AccessibleButton, { ButtonEvent } from "../views/elements/AccessibleButton";
import BaseDialog from "../views/dialogs/BaseDialog"; import BaseDialog from "../views/dialogs/BaseDialog";
import Spinner from "../views/elements/Spinner"; import Spinner from "../views/elements/Spinner";
import SearchBox from "./SearchBox"; import SearchBox from "./SearchBox";
import RoomAvatar from "../views/avatars/RoomAvatar"; import RoomAvatar from "../views/avatars/RoomAvatar";
import RoomName from "../views/elements/RoomName"; import RoomName from "../views/elements/RoomName";
import {useAsyncMemo} from "../../hooks/useAsyncMemo"; import { useAsyncMemo } from "../../hooks/useAsyncMemo";
import {EnhancedMap} from "../../utils/maps"; import { EnhancedMap } from "../../utils/maps";
import StyledCheckbox from "../views/elements/StyledCheckbox"; import StyledCheckbox from "../views/elements/StyledCheckbox";
import AutoHideScrollbar from "./AutoHideScrollbar"; import AutoHideScrollbar from "./AutoHideScrollbar";
import BaseAvatar from "../views/avatars/BaseAvatar"; import BaseAvatar from "../views/avatars/BaseAvatar";
import {mediaFromMxc} from "../../customisations/Media"; import { mediaFromMxc } from "../../customisations/Media";
import InfoTooltip from "../views/elements/InfoTooltip"; import InfoTooltip from "../views/elements/InfoTooltip";
import TextWithTooltip from "../views/elements/TextWithTooltip"; import TextWithTooltip from "../views/elements/TextWithTooltip";
import {useStateToggle} from "../../hooks/useStateToggle"; import { useStateToggle } from "../../hooks/useStateToggle";
import {getOrder} from "../../stores/SpaceStore"; import { getChildOrder } from "../../stores/SpaceStore";
import AccessibleTooltipButton from "../views/elements/AccessibleTooltipButton"; import AccessibleTooltipButton from "../views/elements/AccessibleTooltipButton";
import {linkifyElement} from "../../HtmlUtils"; import { linkifyElement } from "../../HtmlUtils";
interface IHierarchyProps { interface IHierarchyProps {
space: Room; space: Room;
@ -286,7 +286,7 @@ export const HierarchyLevel = ({
const children = Array.from(relations.get(spaceId)?.values() || []); const children = Array.from(relations.get(spaceId)?.values() || []);
const sortedChildren = sortBy(children, ev => { 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 // 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 [subspaces, childRooms] = sortedChildren.reduce((result, ev: ISpaceSummaryEvent) => {
const roomId = ev.state_key; const roomId = ev.state_key;

View file

@ -17,16 +17,17 @@ See the License for the specific language governing permissions and
limitations under the License. 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 classNames from 'classnames';
import { ResizeMethod } from 'matrix-js-sdk/src/@types/partials';
import * as AvatarLogic from '../../../Avatar'; import * as AvatarLogic from '../../../Avatar';
import SettingsStore from "../../../settings/SettingsStore"; import SettingsStore from "../../../settings/SettingsStore";
import AccessibleButton from '../elements/AccessibleButton'; import AccessibleButton from '../elements/AccessibleButton';
import RoomContext from "../../../contexts/RoomContext"; import RoomContext from "../../../contexts/RoomContext";
import MatrixClientContext from "../../../contexts/MatrixClientContext"; import MatrixClientContext from "../../../contexts/MatrixClientContext";
import {useEventEmitter} from "../../../hooks/useEventEmitter"; import { useEventEmitter } from "../../../hooks/useEventEmitter";
import {toPx} from "../../../utils/units"; import { toPx } from "../../../utils/units";
import {ResizeMethod} from "../../../Avatar";
import { _t } from '../../../languageHandler'; import { _t } from '../../../languageHandler';
interface IProps { interface IProps {

View file

@ -15,10 +15,11 @@ limitations under the License.
*/ */
import React from 'react'; import React from 'react';
import { ResizeMethod } from 'matrix-js-sdk/src/@types/partials';
import BaseAvatar from './BaseAvatar'; import BaseAvatar from './BaseAvatar';
import {replaceableComponent} from "../../../utils/replaceableComponent"; import { replaceableComponent } from "../../../utils/replaceableComponent";
import {mediaFromMxc} from "../../../customisations/Media"; import { mediaFromMxc } from "../../../customisations/Media";
import {ResizeMethod} from "../../../Avatar";
export interface IProps { export interface IProps {
groupId?: string; groupId?: string;

View file

@ -16,14 +16,14 @@ limitations under the License.
*/ */
import React from 'react'; 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 dis from "../../../dispatcher/dispatcher";
import {Action} from "../../../dispatcher/actions"; import { Action } from "../../../dispatcher/actions";
import BaseAvatar from "./BaseAvatar"; import BaseAvatar from "./BaseAvatar";
import {replaceableComponent} from "../../../utils/replaceableComponent"; import { replaceableComponent } from "../../../utils/replaceableComponent";
import {mediaFromMxc} from "../../../customisations/Media"; import { mediaFromMxc } from "../../../customisations/Media";
import {ResizeMethod} from "../../../Avatar";
interface IProps extends Omit<React.ComponentProps<typeof BaseAvatar>, "name" | "idName" | "url"> { interface IProps extends Omit<React.ComponentProps<typeof BaseAvatar>, "name" | "idName" | "url"> {
member: RoomMember; member: RoomMember;

View file

@ -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 See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import React, {ComponentProps} from 'react'; import React, { ComponentProps } from 'react';
import Room from 'matrix-js-sdk/src/models/room'; import { Room } from 'matrix-js-sdk/src/models/room';
import { ResizeMethod } from 'matrix-js-sdk/src/@types/partials';
import BaseAvatar from './BaseAvatar'; import BaseAvatar from './BaseAvatar';
import ImageView from '../elements/ImageView'; import ImageView from '../elements/ImageView';
import {MatrixClientPeg} from '../../../MatrixClientPeg'; import { MatrixClientPeg } from '../../../MatrixClientPeg';
import Modal from '../../../Modal'; import Modal from '../../../Modal';
import * as Avatar from '../../../Avatar'; import * as Avatar from '../../../Avatar';
import {ResizeMethod} from "../../../Avatar"; import { replaceableComponent } from "../../../utils/replaceableComponent";
import {replaceableComponent} from "../../../utils/replaceableComponent"; import { mediaFromMxc } from "../../../customisations/Media";
import {mediaFromMxc} from "../../../customisations/Media";
interface IProps extends Omit<ComponentProps<typeof BaseAvatar>, "name" | "idName" | "url" | "onClick"> { interface IProps extends Omit<ComponentProps<typeof BaseAvatar>, "name" | "idName" | "url" | "onClick"> {
// Room may be left unset here, but if it is, // Room may be left unset here, but if it is,

View file

@ -25,6 +25,7 @@ import TextWithTooltip from "../elements/TextWithTooltip";
import Modal from "../../../Modal"; import Modal from "../../../Modal";
import BetaFeedbackDialog from "../dialogs/BetaFeedbackDialog"; import BetaFeedbackDialog from "../dialogs/BetaFeedbackDialog";
import SdkConfig from "../../../SdkConfig"; import SdkConfig from "../../../SdkConfig";
import SettingsFlag from "../elements/SettingsFlag";
interface IProps { interface IProps {
title?: string; title?: string;
@ -66,7 +67,7 @@ const BetaCard = ({ title: titleOverride, featureId }: IProps) => {
const info = SettingsStore.getBetaInfo(featureId); const info = SettingsStore.getBetaInfo(featureId);
if (!info) return null; // Beta is invalid/disabled 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); const value = SettingsStore.getValue(featureId);
let feedbackButton; let feedbackButton;
@ -82,26 +83,33 @@ const BetaCard = ({ title: titleOverride, featureId }: IProps) => {
} }
return <div className="mx_BetaCard"> return <div className="mx_BetaCard">
<div> <div className="mx_BetaCard_columns">
<h3 className="mx_BetaCard_title">
{ titleOverride || _t(title) }
<BetaPill />
</h3>
<span className="mx_BetaCard_caption">{ _t(caption) }</span>
<div> <div>
{ feedbackButton } <h3 className="mx_BetaCard_title">
<AccessibleButton { titleOverride || _t(title) }
onClick={() => SettingsStore.setValue(featureId, null, SettingLevel.DEVICE, !value)} <BetaPill />
kind={feedbackButton ? "primary_outline" : "primary"} </h3>
> <span className="mx_BetaCard_caption">{ _t(caption) }</span>
{ value ? _t("Leave the beta") : _t("Join the beta") } <div className="mx_BetaCard_buttons">
</AccessibleButton> { feedbackButton }
<AccessibleButton
onClick={() => SettingsStore.setValue(featureId, null, SettingLevel.DEVICE, !value)}
kind={feedbackButton ? "primary_outline" : "primary"}
>
{ value ? _t("Leave the beta") : _t("Join the beta") }
</AccessibleButton>
</div>
{ disclaimer && <div className="mx_BetaCard_disclaimer">
{ disclaimer(value) }
</div> }
</div> </div>
{ disclaimer && <div className="mx_BetaCard_disclaimer"> <img src={image} alt="" />
{ disclaimer(value) }
</div> }
</div> </div>
<img src={image} alt="" /> { extraSettings && <div className="mx_BetaCard_relatedSettings">
{ extraSettings.map(key => (
<SettingsFlag key={key} name={key} level={SettingLevel.DEVICE} />
)) }
</div> }
</div>; </div>;
}; };

View file

@ -23,45 +23,70 @@ import TagOrderActions from '../../../actions/TagOrderActions';
import {MenuItem} from "../../structures/ContextMenu"; import {MenuItem} from "../../structures/ContextMenu";
import MatrixClientContext from "../../../contexts/MatrixClientContext"; import MatrixClientContext from "../../../contexts/MatrixClientContext";
import {replaceableComponent} from "../../../utils/replaceableComponent"; import {replaceableComponent} from "../../../utils/replaceableComponent";
import GroupFilterOrderStore from "../../../stores/GroupFilterOrderStore";
@replaceableComponent("views.context_menus.TagTileContextMenu") @replaceableComponent("views.context_menus.TagTileContextMenu")
export default class TagTileContextMenu extends React.Component { export default class TagTileContextMenu extends React.Component {
static propTypes = { static propTypes = {
tag: PropTypes.string.isRequired, tag: PropTypes.string.isRequired,
index: PropTypes.number.isRequired,
/* callback called when the menu is dismissed */ /* callback called when the menu is dismissed */
onFinished: PropTypes.func.isRequired, onFinished: PropTypes.func.isRequired,
}; };
static contextType = MatrixClientContext; static contextType = MatrixClientContext;
constructor() { _onViewCommunityClick = () => {
super();
this._onViewCommunityClick = this._onViewCommunityClick.bind(this);
this._onRemoveClick = this._onRemoveClick.bind(this);
}
_onViewCommunityClick() {
dis.dispatch({ dis.dispatch({
action: 'view_group', action: 'view_group',
group_id: this.props.tag, group_id: this.props.tag,
}); });
this.props.onFinished(); this.props.onFinished();
} };
_onRemoveClick() { _onRemoveClick = () => {
dis.dispatch(TagOrderActions.removeTag(this.context, this.props.tag)); dis.dispatch(TagOrderActions.removeTag(this.context, this.props.tag));
this.props.onFinished(); 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() { render() {
let moveUp;
let moveDown;
if (this.props.index > 0) {
moveUp = (
<MenuItem className="mx_TagTileContextMenu_item mx_TagTileContextMenu_moveUp" onClick={this._onMoveUp}>
{ _t("Move up") }
</MenuItem>
);
}
if (this.props.index < (GroupFilterOrderStore.getOrderedTags() || []).length - 1) {
moveDown = (
<MenuItem className="mx_TagTileContextMenu_item mx_TagTileContextMenu_moveDown" onClick={this._onMoveDown}>
{ _t("Move down") }
</MenuItem>
);
}
return <div> return <div>
<MenuItem className="mx_TagTileContextMenu_item mx_TagTileContextMenu_viewCommunity" onClick={this._onViewCommunityClick}> <MenuItem className="mx_TagTileContextMenu_item mx_TagTileContextMenu_viewCommunity" onClick={this._onViewCommunityClick}>
{ _t('View Community') } { _t('View Community') }
</MenuItem> </MenuItem>
{ (moveUp || moveDown) ? <hr className="mx_TagTileContextMenu_separator" role="separator" /> : null }
{ moveUp }
{ moveDown }
<hr className="mx_TagTileContextMenu_separator" role="separator" /> <hr className="mx_TagTileContextMenu_separator" role="separator" />
<MenuItem className="mx_TagTileContextMenu_item mx_TagTileContextMenu_hideCommunity" onClick={this._onRemoveClick}> <MenuItem className="mx_TagTileContextMenu_item mx_TagTileContextMenu_hideCommunity" onClick={this._onRemoveClick}>
{ _t('Hide') } { _t("Unpin") }
</MenuItem> </MenuItem>
</div>; </div>;
} }

View file

@ -44,7 +44,12 @@ const BetaFeedbackDialog: React.FC<IProps> = ({featureId, onFinished}) => {
const sendFeedback = async (ok: boolean) => { const sendFeedback = async (ok: boolean) => {
if (!ok) return onFinished(false); 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); onFinished(true);
Modal.createTrackedDialog("Beta Dialog Sent", featureId, InfoDialog, { Modal.createTrackedDialog("Beta Dialog Sent", featureId, InfoDialog, {

View file

@ -14,30 +14,31 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import React, {useMemo, useState, useEffect} from "react"; import React, { useMemo, useState, useEffect } from "react";
import classnames from "classnames"; import classnames from "classnames";
import {MatrixEvent} from "matrix-js-sdk/src/models/event"; import { MatrixEvent } from "matrix-js-sdk/src/models/event";
import {Room} from "matrix-js-sdk/src/models/room"; import { Room } from "matrix-js-sdk/src/models/room";
import {MatrixClient} from "matrix-js-sdk/src/client"; import { MatrixClient } from "matrix-js-sdk/src/client";
import { RoomMember } from "matrix-js-sdk/src/models/room-member";
import {_t} from "../../../languageHandler"; import { _t } from "../../../languageHandler";
import dis from "../../../dispatcher/dispatcher"; import dis from "../../../dispatcher/dispatcher";
import {useSettingValue, useFeatureEnabled} from "../../../hooks/useSettings"; import { useSettingValue, useFeatureEnabled } from "../../../hooks/useSettings";
import {UIFeature} from "../../../settings/UIFeature"; import { UIFeature } from "../../../settings/UIFeature";
import {Layout} from "../../../settings/Layout"; import { Layout } from "../../../settings/Layout";
import {IDialogProps} from "./IDialogProps"; import { IDialogProps } from "./IDialogProps";
import BaseDialog from "./BaseDialog"; import BaseDialog from "./BaseDialog";
import {avatarUrlForUser} from "../../../Avatar"; import { avatarUrlForUser } from "../../../Avatar";
import EventTile from "../rooms/EventTile"; import EventTile from "../rooms/EventTile";
import SearchBox from "../../structures/SearchBox"; import SearchBox from "../../structures/SearchBox";
import DecoratedRoomAvatar from "../avatars/DecoratedRoomAvatar"; import DecoratedRoomAvatar from "../avatars/DecoratedRoomAvatar";
import {Alignment} from '../elements/Tooltip'; import { Alignment } from '../elements/Tooltip';
import AccessibleTooltipButton from "../elements/AccessibleTooltipButton"; import AccessibleTooltipButton from "../elements/AccessibleTooltipButton";
import AutoHideScrollbar from "../../structures/AutoHideScrollbar"; import AutoHideScrollbar from "../../structures/AutoHideScrollbar";
import {StaticNotificationState} from "../../../stores/notifications/StaticNotificationState"; import { StaticNotificationState } from "../../../stores/notifications/StaticNotificationState";
import NotificationBadge from "../rooms/NotificationBadge"; import NotificationBadge from "../rooms/NotificationBadge";
import {RoomPermalinkCreator} from "../../../utils/permalinks/Permalinks"; import { RoomPermalinkCreator } from "../../../utils/permalinks/Permalinks";
import {sortRooms} from "../../../stores/room-list/algorithms/tag-sorting/RecentAlgorithm"; import { sortRooms } from "../../../stores/room-list/algorithms/tag-sorting/RecentAlgorithm";
import QueryMatcher from "../../../autocomplete/QueryMatcher"; import QueryMatcher from "../../../autocomplete/QueryMatcher";
const AVATAR_SIZE = 30; const AVATAR_SIZE = 30;
@ -171,7 +172,7 @@ const ForwardDialog: React.FC<IProps> = ({ matrixClient: cli, event, permalinkCr
); );
}, },
getMxcAvatarUrl: () => profileInfo.avatar_url, getMxcAvatarUrl: () => profileInfo.avatar_url,
}; } as RoomMember;
const [query, setQuery] = useState(""); const [query, setQuery] = useState("");
const lcQuery = query.toLowerCase(); const lcQuery = query.toLowerCase();

View file

@ -18,7 +18,6 @@ limitations under the License.
import TagTile from './TagTile'; import TagTile from './TagTile';
import React from 'react'; import React from 'react';
import { Draggable } from 'react-beautiful-dnd';
import { ContextMenu, toRightOf, useContextMenu } from "../../structures/ContextMenu"; import { ContextMenu, toRightOf, useContextMenu } from "../../structures/ContextMenu";
import * as sdk from '../../../index'; import * as sdk from '../../../index';
@ -31,32 +30,17 @@ export default function DNDTagTile(props) {
const TagTileContextMenu = sdk.getComponent('context_menus.TagTileContextMenu'); const TagTileContextMenu = sdk.getComponent('context_menus.TagTileContextMenu');
contextMenu = ( contextMenu = (
<ContextMenu {...toRightOf(elementRect)} onFinished={closeMenu}> <ContextMenu {...toRightOf(elementRect)} onFinished={closeMenu}>
<TagTileContextMenu tag={props.tag} onFinished={closeMenu} /> <TagTileContextMenu tag={props.tag} onFinished={closeMenu} index={props.index} />
</ContextMenu> </ContextMenu>
); );
} }
return <div> return <>
<Draggable <TagTile
key={props.tag} {...props}
draggableId={props.tag} contextMenuButtonRef={handle}
index={props.index} menuDisplayed={menuDisplayed}
type="draggable-TagTile" openMenu={openMenu}
> />
{(provided, snapshot) => (
<div
ref={provided.innerRef}
{...provided.draggableProps}
{...provided.dragHandleProps}
>
<TagTile
{...props}
contextMenuButtonRef={handle}
menuDisplayed={menuDisplayed}
openMenu={openMenu}
/>
</div>
)}
</Draggable>
{contextMenu} {contextMenu}
</div>; </>;
} }

View file

@ -14,10 +14,14 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import React from "react";
import EventIndexPeg from "../../../indexing/EventIndexPeg"; import EventIndexPeg from "../../../indexing/EventIndexPeg";
import { _t } from "../../../languageHandler"; import { _t } from "../../../languageHandler";
import SdkConfig from "../../../SdkConfig"; 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 { export enum WarningKind {
Files, Files,
@ -33,6 +37,22 @@ export default function DesktopBuildsNotice({isRoomEncrypted, kind}: IProps) {
if (!isRoomEncrypted) return null; if (!isRoomEncrypted) return null;
if (EventIndexPeg.get()) return null; if (EventIndexPeg.get()) return null;
if (EventIndexPeg.error) {
return <>
{_t("Message search initialisation failed, check <a>your settings</a> for more information", {}, {
a: sub => (<a onClick={(evt) => {
evt.preventDefault();
dis.dispatch({
action: Action.ViewUserSettings,
initialTabId: UserTab.Security,
});
}}>
{sub}
</a>),
})}
</>;
}
const {desktopBuilds, brand} = SdkConfig.get(); const {desktopBuilds, brand} = SdkConfig.get();
let text = null; let text = null;

View file

@ -17,13 +17,14 @@ limitations under the License.
import React from 'react'; import React from 'react';
import classnames from 'classnames'; import classnames from 'classnames';
import { MatrixEvent } from 'matrix-js-sdk/src/models/event'; import { MatrixEvent } from 'matrix-js-sdk/src/models/event';
import { RoomMember } from 'matrix-js-sdk/src/models/room-member';
import * as Avatar from '../../../Avatar'; import * as Avatar from '../../../Avatar';
import EventTile from '../rooms/EventTile'; import EventTile from '../rooms/EventTile';
import SettingsStore from "../../../settings/SettingsStore"; import SettingsStore from "../../../settings/SettingsStore";
import {Layout} from "../../../settings/Layout"; import { Layout } from "../../../settings/Layout";
import {UIFeature} from "../../../settings/UIFeature"; import { UIFeature } from "../../../settings/UIFeature";
import {replaceableComponent} from "../../../utils/replaceableComponent"; import { replaceableComponent } from "../../../utils/replaceableComponent";
interface IProps { interface IProps {
/** /**
@ -101,7 +102,8 @@ export default class EventTilePreview extends React.Component<IProps, IState> {
// Fake it more // Fake it more
event.sender = { event.sender = {
name: this.props.displayName, name: this.props.displayName || this.props.userId,
rawDisplayName: this.props.displayName,
userId: this.props.userId, userId: this.props.userId,
getAvatarUrl: (..._) => { getAvatarUrl: (..._) => {
return Avatar.avatarUrlForUser( return Avatar.avatarUrlForUser(
@ -110,7 +112,7 @@ export default class EventTilePreview extends React.Component<IProps, IState> {
); );
}, },
getMxcAvatarUrl: () => this.props.avatarUrl, getMxcAvatarUrl: () => this.props.avatarUrl,
}; } as RoomMember;
return event; return event;
} }

View file

@ -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;

View file

@ -14,10 +14,10 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import {useEffect, useState} from "react"; import React, { useEffect, useState } from "react";
import {Room} from "matrix-js-sdk/src/models/room"; import { Room } from "matrix-js-sdk/src/models/room";
import {useEventEmitter} from "../../../hooks/useEventEmitter"; import { useEventEmitter } from "../../../hooks/useEventEmitter";
interface IProps { interface IProps {
room: Room; room: Room;
@ -34,7 +34,7 @@ const RoomName = ({ room, children }: IProps): JSX.Element => {
}, [room]); }, [room]);
if (children) return children(name); if (children) return children(name);
return name || ""; return <>{ name || "" }</>;
}; };
export default RoomName; export default RoomName;

View file

@ -77,9 +77,10 @@ export default class SettingsFlag extends React.Component<IProps, IState> {
public render() { public render() {
const canChange = SettingsStore.canSetValue(this.props.name, this.props.roomId, this.props.level); const canChange = SettingsStore.canSetValue(this.props.name, this.props.roomId, this.props.level);
let label = this.props.label; const label = this.props.label
if (!label) label = SettingsStore.getDisplayName(this.props.name, this.props.level); ? _t(this.props.label)
else label = _t(label); : SettingsStore.getDisplayName(this.props.name, this.props.level);
const description = SettingsStore.getDescription(this.props.name);
if (this.props.useCheckbox) { if (this.props.useCheckbox) {
return <StyledCheckbox return <StyledCheckbox
@ -99,6 +100,9 @@ export default class SettingsFlag extends React.Component<IProps, IState> {
disabled={this.props.disabled || !canChange} disabled={this.props.disabled || !canChange}
aria-label={label} aria-label={label}
/> />
{ description && <div className="mx_SettingsFlag_microcopy">
{ description }
</div> }
</div> </div>
); );
} }

View file

@ -66,9 +66,7 @@ export default class GroupPublicityToggle extends React.Component {
render() { render() {
const GroupTile = sdk.getComponent('groups.GroupTile'); const GroupTile = sdk.getComponent('groups.GroupTile');
return <div className="mx_GroupPublicity_toggle"> return <div className="mx_GroupPublicity_toggle">
<GroupTile groupId={this.props.groupId} showDescription={false} <GroupTile groupId={this.props.groupId} showDescription={false} avatarHeight={40} />
avatarHeight={40} draggable={false}
/>
<ToggleSwitch checked={this.state.isGroupPublicised} <ToggleSwitch checked={this.state.isGroupPublicised}
disabled={!this.state.ready || this.state.busy} disabled={!this.state.ready || this.state.busy}
onChange={this._onPublicityToggle} /> onChange={this._onPublicityToggle} />

View file

@ -16,15 +16,15 @@ limitations under the License.
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { Draggable, Droppable } from 'react-beautiful-dnd';
import * as sdk from '../../../index'; import * as sdk from '../../../index';
import dis from '../../../dispatcher/dispatcher'; import dis from '../../../dispatcher/dispatcher';
import FlairStore from '../../../stores/FlairStore'; import FlairStore from '../../../stores/FlairStore';
import MatrixClientContext from "../../../contexts/MatrixClientContext"; import MatrixClientContext from "../../../contexts/MatrixClientContext";
import {replaceableComponent} from "../../../utils/replaceableComponent"; import {replaceableComponent} from "../../../utils/replaceableComponent";
import {mediaFromMxc} from "../../../customisations/Media"; import {mediaFromMxc} from "../../../customisations/Media";
import { _t } from "../../../languageHandler";
function nop() {} import TagOrderActions from "../../../actions/TagOrderActions";
import GroupFilterOrderStore from "../../../stores/GroupFilterOrderStore";
@replaceableComponent("views.groups.GroupTile") @replaceableComponent("views.groups.GroupTile")
class GroupTile extends React.Component { class GroupTile extends React.Component {
@ -34,7 +34,6 @@ class GroupTile extends React.Component {
showDescription: PropTypes.bool, showDescription: PropTypes.bool,
// Height of the group avatar in pixels // Height of the group avatar in pixels
avatarHeight: PropTypes.number, avatarHeight: PropTypes.number,
draggable: PropTypes.bool,
}; };
static contextType = MatrixClientContext; static contextType = MatrixClientContext;
@ -42,7 +41,6 @@ class GroupTile extends React.Component {
static defaultProps = { static defaultProps = {
showDescription: true, showDescription: true,
avatarHeight: 50, avatarHeight: 50,
draggable: true,
}; };
state = { state = {
@ -57,7 +55,7 @@ class GroupTile extends React.Component {
}); });
} }
onMouseDown = e => { onClick = e => {
e.preventDefault(); e.preventDefault();
dis.dispatch({ dis.dispatch({
action: 'view_group', action: 'view_group',
@ -65,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() { render() {
const BaseAvatar = sdk.getComponent('avatars.BaseAvatar'); const BaseAvatar = sdk.getComponent('avatars.BaseAvatar');
const AccessibleButton = sdk.getComponent('elements.AccessibleButton'); const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
@ -78,7 +88,7 @@ class GroupTile extends React.Component {
? mediaFromMxc(profile.avatarUrl).getSquareThumbnailHttp(avatarHeight) ? mediaFromMxc(profile.avatarUrl).getSquareThumbnailHttp(avatarHeight)
: null; : null;
let avatarElement = ( const avatarElement = (
<div className="mx_GroupTile_avatar"> <div className="mx_GroupTile_avatar">
<BaseAvatar <BaseAvatar
name={name} name={name}
@ -88,46 +98,21 @@ class GroupTile extends React.Component {
height={avatarHeight} /> height={avatarHeight} />
</div> </div>
); );
if (this.props.draggable) {
const avatarClone = avatarElement;
avatarElement = (
<Droppable droppableId="my-groups-droppable" type="draggable-TagTile">
{ (droppableProvided, droppableSnapshot) => (
<div ref={droppableProvided.innerRef}>
<Draggable
key={"GroupTile " + this.props.groupId}
draggableId={"GroupTile " + this.props.groupId}
index={this.props.groupId}
type="draggable-TagTile"
>
{ (provided, snapshot) => (
<div>
<div
ref={provided.innerRef}
{...provided.draggableProps}
{...provided.dragHandleProps}
>
{avatarClone}
</div>
{ /* Instead of a blank placeholder, use a copy of the avatar itself. */ }
{ provided.placeholder ? avatarClone : <div /> }
</div>
) }
</Draggable>
</div>
) }
</Droppable>
);
}
// XXX: Use onMouseDown as a workaround for https://github.com/atlassian/react-beautiful-dnd/issues/273 return <AccessibleButton className="mx_GroupTile" onClick={this.onClick}>
// instead of onClick. Otherwise we experience https://github.com/vector-im/element-web/issues/6156
return <AccessibleButton className="mx_GroupTile" onMouseDown={this.onMouseDown} onClick={nop}>
{ avatarElement } { avatarElement }
<div className="mx_GroupTile_profile"> <div className="mx_GroupTile_profile">
<div className="mx_GroupTile_name">{ name }</div> <div className="mx_GroupTile_name">{ name }</div>
{ descElement } { descElement }
<div className="mx_GroupTile_groupId">{ this.props.groupId }</div> <div className="mx_GroupTile_groupId">{ this.props.groupId }</div>
{ !(GroupFilterOrderStore.getOrderedTags() || []).includes(this.props.groupId)
? <AccessibleButton kind="link" onClick={this.onPinClick}>
{ _t("Pin") }
</AccessibleButton>
: <AccessibleButton kind="link" onClick={this.onUnpinClick}>
{ _t("Unpin") }
</AccessibleButton>
}
</div> </div>
</AccessibleButton>; </AccessibleButton>;
} }

View file

@ -15,41 +15,40 @@ limitations under the License.
*/ */
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import { MatrixEvent } from 'matrix-js-sdk/src';
import {MatrixClientPeg} from '../../../MatrixClientPeg'; import { MatrixClientPeg } from '../../../MatrixClientPeg';
import * as sdk from '../../../index'; import * as sdk from '../../../index';
import { _t } from '../../../languageHandler'; import { _t } from '../../../languageHandler';
import {getNameForEventRoom, userLabelForEventRoom} import { getNameForEventRoom, userLabelForEventRoom }
from '../../../utils/KeyVerificationStateObserver'; from '../../../utils/KeyVerificationStateObserver';
import dis from "../../../dispatcher/dispatcher"; import dis from "../../../dispatcher/dispatcher";
import {RightPanelPhases} from "../../../stores/RightPanelStorePhases"; import { RightPanelPhases } from "../../../stores/RightPanelStorePhases";
import {Action} from "../../../dispatcher/actions"; import { Action } from "../../../dispatcher/actions";
import EventTileBubble from "./EventTileBubble"; import EventTileBubble from "./EventTileBubble";
import {replaceableComponent} from "../../../utils/replaceableComponent"; import { replaceableComponent } from "../../../utils/replaceableComponent";
interface IProps {
mxEvent: MatrixEvent
}
@replaceableComponent("views.messages.MKeyVerificationRequest") @replaceableComponent("views.messages.MKeyVerificationRequest")
export default class MKeyVerificationRequest extends React.Component { export default class MKeyVerificationRequest extends React.Component<IProps> {
constructor(props) { public componentDidMount() {
super(props);
this.state = {};
}
componentDidMount() {
const request = this.props.mxEvent.verificationRequest; const request = this.props.mxEvent.verificationRequest;
if (request) { if (request) {
request.on("change", this._onRequestChanged); request.on("change", this.onRequestChanged);
} }
} }
componentWillUnmount() { public componentWillUnmount() {
const request = this.props.mxEvent.verificationRequest; const request = this.props.mxEvent.verificationRequest;
if (request) { if (request) {
request.off("change", this._onRequestChanged); request.off("change", this.onRequestChanged);
} }
} }
_openRequest = () => { private openRequest = () => {
const {verificationRequest} = this.props.mxEvent; const { verificationRequest } = this.props.mxEvent;
const member = MatrixClientPeg.get().getUser(verificationRequest.otherUserId); const member = MatrixClientPeg.get().getUser(verificationRequest.otherUserId);
dis.dispatch({ dis.dispatch({
action: Action.SetRightPanelPhase, action: Action.SetRightPanelPhase,
@ -58,15 +57,15 @@ export default class MKeyVerificationRequest extends React.Component {
}); });
}; };
_onRequestChanged = () => { private onRequestChanged = () => {
this.forceUpdate(); this.forceUpdate();
}; };
_onAcceptClicked = async () => { private onAcceptClicked = async () => {
const request = this.props.mxEvent.verificationRequest; const request = this.props.mxEvent.verificationRequest;
if (request) { if (request) {
try { try {
this._openRequest(); this.openRequest();
await request.accept(); await request.accept();
} catch (err) { } catch (err) {
console.error(err.message); 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; const request = this.props.mxEvent.verificationRequest;
if (request) { if (request) {
try { try {
@ -85,7 +84,7 @@ export default class MKeyVerificationRequest extends React.Component {
} }
}; };
_acceptedLabel(userId) { private acceptedLabel(userId: string) {
const client = MatrixClientPeg.get(); const client = MatrixClientPeg.get();
const myUserId = client.getUserId(); const myUserId = client.getUserId();
if (userId === myUserId) { 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 client = MatrixClientPeg.get();
const myUserId = client.getUserId(); const myUserId = client.getUserId();
const {cancellationCode} = this.props.mxEvent.verificationRequest; const {cancellationCode} = this.props.mxEvent.verificationRequest;
@ -115,9 +114,8 @@ export default class MKeyVerificationRequest extends React.Component {
} }
} }
render() { public render() {
const AccessibleButton = sdk.getComponent("elements.AccessibleButton"); const AccessibleButton = sdk.getComponent("elements.AccessibleButton");
const FormButton = sdk.getComponent("elements.FormButton");
const {mxEvent} = this.props; const {mxEvent} = this.props;
const request = mxEvent.verificationRequest; const request = mxEvent.verificationRequest;
@ -134,11 +132,11 @@ export default class MKeyVerificationRequest extends React.Component {
let stateLabel; let stateLabel;
const accepted = request.ready || request.started || request.done; const accepted = request.ready || request.started || request.done;
if (accepted) { if (accepted) {
stateLabel = (<AccessibleButton onClick={this._openRequest}> stateLabel = (<AccessibleButton onClick={this.openRequest}>
{this._acceptedLabel(request.receivingUserId)} {this.acceptedLabel(request.receivingUserId)}
</AccessibleButton>); </AccessibleButton>);
} else if (request.cancelled) { } else if (request.cancelled) {
stateLabel = this._cancelledLabel(request.cancellingUserId); stateLabel = this.cancelledLabel(request.cancellingUserId);
} else if (request.accepting) { } else if (request.accepting) {
stateLabel = _t("Accepting …"); stateLabel = _t("Accepting …");
} else if (request.declining) { } else if (request.declining) {
@ -153,8 +151,12 @@ export default class MKeyVerificationRequest extends React.Component {
subtitle = userLabelForEventRoom(request.requestingUserId, mxEvent.getRoomId()); subtitle = userLabelForEventRoom(request.requestingUserId, mxEvent.getRoomId());
if (request.canAccept) { if (request.canAccept) {
stateNode = (<div className="mx_cryptoEvent_buttons"> stateNode = (<div className="mx_cryptoEvent_buttons">
<FormButton kind="danger" onClick={this._onRejectClicked} label={_t("Decline")} /> <AccessibleButton kind="danger" onClick={this.onRejectClicked}>
<FormButton onClick={this._onAcceptClicked} label={_t("Accept")} /> {_t("Decline")}
</AccessibleButton>
<AccessibleButton onClick={this.onAcceptClicked}>
{_t("Accept")}
</AccessibleButton>
</div>); </div>);
} }
} else { // request sent by us } else { // request sent by us
@ -174,8 +176,3 @@ export default class MKeyVerificationRequest extends React.Component {
return null; return null;
} }
} }
MKeyVerificationRequest.propTypes = {
/* the MatrixEvent to show */
mxEvent: PropTypes.object.isRequired,
};

View file

@ -14,28 +14,29 @@ See the License for the specific language governing permissions and
limitations under the License. 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 EncryptionInfo from "./EncryptionInfo";
import VerificationPanel from "./VerificationPanel"; import VerificationPanel from "./VerificationPanel";
import {MatrixClientPeg} from "../../../MatrixClientPeg"; import { MatrixClientPeg } from "../../../MatrixClientPeg";
import {ensureDMExists} from "../../../createRoom"; import { ensureDMExists } from "../../../createRoom";
import {useEventEmitter} from "../../../hooks/useEventEmitter"; import { useEventEmitter } from "../../../hooks/useEventEmitter";
import Modal from "../../../Modal"; import Modal from "../../../Modal";
import {PHASE_REQUESTED, PHASE_UNSENT} from "matrix-js-sdk/src/crypto/verification/request/VerificationRequest";
import * as sdk from "../../../index"; import * as sdk from "../../../index";
import {_t} from "../../../languageHandler"; 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 dis from "../../../dispatcher/dispatcher"; import dis from "../../../dispatcher/dispatcher";
import {Action} from "../../../dispatcher/actions"; import { Action } from "../../../dispatcher/actions";
import {RightPanelPhases} from "../../../stores/RightPanelStorePhases"; import { RightPanelPhases } from "../../../stores/RightPanelStorePhases";
// cancellation codes which constitute a key mismatch // cancellation codes which constitute a key mismatch
const MISMATCHES = ["m.key_mismatch", "m.user_error", "m.mismatched_sas"]; const MISMATCHES = ["m.key_mismatch", "m.user_error", "m.mismatched_sas"];
interface IProps { interface IProps {
member: RoomMember; member: RoomMember | User;
onClose: () => void; onClose: () => void;
verificationRequest: VerificationRequest; verificationRequest: VerificationRequest;
verificationRequestPromise: Promise<VerificationRequest>; verificationRequestPromise: Promise<VerificationRequest>;

View file

@ -1594,7 +1594,7 @@ const UserInfo: React.FC<IProps> = ({
content = ( content = (
<BasicUserInfo <BasicUserInfo
room={room} room={room}
member={member} member={member as User}
groupId={groupId as string} groupId={groupId as string}
devices={devices} devices={devices}
isRoomEncrypted={isRoomEncrypted} /> isRoomEncrypted={isRoomEncrypted} />
@ -1605,7 +1605,7 @@ const UserInfo: React.FC<IProps> = ({
content = ( content = (
<EncryptionPanel <EncryptionPanel
{...props as React.ComponentProps<typeof EncryptionPanel>} {...props as React.ComponentProps<typeof EncryptionPanel>}
member={member} member={member as User | RoomMember}
onClose={onEncryptionPanelClose} onClose={onEncryptionPanelClose}
isRoomEncrypted={isRoomEncrypted} isRoomEncrypted={isRoomEncrypted}
/> />

View file

@ -195,14 +195,7 @@ export default class VerificationPanel extends React.PureComponent<IProps, IStat
private renderQRReciprocatePhase() { private renderQRReciprocatePhase() {
const {member, request} = this.props; const {member, request} = this.props;
let Button; const AccessibleButton = sdk.getComponent("elements.AccessibleButton");
// a bit of a hack, but the FormButton should only be used in the right panel
// they should probably just be the same component with a css class applied to it?
if (this.props.inDialog) {
Button = sdk.getComponent("elements.AccessibleButton");
} else {
Button = sdk.getComponent("elements.FormButton");
}
const description = request.isSelfVerification ? const description = request.isSelfVerification ?
_t("Almost there! Is your other session showing the same shield?") : _t("Almost there! Is your other session showing the same shield?") :
_t("Almost there! Is %(displayName)s showing the same shield?", { _t("Almost there! Is %(displayName)s showing the same shield?", {
@ -211,21 +204,24 @@ export default class VerificationPanel extends React.PureComponent<IProps, IStat
let body: JSX.Element; let body: JSX.Element;
if (this.state.reciprocateQREvent) { if (this.state.reciprocateQREvent) {
// Element Web doesn't support scanning yet, so assume here we're the client being scanned. // Element Web doesn't support scanning yet, so assume here we're the client being scanned.
//
// we're passing both a label and a child string to Button as
// FormButton and AccessibleButton expect this differently
body = <React.Fragment> body = <React.Fragment>
<p>{description}</p> <p>{description}</p>
<E2EIcon isUser={true} status="verified" size={128} hideTooltip={true} /> <E2EIcon isUser={true} status="verified" size={128} hideTooltip={true} />
<div className="mx_VerificationPanel_reciprocateButtons"> <div className="mx_VerificationPanel_reciprocateButtons">
<Button <AccessibleButton
label={_t("No")} kind="danger" kind="danger"
disabled={this.state.reciprocateButtonClicked} disabled={this.state.reciprocateButtonClicked}
onClick={this.onReciprocateNoClick}>{_t("No")}</Button> onClick={this.onReciprocateNoClick}
<Button >
label={_t("Yes")} kind="primary" { _t("No") }
</AccessibleButton>
<AccessibleButton
kind="primary"
disabled={this.state.reciprocateButtonClicked} disabled={this.state.reciprocateButtonClicked}
onClick={this.onReciprocateYesClick}>{_t("Yes")}</Button> onClick={this.onReciprocateYesClick}
>
{ _t("Yes") }
</AccessibleButton>
</div> </div>
</React.Fragment>; </React.Fragment>;
} else { } else {

View file

@ -134,6 +134,10 @@ export default class AliasSettings extends React.Component {
} }
} }
this.setState({ localAliases }); this.setState({ localAliases });
if (localAliases.length === 0) {
this.setState({ detailsOpen: true });
}
} finally { } finally {
this.setState({ localAliasesLoading: false }); this.setState({ localAliasesLoading: false });
} }
@ -388,7 +392,7 @@ export default class AliasSettings extends React.Component {
/> />
<span className='mx_SettingsTab_subheading mx_AliasSettings_localAliasHeader'>{_t("Local Addresses")}</span> <span className='mx_SettingsTab_subheading mx_AliasSettings_localAliasHeader'>{_t("Local Addresses")}</span>
<p>{_t("Set addresses for this room so users can find this room through your homeserver (%(localDomain)s)", {localDomain})}</p> <p>{_t("Set addresses for this room so users can find this room through your homeserver (%(localDomain)s)", {localDomain})}</p>
<details onToggle={this.onLocalAliasesToggled}> <details onToggle={this.onLocalAliasesToggled} open={this.state.detailsOpen}>
<summary>{ this.state.detailsOpen ? _t('Show less') : _t("Show more")}</summary> <summary>{ this.state.detailsOpen ? _t('Show less') : _t("Show more")}</summary>
{localAliasesList} {localAliasesList}
</details> </details>

View file

@ -15,15 +15,17 @@ limitations under the License.
*/ */
import React from 'react'; 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 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 RateLimitedFunc from '../../../ratelimitedfunc';
import SettingsStore from "../../../settings/SettingsStore"; import SettingsStore from "../../../settings/SettingsStore";
import AutoHideScrollbar from "../../structures/AutoHideScrollbar"; import AutoHideScrollbar from "../../structures/AutoHideScrollbar";
import { UIFeature } from "../../../settings/UIFeature"; import { UIFeature } from "../../../settings/UIFeature";
import { ResizeNotifier } from "../../../utils/ResizeNotifier"; import ResizeNotifier from "../../../utils/ResizeNotifier";
import CallViewForRoom from '../voip/CallViewForRoom'; import CallViewForRoom from '../voip/CallViewForRoom';
import { objectHasDiff } from "../../../utils/objects"; import { objectHasDiff } from "../../../utils/objects";
import { replaceableComponent } from "../../../utils/replaceableComponent"; import { replaceableComponent } from "../../../utils/replaceableComponent";
@ -106,9 +108,7 @@ export default class AuxPanel extends React.Component<IProps, IState> {
if (this.props.room && SettingsStore.getValue("feature_state_counters")) { if (this.props.room && SettingsStore.getValue("feature_state_counters")) {
const stateEvs = this.props.room.currentState.getStateEvents('re.jki.counter'); const stateEvs = this.props.room.currentState.getStateEvents('re.jki.counter');
stateEvs.sort((a, b) => { stateEvs.sort((a, b) => lexicographicCompare(a.getStateKey(), b.getStateKey()));
return a.getStateKey() < b.getStateKey();
});
for (const ev of stateEvs) { for (const ev of stateEvs) {
const title = ev.getContent().title; const title = ev.getContent().title;

View file

@ -17,7 +17,6 @@ limitations under the License.
import React from 'react'; import React from 'react';
import classNames from "classnames"; import classNames from "classnames";
import { EventType } from "matrix-js-sdk/src/@types/event"; import { EventType } from "matrix-js-sdk/src/@types/event";
import { EventStatus, MatrixEvent } from "matrix-js-sdk/src/models/event"; import { EventStatus, MatrixEvent } from "matrix-js-sdk/src/models/event";
import { Relations } from "matrix-js-sdk/src/models/relations"; import { Relations } from "matrix-js-sdk/src/models/relations";
@ -29,24 +28,24 @@ import { hasText } from "../../../TextForEvent";
import * as sdk from "../../../index"; import * as sdk from "../../../index";
import dis from '../../../dispatcher/dispatcher'; import dis from '../../../dispatcher/dispatcher';
import SettingsStore from "../../../settings/SettingsStore"; import SettingsStore from "../../../settings/SettingsStore";
import {Layout} from "../../../settings/Layout"; import { Layout } from "../../../settings/Layout";
import {formatTime} from "../../../DateUtils"; import { formatTime } from "../../../DateUtils";
import {MatrixClientPeg} from '../../../MatrixClientPeg'; import { MatrixClientPeg } from '../../../MatrixClientPeg';
import {ALL_RULE_TYPES} from "../../../mjolnir/BanList"; import { ALL_RULE_TYPES } from "../../../mjolnir/BanList";
import MatrixClientContext from "../../../contexts/MatrixClientContext"; import MatrixClientContext from "../../../contexts/MatrixClientContext";
import {E2E_STATE} from "./E2EIcon"; import { E2E_STATE } from "./E2EIcon";
import {toRem} from "../../../utils/units"; import { toRem } from "../../../utils/units";
import {WidgetType} from "../../../widgets/WidgetType"; import { WidgetType } from "../../../widgets/WidgetType";
import RoomAvatar from "../avatars/RoomAvatar"; import RoomAvatar from "../avatars/RoomAvatar";
import {WIDGET_LAYOUT_EVENT_TYPE} from "../../../stores/widgets/WidgetLayoutStore"; import { WIDGET_LAYOUT_EVENT_TYPE } from "../../../stores/widgets/WidgetLayoutStore";
import {objectHasDiff} from "../../../utils/objects"; import { objectHasDiff } from "../../../utils/objects";
import {replaceableComponent} from "../../../utils/replaceableComponent"; import { replaceableComponent } from "../../../utils/replaceableComponent";
import Tooltip from "../elements/Tooltip"; import Tooltip from "../elements/Tooltip";
import { EditorStateTransfer } from "../../../utils/EditorStateTransfer"; import EditorStateTransfer from "../../../utils/EditorStateTransfer";
import { RoomPermalinkCreator } from '../../../utils/permalinks/Permalinks'; import { RoomPermalinkCreator } from '../../../utils/permalinks/Permalinks';
import {StaticNotificationState} from "../../../stores/notifications/StaticNotificationState"; import { StaticNotificationState } from "../../../stores/notifications/StaticNotificationState";
import NotificationBadge from "./NotificationBadge"; import NotificationBadge from "./NotificationBadge";
import {ComposerInsertPayload} from "../../../dispatcher/payloads/ComposerInsertPayload"; import { ComposerInsertPayload } from "../../../dispatcher/payloads/ComposerInsertPayload";
import { Action } from '../../../dispatcher/actions'; import { Action } from '../../../dispatcher/actions';
const eventTileTypes = { const eventTileTypes = {

View file

@ -14,30 +14,31 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import React, {useContext} from "react"; import React, { useContext } from "react";
import {EventType} from "matrix-js-sdk/src/@types/event"; 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 MatrixClientContext from "../../../contexts/MatrixClientContext";
import RoomContext from "../../../contexts/RoomContext"; import RoomContext from "../../../contexts/RoomContext";
import DMRoomMap from "../../../utils/DMRoomMap"; import DMRoomMap from "../../../utils/DMRoomMap";
import {_t} from "../../../languageHandler"; import { _t } from "../../../languageHandler";
import AccessibleButton from "../elements/AccessibleButton"; 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 RoomAvatar from "../avatars/RoomAvatar";
import defaultDispatcher from "../../../dispatcher/dispatcher"; import defaultDispatcher from "../../../dispatcher/dispatcher";
import {ViewUserPayload} from "../../../dispatcher/payloads/ViewUserPayload"; import { ViewUserPayload } from "../../../dispatcher/payloads/ViewUserPayload";
import {Action} from "../../../dispatcher/actions"; import { Action } from "../../../dispatcher/actions";
import dis from "../../../dispatcher/dispatcher"; import dis from "../../../dispatcher/dispatcher";
import SpaceStore from "../../../stores/SpaceStore"; import SpaceStore from "../../../stores/SpaceStore";
import {showSpaceInvite} from "../../../utils/space"; import { showSpaceInvite } from "../../../utils/space";
import { privateShouldBeEncrypted } from "../../../createRoom"; import { privateShouldBeEncrypted } from "../../../createRoom";
import EventTileBubble from "../messages/EventTileBubble"; import EventTileBubble from "../messages/EventTileBubble";
import { ROOM_SECURITY_TAB } from "../dialogs/RoomSettingsDialog"; import { ROOM_SECURITY_TAB } from "../dialogs/RoomSettingsDialog";
function hasExpectedEncryptionSettings(room): boolean { function hasExpectedEncryptionSettings(matrixClient: MatrixClient, room: Room): boolean {
const isEncrypted: boolean = room._client?.isRoomEncrypted(room.roomId); const isEncrypted: boolean = matrixClient.isRoomEncrypted(room.roomId);
const isPublic: boolean = room.getJoinRule() === "public"; const isPublic: boolean = room.getJoinRule() === "public";
return isPublic || !privateShouldBeEncrypted() || isEncrypted; return isPublic || !privateShouldBeEncrypted() || isEncrypted;
} }
@ -61,7 +62,7 @@ const NewRoomIntro = () => {
defaultDispatcher.dispatch<ViewUserPayload>({ defaultDispatcher.dispatch<ViewUserPayload>({
action: Action.ViewUser, action: Action.ViewUser,
// XXX: We should be using a real member object and not assuming what the receiver wants. // 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,
}); });
}} /> }} />
@ -194,7 +195,7 @@ const NewRoomIntro = () => {
return <div className="mx_NewRoomIntro"> return <div className="mx_NewRoomIntro">
{ !hasExpectedEncryptionSettings(room) && ( { !hasExpectedEncryptionSettings(cli, room) && (
<EventTileBubble <EventTileBubble
className="mx_cryptoEvent mx_cryptoEvent_icon_warning" className="mx_cryptoEvent mx_cryptoEvent_icon_warning"
title={_t("End-to-end encryption isn't enabled")} title={_t("End-to-end encryption isn't enabled")}

View file

@ -22,7 +22,7 @@ import { EventType } from "matrix-js-sdk/src/@types/event";
import { _t, _td } from "../../../languageHandler"; import { _t, _td } from "../../../languageHandler";
import { RovingTabIndexProvider } from "../../../accessibility/RovingTabIndex"; 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 RoomListStore, { LISTS_UPDATE_EVENT } from "../../../stores/room-list/RoomListStore";
import RoomViewStore from "../../../stores/RoomViewStore"; import RoomViewStore from "../../../stores/RoomViewStore";
import { ITagMap } from "../../../stores/room-list/algorithms/models"; import { ITagMap } from "../../../stores/room-list/algorithms/models";

View file

@ -45,7 +45,7 @@ import { ActionPayload } from "../../../dispatcher/payloads";
import { Enable, Resizable } from "re-resizable"; import { Enable, Resizable } from "re-resizable";
import { Direction } from "re-resizable/lib/resizer"; import { Direction } from "re-resizable/lib/resizer";
import { polyfillTouchEvent } from "../../../@types/polyfill"; import { polyfillTouchEvent } from "../../../@types/polyfill";
import { ResizeNotifier } from "../../../utils/ResizeNotifier"; import ResizeNotifier from "../../../utils/ResizeNotifier";
import { RoomNotificationStateStore } from "../../../stores/notifications/RoomNotificationStateStore"; import { RoomNotificationStateStore } from "../../../stores/notifications/RoomNotificationStateStore";
import RoomListLayoutStore from "../../../stores/room-list/RoomListLayoutStore"; import RoomListLayoutStore from "../../../stores/room-list/RoomListLayoutStore";
import { arrayFastClone, arrayHasOrderChange } from "../../../utils/arrays"; import { arrayFastClone, arrayHasOrderChange } from "../../../utils/arrays";

View file

@ -119,7 +119,7 @@ export default class RoomTile extends React.PureComponent<IProps, IState> {
}; };
private onLocalEchoUpdated = (ev: MatrixEvent, room: Room) => { 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}); this.setState({hasUnsentEvents: this.countUnsentEvents() > 0});
}; };
@ -316,7 +316,7 @@ export default class RoomTile extends React.PureComponent<IProps, IState> {
0, 0,
)); ));
} else { } 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) { if ((ev as React.KeyboardEvent).key === Key.ENTER) {

View file

@ -1,99 +0,0 @@
/*
Copyright 2015, 2016 OpenMarket Ltd
Copyright 2020 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, {createRef} 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";
@replaceableComponent("views.rooms.SearchBar")
export default class SearchBar extends React.Component {
constructor(props) {
super(props);
this._search_term = createRef();
this.state = {
scope: 'Room',
};
}
onThisRoomClick = () => {
this.setState({ scope: 'Room' }, () => this._searchIfQuery());
};
onAllRoomsClick = () => {
this.setState({ scope: 'All' }, () => this._searchIfQuery());
};
onSearchChange = (e) => {
switch (e.key) {
case Key.ENTER:
this.onSearch();
break;
case Key.ESCAPE:
this.props.onCancelClick();
break;
}
};
_searchIfQuery() {
if (this._search_term.current.value) {
this.onSearch();
}
}
onSearch = () => {
this.props.onSearch(this._search_term.current.value, this.state.scope);
};
render() {
const searchButtonClasses = classNames("mx_SearchBar_searchButton", {
mx_SearchBar_searching: this.props.searchInProgress,
});
const thisRoomClasses = classNames("mx_SearchBar_button", {
mx_SearchBar_unselected: this.state.scope !== 'Room',
});
const allRoomsClasses = classNames("mx_SearchBar_button", {
mx_SearchBar_unselected: this.state.scope !== 'All',
});
return (
<>
<div className="mx_SearchBar">
<div className="mx_SearchBar_buttons" role="radiogroup">
<AccessibleButton className={ thisRoomClasses } onClick={this.onThisRoomClick} aria-checked={this.state.scope === 'Room'} role="radio">
{_t("This Room")}
</AccessibleButton>
<AccessibleButton className={ allRoomsClasses } onClick={this.onAllRoomsClick} aria-checked={this.state.scope === 'All'} role="radio">
{_t("All Rooms")}
</AccessibleButton>
</div>
<div className="mx_SearchBar_input mx_textinput">
<input ref={this._search_term} type="text" autoFocus={true} placeholder={_t("Search…")} onKeyDown={this.onSearchChange} />
<AccessibleButton className={ searchButtonClasses } onClick={this.onSearch} />
</div>
<AccessibleButton className="mx_SearchBar_cancel" onClick={this.props.onCancelClick} />
</div>
<DesktopBuildsNotice isRoomEncrypted={this.props.isRoomEncrypted} kind={WarningKind.Search} />
</>
);
}
}

View file

@ -0,0 +1,130 @@
/*
Copyright 2015, 2016 OpenMarket Ltd
Copyright 2020 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, { 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";
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<IProps, IState> {
private searchTerm: RefObject<HTMLInputElement> = createRef();
constructor(props: IProps) {
super(props);
this.state = {
scope: SearchScope.Room,
};
}
private onThisRoomClick = () => {
this.setState({ scope: SearchScope.Room }, () => this.searchIfQuery());
};
private onAllRoomsClick = () => {
this.setState({ scope: SearchScope.All }, () => this.searchIfQuery());
};
private onSearchChange = (e: React.KeyboardEvent) => {
switch (e.key) {
case Key.ENTER:
this.onSearch();
break;
case Key.ESCAPE:
this.props.onCancelClick();
break;
}
};
private searchIfQuery(): void {
if (this.searchTerm.current.value) {
this.onSearch();
}
}
private onSearch = (): void => {
this.props.onSearch(this.searchTerm.current.value, this.state.scope);
};
public render() {
const searchButtonClasses = classNames("mx_SearchBar_searchButton", {
mx_SearchBar_searching: this.props.searchInProgress,
});
const thisRoomClasses = classNames("mx_SearchBar_button", {
mx_SearchBar_unselected: this.state.scope !== SearchScope.Room,
});
const allRoomsClasses = classNames("mx_SearchBar_button", {
mx_SearchBar_unselected: this.state.scope !== SearchScope.All,
});
return (
<>
<div className="mx_SearchBar">
<div className="mx_SearchBar_buttons" role="radiogroup">
<AccessibleButton
className={thisRoomClasses}
onClick={this.onThisRoomClick}
aria-checked={this.state.scope === SearchScope.Room}
role="radio"
>
{_t("This Room")}
</AccessibleButton>
<AccessibleButton
className={allRoomsClasses}
onClick={this.onAllRoomsClick}
aria-checked={this.state.scope === SearchScope.All}
role="radio"
>
{_t("All Rooms")}
</AccessibleButton>
</div>
<div className="mx_SearchBar_input mx_textinput">
<input
ref={this.searchTerm}
type="text"
autoFocus={true}
placeholder={_t("Search…")}
onKeyDown={this.onSearchChange}
/>
<AccessibleButton className={ searchButtonClasses } onClick={this.onSearch} />
</div>
<AccessibleButton className="mx_SearchBar_cancel" onClick={this.props.onCancelClick} />
</div>
<DesktopBuildsNotice isRoomEncrypted={this.props.isRoomEncrypted} kind={WarningKind.Search} />
</>
);
}
}

View file

@ -16,7 +16,7 @@ limitations under the License.
*/ */
import React from 'react'; import React from 'react';
import Room from "matrix-js-sdk/src/models/room"; import { Room } from "matrix-js-sdk/src/models/room";
import { RoomMember } from "matrix-js-sdk/src/models/room-member"; import { RoomMember } from "matrix-js-sdk/src/models/room-member";
import { MatrixEvent } from "matrix-js-sdk/src/models/event"; import { MatrixEvent } from "matrix-js-sdk/src/models/event";

View file

@ -14,18 +14,20 @@ See the License for the specific language governing permissions and
limitations under the License. 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 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 { _t } from "../../../languageHandler";
import RoomAvatar from "../avatars/RoomAvatar"; import RoomAvatar from "../avatars/RoomAvatar";
import {useContextMenu} from "../../structures/ContextMenu"; import { useContextMenu } from "../../structures/ContextMenu";
import SpaceCreateMenu from "./SpaceCreateMenu"; import SpaceCreateMenu from "./SpaceCreateMenu";
import {SpaceItem} from "./SpaceTreeLevel"; import { SpaceItem } from "./SpaceTreeLevel";
import AccessibleTooltipButton from "../elements/AccessibleTooltipButton"; import AccessibleTooltipButton from "../elements/AccessibleTooltipButton";
import {useEventEmitter} from "../../../hooks/useEventEmitter"; import { useEventEmitter } from "../../../hooks/useEventEmitter";
import SpaceStore, { import SpaceStore, {
HOME_SPACE,
UPDATE_INVITED_SPACES, UPDATE_INVITED_SPACES,
UPDATE_SELECTED_SPACE, UPDATE_SELECTED_SPACE,
UPDATE_TOP_LEVEL_SPACES, UPDATE_TOP_LEVEL_SPACES,
@ -37,9 +39,10 @@ import {
RovingAccessibleTooltipButton, RovingAccessibleTooltipButton,
RovingTabIndexProvider, RovingTabIndexProvider,
} from "../../../accessibility/RovingTabIndex"; } from "../../../accessibility/RovingTabIndex";
import {Key} from "../../../Keyboard"; import { Key } from "../../../Keyboard";
import {RoomNotificationStateStore} from "../../../stores/notifications/RoomNotificationStateStore"; import { RoomNotificationStateStore } from "../../../stores/notifications/RoomNotificationStateStore";
import {NotificationState} from "../../../stores/notifications/NotificationState"; import { NotificationState } from "../../../stores/notifications/NotificationState";
import SettingsStore from "../../../settings/SettingsStore";
interface IButtonProps { interface IButtonProps {
space?: Room; space?: Room;
@ -120,11 +123,65 @@ const useSpaces = (): [Room[], Room[], Room | null] => {
return [invites, spaces, activeSpace]; return [invites, spaces, activeSpace];
}; };
interface IInnerSpacePanelProps {
children?: ReactNode;
isPanelCollapsed: boolean;
setPanelCollapsed: Dispatch<SetStateAction<boolean>>;
}
// Optimisation based on https://github.com/atlassian/react-beautiful-dnd/blob/master/docs/api/droppable.md#recommended-droppable--performance-optimisation
const InnerSpacePanel = React.memo<IInnerSpacePanelProps>(({ children, isPanelCollapsed, setPanelCollapsed }) => {
const [invites, spaces, activeSpace] = useSpaces();
const activeSpaces = activeSpace ? [activeSpace] : [];
const homeNotificationState = SettingsStore.getValue("feature_spaces.all_rooms")
? RoomNotificationStateStore.instance.globalState : SpaceStore.instance.getNotificationState(HOME_SPACE);
return <div className="mx_SpaceTreeLevel">
<SpaceButton
className="mx_SpaceButton_home"
onClick={() => SpaceStore.instance.setActiveSpace(null)}
selected={!activeSpace}
tooltip={SettingsStore.getValue("feature_spaces.all_rooms") ? _t("All rooms") : _t("Home")}
notificationState={homeNotificationState}
isNarrow={isPanelCollapsed}
/>
{ invites.map(s => (
<SpaceItem
key={s.roomId}
space={s}
activeSpaces={activeSpaces}
isPanelCollapsed={isPanelCollapsed}
onExpand={() => setPanelCollapsed(false)}
/>
)) }
{ spaces.map((s, i) => (
<Draggable key={s.roomId} draggableId={s.roomId} index={i}>
{(provided, snapshot) => (
<SpaceItem
{...provided.draggableProps}
{...provided.dragHandleProps}
key={s.roomId}
innerRef={provided.innerRef}
className={snapshot.isDragging
? "mx_SpaceItem_dragging"
: undefined}
space={s}
activeSpaces={activeSpaces}
isPanelCollapsed={isPanelCollapsed}
onExpand={() => setPanelCollapsed(false)}
/>
)}
</Draggable>
)) }
{ children }
</div>;
});
const SpacePanel = () => { const SpacePanel = () => {
// We don't need the handle as we position the menu in a constant location // We don't need the handle as we position the menu in a constant location
// eslint-disable-next-line @typescript-eslint/no-unused-vars // eslint-disable-next-line @typescript-eslint/no-unused-vars
const [menuDisplayed, handle, openMenu, closeMenu] = useContextMenu<void>(); const [menuDisplayed, handle, openMenu, closeMenu] = useContextMenu<void>();
const [invites, spaces, activeSpace] = useSpaces();
const [isPanelCollapsed, setPanelCollapsed] = useState(true); const [isPanelCollapsed, setPanelCollapsed] = useState(true);
useEffect(() => { useEffect(() => {
@ -133,10 +190,6 @@ const SpacePanel = () => {
} }
}, [isPanelCollapsed]); // eslint-disable-line react-hooks/exhaustive-deps }, [isPanelCollapsed]); // eslint-disable-line react-hooks/exhaustive-deps
const newClasses = classNames("mx_SpaceButton_new", {
mx_SpaceButton_newCancel: menuDisplayed,
});
let contextMenu = null; let contextMenu = null;
if (menuDisplayed) { if (menuDisplayed) {
contextMenu = <SpaceCreateMenu onFinished={closeMenu} />; contextMenu = <SpaceCreateMenu onFinished={closeMenu} />;
@ -203,59 +256,61 @@ const SpacePanel = () => {
} }
}; };
const activeSpaces = activeSpace ? [activeSpace] : []; const onNewClick = menuDisplayed ? closeMenu : () => {
const expandCollapseButtonTitle = isPanelCollapsed ? _t("Expand space panel") : _t("Collapse space panel"); if (!isPanelCollapsed) setPanelCollapsed(true);
// TODO drag and drop for re-arranging order openMenu();
return <RovingTabIndexProvider handleHomeEnd={true} onKeyDown={onKeyDown}> };
{({onKeyDownHandler}) => (
<ul return (
className={classNames("mx_SpacePanel", { collapsed: isPanelCollapsed })} <DragDropContext onDragEnd={result => {
onKeyDown={onKeyDownHandler} if (!result.destination) return; // dropped outside the list
> SpaceStore.instance.moveRootSpace(result.source.index, result.destination.index);
<AutoHideScrollbar className="mx_SpacePanel_spaceTreeWrapper"> }}>
<div className="mx_SpaceTreeLevel"> <RovingTabIndexProvider handleHomeEnd={true} onKeyDown={onKeyDown}>
<SpaceButton {({onKeyDownHandler}) => (
className="mx_SpaceButton_home" <ul
onClick={() => SpaceStore.instance.setActiveSpace(null)} className={classNames("mx_SpacePanel", { collapsed: isPanelCollapsed })}
selected={!activeSpace} onKeyDown={onKeyDownHandler}
tooltip={_t("All rooms")} >
notificationState={RoomNotificationStateStore.instance.globalState} <Droppable droppableId="top-level-spaces">
isNarrow={isPanelCollapsed} {(provided, snapshot) => (
<AutoHideScrollbar
{...provided.droppableProps}
wrappedRef={provided.innerRef}
className="mx_SpacePanel_spaceTreeWrapper"
style={snapshot.isDraggingOver ? {
pointerEvents: "none",
} : undefined}
>
<InnerSpacePanel
isPanelCollapsed={isPanelCollapsed}
setPanelCollapsed={setPanelCollapsed}
>
{ provided.placeholder }
</InnerSpacePanel>
<SpaceButton
className={classNames("mx_SpaceButton_new", {
mx_SpaceButton_newCancel: menuDisplayed,
})}
tooltip={menuDisplayed ? _t("Cancel") : _t("Create a space")}
onClick={onNewClick}
isNarrow={isPanelCollapsed}
/>
</AutoHideScrollbar>
)}
</Droppable>
<AccessibleTooltipButton
className={classNames("mx_SpacePanel_toggleCollapse", { expanded: !isPanelCollapsed })}
onClick={() => setPanelCollapsed(!isPanelCollapsed)}
title={isPanelCollapsed ? _t("Expand space panel") : _t("Collapse space panel")}
/> />
{ invites.map(s => <SpaceItem { contextMenu }
key={s.roomId} </ul>
space={s} )}
activeSpaces={activeSpaces} </RovingTabIndexProvider>
isPanelCollapsed={isPanelCollapsed} </DragDropContext>
onExpand={() => setPanelCollapsed(false)} );
/>) }
{ spaces.map(s => <SpaceItem
key={s.roomId}
space={s}
activeSpaces={activeSpaces}
isPanelCollapsed={isPanelCollapsed}
onExpand={() => setPanelCollapsed(false)}
/>) }
</div>
<SpaceButton
className={newClasses}
tooltip={menuDisplayed ? _t("Cancel") : _t("Create a space")}
onClick={menuDisplayed ? closeMenu : () => {
if (!isPanelCollapsed) setPanelCollapsed(true);
openMenu();
}}
isNarrow={isPanelCollapsed}
/>
</AutoHideScrollbar>
<AccessibleTooltipButton
className={classNames("mx_SpacePanel_toggleCollapse", {expanded: !isPanelCollapsed})}
onClick={() => setPanelCollapsed(!isPanelCollapsed)}
title={expandCollapseButtonTitle}
/>
{ contextMenu }
</ul>
)}
</RovingTabIndexProvider>
}; };
export default SpacePanel; export default SpacePanel;

View file

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import React, { createRef } from "react"; import React, { createRef, InputHTMLAttributes, LegacyRef } from "react";
import classNames from "classnames"; import classNames from "classnames";
import { Room } from "matrix-js-sdk/src/models/room"; import { Room } from "matrix-js-sdk/src/models/room";
@ -49,13 +49,14 @@ import { StaticNotificationState } from "../../../stores/notifications/StaticNot
import { NotificationColor } from "../../../stores/notifications/NotificationColor"; import { NotificationColor } from "../../../stores/notifications/NotificationColor";
import { getKeyBindingsManager, RoomListAction } from "../../../KeyBindingsManager"; import { getKeyBindingsManager, RoomListAction } from "../../../KeyBindingsManager";
interface IItemProps { interface IItemProps extends InputHTMLAttributes<HTMLLIElement> {
space?: Room; space?: Room;
activeSpaces: Room[]; activeSpaces: Room[];
isNested?: boolean; isNested?: boolean;
isPanelCollapsed?: boolean; isPanelCollapsed?: boolean;
onExpand?: Function; onExpand?: Function;
parents?: Set<string>; parents?: Set<string>;
innerRef?: LegacyRef<HTMLLIElement>;
} }
interface IItemState { interface IItemState {
@ -362,15 +363,16 @@ export class SpaceItem extends React.PureComponent<IItemProps, IItemState> {
} }
render() { 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 isNarrow = this.props.isPanelCollapsed;
const collapsed = this.isCollapsed; const collapsed = this.isCollapsed;
const isActive = activeSpaces.includes(space); const isActive = activeSpaces.includes(space);
const itemClasses = classNames({ const itemClasses = classNames(this.props.className, {
"mx_SpaceItem": true, "mx_SpaceItem": true,
"mx_SpaceItem_narrow": isNarrow, "mx_SpaceItem_narrow": isPanelCollapsed,
"collapsed": collapsed, "collapsed": collapsed,
"hasSubSpaces": this.state.childSpaces?.length, "hasSubSpaces": this.state.childSpaces?.length,
}); });
@ -379,7 +381,7 @@ export class SpaceItem extends React.PureComponent<IItemProps, IItemState> {
const classes = classNames("mx_SpaceButton", { const classes = classNames("mx_SpaceButton", {
mx_SpaceButton_active: isActive, mx_SpaceButton_active: isActive,
mx_SpaceButton_hasMenuOpen: !!this.state.contextMenuPosition, mx_SpaceButton_hasMenuOpen: !!this.state.contextMenuPosition,
mx_SpaceButton_narrow: isNarrow, mx_SpaceButton_narrow: isPanelCollapsed,
mx_SpaceButton_invite: isInvite, mx_SpaceButton_invite: isInvite,
}); });
const notificationState = isInvite const notificationState = isInvite
@ -392,7 +394,7 @@ export class SpaceItem extends React.PureComponent<IItemProps, IItemState> {
spaces={this.state.childSpaces} spaces={this.state.childSpaces}
activeSpaces={activeSpaces} activeSpaces={activeSpaces}
isNested={true} isNested={true}
parents={new Set(this.props.parents).add(this.props.space.roomId)} parents={new Set(parents).add(space.roomId)}
/>; />;
} }
@ -414,13 +416,13 @@ export class SpaceItem extends React.PureComponent<IItemProps, IItemState> {
/> : null; /> : null;
return ( return (
<li className={itemClasses}> <li {...otherProps} className={itemClasses} ref={innerRef}>
<RovingAccessibleTooltipButton <RovingAccessibleTooltipButton
className={classes} className={classes}
title={space.name} title={space.name}
onClick={this.onClick} onClick={this.onClick}
onContextMenu={this.onContextMenu} onContextMenu={this.onContextMenu}
forceHide={!isNarrow || !!this.state.contextMenuPosition} forceHide={!isPanelCollapsed || !!this.state.contextMenuPosition}
role="treeitem" role="treeitem"
aria-expanded={!collapsed} aria-expanded={!collapsed}
inputRef={this.buttonRef} inputRef={this.buttonRef}
@ -429,7 +431,7 @@ export class SpaceItem extends React.PureComponent<IItemProps, IItemState> {
{ toggleCollapseButton } { toggleCollapseButton }
<div className="mx_SpaceButton_selectionWrapper"> <div className="mx_SpaceButton_selectionWrapper">
<RoomAvatar width={avatarSize} height={avatarSize} room={space} /> <RoomAvatar width={avatarSize} height={avatarSize} room={space} />
{ !isNarrow && <span className="mx_SpaceButton_name">{ space.name }</span> } { !isPanelCollapsed && <span className="mx_SpaceButton_name">{ space.name }</span> }
{ notifBadge } { notifBadge }
{ this.renderContextMenu() } { this.renderContextMenu() }
</div> </div>

View file

@ -15,8 +15,8 @@ limitations under the License.
*/ */
import React, {ReactNode} from "react"; import React, {ReactNode} from "react";
import AccessibleButton from "../elements/AccessibleButton";
import FormButton from "../elements/FormButton";
import {XOR} from "../../../@types/common"; import {XOR} from "../../../@types/common";
export interface IProps { export interface IProps {
@ -50,8 +50,12 @@ const GenericToast: React.FC<XOR<IPropsExtended, IProps>> = ({
{detailContent} {detailContent}
</div> </div>
<div className="mx_Toast_buttons" aria-live="off"> <div className="mx_Toast_buttons" aria-live="off">
{onReject && rejectLabel && <FormButton label={rejectLabel} kind="danger" onClick={onReject} /> } {onReject && rejectLabel && <AccessibleButton kind="danger" onClick={onReject}>
<FormButton label={acceptLabel} onClick={onAccept} /> { rejectLabel }
</AccessibleButton> }
<AccessibleButton onClick={onAccept} kind="primary">
{ acceptLabel }
</AccessibleButton>
</div> </div>
</div>; </div>;
}; };

View file

@ -23,7 +23,7 @@ import { _t } from '../../../languageHandler';
import { ActionPayload } from '../../../dispatcher/payloads'; import { ActionPayload } from '../../../dispatcher/payloads';
import CallHandler, { AudioID } from '../../../CallHandler'; import CallHandler, { AudioID } from '../../../CallHandler';
import RoomAvatar from '../avatars/RoomAvatar'; import RoomAvatar from '../avatars/RoomAvatar';
import FormButton from '../elements/FormButton'; import AccessibleButton from '../elements/AccessibleButton';
import { CallState } from 'matrix-js-sdk/src/webrtc/call'; import { CallState } from 'matrix-js-sdk/src/webrtc/call';
import {replaceableComponent} from "../../../utils/replaceableComponent"; import {replaceableComponent} from "../../../utils/replaceableComponent";
import AccessibleTooltipButton from '../elements/AccessibleTooltipButton'; import AccessibleTooltipButton from '../elements/AccessibleTooltipButton';
@ -143,21 +143,22 @@ export default class IncomingCallBox extends React.Component<IProps, IState> {
/> />
</div> </div>
<div className="mx_IncomingCallBox_buttons"> <div className="mx_IncomingCallBox_buttons">
<FormButton <AccessibleButton
className={"mx_IncomingCallBox_decline"} className={"mx_IncomingCallBox_decline"}
onClick={this.onRejectClick} onClick={this.onRejectClick}
kind="danger" kind="danger"
label={_t("Decline")} >
/> { _t("Decline") }
</AccessibleButton>
<div className="mx_IncomingCallBox_spacer" /> <div className="mx_IncomingCallBox_spacer" />
<FormButton <AccessibleButton
className={"mx_IncomingCallBox_accept"} className={"mx_IncomingCallBox_accept"}
onClick={this.onAnswerClick} onClick={this.onAnswerClick}
kind="primary" kind="primary"
label={_t("Accept")} >
/> { _t("Accept") }
</AccessibleButton>
</div> </div>
</div>; </div>;
} }
} }

View file

@ -14,10 +14,11 @@
* limitations under the License. * limitations under the License.
*/ */
import {MatrixClientPeg} from "../MatrixClientPeg"; import { MatrixClient } from "matrix-js-sdk/src/client";
import {IMediaEventContent, IPreparedMedia, prepEventContentAsMedia} from "./models/IMediaEventContent"; import { ResizeMethod } from "matrix-js-sdk/src/@types/partials";
import {ResizeMethod} from "../Avatar";
import {MatrixClient} from "matrix-js-sdk/src/client"; import { MatrixClientPeg } from "../MatrixClientPeg";
import { IMediaEventContent, IPreparedMedia, prepEventContentAsMedia } from "./models/IMediaEventContent";
// Populate this class with the details of your customisations when copying it. // Populate this class with the details of your customisations when copying it.

View file

@ -164,4 +164,9 @@ export enum Action {
* Inserts content into the active composer. Should be used with ComposerInsertPayload * Inserts content into the active composer. Should be used with ComposerInsertPayload
*/ */
ComposerInsert = "composer_insert", ComposerInsert = "composer_insert",
/**
* Switches space. Should be used with SwitchSpacePayload.
*/
SwitchSpace = "switch_space",
} }

View file

@ -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;
}

View file

@ -15,6 +15,7 @@ limitations under the License.
*/ */
import { RoomMember } from "matrix-js-sdk/src/models/room-member"; import { RoomMember } from "matrix-js-sdk/src/models/room-member";
import { User } from "matrix-js-sdk/src/models/user";
import { ActionPayload } from "../payloads"; import { ActionPayload } from "../payloads";
import { Action } from "../actions"; 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 * The member to view. May be null or falsy to indicate that no member
* should be shown (hide whichever relevant components). * should be shown (hide whichever relevant components).
*/ */
member?: RoomMember; member?: RoomMember | User;
} }

View file

@ -794,6 +794,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.", "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.", "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.", "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.",
"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 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", "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", "Send and receive voice messages": "Send and receive voice messages",
"Render LaTeX maths in messages": "Render LaTeX maths in messages", "Render LaTeX maths in messages": "Render LaTeX maths in messages",
@ -1021,9 +1025,10 @@
"You can change these anytime.": "You can change these anytime.", "You can change these anytime.": "You can change these anytime.",
"Creating...": "Creating...", "Creating...": "Creating...",
"Create": "Create", "Create": "Create",
"All rooms": "All rooms",
"Home": "Home",
"Expand space panel": "Expand space panel", "Expand space panel": "Expand space panel",
"Collapse space panel": "Collapse space panel", "Collapse space panel": "Collapse space panel",
"All rooms": "All rooms",
"Click to copy": "Click to copy", "Click to copy": "Click to copy",
"Copied!": "Copied!", "Copied!": "Copied!",
"Failed to copy": "Failed to copy", "Failed to copy": "Failed to copy",
@ -1935,6 +1940,7 @@
"Error loading Widget": "Error loading Widget", "Error loading Widget": "Error loading Widget",
"Error - Mixed content": "Error - Mixed content", "Error - Mixed content": "Error - Mixed content",
"Popout widget": "Popout widget", "Popout widget": "Popout widget",
"Message search initialisation failed, check <a>your settings</a> for more information": "Message search initialisation failed, check <a>your settings</a> for more information",
"Use the <a>Desktop app</a> to see all encrypted files": "Use the <a>Desktop app</a> to see all encrypted files", "Use the <a>Desktop app</a> to see all encrypted files": "Use the <a>Desktop app</a> to see all encrypted files",
"Use the <a>Desktop app</a> to search encrypted messages": "Use the <a>Desktop app</a> to search encrypted messages", "Use the <a>Desktop app</a> to search encrypted messages": "Use the <a>Desktop app</a> 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", "This version of %(brand)s does not support viewing some encrypted files": "This version of %(brand)s does not support viewing some encrypted files",
@ -2032,7 +2038,6 @@
"Continue with %(provider)s": "Continue with %(provider)s", "Continue with %(provider)s": "Continue with %(provider)s",
"Sign in with single sign-on": "Sign in with single sign-on", "Sign in with single sign-on": "Sign in with single sign-on",
"And %(count)s more...|other": "And %(count)s more...", "And %(count)s more...|other": "And %(count)s more...",
"Home": "Home",
"Enter a server name": "Enter a server name", "Enter a server name": "Enter a server name",
"Looks good": "Looks good", "Looks good": "Looks good",
"You are not allowed to view this server's rooms list": "You are not allowed to view this server's rooms list", "You are not allowed to view this server's rooms list": "You are not allowed to view this server's rooms list",
@ -2508,6 +2513,8 @@
"Update status": "Update status", "Update status": "Update status",
"Set status": "Set status", "Set status": "Set status",
"Set a new status...": "Set a new status...", "Set a new status...": "Set a new status...",
"Move up": "Move up",
"Move down": "Move down",
"View Community": "View Community", "View Community": "View Community",
"Unable to start audio streaming.": "Unable to start audio streaming.", "Unable to start audio streaming.": "Unable to start audio streaming.",
"Failed to start livestream": "Failed to start livestream", "Failed to start livestream": "Failed to start livestream",
@ -2654,7 +2661,7 @@
"%(count)s messages deleted.|one": "%(count)s message deleted.", "%(count)s messages deleted.|one": "%(count)s message deleted.",
"Your Communities": "Your Communities", "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!", "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", "Error whilst fetching joined communities": "Error whilst fetching joined communities",
"Create a new community": "Create a new community", "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.", "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.",
@ -3009,5 +3016,6 @@
"Esc": "Esc", "Esc": "Esc",
"Enter": "Enter", "Enter": "Enter",
"Space": "Space", "Space": "Space",
"End": "End" "End": "End",
"[number]": "[number]"
} }

View file

@ -112,7 +112,7 @@ export interface IVariables {
[key: string]: SubstitutionValue; [key: string]: SubstitutionValue;
} }
type Tags = Record<string, SubstitutionValue>; export type Tags = Record<string, SubstitutionValue>;
export type TranslatedString = string | React.ReactNode; export type TranslatedString = string | React.ReactNode;

View file

@ -92,7 +92,7 @@ export class BanList {
if (!room) return; if (!room) return;
for (const eventType of ALL_RULE_TYPES) { 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) { for (const ev of events) {
if (!ev.getStateKey()) continue; if (!ev.getStateKey()) continue;

View file

@ -263,7 +263,13 @@ function uint8ToString(buf: Buffer) {
return out; return out;
} }
export async function submitFeedback(endpoint: string, label: string, comment: string, canContact = false) { export async function submitFeedback(
endpoint: string,
label: string,
comment: string,
canContact = false,
extraData: Record<string, string> = {},
) {
let version = "UNKNOWN"; let version = "UNKNOWN";
try { try {
version = await PlatformPeg.get().getAppVersion(); 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("platform", PlatformPeg.get().getHumanReadableName());
body.append("user_id", MatrixClientPeg.get()?.getUserId()); 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, () => {}); await _submitReport(SdkConfig.get().bug_report_endpoint_url, body, () => {});
} }

View file

@ -1,6 +1,6 @@
/* /*
Copyright 2017 Travis Ralston 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"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
@ -94,6 +94,9 @@ export interface ISetting {
[level: SettingLevel]: string; [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 // The supported levels are required. Preferably, use the preset arrays
// at the top of this file to define this rather than a custom array. // at the top of this file to define this rather than a custom array.
supportedLevels?: SettingLevel[]; supportedLevels?: SettingLevel[];
@ -127,6 +130,7 @@ export interface ISetting {
image: string; // require(...) image: string; // require(...)
feedbackSubheading?: string; feedbackSubheading?: string;
feedbackLabel?: string; feedbackLabel?: string;
extraSettings?: string[];
}; };
} }
@ -174,8 +178,33 @@ export const SETTINGS: {[setting: string]: ISetting} = {
feedbackSubheading: _td("Your feedback will help make spaces better. " + feedbackSubheading: _td("Your feedback will help make spaces better. " +
"The more detail you can go into, the better."), "The more detail you can go into, the better."),
feedbackLabel: "spaces-feedback", feedbackLabel: "spaces-feedback",
extraSettings: [
"feature_spaces.all_rooms",
"feature_spaces.space_member_dms",
"feature_spaces.space_dm_badges",
],
}, },
}, },
"feature_spaces.all_rooms": {
displayName: _td("Show all rooms in Home"),
supportedLevels: LEVELS_FEATURE,
default: true,
controller: new ReloadOnChangeController(),
},
"feature_spaces.space_member_dms": {
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 People in Spaces"),
supportedLevels: LEVELS_FEATURE,
default: false,
controller: new ReloadOnChangeController(),
},
"feature_dnd": { "feature_dnd": {
isFeature: true, isFeature: true,
displayName: _td("Show options to enable 'Do not disturb' mode"), displayName: _td("Show options to enable 'Do not disturb' mode"),

View file

@ -248,6 +248,16 @@ export default class SettingsStore {
return _t(displayName as string); 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. * Determines if a setting is also a feature.
* @param {string} settingName The setting to look up. * @param {string} settingName The setting to look up.

View file

@ -107,8 +107,9 @@ export class CommunityPrototypeStore extends AsyncStoreWithClient<IState> {
const pl = generalChat.currentState.getStateEvents("m.room.power_levels", ""); const pl = generalChat.currentState.getStateEvents("m.room.power_levels", "");
if (!pl) return this.isAdminOf(communityId); 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; return invitePl <= myMember.powerLevel;
} }
@ -159,10 +160,16 @@ export class CommunityPrototypeStore extends AsyncStoreWithClient<IState> {
if (SettingsStore.getValue("feature_communities_v2_prototypes")) { if (SettingsStore.getValue("feature_communities_v2_prototypes")) {
const data = this.matrixClient.getAccountData("im.vector.group_info." + roomId); const data = this.matrixClient.getAccountData("im.vector.group_info." + roomId);
if (data && data.getContent()) { 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<any> { protected async onReady(): Promise<any> {

View file

@ -276,7 +276,7 @@ class RoomViewStore extends Store<ActionPayload> {
const address = this.state.roomAlias || this.state.roomId; const address = this.state.roomAlias || this.state.roomId;
const viaServers = this.state.viaServers || []; const viaServers = this.state.viaServers || [];
try { try {
await retry<void, MatrixError>(() => cli.joinRoom(address, { await retry<any, MatrixError>(() => cli.joinRoom(address, {
viaServers, viaServers,
...payload.opts, ...payload.opts,
}), NUM_JOIN_RETRY, (err) => { }), NUM_JOIN_RETRY, (err) => {

View file

@ -14,36 +14,44 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import {ListIteratee, Many, sortBy, throttle} from "lodash"; import { ListIteratee, Many, sortBy, throttle } from "lodash";
import {EventType, RoomType} from "matrix-js-sdk/src/@types/event"; import { EventType, RoomType } from "matrix-js-sdk/src/@types/event";
import {Room} from "matrix-js-sdk/src/models/room"; import { Room } from "matrix-js-sdk/src/models/room";
import {MatrixEvent} from "matrix-js-sdk/src/models/event"; import { MatrixEvent } from "matrix-js-sdk/src/models/event";
import {AsyncStoreWithClient} from "./AsyncStoreWithClient"; import { AsyncStoreWithClient } from "./AsyncStoreWithClient";
import defaultDispatcher from "../dispatcher/dispatcher"; import defaultDispatcher from "../dispatcher/dispatcher";
import {ActionPayload} from "../dispatcher/payloads"; import { ActionPayload } from "../dispatcher/payloads";
import RoomListStore from "./room-list/RoomListStore"; import RoomListStore from "./room-list/RoomListStore";
import SettingsStore from "../settings/SettingsStore"; import SettingsStore from "../settings/SettingsStore";
import DMRoomMap from "../utils/DMRoomMap"; import DMRoomMap from "../utils/DMRoomMap";
import {FetchRoomFn} from "./notifications/ListNotificationState"; import { FetchRoomFn } from "./notifications/ListNotificationState";
import {SpaceNotificationState} from "./notifications/SpaceNotificationState"; import { SpaceNotificationState } from "./notifications/SpaceNotificationState";
import {RoomNotificationStateStore} from "./notifications/RoomNotificationStateStore"; import { RoomNotificationStateStore } from "./notifications/RoomNotificationStateStore";
import {DefaultTagID} from "./room-list/models"; import { DefaultTagID } from "./room-list/models";
import {EnhancedMap, mapDiff} from "../utils/maps"; import { EnhancedMap, mapDiff } from "../utils/maps";
import {setHasDiff} from "../utils/sets"; import { setHasDiff } from "../utils/sets";
import {ISpaceSummaryEvent, ISpaceSummaryRoom} from "../components/structures/SpaceRoomDirectory"; import { ISpaceSummaryEvent, ISpaceSummaryRoom } from "../components/structures/SpaceRoomDirectory";
import RoomViewStore from "./RoomViewStore"; import RoomViewStore from "./RoomViewStore";
import { Action } from "../dispatcher/actions";
import { arrayHasDiff } from "../utils/arrays";
import { objectDiff } from "../utils/objects";
import { arrayHasOrderChange } from "../utils/arrays";
import { reorderLexicographically } from "../utils/stringOrderField";
type SpaceKey = string | symbol;
interface IState {} interface IState {}
const ACTIVE_SPACE_LS_KEY = "mx_active_space"; const ACTIVE_SPACE_LS_KEY = "mx_active_space";
export const HOME_SPACE = Symbol("home-space");
export const SUGGESTED_ROOMS = Symbol("suggested-rooms"); export const SUGGESTED_ROOMS = Symbol("suggested-rooms");
export const UPDATE_TOP_LEVEL_SPACES = Symbol("top-level-spaces"); export const UPDATE_TOP_LEVEL_SPACES = Symbol("top-level-spaces");
export const UPDATE_INVITED_SPACES = Symbol("invited-spaces"); export const UPDATE_INVITED_SPACES = Symbol("invited-spaces");
export const UPDATE_SELECTED_SPACE = Symbol("selected-space"); 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 { export interface ISuggestedRoom extends ISpaceSummaryRoom {
viaServers: string[]; viaServers: string[];
@ -51,7 +59,8 @@ export interface ISuggestedRoom extends ISpaceSummaryRoom {
const MAX_SUGGESTED_ROOMS = 20; 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] const partitionSpacesAndRooms = (arr: Room[]): [Room[], Room[]] => { // [spaces, rooms]
return arr.reduce((result, room: Room) => { return arr.reduce((result, room: Room) => {
@ -60,18 +69,18 @@ const partitionSpacesAndRooms = (arr: Room[]): [Room[], Room[]] => { // [spaces,
}, [[], []]); }, [[], []]);
}; };
// For sorting space children using a validated `order`, `m.room.create`'s `origin_server_ts`, `room_id` const validOrder = (order: string): string | undefined => {
export const getOrder = (order: string, creationTs: number, roomId: string): Array<Many<ListIteratee<any>>> => { if (typeof order === "string" && order.length <= 50 && Array.from(order).every((c: string) => {
let validatedOrder: string = null;
if (typeof order === "string" && Array.from(order).every((c: string) => {
const charCode = c.charCodeAt(0); const charCode = c.charCodeAt(0);
return charCode >= 0x20 && charCode <= 0x7E; return charCode >= 0x20 && charCode <= 0x7E;
})) { })) {
validatedOrder = order; return order;
} }
};
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<Many<ListIteratee<any>>> => {
return [validOrder(order), creationTs, roomId];
} }
const getRoomFn: FetchRoomFn = (room: Room) => { const getRoomFn: FetchRoomFn = (room: Room) => {
@ -85,16 +94,19 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
// The spaces representing the roots of the various tree-like hierarchies // The spaces representing the roots of the various tree-like hierarchies
private rootSpaces: Room[] = []; private rootSpaces: Room[] = [];
// The list of rooms not present in any currently joined spaces
private orphanedRooms = new Set<string>();
// Map from room ID to set of spaces which list it as a child // Map from room ID to set of spaces which list it as a child
private parentMap = new EnhancedMap<string, Set<string>>(); private parentMap = new EnhancedMap<string, Set<string>>();
// Map from spaceId to SpaceNotificationState instance representing that space // Map from SpaceKey to SpaceNotificationState instance representing that space
private notificationStateMap = new Map<string, SpaceNotificationState>(); private notificationStateMap = new Map<SpaceKey, SpaceNotificationState>();
// Map from space key to Set of room IDs that should be shown as part of that space's filter // Map from space key to Set of room IDs that should be shown as part of that space's filter
private spaceFilteredRooms = new Map<string, Set<string>>(); private spaceFilteredRooms = new Map<SpaceKey, Set<string>>();
// The space currently selected in the Space Panel - if null then All Rooms is selected // The space currently selected in the Space Panel - if null then Home is selected
private _activeSpace?: Room = null; private _activeSpace?: Room = null;
private _suggestedRooms: ISuggestedRoom[] = []; private _suggestedRooms: ISuggestedRoom[] = [];
private _invitedSpaces = new Set<Room>(); private _invitedSpaces = new Set<Room>();
private spaceOrderLocalEchoMap = new Map<string, string>();
public get invitedSpaces(): Room[] { public get invitedSpaces(): Room[] {
return Array.from(this._invitedSpaces); return Array.from(this._invitedSpaces);
@ -133,7 +145,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
// if the space being selected is an invite then always view that invite // 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 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 // 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" this.matrixClient?.getRoom(roomId)?.getMyMembership() === "join"
) { ) {
defaultDispatcher.dispatch({ defaultDispatcher.dispatch({
@ -214,7 +226,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
const roomId = ev.getStateKey(); const roomId = ev.getStateKey();
const childRoom = this.matrixClient?.getRoom(roomId); const childRoom = this.matrixClient?.getRoom(roomId);
const createTs = childRoom?.currentState.getStateEvents(EventType.RoomCreate, "")?.getTs(); const createTs = childRoom?.currentState.getStateEvents(EventType.RoomCreate, "")?.getTs();
return getOrder(ev.getContent().order, createTs, roomId); return getChildOrder(ev.getContent().order, createTs, roomId);
}).map(ev => { }).map(ev => {
return this.matrixClient.getRoom(ev.getStateKey()); return this.matrixClient.getRoom(ev.getStateKey());
}).filter(room => { }).filter(room => {
@ -251,10 +263,10 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
} }
public getSpaceFilteredRoomIds = (space: Room | null): Set<string> => { public getSpaceFilteredRoomIds = (space: Room | null): Set<string> => {
if (!space) { if (!space && SettingsStore.getValue("feature_spaces.all_rooms")) {
return new Set(this.matrixClient.getVisibleRooms().map(r => r.roomId)); 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(() => { private rebuild = throttle(() => {
@ -285,7 +297,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
}); });
}); });
const [rootSpaces] = partitionSpacesAndRooms(Array.from(unseenChildren)); const [rootSpaces, orphanedRooms] = partitionSpacesAndRooms(Array.from(unseenChildren));
// somewhat algorithm to handle full-cycles // somewhat algorithm to handle full-cycles
const detachedNodes = new Set<Room>(spaces); const detachedNodes = new Set<Room>(spaces);
@ -326,7 +338,8 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
// rootSpaces.push(space); // rootSpaces.push(space);
// }); // });
this.rootSpaces = rootSpaces; this.orphanedRooms = new Set(orphanedRooms.map(r => r.roomId));
this.rootSpaces = this.sortRootSpaces(rootSpaces);
this.parentMap = backrefs; this.parentMap = backrefs;
// if the currently selected space no longer exists, remove its selection // if the currently selected space no longer exists, remove its selection
@ -338,14 +351,34 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
this.emit(UPDATE_TOP_LEVEL_SPACES, this.spacePanelSpaces); 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 // 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(this.sortRootSpaces(invitedSpaces));
this.emit(UPDATE_INVITED_SPACES, this.invitedSpaces); this.emit(UPDATE_INVITED_SPACES, this.invitedSpaces);
}, 100, {trailing: true, leading: true}); }, 100, {trailing: true, leading: true});
onSpaceUpdate = () => { private onSpaceUpdate = () => {
this.rebuild(); 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) => { private onSpaceMembersChange = (ev: MatrixEvent) => {
// skip this update if we do not have a DM with this user // skip this update if we do not have a DM with this user
if (DMRoomMap.shared().getDMRoomsForUserId(ev.getStateKey()).length < 1) return; if (DMRoomMap.shared().getDMRoomsForUserId(ev.getStateKey()).length < 1) return;
@ -359,6 +392,18 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
const oldFilteredRooms = this.spaceFilteredRooms; const oldFilteredRooms = this.spaceFilteredRooms;
this.spaceFilteredRooms = new Map(); 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<string>(invites.map(room => room.roomId)));
visibleRooms.forEach(room => {
if (this.showInHomeSpace(room)) {
this.spaceFilteredRooms.get(HOME_SPACE).add(room.roomId);
}
});
}
this.rootSpaces.forEach(s => { this.rootSpaces.forEach(s => {
// traverse each space tree in DFS to build up the supersets as you go up, // traverse each space tree in DFS to build up the supersets as you go up,
// reusing results from like subtrees. // reusing results from like subtrees.
@ -374,13 +419,15 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
const roomIds = new Set(childRooms.map(r => r.roomId)); const roomIds = new Set(childRooms.map(r => r.roomId));
const space = this.matrixClient?.getRoom(spaceId); const space = this.matrixClient?.getRoom(spaceId);
// Add relevant DMs if (SettingsStore.getValue("feature_spaces.space_member_dms")) {
space?.getMembers().forEach(member => { // Add relevant DMs
if (member.membership !== "join" && member.membership !== "invite") return; space?.getMembers().forEach(member => {
DMRoomMap.shared().getDMRoomsForUserId(member.userId).forEach(roomId => { if (member.membership !== "join" && member.membership !== "invite") return;
roomIds.add(roomId); DMRoomMap.shared().getDMRoomsForUserId(member.userId).forEach(roomId => {
roomIds.add(roomId);
});
}); });
}); }
const newPath = new Set(parentPath).add(spaceId); const newPath = new Set(parentPath).add(spaceId);
childSpaces.forEach(childSpace => { childSpaces.forEach(childSpace => {
@ -406,6 +453,8 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
// Update NotificationStates // Update NotificationStates
this.getNotificationState(s)?.setRooms(visibleRooms.filter(room => { this.getNotificationState(s)?.setRooms(visibleRooms.filter(room => {
if (roomIds.has(room.roomId)) { if (roomIds.has(room.roomId)) {
if (s !== HOME_SPACE && SettingsStore.getValue("feature_spaces.space_dm_badges")) return true;
return !DMRoomMap.shared().getUserIdForRoomId(room.roomId) return !DMRoomMap.shared().getUserIdForRoomId(room.roomId)
|| RoomListStore.instance.getTagsForRoom(room).includes(DefaultTagID.Favourite); || RoomListStore.instance.getTagsForRoom(room).includes(DefaultTagID.Favourite);
} }
@ -423,8 +472,14 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
parent = this.rootSpaces.find(s => this.spaceFilteredRooms.get(s.roomId)?.has(roomId)); parent = this.rootSpaces.find(s => this.spaceFilteredRooms.get(s.roomId)?.has(roomId));
} }
if (!parent) { if (!parent) {
const parents = Array.from(this.parentMap.get(roomId) || []); const parentIds = Array.from(this.parentMap.get(roomId) || []);
parent = parents.find(p => this.matrixClient.getRoom(p)); 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 // don't trigger a context switch when we are switching a space to match the chosen room
@ -472,6 +527,14 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
} }
}; };
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) => { private onRoomState = (ev: MatrixEvent) => {
const room = this.matrixClient.getRoom(ev.getRoomId()); const room = this.matrixClient.getRoom(ev.getRoomId());
if (!room) return; if (!room) return;
@ -489,6 +552,8 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
// TODO confirm this after implementing parenting behaviour // TODO confirm this after implementing parenting behaviour
if (room.isSpaceRoom()) { if (room.isSpaceRoom()) {
this.onSpaceUpdate(); this.onSpaceUpdate();
} else if (!SettingsStore.getValue("feature_spaces.all_rooms")) {
this.onRoomUpdate(room);
} }
this.emit(room.roomId); this.emit(room.roomId);
break; break;
@ -501,8 +566,47 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
} }
}; };
private onRoomAccountData = (ev: MatrixEvent, room: Room, lastEv?: MatrixEvent) => {
if (!room.isSpaceRoom()) return;
if (ev.getType() === EventType.SpaceOrder) {
this.spaceOrderLocalEchoMap.delete(room.roomId); // clear any local echo
const order = ev.getContent()?.order;
const lastOrder = lastEv?.getContent()?.order;
if (order !== lastOrder) {
this.notifyIfOrderChanged();
}
} else if (ev.getType() === EventType.Tag && !SettingsStore.getValue("feature_spaces.all_rooms")) {
// If the room was in favourites and now isn't or the opposite then update its position in the trees
const oldTags = lastEv?.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<Record<string, string[]>>(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() { protected async reset() {
this.rootSpaces = []; this.rootSpaces = [];
this.orphanedRooms = new Set();
this.parentMap = new EnhancedMap(); this.parentMap = new EnhancedMap();
this.notificationStateMap = new Map(); this.notificationStateMap = new Map();
this.spaceFilteredRooms = new Map(); this.spaceFilteredRooms = new Map();
@ -516,7 +620,11 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
if (this.matrixClient) { if (this.matrixClient) {
this.matrixClient.removeListener("Room", this.onRoom); this.matrixClient.removeListener("Room", this.onRoom);
this.matrixClient.removeListener("Room.myMembership", this.onRoom); this.matrixClient.removeListener("Room.myMembership", this.onRoom);
this.matrixClient.removeListener("Room.accountData", this.onRoomAccountData);
this.matrixClient.removeListener("RoomState.events", this.onRoomState); this.matrixClient.removeListener("RoomState.events", this.onRoomState);
if (!SettingsStore.getValue("feature_spaces.all_rooms")) {
this.matrixClient.removeListener("accountData", this.onAccountData);
}
} }
await this.reset(); await this.reset();
} }
@ -525,7 +633,11 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
if (!SettingsStore.getValue("feature_spaces")) return; if (!SettingsStore.getValue("feature_spaces")) return;
this.matrixClient.on("Room", this.onRoom); this.matrixClient.on("Room", this.onRoom);
this.matrixClient.on("Room.myMembership", this.onRoom); this.matrixClient.on("Room.myMembership", this.onRoom);
this.matrixClient.on("Room.accountData", this.onRoomAccountData);
this.matrixClient.on("RoomState.events", this.onRoomState); this.matrixClient.on("RoomState.events", this.onRoomState);
if (!SettingsStore.getValue("feature_spaces.all_rooms")) {
this.matrixClient.on("accountData", this.onAccountData);
}
await this.onSpaceUpdate(); // trigger an initial update await this.onSpaceUpdate(); // trigger an initial update
@ -550,7 +662,10 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
// Don't context switch when navigating to the space room // Don't context switch when navigating to the space room
// as it will cause you to end up in the wrong room // as it will cause you to end up in the wrong room
this.setActiveSpace(room, false); 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); this.switchToRelatedSpace(roomId);
} }
@ -565,10 +680,16 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
this.setActiveSpace(null, false); this.setActiveSpace(null, false);
} }
break; 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]);
}
} }
} }
public getNotificationState(key: string): SpaceNotificationState { public getNotificationState(key: SpaceKey): SpaceNotificationState {
if (this.notificationStateMap.has(key)) { if (this.notificationStateMap.has(key)) {
return this.notificationStateMap.get(key); return this.notificationStateMap.get(key);
} }
@ -599,6 +720,38 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
} }
childSpaces.forEach(s => this.traverseSpace(s.roomId, fn, includeRooms, newPath)); childSpaces.forEach(s => this.traverseSpace(s.roomId, fn, includeRooms, newPath));
} }
private getSpaceTagOrdering = (space: Room): string | undefined => {
if (this.spaceOrderLocalEchoMap.has(space.roomId)) return this.spaceOrderLocalEchoMap.get(space.roomId);
return validOrder(space.getAccountData(EventType.SpaceOrder)?.getContent()?.order);
};
private sortRootSpaces(spaces: Room[]): Room[] {
return sortBy(spaces, [this.getSpaceTagOrdering, "roomId"]);
}
private async setRootSpaceOrder(space: Room, order: string): Promise<void> {
this.spaceOrderLocalEchoMap.set(space.roomId, order);
try {
await this.matrixClient.setRoomAccountData(space.roomId, EventType.SpaceOrder, { order });
} catch (e) {
console.warn("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 {
const currentOrders = this.rootSpaces.map(this.getSpaceTagOrdering);
const changes = reorderLexicographically(currentOrders, fromIndex, toIndex);
changes.forEach(({ index, order }) => {
this.setRootSpaceOrder(this.rootSpaces[index], order);
});
this.notifyIfOrderChanged();
}
} }
export default class SpaceStore { export default class SpaceStore {

View file

@ -19,6 +19,7 @@ import { Room } from "matrix-js-sdk/src/models/room";
import { RoomListStoreClass } from "./RoomListStore"; import { RoomListStoreClass } from "./RoomListStore";
import { SpaceFilterCondition } from "./filters/SpaceFilterCondition"; import { SpaceFilterCondition } from "./filters/SpaceFilterCondition";
import SpaceStore, { UPDATE_SELECTED_SPACE } from "../SpaceStore"; 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 * Watches for changes in spaces to manage the filter on the provided RoomListStore
@ -28,6 +29,11 @@ export class SpaceWatcher {
private activeSpace: Room = SpaceStore.instance.activeSpace; private activeSpace: Room = SpaceStore.instance.activeSpace;
constructor(private store: RoomListStoreClass) { constructor(private store: RoomListStoreClass) {
if (!SettingsStore.getValue("feature_spaces.all_rooms")) {
this.filter = new SpaceFilterCondition();
this.updateFilter();
store.addFilter(this.filter);
}
SpaceStore.instance.on(UPDATE_SELECTED_SPACE, this.onSelectedSpaceUpdated); SpaceStore.instance.on(UPDATE_SELECTED_SPACE, this.onSelectedSpaceUpdated);
} }
@ -35,7 +41,7 @@ export class SpaceWatcher {
this.activeSpace = activeSpace; this.activeSpace = activeSpace;
if (this.filter) { if (this.filter) {
if (activeSpace) { if (activeSpace || !SettingsStore.getValue("feature_spaces.all_rooms")) {
this.updateFilter(); this.updateFilter();
} else { } else {
this.store.removeFilter(this.filter); this.store.removeFilter(this.filter);
@ -49,9 +55,11 @@ export class SpaceWatcher {
}; };
private updateFilter = () => { private updateFilter = () => {
SpaceStore.instance.traverseSpace(this.activeSpace.roomId, roomId => { if (this.activeSpace) {
this.store.matrixClient?.getRoom(roomId)?.loadMembersIfNeeded(); SpaceStore.instance.traverseSpace(this.activeSpace.roomId, roomId => {
}); this.store.matrixClient?.getRoom(roomId)?.loadMembersIfNeeded();
});
}
this.filter.updateSpace(this.activeSpace); this.filter.updateSpace(this.activeSpace);
}; };
} }

View file

@ -19,7 +19,7 @@ import { Room } from "matrix-js-sdk/src/models/room";
import { FILTER_CHANGED, FilterKind, IFilterCondition } from "./IFilterCondition"; import { FILTER_CHANGED, FilterKind, IFilterCondition } from "./IFilterCondition";
import { IDestroyable } from "../../../utils/IDestroyable"; import { IDestroyable } from "../../../utils/IDestroyable";
import SpaceStore from "../../SpaceStore"; import SpaceStore, { HOME_SPACE } from "../../SpaceStore";
import { setHasDiff } from "../../../utils/sets"; import { setHasDiff } from "../../../utils/sets";
/** /**
@ -29,7 +29,7 @@ import { setHasDiff } from "../../../utils/sets";
* + All DMs * + All DMs
*/ */
export class SpaceFilterCondition extends EventEmitter implements IFilterCondition, IDestroyable { export class SpaceFilterCondition extends EventEmitter implements IFilterCondition, IDestroyable {
private roomIds = new Set<Room>(); private roomIds = new Set<string>();
private space: Room = null; private space: Room = null;
public get kind(): FilterKind { public get kind(): FilterKind {
@ -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) { 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); SpaceStore.instance.on(this.getSpaceEventKey(this.space = space), this.onStoreUpdate);
this.onStoreUpdate(); // initial update from the change to the space this.onStoreUpdate(); // initial update from the change to the space
} }

View file

@ -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"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
@ -14,36 +14,40 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
import { SerializedPart } from "../editor/parts";
import { Caret } from "../editor/caret";
/** /**
* Used while editing, to pass the event, and to preserve editor state * Used while editing, to pass the event, and to preserve editor state
* from one editor instance to another when remounting the editor * from one editor instance to another when remounting the editor
* upon receiving the remote echo for an unsent event. * upon receiving the remote echo for an unsent event.
*/ */
export default class EditorStateTransfer { export default class EditorStateTransfer {
constructor(event) { private serializedParts: SerializedPart[] = null;
this._event = event; private caret: Caret = null;
this._serializedParts = null;
this.caret = null; constructor(private readonly event: MatrixEvent) {}
public setEditorState(caret: Caret, serializedParts: SerializedPart[]) {
this.caret = caret;
this.serializedParts = serializedParts;
} }
setEditorState(caret, serializedParts) { public hasEditorState() {
this._caret = caret; return !!this.serializedParts;
this._serializedParts = serializedParts;
} }
hasEditorState() { public getSerializedParts() {
return !!this._serializedParts; return this.serializedParts;
} }
getSerializedParts() { public getCaret() {
return this._serializedParts; return this.caret;
} }
getCaret() { public getEvent() {
return this._caret; return this.event;
}
getEvent() {
return this._event;
} }
} }

View file

@ -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"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
@ -14,7 +14,10 @@ See the License for the specific language governing permissions and
limitations under the License. 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 * 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' * for any tags in the strings apart from 'a'
* @returns {*} Translated string or react component * @returns {*} Translated string or react component
*/ */
export function messageForResourceLimitError(limitType, adminContact, strings, extraTranslations) { export function messageForResourceLimitError(
limitType: string,
adminContact: string,
strings: Record<string, string>,
extraTranslations?: Tags,
): TranslatedString {
let errString = strings[limitType]; let errString = strings[limitType];
if (errString === undefined) errString = strings['']; 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') { if (err.errcode === 'M_RESOURCE_LIMIT_EXCEEDED') {
const limitError = messageForResourceLimitError( const limitError = messageForResourceLimitError(
err.data.limit_type, err.data.limit_type,

View file

@ -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"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
@ -14,9 +14,12 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import { EventStatus } from 'matrix-js-sdk/src/models/event'; import { Room } from 'matrix-js-sdk/src/models/room';
import {MatrixClientPeg} from '../MatrixClientPeg'; import { MatrixEvent, EventStatus } from 'matrix-js-sdk/src/models/event';
import { MatrixClientPeg } from '../MatrixClientPeg';
import shouldHideEvent from "../shouldHideEvent"; import shouldHideEvent from "../shouldHideEvent";
/** /**
* Returns whether an event should allow actions like reply, reactions, edit, etc. * 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 * 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 * @param {MatrixEvent} mxEvent The event to check
* @returns {boolean} true if actionable * @returns {boolean} true if actionable
*/ */
export function isContentActionable(mxEvent) { export function isContentActionable(mxEvent: MatrixEvent): boolean {
const { status: eventStatus } = mxEvent; const { status: eventStatus } = mxEvent;
// status is SENT before remote-echo, null after // status is SENT before remote-echo, null after
@ -45,7 +48,7 @@ export function isContentActionable(mxEvent) {
return false; return false;
} }
export function canEditContent(mxEvent) { export function canEditContent(mxEvent: MatrixEvent): boolean {
if (mxEvent.status === EventStatus.CANCELLED || mxEvent.getType() !== "m.room.message" || mxEvent.isRedacted()) { if (mxEvent.status === EventStatus.CANCELLED || mxEvent.getType() !== "m.room.message" || mxEvent.isRedacted()) {
return false; return false;
} }
@ -56,7 +59,7 @@ export function canEditContent(mxEvent) {
mxEvent.getSender() === MatrixClientPeg.get().getUserId(); mxEvent.getSender() === MatrixClientPeg.get().getUserId();
} }
export function canEditOwnEvent(mxEvent) { export function canEditOwnEvent(mxEvent: MatrixEvent): boolean {
// for now we only allow editing // for now we only allow editing
// your own events. So this just call through // your own events. So this just call through
// In the future though, moderators will be able to // In the future though, moderators will be able to
@ -67,7 +70,7 @@ export function canEditOwnEvent(mxEvent) {
} }
const MAX_JUMP_DISTANCE = 100; 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 liveTimeline = room.getLiveTimeline();
const events = liveTimeline.getEvents().concat(room.getPendingEvents()); const events = liveTimeline.getEvents().concat(room.getPendingEvents());
const maxIdx = events.length - 1; const maxIdx = events.length - 1;

View file

@ -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"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
@ -15,14 +15,15 @@ limitations under the License.
*/ */
import { SERVICE_TYPES } from 'matrix-js-sdk/src/service-types'; import { SERVICE_TYPES } from 'matrix-js-sdk/src/service-types';
import SdkConfig from '../SdkConfig'; import SdkConfig from '../SdkConfig';
import {MatrixClientPeg} from '../MatrixClientPeg'; import {MatrixClientPeg} from '../MatrixClientPeg';
export function getDefaultIdentityServerUrl() { export function getDefaultIdentityServerUrl(): string {
return SdkConfig.get()['validated_server_config']['isUrl']; return SdkConfig.get()['validated_server_config']['isUrl'];
} }
export function useDefaultIdentityServer() { export function useDefaultIdentityServer(): void {
const url = getDefaultIdentityServerUrl(); const url = getDefaultIdentityServerUrl();
// Account data change will update localstorage, client, etc through dispatcher // Account data change will update localstorage, client, etc through dispatcher
MatrixClientPeg.get().setAccountData("m.identity_server", { 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<boolean> {
let terms; let terms;
try { try {
terms = await MatrixClientPeg.get().getTerms(SERVICE_TYPES.IS, fullUrl); 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); 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"); const event = MatrixClientPeg.get().getAccountData("m.identity_server");
return event && event.getContent() && event.getContent()['base_url']; return event && event.getContent() && event.getContent()['base_url'];
} }

View file

@ -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"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
@ -14,31 +14,33 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import React from 'react'; import React, { ReactNode } from 'react';
import classNames from 'classnames'; import classNames from 'classnames';
import DiffMatchPatch from 'diff-match-patch'; import { diff_match_patch as DiffMatchPatch } from 'diff-match-patch';
import {DiffDOM} from "diff-dom"; import { Action, DiffDOM, IDiff } from "diff-dom";
import { checkBlockNode, bodyToHtml } from "../HtmlUtils"; import { IContent } from "matrix-js-sdk/src/models/event";
import { bodyToHtml, checkBlockNode, IOptsReturnString } from "../HtmlUtils";
const decodeEntities = (function() { const decodeEntities = (function() {
let textarea = null; let textarea = null;
return function(string) { return function(str: string): string {
if (!textarea) { if (!textarea) {
textarea = document.createElement("textarea"); textarea = document.createElement("textarea");
} }
textarea.innerHTML = string; textarea.innerHTML = str;
return textarea.value; return textarea.value;
}; };
})(); })();
function textToHtml(text) { function textToHtml(text: string): string {
const container = document.createElement("div"); const container = document.createElement("div");
container.textContent = text; container.textContent = text;
return container.innerHTML; return container.innerHTML;
} }
function getSanitizedHtmlBody(content) { function getSanitizedHtmlBody(content: IContent): string {
const opts = { const opts: IOptsReturnString = {
stripReplyFallback: true, stripReplyFallback: true,
returnString: 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"); const wrapper = document.createElement(checkBlockNode(child) ? "div" : "span");
wrapper.className = "mx_EditHistoryMessage_insertion"; wrapper.className = "mx_EditHistoryMessage_insertion";
wrapper.appendChild(child); wrapper.appendChild(child);
return wrapper; return wrapper;
} }
function wrapDeletion(child) { function wrapDeletion(child: Node): HTMLElement {
const wrapper = document.createElement(checkBlockNode(child) ? "div" : "span"); const wrapper = document.createElement(checkBlockNode(child) ? "div" : "span");
wrapper.className = "mx_EditHistoryMessage_deletion"; wrapper.className = "mx_EditHistoryMessage_deletion";
wrapper.appendChild(child); wrapper.appendChild(child);
return wrapper; return wrapper;
} }
function findRefNodes(root, route, isAddition) { function findRefNodes(root: Node, route: number[], isAddition = false) {
let refNode = root; let refNode = root;
let refParentNode; let refParentNode;
const end = isAddition ? route.length - 1 : route.length; const end = isAddition ? route.length - 1 : route.length;
@ -79,7 +81,7 @@ function findRefNodes(root, route, isAddition) {
refParentNode = refNode; refParentNode = refNode;
refNode = refNode.childNodes[route[i]]; refNode = refNode.childNodes[route[i]];
} }
return {refNode, refParentNode}; return { refNode, refParentNode };
} }
function diffTreeToDOM(desc) { 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) { if (nextSibling) {
parent.insertBefore(child, nextSibling); parent.insertBefore(child, nextSibling);
} else { } 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, // routes are arrays with indices,
// to be interpreted as a path in the dom tree // to be interpreted as a path in the dom tree
@ -127,7 +129,7 @@ function isRouteOfNextSibling(route1, route2) {
return route2[lastD1Idx] >= route1[lastD1Idx]; return route2[lastD1Idx] >= route1[lastD1Idx];
} }
function adjustRoutes(diff, remainingDiffs) { function adjustRoutes(diff: IDiff, remainingDiffs: IDiff[]): void {
if (diff.action === "removeTextElement" || diff.action === "removeElement") { if (diff.action === "removeTextElement" || diff.action === "removeElement") {
// as removed text is not removed from the html, but marked as deleted, // 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. // 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)); 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); const {refNode, refParentNode} = findRefNodes(originalRootNode, diff.route);
switch (diff.action) { switch (diff.action) {
case "replaceElement": { case Action.ReplaceElement: {
const container = document.createElement("span"); const container = document.createElement("span");
const delNode = wrapDeletion(diffTreeToDOM(diff.oldValue)); const delNode = wrapDeletion(diffTreeToDOM(diff.oldValue));
const insNode = wrapInsertion(diffTreeToDOM(diff.newValue)); const insNode = wrapInsertion(diffTreeToDOM(diff.newValue));
@ -156,22 +158,22 @@ function renderDifferenceInDOM(originalRootNode, diff, diffMathPatch) {
refNode.parentNode.replaceChild(container, refNode); refNode.parentNode.replaceChild(container, refNode);
break; break;
} }
case "removeTextElement": { case Action.RemoveTextElement: {
const delNode = wrapDeletion(stringAsTextNode(diff.value)); const delNode = wrapDeletion(stringAsTextNode(diff.value));
refNode.parentNode.replaceChild(delNode, refNode); refNode.parentNode.replaceChild(delNode, refNode);
break; break;
} }
case "removeElement": { case Action.RemoveElement: {
const delNode = wrapDeletion(diffTreeToDOM(diff.element)); const delNode = wrapDeletion(diffTreeToDOM(diff.element));
refNode.parentNode.replaceChild(delNode, refNode); refNode.parentNode.replaceChild(delNode, refNode);
break; break;
} }
case "modifyTextElement": { case Action.ModifyTextElement: {
const textDiffs = diffMathPatch.diff_main(diff.oldValue, diff.newValue); const textDiffs = diffMathPatch.diff_main(diff.oldValue, diff.newValue);
diffMathPatch.diff_cleanupSemantic(textDiffs); diffMathPatch.diff_cleanupSemantic(textDiffs);
const container = document.createElement("span"); const container = document.createElement("span");
for (const [modifier, text] of textDiffs) { for (const [modifier, text] of textDiffs) {
let textDiffNode = stringAsTextNode(text); let textDiffNode: Node = stringAsTextNode(text);
if (modifier < 0) { if (modifier < 0) {
textDiffNode = wrapDeletion(textDiffNode); textDiffNode = wrapDeletion(textDiffNode);
} else if (modifier > 0) { } else if (modifier > 0) {
@ -182,12 +184,12 @@ function renderDifferenceInDOM(originalRootNode, diff, diffMathPatch) {
refNode.parentNode.replaceChild(container, refNode); refNode.parentNode.replaceChild(container, refNode);
break; break;
} }
case "addElement": { case Action.AddElement: {
const insNode = wrapInsertion(diffTreeToDOM(diff.element)); const insNode = wrapInsertion(diffTreeToDOM(diff.element));
insertBefore(refParentNode, refNode, insNode); insertBefore(refParentNode, refNode, insNode);
break; break;
} }
case "addTextElement": { case Action.AddTextElement: {
// XXX: sometimes diffDOM says insert a newline when there shouldn't be one // 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. // 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 // 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, // 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 // show the link with old href as removed and with the new href as added
case "removeAttribute": case Action.RemoveAttribute:
case "addAttribute": case Action.AddAttribute:
case "modifyAttribute": { case Action.ModifyAttribute: {
const delNode = wrapDeletion(refNode.cloneNode(true)); 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") { if (diff.action === "addAttribute" || diff.action === "modifyAttribute") {
updatedNode.setAttribute(diff.name, diff.newValue); updatedNode.setAttribute(diff.name, diff.newValue);
} else { } 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]); return r1.length === r2.length && !r1.some((e, i) => e !== r2[i]);
} }
// workaround for https://github.com/fiduswriter/diffDOM/issues/90 // workaround for https://github.com/fiduswriter/diffDOM/issues/90
function filterCancelingOutDiffs(originalDiffActions) { function filterCancelingOutDiffs(originalDiffActions: IDiff[]): IDiff[] {
const diffActions = originalDiffActions.slice(); const diffActions = originalDiffActions.slice();
for (let i = 0; i < diffActions.length; ++i) { for (let i = 0; i < diffActions.length; ++i) {
@ -252,7 +254,7 @@ function filterCancelingOutDiffs(originalDiffActions) {
* @param {object} editContent the content for the edit message * @param {object} editContent the content for the edit message
* @return {object} a react element similar to what `bodyToHtml` returns * @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 // wrap the body in a div, DiffDOM needs a root element
const originalBody = `<div>${getSanitizedHtmlBody(originalContent)}</div>`; const originalBody = `<div>${getSanitizedHtmlBody(originalContent)}</div>`;
const editBody = `<div>${getSanitizedHtmlBody(editContent)}</div>`; const editBody = `<div>${getSanitizedHtmlBody(editContent)}</div>`;

View file

@ -14,13 +14,15 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
export default class PinningUtils { export default class PinningUtils {
/** /**
* Determines if the given event may be pinned. * Determines if the given event may be pinned.
* @param {MatrixEvent} event The event to check. * @param {MatrixEvent} event The event to check.
* @return {boolean} True if the event may be pinned, false otherwise. * @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) return false;
if (event.getType() !== "m.room.message") return false; if (event.getType() !== "m.room.message") return false;
if (event.isRedacted()) return false; if (event.isRedacted()) return false;

View file

@ -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"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
@ -14,6 +14,8 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
/** /**
* Given MatrixEvent containing receipts, return the first * Given MatrixEvent containing receipts, return the first
* read receipt from the given user ID, or null if no such * 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 * @param {string} userId A user ID
* @returns {Object} Read receipt * @returns {Object} Read receipt
*/ */
export function findReadReceiptFromUserId(receiptEvent, userId) { export function findReadReceiptFromUserId(receiptEvent: MatrixEvent, userId: string): object | null {
const receiptKeys = Object.keys(receiptEvent.getContent()); const receiptKeys = Object.keys(receiptEvent.getContent());
for (let i = 0; i < receiptKeys.length; ++i) { for (let i = 0; i < receiptKeys.length; ++i) {
const rcpt = receiptEvent.getContent()[receiptKeys[i]]; const rcpt = receiptEvent.getContent()[receiptKeys[i]];

View file

@ -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"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
@ -22,59 +22,58 @@ limitations under the License.
* Fires when the middle panel has been resized by a pixel. * Fires when the middle panel has been resized by a pixel.
* @event module:utils~ResizeNotifier#"middlePanelResizedNoisy" * @event module:utils~ResizeNotifier#"middlePanelResizedNoisy"
*/ */
import { EventEmitter } from "events"; import { EventEmitter } from "events";
import { throttle } from "lodash"; import { throttle } from "lodash";
export default class ResizeNotifier extends EventEmitter { export default class ResizeNotifier extends EventEmitter {
constructor() { private _isResizing = false;
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;
}
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; return this._isResizing;
} }
startResizing() { public startResizing() {
this._isResizing = true; this._isResizing = true;
this.emit("isResizing", true); this.emit("isResizing", true);
} }
stopResizing() { public stopResizing() {
this._isResizing = false; this._isResizing = false;
this.emit("isResizing", false); this.emit("isResizing", false);
} }
_noisyMiddlePanel() { private noisyMiddlePanel() {
this.emit("middlePanelResizedNoisy"); this.emit("middlePanelResizedNoisy");
} }
_updateMiddlePanel() { private updateMiddlePanel() {
this._throttledMiddlePanel(); this.throttledMiddlePanel();
this._noisyMiddlePanel(); this.noisyMiddlePanel();
} }
// can be called in quick succession // can be called in quick succession
notifyLeftHandleResized() { public notifyLeftHandleResized() {
// don't emit event for own region // don't emit event for own region
this._updateMiddlePanel(); this.updateMiddlePanel();
} }
// can be called in quick succession // can be called in quick succession
notifyRightHandleResized() { public notifyRightHandleResized() {
this._updateMiddlePanel(); this.updateMiddlePanel();
} }
notifyTimelineHeightChanged() { public notifyTimelineHeightChanged() {
this._updateMiddlePanel(); this.updateMiddlePanel();
} }
// can be called in quick succession // can be called in quick succession
notifyWindowResized() { public notifyWindowResized() {
this._updateMiddlePanel(); this.updateMiddlePanel();
} }
} }

View file

@ -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'; 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 { export enum E2EStatus {
Warning = "warning", Warning = "warning",
Verified = "verified", Verified = "verified",
Normal = "normal" Normal = "normal"
} }
export async function shieldStatusForRoom(client: Client, room: Room): Promise<E2EStatus> { export async function shieldStatusForRoom(client: MatrixClient, room: Room): Promise<E2EStatus> {
const members = (await room.getEncryptionTargetMembers()).map(({userId}) => userId); const members = (await room.getEncryptionTargetMembers()).map(({userId}) => userId);
const inDMMap = !!DMRoomMap.shared().getUserIdForRoomId(room.roomId); const inDMMap = !!DMRoomMap.shared().getUserIdForRoomId(room.roomId);

View file

@ -16,19 +16,20 @@ limitations under the License.
*/ */
import * as url from "url"; 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 SdkConfig from "../SdkConfig";
import dis from '../dispatcher/dispatcher'; import dis from '../dispatcher/dispatcher';
import WidgetEchoStore from '../stores/WidgetEchoStore'; import WidgetEchoStore from '../stores/WidgetEchoStore';
import SettingsStore from "../settings/SettingsStore"; import SettingsStore from "../settings/SettingsStore";
import {IntegrationManagers} from "../integrations/IntegrationManagers"; import { IntegrationManagers } from "../integrations/IntegrationManagers";
import {Room} from "matrix-js-sdk/src/models/room"; import { WidgetType } from "../widgets/WidgetType";
import {WidgetType} from "../widgets/WidgetType"; import { objectClone } from "./objects";
import {objectClone} from "./objects"; import { _t } from "../languageHandler";
import {_t} from "../languageHandler"; import { IApp } from "../stores/WidgetStore";
import {Capability, IWidget, IWidgetData, MatrixCapabilities} from "matrix-widget-api";
import {IApp} from "../stores/WidgetStore";
// How long we wait for the state event echo to come back from the server // How long we wait for the state event echo to come back from the server
// before waitFor[Room/User]Widget rejects its promise // 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"); return widgets.filter(w => w.content && w.content.type === "m.integration_manager");
} }
static getRoomWidgetsOfType(room: Room, type: WidgetType): IWidgetEvent[] { static getRoomWidgetsOfType(room: Room, type: WidgetType): MatrixEvent[] {
const widgets = WidgetUtils.getRoomWidgets(room); const widgets = WidgetUtils.getRoomWidgets(room) || [];
return (widgets || []).filter(w => { return widgets.filter(w => {
const content = w.getContent(); const content = w.getContent();
return content.url && type.matches(content.type); return content.url && type.matches(content.type);
}); });

View file

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. 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 * Quickly resample an array to have less/more data points. If an input which is larger
@ -223,6 +223,21 @@ export function arrayMerge<T>(...a: T[][]): T[] {
}, new Set<T>())); }, new Set<T>()));
} }
/**
* Moves a single element from fromIndex to toIndex.
* @param {array} list the list from which to construct the new list.
* @param {number} fromIndex the index of the element to move.
* @param {number} toIndex the index of where to put the element.
* @returns {array} A new array with the requested value moved.
*/
export function moveElement<T>(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. * Helper functions to perform LINQ-like queries on arrays.
*/ */

View file

@ -1,5 +1,5 @@
/* /*
Copyright 2017 Vector Creations Ltd Copyright 2017 - 2021 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
@ -14,10 +14,10 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import {createClient} from "matrix-js-sdk/src/matrix"; import { createClient, ICreateClientOpts } from "matrix-js-sdk/src/matrix";
import {IndexedDBCryptoStore} from "matrix-js-sdk/src/crypto/store/indexeddb-crypto-store"; import { IndexedDBCryptoStore } from "matrix-js-sdk/src/crypto/store/indexeddb-crypto-store";
import {WebStorageSessionStore} from "matrix-js-sdk/src/store/session/webstorage"; import { WebStorageSessionStore } from "matrix-js-sdk/src/store/session/webstorage";
import {IndexedDBStore} from "matrix-js-sdk/src/store/indexeddb"; import { IndexedDBStore } from "matrix-js-sdk/src/store/indexeddb";
const localStorage = window.localStorage; const localStorage = window.localStorage;
@ -41,8 +41,8 @@ try {
* *
* @returns {MatrixClient} the newly-created MatrixClient * @returns {MatrixClient} the newly-created MatrixClient
*/ */
export default function createMatrixClient(opts) { export default function createMatrixClient(opts: ICreateClientOpts) {
const storeOpts = { const storeOpts: Partial<ICreateClientOpts> = {
useAuthorizationHeader: true, useAuthorizationHeader: true,
}; };
@ -65,9 +65,10 @@ export default function createMatrixClient(opts) {
); );
} }
opts = Object.assign(storeOpts, opts); return createClient({
...storeOpts,
return createClient(opts); ...opts,
});
} }
createMatrixClient.indexedDbWorkerScript = null; createMatrixClient.indexedDbWorkerScript = null;

View file

@ -0,0 +1,148 @@
/*
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 { alphabetPad, baseToString, stringToBase, DEFAULT_ALPHABET } from "matrix-js-sdk/src/utils";
import { moveElement } from "./arrays";
export function midPointsBetweenStrings(
a: string,
b: string,
count: number,
maxLen: number,
alphabet = DEFAULT_ALPHABET,
): string[] {
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(
alphabetPad(padA, padN + 1, alphabet),
alphabetPad(padB, padN + 1, alphabet),
count,
padN + 1,
alphabet,
);
}
return [];
}
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));
}
interface IEntry {
index: number;
order: string;
}
export const reorderLexicographically = (
orders: Array<string | undefined>,
fromIndex: number,
toIndex: number,
maxLen = 50,
): IEntry[] => {
// sanity check inputs
if (
fromIndex < 0 || toIndex < 0 ||
fromIndex > orders.length || toIndex > orders.length ||
fromIndex === toIndex
) {
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 = moveElement(ordersWithIndices, fromIndex, toIndex);
// check if we have to fill undefined orders to complete placement
const orderToLeftUndefined = newOrder[toIndex - 1]?.order === undefined;
let leftBoundIdx = toIndex;
let rightBoundIdx = toIndex;
let canMoveLeft = true;
const nextBase = newOrder[toIndex + 1]?.order !== undefined
? 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 &&
firstOrderBase !== undefined &&
nextBase - firstOrderBase <= bigToIndex &&
firstOrderBase <= bigToIndex
) {
canMoveLeft = false;
}
const canDisplaceRight = !orderToLeftUndefined;
let canMoveRight = canDisplaceRight;
if (canDisplaceRight) {
const prevBase = newOrder[toIndex - 1]?.order !== undefined
? 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;
rightBoundIdx = i;
}
// verify the right move would be sufficient
if (rightBoundIdx === newOrder.length - 1 &&
(newOrder[rightBoundIdx]
? stringToBase(newOrder[rightBoundIdx].order)
: BigInt(Number.MAX_VALUE)) - prevBase <= (rightBoundIdx - toIndex)
) {
canMoveRight = false;
}
}
// 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 ?? "";
const nextOrder = newOrder[rightBoundIdx + 1]?.order
?? DEFAULT_ALPHABET.charAt(DEFAULT_ALPHABET.length - 1).repeat(prevOrder.length || 1);
const changes = midPointsBetweenStrings(prevOrder, nextOrder, 1 + rightBoundIdx - leftBoundIdx, maxLen);
return changes.map((order, i) => ({
index: newOrder[leftBoundIdx + i].index,
order,
}));
};

View file

@ -42,7 +42,7 @@ import DMRoomMap from "../../../src/utils/DMRoomMap";
configure({ adapter: new Adapter() }); configure({ adapter: new Adapter() });
let client; 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. // wrap MessagePanel with a component which provides the MatrixClient in the context.
class WrappedMessagePanel extends React.Component { class WrappedMessagePanel extends React.Component {

View file

@ -6,7 +6,6 @@ import * as TestUtils from '../../../test-utils';
import {MatrixClientPeg} from '../../../../src/MatrixClientPeg'; import {MatrixClientPeg} from '../../../../src/MatrixClientPeg';
import sdk from '../../../skinned-sdk'; import sdk from '../../../skinned-sdk';
import { DragDropContext } from 'react-beautiful-dnd';
import dis from '../../../../src/dispatcher/dispatcher'; import dis from '../../../../src/dispatcher/dispatcher';
import DMRoomMap from '../../../../src/utils/DMRoomMap'; import DMRoomMap from '../../../../src/utils/DMRoomMap';
@ -68,9 +67,7 @@ describe('RoomList', () => {
const RoomList = sdk.getComponent('views.rooms.RoomList'); const RoomList = sdk.getComponent('views.rooms.RoomList');
const WrappedRoomList = TestUtils.wrapInMatrixClientContext(RoomList); const WrappedRoomList = TestUtils.wrapInMatrixClientContext(RoomList);
root = ReactDOM.render( root = ReactDOM.render(
<DragDropContext> <WrappedRoomList searchFilter="" onResize={() => {}} />,
<WrappedRoomList searchFilter="" onResize={() => {}} />
</DragDropContext>,
parentDiv, parentDiv,
); );
ReactTestUtils.findRenderedComponentWithType(root, RoomList); ReactTestUtils.findRenderedComponentWithType(root, RoomList);

View file

@ -140,8 +140,6 @@ async function changeRoomSettings(session, settings) {
if (settings.alias) { if (settings.alias) {
session.log.step(`sets alias to ${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]"); const aliasField = await session.query(".mx_RoomSettingsDialog .mx_AliasSettings details input[type=text]");
await session.replaceInputText(aliasField, settings.alias.substring(1, settings.alias.lastIndexOf(":"))); await session.replaceInputText(aliasField, settings.alias.substring(1, settings.alias.lastIndexOf(":")));
const addButton = await session.query(".mx_RoomSettingsDialog .mx_AliasSettings details .mx_AccessibleButton"); const addButton = await session.query(".mx_RoomSettingsDialog .mx_AliasSettings details .mx_AccessibleButton");

View file

@ -123,8 +123,15 @@ describe("SpaceStore", () => {
jest.runAllTimers(); jest.runAllTimers();
client.getVisibleRooms.mockReturnValue(rooms = []); client.getVisibleRooms.mockReturnValue(rooms = []);
getValue.mockImplementation(settingName => { getValue.mockImplementation(settingName => {
if (settingName === "feature_spaces") { switch (settingName) {
return true; 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;
} }
}); });
}); });

View file

@ -0,0 +1,291 @@
/*
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 { sortBy } from "lodash";
import { averageBetweenStrings, DEFAULT_ALPHABET } from "matrix-js-sdk/src/utils";
import { midPointsBetweenStrings, reorderLexicographically } from "../../src/utils/stringOrderField";
const moveLexicographicallyTest = (
orders: Array<string | undefined>,
fromIndex: number,
toIndex: number,
expectedChanges: number,
maxLength?: number,
): void => {
const ops = reorderLexicographically(orders, fromIndex, toIndex, maxLength);
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]);
expect(newOrders[toIndex][0]).toBe(fromIndex);
expect(ops).toHaveLength(expectedChanges);
};
describe("stringOrderField", () => {
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([]);
});
});
describe("reorderLexicographically", () => {
it("should work when moving left", () => {
moveLexicographicallyTest(["a", "c", "e", "g", "i"], 2, 1, 1);
});
it("should work when moving right", () => {
moveLexicographicallyTest(["a", "c", "e", "g", "i"], 1, 2, 1);
});
it("should work when all orders are undefined", () => {
moveLexicographicallyTest(
[undefined, undefined, undefined, undefined, undefined, undefined],
4,
1,
2,
);
});
it("should work when moving to end and all orders are undefined", () => {
moveLexicographicallyTest(
[undefined, undefined, undefined, undefined, undefined, undefined],
1,
4,
5,
);
});
it("should work when moving left and some orders are undefined", () => {
moveLexicographicallyTest(
["a", "c", "e", undefined, undefined, undefined],
5,
2,
1,
);
moveLexicographicallyTest(
["a", "a", "e", undefined, undefined, undefined],
5,
1,
2,
);
});
it("should work moving to the start when all is undefined", () => {
moveLexicographicallyTest(
[undefined, undefined, undefined, undefined],
2,
0,
1,
);
});
it("should work moving to the end when all is undefined", () => {
moveLexicographicallyTest(
[undefined, undefined, undefined, undefined],
1,
3,
4,
);
});
it("should work moving left when all is undefined", () => {
moveLexicographicallyTest(
[undefined, undefined, undefined, undefined, undefined, undefined],
4,
1,
2,
);
});
it("should work moving right when all is undefined", () => {
moveLexicographicallyTest(
[undefined, undefined, undefined, undefined],
1,
2,
3,
);
});
it("should work moving more right when all is undefined", () => {
moveLexicographicallyTest(
[undefined, undefined, undefined, undefined, undefined, /**/ undefined, undefined],
1,
4,
5,
);
});
it("should work moving left when right is undefined", () => {
moveLexicographicallyTest(
["20", undefined, undefined, undefined, undefined, undefined],
4,
2,
2,
);
});
it("should work moving right when right is undefined", () => {
moveLexicographicallyTest(
["50", undefined, undefined, undefined, undefined, /**/ undefined, undefined],
1,
4,
4,
);
});
it("should work moving left when right is defined", () => {
moveLexicographicallyTest(
["10", "20", "30", "40", undefined, undefined],
3,
1,
1,
);
});
it("should work moving right when right is defined", () => {
moveLexicographicallyTest(
["10", "20", "30", "40", "50", undefined],
1,
3,
1,
);
});
it("should work moving left when all is defined", () => {
moveLexicographicallyTest(
["11", "13", "15", "17", "19"],
2,
1,
1,
);
});
it("should work moving right when all is defined", () => {
moveLexicographicallyTest(
["11", "13", "15", "17", "19"],
1,
2,
1,
);
});
it("should work moving left into no left space", () => {
moveLexicographicallyTest(
["11", "12", "13", "14", "19"],
3,
1,
2,
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,
);
});
it("should work moving right into no right space", () => {
moveLexicographicallyTest(
["15", "16", "17", "18", "19"],
1,
3,
3,
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),
],
1,
3,
3,
1,
);
});
it("should work 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,
3,
3,
1,
);
});
it("should work 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,
);
});
});
});

229
yarn.lock
View file

@ -1017,13 +1017,20 @@
pirates "^4.0.0" pirates "^4.0.0"
source-map-support "^0.5.16" 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" version "7.12.5"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.12.5.tgz#410e7e487441e1b360c29be715d870d9b985882e" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.12.5.tgz#410e7e487441e1b360c29be715d870d9b985882e"
integrity sha512-plcc+hbExy3McchJCEQG3knOsuh3HH+Prx1P6cLIkET/0dLuQDEnrT+s27Axgc9bqfsmNUNHfscgMUdBpC9xfg== integrity sha512-plcc+hbExy3McchJCEQG3knOsuh3HH+Prx1P6cLIkET/0dLuQDEnrT+s27Axgc9bqfsmNUNHfscgMUdBpC9xfg==
dependencies: dependencies:
regenerator-runtime "^0.13.4" regenerator-runtime "^0.13.4"
"@babel/runtime@^7.12.1", "@babel/runtime@^7.9.2":
version "7.14.6"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.14.6.tgz#535203bc0892efc7dec60bdc27b2ecf6e409062d"
integrity sha512-/PCB2uJ7oM44tz8YhC4Z/6PeOKXp4K588f+5M3clr1M4zbqztlo0XEfJ2LEzj/FgwfgGcIdl8n7YYjTCI0BYwg==
dependencies:
regenerator-runtime "^0.13.4"
"@babel/template@^7.10.4", "@babel/template@^7.12.7", "@babel/template@^7.3.3": "@babel/template@^7.10.4", "@babel/template@^7.12.7", "@babel/template@^7.3.3":
version "7.12.7" version "7.12.7"
resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.12.7.tgz#c817233696018e39fbb6c491d2fb684e05ed43bc" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.12.7.tgz#c817233696018e39fbb6c491d2fb684e05ed43bc"
@ -1479,6 +1486,11 @@
resolved "https://registry.yarnpkg.com/@types/counterpart/-/counterpart-0.18.1.tgz#b1b784d9e54d9879f0a8cb12f2caedab65430fe8" resolved "https://registry.yarnpkg.com/@types/counterpart/-/counterpart-0.18.1.tgz#b1b784d9e54d9879f0a8cb12f2caedab65430fe8"
integrity sha512-PRuFlBBkvdDOtxlIASzTmkEFar+S66Ek48NVVTWMUjtJAdn5vyMSN8y6IZIoIymGpR36q2nZbIYazBWyFxL+IQ== 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": "@types/events@^3.0.0":
version "3.0.0" version "3.0.0"
resolved "https://registry.yarnpkg.com/@types/events/-/events-3.0.0.tgz#2862f3f58a9a7f7c3e78d79f130dd4d71c25c2a7" resolved "https://registry.yarnpkg.com/@types/events/-/events-3.0.0.tgz#2862f3f58a9a7f7c3e78d79f130dd4d71c25c2a7"
@ -1504,6 +1516,14 @@
dependencies: dependencies:
"@types/node" "*" "@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": "@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0", "@types/istanbul-lib-coverage@^2.0.1":
version "2.0.3" version "2.0.3"
resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.3.tgz#4ba8ddb720221f432e443bd5f9117fd22cfd4762" resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.3.tgz#4ba8ddb720221f432e443bd5f9117fd22cfd4762"
@ -1620,12 +1640,29 @@
dependencies: dependencies:
"@types/node" "*" "@types/node" "*"
"@types/react-dom@^16.9.10": "@types/react-beautiful-dnd@^13.0.0":
version "16.9.10" version "13.0.0"
resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-16.9.10.tgz#4485b0bec3d41f856181b717f45fd7831101156f" resolved "https://registry.yarnpkg.com/@types/react-beautiful-dnd/-/react-beautiful-dnd-13.0.0.tgz#e60d3d965312fcf1516894af92dc3e9249587db4"
integrity sha512-ItatOrnXDMAYpv6G8UCk2VhbYVTjZT9aorLtA/OzDN9XJ2GKcfam68jutoAcILdRjsRUO8qb7AmyObF77Q8QFw== integrity sha512-by80tJ8aTTDXT256Gl+RfLRtFjYbUWOnZuEigJgNsJrSEGxvFe5eY6k3g4VIvf0M/6+xoLgfYWoWonlOo6Wqdg==
dependencies: dependencies:
"@types/react" "^16" "@types/react" "*"
"@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" "*"
"@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": "@types/react-transition-group@^4.4.0":
version "4.4.0" version "4.4.0"
@ -1634,12 +1671,13 @@
dependencies: dependencies:
"@types/react" "*" "@types/react" "*"
"@types/react@*", "@types/react@^16", "@types/react@^16.14", "@types/react@^16.9": "@types/react@*", "@types/react@^17.0.2":
version "16.14.2" version "17.0.11"
resolved "https://registry.yarnpkg.com/@types/react/-/react-16.14.2.tgz#85dcc0947d0645349923c04ccef6018a1ab7538c" resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.11.tgz#67fcd0ddbf5a0b083a0f94e926c7d63f3b836451"
integrity sha512-BzzcAlyDxXl2nANlabtT4thtvbbnhee8hMmH/CcJrISDBVcJS1iOsP1f0OAgSdGE0MsY9tqcrb9YoZcOFv9dbQ== integrity sha512-yFRQbD+whVonItSk7ZzP/L+gPTJVBkL/7shLEF+i9GC/1cV3JmUxEQz6+9ylhUpWSDuqo1N9qEvqS6vTj4USUA==
dependencies: dependencies:
"@types/prop-types" "*" "@types/prop-types" "*"
"@types/scheduler" "*"
csstype "^3.0.2" csstype "^3.0.2"
"@types/sanitize-html@^2.3.1": "@types/sanitize-html@^2.3.1":
@ -1649,6 +1687,11 @@
dependencies: dependencies:
htmlparser2 "^6.0.0" 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": "@types/stack-utils@^1.0.1":
version "1.0.1" version "1.0.1"
resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-1.0.1.tgz#0a851d3bd96498fa25c33ab7278ed3bd65f06c3e" resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-1.0.1.tgz#0a851d3bd96498fa25c33ab7278ed3bd65f06c3e"
@ -2116,14 +2159,6 @@ babel-preset-jest@^26.6.2:
babel-plugin-jest-hoist "^26.6.2" babel-plugin-jest-hoist "^26.6.2"
babel-preset-current-node-syntax "^1.0.0" 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: bail@^1.0.0:
version "1.0.5" version "1.0.5"
resolved "https://registry.yarnpkg.com/bail/-/bail-1.0.5.tgz#b6fa133404a392cbc1f8c4bf63f5953351e7a776" resolved "https://registry.yarnpkg.com/bail/-/bail-1.0.5.tgz#b6fa133404a392cbc1f8c4bf63f5953351e7a776"
@ -2642,11 +2677,6 @@ core-js@^1.0.0:
resolved "https://registry.yarnpkg.com/core-js/-/core-js-1.2.7.tgz#652294c14651db28fa93bd2d5ff2983a4f08c636" resolved "https://registry.yarnpkg.com/core-js/-/core-js-1.2.7.tgz#652294c14651db28fa93bd2d5ff2983a4f08c636"
integrity sha1-ZSKUwUZR2yj6k70tX/KYOk8IxjY= 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: core-util-is@1.0.2, core-util-is@~1.0.0:
version "1.0.2" version "1.0.2"
resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7"
@ -2706,6 +2736,13 @@ cross-spawn@^7.0.0, cross-spawn@^7.0.2:
shebang-command "^2.0.0" shebang-command "^2.0.0"
which "^2.0.1" 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: css-select@^4.1.2:
version "4.1.2" version "4.1.2"
resolved "https://registry.yarnpkg.com/css-select/-/css-select-4.1.2.tgz#8b52b6714ed3a80d8221ec971c543f3b12653286" resolved "https://registry.yarnpkg.com/css-select/-/css-select-4.1.2.tgz#8b52b6714ed3a80d8221ec971c543f3b12653286"
@ -4235,7 +4272,7 @@ highlight.js@^10.5.0:
resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-10.5.0.tgz#3f09fede6a865757378f2d9ebdcbc15ba268f98f" resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-10.5.0.tgz#3f09fede6a865757378f2d9ebdcbc15ba268f98f"
integrity sha512-xTmvd9HiIHR6L53TMC7TKolEj65zG1XU+Onr8oi86mYa+nLcIbxTTWkpW7CsEwv/vK7u1zb8alZIMLDqqN6KTw== integrity sha512-xTmvd9HiIHR6L53TMC7TKolEj65zG1XU+Onr8oi86mYa+nLcIbxTTWkpW7CsEwv/vK7u1zb8alZIMLDqqN6KTw==
hoist-non-react-statics@^3.3.0: hoist-non-react-statics@^3.3.0, hoist-non-react-statics@^3.3.2:
version "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" resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45"
integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw== integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==
@ -4450,13 +4487,6 @@ internal-slot@^1.0.2:
has "^1.0.3" has "^1.0.3"
side-channel "^1.0.2" 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: ip-regex@^2.1.0:
version "2.1.0" version "2.1.0"
resolved "https://registry.yarnpkg.com/ip-regex/-/ip-regex-2.1.0.tgz#fa78bf5d2e6913c911ce9f819ee5146bb6d844e9" resolved "https://registry.yarnpkg.com/ip-regex/-/ip-regex-2.1.0.tgz#fa78bf5d2e6913c911ce9f819ee5146bb6d844e9"
@ -5594,11 +5624,6 @@ locate-path@^5.0.0:
dependencies: dependencies:
p-locate "^4.1.0" 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: lodash.escape@^4.0.1:
version "4.0.1" version "4.0.1"
resolved "https://registry.yarnpkg.com/lodash.escape/-/lodash.escape-4.0.1.tgz#c9044690c21e04294beaa517712fded1fa88de98" resolved "https://registry.yarnpkg.com/lodash.escape/-/lodash.escape-4.0.1.tgz#c9044690c21e04294beaa517712fded1fa88de98"
@ -5619,7 +5644,7 @@ lodash.sortby@^4.7.0:
resolved "https://registry.yarnpkg.com/lodash.sortby/-/lodash.sortby-4.7.0.tgz#edd14c824e2cc9c1e0b0a1b42bb5210516a42438" resolved "https://registry.yarnpkg.com/lodash.sortby/-/lodash.sortby-4.7.0.tgz#edd14c824e2cc9c1e0b0a1b42bb5210516a42438"
integrity sha1-7dFMgk4sycHgsKG0K7UhBRakJDg= 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" version "4.17.21"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
@ -5788,10 +5813,10 @@ mdurl@~1.0.1:
resolved "https://registry.yarnpkg.com/mdurl/-/mdurl-1.0.1.tgz#fe85b2ec75a59037f2adfec100fd6c601761152e" resolved "https://registry.yarnpkg.com/mdurl/-/mdurl-1.0.1.tgz#fe85b2ec75a59037f2adfec100fd6c601761152e"
integrity sha1-/oWy7HWlkDfyrf7BAP1sYBdhFS4= integrity sha1-/oWy7HWlkDfyrf7BAP1sYBdhFS4=
memoize-one@^3.0.1: memoize-one@^5.1.1:
version "3.1.1" version "5.2.1"
resolved "https://registry.yarnpkg.com/memoize-one/-/memoize-one-3.1.1.tgz#ef609811e3bc28970eac2884eece64d167830d17" resolved "https://registry.yarnpkg.com/memoize-one/-/memoize-one-5.2.1.tgz#8337aa3c4335581839ec01c3d594090cebe8f00e"
integrity sha512-YqVh744GsMlZu6xkhGslPSqSurOv6P+kLN2J3ysBZfagLcL5FdRK/0UpgLoL8hwjjEvvAVkjJZyFP+1T6p1vgA== integrity sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==
meow@^9.0.0: meow@^9.0.0:
version "9.0.0" version "9.0.0"
@ -6437,11 +6462,6 @@ path-type@^4.0.0:
resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b"
integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== 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: performance-now@^2.1.0:
version "2.1.0" version "2.1.0"
resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b"
@ -6651,7 +6671,7 @@ prompts@^2.0.1:
kleur "^3.0.3" kleur "^3.0.3"
sisteransi "^1.0.5" 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" version "15.7.2"
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5" resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5"
integrity sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ== integrity sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==
@ -6728,12 +6748,12 @@ quick-lru@^4.0.1:
resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-4.0.1.tgz#5b8878f113a58217848c6482026c73e1ba57727f" resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-4.0.1.tgz#5b8878f113a58217848c6482026c73e1ba57727f"
integrity sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g== integrity sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==
raf-schd@^2.1.0: raf-schd@^4.0.2:
version "2.1.2" version "4.0.3"
resolved "https://registry.yarnpkg.com/raf-schd/-/raf-schd-2.1.2.tgz#ec622b5167f2912089f054dc03ebd5bcf33c8f62" resolved "https://registry.yarnpkg.com/raf-schd/-/raf-schd-4.0.3.tgz#5d6c34ef46f8b2a0e880a8fcdb743efc5bfdbc1a"
integrity sha512-Orl0IEvMtUCgPddgSxtxreK77UiQz4nPYJy9RggVzu4mKsZkQWiAaG1y9HlYWdvm9xtN348xRaT37qkvL/+A+g== integrity sha512-tQkJl2GRWh83ui2DiPTJz9wEiMN20syf+5oKfB03yYP7ioZcJwsIK8FjrtLwH1m7C7e+Tt2yYBlrOpdT+dyeIQ==
raf@^3.1.0, raf@^3.4.1: raf@^3.4.1:
version "3.4.1" version "3.4.1"
resolved "https://registry.yarnpkg.com/raf/-/raf-3.4.1.tgz#0742e99a4a6552f445d73e3ee0328af0ff1ede39" resolved "https://registry.yarnpkg.com/raf/-/raf-3.4.1.tgz#0742e99a4a6552f445d73e3ee0328af0ff1ede39"
integrity sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA== integrity sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA==
@ -6760,21 +6780,18 @@ re-resizable@^6.9.0:
dependencies: dependencies:
fast-memoize "^2.5.1" fast-memoize "^2.5.1"
react-beautiful-dnd@^4.0.1: react-beautiful-dnd@^13.1.0:
version "4.0.1" version "13.1.0"
resolved "https://registry.yarnpkg.com/react-beautiful-dnd/-/react-beautiful-dnd-4.0.1.tgz#3b0a49bf6be75af351176c904f012611dd292b81" resolved "https://registry.yarnpkg.com/react-beautiful-dnd/-/react-beautiful-dnd-13.1.0.tgz#ec97c81093593526454b0de69852ae433783844d"
integrity sha512-d73RMu4QOFCyjUELLWFyY/EuclnfqulI9pECx+2gIuJvV0ycf1uR88o+1x0RSB9ILD70inHMzCBKNkWVbbt+vA== integrity sha512-aGvblPZTJowOWUNiwd6tNfEpgkX5OxmpqxHKNW/4VmvZTNTbeiq7bA3bn5T+QSF2uibXB0D1DmJsb1aC/+3cUA==
dependencies: dependencies:
babel-runtime "^6.26.0" "@babel/runtime" "^7.9.2"
invariant "^2.2.2" css-box-model "^1.2.0"
memoize-one "^3.0.1" memoize-one "^5.1.1"
prop-types "^15.6.0" raf-schd "^4.0.2"
raf-schd "^2.1.0" react-redux "^7.2.0"
react-motion "^0.5.2" redux "^4.0.4"
react-redux "^5.0.6" use-memo-one "^1.1.1"
redux "^3.7.2"
redux-thunk "^2.2.0"
reselect "^3.0.1"
react-clientside-effect@^1.2.2: react-clientside-effect@^1.2.2:
version "1.2.3" version "1.2.3"
@ -6809,7 +6826,7 @@ react-focus-lock@^2.5.0:
resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0" resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0"
integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w== integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==
react-is@^16.6.0, react-is@^16.7.0, react-is@^16.8.1: react-is@^16.13.1, react-is@^16.7.0, react-is@^16.8.1:
version "16.13.1" version "16.13.1"
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
@ -6819,32 +6836,17 @@ react-is@^17.0.1:
resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.1.tgz#5b3531bd76a645a4c9fb6e693ed36419e3301339" resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.1.tgz#5b3531bd76a645a4c9fb6e693ed36419e3301339"
integrity sha512-NAnt2iGDXohE5LI7uBnLnqvLQMtzhkiAOLXTmv+qnF9Ky7xAPcX8Up/xWIhxvLVGJvuLiNc4xQLtuqDRzb4fSA== integrity sha512-NAnt2iGDXohE5LI7uBnLnqvLQMtzhkiAOLXTmv+qnF9Ky7xAPcX8Up/xWIhxvLVGJvuLiNc4xQLtuqDRzb4fSA==
react-lifecycles-compat@^3.0.0: react-redux@^7.2.0:
version "3.0.4" version "7.2.4"
resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362" resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-7.2.4.tgz#1ebb474032b72d806de2e0519cd07761e222e225"
integrity sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA== integrity sha512-hOQ5eOSkEJEXdpIKbnRyl04LhaWabkDPV+Ix97wqQX3T3d2NQ8DUblNXXtNMavc7DpswyQM6xfaN4HQDKNY2JA==
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: dependencies:
performance-now "^0.2.0" "@babel/runtime" "^7.12.1"
prop-types "^15.5.8" "@types/react-redux" "^7.1.16"
raf "^3.1.0" hoist-non-react-statics "^3.3.2"
loose-envify "^1.4.0"
react-redux@^5.0.6: prop-types "^15.7.2"
version "5.1.2" react-is "^16.13.1"
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-shallow-renderer@^16.13.1: react-shallow-renderer@^16.13.1:
version "16.14.1" version "16.14.1"
@ -6973,20 +6975,12 @@ redent@^3.0.0:
indent-string "^4.0.0" indent-string "^4.0.0"
strip-indent "^3.0.0" strip-indent "^3.0.0"
redux-thunk@^2.2.0: redux@^4.0.0, redux@^4.0.4:
version "2.3.0" version "4.1.0"
resolved "https://registry.yarnpkg.com/redux-thunk/-/redux-thunk-2.3.0.tgz#51c2c19a185ed5187aaa9a2d08b666d0d6467622" resolved "https://registry.yarnpkg.com/redux/-/redux-4.1.0.tgz#eb049679f2f523c379f1aff345c8612f294c88d4"
integrity sha512-km6dclyFnmcvxhAcrQV2AkZmPQjzPDjgVlQtR0EQjxZPyJ0BnMf3in1ryuR8A2qU0HldVRfxYXbFSKlI3N7Slw== integrity sha512-uI2dQN43zqLWCt6B/BMGRMY6db7TTY4qeHHfGeKb3EOhmOKjU3KdWvNLJyqaHRksv/ErdNH7cFZWg9jXtewy4g==
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: dependencies:
lodash "^4.2.1" "@babel/runtime" "^7.9.2"
lodash-es "^4.2.1"
loose-envify "^1.1.0"
symbol-observable "^1.0.3"
regenerate-unicode-properties@^8.2.0: regenerate-unicode-properties@^8.2.0:
version "8.2.0" version "8.2.0"
@ -7000,11 +6994,6 @@ regenerate@^1.4.0:
resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.4.2.tgz#b9346d8827e8f5a32f7ba29637d398b69014848a" resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.4.2.tgz#b9346d8827e8f5a32f7ba29637d398b69014848a"
integrity sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A== 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: regenerator-runtime@^0.13.4:
version "0.13.7" version "0.13.7"
resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz#cac2dacc8a1ea675feaabaeb8ae833898ae46f55" resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz#cac2dacc8a1ea675feaabaeb8ae833898ae46f55"
@ -7162,11 +7151,6 @@ require-main-filename@^2.0.0:
resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-2.0.0.tgz#d0b329ecc7cc0f61649f62215be69af54aa8989b" resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-2.0.0.tgz#d0b329ecc7cc0f61649f62215be69af54aa8989b"
integrity sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg== 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: resize-observer-polyfill@^1.5.1:
version "1.5.1" version "1.5.1"
resolved "https://registry.yarnpkg.com/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz#0e9020dd3d21024458d4ebd27e23e40269810464" resolved "https://registry.yarnpkg.com/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz#0e9020dd3d21024458d4ebd27e23e40269810464"
@ -7889,11 +7873,6 @@ svg-tags@^1.0.0:
resolved "https://registry.yarnpkg.com/svg-tags/-/svg-tags-1.0.0.tgz#58f71cee3bd519b59d4b2a843b6c7de64ac04764" resolved "https://registry.yarnpkg.com/svg-tags/-/svg-tags-1.0.0.tgz#58f71cee3bd519b59d4b2a843b6c7de64ac04764"
integrity sha1-WPcc7jvVGbWdSyqEO2x95krAR2Q= 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: symbol-tree@^3.2.4:
version "3.2.4" version "3.2.4"
resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.4.tgz#430637d248ba77e078883951fb9aa0eed7c63fa2" resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.4.tgz#430637d248ba77e078883951fb9aa0eed7c63fa2"
@ -7956,6 +7935,11 @@ through@^2.3.6:
resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5"
integrity sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU= 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: tmatch@^2.0.1:
version "2.0.1" version "2.0.1"
resolved "https://registry.yarnpkg.com/tmatch/-/tmatch-2.0.1.tgz#0c56246f33f30da1b8d3d72895abaf16660f38cf" resolved "https://registry.yarnpkg.com/tmatch/-/tmatch-2.0.1.tgz#0c56246f33f30da1b8d3d72895abaf16660f38cf"
@ -8271,6 +8255,11 @@ use-callback-ref@^1.2.1:
resolved "https://registry.yarnpkg.com/use-callback-ref/-/use-callback-ref-1.2.5.tgz#6115ed242cfbaed5915499c0a9842ca2912f38a5" resolved "https://registry.yarnpkg.com/use-callback-ref/-/use-callback-ref-1.2.5.tgz#6115ed242cfbaed5915499c0a9842ca2912f38a5"
integrity sha512-gN3vgMISAgacF7sqsLPByqoePooY3n2emTH59Ur5d/M8eg4WTWu1xp8i8DHjohftIyEx0S08RiYxbffr4j8Peg== 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: use-sidecar@^1.0.1:
version "1.0.4" version "1.0.4"
resolved "https://registry.yarnpkg.com/use-sidecar/-/use-sidecar-1.0.4.tgz#38398c3723727f9f924bed2343dfa3db6aaaee46" resolved "https://registry.yarnpkg.com/use-sidecar/-/use-sidecar-1.0.4.tgz#38398c3723727f9f924bed2343dfa3db6aaaee46"