Merge branch 'develop' into bwindels/make-reply-not-overlay
This commit is contained in:
commit
5d98805d77
278 changed files with 6448 additions and 3045 deletions
|
@ -80,7 +80,7 @@ export default class AutoDiscoveryUtils {
|
|||
{
|
||||
a: (sub) => {
|
||||
return <a
|
||||
href="https://github.com/vector-im/riot-web/blob/master/docs/config.md"
|
||||
href="https://github.com/vector-im/element-web/blob/master/docs/config.md"
|
||||
target="_blank"
|
||||
rel="noreferrer noopener"
|
||||
>{sub}</a>;
|
||||
|
@ -203,7 +203,7 @@ export default class AutoDiscoveryUtils {
|
|||
// Note: In the cases where we rely on the default IS from the config (namely
|
||||
// lack of identity server provided by the discovery method), we intentionally do not
|
||||
// validate it. This has already been validated and this helps some off-the-grid usage
|
||||
// of Riot.
|
||||
// of Element.
|
||||
let preferredIdentityUrl = defaultConfig && defaultConfig['isUrl'];
|
||||
if (isResult && isResult.state === AutoDiscovery.SUCCESS) {
|
||||
preferredIdentityUrl = isResult["base_url"];
|
||||
|
|
|
@ -28,7 +28,7 @@ import {MatrixClientPeg} from '../MatrixClientPeg';
|
|||
// called createObjectURL(), and so if the content contains any scripting then it
|
||||
// will pose a XSS vulnerability when the browser renders it. This is particularly
|
||||
// bad if the user right-clicks the URI and pastes it into a new window or tab,
|
||||
// as the blob will then execute with access to Riot's full JS environment(!)
|
||||
// as the blob will then execute with access to Element's full JS environment(!)
|
||||
//
|
||||
// See https://github.com/matrix-org/matrix-react-sdk/pull/1820#issuecomment-385210647
|
||||
// for details.
|
||||
|
|
86
src/utils/Whenable.ts
Normal file
86
src/utils/Whenable.ts
Normal file
|
@ -0,0 +1,86 @@
|
|||
/*
|
||||
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 { IDestroyable } from "./IDestroyable";
|
||||
import { arrayFastClone } from "./arrays";
|
||||
|
||||
export type WhenFn<T> = (w: Whenable<T>) => void;
|
||||
|
||||
/**
|
||||
* Whenables are a cheap way to have Observable patterns mixed with typical
|
||||
* usage of Promises, without having to tear down listeners or calls. Whenables
|
||||
* are intended to be used when a condition will be met multiple times and
|
||||
* the consumer needs to know *when* that happens.
|
||||
*/
|
||||
export abstract class Whenable<T> implements IDestroyable {
|
||||
private listeners: {condition: T | null, fn: WhenFn<T>}[] = [];
|
||||
|
||||
/**
|
||||
* Sets up a call to `fn` *when* the `condition` is met.
|
||||
* @param condition The condition to match.
|
||||
* @param fn The function to call.
|
||||
* @returns This.
|
||||
*/
|
||||
public when(condition: T, fn: WhenFn<T>): Whenable<T> {
|
||||
this.listeners.push({condition, fn});
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up a call to `fn` *when* any of the `conditions` are met.
|
||||
* @param conditions The conditions to match.
|
||||
* @param fn The function to call.
|
||||
* @returns This.
|
||||
*/
|
||||
public whenAnyOf(conditions: T[], fn: WhenFn<T>): Whenable<T> {
|
||||
for (const condition of conditions) {
|
||||
this.when(condition, fn);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up a call to `fn` *when* any condition is met.
|
||||
* @param fn The function to call.
|
||||
* @returns This.
|
||||
*/
|
||||
public whenAnything(fn: WhenFn<T>): Whenable<T> {
|
||||
this.listeners.push({condition: null, fn});
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Notifies all the listeners of a given condition.
|
||||
* @param condition The new condition that has been met.
|
||||
*/
|
||||
protected notifyCondition(condition: T) {
|
||||
const listeners = arrayFastClone(this.listeners); // clone just in case the handler modifies us
|
||||
for (const listener of listeners) {
|
||||
if (listener.condition === null || listener.condition === condition) {
|
||||
try {
|
||||
listener.fn(this);
|
||||
} catch (e) {
|
||||
console.error(`Error calling whenable listener for ${condition}:`, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public destroy() {
|
||||
this.listeners = [];
|
||||
}
|
||||
}
|
|
@ -69,7 +69,7 @@ export default class WidgetUtils {
|
|||
return false;
|
||||
}
|
||||
|
||||
// TODO: Enable support for m.widget event type (https://github.com/vector-im/riot-web/issues/13111)
|
||||
// TODO: Enable support for m.widget event type (https://github.com/vector-im/element-web/issues/13111)
|
||||
return room.currentState.maySendStateEvent('im.vector.modular.widgets', me);
|
||||
}
|
||||
|
||||
|
@ -185,7 +185,7 @@ export default class WidgetUtils {
|
|||
}
|
||||
|
||||
const room = MatrixClientPeg.get().getRoom(roomId);
|
||||
// TODO: Enable support for m.widget event type (https://github.com/vector-im/riot-web/issues/13111)
|
||||
// TODO: Enable support for m.widget event type (https://github.com/vector-im/element-web/issues/13111)
|
||||
const startingWidgetEvents = room.currentState.getStateEvents('im.vector.modular.widgets');
|
||||
if (eventsInIntendedState(startingWidgetEvents)) {
|
||||
resolve();
|
||||
|
@ -195,7 +195,7 @@ export default class WidgetUtils {
|
|||
function onRoomStateEvents(ev) {
|
||||
if (ev.getRoomId() !== roomId) return;
|
||||
|
||||
// TODO: Enable support for m.widget event type (https://github.com/vector-im/riot-web/issues/13111)
|
||||
// TODO: Enable support for m.widget event type (https://github.com/vector-im/element-web/issues/13111)
|
||||
const currentWidgetEvents = room.currentState.getStateEvents('im.vector.modular.widgets');
|
||||
|
||||
if (eventsInIntendedState(currentWidgetEvents)) {
|
||||
|
@ -263,8 +263,8 @@ export default class WidgetUtils {
|
|||
|
||||
if (addingWidget) {
|
||||
content = {
|
||||
// TODO: Enable support for m.widget event type (https://github.com/vector-im/riot-web/issues/13111)
|
||||
// For now we'll send the legacy event type for compatibility with older apps/riots
|
||||
// TODO: Enable support for m.widget event type (https://github.com/vector-im/element-web/issues/13111)
|
||||
// For now we'll send the legacy event type for compatibility with older apps/elements
|
||||
type: widgetType.legacy,
|
||||
url: widgetUrl,
|
||||
name: widgetName,
|
||||
|
@ -277,7 +277,7 @@ export default class WidgetUtils {
|
|||
WidgetEchoStore.setRoomWidgetEcho(roomId, widgetId, content);
|
||||
|
||||
const client = MatrixClientPeg.get();
|
||||
// TODO: Enable support for m.widget event type (https://github.com/vector-im/riot-web/issues/13111)
|
||||
// TODO: Enable support for m.widget event type (https://github.com/vector-im/element-web/issues/13111)
|
||||
return client.sendStateEvent(roomId, "im.vector.modular.widgets", content, widgetId).then(() => {
|
||||
return WidgetUtils.waitForRoomWidget(widgetId, roomId, addingWidget);
|
||||
}).finally(() => {
|
||||
|
@ -291,7 +291,7 @@ export default class WidgetUtils {
|
|||
* @return {[object]} Array containing current / active room widgets
|
||||
*/
|
||||
static getRoomWidgets(room: Room) {
|
||||
// TODO: Enable support for m.widget event type (https://github.com/vector-im/riot-web/issues/13111)
|
||||
// TODO: Enable support for m.widget event type (https://github.com/vector-im/element-web/issues/13111)
|
||||
const appsStateEvents = room.currentState.getStateEvents('im.vector.modular.widgets');
|
||||
if (!appsStateEvents) {
|
||||
return [];
|
||||
|
@ -466,7 +466,7 @@ export default class WidgetUtils {
|
|||
// safe to send.
|
||||
// We'll end up using a local render URL when we see a Jitsi widget anyways, so this is
|
||||
// really just for backwards compatibility and to appease the spec.
|
||||
baseUrl = "https://riot.im/app/";
|
||||
baseUrl = "https://app.element.io/";
|
||||
}
|
||||
const url = new URL("jitsi.html#" + queryString, baseUrl); // this strips hash fragment from baseUrl
|
||||
return url.href;
|
||||
|
|
46
src/utils/maps.ts
Normal file
46
src/utils/maps.ts
Normal file
|
@ -0,0 +1,46 @@
|
|||
/*
|
||||
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 { arrayDiff, arrayMerge, arrayUnion } from "./arrays";
|
||||
|
||||
/**
|
||||
* Determines the keys added, changed, and removed between two Maps.
|
||||
* For changes, simple triple equal comparisons are done, not in-depth tree checking.
|
||||
* @param a The first Map. Must be defined.
|
||||
* @param b The second Map. Must be defined.
|
||||
* @returns The difference between the keys of each Map.
|
||||
*/
|
||||
export function mapDiff<K, V>(a: Map<K, V>, b: Map<K, V>): { changed: K[], added: K[], removed: K[] } {
|
||||
const aKeys = [...a.keys()];
|
||||
const bKeys = [...b.keys()];
|
||||
const keyDiff = arrayDiff(aKeys, bKeys);
|
||||
const possibleChanges = arrayUnion(aKeys, bKeys);
|
||||
const changes = possibleChanges.filter(k => a.get(k) !== b.get(k));
|
||||
|
||||
return {changed: changes, added: keyDiff.added, removed: keyDiff.removed};
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all the key changes (added, removed, or value difference) between two Maps.
|
||||
* Triple equals is used to compare values, not in-depth tree checking.
|
||||
* @param a The first Map. Must be defined.
|
||||
* @param b The second Map. Must be defined.
|
||||
* @returns The keys which have been added, removed, or changed between the two Maps.
|
||||
*/
|
||||
export function mapKeyChanges<K, V>(a: Map<K, V>, b: Map<K, V>): K[] {
|
||||
const diff = mapDiff(a, b);
|
||||
return arrayMerge(diff.removed, diff.added, diff.changed);
|
||||
}
|
|
@ -63,7 +63,7 @@ export function getEffectiveMembership(membership: string): EffectiveMembership
|
|||
if (membership === 'invite') {
|
||||
return EffectiveMembership.Invite;
|
||||
} else if (membership === 'join') {
|
||||
// TODO: Include knocks? Update docs as needed in the enum. https://github.com/vector-im/riot-web/issues/14237
|
||||
// TODO: Include knocks? Update docs as needed in the enum. https://github.com/vector-im/element-web/issues/14237
|
||||
return EffectiveMembership.Join;
|
||||
} else {
|
||||
// Probably a leave, kick, or ban
|
||||
|
|
|
@ -16,15 +16,17 @@ limitations under the License.
|
|||
|
||||
import { arrayDiff, arrayHasDiff, arrayMerge, arrayUnion } from "./arrays";
|
||||
|
||||
type ObjectExcluding<O extends {}, P extends (keyof O)[]> = {[k in Exclude<keyof O, P[number]>]: O[k]};
|
||||
|
||||
/**
|
||||
* Gets a new object which represents the provided object, excluding some properties.
|
||||
* @param a The object to strip properties of. Must be defined.
|
||||
* @param props The property names to remove.
|
||||
* @returns The new object without the provided properties.
|
||||
*/
|
||||
export function objectExcluding(a: any, props: string[]): any {
|
||||
export function objectExcluding<O extends {}, P extends Array<keyof O>>(a: O, props: P): ObjectExcluding<O, P> {
|
||||
// We use a Map to avoid hammering the `delete` keyword, which is slow and painful.
|
||||
const tempMap = new Map<string, any>(Object.entries(a));
|
||||
const tempMap = new Map<keyof O, any>(Object.entries(a) as [keyof O, any][]);
|
||||
for (const prop of props) {
|
||||
tempMap.delete(prop);
|
||||
}
|
||||
|
@ -33,7 +35,7 @@ export function objectExcluding(a: any, props: string[]): any {
|
|||
return Array.from(tempMap.entries()).reduce((c, [k, v]) => {
|
||||
c[k] = v;
|
||||
return c;
|
||||
}, {});
|
||||
}, {} as O);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -43,13 +45,13 @@ export function objectExcluding(a: any, props: string[]): any {
|
|||
* @param props The property names to keep.
|
||||
* @returns The new object with only the provided properties.
|
||||
*/
|
||||
export function objectWithOnly(a: any, props: string[]): any {
|
||||
const existingProps = Object.keys(a);
|
||||
export function objectWithOnly<O extends {}, P extends Array<keyof O>>(a: O, props: P): {[k in P[number]]: O[k]} {
|
||||
const existingProps = Object.keys(a) as (keyof O)[];
|
||||
const diff = arrayDiff(existingProps, props);
|
||||
if (diff.removed.length === 0) {
|
||||
return objectShallowClone(a);
|
||||
} else {
|
||||
return objectExcluding(a, diff.removed);
|
||||
return objectExcluding(a, diff.removed) as {[k in P[number]]: O[k]};
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -64,9 +66,9 @@ export function objectWithOnly(a: any, props: string[]): any {
|
|||
* First argument is the property key with the second being the current value.
|
||||
* @returns A cloned object.
|
||||
*/
|
||||
export function objectShallowClone(a: any, propertyCloner?: (k: string, v: any) => any): any {
|
||||
const newObj = {};
|
||||
for (const [k, v] of Object.entries(a)) {
|
||||
export function objectShallowClone<O extends {}>(a: O, propertyCloner?: (k: keyof O, v: O[keyof O]) => any): O {
|
||||
const newObj = {} as O;
|
||||
for (const [k, v] of Object.entries(a) as [keyof O, O[keyof O]][]) {
|
||||
newObj[k] = v;
|
||||
if (propertyCloner) {
|
||||
newObj[k] = propertyCloner(k, v);
|
||||
|
@ -83,7 +85,7 @@ export function objectShallowClone(a: any, propertyCloner?: (k: string, v: any)
|
|||
* @param b The second object. Must be defined.
|
||||
* @returns True if there's a difference between the objects, false otherwise
|
||||
*/
|
||||
export function objectHasDiff(a: any, b: any): boolean {
|
||||
export function objectHasDiff<O extends {}>(a: O, b: O): boolean {
|
||||
const aKeys = Object.keys(a);
|
||||
const bKeys = Object.keys(b);
|
||||
if (arrayHasDiff(aKeys, bKeys)) return true;
|
||||
|
@ -92,6 +94,8 @@ export function objectHasDiff(a: any, b: any): boolean {
|
|||
return possibleChanges.some(k => a[k] !== b[k]);
|
||||
}
|
||||
|
||||
type Diff<K> = { changed: K[], added: K[], removed: K[] };
|
||||
|
||||
/**
|
||||
* Determines the keys added, changed, and removed between two objects.
|
||||
* For changes, simple triple equal comparisons are done, not in-depth
|
||||
|
@ -100,9 +104,9 @@ export function objectHasDiff(a: any, b: any): boolean {
|
|||
* @param b The second object. Must be defined.
|
||||
* @returns The difference between the keys of each object.
|
||||
*/
|
||||
export function objectDiff(a: any, b: any): { changed: string[], added: string[], removed: string[] } {
|
||||
const aKeys = Object.keys(a);
|
||||
const bKeys = Object.keys(b);
|
||||
export function objectDiff<O extends {}>(a: O, b: O): Diff<keyof O> {
|
||||
const aKeys = Object.keys(a) as (keyof O)[];
|
||||
const bKeys = Object.keys(b) as (keyof O)[];
|
||||
const keyDiff = arrayDiff(aKeys, bKeys);
|
||||
const possibleChanges = arrayUnion(aKeys, bKeys);
|
||||
const changes = possibleChanges.filter(k => a[k] !== b[k]);
|
||||
|
@ -119,7 +123,7 @@ export function objectDiff(a: any, b: any): { changed: string[], added: string[]
|
|||
* @returns The keys which have been added, removed, or changed between the
|
||||
* two objects.
|
||||
*/
|
||||
export function objectKeyChanges(a: any, b: any): string[] {
|
||||
export function objectKeyChanges<O extends {}>(a: O, b: O): (keyof O)[] {
|
||||
const diff = objectDiff(a, b);
|
||||
return arrayMerge(diff.removed, diff.added, diff.changed);
|
||||
}
|
||||
|
@ -131,6 +135,6 @@ export function objectKeyChanges(a: any, b: any): string[] {
|
|||
* @param obj The object to clone.
|
||||
* @returns The cloned object
|
||||
*/
|
||||
export function objectClone(obj: any): any {
|
||||
export function objectClone<O extends {}>(obj: O): O {
|
||||
return JSON.parse(JSON.stringify(obj));
|
||||
}
|
||||
|
|
|
@ -139,7 +139,7 @@ export function pillifyLinks(nodes, mxEvent, pills) {
|
|||
* It's critical to call this after pillifyLinks, otherwise
|
||||
* Pills will leak, leaking entire DOM trees via the event
|
||||
* emitter on BaseAvatar as per
|
||||
* https://github.com/vector-im/riot-web/issues/12417
|
||||
* https://github.com/vector-im/element-web/issues/12417
|
||||
*
|
||||
* @param {Node[]} pills - array of pill containers whose React
|
||||
* components should be unmounted.
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue