Merge branch 'develop' into t3chguy/fix/18071
This commit is contained in:
commit
39d9ec3ced
464 changed files with 10033 additions and 6253 deletions
|
@ -90,7 +90,7 @@ export default class AutoDiscoveryUtils {
|
|||
href="https://github.com/vector-im/element-web/blob/master/docs/config.md"
|
||||
target="_blank"
|
||||
rel="noreferrer noopener"
|
||||
>{sub}</a>;
|
||||
>{ sub }</a>;
|
||||
},
|
||||
},
|
||||
);
|
||||
|
@ -130,8 +130,8 @@ export default class AutoDiscoveryUtils {
|
|||
serverErrorIsFatal: isFatalError,
|
||||
serverDeadError: (
|
||||
<div>
|
||||
<strong>{title}</strong>
|
||||
<div>{body}</div>
|
||||
<strong>{ title }</strong>
|
||||
<div>{ body }</div>
|
||||
</div>
|
||||
),
|
||||
};
|
||||
|
|
|
@ -44,7 +44,7 @@ export function messageForResourceLimitError(
|
|||
|
||||
const linkSub = sub => {
|
||||
if (adminContact) {
|
||||
return <a href={adminContact} target="_blank" rel="noreferrer noopener">{sub}</a>;
|
||||
return <a href={adminContact} target="_blank" rel="noreferrer noopener">{ sub }</a>;
|
||||
} else {
|
||||
return sub;
|
||||
}
|
||||
|
@ -76,12 +76,12 @@ export function messageForSyncError(err: MatrixError | Error): ReactNode {
|
|||
},
|
||||
);
|
||||
return <div>
|
||||
<div>{limitError}</div>
|
||||
<div>{adminContact}</div>
|
||||
<div>{ limitError }</div>
|
||||
<div>{ adminContact }</div>
|
||||
</div>;
|
||||
} else {
|
||||
return <div>
|
||||
{_t("Unable to connect to Homeserver. Retrying...")}
|
||||
{ _t("Unable to connect to Homeserver. Retrying...") }
|
||||
</div>;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -111,14 +111,19 @@ export function getEventDisplayInfo(mxEvent: MatrixEvent): {
|
|||
let tileHandler = getHandlerTile(mxEvent);
|
||||
|
||||
// Info messages are basically information about commands processed on a room
|
||||
let isBubbleMessage = eventType.startsWith("m.key.verification") ||
|
||||
(eventType === EventType.RoomMessage && msgtype && msgtype.startsWith("m.key.verification")) ||
|
||||
(eventType === EventType.RoomCreate) ||
|
||||
(eventType === EventType.RoomEncryption) ||
|
||||
(tileHandler === "messages.MJitsiWidgetEvent");
|
||||
let isBubbleMessage = (
|
||||
eventType.startsWith("m.key.verification") ||
|
||||
(eventType === EventType.RoomMessage && msgtype && msgtype.startsWith("m.key.verification")) ||
|
||||
(eventType === EventType.RoomCreate) ||
|
||||
(eventType === EventType.RoomEncryption) ||
|
||||
(eventType === EventType.CallInvite) ||
|
||||
(tileHandler === "messages.MJitsiWidgetEvent")
|
||||
);
|
||||
let isInfoMessage = (
|
||||
!isBubbleMessage && eventType !== EventType.RoomMessage &&
|
||||
eventType !== EventType.Sticker && eventType !== EventType.RoomCreate
|
||||
!isBubbleMessage &&
|
||||
eventType !== EventType.RoomMessage &&
|
||||
eventType !== EventType.Sticker &&
|
||||
eventType !== EventType.RoomCreate
|
||||
);
|
||||
|
||||
// If we're showing hidden events in the timeline, we should use the
|
||||
|
|
54
src/utils/FileUtils.ts
Normal file
54
src/utils/FileUtils.ts
Normal file
|
@ -0,0 +1,54 @@
|
|||
/*
|
||||
Copyright 2015 - 2021 The Matrix.org Foundation C.I.C.
|
||||
Copyright 2021 Šimon Brandner <simon.bra.ag@gmail.com>
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import filesize from 'filesize';
|
||||
import { IMediaEventContent } from '../customisations/models/IMediaEventContent';
|
||||
import { _t } from '../languageHandler';
|
||||
|
||||
/**
|
||||
* Extracts a human readable label for the file attachment to use as
|
||||
* link text.
|
||||
*
|
||||
* @param {IMediaEventContent} content The "content" key of the matrix event.
|
||||
* @param {string} fallbackText The fallback text
|
||||
* @param {boolean} withSize Whether to include size information. Default true.
|
||||
* @return {string} the human readable link text for the attachment.
|
||||
*/
|
||||
export function presentableTextForFile(
|
||||
content: IMediaEventContent,
|
||||
fallbackText = _t("Attachment"),
|
||||
withSize = true,
|
||||
): string {
|
||||
let text = fallbackText;
|
||||
if (content.body && content.body.length > 0) {
|
||||
// The content body should be the name of the file including a
|
||||
// file extension.
|
||||
text = content.body;
|
||||
}
|
||||
|
||||
if (content.info && content.info.size && withSize) {
|
||||
// If we know the size of the file then add it as human readable
|
||||
// string to the end of the link text so that the user knows how
|
||||
// big a file they are downloading.
|
||||
// The content.info also contains a MIME-type but we don't display
|
||||
// it since it is "ugly", users generally aren't aware what it
|
||||
// means and the type of the attachment can usually be inferrered
|
||||
// from the file extension.
|
||||
text += ' (' + filesize(content.info.size) + ')';
|
||||
}
|
||||
return text;
|
||||
}
|
59
src/utils/LazyValue.ts
Normal file
59
src/utils/LazyValue.ts
Normal file
|
@ -0,0 +1,59 @@
|
|||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Utility class for lazily getting a variable.
|
||||
*/
|
||||
export class LazyValue<T> {
|
||||
private val: T;
|
||||
private prom: Promise<T>;
|
||||
private done = false;
|
||||
|
||||
public constructor(private getFn: () => Promise<T>) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether or not a cached value is present.
|
||||
*/
|
||||
public get present(): boolean {
|
||||
// we use a tracking variable just in case the final value is falsey
|
||||
return this.done;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the value without invoking a get. May be undefined until the
|
||||
* value is fetched properly.
|
||||
*/
|
||||
public get cachedValue(): T {
|
||||
return this.val;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a promise which resolves to the value, eventually.
|
||||
*/
|
||||
public get value(): Promise<T> {
|
||||
if (this.prom) return this.prom;
|
||||
this.prom = this.getFn();
|
||||
|
||||
// Fork the promise chain to avoid accidentally making it return undefined always.
|
||||
this.prom.then(v => {
|
||||
this.val = v;
|
||||
this.done = true;
|
||||
});
|
||||
|
||||
return this.prom;
|
||||
}
|
||||
}
|
120
src/utils/MediaEventHelper.ts
Normal file
120
src/utils/MediaEventHelper.ts
Normal file
|
@ -0,0 +1,120 @@
|
|||
/*
|
||||
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 { MatrixEvent } from "matrix-js-sdk/src";
|
||||
import { LazyValue } from "./LazyValue";
|
||||
import { Media, mediaFromContent } from "../customisations/Media";
|
||||
import { decryptFile } from "./DecryptFile";
|
||||
import { IMediaEventContent } from "../customisations/models/IMediaEventContent";
|
||||
import { IDestroyable } from "./IDestroyable";
|
||||
import { EventType, MsgType } from "matrix-js-sdk/src/@types/event";
|
||||
|
||||
// TODO: We should consider caching the blobs. https://github.com/vector-im/element-web/issues/17192
|
||||
|
||||
export class MediaEventHelper implements IDestroyable {
|
||||
// Either an HTTP or Object URL (when encrypted) to the media.
|
||||
public readonly sourceUrl: LazyValue<string>;
|
||||
public readonly thumbnailUrl: LazyValue<string>;
|
||||
|
||||
// Either the raw or decrypted (when encrypted) contents of the file.
|
||||
public readonly sourceBlob: LazyValue<Blob>;
|
||||
public readonly thumbnailBlob: LazyValue<Blob>;
|
||||
|
||||
public readonly media: Media;
|
||||
|
||||
public constructor(private event: MatrixEvent) {
|
||||
this.sourceUrl = new LazyValue(this.prepareSourceUrl);
|
||||
this.thumbnailUrl = new LazyValue(this.prepareThumbnailUrl);
|
||||
this.sourceBlob = new LazyValue(this.fetchSource);
|
||||
this.thumbnailBlob = new LazyValue(this.fetchThumbnail);
|
||||
|
||||
this.media = mediaFromContent(this.event.getContent());
|
||||
}
|
||||
|
||||
public get fileName(): string {
|
||||
return this.event.getContent<IMediaEventContent>().body || "download";
|
||||
}
|
||||
|
||||
public destroy() {
|
||||
if (this.media.isEncrypted) {
|
||||
if (this.sourceUrl.present) URL.revokeObjectURL(this.sourceUrl.cachedValue);
|
||||
if (this.thumbnailUrl.present) URL.revokeObjectURL(this.thumbnailUrl.cachedValue);
|
||||
}
|
||||
}
|
||||
|
||||
private prepareSourceUrl = async () => {
|
||||
if (this.media.isEncrypted) {
|
||||
const blob = await this.sourceBlob.value;
|
||||
return URL.createObjectURL(blob);
|
||||
} else {
|
||||
return this.media.srcHttp;
|
||||
}
|
||||
};
|
||||
|
||||
private prepareThumbnailUrl = async () => {
|
||||
if (this.media.isEncrypted) {
|
||||
const blob = await this.thumbnailBlob.value;
|
||||
if (blob === null) return null;
|
||||
return URL.createObjectURL(blob);
|
||||
} else {
|
||||
return this.media.thumbnailHttp;
|
||||
}
|
||||
};
|
||||
|
||||
private fetchSource = () => {
|
||||
if (this.media.isEncrypted) {
|
||||
return decryptFile(this.event.getContent<IMediaEventContent>().file);
|
||||
}
|
||||
return this.media.downloadSource().then(r => r.blob());
|
||||
};
|
||||
|
||||
private fetchThumbnail = () => {
|
||||
if (!this.media.hasThumbnail) return Promise.resolve(null);
|
||||
|
||||
if (this.media.isEncrypted) {
|
||||
const content = this.event.getContent<IMediaEventContent>();
|
||||
if (content.info?.thumbnail_file) {
|
||||
return decryptFile(content.info.thumbnail_file);
|
||||
} else {
|
||||
// "Should never happen"
|
||||
console.warn("Media claims to have thumbnail and is encrypted, but no thumbnail_file found");
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
}
|
||||
|
||||
return fetch(this.media.thumbnailHttp).then(r => r.blob());
|
||||
};
|
||||
|
||||
public static isEligible(event: MatrixEvent): boolean {
|
||||
if (!event) return false;
|
||||
if (event.isRedacted()) return false;
|
||||
if (event.getType() === EventType.Sticker) return true;
|
||||
if (event.getType() !== EventType.RoomMessage) return false;
|
||||
|
||||
const content = event.getContent();
|
||||
const mediaMsgTypes: string[] = [
|
||||
MsgType.Video,
|
||||
MsgType.Audio,
|
||||
MsgType.Image,
|
||||
MsgType.File,
|
||||
];
|
||||
if (mediaMsgTypes.includes(content.msgtype)) return true;
|
||||
if (typeof(content.url) === 'string') return true;
|
||||
|
||||
// Finally, it's probably not media
|
||||
return false;
|
||||
}
|
||||
}
|
|
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import zxcvbn from 'zxcvbn';
|
||||
import zxcvbn, { ZXCVBNFeedbackWarning } from 'zxcvbn';
|
||||
|
||||
import { MatrixClientPeg } from '../MatrixClientPeg';
|
||||
import { _t, _td } from '../languageHandler';
|
||||
|
@ -84,7 +84,7 @@ export function scorePassword(password: string) {
|
|||
}
|
||||
// and warning, if any
|
||||
if (zxcvbnResult.feedback.warning) {
|
||||
zxcvbnResult.feedback.warning = _t(zxcvbnResult.feedback.warning);
|
||||
zxcvbnResult.feedback.warning = _t(zxcvbnResult.feedback.warning) as ZXCVBNFeedbackWarning;
|
||||
}
|
||||
|
||||
return zxcvbnResult;
|
||||
|
|
93
src/utils/RoomUpgrade.ts
Normal file
93
src/utils/RoomUpgrade.ts
Normal file
|
@ -0,0 +1,93 @@
|
|||
/*
|
||||
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 { Room } from "matrix-js-sdk/src/models/room";
|
||||
import { EventType } from "matrix-js-sdk/src/@types/event";
|
||||
|
||||
import { inviteUsersToRoom } from "../RoomInvite";
|
||||
import Modal from "../Modal";
|
||||
import { _t } from "../languageHandler";
|
||||
import ErrorDialog from "../components/views/dialogs/ErrorDialog";
|
||||
import SpaceStore from "../stores/SpaceStore";
|
||||
|
||||
export async function upgradeRoom(
|
||||
room: Room,
|
||||
targetVersion: string,
|
||||
inviteUsers = false,
|
||||
handleError = true,
|
||||
updateSpaces = true,
|
||||
): Promise<string> {
|
||||
const cli = room.client;
|
||||
|
||||
let newRoomId: string;
|
||||
try {
|
||||
({ replacement_room: newRoomId } = await cli.upgradeRoom(room.roomId, targetVersion));
|
||||
} catch (e) {
|
||||
if (!handleError) throw e;
|
||||
console.error(e);
|
||||
|
||||
Modal.createTrackedDialog("Room Upgrade Error", "", ErrorDialog, {
|
||||
title: _t('Error upgrading room'),
|
||||
description: _t('Double check that your server supports the room version chosen and try again.'),
|
||||
});
|
||||
throw e;
|
||||
}
|
||||
|
||||
// We have to wait for the js-sdk to give us the room back so
|
||||
// we can more effectively abuse the MultiInviter behaviour
|
||||
// which heavily relies on the Room object being available.
|
||||
if (inviteUsers) {
|
||||
const checkForUpgradeFn = async (newRoom: Room): Promise<void> => {
|
||||
// The upgradePromise should be done by the time we await it here.
|
||||
if (newRoom.roomId !== newRoomId) return;
|
||||
|
||||
const toInvite = [
|
||||
...room.getMembersWithMembership("join"),
|
||||
...room.getMembersWithMembership("invite"),
|
||||
].map(m => m.userId).filter(m => m !== cli.getUserId());
|
||||
|
||||
if (toInvite.length > 0) {
|
||||
// Errors are handled internally to this function
|
||||
await inviteUsersToRoom(newRoomId, toInvite);
|
||||
}
|
||||
|
||||
cli.removeListener('Room', checkForUpgradeFn);
|
||||
};
|
||||
cli.on('Room', checkForUpgradeFn);
|
||||
}
|
||||
|
||||
if (updateSpaces) {
|
||||
const parents = SpaceStore.instance.getKnownParents(room.roomId);
|
||||
try {
|
||||
for (const parentId of parents) {
|
||||
const parent = cli.getRoom(parentId);
|
||||
if (!parent?.currentState.maySendStateEvent(EventType.SpaceChild, cli.getUserId())) continue;
|
||||
|
||||
const currentEv = parent.currentState.getStateEvents(EventType.SpaceChild, room.roomId);
|
||||
await cli.sendStateEvent(parentId, EventType.SpaceChild, {
|
||||
...(currentEv?.getContent() || {}), // copy existing attributes like suggested
|
||||
via: [cli.getDomain()],
|
||||
}, newRoomId);
|
||||
await cli.sendStateEvent(parentId, EventType.SpaceChild, {}, room.roomId);
|
||||
}
|
||||
} catch (e) {
|
||||
// These errors are not critical to the room upgrade itself
|
||||
console.warn("Failed to update parent spaces during room upgrade", e);
|
||||
}
|
||||
}
|
||||
|
||||
return newRoomId;
|
||||
}
|
|
@ -16,10 +16,9 @@ limitations under the License.
|
|||
|
||||
import React from "react";
|
||||
import { Room } from "matrix-js-sdk/src/models/room";
|
||||
import { MatrixClient } from "matrix-js-sdk/src/client";
|
||||
import { EventType } from "matrix-js-sdk/src/@types/event";
|
||||
|
||||
import { calculateRoomVia } from "../utils/permalinks/Permalinks";
|
||||
import { calculateRoomVia } from "./permalinks/Permalinks";
|
||||
import Modal from "../Modal";
|
||||
import SpaceSettingsDialog from "../components/views/dialogs/SpaceSettingsDialog";
|
||||
import AddExistingToSpaceDialog from "../components/views/dialogs/AddExistingToSpaceDialog";
|
||||
|
@ -34,8 +33,8 @@ import Spinner from "../components/views/elements/Spinner";
|
|||
import dis from "../dispatcher/dispatcher";
|
||||
import LeaveSpaceDialog from "../components/views/dialogs/LeaveSpaceDialog";
|
||||
|
||||
export const shouldShowSpaceSettings = (cli: MatrixClient, space: Room) => {
|
||||
const userId = cli.getUserId();
|
||||
export const shouldShowSpaceSettings = (space: Room) => {
|
||||
const userId = space.client.getUserId();
|
||||
return space.getMyMembership() === "join"
|
||||
&& (space.currentState.maySendStateEvent(EventType.RoomAvatar, userId)
|
||||
|| space.currentState.maySendStateEvent(EventType.RoomName, userId)
|
||||
|
@ -52,20 +51,20 @@ export const makeSpaceParentEvent = (room: Room, canonical = false) => ({
|
|||
state_key: room.roomId,
|
||||
});
|
||||
|
||||
export const showSpaceSettings = (cli: MatrixClient, space: Room) => {
|
||||
export const showSpaceSettings = (space: Room) => {
|
||||
Modal.createTrackedDialog("Space Settings", "", SpaceSettingsDialog, {
|
||||
matrixClient: cli,
|
||||
matrixClient: space.client,
|
||||
space,
|
||||
}, /*className=*/null, /*isPriority=*/false, /*isStatic=*/true);
|
||||
};
|
||||
|
||||
export const showAddExistingRooms = async (cli: MatrixClient, space: Room) => {
|
||||
export const showAddExistingRooms = async (space: Room) => {
|
||||
return Modal.createTrackedDialog(
|
||||
"Space Landing",
|
||||
"Add Existing",
|
||||
AddExistingToSpaceDialog,
|
||||
{
|
||||
matrixClient: cli,
|
||||
matrixClient: space.client,
|
||||
onCreateRoomClick: showCreateNewRoom,
|
||||
space,
|
||||
},
|
||||
|
@ -73,7 +72,7 @@ export const showAddExistingRooms = async (cli: MatrixClient, space: Room) => {
|
|||
).finished;
|
||||
};
|
||||
|
||||
export const showCreateNewRoom = async (cli: MatrixClient, space: Room) => {
|
||||
export const showCreateNewRoom = async (space: Room) => {
|
||||
const modal = Modal.createTrackedDialog<[boolean, IOpts]>(
|
||||
"Space Landing",
|
||||
"Create Room",
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue