) }
{ otherVariables.map((item, index) =>
diff --git a/src/AsyncWrapper.tsx b/src/AsyncWrapper.tsx
index 3bbef71093..ef8924add8 100644
--- a/src/AsyncWrapper.tsx
+++ b/src/AsyncWrapper.tsx
@@ -77,6 +77,7 @@ export default class AsyncWrapper extends React.Component {
const Component = this.state.component;
return ;
} else if (this.state.error) {
+ // FIXME: Using an import will result in test failures
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
return
diff --git a/src/Avatar.ts b/src/Avatar.ts
index 4c4bd1c265..c0ecb19eaf 100644
--- a/src/Avatar.ts
+++ b/src/Avatar.ts
@@ -18,10 +18,11 @@ import { RoomMember } from "matrix-js-sdk/src/models/room-member";
import { User } from "matrix-js-sdk/src/models/user";
import { Room } from "matrix-js-sdk/src/models/room";
import { ResizeMethod } from "matrix-js-sdk/src/@types/partials";
+import { split } from "lodash";
import DMRoomMap from './utils/DMRoomMap';
import { mediaFromMxc } from "./customisations/Media";
-import SettingsStore from "./settings/SettingsStore";
+import SpaceStore from "./stores/SpaceStore";
// Not to be used for BaseAvatar urls as that has similar default avatar fallback already
export function avatarUrlForMember(
@@ -122,27 +123,13 @@ export function getInitialLetter(name: string): string {
return undefined;
}
- let idx = 0;
const initial = name[0];
if ((initial === '@' || initial === '#' || initial === '+') && name[1]) {
- idx++;
+ name = name.substring(1);
}
- // string.codePointAt(0) would do this, but that isn't supported by
- // some browsers (notably PhantomJS).
- let chars = 1;
- const first = name.charCodeAt(idx);
-
- // check if it’s the start of a surrogate pair
- if (first >= 0xD800 && first <= 0xDBFF && name[idx+1]) {
- const second = name.charCodeAt(idx+1);
- if (second >= 0xDC00 && second <= 0xDFFF) {
- chars++;
- }
- }
-
- const firstChar = name.substring(idx, idx+chars);
- return firstChar.toUpperCase();
+ // rely on the grapheme cluster splitter in lodash so that we don't break apart compound emojis
+ return split(name, "", 1)[0].toUpperCase();
}
export function avatarUrlForRoom(room: Room, width: number, height: number, resizeMethod?: ResizeMethod) {
@@ -153,7 +140,7 @@ export function avatarUrlForRoom(room: Room, width: number, height: number, resi
}
// space rooms cannot be DMs so skip the rest
- if (SettingsStore.getValue("feature_spaces") && room.isSpaceRoom()) return null;
+ if (SpaceStore.spacesEnabled && room.isSpaceRoom()) return null;
let otherMember = null;
const otherUserId = DMRoomMap.shared().getUserIdForRoomId(room.roomId);
diff --git a/src/BlurhashEncoder.ts b/src/BlurhashEncoder.ts
new file mode 100644
index 0000000000..2aee370fe9
--- /dev/null
+++ b/src/BlurhashEncoder.ts
@@ -0,0 +1,60 @@
+/*
+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 { defer, IDeferred } from "matrix-js-sdk/src/utils";
+
+// @ts-ignore - `.ts` is needed here to make TS happy
+import BlurhashWorker from "./workers/blurhash.worker.ts";
+
+interface IBlurhashWorkerResponse {
+ seq: number;
+ blurhash: string;
+}
+
+export class BlurhashEncoder {
+ private static internalInstance = new BlurhashEncoder();
+
+ public static get instance(): BlurhashEncoder {
+ return BlurhashEncoder.internalInstance;
+ }
+
+ private readonly worker: Worker;
+ private seq = 0;
+ private pendingDeferredMap = new Map>();
+
+ constructor() {
+ this.worker = new BlurhashWorker();
+ this.worker.onmessage = this.onMessage;
+ }
+
+ private onMessage = (ev: MessageEvent) => {
+ const { seq, blurhash } = ev.data;
+ const deferred = this.pendingDeferredMap.get(seq);
+ if (deferred) {
+ this.pendingDeferredMap.delete(seq);
+ deferred.resolve(blurhash);
+ }
+ };
+
+ public getBlurhash(imageData: ImageData): Promise {
+ const seq = this.seq++;
+ const deferred = defer();
+ this.pendingDeferredMap.set(seq, deferred);
+ this.worker.postMessage({ seq, imageData });
+ return deferred.promise;
+ }
+}
+
diff --git a/src/CallHandler.tsx b/src/CallHandler.tsx
index cb54db3f8a..e7ba1aa9fb 100644
--- a/src/CallHandler.tsx
+++ b/src/CallHandler.tsx
@@ -99,7 +99,7 @@ const CHECK_PROTOCOLS_ATTEMPTS = 3;
// (and store the ID of their native room)
export const VIRTUAL_ROOM_EVENT_TYPE = 'im.vector.is_virtual_room';
-export enum AudioID {
+enum AudioID {
Ring = 'ringAudio',
Ringback = 'ringbackAudio',
CallEnd = 'callendAudio',
@@ -124,9 +124,9 @@ interface ThirdpartyLookupResponseFields {
}
interface ThirdpartyLookupResponse {
- userid: string,
- protocol: string,
- fields: ThirdpartyLookupResponseFields,
+ userid: string;
+ protocol: string;
+ fields: ThirdpartyLookupResponseFields;
}
// Unlike 'CallType' in js-sdk, this one includes screen sharing
@@ -142,6 +142,7 @@ export enum PlaceCallType {
export enum CallHandlerEvent {
CallsChanged = "calls_changed",
CallChangeRoom = "call_change_room",
+ SilencedCallsChanged = "silenced_calls_changed",
}
export default class CallHandler extends EventEmitter {
@@ -154,7 +155,7 @@ export default class CallHandler extends EventEmitter {
private supportsPstnProtocol = null;
private pstnSupportPrefixed = null; // True if the server only support the prefixed pstn protocol
private supportsSipNativeVirtual = null; // im.vector.protocol.sip_virtual and im.vector.protocol.sip_native
- private pstnSupportCheckTimer: NodeJS.Timeout; // number actually because we're in the browser
+ private pstnSupportCheckTimer: number;
// For rooms we've been invited to, true if they're from virtual user, false if we've checked and they aren't.
private invitedRoomsAreVirtual = new Map();
private invitedRoomCheckInProgress = false;
@@ -164,6 +165,8 @@ export default class CallHandler extends EventEmitter {
// do the async lookup when we get new information and then store these mappings here
private assertedIdentityNativeUsers = new Map();
+ private silencedCalls = new Set(); // callIds
+
static sharedInstance() {
if (!window.mxCallHandler) {
window.mxCallHandler = new CallHandler();
@@ -224,6 +227,33 @@ export default class CallHandler extends EventEmitter {
}
}
+ public silenceCall(callId: string) {
+ this.silencedCalls.add(callId);
+ this.emit(CallHandlerEvent.SilencedCallsChanged, this.silencedCalls);
+
+ // Don't pause audio if we have calls which are still ringing
+ if (this.areAnyCallsUnsilenced()) return;
+ this.pause(AudioID.Ring);
+ }
+
+ public unSilenceCall(callId: string) {
+ this.silencedCalls.delete(callId);
+ this.emit(CallHandlerEvent.SilencedCallsChanged, this.silencedCalls);
+ this.play(AudioID.Ring);
+ }
+
+ public isCallSilenced(callId: string): boolean {
+ return this.silencedCalls.has(callId);
+ }
+
+ /**
+ * Returns true if there is at least one unsilenced call
+ * @returns {boolean}
+ */
+ private areAnyCallsUnsilenced(): boolean {
+ return this.calls.size > this.silencedCalls.size;
+ }
+
private async checkProtocols(maxTries) {
try {
const protocols = await MatrixClientPeg.get().getThirdpartyProtocols();
@@ -301,6 +331,13 @@ export default class CallHandler extends EventEmitter {
}, true);
};
+ public getCallById(callId: string): MatrixCall {
+ for (const call of this.calls.values()) {
+ if (call.callId === callId) return call;
+ }
+ return null;
+ }
+
getCallForRoom(roomId: string): MatrixCall {
return this.calls.get(roomId) || null;
}
@@ -394,7 +431,7 @@ export default class CallHandler extends EventEmitter {
}
private setCallListeners(call: MatrixCall) {
- let mappedRoomId = CallHandler.sharedInstance().roomIdForCall(call);
+ let mappedRoomId = this.roomIdForCall(call);
call.on(CallEvent.Error, (err: CallError) => {
if (!this.matchesCallForThisRoom(call)) return;
@@ -441,6 +478,10 @@ export default class CallHandler extends EventEmitter {
break;
}
+ if (newState !== CallState.Ringing) {
+ this.silencedCalls.delete(call.callId);
+ }
+
switch (newState) {
case CallState.Ringing:
this.play(AudioID.Ring);
@@ -615,23 +656,23 @@ export default class CallHandler extends EventEmitter {
private showICEFallbackPrompt() {
const cli = MatrixClientPeg.get();
- const code = sub => {sub};
+ const code = sub => { sub };
Modal.createTrackedDialog('No TURN servers', '', QuestionDialog, {
title: _t("Call failed due to misconfigured server"),
description:
-
{_t(
+
{ _t(
"Please ask the administrator of your homeserver " +
"(%(homeserverDomain)s) to configure a TURN server in " +
"order for calls to work reliably.",
{ homeserverDomain: cli.getDomain() }, { code },
- )}
-
{_t(
+ ) }
+
{ _t(
"Alternatively, you can try to use the public server at " +
"turn.matrix.org, but this will not be as reliable, and " +
"it will share your IP address with that server. You can also manage " +
"this in Settings.",
null, { code },
- )}
+ ) }
,
button: _t('Try using turn.matrix.org'),
cancelButton: _t('OK'),
@@ -649,19 +690,19 @@ export default class CallHandler extends EventEmitter {
if (call.type === CallType.Voice) {
title = _t("Unable to access microphone");
description =
- {_t(
+ { _t(
"Call failed because microphone could not be accessed. " +
"Check that a microphone is plugged in and set up correctly.",
- )}
+ ) }
;
} else if (call.type === CallType.Video) {
title = _t("Unable to access webcam / microphone");
description =
- {_t("Call failed because webcam or microphone could not be accessed. Check that:")}
+ { _t("Call failed because webcam or microphone could not be accessed. Check that:") }
-
{_t("A microphone and webcam are plugged in and set up correctly")}
-
{_t("Permission is granted to use the webcam")}
-
{_t("No other application is using the webcam")}
+
{ _t("A microphone and webcam are plugged in and set up correctly") }
+
{ _t("Permission is granted to use the webcam") }
+
{ _t("No other application is using the webcam") }
;
}
@@ -871,6 +912,12 @@ export default class CallHandler extends EventEmitter {
case Action.DialNumber:
this.dialNumber(payload.number);
break;
+ case Action.TransferCallToMatrixID:
+ this.startTransferToMatrixID(payload.call, payload.destination, payload.consultFirst);
+ break;
+ case Action.TransferCallToPhoneNumber:
+ this.startTransferToPhoneNumber(payload.call, payload.destination, payload.consultFirst);
+ break;
}
};
@@ -905,6 +952,48 @@ export default class CallHandler extends EventEmitter {
});
}
+ private async startTransferToPhoneNumber(call: MatrixCall, destination: string, consultFirst: boolean) {
+ const results = await this.pstnLookup(destination);
+ if (!results || results.length === 0 || !results[0].userid) {
+ Modal.createTrackedDialog('', '', ErrorDialog, {
+ title: _t("Unable to transfer call"),
+ description: _t("There was an error looking up the phone number"),
+ });
+ return;
+ }
+
+ await this.startTransferToMatrixID(call, results[0].userid, consultFirst);
+ }
+
+ private async startTransferToMatrixID(call: MatrixCall, destination: string, consultFirst: boolean) {
+ if (consultFirst) {
+ const dmRoomId = await ensureDMExists(MatrixClientPeg.get(), destination);
+
+ dis.dispatch({
+ action: 'place_call',
+ type: call.type,
+ room_id: dmRoomId,
+ transferee: call,
+ });
+ dis.dispatch({
+ action: 'view_room',
+ room_id: dmRoomId,
+ should_peek: false,
+ joining: false,
+ });
+ } else {
+ try {
+ await call.transfer(destination);
+ } catch (e) {
+ console.log("Failed to transfer call", e);
+ Modal.createTrackedDialog('Failed to transfer call', '', ErrorDialog, {
+ title: _t('Transfer Failed'),
+ description: _t('Failed to transfer call'),
+ });
+ }
+ }
+ }
+
setActiveCallRoomId(activeCallRoomId: string) {
logger.info("Setting call in room " + activeCallRoomId + " active");
diff --git a/src/ContentMessages.tsx b/src/ContentMessages.tsx
index ef0a89a690..c5bcb226ff 100644
--- a/src/ContentMessages.tsx
+++ b/src/ContentMessages.tsx
@@ -17,9 +17,9 @@ limitations under the License.
*/
import React from "react";
-import dis from './dispatcher/dispatcher';
-import { MatrixClientPeg } from './MatrixClientPeg';
import { MatrixClient } from "matrix-js-sdk/src/client";
+
+import dis from './dispatcher/dispatcher';
import * as sdk from './index';
import { _t } from './languageHandler';
import Modal from './Modal';
@@ -27,7 +27,6 @@ import RoomViewStore from './stores/RoomViewStore';
import encrypt from "browser-encrypt-attachment";
import extractPngChunks from "png-chunks-extract";
import Spinner from "./components/views/elements/Spinner";
-
import { Action } from "./dispatcher/actions";
import CountlyAnalytics from "./CountlyAnalytics";
import {
@@ -38,7 +37,8 @@ import {
UploadStartedPayload,
} from "./dispatcher/payloads/UploadPayload";
import { IUpload } from "./models/IUpload";
-import { IImageInfo } from "matrix-js-sdk/src/@types/partials";
+import { IAbortablePromise, IImageInfo } from "matrix-js-sdk/src/@types/partials";
+import { BlurhashEncoder } from "./BlurhashEncoder";
const MAX_WIDTH = 800;
const MAX_HEIGHT = 600;
@@ -47,6 +47,8 @@ const MAX_HEIGHT = 600;
// 5669 px (x-axis) , 5669 px (y-axis) , per metre
const PHYS_HIDPI = [0x00, 0x00, 0x16, 0x25, 0x00, 0x00, 0x16, 0x25, 0x01];
+export const BLURHASH_FIELD = "xyz.amorgan.blurhash"; // MSC2448
+
export class UploadCanceledError extends Error {}
type ThumbnailableElement = HTMLImageElement | HTMLVideoElement;
@@ -77,14 +79,11 @@ interface IThumbnail {
};
w: number;
h: number;
+ [BLURHASH_FIELD]: string;
};
thumbnail: Blob;
}
-interface IAbortablePromise extends Promise {
- abort(): void;
-}
-
/**
* Create a thumbnail for a image DOM element.
* The image will be smaller than MAX_WIDTH and MAX_HEIGHT.
@@ -103,44 +102,62 @@ interface IAbortablePromise extends Promise {
* @return {Promise} A promise that resolves with an object with an info key
* and a thumbnail key.
*/
-function createThumbnail(
+async function createThumbnail(
element: ThumbnailableElement,
inputWidth: number,
inputHeight: number,
mimeType: string,
): Promise {
- return new Promise((resolve) => {
- let targetWidth = inputWidth;
- let targetHeight = inputHeight;
- if (targetHeight > MAX_HEIGHT) {
- targetWidth = Math.floor(targetWidth * (MAX_HEIGHT / targetHeight));
- targetHeight = MAX_HEIGHT;
- }
- if (targetWidth > MAX_WIDTH) {
- targetHeight = Math.floor(targetHeight * (MAX_WIDTH / targetWidth));
- targetWidth = MAX_WIDTH;
- }
+ let targetWidth = inputWidth;
+ let targetHeight = inputHeight;
+ if (targetHeight > MAX_HEIGHT) {
+ targetWidth = Math.floor(targetWidth * (MAX_HEIGHT / targetHeight));
+ targetHeight = MAX_HEIGHT;
+ }
+ if (targetWidth > MAX_WIDTH) {
+ targetHeight = Math.floor(targetHeight * (MAX_WIDTH / targetWidth));
+ targetWidth = MAX_WIDTH;
+ }
- const canvas = document.createElement("canvas");
+ let canvas: HTMLCanvasElement | OffscreenCanvas;
+ if (window.OffscreenCanvas) {
+ canvas = new window.OffscreenCanvas(targetWidth, targetHeight);
+ } else {
+ canvas = document.createElement("canvas");
canvas.width = targetWidth;
canvas.height = targetHeight;
- canvas.getContext("2d").drawImage(element, 0, 0, targetWidth, targetHeight);
- canvas.toBlob(function(thumbnail) {
- resolve({
- info: {
- thumbnail_info: {
- w: targetWidth,
- h: targetHeight,
- mimetype: thumbnail.type,
- size: thumbnail.size,
- },
- w: inputWidth,
- h: inputHeight,
- },
- thumbnail: thumbnail,
- });
- }, mimeType);
- });
+ }
+
+ const context = canvas.getContext("2d");
+ context.drawImage(element, 0, 0, targetWidth, targetHeight);
+
+ let thumbnailPromise: Promise;
+
+ if (window.OffscreenCanvas) {
+ thumbnailPromise = (canvas as OffscreenCanvas).convertToBlob({ type: mimeType });
+ } else {
+ thumbnailPromise = new Promise(resolve => (canvas as HTMLCanvasElement).toBlob(resolve, mimeType));
+ }
+
+ const imageData = context.getImageData(0, 0, targetWidth, targetHeight);
+ // thumbnailPromise and blurhash promise are being awaited concurrently
+ const blurhash = await BlurhashEncoder.instance.getBlurhash(imageData);
+ const thumbnail = await thumbnailPromise;
+
+ return {
+ info: {
+ thumbnail_info: {
+ w: targetWidth,
+ h: targetHeight,
+ mimetype: thumbnail.type,
+ size: thumbnail.size,
+ },
+ w: inputWidth,
+ h: inputHeight,
+ [BLURHASH_FIELD]: blurhash,
+ },
+ thumbnail,
+ };
}
/**
@@ -220,7 +237,8 @@ function infoForImageFile(matrixClient, roomId, imageFile) {
}
/**
- * Load a file into a newly created video element.
+ * Load a file into a newly created video element and pull some strings
+ * in an attempt to guarantee the first frame will be showing.
*
* @param {File} videoFile The file to load in an video element.
* @return {Promise} A promise that resolves with the video image element.
@@ -229,20 +247,25 @@ function loadVideoElement(videoFile): Promise {
return new Promise((resolve, reject) => {
// Load the file into an html element
const video = document.createElement("video");
+ video.preload = "metadata";
+ video.playsInline = true;
+ video.muted = true;
const reader = new FileReader();
reader.onload = function(ev) {
- video.src = ev.target.result as string;
-
- // Once ready, returns its size
// Wait until we have enough data to thumbnail the first frame.
- video.onloadeddata = function() {
+ video.onloadeddata = async function() {
resolve(video);
+ video.pause();
};
video.onerror = function(e) {
reject(e);
};
+
+ video.src = ev.target.result as string;
+ video.load();
+ video.play();
};
reader.onerror = function(e) {
reject(e);
@@ -312,7 +335,7 @@ export function uploadFile(
roomId: string,
file: File | Blob,
progressHandler?: any, // TODO: Types
-): Promise<{url?: string, file?: any}> { // TODO: Types
+): IAbortablePromise<{url?: string, file?: any}> { // TODO: Types
let canceled = false;
if (matrixClient.isRoomEncrypted(roomId)) {
// If the room is encrypted then encrypt the file before uploading it.
@@ -344,10 +367,10 @@ export function uploadFile(
encryptInfo.mimetype = file.type;
}
return { "file": encryptInfo };
- });
- (prom as IAbortablePromise).abort = () => {
+ }) as IAbortablePromise<{ file: any }>;
+ prom.abort = () => {
canceled = true;
- if (uploadPromise) MatrixClientPeg.get().cancelUpload(uploadPromise);
+ if (uploadPromise) matrixClient.cancelUpload(uploadPromise);
};
return prom;
} else {
@@ -357,11 +380,11 @@ export function uploadFile(
const promise1 = basePromise.then(function(url) {
if (canceled) throw new UploadCanceledError();
// If the attachment isn't encrypted then include the URL directly.
- return { "url": url };
- });
- (promise1 as any).abort = () => {
+ return { url };
+ }) as IAbortablePromise<{ url: string }>;
+ promise1.abort = () => {
canceled = true;
- MatrixClientPeg.get().cancelUpload(basePromise);
+ matrixClient.cancelUpload(basePromise);
};
return promise1;
}
@@ -373,7 +396,7 @@ export default class ContentMessages {
sendStickerContentToRoom(url: string, roomId: string, info: IImageInfo, text: string, matrixClient: MatrixClient) {
const startTime = CountlyAnalytics.getTimestamp();
- const prom = MatrixClientPeg.get().sendStickerMessage(roomId, url, info, text).catch((e) => {
+ const prom = matrixClient.sendStickerMessage(roomId, url, info, text).catch((e) => {
console.warn(`Failed to send content with URL ${url} to room ${roomId}`, e);
throw e;
});
@@ -397,14 +420,15 @@ export default class ContentMessages {
const isQuoting = Boolean(RoomViewStore.getQuotingEvent());
if (isQuoting) {
+ // FIXME: Using an import will result in Element crashing
const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
const { finished } = Modal.createTrackedDialog<[boolean]>('Upload Reply Warning', '', QuestionDialog, {
title: _t('Replying With Files'),
description: (
-
{_t(
+
{ _t(
'At this time it is not possible to reply with a file. ' +
'Would you like to upload this file without replying?',
- )}
+ ) }
),
hasCancelButton: true,
button: _t("Continue"),
@@ -415,7 +439,7 @@ export default class ContentMessages {
if (!this.mediaConfig) { // hot-path optimization to not flash a spinner if we don't need to
const modal = Modal.createDialog(Spinner, null, 'mx_Dialog_spinner');
- await this.ensureMediaConfigFetched();
+ await this.ensureMediaConfigFetched(matrixClient);
modal.close();
}
@@ -431,6 +455,7 @@ export default class ContentMessages {
}
if (tooBigFiles.length > 0) {
+ // FIXME: Using an import will result in Element crashing
const UploadFailureDialog = sdk.getComponent("dialogs.UploadFailureDialog");
const { finished } = Modal.createTrackedDialog<[boolean]>('Upload Failure', '', UploadFailureDialog, {
badFiles: tooBigFiles,
@@ -441,7 +466,6 @@ export default class ContentMessages {
if (!shouldContinue) return;
}
- const UploadConfirmDialog = sdk.getComponent("dialogs.UploadConfirmDialog");
let uploadAll = false;
// Promise to complete before sending next file into room, used for synchronisation of file-sending
// to match the order the files were specified in
@@ -449,6 +473,8 @@ export default class ContentMessages {
for (let i = 0; i < okFiles.length; ++i) {
const file = okFiles[i];
if (!uploadAll) {
+ // FIXME: Using an import will result in Element crashing
+ const UploadConfirmDialog = sdk.getComponent("dialogs.UploadConfirmDialog");
const { finished } = Modal.createTrackedDialog<[boolean, boolean]>('Upload Files confirmation',
'', UploadConfirmDialog, {
file,
@@ -470,7 +496,7 @@ export default class ContentMessages {
return this.inprogress.filter(u => !u.canceled);
}
- cancelUpload(promise: Promise) {
+ cancelUpload(promise: Promise, matrixClient: MatrixClient) {
let upload: IUpload;
for (let i = 0; i < this.inprogress.length; ++i) {
if (this.inprogress[i].promise === promise) {
@@ -480,7 +506,7 @@ export default class ContentMessages {
}
if (upload) {
upload.canceled = true;
- MatrixClientPeg.get().cancelUpload(upload.promise);
+ matrixClient.cancelUpload(upload.promise);
dis.dispatch({ action: Action.UploadCanceled, upload });
}
}
@@ -527,10 +553,10 @@ export default class ContentMessages {
content.msgtype = 'm.file';
resolve();
}
- });
+ }) as IAbortablePromise;
// create temporary abort handler for before the actual upload gets passed off to js-sdk
- (prom as IAbortablePromise).abort = () => {
+ prom.abort = () => {
upload.canceled = true;
};
@@ -545,7 +571,7 @@ export default class ContentMessages {
dis.dispatch({ action: Action.UploadStarted, upload });
// Focus the composer view
- dis.fire(Action.FocusComposer);
+ dis.fire(Action.FocusSendMessageComposer);
function onProgress(ev) {
upload.total = ev.total;
@@ -559,9 +585,7 @@ export default class ContentMessages {
// XXX: upload.promise must be the promise that
// is returned by uploadFile as it has an abort()
// method hacked onto it.
- upload.promise = uploadFile(
- matrixClient, roomId, file, onProgress,
- );
+ upload.promise = uploadFile(matrixClient, roomId, file, onProgress);
return upload.promise.then(function(result) {
content.file = result.file;
content.url = result.url;
@@ -584,6 +608,7 @@ export default class ContentMessages {
{ fileName: upload.fileName },
);
}
+ // FIXME: Using an import will result in Element crashing
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
Modal.createTrackedDialog('Upload failed', '', ErrorDialog, {
title: _t('Upload Failed'),
@@ -621,11 +646,11 @@ export default class ContentMessages {
return true;
}
- private ensureMediaConfigFetched() {
+ private ensureMediaConfigFetched(matrixClient: MatrixClient) {
if (this.mediaConfig !== null) return;
console.log("[Media Config] Fetching");
- return MatrixClientPeg.get().getMediaConfig().then((config) => {
+ return matrixClient.getMediaConfig().then((config) => {
console.log("[Media Config] Fetched config:", config);
return config;
}).catch(() => {
diff --git a/src/CountlyAnalytics.ts b/src/CountlyAnalytics.ts
index 39dcac4048..72b0462bcd 100644
--- a/src/CountlyAnalytics.ts
+++ b/src/CountlyAnalytics.ts
@@ -15,12 +15,13 @@ limitations under the License.
*/
import { randomString } from "matrix-js-sdk/src/randomstring";
+import { IContent } from "matrix-js-sdk/src/models/event";
+import { sleep } from "matrix-js-sdk/src/utils";
import { getCurrentLanguage } from './languageHandler';
import PlatformPeg from './PlatformPeg';
import SdkConfig from './SdkConfig';
import { MatrixClientPeg } from "./MatrixClientPeg";
-import { sleep } from "./utils/promise";
import RoomViewStore from "./stores/RoomViewStore";
import { Action } from "./dispatcher/actions";
@@ -255,7 +256,7 @@ interface ICreateRoomEvent extends IEvent {
num_users: number;
is_encrypted: boolean;
is_public: boolean;
- }
+ };
}
interface IJoinRoomEvent extends IEvent {
@@ -363,8 +364,8 @@ export default class CountlyAnalytics {
private initTime = CountlyAnalytics.getTimestamp();
private firstPage = true;
- private heartbeatIntervalId: NodeJS.Timeout;
- private activityIntervalId: NodeJS.Timeout;
+ private heartbeatIntervalId: number;
+ private activityIntervalId: number;
private trackTime = true;
private lastBeat: number;
private storedDuration = 0;
@@ -868,7 +869,7 @@ export default class CountlyAnalytics {
roomId: string,
isEdit: boolean,
isReply: boolean,
- content: {format?: string, msgtype: string},
+ content: IContent,
) {
if (this.disabled) return;
const cli = MatrixClientPeg.get();
diff --git a/src/DecryptionFailureTracker.ts b/src/DecryptionFailureTracker.ts
index d40574a6db..df306a54f5 100644
--- a/src/DecryptionFailureTracker.ts
+++ b/src/DecryptionFailureTracker.ts
@@ -46,8 +46,8 @@ export class DecryptionFailureTracker {
};
// Set to an interval ID when `start` is called
- public checkInterval: NodeJS.Timeout = null;
- public trackInterval: NodeJS.Timeout = null;
+ public checkInterval: number = null;
+ public trackInterval: number = null;
// Spread the load on `Analytics` by tracking at a low frequency, `TRACK_INTERVAL_MS`.
static TRACK_INTERVAL_MS = 60000;
diff --git a/src/DeviceListener.ts b/src/DeviceListener.ts
index d70585e5ec..51c624e3c3 100644
--- a/src/DeviceListener.ts
+++ b/src/DeviceListener.ts
@@ -33,6 +33,7 @@ import { isSecretStorageBeingAccessed, accessSecretStorage } from "./SecurityMan
import { isSecureBackupRequired } from './utils/WellKnownUtils';
import { isLoggedIn } from './components/structures/MatrixChat';
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
+import { ActionPayload } from "./dispatcher/payloads";
const KEY_BACKUP_POLL_INTERVAL = 5 * 60 * 1000;
@@ -58,28 +59,28 @@ export default class DeviceListener {
}
start() {
- MatrixClientPeg.get().on('crypto.willUpdateDevices', this._onWillUpdateDevices);
- MatrixClientPeg.get().on('crypto.devicesUpdated', this._onDevicesUpdated);
- MatrixClientPeg.get().on('deviceVerificationChanged', this._onDeviceVerificationChanged);
- MatrixClientPeg.get().on('userTrustStatusChanged', this._onUserTrustStatusChanged);
- MatrixClientPeg.get().on('crossSigning.keysChanged', this._onCrossSingingKeysChanged);
- MatrixClientPeg.get().on('accountData', this._onAccountData);
- MatrixClientPeg.get().on('sync', this._onSync);
- MatrixClientPeg.get().on('RoomState.events', this._onRoomStateEvents);
- this.dispatcherRef = dis.register(this._onAction);
- this._recheck();
+ MatrixClientPeg.get().on('crypto.willUpdateDevices', this.onWillUpdateDevices);
+ MatrixClientPeg.get().on('crypto.devicesUpdated', this.onDevicesUpdated);
+ MatrixClientPeg.get().on('deviceVerificationChanged', this.onDeviceVerificationChanged);
+ MatrixClientPeg.get().on('userTrustStatusChanged', this.onUserTrustStatusChanged);
+ MatrixClientPeg.get().on('crossSigning.keysChanged', this.onCrossSingingKeysChanged);
+ MatrixClientPeg.get().on('accountData', this.onAccountData);
+ MatrixClientPeg.get().on('sync', this.onSync);
+ MatrixClientPeg.get().on('RoomState.events', this.onRoomStateEvents);
+ this.dispatcherRef = dis.register(this.onAction);
+ this.recheck();
}
stop() {
if (MatrixClientPeg.get()) {
- MatrixClientPeg.get().removeListener('crypto.willUpdateDevices', this._onWillUpdateDevices);
- MatrixClientPeg.get().removeListener('crypto.devicesUpdated', this._onDevicesUpdated);
- MatrixClientPeg.get().removeListener('deviceVerificationChanged', this._onDeviceVerificationChanged);
- MatrixClientPeg.get().removeListener('userTrustStatusChanged', this._onUserTrustStatusChanged);
- MatrixClientPeg.get().removeListener('crossSigning.keysChanged', this._onCrossSingingKeysChanged);
- MatrixClientPeg.get().removeListener('accountData', this._onAccountData);
- MatrixClientPeg.get().removeListener('sync', this._onSync);
- MatrixClientPeg.get().removeListener('RoomState.events', this._onRoomStateEvents);
+ MatrixClientPeg.get().removeListener('crypto.willUpdateDevices', this.onWillUpdateDevices);
+ MatrixClientPeg.get().removeListener('crypto.devicesUpdated', this.onDevicesUpdated);
+ MatrixClientPeg.get().removeListener('deviceVerificationChanged', this.onDeviceVerificationChanged);
+ MatrixClientPeg.get().removeListener('userTrustStatusChanged', this.onUserTrustStatusChanged);
+ MatrixClientPeg.get().removeListener('crossSigning.keysChanged', this.onCrossSingingKeysChanged);
+ MatrixClientPeg.get().removeListener('accountData', this.onAccountData);
+ MatrixClientPeg.get().removeListener('sync', this.onSync);
+ MatrixClientPeg.get().removeListener('RoomState.events', this.onRoomStateEvents);
}
if (this.dispatcherRef) {
dis.unregister(this.dispatcherRef);
@@ -103,15 +104,15 @@ export default class DeviceListener {
this.dismissed.add(d);
}
- this._recheck();
+ this.recheck();
}
dismissEncryptionSetup() {
this.dismissedThisDeviceToast = true;
- this._recheck();
+ this.recheck();
}
- _ensureDeviceIdsAtStartPopulated() {
+ private ensureDeviceIdsAtStartPopulated() {
if (this.ourDeviceIdsAtStart === null) {
const cli = MatrixClientPeg.get();
this.ourDeviceIdsAtStart = new Set(
@@ -120,39 +121,39 @@ export default class DeviceListener {
}
}
- _onWillUpdateDevices = async (users: string[], initialFetch?: boolean) => {
+ private onWillUpdateDevices = async (users: string[], initialFetch?: boolean) => {
// If we didn't know about *any* devices before (ie. it's fresh login),
// then they are all pre-existing devices, so ignore this and set the
// devicesAtStart list to the devices that we see after the fetch.
if (initialFetch) return;
const myUserId = MatrixClientPeg.get().getUserId();
- if (users.includes(myUserId)) this._ensureDeviceIdsAtStartPopulated();
+ if (users.includes(myUserId)) this.ensureDeviceIdsAtStartPopulated();
// No need to do a recheck here: we just need to get a snapshot of our devices
// before we download any new ones.
};
- _onDevicesUpdated = (users: string[]) => {
+ private onDevicesUpdated = (users: string[]) => {
if (!users.includes(MatrixClientPeg.get().getUserId())) return;
- this._recheck();
+ this.recheck();
};
- _onDeviceVerificationChanged = (userId: string) => {
+ private onDeviceVerificationChanged = (userId: string) => {
if (userId !== MatrixClientPeg.get().getUserId()) return;
- this._recheck();
+ this.recheck();
};
- _onUserTrustStatusChanged = (userId: string) => {
+ private onUserTrustStatusChanged = (userId: string) => {
if (userId !== MatrixClientPeg.get().getUserId()) return;
- this._recheck();
+ this.recheck();
};
- _onCrossSingingKeysChanged = () => {
- this._recheck();
+ private onCrossSingingKeysChanged = () => {
+ this.recheck();
};
- _onAccountData = (ev) => {
+ private onAccountData = (ev: MatrixEvent) => {
// User may have:
// * migrated SSSS to symmetric
// * uploaded keys to secret storage
@@ -160,34 +161,35 @@ export default class DeviceListener {
// which result in account data changes affecting checks below.
if (
ev.getType().startsWith('m.secret_storage.') ||
- ev.getType().startsWith('m.cross_signing.')
+ ev.getType().startsWith('m.cross_signing.') ||
+ ev.getType() === 'm.megolm_backup.v1'
) {
- this._recheck();
+ this.recheck();
}
};
- _onSync = (state, prevState) => {
- if (state === 'PREPARED' && prevState === null) this._recheck();
+ private onSync = (state, prevState) => {
+ if (state === 'PREPARED' && prevState === null) this.recheck();
};
- _onRoomStateEvents = (ev: MatrixEvent) => {
+ private onRoomStateEvents = (ev: MatrixEvent) => {
if (ev.getType() !== "m.room.encryption") {
return;
}
// If a room changes to encrypted, re-check as it may be our first
// encrypted room. This also catches encrypted room creation as well.
- this._recheck();
+ this.recheck();
};
- _onAction = ({ action }) => {
+ private onAction = ({ action }: ActionPayload) => {
if (action !== "on_logged_in") return;
- this._recheck();
+ this.recheck();
};
// The server doesn't tell us when key backup is set up, so we poll
// & cache the result
- async _getKeyBackupInfo() {
+ private async getKeyBackupInfo() {
const now = (new Date()).getTime();
if (!this.keyBackupInfo || this.keyBackupFetchedAt < now - KEY_BACKUP_POLL_INTERVAL) {
this.keyBackupInfo = await MatrixClientPeg.get().getKeyBackupVersion();
@@ -205,7 +207,7 @@ export default class DeviceListener {
return cli && cli.getRooms().some(r => cli.isRoomEncrypted(r.roomId));
}
- async _recheck() {
+ private async recheck() {
const cli = MatrixClientPeg.get();
if (!await cli.doesServerSupportUnstableFeature("org.matrix.e2e_cross_signing")) return;
@@ -234,7 +236,7 @@ export default class DeviceListener {
// Cross-signing on account but this device doesn't trust the master key (verify this session)
showSetupEncryptionToast(SetupKind.VERIFY_THIS_SESSION);
} else {
- const backupInfo = await this._getKeyBackupInfo();
+ const backupInfo = await this.getKeyBackupInfo();
if (backupInfo) {
// No cross-signing on account but key backup available (upgrade encryption)
showSetupEncryptionToast(SetupKind.UPGRADE_ENCRYPTION);
@@ -255,7 +257,7 @@ export default class DeviceListener {
// This needs to be done after awaiting on downloadKeys() above, so
// we make sure we get the devices after the fetch is done.
- this._ensureDeviceIdsAtStartPopulated();
+ this.ensureDeviceIdsAtStartPopulated();
// Unverified devices that were there last time the app ran
// (technically could just be a boolean: we don't actually
diff --git a/src/HtmlUtils.tsx b/src/HtmlUtils.tsx
index c80b50c566..af5d2b3019 100644
--- a/src/HtmlUtils.tsx
+++ b/src/HtmlUtils.tsx
@@ -25,7 +25,6 @@ import _linkifyElement from 'linkifyjs/element';
import _linkifyString from 'linkifyjs/string';
import classNames from 'classnames';
import EMOJIBASE_REGEX from 'emojibase-regex';
-import url from 'url';
import katex from 'katex';
import { AllHtmlEntities } from 'html-entities';
import { IContent } from 'matrix-js-sdk/src/models/event';
@@ -34,7 +33,7 @@ import { IExtendedSanitizeOptions } from './@types/sanitize-html';
import linkifyMatrix from './linkify-matrix';
import SettingsStore from './settings/SettingsStore';
import { tryTransformPermalinkToLocalHref } from "./utils/permalinks/Permalinks";
-import { SHORTCODE_TO_EMOJI, getEmojiFromUnicode } from "./emoji";
+import { getEmojiFromUnicode } from "./emoji";
import ReplyThread from "./components/views/elements/ReplyThread";
import { mediaFromMxc } from "./customisations/Media";
@@ -58,7 +57,9 @@ const BIGEMOJI_REGEX = new RegExp(`^(${EMOJIBASE_REGEX.source})+$`, 'i');
const COLOR_REGEX = /^#[0-9a-fA-F]{6}$/;
-export const PERMITTED_URL_SCHEMES = ['http', 'https', 'ftp', 'mailto', 'magnet'];
+export const PERMITTED_URL_SCHEMES = ['http', 'https', 'ftp', 'mailto', 'magnet', 'matrix'];
+
+const MEDIA_API_MXC_REGEX = /\/_matrix\/media\/r0\/(?:download|thumbnail)\/(.+?)\/(.+?)(?:[?/]|$)/;
/*
* Return true if the given string contains emoji
@@ -78,20 +79,8 @@ function mightContainEmoji(str: string): boolean {
* @return {String} The shortcode (such as :thumbup:)
*/
export function unicodeToShortcode(char: string): string {
- const data = getEmojiFromUnicode(char);
- return (data && data.shortcodes ? `:${data.shortcodes[0]}:` : '');
-}
-
-/**
- * Returns the unicode character for an emoji shortcode
- *
- * @param {String} shortcode The shortcode (such as :thumbup:)
- * @return {String} The emoji character; null if none exists
- */
-export function shortcodeToUnicode(shortcode: string): string {
- shortcode = shortcode.slice(1, shortcode.length - 1);
- const data = SHORTCODE_TO_EMOJI.get(shortcode);
- return data ? data.unicode : null;
+ const shortcodes = getEmojiFromUnicode(char)?.shortcodes;
+ return shortcodes?.length ? `:${shortcodes[0]}:` : '';
}
export function processHtmlForSending(html: string): string {
@@ -151,10 +140,8 @@ export function getHtmlText(insaneHtml: string): string {
*/
export function isUrlPermitted(inputUrl: string): boolean {
try {
- const parsed = url.parse(inputUrl);
- if (!parsed.protocol) return false;
// URL parser protocol includes the trailing colon
- return PERMITTED_URL_SCHEMES.includes(parsed.protocol.slice(0, -1));
+ return PERMITTED_URL_SCHEMES.includes(new URL(inputUrl).protocol.slice(0, -1));
} catch (e) {
return false;
}
@@ -176,18 +163,31 @@ const transformTags: IExtendedSanitizeOptions["transformTags"] = { // custom to
return { tagName, attribs };
},
'img': function(tagName: string, attribs: sanitizeHtml.Attributes) {
+ let src = attribs.src;
// Strip out imgs that aren't `mxc` here instead of using allowedSchemesByTag
// because transformTags is used _before_ we filter by allowedSchemesByTag and
// we don't want to allow images with `https?` `src`s.
// We also drop inline images (as if they were not present at all) when the "show
// images" preference is disabled. Future work might expose some UI to reveal them
// like standalone image events have.
- if (!attribs.src || !attribs.src.startsWith('mxc://') || !SettingsStore.getValue("showImages")) {
+ if (!src || !SettingsStore.getValue("showImages")) {
return { tagName, attribs: {} };
}
+
+ if (!src.startsWith("mxc://")) {
+ const match = MEDIA_API_MXC_REGEX.exec(src);
+ if (match) {
+ src = `mxc://${match[1]}/${match[2]}`;
+ }
+ }
+
+ if (!src.startsWith("mxc://")) {
+ return { tagName, attribs: {} };
+ }
+
const width = Number(attribs.width) || 800;
const height = Number(attribs.height) || 600;
- attribs.src = mediaFromMxc(attribs.src).getThumbnailOfSourceHttp(width, height);
+ attribs.src = mediaFromMxc(src).getThumbnailOfSourceHttp(width, height);
return { tagName, attribs };
},
'code': function(tagName: string, attribs: sanitizeHtml.Attributes) {
@@ -358,11 +358,11 @@ interface IOpts {
stripReplyFallback?: boolean;
returnString?: boolean;
forComposerQuote?: boolean;
- ref?: React.Ref;
+ ref?: React.Ref;
}
export interface IOptsReturnNode extends IOpts {
- returnString: false;
+ returnString: false | undefined;
}
export interface IOptsReturnString extends IOpts {
@@ -403,9 +403,14 @@ export function bodyToHtml(content: IContent, highlights: string[], opts: IOpts
try {
if (highlights && highlights.length > 0) {
const highlighter = new HtmlHighlighter("mx_EventTile_searchHighlight", opts.highlightLink);
- const safeHighlights = highlights.map(function(highlight) {
- return sanitizeHtml(highlight, sanitizeParams);
- });
+ const safeHighlights = highlights
+ // sanitizeHtml can hang if an unclosed HTML tag is thrown at it
+ // A search for ` !highlight.includes("<"))
+ .map((highlight: string): string => sanitizeHtml(highlight, sanitizeParams));
// XXX: hacky bodge to temporarily apply a textFilter to the sanitizeParams structure.
sanitizeParams.textFilter = function(safeText) {
return highlighter.applyHighlights(safeText, safeHighlights).join('');
diff --git a/src/IdentityAuthClient.js b/src/IdentityAuthClient.js
index 31a5021317..e91e1d72cf 100644
--- a/src/IdentityAuthClient.js
+++ b/src/IdentityAuthClient.js
@@ -127,7 +127,7 @@ export default class IdentityAuthClient {
await this._matrixClient.getIdentityAccount(token);
} catch (e) {
if (e.errcode === "M_TERMS_NOT_SIGNED") {
- console.log("Identity Server requires new terms to be agreed to");
+ console.log("Identity server requires new terms to be agreed to");
await startTermsFlow([new Service(
SERVICE_TYPES.IS,
identityServerUrl,
@@ -149,17 +149,17 @@ export default class IdentityAuthClient {
title: _t("Identity server has no terms of service"),
description: (
-
{_t(
+
{ _t(
"This action requires accessing the default identity server " +
" to validate an email address or phone number, " +
"but the server does not have any terms of service.", {},
{
- server: () => {abbreviateUrl(identityServerUrl)},
+ server: () => { abbreviateUrl(identityServerUrl) },
},
- )}
-
{_t(
+ ) }
+
{ _t(
"Only continue if you trust the owner of the server.",
- )}
+ ) }
),
button: _t("Trust"),
diff --git a/src/Lifecycle.ts b/src/Lifecycle.ts
index 76dee5ab55..410124a637 100644
--- a/src/Lifecycle.ts
+++ b/src/Lifecycle.ts
@@ -21,6 +21,7 @@ import { createClient } from 'matrix-js-sdk/src/matrix';
import { InvalidStoreError } from "matrix-js-sdk/src/errors";
import { MatrixClient } from "matrix-js-sdk/src/client";
import { decryptAES, encryptAES, IEncryptedPayload } from "matrix-js-sdk/src/crypto/aes";
+import { QueryDict } from 'matrix-js-sdk/src/utils';
import { IMatrixClientCreds, MatrixClientPeg } from './MatrixClientPeg';
import SecurityCustomisations from "./customisations/Security";
@@ -33,7 +34,6 @@ import Presence from './Presence';
import dis from './dispatcher/dispatcher';
import DMRoomMap from './utils/DMRoomMap';
import Modal from './Modal';
-import * as sdk from './index';
import ActiveWidgetStore from './stores/ActiveWidgetStore';
import PlatformPeg from "./PlatformPeg";
import { sendLoginRequest } from "./Login";
@@ -52,6 +52,10 @@ import CallHandler from './CallHandler';
import LifecycleCustomisations from "./customisations/Lifecycle";
import ErrorDialog from "./components/views/dialogs/ErrorDialog";
import { _t } from "./languageHandler";
+import LazyLoadingResyncDialog from "./components/views/dialogs/LazyLoadingResyncDialog";
+import LazyLoadingDisabledDialog from "./components/views/dialogs/LazyLoadingDisabledDialog";
+import SessionRestoreErrorDialog from "./components/views/dialogs/SessionRestoreErrorDialog";
+import StorageEvictedDialog from "./components/views/dialogs/StorageEvictedDialog";
const HOMESERVER_URL_KEY = "mx_hs_url";
const ID_SERVER_URL_KEY = "mx_is_url";
@@ -62,7 +66,7 @@ interface ILoadSessionOpts {
guestIsUrl?: string;
ignoreGuest?: boolean;
defaultDeviceDisplayName?: string;
- fragmentQueryParams?: Record;
+ fragmentQueryParams?: QueryDict;
}
/**
@@ -115,8 +119,8 @@ export async function loadSession(opts: ILoadSessionOpts = {}): Promise
) {
console.log("Using guest access credentials");
return doSetLoggedIn({
- userId: fragmentQueryParams.guest_user_id,
- accessToken: fragmentQueryParams.guest_access_token,
+ userId: fragmentQueryParams.guest_user_id as string,
+ accessToken: fragmentQueryParams.guest_access_token as string,
homeserverUrl: guestHsUrl,
identityServerUrl: guestIsUrl,
guest: true,
@@ -170,7 +174,7 @@ export async function getStoredSessionOwner(): Promise<[string, boolean]> {
* login, else false
*/
export function attemptTokenLogin(
- queryParams: Record,
+ queryParams: QueryDict,
defaultDeviceDisplayName?: string,
fragmentAfterLogin?: string,
): Promise {
@@ -195,7 +199,7 @@ export function attemptTokenLogin(
homeserver,
identityServer,
"m.login.token", {
- token: queryParams.loginToken,
+ token: queryParams.loginToken as string,
initial_device_display_name: defaultDeviceDisplayName,
},
).then(function(creds) {
@@ -238,8 +242,6 @@ export function handleInvalidStoreError(e: InvalidStoreError): Promise {
return Promise.resolve().then(() => {
const lazyLoadEnabled = e.value;
if (lazyLoadEnabled) {
- const LazyLoadingResyncDialog =
- sdk.getComponent("views.dialogs.LazyLoadingResyncDialog");
return new Promise((resolve) => {
Modal.createDialog(LazyLoadingResyncDialog, {
onFinished: resolve,
@@ -250,8 +252,6 @@ export function handleInvalidStoreError(e: InvalidStoreError): Promise {
// between LL/non-LL version on same host.
// as disabling LL when previously enabled
// is a strong indicator of this (/develop & /app)
- const LazyLoadingDisabledDialog =
- sdk.getComponent("views.dialogs.LazyLoadingDisabledDialog");
return new Promise((resolve) => {
Modal.createDialog(LazyLoadingDisabledDialog, {
onFinished: resolve,
@@ -451,9 +451,6 @@ export async function restoreFromLocalStorage(opts?: { ignoreGuest?: boolean }):
async function handleLoadSessionFailure(e: Error): Promise {
console.error("Unable to load session", e);
- const SessionRestoreErrorDialog =
- sdk.getComponent('views.dialogs.SessionRestoreErrorDialog');
-
const modal = Modal.createTrackedDialog('Session Restore Error', '', SessionRestoreErrorDialog, {
error: e.message,
});
@@ -612,7 +609,6 @@ async function doSetLoggedIn(
}
function showStorageEvictedDialog(): Promise {
- const StorageEvictedDialog = sdk.getComponent('views.dialogs.StorageEvictedDialog');
return new Promise(resolve => {
Modal.createTrackedDialog('Storage evicted', '', StorageEvictedDialog, {
onFinished: resolve,
diff --git a/src/Markdown.js b/src/Markdown.ts
similarity index 74%
rename from src/Markdown.js
rename to src/Markdown.ts
index f670bded12..96169d4011 100644
--- a/src/Markdown.js
+++ b/src/Markdown.ts
@@ -1,5 +1,6 @@
/*
Copyright 2016 OpenMarket Ltd
+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.
@@ -15,16 +16,24 @@ limitations under the License.
*/
import * as commonmark from 'commonmark';
-import {escape} from "lodash";
+import { escape } from "lodash";
const ALLOWED_HTML_TAGS = ['sub', 'sup', 'del', 'u'];
// These types of node are definitely text
const TEXT_NODES = ['text', 'softbreak', 'linebreak', 'paragraph', 'document'];
-function is_allowed_html_tag(node) {
+// As far as @types/commonmark is concerned, these are not public, so add them
+interface CommonmarkHtmlRendererInternal extends commonmark.HtmlRenderer {
+ paragraph: (node: commonmark.Node, entering: boolean) => void;
+ link: (node: commonmark.Node, entering: boolean) => void;
+ html_inline: (node: commonmark.Node) => void; // eslint-disable-line camelcase
+ html_block: (node: commonmark.Node) => void; // eslint-disable-line camelcase
+}
+
+function isAllowedHtmlTag(node: commonmark.Node): boolean {
if (node.literal != null &&
- node.literal.match('^<((div|span) data-mx-maths="[^"]*"|\/(div|span))>$') != null) {
+ node.literal.match('^<((div|span) data-mx-maths="[^"]*"|/(div|span))>$') != null) {
return true;
}
@@ -39,21 +48,12 @@ function is_allowed_html_tag(node) {
return false;
}
-function html_if_tag_allowed(node) {
- if (is_allowed_html_tag(node)) {
- this.lit(node.literal);
- return;
- } else {
- this.lit(escape(node.literal));
- }
-}
-
/*
* Returns true if the parse output containing the node
* comprises multiple block level elements (ie. lines),
* or false if it is only a single line.
*/
-function is_multi_line(node) {
+function isMultiLine(node: commonmark.Node): boolean {
let par = node;
while (par.parent) {
par = par.parent;
@@ -67,6 +67,9 @@ function is_multi_line(node) {
* it's plain text.
*/
export default class Markdown {
+ private input: string;
+ private parsed: commonmark.Node;
+
constructor(input) {
this.input = input;
@@ -74,7 +77,7 @@ export default class Markdown {
this.parsed = parser.parse(this.input);
}
- isPlainText() {
+ isPlainText(): boolean {
const walker = this.parsed.walker();
let ev;
@@ -87,7 +90,7 @@ export default class Markdown {
// if it's an allowed html tag, we need to render it and therefore
// we will need to use HTML. If it's not allowed, it's not HTML since
// we'll just be treating it as text.
- if (is_allowed_html_tag(node)) {
+ if (isAllowedHtmlTag(node)) {
return false;
}
} else {
@@ -97,7 +100,7 @@ export default class Markdown {
return true;
}
- toHTML({ externalLinks = false } = {}) {
+ toHTML({ externalLinks = false } = {}): string {
const renderer = new commonmark.HtmlRenderer({
safe: false,
@@ -107,7 +110,7 @@ export default class Markdown {
// block quote ends up all on one line
// (https://github.com/vector-im/element-web/issues/3154)
softbreak: ' ',
- });
+ }) as CommonmarkHtmlRendererInternal;
// Trying to strip out the wrapping causes a lot more complication
// than it's worth, i think. For instance, this code will go and strip
@@ -118,16 +121,16 @@ export default class Markdown {
//
// Let's try sending with s anyway for now, though.
- const real_paragraph = renderer.paragraph;
+ const realParagraph = renderer.paragraph;
- renderer.paragraph = function(node, entering) {
+ renderer.paragraph = function(node: commonmark.Node, entering: boolean) {
// If there is only one top level node, just return the
// bare text: it's a single line of text and so should be
// 'inline', rather than unnecessarily wrapped in its own
// p tag. If, however, we have multiple nodes, each gets
// its own p tag to keep them as separate paragraphs.
- if (is_multi_line(node)) {
- real_paragraph.call(this, node, entering);
+ if (isMultiLine(node)) {
+ realParagraph.call(this, node, entering);
}
};
@@ -150,19 +153,26 @@ export default class Markdown {
}
};
- renderer.html_inline = html_if_tag_allowed;
+ renderer.html_inline = function(node: commonmark.Node) {
+ if (isAllowedHtmlTag(node)) {
+ this.lit(node.literal);
+ return;
+ } else {
+ this.lit(escape(node.literal));
+ }
+ };
- renderer.html_block = function(node) {
-/*
+ renderer.html_block = function(node: commonmark.Node) {
+ /*
// as with `paragraph`, we only insert line breaks
// if there are multiple lines in the markdown.
const isMultiLine = is_multi_line(node);
if (isMultiLine) this.cr();
-*/
- html_if_tag_allowed.call(this, node);
-/*
+ */
+ renderer.html_inline(node);
+ /*
if (isMultiLine) this.cr();
-*/
+ */
};
return renderer.render(this.parsed);
@@ -177,23 +187,22 @@ export default class Markdown {
* N.B. this does **NOT** render arbitrary MD to plain text - only MD
* which has no formatting. Otherwise it emits HTML(!).
*/
- toPlaintext() {
- const renderer = new commonmark.HtmlRenderer({safe: false});
- const real_paragraph = renderer.paragraph;
+ toPlaintext(): string {
+ const renderer = new commonmark.HtmlRenderer({ safe: false }) as CommonmarkHtmlRendererInternal;
- renderer.paragraph = function(node, entering) {
+ renderer.paragraph = function(node: commonmark.Node, entering: boolean) {
// as with toHTML, only append lines to paragraphs if there are
// multiple paragraphs
- if (is_multi_line(node)) {
+ if (isMultiLine(node)) {
if (!entering && node.next) {
this.lit('\n\n');
}
}
};
- renderer.html_block = function(node) {
+ renderer.html_block = function(node: commonmark.Node) {
this.lit(node.literal);
- if (is_multi_line(node) && node.next) this.lit('\n\n');
+ if (isMultiLine(node) && node.next) this.lit('\n\n');
};
return renderer.render(this.parsed);
diff --git a/src/MatrixClientPeg.ts b/src/MatrixClientPeg.ts
index cb6cb5c65c..f43351aab2 100644
--- a/src/MatrixClientPeg.ts
+++ b/src/MatrixClientPeg.ts
@@ -17,8 +17,8 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
-import { ICreateClientOpts } from 'matrix-js-sdk/src/matrix';
-import { MatrixClient } from 'matrix-js-sdk/src/client';
+import { ICreateClientOpts, PendingEventOrdering } from 'matrix-js-sdk/src/matrix';
+import { IStartClientOpts, MatrixClient } from 'matrix-js-sdk/src/client';
import { MemoryStore } from 'matrix-js-sdk/src/store/memory';
import * as utils from 'matrix-js-sdk/src/utils';
import { EventTimeline } from 'matrix-js-sdk/src/models/event-timeline';
@@ -47,25 +47,8 @@ export interface IMatrixClientCreds {
freshLogin?: boolean;
}
-// TODO: Move this to the js-sdk
-export interface IOpts {
- initialSyncLimit?: number;
- pendingEventOrdering?: "detached" | "chronological";
- lazyLoadMembers?: boolean;
- clientWellKnownPollPeriod?: number;
-}
-
export interface IMatrixClientPeg {
- opts: IOpts;
-
- /**
- * Sets the script href passed to the IndexedDB web worker
- * If set, a separate web worker will be started to run the IndexedDB
- * queries on.
- *
- * @param {string} script href to the script to be passed to the web worker
- */
- setIndexedDbWorkerScript(script: string): void;
+ opts: IStartClientOpts;
/**
* Return the server name of the user's homeserver
@@ -122,12 +105,12 @@ export interface IMatrixClientPeg {
* This module provides a singleton instance of this class so the 'current'
* Matrix Client object is available easily.
*/
-class _MatrixClientPeg implements IMatrixClientPeg {
+class MatrixClientPegClass implements IMatrixClientPeg {
// These are the default options used when when the
// client is started in 'start'. These can be altered
// at any time up to after the 'will_start_client'
// event is finished processing.
- public opts: IOpts = {
+ public opts: IStartClientOpts = {
initialSyncLimit: 20,
};
@@ -141,10 +124,6 @@ class _MatrixClientPeg implements IMatrixClientPeg {
constructor() {
}
- public setIndexedDbWorkerScript(script: string): void {
- createMatrixClient.indexedDbWorkerScript = script;
- }
-
public get(): MatrixClient {
return this.matrixClient;
}
@@ -219,6 +198,7 @@ class _MatrixClientPeg implements IMatrixClientPeg {
} catch (e) {
if (e && e.name === 'InvalidCryptoStoreError') {
// The js-sdk found a crypto DB too new for it to use
+ // FIXME: Using an import will result in test failures
const CryptoStoreTooNewDialog =
sdk.getComponent("views.dialogs.CryptoStoreTooNewDialog");
Modal.createDialog(CryptoStoreTooNewDialog);
@@ -230,7 +210,7 @@ class _MatrixClientPeg implements IMatrixClientPeg {
const opts = utils.deepCopy(this.opts);
// the react sdk doesn't work without this, so don't allow
- opts.pendingEventOrdering = "detached";
+ opts.pendingEventOrdering = PendingEventOrdering.Detached;
opts.lazyLoadMembers = true;
opts.clientWellKnownPollPeriod = 2 * 60 * 60; // 2 hours
@@ -320,7 +300,7 @@ class _MatrixClientPeg implements IMatrixClientPeg {
}
if (!window.mxMatrixClientPeg) {
- window.mxMatrixClientPeg = new _MatrixClientPeg();
+ window.mxMatrixClientPeg = new MatrixClientPegClass();
}
export const MatrixClientPeg = window.mxMatrixClientPeg;
diff --git a/src/MediaDeviceHandler.ts b/src/MediaDeviceHandler.ts
index 49ef123def..073f24523d 100644
--- a/src/MediaDeviceHandler.ts
+++ b/src/MediaDeviceHandler.ts
@@ -20,12 +20,15 @@ import { SettingLevel } from "./settings/SettingLevel";
import { setMatrixCallAudioInput, setMatrixCallVideoInput } from "matrix-js-sdk/src/matrix";
import EventEmitter from 'events';
-interface IMediaDevices {
- audioOutput: Array;
- audioInput: Array;
- videoInput: Array;
+// XXX: MediaDeviceKind is a union type, so we make our own enum
+export enum MediaDeviceKindEnum {
+ AudioOutput = "audiooutput",
+ AudioInput = "audioinput",
+ VideoInput = "videoinput",
}
+export type IMediaDevices = Record>;
+
export enum MediaDeviceHandlerEvent {
AudioOutputChanged = "audio_output_changed",
}
@@ -51,20 +54,14 @@ export default class MediaDeviceHandler extends EventEmitter {
try {
const devices = await navigator.mediaDevices.enumerateDevices();
+ const output = {
+ [MediaDeviceKindEnum.AudioOutput]: [],
+ [MediaDeviceKindEnum.AudioInput]: [],
+ [MediaDeviceKindEnum.VideoInput]: [],
+ };
- const audioOutput = [];
- const audioInput = [];
- const videoInput = [];
-
- devices.forEach((device) => {
- switch (device.kind) {
- case 'audiooutput': audioOutput.push(device); break;
- case 'audioinput': audioInput.push(device); break;
- case 'videoinput': videoInput.push(device); break;
- }
- });
-
- return { audioOutput, audioInput, videoInput };
+ devices.forEach((device) => output[device.kind].push(device));
+ return output;
} catch (error) {
console.warn('Unable to refresh WebRTC Devices: ', error);
}
@@ -106,6 +103,14 @@ export default class MediaDeviceHandler extends EventEmitter {
setMatrixCallVideoInput(deviceId);
}
+ public setDevice(deviceId: string, kind: MediaDeviceKindEnum): void {
+ switch (kind) {
+ case MediaDeviceKindEnum.AudioOutput: this.setAudioOutput(deviceId); break;
+ case MediaDeviceKindEnum.AudioInput: this.setAudioInput(deviceId); break;
+ case MediaDeviceKindEnum.VideoInput: this.setVideoInput(deviceId); break;
+ }
+ }
+
public static getAudioOutput(): string {
return SettingsStore.getValueAt(SettingLevel.DEVICE, "webrtc_audiooutput");
}
diff --git a/src/Modal.tsx b/src/Modal.tsx
index 0b0e621e89..1e84078ddb 100644
--- a/src/Modal.tsx
+++ b/src/Modal.tsx
@@ -18,10 +18,10 @@ limitations under the License.
import React from 'react';
import ReactDOM from 'react-dom';
import classNames from 'classnames';
+import { defer } from "matrix-js-sdk/src/utils";
import Analytics from './Analytics';
import dis from './dispatcher/dispatcher';
-import { defer } from './utils/promise';
import AsyncWrapper from './AsyncWrapper';
const DIALOG_CONTAINER_ID = "mx_Dialog_Container";
@@ -378,7 +378,7 @@ export class ModalManager {
const dialog = (
- {modal.elem}
+ { modal.elem }
diff --git a/src/Notifier.ts b/src/Notifier.ts
index 2335dc59ac..1137e44aec 100644
--- a/src/Notifier.ts
+++ b/src/Notifier.ts
@@ -27,7 +27,6 @@ import * as TextForEvent from './TextForEvent';
import Analytics from './Analytics';
import * as Avatar from './Avatar';
import dis from './dispatcher/dispatcher';
-import * as sdk from './index';
import { _t } from './languageHandler';
import Modal from './Modal';
import SettingsStore from "./settings/SettingsStore";
@@ -37,6 +36,7 @@ import { isPushNotifyDisabled } from "./settings/controllers/NotificationControl
import RoomViewStore from "./stores/RoomViewStore";
import UserActivity from "./UserActivity";
import { mediaFromMxc } from "./customisations/Media";
+import ErrorDialog from "./components/views/dialogs/ErrorDialog";
/*
* Dispatches:
@@ -240,7 +240,6 @@ export const Notifier = {
? _t('%(brand)s does not have permission to send you notifications - ' +
'please check your browser settings', { brand })
: _t('%(brand)s was not given permission to send notifications - please try again', { brand });
- const ErrorDialog = sdk.getComponent('dialogs.ErrorDialog');
Modal.createTrackedDialog('Unable to enable Notifications', result, ErrorDialog, {
title: _t('Unable to enable Notifications'),
description,
@@ -329,7 +328,7 @@ export const Notifier = {
onEvent: function(ev: MatrixEvent) {
if (!this.isSyncing) return; // don't alert for any messages initially
- if (ev.sender && ev.sender.userId === MatrixClientPeg.get().credentials.userId) return;
+ if (ev.getSender() === MatrixClientPeg.get().credentials.userId) return;
MatrixClientPeg.get().decryptEventIfNeeded(ev);
diff --git a/src/RoomInvite.tsx b/src/RoomInvite.tsx
index c7f377b6e8..7d093f4092 100644
--- a/src/RoomInvite.tsx
+++ b/src/RoomInvite.tsx
@@ -22,13 +22,13 @@ import { User } from "matrix-js-sdk/src/models/user";
import { MatrixClientPeg } from './MatrixClientPeg';
import MultiInviter, { CompletionStates } from './utils/MultiInviter';
import Modal from './Modal';
-import * as sdk from './';
import { _t } from './languageHandler';
import InviteDialog, { KIND_DM, KIND_INVITE, Member } from "./components/views/dialogs/InviteDialog";
import CommunityPrototypeInviteDialog from "./components/views/dialogs/CommunityPrototypeInviteDialog";
import { CommunityPrototypeStore } from "./stores/CommunityPrototypeStore";
import BaseAvatar from "./components/views/avatars/BaseAvatar";
import { mediaFromMxc } from "./customisations/Media";
+import ErrorDialog from "./components/views/dialogs/ErrorDialog";
export interface IInviteResult {
states: CompletionStates;
@@ -51,7 +51,6 @@ export function inviteMultipleToRoom(roomId: string, addresses: string[]): Promi
export function showStartChatInviteDialog(initialText = ""): void {
// This dialog handles the room creation internally - we don't need to worry about it.
- const InviteDialog = sdk.getComponent("dialogs.InviteDialog");
Modal.createTrackedDialog(
'Start DM', '', InviteDialog, { kind: KIND_DM, initialText },
/*className=*/null, /*isPriority=*/false, /*isStatic=*/true,
@@ -111,7 +110,6 @@ export function inviteUsersToRoom(roomId: string, userIds: string[]): Promise {
console.error(err.stack);
- const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
Modal.createTrackedDialog('Failed to invite', '', ErrorDialog, {
title: _t("Failed to invite"),
description: ((err && err.message) ? err.message : _t("Operation failed")),
@@ -131,7 +129,6 @@ export function showAnyInviteErrors(
// Just get the first message because there was a fatal problem on the first
// user. This usually means that no other users were attempted, making it
// pointless for us to list who failed exactly.
- const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
Modal.createTrackedDialog('Failed to invite users to the room', '', ErrorDialog, {
title: _t("Failed to invite users to the room:", { roomName: room.name }),
description: inviter.getErrorText(failedUsers[0]),
@@ -178,7 +175,6 @@ export function showAnyInviteErrors(
;
- const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
Modal.createTrackedDialog("Some invites could not be sent", "", ErrorDialog, {
title: _t("Some invites couldn't be sent"),
description,
diff --git a/src/Rooms.ts b/src/Rooms.ts
index 4d1682660b..6e2fd4d3a2 100644
--- a/src/Rooms.ts
+++ b/src/Rooms.ts
@@ -17,6 +17,7 @@ limitations under the License.
import { Room } from "matrix-js-sdk/src/models/room";
import { MatrixClientPeg } from './MatrixClientPeg';
+import AliasCustomisations from './customisations/Alias';
/**
* Given a room object, return the alias we should use for it,
@@ -28,7 +29,18 @@ import { MatrixClientPeg } from './MatrixClientPeg';
* @returns {string} A display alias for the given room
*/
export function getDisplayAliasForRoom(room: Room): string {
- return room.getCanonicalAlias() || room.getAltAliases()[0];
+ return getDisplayAliasForAliasSet(
+ room.getCanonicalAlias(), room.getAltAliases(),
+ );
+}
+
+// The various display alias getters should all feed through this one path so
+// there's a single place to change the logic.
+export function getDisplayAliasForAliasSet(canonicalAlias: string, altAliases: string[]): string {
+ if (AliasCustomisations.getDisplayAliasForAliasSet) {
+ return AliasCustomisations.getDisplayAliasForAliasSet(canonicalAlias, altAliases);
+ }
+ return canonicalAlias || altAliases?.[0];
}
export function looksLikeDirectMessageRoom(room: Room, myUserId: string): boolean {
@@ -72,10 +84,8 @@ export function guessAndSetDMRoom(room: Room, isDirect: boolean): Promise
this room as a DM room
* @returns {object} A promise
*/
-export function setDMRoom(roomId: string, userId: string): Promise {
- if (MatrixClientPeg.get().isGuest()) {
- return Promise.resolve();
- }
+export async function setDMRoom(roomId: string, userId: string): Promise {
+ if (MatrixClientPeg.get().isGuest()) return;
const mDirectEvent = MatrixClientPeg.get().getAccountData('m.direct');
let dmRoomMap = {};
@@ -104,7 +114,7 @@ export function setDMRoom(roomId: string, userId: string): Promise {
dmRoomMap[userId] = roomList;
}
- return MatrixClientPeg.get().setAccountData('m.direct', dmRoomMap);
+ await MatrixClientPeg.get().setAccountData('m.direct', dmRoomMap);
}
/**
diff --git a/src/Searching.js b/src/Searching.ts
similarity index 79%
rename from src/Searching.js
rename to src/Searching.ts
index d0666b1760..37f85efa77 100644
--- a/src/Searching.js
+++ b/src/Searching.ts
@@ -1,5 +1,5 @@
/*
-Copyright 2019 The Matrix.org Foundation C.I.C.
+Copyright 2019 - 2021 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -14,26 +14,42 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
+import {
+ IResultRoomEvents,
+ ISearchRequestBody,
+ ISearchResponse,
+ ISearchResult,
+ ISearchResults,
+ SearchOrderBy,
+} from "matrix-js-sdk/src/@types/search";
+import { IRoomEventFilter } from "matrix-js-sdk/src/filter";
+import { EventType } from "matrix-js-sdk/src/@types/event";
+
+import { ISearchArgs } from "./indexing/BaseEventIndexManager";
import EventIndexPeg from "./indexing/EventIndexPeg";
import { MatrixClientPeg } from "./MatrixClientPeg";
+import { SearchResult } from "matrix-js-sdk/src/models/search-result";
const SEARCH_LIMIT = 10;
-async function serverSideSearch(term, roomId = undefined) {
+async function serverSideSearch(
+ term: string,
+ roomId: string = undefined,
+): Promise<{ response: ISearchResponse, query: ISearchRequestBody }> {
const client = MatrixClientPeg.get();
- const filter = {
+ const filter: IRoomEventFilter = {
limit: SEARCH_LIMIT,
};
if (roomId !== undefined) filter.rooms = [roomId];
- const body = {
+ const body: ISearchRequestBody = {
search_categories: {
room_events: {
search_term: term,
filter: filter,
- order_by: "recent",
+ order_by: SearchOrderBy.Recent,
event_context: {
before_limit: 1,
after_limit: 1,
@@ -45,31 +61,26 @@ async function serverSideSearch(term, roomId = undefined) {
const response = await client.search({ body: body });
- const result = {
- response: response,
- query: body,
- };
-
- return result;
+ return { response, query: body };
}
-async function serverSideSearchProcess(term, roomId = undefined) {
+async function serverSideSearchProcess(term: string, roomId: string = undefined): Promise {
const client = MatrixClientPeg.get();
const result = await serverSideSearch(term, roomId);
// The js-sdk method backPaginateRoomEventsSearch() uses _query internally
- // so we're reusing the concept here since we wan't to delegate the
+ // so we're reusing the concept here since we want to delegate the
// pagination back to backPaginateRoomEventsSearch() in some cases.
- const searchResult = {
+ const searchResults: ISearchResults = {
_query: result.query,
results: [],
highlights: [],
};
- return client.processRoomEventsSearch(searchResult, result.response);
+ return client.processRoomEventsSearch(searchResults, result.response);
}
-function compareEvents(a, b) {
+function compareEvents(a: ISearchResult, b: ISearchResult): number {
const aEvent = a.result;
const bEvent = b.result;
@@ -79,7 +90,7 @@ function compareEvents(a, b) {
return 0;
}
-async function combinedSearch(searchTerm) {
+async function combinedSearch(searchTerm: string): Promise {
const client = MatrixClientPeg.get();
// Create two promises, one for the local search, one for the
@@ -111,10 +122,10 @@ async function combinedSearch(searchTerm) {
// returns since that one can be either a server-side one, a local one or a
// fake one to fetch the remaining cached events. See the docs for
// combineEvents() for an explanation why we need to cache events.
- const emptyResult = {
+ const emptyResult: ISeshatSearchResults = {
seshatQuery: localQuery,
_query: serverQuery,
- serverSideNextBatch: serverResponse.next_batch,
+ serverSideNextBatch: serverResponse.search_categories.room_events.next_batch,
cachedEvents: [],
oldestEventFrom: "server",
results: [],
@@ -125,7 +136,7 @@ async function combinedSearch(searchTerm) {
const combinedResult = combineResponses(emptyResult, localResponse, serverResponse.search_categories.room_events);
// Let the client process the combined result.
- const response = {
+ const response: ISearchResponse = {
search_categories: {
room_events: combinedResult,
},
@@ -139,10 +150,14 @@ async function combinedSearch(searchTerm) {
return result;
}
-async function localSearch(searchTerm, roomId = undefined, processResult = true) {
+async function localSearch(
+ searchTerm: string,
+ roomId: string = undefined,
+ processResult = true,
+): Promise<{ response: IResultRoomEvents, query: ISearchArgs }> {
const eventIndex = EventIndexPeg.get();
- const searchArgs = {
+ const searchArgs: ISearchArgs = {
search_term: searchTerm,
before_limit: 1,
after_limit: 1,
@@ -167,11 +182,18 @@ async function localSearch(searchTerm, roomId = undefined, processResult = true)
return result;
}
-async function localSearchProcess(searchTerm, roomId = undefined) {
+export interface ISeshatSearchResults extends ISearchResults {
+ seshatQuery?: ISearchArgs;
+ cachedEvents?: ISearchResult[];
+ oldestEventFrom?: "local" | "server";
+ serverSideNextBatch?: string;
+}
+
+async function localSearchProcess(searchTerm: string, roomId: string = undefined): Promise {
const emptyResult = {
results: [],
highlights: [],
- };
+ } as ISeshatSearchResults;
if (searchTerm === "") return emptyResult;
@@ -179,7 +201,7 @@ async function localSearchProcess(searchTerm, roomId = undefined) {
emptyResult.seshatQuery = result.query;
- const response = {
+ const response: ISearchResponse = {
search_categories: {
room_events: result.response,
},
@@ -192,7 +214,7 @@ async function localSearchProcess(searchTerm, roomId = undefined) {
return processedResult;
}
-async function localPagination(searchResult) {
+async function localPagination(searchResult: ISeshatSearchResults): Promise {
const eventIndex = EventIndexPeg.get();
const searchArgs = searchResult.seshatQuery;
@@ -221,10 +243,10 @@ async function localPagination(searchResult) {
return result;
}
-function compareOldestEvents(firstResults, secondResults) {
+function compareOldestEvents(firstResults: ISearchResult[], secondResults: ISearchResult[]): number {
try {
- const oldestFirstEvent = firstResults.results[firstResults.results.length - 1].result;
- const oldestSecondEvent = secondResults.results[secondResults.results.length - 1].result;
+ const oldestFirstEvent = firstResults[firstResults.length - 1].result;
+ const oldestSecondEvent = secondResults[secondResults.length - 1].result;
if (oldestFirstEvent.origin_server_ts <= oldestSecondEvent.origin_server_ts) {
return -1;
@@ -236,7 +258,12 @@ function compareOldestEvents(firstResults, secondResults) {
}
}
-function combineEventSources(previousSearchResult, response, a, b) {
+function combineEventSources(
+ previousSearchResult: ISeshatSearchResults,
+ response: IResultRoomEvents,
+ a: ISearchResult[],
+ b: ISearchResult[],
+): void {
// Merge event sources and sort the events.
const combinedEvents = a.concat(b).sort(compareEvents);
// Put half of the events in the response, and cache the other half.
@@ -353,8 +380,12 @@ function combineEventSources(previousSearchResult, response, a, b) {
* different event sources.
*
*/
-function combineEvents(previousSearchResult, localEvents = undefined, serverEvents = undefined) {
- const response = {};
+function combineEvents(
+ previousSearchResult: ISeshatSearchResults,
+ localEvents: IResultRoomEvents = undefined,
+ serverEvents: IResultRoomEvents = undefined,
+): IResultRoomEvents {
+ const response = {} as IResultRoomEvents;
const cachedEvents = previousSearchResult.cachedEvents;
let oldestEventFrom = previousSearchResult.oldestEventFrom;
@@ -364,7 +395,7 @@ function combineEvents(previousSearchResult, localEvents = undefined, serverEven
// This is a first search call, combine the events from the server and
// the local index. Note where our oldest event came from, we shall
// fetch the next batch of events from the other source.
- if (compareOldestEvents(localEvents, serverEvents) < 0) {
+ if (compareOldestEvents(localEvents.results, serverEvents.results) < 0) {
oldestEventFrom = "local";
}
@@ -375,7 +406,7 @@ function combineEvents(previousSearchResult, localEvents = undefined, serverEven
// meaning that our oldest event was on the server.
// Change the source of the oldest event if our local event is older
// than the cached one.
- if (compareOldestEvents(localEvents, cachedEvents) < 0) {
+ if (compareOldestEvents(localEvents.results, cachedEvents) < 0) {
oldestEventFrom = "local";
}
combineEventSources(previousSearchResult, response, localEvents.results, cachedEvents);
@@ -384,7 +415,7 @@ function combineEvents(previousSearchResult, localEvents = undefined, serverEven
// meaning that our oldest event was in the local index.
// Change the source of the oldest event if our server event is older
// than the cached one.
- if (compareOldestEvents(serverEvents, cachedEvents) < 0) {
+ if (compareOldestEvents(serverEvents.results, cachedEvents) < 0) {
oldestEventFrom = "server";
}
combineEventSources(previousSearchResult, response, serverEvents.results, cachedEvents);
@@ -412,7 +443,11 @@ function combineEvents(previousSearchResult, localEvents = undefined, serverEven
* @return {object} A response object that combines the events from the
* different event sources.
*/
-function combineResponses(previousSearchResult, localEvents = undefined, serverEvents = undefined) {
+function combineResponses(
+ previousSearchResult: ISeshatSearchResults,
+ localEvents: IResultRoomEvents = undefined,
+ serverEvents: IResultRoomEvents = undefined,
+): IResultRoomEvents {
// Combine our events first.
const response = combineEvents(previousSearchResult, localEvents, serverEvents);
@@ -454,42 +489,51 @@ function combineResponses(previousSearchResult, localEvents = undefined, serverE
return response;
}
-function restoreEncryptionInfo(searchResultSlice = []) {
+interface IEncryptedSeshatEvent {
+ curve25519Key: string;
+ ed25519Key: string;
+ algorithm: string;
+ forwardingCurve25519KeyChain: string[];
+}
+
+function restoreEncryptionInfo(searchResultSlice: SearchResult[] = []): void {
for (let i = 0; i < searchResultSlice.length; i++) {
const timeline = searchResultSlice[i].context.getTimeline();
for (let j = 0; j < timeline.length; j++) {
- const ev = timeline[j];
+ const mxEv = timeline[j];
+ const ev = mxEv.event as IEncryptedSeshatEvent;
- if (ev.event.curve25519Key) {
- ev.makeEncrypted(
- "m.room.encrypted",
- { algorithm: ev.event.algorithm },
- ev.event.curve25519Key,
- ev.event.ed25519Key,
+ if (ev.curve25519Key) {
+ mxEv.makeEncrypted(
+ EventType.RoomMessageEncrypted,
+ { algorithm: ev.algorithm },
+ ev.curve25519Key,
+ ev.ed25519Key,
);
- ev.forwardingCurve25519KeyChain = ev.event.forwardingCurve25519KeyChain;
+ // @ts-ignore
+ mxEv.forwardingCurve25519KeyChain = ev.forwardingCurve25519KeyChain;
- delete ev.event.curve25519Key;
- delete ev.event.ed25519Key;
- delete ev.event.algorithm;
- delete ev.event.forwardingCurve25519KeyChain;
+ delete ev.curve25519Key;
+ delete ev.ed25519Key;
+ delete ev.algorithm;
+ delete ev.forwardingCurve25519KeyChain;
}
}
}
}
-async function combinedPagination(searchResult) {
+async function combinedPagination(searchResult: ISeshatSearchResults): Promise {
const eventIndex = EventIndexPeg.get();
const client = MatrixClientPeg.get();
const searchArgs = searchResult.seshatQuery;
const oldestEventFrom = searchResult.oldestEventFrom;
- let localResult;
- let serverSideResult;
+ let localResult: IResultRoomEvents;
+ let serverSideResult: ISearchResponse;
- // Fetch events from the local index if we have a token for itand if it's
+ // Fetch events from the local index if we have a token for it and if it's
// the local indexes turn or the server has exhausted its results.
if (searchArgs.next_batch && (!searchResult.serverSideNextBatch || oldestEventFrom === "server")) {
localResult = await eventIndex.search(searchArgs);
@@ -502,7 +546,7 @@ async function combinedPagination(searchResult) {
serverSideResult = await client.search(body);
}
- let serverEvents;
+ let serverEvents: IResultRoomEvents;
if (serverSideResult) {
serverEvents = serverSideResult.search_categories.room_events;
@@ -532,8 +576,8 @@ async function combinedPagination(searchResult) {
return result;
}
-function eventIndexSearch(term, roomId = undefined) {
- let searchPromise;
+function eventIndexSearch(term: string, roomId: string = undefined): Promise {
+ let searchPromise: Promise;
if (roomId !== undefined) {
if (MatrixClientPeg.get().isRoomEncrypted(roomId)) {
@@ -554,7 +598,7 @@ function eventIndexSearch(term, roomId = undefined) {
return searchPromise;
}
-function eventIndexSearchPagination(searchResult) {
+function eventIndexSearchPagination(searchResult: ISeshatSearchResults): Promise {
const client = MatrixClientPeg.get();
const seshatQuery = searchResult.seshatQuery;
@@ -580,7 +624,7 @@ function eventIndexSearchPagination(searchResult) {
}
}
-export function searchPagination(searchResult) {
+export function searchPagination(searchResult: ISearchResults): Promise {
const eventIndex = EventIndexPeg.get();
const client = MatrixClientPeg.get();
@@ -590,7 +634,7 @@ export function searchPagination(searchResult) {
else return eventIndexSearchPagination(searchResult);
}
-export default function eventSearch(term, roomId = undefined) {
+export default function eventSearch(term: string, roomId: string = undefined): Promise {
const eventIndex = EventIndexPeg.get();
if (eventIndex === null) return serverSideSearchProcess(term, roomId);
diff --git a/src/SecurityManager.ts b/src/SecurityManager.ts
index 6b372bba28..370b21b396 100644
--- a/src/SecurityManager.ts
+++ b/src/SecurityManager.ts
@@ -14,7 +14,8 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
-import { ICryptoCallbacks, ISecretStorageKeyInfo } from 'matrix-js-sdk/src/matrix';
+import { ICryptoCallbacks } from 'matrix-js-sdk/src/matrix';
+import { ISecretStorageKeyInfo } from 'matrix-js-sdk/src/crypto/api';
import { MatrixClient } from 'matrix-js-sdk/src/client';
import Modal from './Modal';
import * as sdk from './index';
@@ -42,8 +43,8 @@ let secretStorageBeingAccessed = false;
let nonInteractive = false;
let dehydrationCache: {
- key?: Uint8Array,
- keyInfo?: ISecretStorageKeyInfo,
+ key?: Uint8Array;
+ keyInfo?: ISecretStorageKeyInfo;
} = {};
function isCachingAllowed(): boolean {
@@ -354,6 +355,7 @@ export async function accessSecretStorage(func = async () => { }, forceReset = f
throw new Error("Secret storage creation canceled");
}
} else {
+ // FIXME: Using an import will result in test failures
const InteractiveAuthDialog = sdk.getComponent("dialogs.InteractiveAuthDialog");
await cli.bootstrapCrossSigning({
authUploadDeviceSigningKeys: async (makeRequest) => {
diff --git a/src/SlashCommands.tsx b/src/SlashCommands.tsx
index 0f38c5fffc..9f5ac83a56 100644
--- a/src/SlashCommands.tsx
+++ b/src/SlashCommands.tsx
@@ -23,7 +23,6 @@ import { User } from "matrix-js-sdk/src/models/user";
import * as ContentHelpers from 'matrix-js-sdk/src/content-helpers';
import { MatrixClientPeg } from './MatrixClientPeg';
import dis from './dispatcher/dispatcher';
-import * as sdk from './index';
import { _t, _td } from './languageHandler';
import Modal from './Modal';
import MultiInviter from './utils/MultiInviter';
@@ -50,6 +49,12 @@ import { UIFeature } from "./settings/UIFeature";
import { CHAT_EFFECTS } from "./effects";
import CallHandler from "./CallHandler";
import { guessAndSetDMRoom } from "./Rooms";
+import UploadConfirmDialog from './components/views/dialogs/UploadConfirmDialog';
+import ErrorDialog from './components/views/dialogs/ErrorDialog';
+import DevtoolsDialog from './components/views/dialogs/DevtoolsDialog';
+import RoomUpgradeWarningDialog from "./components/views/dialogs/RoomUpgradeWarningDialog";
+import InfoDialog from "./components/views/dialogs/InfoDialog";
+import SlashCommandHelpDialog from "./components/views/dialogs/SlashCommandHelpDialog";
// XXX: workaround for https://github.com/microsoft/TypeScript/issues/31816
interface HTMLInputEvent extends Event {
@@ -63,7 +68,6 @@ const singleMxcUpload = async (): Promise => {
fileSelector.onchange = (ev: HTMLInputEvent) => {
const file = ev.target.files[0];
- const UploadConfirmDialog = sdk.getComponent("dialogs.UploadConfirmDialog");
Modal.createTrackedDialog('Upload Files confirmation', '', UploadConfirmDialog, {
file,
onFinished: (shouldContinue) => {
@@ -246,7 +250,6 @@ export const Commands = [
args: '',
description: _td('Searches DuckDuckGo for results'),
runFn: function() {
- const ErrorDialog = sdk.getComponent('dialogs.ErrorDialog');
// TODO Don't explain this away, actually show a search UI here.
Modal.createTrackedDialog('Slash Commands', '/ddg is not a command', ErrorDialog, {
title: _t('/ddg is not a command'),
@@ -269,8 +272,6 @@ export const Commands = [
return reject(_t("You do not have the required permissions to use this command."));
}
- const RoomUpgradeWarningDialog = sdk.getComponent("dialogs.RoomUpgradeWarningDialog");
-
const { finished } = Modal.createTrackedDialog('Slash Commands', 'upgrade room confirmation',
RoomUpgradeWarningDialog, { roomId: roomId, targetVersion: args }, /*className=*/null,
/*isPriority=*/false, /*isStatic=*/true);
@@ -314,7 +315,6 @@ export const Commands = [
if (checkForUpgradeFn) cli.removeListener('Room', checkForUpgradeFn);
- const ErrorDialog = sdk.getComponent('dialogs.ErrorDialog');
Modal.createTrackedDialog('Slash Commands', 'room upgrade error', ErrorDialog, {
title: _t('Error upgrading room'),
description: _t(
@@ -434,7 +434,6 @@ export const Commands = [
const topic = topicEvents && topicEvents.getContent().topic;
const topicHtml = topic ? linkifyAndSanitizeHtml(topic) : _t('This room has no topic.');
- const InfoDialog = sdk.getComponent('dialogs.InfoDialog');
Modal.createTrackedDialog('Slash Commands', 'Topic', InfoDialog, {
title: room.name,
description: ,
@@ -481,14 +480,14 @@ export const Commands = [
'Identity server',
QuestionDialog, {
title: _t("Use an identity server"),
- description:
{_t(
+ description:
{ _t(
"Use an identity server to invite by email. " +
"Click continue to use the default identity server " +
"(%(defaultIdentityServerName)s) or manage in Settings.",
{
defaultIdentityServerName: abbreviateUrl(defaultIdentityServerUrl),
},
- )}
,
+ ) },
button: _t("Continue"),
},
);
@@ -523,7 +522,7 @@ export const Commands = [
aliases: ['j', 'goto'],
args: '',
description: _td('Joins room with given address'),
- runFn: function(_, args) {
+ runFn: function(roomId, args) {
if (args) {
// Note: we support 2 versions of this command. The first is
// the public-facing one for most users and the other is a
@@ -737,7 +736,6 @@ export const Commands = [
ignoredUsers.push(userId); // de-duped internally in the js-sdk
return success(
cli.setIgnoredUsers(ignoredUsers).then(() => {
- const InfoDialog = sdk.getComponent('dialogs.InfoDialog');
Modal.createTrackedDialog('Slash Commands', 'User ignored', InfoDialog, {
title: _t('Ignored user'),
description:
- {_t("Add image (optional)")}
+ { _t("Add image (optional)") }
- {_t("An image will help people identify your community.")}
+ { _t("An image will help people identify your community.") }
- {_t("Save")}
+ { _t("Save") }
diff --git a/src/components/views/dialogs/ErrorDialog.tsx b/src/components/views/dialogs/ErrorDialog.tsx
index 0f675f0df7..56cd76237f 100644
--- a/src/components/views/dialogs/ErrorDialog.tsx
+++ b/src/components/views/dialogs/ErrorDialog.tsx
@@ -26,9 +26,9 @@ limitations under the License.
*/
import React from 'react';
-import * as sdk from '../../../index';
import { _t } from '../../../languageHandler';
import { replaceableComponent } from "../../../utils/replaceableComponent";
+import BaseDialog from "./BaseDialog";
interface IProps {
onFinished: (success: boolean) => void;
@@ -57,7 +57,6 @@ export default class ErrorDialog extends React.Component {
};
public render() {
- const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
return (
{
countlyFeedbackSection =
-
{_t("Rate %(brand)s", { brand })}
+
{ _t("Rate %(brand)s", { brand }) }
-
{_t("Tell us below how you feel about %(brand)s so far.", { brand })}
-
{_t("Please go into as much detail as you like, so we can track down the problem.")}
+
{ _t("Tell us below how you feel about %(brand)s so far.", { brand }) }
+
{ _t("Please go into as much detail as you like, so we can track down the problem.") }
{
let subheading;
if (hasFeedback) {
subheading = (
-
{_t("There are two ways you can provide feedback and help us improve %(brand)s.", { brand })}
+
{ _t("There are two ways you can provide feedback and help us improve %(brand)s.", { brand }) }
);
}
@@ -106,7 +106,7 @@ export default (props) => {
_t("PRO TIP: If you start a bug, please submit debug logs " +
"to help us track down the problem.", {}, {
debugLogsLink: sub => (
- {sub}
+ { sub }
),
})
}
@@ -121,7 +121,7 @@ export default (props) => {
{ subheading }
-
{_t("Report a bug")}
+
{ _t("Report a bug") }
{
_t("Please view existing bugs on Github first. " +
"No match? Start a new one.", {}, {
@@ -133,7 +133,7 @@ export default (props) => {
},
})
}
- {_t("Continuing temporarily allows the %(hostSignupBrand)s setup process to access your " +
+ { _t("Continuing temporarily allows the %(hostSignupBrand)s setup process to access your " +
"account to fetch verified email addresses. This data is not stored.", {
hostSignupBrand: this.config.brand,
- })}
+ }) }
{ _t(
"Verify this user to mark them as trusted. " +
"Trusting users gives you extra peace of mind when using " +
"end-to-end encrypted messages.",
- )}
,
-
{_t(
+ ) }
,
+
{ _t(
// NB. Below wording adjusted to singular 'session' until we have
// cross-signing
"Verifying this user will mark their session as trusted, and " +
"also mark your session as trusted to them.",
- )}
,
+ ) }
,
];
const selfDetailText = [
-
{_t(
+
{ _t(
"Verify this device to mark it as trusted. " +
"Trusting this device gives you and other users extra peace of mind when using " +
"end-to-end encrypted messages.",
- )}
,
-
{_t(
+ ) }
,
+
{ _t(
"Verifying this device will mark it as trusted, and users who have verified with " +
"you will trust this device.",
- )}
);
}
@@ -251,7 +251,7 @@ export default class IncomingSasDialog extends React.Component {
onFinished={this._onFinished}
fixedWidth={false}
>
- {body}
+ { body }
);
}
diff --git a/src/components/views/dialogs/IntegrationsDisabledDialog.js b/src/components/views/dialogs/IntegrationsDisabledDialog.js
index 1e2ff09196..6a5b2f08f9 100644
--- a/src/components/views/dialogs/IntegrationsDisabledDialog.js
+++ b/src/components/views/dialogs/IntegrationsDisabledDialog.js
@@ -49,7 +49,7 @@ export default class IntegrationsDisabledDialog extends React.Component {
title={_t("Integrations are disabled")}
>
-
{_t("Enable 'Manage Integrations' in Settings to do this.")}
+
{ _t("Enable 'Manage Integrations' in Settings to do this.") }
- {_t(
- "Your %(brand)s doesn't allow you to use an Integration Manager to do this. " +
+ { _t(
+ "Your %(brand)s doesn't allow you to use an integration manager to do this. " +
"Please contact an admin.",
{ brand },
- )}
+ ) }
- {body}
+ { body }
{
return (
- {avatar}
- {this.props.member.name}
+ { avatar }
+ { this.props.member.name }
{ closeButton }
@@ -252,20 +267,20 @@ class DMRoomTile extends React.PureComponent {
// Push any text we missed (first bit/middle of text)
if (ii > i) {
// Push any text we aren't highlighting (middle of text match, or beginning of text)
- result.push({str.substring(i, ii)});
+ result.push({ str.substring(i, ii) });
}
i = ii; // copy over ii only if we have a match (to preserve i for end-of-text matching)
// Highlight the word the user entered
const substr = str.substring(i, filterStr.length + i);
- result.push({substr});
+ result.push({ substr });
i += substr.length;
}
// Push any text we missed (end of text)
if (i < str.length) {
- result.push({str.substring(i)});
+ result.push({ str.substring(i) });
}
return result;
@@ -275,7 +290,7 @@ class DMRoomTile extends React.PureComponent {
let timestamp = null;
if (this.props.lastActiveTs) {
const humanTs = humanizeTime(this.props.lastActiveTs);
- timestamp = {humanTs};
+ timestamp = { humanTs };
}
const avatarSize = 36;
@@ -302,8 +317,8 @@ class DMRoomTile extends React.PureComponent {
// the browser from reloading the image source when the avatar remounts).
const stackedAvatar = (
- {avatar}
- {checkmark}
+ { avatar }
+ { checkmark }
);
@@ -313,12 +328,12 @@ class DMRoomTile extends React.PureComponent {
return (
- {stackedAvatar}
+ { stackedAvatar }
-
{this.highlightName(this.props.member.name)}
-
{caption}
+
{ this.highlightName(this.props.member.name) }
+
{ caption }
- {timestamp}
+ { timestamp }
);
}
@@ -330,16 +345,16 @@ interface IInviteDialogProps {
// The kind of invite being performed. Assumed to be KIND_DM if
// not provided.
- kind: string,
+ kind: string;
// The room ID this dialog is for. Only required for KIND_INVITE.
- roomId: string,
+ roomId: string;
// The call to transfer. Only required for KIND_CALL_TRANSFER.
- call: MatrixCall,
+ call: MatrixCall;
// Initial value to populate the filter with
- initialText: string,
+ initialText: string;
}
interface IInviteDialogState {
@@ -354,10 +369,12 @@ interface IInviteDialogState {
canUseIdentityServer: boolean;
tryingIdentityServer: boolean;
consultFirst: boolean;
+ dialPadValue: string;
+ currentTabId: TabId;
// These two flags are used for the 'Go' button to communicate what is going on.
- busy: boolean,
- errorText: string,
+ busy: boolean;
+ errorText: string;
}
@replaceableComponent("views.dialogs.InviteDialog")
@@ -368,7 +385,7 @@ export default class InviteDialog extends React.PureComponent void;
- private debounceTimer: NodeJS.Timeout = null; // actually number because we're in the browser
+ private debounceTimer: number = null; // actually number because we're in the browser
private editorRef = createRef();
private unmounted = false;
@@ -405,6 +422,8 @@ export default class InviteDialog extends React.PureComponent {
- this.convertFilter();
- const targets = this.convertFilter();
- const targetIds = targets.map(t => t.userId);
- if (targetIds.length > 1) {
- this.setState({
- errorText: _t("A call can only be transferred to a single user."),
- });
- }
-
- if (this.state.consultFirst) {
- const dmRoomId = await ensureDMExists(MatrixClientPeg.get(), targetIds[0]);
-
- dis.dispatch({
- action: 'place_call',
- type: this.props.call.type,
- room_id: dmRoomId,
- transferee: this.props.call,
- });
- dis.dispatch({
- action: 'view_room',
- room_id: dmRoomId,
- should_peek: false,
- joining: false,
- });
- this.props.onFinished();
- } else {
- this.setState({ busy: true });
- try {
- await this.props.call.transfer(targetIds[0]);
- this.setState({ busy: false });
- this.props.onFinished();
- } catch (e) {
+ if (this.state.currentTabId == TabId.UserDirectory) {
+ this.convertFilter();
+ const targets = this.convertFilter();
+ const targetIds = targets.map(t => t.userId);
+ if (targetIds.length > 1) {
this.setState({
- busy: false,
- errorText: _t("Failed to transfer call"),
+ errorText: _t("A call can only be transferred to a single user."),
});
+ return;
}
+
+ dis.dispatch({
+ action: Action.TransferCallToMatrixID,
+ call: this.props.call,
+ destination: targetIds[0],
+ consultFirst: this.state.consultFirst,
+ } as TransferCallPayload);
+ } else {
+ dis.dispatch({
+ action: Action.TransferCallToPhoneNumber,
+ call: this.props.call,
+ destination: this.state.dialPadValue,
+ consultFirst: this.state.consultFirst,
+ } as TransferCallPayload);
}
+ this.props.onFinished();
};
private onKeyDown = (e) => {
@@ -825,6 +832,10 @@ export default class InviteDialog extends React.PureComponent {
+ this.props.onFinished([]);
+ };
+
private updateSuggestions = async (term) => {
MatrixClientPeg.get().searchUserDirectory({ term }).then(async r => {
if (term !== this.state.filterText) {
@@ -960,11 +971,14 @@ export default class InviteDialog extends React.PureComponent {
if (!this.state.busy) {
let filterText = this.state.filterText;
- const targets = this.state.targets.map(t => t); // cheap clone for mutation
+ let targets = this.state.targets.map(t => t); // cheap clone for mutation
const idx = targets.indexOf(member);
if (idx >= 0) {
targets.splice(idx, 1);
} else {
+ if (this.props.kind === KIND_CALL_TRANSFER && targets.length > 0) {
+ targets = [];
+ }
targets.push(member);
filterText = ""; // clear the filter when the user accepts a suggestion
}
@@ -1046,7 +1060,6 @@ export default class InviteDialog extends React.PureComponent 0) {
- const QuestionDialog = sdk.getComponent('dialogs.QuestionDialog');
Modal.createTrackedDialog('Invite Paste Fail', '', QuestionDialog, {
title: _t('Failed to find the following users'),
description: _t(
@@ -1139,8 +1152,8 @@ export default class InviteDialog extends React.PureComponent
-
{ _t(
"Use an identity server to invite by email. " +
"Use the default (%(defaultIdentityServerName)s) " +
"or manage in Settings.",
@@ -1231,24 +1249,46 @@ export default class InviteDialog extends React.PureComponent{sub},
- settings: sub => {sub},
+ default: sub => { sub },
+ settings: sub => { sub },
},
- )}
+ ) }
);
} else {
return (
-
{_t(
+
{ _t(
"Use an identity server to invite by email. " +
"Manage in Settings.",
{}, {
- settings: sub => {sub},
+ settings: sub => { sub },
},
- )}
- {_t("Your homeserver doesn't seem to support this feature.")}
+ { _t("Your homeserver doesn't seem to support this feature.") }
);
} else if (error.errcode) {
// some kind of error from the homeserver
content = (
- {_t("Something went wrong!")}
+ { _t("Something went wrong!") }
);
} else {
content = (
- {_t("Cannot reach homeserver")}
+ { _t("Cannot reach homeserver") }
- {_t("Ensure you have a stable internet connection, or get in touch with the server admin")}
+ { _t("Ensure you have a stable internet connection, or get in touch with the server admin") }
);
}
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
@@ -170,7 +170,7 @@ export default class MessageEditHistoryDialog extends React.PureComponent {
onFinished={this.props.onFinished}
title={_t("Message edits")}
>
- {content}
+ { content }
);
}
diff --git a/src/components/views/dialogs/ModalWidgetDialog.tsx b/src/components/views/dialogs/ModalWidgetDialog.tsx
index 6bc84b66b4..1bf7eb7307 100644
--- a/src/components/views/dialogs/ModalWidgetDialog.tsx
+++ b/src/components/views/dialogs/ModalWidgetDialog.tsx
@@ -191,9 +191,9 @@ export default class ModalWidgetDialog extends React.PureComponent
- {_t("Data on this screen is shared with %(widgetDomain)s", {
+ { _t("Data on this screen is shared with %(widgetDomain)s", {
widgetDomain: parsed.hostname,
- })}
+ }) }
;
+ return
{ _t("Identity server") } ({ host })
;
case SERVICE_TYPES.IM:
- return
{_t("Integration Manager")} ({host})
;
+ return
{ _t("Integration manager") } ({ host })
;
}
}
@@ -99,13 +100,13 @@ export default class TermsDialog extends React.PureComponent
- {_t("Find others by phone or email")}
+ { _t("Find others by phone or email") }
- {_t("Be found by phone or email")}
+ { _t("Be found by phone or email") }
;
case SERVICE_TYPES.IM:
return
- {_t("Use bots, bridges, widgets and sticker packs")}
+ { _t("Use bots, bridges, widgets and sticker packs") }
- {buttons}
+ { buttons }
);
}
diff --git a/src/components/views/dialogs/UserSettingsDialog.tsx b/src/components/views/dialogs/UserSettingsDialog.tsx
index 2878a07d35..7608d7cb55 100644
--- a/src/components/views/dialogs/UserSettingsDialog.tsx
+++ b/src/components/views/dialogs/UserSettingsDialog.tsx
@@ -28,11 +28,11 @@ import PreferencesUserSettingsTab from "../settings/tabs/user/PreferencesUserSet
import VoiceUserSettingsTab from "../settings/tabs/user/VoiceUserSettingsTab";
import HelpUserSettingsTab from "../settings/tabs/user/HelpUserSettingsTab";
import FlairUserSettingsTab from "../settings/tabs/user/FlairUserSettingsTab";
-import * as sdk from "../../../index";
import SdkConfig from "../../../SdkConfig";
import MjolnirUserSettingsTab from "../settings/tabs/user/MjolnirUserSettingsTab";
import { UIFeature } from "../../../settings/UIFeature";
import { replaceableComponent } from "../../../utils/replaceableComponent";
+import BaseDialog from "./BaseDialog";
export enum UserTab {
General = "USER_GENERAL_TAB",
@@ -81,7 +81,7 @@ export default class UserSettingsDialog extends React.Component
this.setState({ mjolnirEnabled: newValue });
};
- _getTabs() {
+ private getTabs() {
const tabs = [];
tabs.push(new Tab(
@@ -162,8 +162,6 @@ export default class UserSettingsDialog extends React.Component
}
render() {
- const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
-
return (
title={_t("Settings")}
>
-
+
);
diff --git a/src/components/views/dialogs/VerificationRequestDialog.js b/src/components/views/dialogs/VerificationRequestDialog.tsx
similarity index 70%
rename from src/components/views/dialogs/VerificationRequestDialog.js
rename to src/components/views/dialogs/VerificationRequestDialog.tsx
index bf5d63b895..65b7f71dbd 100644
--- a/src/components/views/dialogs/VerificationRequestDialog.js
+++ b/src/components/views/dialogs/VerificationRequestDialog.tsx
@@ -1,5 +1,5 @@
/*
-Copyright 2020 The Matrix.org Foundation C.I.C.
+Copyright 2020-2021 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -15,27 +15,33 @@ limitations under the License.
*/
import React from 'react';
-import PropTypes from 'prop-types';
import { MatrixClientPeg } from '../../../MatrixClientPeg';
-import * as sdk from '../../../index';
import { _t } from '../../../languageHandler';
import { replaceableComponent } from "../../../utils/replaceableComponent";
+import { VerificationRequest } from "matrix-js-sdk/src/crypto/verification/request/VerificationRequest";
+import BaseDialog from "./BaseDialog";
+import EncryptionPanel from "../right_panel/EncryptionPanel";
+import { User } from 'matrix-js-sdk/src/models/user';
+
+interface IProps {
+ verificationRequest: VerificationRequest;
+ verificationRequestPromise: Promise;
+ onFinished: () => void;
+ member: User;
+}
+
+interface IState {
+ verificationRequest: VerificationRequest;
+}
@replaceableComponent("views.dialogs.VerificationRequestDialog")
-export default class VerificationRequestDialog extends React.Component {
- static propTypes = {
- verificationRequest: PropTypes.object,
- verificationRequestPromise: PropTypes.object,
- onFinished: PropTypes.func.isRequired,
- member: PropTypes.string,
- };
-
- constructor(...args) {
- super(...args);
- this.state = {};
- if (this.props.verificationRequest) {
- this.state.verificationRequest = this.props.verificationRequest;
- } else if (this.props.verificationRequestPromise) {
+export default class VerificationRequestDialog extends React.Component {
+ constructor(props) {
+ super(props);
+ this.state = {
+ verificationRequest: this.props.verificationRequest,
+ };
+ if (this.props.verificationRequestPromise) {
this.props.verificationRequestPromise.then(r => {
this.setState({ verificationRequest: r });
});
@@ -43,8 +49,6 @@ export default class VerificationRequestDialog extends React.Component {
}
render() {
- const BaseDialog = sdk.getComponent("views.dialogs.BaseDialog");
- const EncryptionPanel = sdk.getComponent("views.right_panel.EncryptionPanel");
const request = this.state.verificationRequest;
const otherUserId = request && request.otherUserId;
const member = this.props.member ||
@@ -65,6 +69,7 @@ export default class VerificationRequestDialog extends React.Component {
verificationRequestPromise={this.props.verificationRequestPromise}
onClose={this.props.onFinished}
member={member}
+ isRoomEncrypted={false}
/>
;
}
diff --git a/src/components/views/dialogs/WidgetCapabilitiesPromptDialog.tsx b/src/components/views/dialogs/WidgetCapabilitiesPromptDialog.tsx
index 638d5cde93..ebeab191b1 100644
--- a/src/components/views/dialogs/WidgetCapabilitiesPromptDialog.tsx
+++ b/src/components/views/dialogs/WidgetCapabilitiesPromptDialog.tsx
@@ -105,7 +105,7 @@ export default class WidgetCapabilitiesPromptDialog extends React.PureComponent<
const checkboxRows = Object.entries(this.state.booleanStates).map(([cap, isChecked], i) => {
const text = CapabilityText.for(cap, this.props.widgetKind);
const byline = text.byline
- ? {text.byline}
+ ? { text.byline }
: null;
return (
@@ -113,8 +113,8 @@ export default class WidgetCapabilitiesPromptDialog extends React.PureComponent<
this.onToggle(cap)}
- >{text.primary}
- {byline}
+ >{ text.primary }
+ { byline }
);
});
@@ -127,8 +127,8 @@ export default class WidgetCapabilitiesPromptDialog extends React.PureComponent<
>
-
{_t("This widget would like to:")}
- {checkboxRows}
+
{ _t("This widget would like to:") }
+ { checkboxRows }
- {_t("The widget will verify your user ID, but won't be able to perform actions for you:")}
+ { _t("The widget will verify your user ID, but won't be able to perform actions for you:") }
- {/* cheap trim to just get the path */}
- {this.props.widget.templateUrl.split("?")[0].split("#")[0]}
+ { /* cheap trim to just get the path */ }
+ { this.props.widget.templateUrl.split("?")[0].split("#")[0] }
- {_t("Forgotten or lost all recovery methods? Reset all", null, {
+ { _t("Forgotten or lost all recovery methods? Reset all", null, {
a: (sub) => {sub},
- })}
+ className="mx_AccessSecretStorageDialog_reset_link">{ sub },
+ }) }
);
diff --git a/src/components/views/dialogs/security/ConfirmDestroyCrossSigningDialog.tsx b/src/components/views/dialogs/security/ConfirmDestroyCrossSigningDialog.tsx
index 6272302a76..392598ca36 100644
--- a/src/components/views/dialogs/security/ConfirmDestroyCrossSigningDialog.tsx
+++ b/src/components/views/dialogs/security/ConfirmDestroyCrossSigningDialog.tsx
@@ -16,8 +16,9 @@ limitations under the License.
import React from 'react';
import { _t } from "../../../../languageHandler";
-import * as sdk from "../../../../index";
import { replaceableComponent } from "../../../../utils/replaceableComponent";
+import BaseDialog from "../BaseDialog";
+import DialogButtons from "../../elements/DialogButtons";
interface IProps {
onFinished: (success: boolean) => void;
@@ -34,9 +35,6 @@ export default class ConfirmDestroyCrossSigningDialog extends React.Component
- {_t(
+ { _t(
"Deleting cross-signing keys is permanent. " +
"Anyone you have verified with will see security alerts. " +
"You almost certainly don't want to do this, unless " +
"you've lost every device you can cross-sign from.",
- )}
+ ) }
;
};
diff --git a/src/components/views/elements/AccessibleButton.tsx b/src/components/views/elements/AccessibleButton.tsx
index 997bbcb9c2..8bb6341c3d 100644
--- a/src/components/views/elements/AccessibleButton.tsx
+++ b/src/components/views/elements/AccessibleButton.tsx
@@ -14,7 +14,7 @@
limitations under the License.
*/
-import React from 'react';
+import React, { ReactHTML } from 'react';
import { Key } from '../../../Keyboard';
import classnames from 'classnames';
@@ -29,7 +29,7 @@ export type ButtonEvent = React.MouseEvent | React.KeyboardEvent {
inputRef?: React.Ref;
- element?: string;
+ element?: keyof ReactHTML;
// The kind of button, similar to how Bootstrap works.
// See available classes for AccessibleButton for options.
kind?: string;
@@ -122,7 +122,7 @@ export default function AccessibleButton({
}
AccessibleButton.defaultProps = {
- element: 'div',
+ element: 'div' as keyof ReactHTML,
role: 'button',
tabIndex: 0,
};
diff --git a/src/components/views/elements/AppPermission.js b/src/components/views/elements/AppPermission.js
index 152d3c6b95..a7d249164b 100644
--- a/src/components/views/elements/AppPermission.js
+++ b/src/components/views/elements/AppPermission.js
@@ -94,15 +94,15 @@ export default class AppPermission extends React.Component {
const warningTooltipText = (
- {_t("Any of the following data may be shared:")}
+ { _t("Any of the following data may be shared:") }
-
{_t("Your display name")}
-
{_t("Your avatar URL")}
-
{_t("Your user ID")}
-
{_t("Your theme")}
-
{_t("%(brand)s URL", { brand })}
-
{_t("Room ID")}
-
{_t("Widget ID")}
+
{ _t("Your display name") }
+
{ _t("Your avatar URL") }
+
{ _t("Your user ID") }
+
{ _t("Your theme") }
+
{ _t("%(brand)s URL", { brand }) }
+
{ _t("Room ID") }
+
{ _t("Widget ID") }
);
@@ -114,7 +114,7 @@ export default class AppPermission extends React.Component {
// Due to i18n limitations, we can't dedupe the code for variables in these two messages.
const warning = this.state.isWrapped
- ? _t("Using this widget may share data with %(widgetDomain)s & your Integration Manager.",
+ ? _t("Using this widget may share data with %(widgetDomain)s & your integration manager.",
{ widgetDomain: this.state.widgetDomain }, { helpIcon: () => warningTooltip })
: _t("Using this widget may share data with %(widgetDomain)s.",
{ widgetDomain: this.state.widgetDomain }, { helpIcon: () => warningTooltip });
@@ -124,22 +124,22 @@ export default class AppPermission extends React.Component {
return (
diff --git a/src/components/views/elements/DialPadBackspaceButton.tsx b/src/components/views/elements/DialPadBackspaceButton.tsx
new file mode 100644
index 0000000000..69f0fcb39a
--- /dev/null
+++ b/src/components/views/elements/DialPadBackspaceButton.tsx
@@ -0,0 +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 * as React from "react";
+import AccessibleButton from "./AccessibleButton";
+
+interface IProps {
+ // Callback for when the button is pressed
+ onBackspacePress: () => void;
+}
+
+export default class DialPadBackspaceButton extends React.PureComponent {
+ render() {
+ return
+
+
;
+ }
+}
diff --git a/src/components/views/elements/DialogButtons.js b/src/components/views/elements/DialogButtons.js
index af68260563..9dd4a84b9a 100644
--- a/src/components/views/elements/DialogButtons.js
+++ b/src/components/views/elements/DialogButtons.js
@@ -92,7 +92,7 @@ export default class DialogButtons extends React.Component {
let additive = null;
if (this.props.additive) {
- additive =
{ _t(
"Please create a new issue " +
"on GitHub so that we can investigate this bug.", {}, {
newIssueLink: (sub) => {
return { sub };
},
},
- )}
-
{_t(
+ ) }
+
{ _t(
"If you've submitted a bug via GitHub, debug logs can help " +
"us track down the problem. Debug logs contain application " +
"usage data including your username, the IDs or aliases of " +
"the rooms or groups you have visited and the usernames of " +
"other users. They do not contain messages.",
- )}
{ bugReportSection }
- {_t("Clear cache and reload")}
+ { _t("Clear cache and reload") }
;
diff --git a/src/components/views/elements/EventListSummary.tsx b/src/components/views/elements/EventListSummary.tsx
index ab647db9ed..b1cc9c773d 100644
--- a/src/components/views/elements/EventListSummary.tsx
+++ b/src/components/views/elements/EventListSummary.tsx
@@ -29,7 +29,7 @@ interface IProps {
// The minimum number of events needed to trigger summarisation
threshold?: number;
// Whether or not to begin with state.expanded=true
- startExpanded?: boolean,
+ startExpanded?: boolean;
// The list of room members for which to show avatars next to the summary
summaryMembers?: RoomMember[];
// The text to show as the summary of this event list
@@ -63,7 +63,7 @@ const EventListSummary: React.FC = ({
// If we are only given few events then just pass them through
if (events.length < threshold) {
return (
-
;
}
}
diff --git a/src/components/views/elements/ImageView.tsx b/src/components/views/elements/ImageView.tsx
index 74538d2fa9..954c1ab783 100644
--- a/src/components/views/elements/ImageView.tsx
+++ b/src/components/views/elements/ImageView.tsx
@@ -24,7 +24,7 @@ import FocusLock from "react-focus-lock";
import MemberAvatar from "../avatars/MemberAvatar";
import { ContextMenuTooltipButton } from "../../../accessibility/context_menu/ContextMenuTooltipButton";
import MessageContextMenu from "../context_menus/MessageContextMenu";
-import { aboveLeftOf, ContextMenu } from '../../structures/ContextMenu';
+import { aboveLeftOf } from '../../structures/ContextMenu';
import MessageTimestamp from "../messages/MessageTimestamp";
import SettingsStore from "../../../settings/SettingsStore";
import { formatFullDate } from "../../../DateUtils";
@@ -33,6 +33,7 @@ import { replaceableComponent } from "../../../utils/replaceableComponent";
import { RoomPermalinkCreator } from "../../../utils/permalinks/Permalinks";
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
import { normalizeWheelEvent } from "../../../utils/Mouse";
+import { IDialogProps } from '../dialogs/IDialogProps';
// Max scale to keep gaps around the image
const MAX_SCALE = 0.95;
@@ -43,32 +44,31 @@ const ZOOM_COEFFICIENT = 0.0025;
// If we have moved only this much we can zoom
const ZOOM_DISTANCE = 10;
-interface IProps {
- src: string, // the source of the image being displayed
- name?: string, // the main title ('name') for the image
- link?: string, // the link (if any) applied to the name of the image
- width?: number, // width of the image src in pixels
- height?: number, // height of the image src in pixels
- fileSize?: number, // size of the image src in bytes
- onFinished(): void, // callback when the lightbox is dismissed
+interface IProps extends IDialogProps {
+ src: string; // the source of the image being displayed
+ name?: string; // the main title ('name') for the image
+ link?: string; // the link (if any) applied to the name of the image
+ width?: number; // width of the image src in pixels
+ height?: number; // height of the image src in pixels
+ fileSize?: number; // size of the image src in bytes
// the event (if any) that the Image is displaying. Used for event-specific stuff like
// redactions, senders, timestamps etc. Other descriptors are taken from the explicit
// properties above, which let us use lightboxes to display images which aren't associated
// with events.
- mxEvent: MatrixEvent,
- permalinkCreator: RoomPermalinkCreator,
+ mxEvent: MatrixEvent;
+ permalinkCreator: RoomPermalinkCreator;
}
interface IState {
- zoom: number,
- minZoom: number,
- maxZoom: number,
- rotation: number,
- translationX: number,
- translationY: number,
- moving: boolean,
- contextMenuDisplayed: boolean,
+ zoom: number;
+ minZoom: number;
+ maxZoom: number;
+ rotation: number;
+ translationX: number;
+ translationY: number;
+ moving: boolean;
+ contextMenuDisplayed: boolean;
}
@replaceableComponent("views.elements.ImageView")
@@ -122,7 +122,7 @@ export default class ImageView extends React.Component {
const image = this.image.current;
const imageWrapper = this.imageWrapper.current;
- const rotation = inputRotation || this.state.rotation;
+ const rotation = inputRotation ?? this.state.rotation;
const imageIsNotFlipped = rotation % 180 === 0;
@@ -304,17 +304,13 @@ export default class ImageView extends React.Component {
let contextMenu = null;
if (this.state.contextMenuDisplayed) {
contextMenu = (
-
-
-
+ onCloseDialog={this.props.onFinished}
+ />
);
}
@@ -456,28 +452,28 @@ export default class ImageView extends React.Component {
;
diff --git a/src/components/views/elements/PowerSelector.js b/src/components/views/elements/PowerSelector.js
index ef449df295..016e7ddea5 100644
--- a/src/components/views/elements/PowerSelector.js
+++ b/src/components/views/elements/PowerSelector.js
@@ -161,7 +161,7 @@ export default class PowerSelector extends React.Component {
label={label} onChange={this.onSelectChange}
value={String(this.state.selectValue)} disabled={this.props.disabled}
>
- {options}
+ { options }
);
}
diff --git a/src/components/views/elements/ReplyThread.js b/src/components/views/elements/ReplyThread.tsx
similarity index 73%
rename from src/components/views/elements/ReplyThread.js
rename to src/components/views/elements/ReplyThread.tsx
index aea447c9b1..0eb795e257 100644
--- a/src/components/views/elements/ReplyThread.js
+++ b/src/components/views/elements/ReplyThread.tsx
@@ -1,7 +1,6 @@
/*
-Copyright 2017 New Vector Ltd
+Copyright 2017 - 2021 The Matrix.org Foundation C.I.C.
Copyright 2019 Michael Telatynski <7t3chguy@gmail.com>
-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.
@@ -15,71 +14,72 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
+
import React from 'react';
-import * as sdk from '../../../index';
import { _t } from '../../../languageHandler';
-import PropTypes from 'prop-types';
import dis from '../../../dispatcher/dispatcher';
-import { wantsDateSeparator } from '../../../DateUtils';
import { MatrixEvent } from 'matrix-js-sdk/src/models/event';
import { makeUserPermalink, RoomPermalinkCreator } from "../../../utils/permalinks/Permalinks";
import SettingsStore from "../../../settings/SettingsStore";
-import { LayoutPropType } from "../../../settings/Layout";
+import { Layout } from "../../../settings/Layout";
import escapeHtml from "escape-html";
import MatrixClientContext from "../../../contexts/MatrixClientContext";
+import { getUserNameColorClass } from "../../../utils/FormattingUtils";
import { Action } from "../../../dispatcher/actions";
import sanitizeHtml from "sanitize-html";
-import { UIFeature } from "../../../settings/UIFeature";
import { PERMITTED_URL_SCHEMES } from "../../../HtmlUtils";
import { replaceableComponent } from "../../../utils/replaceableComponent";
+import Spinner from './Spinner';
+import ReplyTile from "../rooms/ReplyTile";
+import Pill from './Pill';
+import { Room } from 'matrix-js-sdk/src/models/room';
+
+interface IProps {
+ // the latest event in this chain of replies
+ parentEv?: MatrixEvent;
+ // called when the ReplyThread contents has changed, including EventTiles thereof
+ onHeightChanged: () => void;
+ permalinkCreator: RoomPermalinkCreator;
+ // Specifies which layout to use.
+ layout?: Layout;
+ // Whether to always show a timestamp
+ alwaysShowTimestamps?: boolean;
+}
+
+interface IState {
+ // The loaded events to be rendered as linear-replies
+ events: MatrixEvent[];
+ // The latest loaded event which has not yet been shown
+ loadedEv: MatrixEvent;
+ // Whether the component is still loading more events
+ loading: boolean;
+ // Whether as error was encountered fetching a replied to event.
+ err: boolean;
+}
// This component does no cycle detection, simply because the only way to make such a cycle would be to
// craft event_id's, using a homeserver that generates predictable event IDs; even then the impact would
// be low as each event being loaded (after the first) is triggered by an explicit user action.
@replaceableComponent("views.elements.ReplyThread")
-export default class ReplyThread extends React.Component {
- static propTypes = {
- // the latest event in this chain of replies
- parentEv: PropTypes.instanceOf(MatrixEvent),
- // called when the ReplyThread contents has changed, including EventTiles thereof
- onHeightChanged: PropTypes.func.isRequired,
- permalinkCreator: PropTypes.instanceOf(RoomPermalinkCreator).isRequired,
- // Specifies which layout to use.
- layout: LayoutPropType,
- // Whether to always show a timestamp
- alwaysShowTimestamps: PropTypes.bool,
- };
-
+export default class ReplyThread extends React.Component {
static contextType = MatrixClientContext;
+ private unmounted = false;
+ private room: Room;
constructor(props, context) {
super(props, context);
this.state = {
- // The loaded events to be rendered as linear-replies
events: [],
-
- // The latest loaded event which has not yet been shown
loadedEv: null,
- // Whether the component is still loading more events
loading: true,
-
- // Whether as error was encountered fetching a replied to event.
err: false,
};
- this.unmounted = false;
- this.context.on("Event.replaced", this.onEventReplaced);
this.room = this.context.getRoom(this.props.parentEv.getRoomId());
- this.room.on("Room.redaction", this.onRoomRedaction);
- this.room.on("Room.redactionCancelled", this.onRoomRedaction);
-
- this.onQuoteClick = this.onQuoteClick.bind(this);
- this.canCollapse = this.canCollapse.bind(this);
- this.collapse = this.collapse.bind(this);
}
- static getParentEventId(ev) {
+ public static getParentEventId(ev: MatrixEvent): string {
if (!ev || ev.isRedacted()) return;
// XXX: For newer relations (annotations, replacements, etc.), we now
@@ -95,7 +95,7 @@ export default class ReplyThread extends React.Component {
}
// Part of Replies fallback support
- static stripPlainReply(body) {
+ public static stripPlainReply(body: string): string {
// Removes lines beginning with `> ` until you reach one that doesn't.
const lines = body.split('\n');
while (lines.length && lines[0].startsWith('> ')) lines.shift();
@@ -105,7 +105,7 @@ export default class ReplyThread extends React.Component {
}
// Part of Replies fallback support
- static stripHTMLReply(html) {
+ public static stripHTMLReply(html: string): string {
// Sanitize the original HTML for inclusion in . We allow
// any HTML, since the original sender could use special tags that we
// don't recognize, but want to pass along to any recipients who do
@@ -127,7 +127,10 @@ export default class ReplyThread extends React.Component {
}
// Part of Replies fallback support
- static getNestedReplyText(ev, permalinkCreator) {
+ public static getNestedReplyText(
+ ev: MatrixEvent,
+ permalinkCreator: RoomPermalinkCreator,
+ ): { body: string, html: string } {
if (!ev) return null;
let { body, formatted_body: html } = ev.getContent();
@@ -203,7 +206,7 @@ export default class ReplyThread extends React.Component {
return { body, html };
}
- static makeReplyMixIn(ev) {
+ public static makeReplyMixIn(ev: MatrixEvent) {
if (!ev) return {};
return {
'm.relates_to': {
@@ -214,10 +217,15 @@ export default class ReplyThread extends React.Component {
};
}
- static makeThread(parentEv, onHeightChanged, permalinkCreator, ref, layout, alwaysShowTimestamps) {
- if (!ReplyThread.getParentEventId(parentEv)) {
- return null;
- }
+ public static makeThread(
+ parentEv: MatrixEvent,
+ onHeightChanged: () => void,
+ permalinkCreator: RoomPermalinkCreator,
+ ref: React.RefObject,
+ layout: Layout,
+ alwaysShowTimestamps: boolean,
+ ): JSX.Element {
+ if (!ReplyThread.getParentEventId(parentEv)) return null;
return {
- if (this.state.events.some(event => event.getId() === eventId)) {
- this.forceUpdate();
- }
- };
-
- onEventReplaced = (ev) => {
- if (this.unmounted) return;
-
- // If one of the events we are rendering gets replaced, force a re-render
- this.updateForEventId(ev.getId());
- };
-
- onRoomRedaction = (ev) => {
- if (this.unmounted) return;
-
- const eventId = ev.getAssociatedId();
- if (!eventId) return;
-
- // If one of the events we are rendering gets redacted, force a re-render
- this.updateForEventId(eventId);
- };
-
- async initialize() {
+ private async initialize(): Promise {
const { parentEv } = this.props;
// at time of making this component we checked that props.parentEv has a parentEventId
const ev = await this.getEvent(ReplyThread.getParentEventId(parentEv));
@@ -287,7 +267,7 @@ export default class ReplyThread extends React.Component {
}
}
- async getNextEvent(ev) {
+ private async getNextEvent(ev: MatrixEvent): Promise {
try {
const inReplyToEventId = ReplyThread.getParentEventId(ev);
return await this.getEvent(inReplyToEventId);
@@ -296,7 +276,7 @@ export default class ReplyThread extends React.Component {
}
}
- async getEvent(eventId) {
+ private async getEvent(eventId: string): Promise {
if (!eventId) return null;
const event = this.room.findEventById(eventId);
if (event) return event;
@@ -313,15 +293,15 @@ export default class ReplyThread extends React.Component {
return this.room.findEventById(eventId);
}
- canCollapse() {
+ public canCollapse = (): boolean => {
return this.state.events.length > 1;
- }
+ };
- collapse() {
+ public collapse = (): void => {
this.initialize();
- }
+ };
- async onQuoteClick() {
+ private onQuoteClick = async (): Promise => {
const events = [this.state.loadedEv, ...this.state.events];
let loadedEv = null;
@@ -334,7 +314,11 @@ export default class ReplyThread extends React.Component {
events,
});
- dis.fire(Action.FocusComposer);
+ dis.fire(Action.FocusSendMessageComposer);
+ };
+
+ private getReplyThreadColorClass(ev: MatrixEvent): string {
+ return getUserNameColorClass(ev.getSender()).replace("Username", "ReplyThread");
}
render() {
@@ -349,9 +333,8 @@ export default class ReplyThread extends React.Component {
;
} else if (this.state.loadedEv) {
const ev = this.state.loadedEv;
- const Pill = sdk.getComponent('elements.Pill');
const room = this.context.getRoom(ev.getRoomId());
- header =
;
@@ -139,7 +139,7 @@ class Dot extends React.PureComponent {
- {this.props.label}
+ { this.props.label }
;
diff --git a/src/components/views/elements/SpellCheckLanguagesDropdown.tsx b/src/components/views/elements/SpellCheckLanguagesDropdown.tsx
index d10a599d95..972dac909a 100644
--- a/src/components/views/elements/SpellCheckLanguagesDropdown.tsx
+++ b/src/components/views/elements/SpellCheckLanguagesDropdown.tsx
@@ -17,11 +17,11 @@ limitations under the License.
import React from 'react';
import Dropdown from "../../views/elements/Dropdown";
-import * as sdk from '../../../index';
import PlatformPeg from "../../../PlatformPeg";
import SettingsStore from "../../../settings/SettingsStore";
import { _t } from "../../../languageHandler";
import { replaceableComponent } from "../../../utils/replaceableComponent";
+import Spinner from "./Spinner";
function languageMatchesSearchQuery(query, language) {
if (language.label.toUpperCase().includes(query.toUpperCase())) return true;
@@ -30,14 +30,14 @@ function languageMatchesSearchQuery(query, language) {
}
interface SpellCheckLanguagesDropdownIProps {
- className: string,
- value: string,
- onOptionChange(language: string),
+ className: string;
+ value: string;
+ onOptionChange(language: string);
}
interface SpellCheckLanguagesDropdownIState {
- searchQuery: string,
- languages: any,
+ searchQuery: string;
+ languages: any;
}
@replaceableComponent("views.elements.SpellCheckLanguagesDropdown")
@@ -45,7 +45,7 @@ export default class SpellCheckLanguagesDropdown extends React.Component {
constructor(props) {
super(props);
- this._onSearchChange = this._onSearchChange.bind(this);
+ this.onSearchChange = this.onSearchChange.bind(this);
this.state = {
searchQuery: '',
@@ -76,15 +76,12 @@ export default class SpellCheckLanguagesDropdown extends React.Component;
}
@@ -118,7 +115,7 @@ export default class SpellCheckLanguagesDropdown extends React.Component
diff --git a/src/components/views/elements/Spinner.js b/src/components/views/elements/Spinner.js
deleted file mode 100644
index 75f85d0441..0000000000
--- a/src/components/views/elements/Spinner.js
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
-Copyright 2015, 2016 OpenMarket Ltd
-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 PropTypes from "prop-types";
-import { _t } from "../../../languageHandler";
-
-const Spinner = ({ w = 32, h = 32, message }) => (
-
- { message &&
{ message }
}
-
-
-);
-
-Spinner.propTypes = {
- w: PropTypes.number,
- h: PropTypes.number,
- message: PropTypes.node,
-};
-
-export default Spinner;
diff --git a/src/components/views/elements/Spinner.tsx b/src/components/views/elements/Spinner.tsx
new file mode 100644
index 0000000000..ee43a5bf0e
--- /dev/null
+++ b/src/components/views/elements/Spinner.tsx
@@ -0,0 +1,45 @@
+/*
+Copyright 2015-2021 The Matrix.org Foundation C.I.C.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+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 { _t } from "../../../languageHandler";
+
+interface IProps {
+ w?: number;
+ h?: number;
+ message?: string;
+}
+
+export default class Spinner extends React.PureComponent {
+ public static defaultProps: Partial = {
+ w: 32,
+ h: 32,
+ };
+
+ public render() {
+ const { w, h, message } = this.props;
+ return (
+
+ { message &&
{ message }
}
+
+
+ );
+ }
+}
diff --git a/src/components/views/elements/Spoiler.js b/src/components/views/elements/Spoiler.js
index 56c18c6e33..802c6cf841 100644
--- a/src/components/views/elements/Spoiler.js
+++ b/src/components/views/elements/Spoiler.js
@@ -37,7 +37,7 @@ export default class Spoiler extends React.Component {
render() {
const reason = this.props.reason ? (
- {"(" + this.props.reason + ")"}
+ { "(" + this.props.reason + ")" }
) : null;
// react doesn't allow appending a DOM node as child.
// as such, we pass the this.props.contentHtml instead and then set the raw
diff --git a/src/components/views/elements/StyledCheckbox.tsx b/src/components/views/elements/StyledCheckbox.tsx
index 366cc2f1f7..b609f7159e 100644
--- a/src/components/views/elements/StyledCheckbox.tsx
+++ b/src/components/views/elements/StyledCheckbox.tsx
@@ -44,7 +44,7 @@ export default class StyledCheckbox extends React.PureComponent
return
diff --git a/src/components/views/settings/SecureBackupPanel.js b/src/components/views/settings/SecureBackupPanel.js
index b0292debe6..d473708ce1 100644
--- a/src/components/views/settings/SecureBackupPanel.js
+++ b/src/components/views/settings/SecureBackupPanel.js
@@ -221,7 +221,7 @@ export default class SecureBackupPanel extends React.PureComponent {
if (error) {
statusDescription = (
- {_t("Unable to load key backup status")}
+ { _t("Unable to load key backup status") }
);
} else if (loading) {
@@ -230,19 +230,19 @@ export default class SecureBackupPanel extends React.PureComponent {
let restoreButtonCaption = _t("Restore from Backup");
if (MatrixClientPeg.get().getKeyBackupEnabled()) {
- statusDescription =
✅ {_t("This session is backing up your keys. ")}
;
+ statusDescription =
✅ { _t("This session is backing up your keys. ") }
;
} else {
statusDescription = <>
-
{_t(
+
{ _t(
"This session is not backing up your keys, " +
"but you do have an existing backup you can restore from " +
"and add to going forward.", {},
- { b: sub => {sub} },
- )}
-
{_t(
+ { b: sub => { sub } },
+ ) }
+
{ _t(
"Connect this session to key backup before signing out to avoid " +
"losing any keys that may only be on this session.",
- )}
+ ) }
>;
restoreButtonCaption = _t("Connect this session to Key Backup");
}
@@ -253,11 +253,11 @@ export default class SecureBackupPanel extends React.PureComponent {
uploadStatus = "";
} else if (sessionsRemaining > 0) {
uploadStatus =
- {_t("Backing up %(sessionsRemaining)s keys...", { sessionsRemaining })}
+ { _t("Backing up %(sessionsRemaining)s keys...", { sessionsRemaining }) }
{ _t(
"Back up your encryption keys with your account data in case you " +
"lose access to your sessions. Your keys will be secured with a " +
"unique Security Key.",
- )}
);
}
diff --git a/src/components/views/settings/SetIdServer.tsx b/src/components/views/settings/SetIdServer.tsx
index 4a73c82d9b..fd8abc0dbe 100644
--- a/src/components/views/settings/SetIdServer.tsx
+++ b/src/components/views/settings/SetIdServer.tsx
@@ -17,7 +17,6 @@ limitations under the License.
import url from 'url';
import React from 'react';
import { _t } from "../../../languageHandler";
-import * as sdk from '../../../index';
import { MatrixClientPeg } from "../../../MatrixClientPeg";
import Modal from '../../../Modal';
import dis from "../../../dispatcher/dispatcher";
@@ -28,6 +27,10 @@ import { getDefaultIdentityServerUrl, doesIdentityServerHaveTerms } from '../../
import { timeout } from "../../../utils/promise";
import { replaceableComponent } from "../../../utils/replaceableComponent";
import { ActionPayload } from '../../../dispatcher/payloads';
+import InlineSpinner from '../elements/InlineSpinner';
+import AccessibleButton from '../elements/AccessibleButton';
+import Field from '../elements/Field';
+import QuestionDialog from "../dialogs/QuestionDialog";
// We'll wait up to this long when checking for 3PID bindings on the IS.
const REACHABILITY_TIMEOUT = 10000; // ms
@@ -41,7 +44,7 @@ const REACHABILITY_TIMEOUT = 10000; // ms
async function checkIdentityServerUrl(u) {
const parsedUrl = url.parse(u);
- if (parsedUrl.protocol !== 'https:') return _t("Identity Server URL must be HTTPS");
+ if (parsedUrl.protocol !== 'https:') return _t("Identity server URL must be HTTPS");
// XXX: duplicated logic from js-sdk but it's quite tied up in the validation logic in the
// js-sdk so probably as easy to duplicate it than to separate it out so we can reuse it
@@ -50,17 +53,17 @@ async function checkIdentityServerUrl(u) {
if (response.ok) {
return null;
} else if (response.status < 200 || response.status >= 300) {
- return _t("Not a valid Identity Server (status code %(code)s)", { code: response.status });
+ return _t("Not a valid identity server (status code %(code)s)", { code: response.status });
} else {
- return _t("Could not connect to Identity Server");
+ return _t("Could not connect to identity server");
}
} catch (e) {
- return _t("Could not connect to Identity Server");
+ return _t("Could not connect to identity server");
}
}
interface IProps {
- // Whether or not the ID server is missing terms. This affects the text
+ // Whether or not the identity server is missing terms. This affects the text
// shown to the user.
missingTerms: boolean;
}
@@ -84,7 +87,7 @@ export default class SetIdServer extends React.Component {
let defaultIdServer = '';
if (!MatrixClientPeg.get().getIdentityServerUrl() && getDefaultIdentityServerUrl()) {
- // If no ID server is configured but there's one in the config, prepopulate
+ // If no identity server is configured but there's one in the config, prepopulate
// the field to help the user.
defaultIdServer = abbreviateUrl(getDefaultIdentityServerUrl());
}
@@ -109,7 +112,7 @@ export default class SetIdServer extends React.Component {
}
private onAction = (payload: ActionPayload) => {
- // We react to changes in the ID server in the event the user is staring at this form
+ // We react to changes in the identity server in the event the user is staring at this form
// when changing their identity server on another device.
if (payload.action !== "id_server_changed") return;
@@ -126,13 +129,12 @@ export default class SetIdServer extends React.Component {
private getTooltip = () => {
if (this.state.checking) {
- const InlineSpinner = sdk.getComponent('views.elements.InlineSpinner');
return
{ _t("Checking server") }
;
} else if (this.state.error) {
- return {this.state.error};
+ return { this.state.error };
} else {
return null;
}
@@ -191,8 +193,8 @@ export default class SetIdServer extends React.Component {
"Disconnect from the identity server and " +
"connect to instead?", {},
{
- current: sub => {abbreviateUrl(currentClientIdServer)},
- new: sub => {abbreviateUrl(idServer)},
+ current: sub => { abbreviateUrl(currentClientIdServer) },
+ new: sub => { abbreviateUrl(idServer) },
},
),
button: _t("Continue"),
@@ -217,16 +219,15 @@ export default class SetIdServer extends React.Component {
};
private showNoTermsWarning(fullUrl) {
- const QuestionDialog = sdk.getComponent("views.dialogs.QuestionDialog");
const { finished } = Modal.createTrackedDialog('No Terms Warning', '', QuestionDialog, {
title: _t("Identity server has no terms of service"),
description: (
- {_t("The identity server you have chosen does not have any terms of service.")}
+ { _t("The identity server you have chosen does not have any terms of service.") }
- {_t("Only continue if you trust the owner of the server.")}
+ { _t("Only continue if you trust the owner of the server.") }
),
@@ -242,7 +243,7 @@ export default class SetIdServer extends React.Component {
title: _t("Disconnect identity server"),
unboundMessage: _t(
"Disconnect from the identity server ?", {},
- { idserver: sub => {abbreviateUrl(this.state.currentClientIdServer)} },
+ { idserver: sub => { abbreviateUrl(this.state.currentClientIdServer) } },
),
button: _t("Disconnect"),
});
@@ -277,41 +278,41 @@ export default class SetIdServer extends React.Component {
let message;
let danger = false;
const messageElements = {
- idserver: sub => {abbreviateUrl(currentClientIdServer)},
- b: sub => {sub},
+ idserver: sub => { abbreviateUrl(currentClientIdServer) },
+ b: sub => { sub },
};
if (!currentServerReachable) {
message =
-
{_t(
+
{ _t(
"You should remove your personal data from identity server " +
" before disconnecting. Unfortunately, identity server " +
" is currently offline or cannot be reached.",
{}, messageElements,
- )}
-
{_t("You should:")}
+ ) }
+
{ _t("You should:") }
-
{_t(
+
{ _t(
"check your browser plugins for anything that might block " +
"the identity server (such as Privacy Badger)",
- )}
-
{_t("contact the administrators of identity server ", {}, {
+ ) }
+
{ _t("contact the administrators of identity server ", {}, {
idserver: messageElements.idserver,
- })}
- {_t(
- "Integration Managers receive configuration data, and can modify widgets, " +
+ { _t(
+ "Integration managers receive configuration data, and can modify widgets, " +
"send room invites, and set power levels on your behalf.",
- )}
+ ) }
);
@@ -230,7 +230,7 @@ export default class PhoneNumbers extends React.Component {
let addVerifySection = (
- {_t("Add")}
+ { _t("Add") }
);
if (this.state.verifying) {
@@ -238,10 +238,10 @@ export default class PhoneNumbers extends React.Component {
addVerifySection = (
- {_t("A text message has been sent to +%(msisdn)s. " +
- "Please enter the verification code it contains.", { msisdn: msisdn })}
+ { _t("A text message has been sent to +%(msisdn)s. " +
+ "Please enter the verification code it contains.", { msisdn: msisdn }) }
- {this.state.verifyError}
+ { this.state.verifyError }
);
}
diff --git a/src/components/views/settings/discovery/EmailAddresses.js b/src/components/views/settings/discovery/EmailAddresses.js
index 352ff1b0ba..970407774b 100644
--- a/src/components/views/settings/discovery/EmailAddresses.js
+++ b/src/components/views/settings/discovery/EmailAddresses.js
@@ -198,14 +198,14 @@ export class EmailAddress extends React.Component {
let status;
if (verifying) {
status =
- {_t("Verify the link in your inbox")}
+ { _t("Verify the link in your inbox") }
- {_t("Complete")}
+ { _t("Complete") }
;
} else if (bound) {
@@ -214,7 +214,7 @@ export class EmailAddress extends React.Component {
kind="danger_sm"
onClick={this.onRevokeClick}
>
- {_t("Revoke")}
+ { _t("Revoke") }
;
} else {
status =
- {_t("Share")}
+ { _t("Share") }
;
}
return (
- {address}
- {status}
+ { address }
+ { status }
);
}
@@ -249,13 +249,13 @@ export default class EmailAddresses extends React.Component {
});
} else {
content =
- {_t("Discovery options will appear once you have added an email above.")}
+ { _t("Discovery options will appear once you have added an email above.") }
;
}
return (
- {content}
+ { content }
);
}
diff --git a/src/components/views/settings/discovery/PhoneNumbers.js b/src/components/views/settings/discovery/PhoneNumbers.js
index 9df4a38f70..b6c944c733 100644
--- a/src/components/views/settings/discovery/PhoneNumbers.js
+++ b/src/components/views/settings/discovery/PhoneNumbers.js
@@ -205,9 +205,9 @@ export class PhoneNumber extends React.Component {
if (verifying) {
status =
- {_t("Please enter verification code sent via text.")}
+ { _t("Please enter verification code sent via text.") }
- {this.state.verifyError}
+ { this.state.verifyError }
);
}
@@ -261,13 +261,13 @@ export default class PhoneNumbers extends React.Component {
});
} else {
content =
- {_t("Discovery options will appear once you have added a phone number above.")}
+ { _t("Discovery options will appear once you have added a phone number above.") }
;
}
return (
- {content}
+ { content }
);
}
diff --git a/src/components/views/settings/tabs/room/AdvancedRoomSettingsTab.tsx b/src/components/views/settings/tabs/room/AdvancedRoomSettingsTab.tsx
index eda3419d14..9322eab711 100644
--- a/src/components/views/settings/tabs/room/AdvancedRoomSettingsTab.tsx
+++ b/src/components/views/settings/tabs/room/AdvancedRoomSettingsTab.tsx
@@ -116,8 +116,8 @@ export default class AdvancedRoomSettingsTab extends React.Component We'll post a link to the new room in the old " +
"version of the room - room members will have to click this link to join the new room.",
{}, {
- "b": (sub) => {sub},
- "i": (sub) => {sub},
+ "b": (sub) => { sub },
+ "i": (sub) => { sub },
},
) }
diff --git a/src/components/views/settings/tabs/room/BridgeSettingsTab.tsx b/src/components/views/settings/tabs/room/BridgeSettingsTab.tsx
index fb144da399..c8188250b1 100644
--- a/src/components/views/settings/tabs/room/BridgeSettingsTab.tsx
+++ b/src/components/views/settings/tabs/room/BridgeSettingsTab.tsx
@@ -61,36 +61,36 @@ export default class BridgeSettingsTab extends React.Component {
let content: JSX.Element;
if (bridgeEvents.length > 0) {
content =
-
{_t(
+
{ _t(
"This room is bridging messages to the following platforms. " +
"Learn more.", {},
{
// TODO: We don't have this link yet: this will prevent the translators
// having to re-translate the string when we do.
- a: sub => {sub},
+ a: sub => { sub },
},
- )}
{ _t(
"This room isn’t bridging messages to any platforms. " +
"Learn more.", {},
{
// TODO: We don't have this link yet: this will prevent the translators
// having to re-translate the string when we do.
- a: sub => {sub},
+ a: sub => { sub },
},
- )}
{ _t('Select the roles required to change various parts of the room') }
+ { powerSelectors }
+ { eventPowerSelectors }
);
diff --git a/src/components/views/settings/tabs/room/SecurityRoomSettingsTab.tsx b/src/components/views/settings/tabs/room/SecurityRoomSettingsTab.tsx
index 4868ffd4fc..88bc2046ce 100644
--- a/src/components/views/settings/tabs/room/SecurityRoomSettingsTab.tsx
+++ b/src/components/views/settings/tabs/room/SecurityRoomSettingsTab.tsx
@@ -18,7 +18,6 @@ import React from 'react';
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
import { _t } from "../../../../../languageHandler";
import { MatrixClientPeg } from "../../../../../MatrixClientPeg";
-import * as sdk from "../../../../..";
import LabelledToggleSwitch from "../../../elements/LabelledToggleSwitch";
import Modal from "../../../../../Modal";
import QuestionDialog from "../../../dialogs/QuestionDialog";
@@ -27,6 +26,7 @@ import { SettingLevel } from "../../../../../settings/SettingLevel";
import SettingsStore from "../../../../../settings/SettingsStore";
import { UIFeature } from "../../../../../settings/UIFeature";
import { replaceableComponent } from "../../../../../utils/replaceableComponent";
+import SettingsFlag from '../../../elements/SettingsFlag';
// Knock and private are reserved keywords which are not yet implemented.
export enum JoinRule {
@@ -78,7 +78,7 @@ export default class SecurityRoomSettingsTab extends React.Component{sub},
+ >{ sub },
},
),
onFinished: (confirm) => {
@@ -289,8 +289,8 @@ export default class SecurityRoomSettingsTab extends React.Component
- {_t("Guests cannot join this room even if explicitly invited.")}
- {_t("Click here to fix")}
+ { _t("Guests cannot join this room even if explicitly invited.") }
+ { _t("Click here to fix") }
);
@@ -302,7 +302,7 @@ export default class SecurityRoomSettingsTab extends React.Component
- {_t("To link to this room, please add an address.")}
+ { _t("To link to this room, please add an address.") }
- {_t('Changes to who can read history will only apply to future messages in this room. ' +
- 'The visibility of existing history will be unchanged.')}
+ { _t('Changes to who can read history will only apply to future messages in this room. ' +
+ 'The visibility of existing history will be unchanged.') }
);
}
@@ -322,7 +322,7 @@ export default class GeneralUserSettingsTab extends React.Component {
// TODO: Convert to new-styled Field
return (
- {_t("Language and region")}
+ { _t("Language and region") }
- {_t("Spell check dictionaries")}
+ { _t("Spell check dictionaries") }
- {_t(
+ { _t(
"Agree to the identity server (%(serverName)s) Terms of Service to " +
"allow yourself to be discoverable by email address or phone number.",
{ serverName: this.state.idServerName },
- )}
+ ) }
;
return (
@@ -364,7 +364,7 @@ export default class GeneralUserSettingsTab extends React.Component {
onFinished={this.state.requiredPolicyInfo.resolve}
introElement={intro}
/>
- { /* has its own heading as it includes the current ID server */ }
+ { /* has its own heading as it includes the current identity server */ }
- {threepidSection}
- { /* has its own heading as it includes the current ID server */ }
+ { threepidSection }
+ { /* has its own heading as it includes the current identity server */ }
);
@@ -397,12 +397,12 @@ export default class GeneralUserSettingsTab extends React.Component {
// TODO: Improve warning text for account deactivation
return (
- {_t("Account management")}
+ { _t("Account management") }
- {_t("Deactivating your account is a permanent action - be careful!")}
+ { _t("Deactivating your account is a permanent action - be careful!") }
- {_t("Deactivate Account")}
+ { _t("Deactivate Account") }
);
@@ -434,28 +434,28 @@ export default class GeneralUserSettingsTab extends React.Component {
let accountManagementSection;
if (SettingsStore.getValue(UIFeature.Deactivate)) {
accountManagementSection = <>
-
{_t("Deactivate account")}
- {this._renderManagementSection()}
+
{ _t("Deactivate account") }
+ { this._renderManagementSection() }
>;
}
let discoverySection;
if (SettingsStore.getValue(UIFeature.IdentityServer)) {
discoverySection = <>
-
- {_t(
+ { _t(
'For help with using %(brand)s, click here or start a chat with our ' +
'bot using the button below.',
{
@@ -212,13 +208,13 @@ export default class HelpUserSettingsTab extends React.Component
rel='noreferrer noopener'
target='_blank'
>
- {sub}
+ { sub }
,
},
- )}
+ ) }
- {_t("Chat with %(brand)s Bot", { brand })}
+ { _t("Chat with %(brand)s Bot", { brand }) }
@@ -239,29 +235,29 @@ export default class HelpUserSettingsTab extends React.Component
if (SdkConfig.get().bug_report_endpoint_url) {
bugReportingSection = (
- {_t('Bug reporting')}
+ { _t('Bug reporting') }
- {_t(
+ { _t(
"If you've submitted a bug via GitHub, debug logs can help " +
"us track down the problem. Debug logs contain application " +
"usage data including your username, the IDs or aliases of " +
"the rooms or groups you have visited and the usernames of " +
"other users. They do not contain messages.",
- )}
+ ) }
- {_t("Access Token")}
- {_t("Your access token gives full access to your account."
- + " Do not share it with anyone." )}
+ { _t("Access Token") }
+ { _t("Your access token gives full access to your account."
+ + " Do not share it with anyone." ) }
- {_t("⚠ These settings are meant for advanced users.")}
+ { _t("⚠ These settings are meant for advanced users.") }
- {_t(
+ { _t(
"Add users and servers you want to ignore here. Use asterisks " +
"to have %(brand)s match any characters. For example, @bot:* " +
"would ignore all users that have the name 'bot' on any server.",
- { brand }, { code: (s) => {s} },
- )}
+ { brand }, { code: (s) => { s } },
+ ) }
- {_t(
+ { _t(
"Ignoring people is done through ban lists which contain rules for " +
"who to ban. Subscribing to a ban list means the users/servers blocked by " +
"that list will be hidden from you.",
- )}
+ ) }
- {_t("Personal ban list")}
+ { _t("Personal ban list") }
- {_t(
+ { _t(
"Your personal ban list holds all the users/servers you personally don't " +
"want to see messages from. After ignoring your first user/server, a new room " +
"will show up in your room list named 'My Ban List' - stay in this room to keep " +
"the ban list in effect.",
- )}
+ ) }
- {_t("Subscribing to a ban list will cause you to join it!")}
+ { _t("Subscribing to a ban list will cause you to join it!") }
- {_t(
+ { _t(
"If this isn't what you want, please use a different tool to ignore users.",
- )}
+ ) }
diff --git a/src/components/views/settings/tabs/user/NotificationUserSettingsTab.js b/src/components/views/settings/tabs/user/NotificationUserSettingsTab.tsx
similarity index 80%
rename from src/components/views/settings/tabs/user/NotificationUserSettingsTab.js
rename to src/components/views/settings/tabs/user/NotificationUserSettingsTab.tsx
index 0aabdd24e2..5717813ae1 100644
--- a/src/components/views/settings/tabs/user/NotificationUserSettingsTab.js
+++ b/src/components/views/settings/tabs/user/NotificationUserSettingsTab.tsx
@@ -1,5 +1,5 @@
/*
-Copyright 2019 New Vector Ltd
+Copyright 2019-2021 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -16,20 +16,15 @@ limitations under the License.
import React from 'react';
import { _t } from "../../../../../languageHandler";
-import * as sdk from "../../../../../index";
import { replaceableComponent } from "../../../../../utils/replaceableComponent";
+import Notifications from "../../Notifications";
@replaceableComponent("views.settings.tabs.user.NotificationUserSettingsTab")
export default class NotificationUserSettingsTab extends React.Component {
- constructor() {
- super();
- }
-
render() {
- const Notifications = sdk.getComponent("views.settings.Notifications");
return (
@@ -347,19 +348,19 @@ export default class SecurityUserSettingsTab extends React.Component {
let privacySection;
if (Analytics.canEnable() || CountlyAnalytics.instance.canEnable()) {
privacySection =
-
{_t("Privacy")}
+
{ _t("Privacy") }
- {_t("Analytics")}
+ { _t("Analytics") }
- {_t(
+ { _t(
"%(brand)s collects anonymous analytics to allow us to improve the application.",
{ brand },
- )}
+ ) }
- {_t("Privacy is important to us, so we don't collect any personal or " +
- "identifiable data for our analytics.")}
+ { _t("Privacy is important to us, so we don't collect any personal or " +
+ "identifiable data for our analytics.") }
- {_t("Learn more about how we use analytics.")}
+ { _t("Learn more about how we use analytics.") }
@@ -376,11 +377,11 @@ export default class SecurityUserSettingsTab extends React.Component {
// only show the section if there's something to show
if (ignoreUsersPanel || invitesPanel || e2ePanel) {
advancedSection = <>
-
- {_t(
+ { _t(
"Manage the names of and sign out of your sessions below or " +
"verify them in your User Profile.", {},
{
a: sub =>
- {sub}
+ { sub }
,
},
- )}
+ ) }
- {_t("A session's public name is visible to people you communicate with")}
+ { _t("A session's public name is visible to people you communicate with") }
{ this.props.isSelf ?
"":
- _t("To be secure, do this in person or use a trusted way to communicate.")}
- {confirm}
+ _t("To be secure, do this in person or use a trusted way to communicate.") }
+ { confirm }
;
}
}
diff --git a/src/components/views/voip/AudioFeed.tsx b/src/components/views/voip/AudioFeed.tsx
index 272d8a06a3..a2ab760c86 100644
--- a/src/components/views/voip/AudioFeed.tsx
+++ b/src/components/views/voip/AudioFeed.tsx
@@ -20,7 +20,7 @@ import { logger } from 'matrix-js-sdk/src/logger';
import MediaDeviceHandler, { MediaDeviceHandlerEvent } from "../../../MediaDeviceHandler";
interface IProps {
- feed: CallFeed,
+ feed: CallFeed;
}
export default class AudioFeed extends React.Component {
diff --git a/src/components/views/voip/CallPreview.tsx b/src/components/views/voip/CallPreview.tsx
index 5d6a564bc2..895d9773e4 100644
--- a/src/components/views/voip/CallPreview.tsx
+++ b/src/components/views/voip/CallPreview.tsx
@@ -15,7 +15,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
-import React from 'react';
+import React, { createRef } from 'react';
import CallView from "./CallView";
import RoomViewStore from '../../../stores/RoomViewStore';
@@ -27,6 +27,23 @@ import SettingsStore from "../../../settings/SettingsStore";
import { CallEvent, CallState, MatrixCall } from 'matrix-js-sdk/src/webrtc/call';
import { MatrixClientPeg } from '../../../MatrixClientPeg';
import { replaceableComponent } from "../../../utils/replaceableComponent";
+import UIStore from '../../../stores/UIStore';
+import { lerp } from '../../../utils/AnimationUtils';
+import { MarkedExecution } from '../../../utils/MarkedExecution';
+import { EventSubscription } from 'fbemitter';
+
+const PIP_VIEW_WIDTH = 336;
+const PIP_VIEW_HEIGHT = 232;
+
+const MOVING_AMT = 0.2;
+const SNAPPING_AMT = 0.05;
+
+const PADDING = {
+ top: 58,
+ bottom: 58,
+ left: 76,
+ right: 8,
+};
const SHOW_CALL_IN_STATES = [
CallState.Connected,
@@ -49,6 +66,10 @@ interface IState {
// Any other call we're displaying: only if the user is on two calls and not viewing either of the rooms
// they belong to
secondaryCall: MatrixCall;
+
+ // Position of the CallPreview
+ translationX: number;
+ translationY: number;
}
// Splits a list of calls into one 'primary' one and a list
@@ -88,9 +109,19 @@ function getPrimarySecondaryCalls(calls: MatrixCall[]): [MatrixCall, MatrixCall[
*/
@replaceableComponent("views.voip.CallPreview")
export default class CallPreview extends React.Component {
- private roomStoreToken: any;
+ private roomStoreToken: EventSubscription;
private dispatcherRef: string;
private settingsWatcherRef: string;
+ private callViewWrapper = createRef();
+ private initX = 0;
+ private initY = 0;
+ private desiredTranslationX = UIStore.instance.windowWidth - PADDING.right - PIP_VIEW_WIDTH;
+ private desiredTranslationY = UIStore.instance.windowHeight - PADDING.bottom - PIP_VIEW_WIDTH;
+ private moving = false;
+ private scheduledUpdate = new MarkedExecution(
+ () => this.animationCallback(),
+ () => requestAnimationFrame(() => this.scheduledUpdate.trigger()),
+ );
constructor(props: IProps) {
super(props);
@@ -105,12 +136,17 @@ export default class CallPreview extends React.Component {
roomId,
primaryCall: primaryCall,
secondaryCall: secondaryCalls[0],
+ translationX: UIStore.instance.windowWidth - PADDING.right - PIP_VIEW_WIDTH,
+ translationY: UIStore.instance.windowHeight - PADDING.bottom - PIP_VIEW_WIDTH,
};
}
public componentDidMount() {
CallHandler.sharedInstance().addListener(CallHandlerEvent.CallChangeRoom, this.updateCalls);
this.roomStoreToken = RoomViewStore.addListener(this.onRoomViewStoreUpdate);
+ document.addEventListener("mousemove", this.onMoving);
+ document.addEventListener("mouseup", this.onEndMoving);
+ window.addEventListener("resize", this.snap);
this.dispatcherRef = dis.register(this.onAction);
MatrixClientPeg.get().on(CallEvent.RemoteHoldUnhold, this.onCallRemoteHold);
}
@@ -118,6 +154,9 @@ export default class CallPreview extends React.Component {
public componentWillUnmount() {
CallHandler.sharedInstance().removeListener(CallHandlerEvent.CallChangeRoom, this.updateCalls);
MatrixClientPeg.get().removeListener(CallEvent.RemoteHoldUnhold, this.onCallRemoteHold);
+ document.removeEventListener("mousemove", this.onMoving);
+ document.removeEventListener("mouseup", this.onEndMoving);
+ window.removeEventListener("resize", this.snap);
if (this.roomStoreToken) {
this.roomStoreToken.remove();
}
@@ -125,7 +164,84 @@ export default class CallPreview extends React.Component {
SettingsStore.unwatchSetting(this.settingsWatcherRef);
}
- private onRoomViewStoreUpdate = (payload) => {
+ private animationCallback = () => {
+ // If the PiP isn't being dragged and there is only a tiny difference in
+ // the desiredTranslation and translation, quit the animationCallback
+ // loop. If that is the case, it means the PiP has snapped into its
+ // position and there is nothing to do. Not doing this would cause an
+ // infinite loop
+ if (
+ !this.moving &&
+ Math.abs(this.state.translationX - this.desiredTranslationX) <= 1 &&
+ Math.abs(this.state.translationY - this.desiredTranslationY) <= 1
+ ) return;
+
+ const amt = this.moving ? MOVING_AMT : SNAPPING_AMT;
+ this.setState({
+ translationX: lerp(this.state.translationX, this.desiredTranslationX, amt),
+ translationY: lerp(this.state.translationY, this.desiredTranslationY, amt),
+ });
+ this.scheduledUpdate.mark();
+ };
+
+ private setTranslation(inTranslationX: number, inTranslationY: number) {
+ const width = this.callViewWrapper.current?.clientWidth || PIP_VIEW_WIDTH;
+ const height = this.callViewWrapper.current?.clientHeight || PIP_VIEW_HEIGHT;
+
+ // Avoid overflow on the x axis
+ if (inTranslationX + width >= UIStore.instance.windowWidth) {
+ this.desiredTranslationX = UIStore.instance.windowWidth - width;
+ } else if (inTranslationX <= 0) {
+ this.desiredTranslationX = 0;
+ } else {
+ this.desiredTranslationX = inTranslationX;
+ }
+
+ // Avoid overflow on the y axis
+ if (inTranslationY + height >= UIStore.instance.windowHeight) {
+ this.desiredTranslationY = UIStore.instance.windowHeight - height;
+ } else if (inTranslationY <= 0) {
+ this.desiredTranslationY = 0;
+ } else {
+ this.desiredTranslationY = inTranslationY;
+ }
+ }
+
+ private snap = () => {
+ const translationX = this.desiredTranslationX;
+ const translationY = this.desiredTranslationY;
+ // We subtract the PiP size from the window size in order to calculate
+ // the position to snap to from the PiP center and not its top-left
+ // corner
+ const windowWidth = (
+ UIStore.instance.windowWidth -
+ (this.callViewWrapper.current?.clientWidth || PIP_VIEW_WIDTH)
+ );
+ const windowHeight = (
+ UIStore.instance.windowHeight -
+ (this.callViewWrapper.current?.clientHeight || PIP_VIEW_HEIGHT)
+ );
+
+ if (translationX >= windowWidth / 2 && translationY >= windowHeight / 2) {
+ this.desiredTranslationX = windowWidth - PADDING.right;
+ this.desiredTranslationY = windowHeight - PADDING.bottom;
+ } else if (translationX >= windowWidth / 2 && translationY <= windowHeight / 2) {
+ this.desiredTranslationX = windowWidth - PADDING.right;
+ this.desiredTranslationY = PADDING.top;
+ } else if (translationX <= windowWidth / 2 && translationY >= windowHeight / 2) {
+ this.desiredTranslationX = PADDING.left;
+ this.desiredTranslationY = windowHeight - PADDING.bottom;
+ } else {
+ this.desiredTranslationX = PADDING.left;
+ this.desiredTranslationY = PADDING.top;
+ }
+
+ // We start animating here because we want the PiP to move when we're
+ // resizing the window
+ this.scheduledUpdate.mark();
+ };
+
+ private onRoomViewStoreUpdate = () => {
if (RoomViewStore.getRoomId() === this.state.roomId) return;
const roomId = RoomViewStore.getRoomId();
@@ -173,10 +289,52 @@ export default class CallPreview extends React.Component {
});
};
+ private onStartMoving = (event: React.MouseEvent) => {
+ event.preventDefault();
+ event.stopPropagation();
+
+ this.moving = true;
+ this.initX = event.pageX - this.desiredTranslationX;
+ this.initY = event.pageY - this.desiredTranslationY;
+ this.scheduledUpdate.mark();
+ };
+
+ private onMoving = (event: React.MouseEvent | MouseEvent) => {
+ if (!this.moving) return;
+
+ event.preventDefault();
+ event.stopPropagation();
+
+ this.setTranslation(event.pageX - this.initX, event.pageY - this.initY);
+ };
+
+ private onEndMoving = () => {
+ this.moving = false;
+ this.snap();
+ };
+
public render() {
if (this.state.primaryCall) {
+ const translatePixelsX = this.state.translationX + "px";
+ const translatePixelsY = this.state.translationY + "px";
+ const style = {
+ transform: `translateX(${translatePixelsX})
+ translateY(${translatePixelsY})`,
+ };
+
return (
-
+
+
+
);
}
diff --git a/src/components/views/voip/CallView.tsx b/src/components/views/voip/CallView.tsx
index c522116e0a..8bdd6e0f55 100644
--- a/src/components/views/voip/CallView.tsx
+++ b/src/components/views/voip/CallView.tsx
@@ -35,10 +35,10 @@ import { replaceableComponent } from "../../../utils/replaceableComponent";
interface IProps {
// The call for us to display
- call: MatrixCall,
+ call: MatrixCall;
// Another ongoing call to display information about
- secondaryCall?: MatrixCall,
+ secondaryCall?: MatrixCall;
// a callback which is called when the content in the CallView changes
// in a way that is likely to cause a resize.
@@ -49,18 +49,21 @@ interface IProps {
// This is sort of a proxy for a number of things but we currently have no
// need to control those things separately, so this is simpler.
pipMode?: boolean;
+
+ // Used for dragging the PiP CallView
+ onMouseDownOnHeader?: (event: React.MouseEvent) => void;
}
interface IState {
- isLocalOnHold: boolean,
- isRemoteOnHold: boolean,
- micMuted: boolean,
- vidMuted: boolean,
- callState: CallState,
- controlsVisible: boolean,
- showMoreMenu: boolean,
- showDialpad: boolean,
- feeds: CallFeed[],
+ isLocalOnHold: boolean;
+ isRemoteOnHold: boolean;
+ micMuted: boolean;
+ vidMuted: boolean;
+ callState: CallState;
+ controlsVisible: boolean;
+ showMoreMenu: boolean;
+ showDialpad: boolean;
+ feeds: CallFeed[];
}
function getFullScreenElement() {
@@ -462,7 +465,7 @@ export default class CallView extends React.Component {
// in the near future, the dial pad button will go on the left. For now, it's the nothing button
// because something needs to have margin-right: auto to make the alignment correct.
const callControls =
);
}
@@ -612,7 +617,7 @@ export default class CallView extends React.Component {
// Saying "Connecting" here isn't really true, but the best thing
// I can come up with, but this might be subject to change as well
contentView =
@@ -152,7 +164,7 @@ export default class IncomingCallBox extends React.Component {
diff --git a/src/components/views/voip/VideoFeed.tsx b/src/components/views/voip/VideoFeed.tsx
index 2f88abe6fb..e5461eb1b4 100644
--- a/src/components/views/voip/VideoFeed.tsx
+++ b/src/components/views/voip/VideoFeed.tsx
@@ -24,9 +24,9 @@ import MemberAvatar from "../avatars/MemberAvatar";
import { replaceableComponent } from "../../../utils/replaceableComponent";
interface IProps {
- call: MatrixCall,
+ call: MatrixCall;
- feed: CallFeed,
+ feed: CallFeed;
// Whether this call view is for picture-in-picture mode
// otherwise, it's the larger call view when viewing the room the call is in.
@@ -36,7 +36,7 @@ interface IProps {
// a callback which is called when the video element is resized
// due to a change in video metadata
- onResize?: (e: Event) => void,
+ onResize?: (e: Event) => void;
}
interface IState {
diff --git a/src/contexts/RoomContext.ts b/src/contexts/RoomContext.ts
index 3464f952a6..0507a3e252 100644
--- a/src/contexts/RoomContext.ts
+++ b/src/contexts/RoomContext.ts
@@ -41,6 +41,11 @@ const RoomContext = createContext({
canReply: false,
layout: Layout.Group,
lowBandwidth: false,
+ alwaysShowTimestamps: false,
+ showTwelveHourTimestamps: false,
+ readMarkerInViewThresholdMs: 3000,
+ readMarkerOutOfViewThresholdMs: 30000,
+ showHiddenEventsInTimeline: false,
showReadReceipts: true,
showRedactions: true,
showJoinLeaves: true,
diff --git a/src/createRoom.ts b/src/createRoom.ts
index 94efecaa6c..effc6ec1ac 100644
--- a/src/createRoom.ts
+++ b/src/createRoom.ts
@@ -17,11 +17,11 @@ limitations under the License.
import { MatrixClient } from "matrix-js-sdk/src/client";
import { Room } from "matrix-js-sdk/src/models/room";
+import { RoomMember } from "matrix-js-sdk/src/models/room-member";
import { EventType } from "matrix-js-sdk/src/@types/event";
import { MatrixClientPeg } from './MatrixClientPeg';
import Modal from './Modal';
-import * as sdk from './index';
import { _t } from './languageHandler';
import dis from "./dispatcher/dispatcher";
import * as Rooms from "./Rooms";
@@ -37,6 +37,8 @@ import { makeSpaceParentEvent } from "./utils/space";
import { Action } from "./dispatcher/actions";
import { ICreateRoomOpts } from "matrix-js-sdk/src/@types/requests";
import { Preset, Visibility } from "matrix-js-sdk/src/@types/partials";
+import ErrorDialog from "./components/views/dialogs/ErrorDialog";
+import Spinner from "./components/views/elements/Spinner";
// we define a number of interfaces which take their names from the js-sdk
/* eslint-disable camelcase */
@@ -80,9 +82,6 @@ export default function createRoom(opts: IOpts): Promise {
const startTime = CountlyAnalytics.getTimestamp();
- const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
- const Loader = sdk.getComponent("elements.Spinner");
-
const client = MatrixClientPeg.get();
if (client.isGuest()) {
dis.dispatch({ action: 'require_registration' });
@@ -153,7 +152,7 @@ export default function createRoom(opts: IOpts): Promise {
}
let modal;
- if (opts.spinner) modal = Modal.createDialog(Loader, null, 'mx_Dialog_spinner');
+ if (opts.spinner) modal = Modal.createDialog(Spinner, null, 'mx_Dialog_spinner');
let roomId;
return client.createRoom(createOpts).finally(function() {
@@ -249,11 +248,11 @@ export function findDMForUser(client: MatrixClient, userId: string): Room {
* NOTE: this assumes you've just created the room and there's not been an opportunity
* for other code to run, so we shouldn't miss RoomState.newMember when it comes by.
*/
-export async function _waitForMember(client: MatrixClient, roomId: string, userId: string, opts = { timeout: 1500 }) {
+export async function waitForMember(client: MatrixClient, roomId: string, userId: string, opts = { timeout: 1500 }) {
const { timeout } = opts;
let handler;
return new Promise((resolve) => {
- handler = function(_event, _roomstate, member) {
+ handler = function(_, __, member: RoomMember) { // eslint-disable-line @typescript-eslint/naming-convention
if (member.userId !== userId) return;
if (member.roomId !== roomId) return;
resolve(true);
@@ -326,7 +325,7 @@ export async function ensureDMExists(client: MatrixClient, userId: string): Prom
}
roomId = await createRoom({ encryption, dmUserId: userId, spinner: false, andView: false });
- await _waitForMember(client, roomId, userId);
+ await waitForMember(client, roomId, userId);
}
return roomId;
}
diff --git a/src/customisations/Alias.ts b/src/customisations/Alias.ts
new file mode 100644
index 0000000000..fcf6742193
--- /dev/null
+++ b/src/customisations/Alias.ts
@@ -0,0 +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.
+*/
+
+function getDisplayAliasForAliasSet(canonicalAlias: string, altAliases: string[]): string {
+ // E.g. prefer one of the aliases over another
+ return null;
+}
+
+// This interface summarises all available customisation points and also marks
+// them all as optional. This allows customisers to only define and export the
+// customisations they need while still maintaining type safety.
+export interface IAliasCustomisations {
+ getDisplayAliasForAliasSet?: typeof getDisplayAliasForAliasSet;
+}
+
+// A real customisation module will define and export one or more of the
+// customisation points that make up `IAliasCustomisations`.
+export default {} as IAliasCustomisations;
diff --git a/src/customisations/Directory.ts b/src/customisations/Directory.ts
new file mode 100644
index 0000000000..7ed4706c7d
--- /dev/null
+++ b/src/customisations/Directory.ts
@@ -0,0 +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.
+*/
+
+function requireCanonicalAliasAccessToPublish(): boolean {
+ // Some environments may not care about this requirement and could return false
+ return true;
+}
+
+// This interface summarises all available customisation points and also marks
+// them all as optional. This allows customisers to only define and export the
+// customisations they need while still maintaining type safety.
+export interface IDirectoryCustomisations {
+ requireCanonicalAliasAccessToPublish?: typeof requireCanonicalAliasAccessToPublish;
+}
+
+// A real customisation module will define and export one or more of the
+// customisation points that make up `IDirectoryCustomisations`.
+export default {} as IDirectoryCustomisations;
diff --git a/src/customisations/Media.ts b/src/customisations/Media.ts
index f8ee834102..ce8f88f6f1 100644
--- a/src/customisations/Media.ts
+++ b/src/customisations/Media.ts
@@ -75,6 +75,7 @@ export class Media {
* The HTTP URL for the source media.
*/
public get srcHttp(): string {
+ // eslint-disable-next-line no-restricted-properties
return this.client.mxcUrlToHttp(this.srcMxc);
}
@@ -84,6 +85,7 @@ export class Media {
*/
public get thumbnailHttp(): string | undefined | null {
if (!this.hasThumbnail) return null;
+ // eslint-disable-next-line no-restricted-properties
return this.client.mxcUrlToHttp(this.thumbnailMxc);
}
@@ -100,6 +102,7 @@ export class Media {
// scale using the device pixel ratio to keep images clear
width = Math.floor(width * window.devicePixelRatio);
height = Math.floor(height * window.devicePixelRatio);
+ // eslint-disable-next-line no-restricted-properties
return this.client.mxcUrlToHttp(this.thumbnailMxc, width, height, mode);
}
@@ -114,6 +117,7 @@ export class Media {
// scale using the device pixel ratio to keep images clear
width = Math.floor(width * window.devicePixelRatio);
height = Math.floor(height * window.devicePixelRatio);
+ // eslint-disable-next-line no-restricted-properties
return this.client.mxcUrlToHttp(this.srcMxc, width, height, mode);
}
diff --git a/src/customisations/Security.ts b/src/customisations/Security.ts
index e215c5cb24..9083274aa5 100644
--- a/src/customisations/Security.ts
+++ b/src/customisations/Security.ts
@@ -16,7 +16,7 @@ limitations under the License.
import { IMatrixClientCreds } from "../MatrixClientPeg";
import { Kind as SetupEncryptionKind } from "../toasts/SetupEncryptionToast";
-import { ISecretStorageKeyInfo } from 'matrix-js-sdk/src/matrix';
+import { ISecretStorageKeyInfo } from 'matrix-js-sdk/src/crypto/api';
/* eslint-disable-next-line @typescript-eslint/no-unused-vars */
function examineLoginResponse(
@@ -69,11 +69,11 @@ function setupEncryptionNeeded(kind: SetupEncryptionKind): boolean {
export interface ISecurityCustomisations {
examineLoginResponse?: typeof examineLoginResponse;
persistCredentials?: typeof persistCredentials;
- createSecretStorageKey?: typeof createSecretStorageKey,
- getSecretStorageKey?: typeof getSecretStorageKey,
- catchAccessSecretStorageError?: typeof catchAccessSecretStorageError,
- setupEncryptionNeeded?: typeof setupEncryptionNeeded,
- getDehydrationKey?: typeof getDehydrationKey,
+ createSecretStorageKey?: typeof createSecretStorageKey;
+ getSecretStorageKey?: typeof getSecretStorageKey;
+ catchAccessSecretStorageError?: typeof catchAccessSecretStorageError;
+ setupEncryptionNeeded?: typeof setupEncryptionNeeded;
+ getDehydrationKey?: typeof getDehydrationKey;
/**
* When false, disables the post-login UI from showing. If there's
@@ -83,7 +83,7 @@ export interface ISecurityCustomisations {
* encryption is set up some other way which would circumvent the default
* UI, such as by presenting alternative UI.
*/
- SHOW_ENCRYPTION_SETUP_UI?: boolean, // default true
+ SHOW_ENCRYPTION_SETUP_UI?: boolean; // default true
}
// A real customisation module will define and export one or more of the
diff --git a/src/customisations/models/IMediaEventContent.ts b/src/customisations/models/IMediaEventContent.ts
index fb05d76a4d..62dfe4ee19 100644
--- a/src/customisations/models/IMediaEventContent.ts
+++ b/src/customisations/models/IMediaEventContent.ts
@@ -32,11 +32,16 @@ export interface IEncryptedFile {
}
export interface IMediaEventContent {
+ body?: string;
url?: string; // required on unencrypted media
file?: IEncryptedFile; // required for *encrypted* media
info?: {
thumbnail_url?: string; // eslint-disable-line camelcase
thumbnail_file?: IEncryptedFile; // eslint-disable-line camelcase
+ mimetype: string;
+ w?: number;
+ h?: number;
+ size?: number;
};
}
diff --git a/src/dispatcher/actions.ts b/src/dispatcher/actions.ts
index 6438ecc0f2..5732428201 100644
--- a/src/dispatcher/actions.ts
+++ b/src/dispatcher/actions.ts
@@ -56,9 +56,21 @@ export enum Action {
CheckUpdates = "check_updates",
/**
- * Focuses the user's cursor to the composer. No additional payload information required.
+ * Focuses the user's cursor to the send message composer. No additional payload information required.
*/
- FocusComposer = "focus_composer",
+ FocusSendMessageComposer = "focus_send_message_composer",
+
+ /**
+ * Focuses the user's cursor to the edit message composer. No additional payload information required.
+ */
+ FocusEditMessageComposer = "focus_edit_message_composer",
+
+ /**
+ * Focuses the user's cursor to the edit message composer or send message
+ * composer based on the current edit state. No additional payload
+ * information required.
+ */
+ FocusAComposer = "focus_a_composer",
/**
* Opens the user menu (previously known as the top left menu). No additional payload information required.
@@ -106,6 +118,18 @@ export enum Action {
*/
DialNumber = "dial_number",
+ /**
+ * Start a call transfer to a Matrix ID
+ * payload: TransferCallPayload
+ */
+ TransferCallToMatrixID = "transfer_call_to_matrix_id",
+
+ /**
+ * Start a call transfer to a phone number
+ * payload: TransferCallPayload
+ */
+ TransferCallToPhoneNumber = "transfer_call_to_phone_number",
+
/**
* Fired when CallHandler has checked for PSTN protocol support
* payload: none
diff --git a/src/dispatcher/payloads/ComposerInsertPayload.ts b/src/dispatcher/payloads/ComposerInsertPayload.ts
index 9702855432..ea5d8a0c53 100644
--- a/src/dispatcher/payloads/ComposerInsertPayload.ts
+++ b/src/dispatcher/payloads/ComposerInsertPayload.ts
@@ -20,7 +20,7 @@ import { ActionPayload } from "../payloads";
import { Action } from "../actions";
interface IBaseComposerInsertPayload extends ActionPayload {
- action: Action.ComposerInsert,
+ action: Action.ComposerInsert;
}
interface IComposerInsertMentionPayload extends IBaseComposerInsertPayload {
diff --git a/src/dispatcher/payloads/TransferCallPayload.ts b/src/dispatcher/payloads/TransferCallPayload.ts
new file mode 100644
index 0000000000..38431bb0d6
--- /dev/null
+++ b/src/dispatcher/payloads/TransferCallPayload.ts
@@ -0,0 +1,33 @@
+/*
+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";
+import { MatrixCall } from "matrix-js-sdk/src/webrtc/call";
+
+export interface TransferCallPayload extends ActionPayload {
+ action: Action.TransferCallToMatrixID | Action.TransferCallToPhoneNumber;
+ // The call to transfer
+ call: MatrixCall;
+ // Where to transfer the call. A Matrix ID if action == TransferCallToMatrixID
+ // and a phone number if action == TransferCallToPhoneNumber
+ destination: string;
+ // If true, puts the current call on hold and dials the transfer target, giving
+ // the user a button to complete the transfer when ready.
+ // If false, ends the call immediately and sends the user to the transfer
+ // destination
+ consultFirst: boolean;
+}
diff --git a/src/editor/model.ts b/src/editor/model.ts
index 1e8498a69e..da1c2f47f5 100644
--- a/src/editor/model.ts
+++ b/src/editor/model.ts
@@ -70,7 +70,7 @@ export default class EditorModel {
* on the model that can span multiple parts. Also see `startRange()`.
* @param {TransformCallback} transformCallback
*/
- setTransformCallback(transformCallback: TransformCallback) {
+ public setTransformCallback(transformCallback: TransformCallback): void {
this.transformCallback = transformCallback;
}
@@ -78,23 +78,23 @@ export default class EditorModel {
* Set a callback for rerendering the model after it has been updated.
* @param {ModelCallback} updateCallback
*/
- setUpdateCallback(updateCallback: UpdateCallback) {
+ public setUpdateCallback(updateCallback: UpdateCallback): void {
this.updateCallback = updateCallback;
}
- get partCreator() {
+ public get partCreator(): PartCreator {
return this._partCreator;
}
- get isEmpty() {
+ public get isEmpty(): boolean {
return this._parts.reduce((len, part) => len + part.text.length, 0) === 0;
}
- clone() {
+ public clone(): EditorModel {
return new EditorModel(this._parts, this._partCreator, this.updateCallback);
}
- private insertPart(index: number, part: Part) {
+ private insertPart(index: number, part: Part): void {
this._parts.splice(index, 0, part);
if (this.activePartIdx >= index) {
++this.activePartIdx;
@@ -104,7 +104,7 @@ export default class EditorModel {
}
}
- private removePart(index: number) {
+ private removePart(index: number): void {
this._parts.splice(index, 1);
if (index === this.activePartIdx) {
this.activePartIdx = null;
@@ -118,22 +118,22 @@ export default class EditorModel {
}
}
- private replacePart(index: number, part: Part) {
+ private replacePart(index: number, part: Part): void {
this._parts.splice(index, 1, part);
}
- get parts() {
+ public get parts(): Part[] {
return this._parts;
}
- get autoComplete() {
+ public get autoComplete(): AutocompleteWrapperModel {
if (this.activePartIdx === this.autoCompletePartIdx) {
return this._autoComplete;
}
return null;
}
- getPositionAtEnd() {
+ public getPositionAtEnd(): DocumentPosition {
if (this._parts.length) {
const index = this._parts.length - 1;
const part = this._parts[index];
@@ -144,11 +144,11 @@ export default class EditorModel {
}
}
- serializeParts() {
+ public serializeParts(): SerializedPart[] {
return this._parts.map(p => p.serialize());
}
- private diff(newValue: string, inputType: string, caret: DocumentOffset) {
+ private diff(newValue: string, inputType: string, caret: DocumentOffset): IDiff {
const previousValue = this.parts.reduce((text, p) => text + p.text, "");
// can't use caret position with drag and drop
if (inputType === "deleteByDrag") {
@@ -158,7 +158,7 @@ export default class EditorModel {
}
}
- reset(serializedParts: SerializedPart[], caret?: Caret, inputType?: string) {
+ public reset(serializedParts: SerializedPart[], caret?: Caret, inputType?: string): void {
this._parts = serializedParts.map(p => this._partCreator.deserializePart(p));
if (!caret) {
caret = this.getPositionAtEnd();
@@ -180,7 +180,7 @@ export default class EditorModel {
* @param {DocumentPosition} position the position to start inserting at
* @return {Number} the amount of characters added
*/
- insert(parts: Part[], position: IPosition) {
+ public insert(parts: Part[], position: IPosition): number {
const insertIndex = this.splitAt(position);
let newTextLength = 0;
for (let i = 0; i < parts.length; ++i) {
@@ -191,7 +191,7 @@ export default class EditorModel {
return newTextLength;
}
- update(newValue: string, inputType: string, caret: DocumentOffset) {
+ public update(newValue: string, inputType: string, caret: DocumentOffset): Promise {
const diff = this.diff(newValue, inputType, caret);
const position = this.positionForOffset(diff.at, caret.atNodeEnd);
let removedOffsetDecrease = 0;
@@ -220,7 +220,7 @@ export default class EditorModel {
return Number.isFinite(result) ? result as number : 0;
}
- private setActivePart(pos: DocumentPosition, canOpenAutoComplete: boolean) {
+ private setActivePart(pos: DocumentPosition, canOpenAutoComplete: boolean): Promise {
const { index } = pos;
const part = this._parts[index];
if (part) {
@@ -250,7 +250,7 @@ export default class EditorModel {
return Promise.resolve();
}
- private onAutoComplete = ({ replaceParts, close }: ICallback) => {
+ private onAutoComplete = ({ replaceParts, close }: ICallback): void => {
let pos;
if (replaceParts) {
this._parts.splice(this.autoCompletePartIdx, this.autoCompletePartCount, ...replaceParts);
@@ -270,7 +270,7 @@ export default class EditorModel {
this.updateCallback(pos);
};
- private mergeAdjacentParts() {
+ private mergeAdjacentParts(): void {
let prevPart;
for (let i = 0; i < this._parts.length; ++i) {
let part = this._parts[i];
@@ -294,7 +294,7 @@ export default class EditorModel {
* @return {Number} how many characters before pos were also removed,
* usually because of non-editable parts that can only be removed in their entirety.
*/
- removeText(pos: IPosition, len: number) {
+ public removeText(pos: IPosition, len: number): number {
let { index, offset } = pos;
let removedOffsetDecrease = 0;
while (len > 0) {
@@ -329,7 +329,7 @@ export default class EditorModel {
}
// return part index where insertion will insert between at offset
- private splitAt(pos: IPosition) {
+ private splitAt(pos: IPosition): number {
if (pos.index === -1) {
return 0;
}
@@ -356,7 +356,7 @@ export default class EditorModel {
* @return {Number} how far from position (in characters) the insertion ended.
* This can be more than the length of `str` when crossing non-editable parts, which are skipped.
*/
- private addText(pos: IPosition, str: string, inputType: string) {
+ private addText(pos: IPosition, str: string, inputType: string): number {
let { index } = pos;
const { offset } = pos;
let addLen = str.length;
@@ -390,7 +390,7 @@ export default class EditorModel {
return addLen;
}
- positionForOffset(totalOffset: number, atPartEnd = false) {
+ public positionForOffset(totalOffset: number, atPartEnd = false): DocumentPosition {
let currentOffset = 0;
const index = this._parts.findIndex(part => {
const partLen = part.text.length;
@@ -416,11 +416,11 @@ export default class EditorModel {
* @param {DocumentPosition?} positionB the other boundary of the range, optional
* @return {Range}
*/
- startRange(positionA: DocumentPosition, positionB = positionA) {
+ public startRange(positionA: DocumentPosition, positionB = positionA): Range {
return new Range(this, positionA, positionB);
}
- replaceRange(startPosition: DocumentPosition, endPosition: DocumentPosition, parts: Part[]) {
+ public replaceRange(startPosition: DocumentPosition, endPosition: DocumentPosition, parts: Part[]): void {
// convert end position to offset, so it is independent of how the document is split into parts
// which we'll change when splitting up at the start position
const endOffset = endPosition.asOffset(this);
@@ -445,9 +445,9 @@ export default class EditorModel {
* @param {ManualTransformCallback} callback to run the transformations in
* @return {Promise} a promise when auto-complete (if applicable) is done updating
*/
- transform(callback: ManualTransformCallback) {
+ public transform(callback: ManualTransformCallback): Promise {
const pos = callback();
- let acPromise = null;
+ let acPromise: Promise = null;
if (!(pos instanceof Range)) {
acPromise = this.setActivePart(pos, true);
} else {
diff --git a/src/editor/parts.ts b/src/editor/parts.ts
index c16a95dbc9..7bda8e1901 100644
--- a/src/editor/parts.ts
+++ b/src/editor/parts.ts
@@ -274,7 +274,7 @@ abstract class PillPart extends BasePart implements IPillPart {
}
// helper method for subclasses
- _setAvatarVars(node: HTMLElement, avatarUrl: string, initialLetter: string) {
+ protected setAvatarVars(node: HTMLElement, avatarUrl: string, initialLetter: string) {
const avatarBackground = `url('${avatarUrl}')`;
const avatarLetter = `'${initialLetter}'`;
// check if the value is changing,
@@ -354,7 +354,7 @@ class RoomPillPart extends PillPart {
initialLetter = Avatar.getInitialLetter(this.room ? this.room.name : this.resourceId);
avatarUrl = Avatar.defaultAvatarUrlForString(this.room ? this.room.roomId : this.resourceId);
}
- this._setAvatarVars(node, avatarUrl, initialLetter);
+ this.setAvatarVars(node, avatarUrl, initialLetter);
}
get type(): IPillPart["type"] {
@@ -399,7 +399,7 @@ class UserPillPart extends PillPart {
if (avatarUrl === defaultAvatarUrl) {
initialLetter = Avatar.getInitialLetter(name);
}
- this._setAvatarVars(node, avatarUrl, initialLetter);
+ this.setAvatarVars(node, avatarUrl, initialLetter);
}
get type(): IPillPart["type"] {
@@ -552,7 +552,7 @@ export class PartCreator {
// part creator that support auto complete for /commands,
// used in SendMessageComposer
export class CommandPartCreator extends PartCreator {
- createPartForInput(text: string, partIndex: number) {
+ public createPartForInput(text: string, partIndex: number): Part {
// at beginning and starts with /? create
if (partIndex === 0 && text[0] === "/") {
// text will be inserted by model, so pass empty string
@@ -562,11 +562,11 @@ export class CommandPartCreator extends PartCreator {
}
}
- command(text: string) {
+ public command(text: string): CommandPart {
return new CommandPart(text, this.autoCompleteCreator);
}
- deserializePart(part: Part): Part {
+ public deserializePart(part: SerializedPart): Part {
if (part.type === "command") {
return this.command(part.text);
} else {
@@ -576,7 +576,7 @@ export class CommandPartCreator extends PartCreator {
}
class CommandPart extends PillCandidatePart {
- get type(): IPillCandidatePart["type"] {
+ public get type(): IPillCandidatePart["type"] {
return Type.Command;
}
}
diff --git a/src/effects/confetti/index.ts b/src/effects/confetti/index.ts
index 53e5dda5d2..ae2bb822c2 100644
--- a/src/effects/confetti/index.ts
+++ b/src/effects/confetti/index.ts
@@ -20,34 +20,34 @@ export type ConfettiOptions = {
/**
* max confetti count
*/
- maxCount: number,
+ maxCount: number;
/**
* particle animation speed
*/
- speed: number,
+ speed: number;
/**
* the confetti animation frame interval in milliseconds
*/
- frameInterval: number,
+ frameInterval: number;
/**
* the alpha opacity of the confetti (between 0 and 1, where 1 is opaque and 0 is invisible)
*/
- alpha: number,
+ alpha: number;
/**
* use gradient instead of solid particle color
*/
- gradient: boolean,
+ gradient: boolean;
};
type ConfettiParticle = {
- color: string,
- color2: string,
- x: number,
- y: number,
- diameter: number,
- tilt: number,
- tiltAngleIncrement: number,
- tiltAngle: number,
+ color: string;
+ color2: string;
+ x: number;
+ y: number;
+ diameter: number;
+ tilt: number;
+ tiltAngleIncrement: number;
+ tiltAngle: number;
};
export const DefaultOptions: ConfettiOptions = {
diff --git a/src/emoji.ts b/src/emoji.ts
index 7caeb06d21..321eae63f6 100644
--- a/src/emoji.ts
+++ b/src/emoji.ts
@@ -15,26 +15,23 @@ limitations under the License.
*/
import EMOJIBASE from 'emojibase-data/en/compact.json';
+import SHORTCODES from 'emojibase-data/en/shortcodes/iamcal.json';
export interface IEmoji {
annotation: string;
- group: number;
+ group?: number;
hexcode: string;
- order: number;
+ order?: number;
shortcodes: string[];
- tags: string[];
+ tags?: string[];
unicode: string;
+ skins?: Omit[]; // Currently unused
emoticon?: string;
}
-interface IEmojiWithFilterString extends IEmoji {
- filterString?: string;
-}
-
// The unicode is stored without the variant selector
-const UNICODE_TO_EMOJI = new Map(); // not exported as gets for it are handled by getEmojiFromUnicode
-export const EMOTICON_TO_EMOJI = new Map();
-export const SHORTCODE_TO_EMOJI = new Map();
+const UNICODE_TO_EMOJI = new Map(); // not exported as gets for it are handled by getEmojiFromUnicode
+export const EMOTICON_TO_EMOJI = new Map();
export const getEmojiFromUnicode = unicode => UNICODE_TO_EMOJI.get(stripVariation(unicode));
@@ -62,17 +59,23 @@ export const DATA_BY_CATEGORY = {
"flags": [],
};
-const ZERO_WIDTH_JOINER = "\u200D";
-
// Store various mappings from unicode/emoticon/shortcode to the Emoji objects
-EMOJIBASE.forEach((emoji: IEmojiWithFilterString) => {
+export const EMOJI: IEmoji[] = EMOJIBASE.map((emojiData: Omit) => {
+ // If there's ever a gap in shortcode coverage, we fudge it by
+ // filling it in with the emoji's CLDR annotation
+ const shortcodeData = SHORTCODES[emojiData.hexcode] ??
+ [emojiData.annotation.toLowerCase().replace(/ /g, "_")];
+
+ const emoji: IEmoji = {
+ ...emojiData,
+ // Homogenize shortcodes by ensuring that everything is an array
+ shortcodes: typeof shortcodeData === "string" ? [shortcodeData] : shortcodeData,
+ };
+
const categoryId = EMOJIBASE_GROUP_ID_TO_CATEGORY[emoji.group];
if (DATA_BY_CATEGORY.hasOwnProperty(categoryId)) {
DATA_BY_CATEGORY[categoryId].push(emoji);
}
- // This is used as the string to match the query against when filtering emojis
- emoji.filterString = (`${emoji.annotation}\n${emoji.shortcodes.join('\n')}}\n${emoji.emoticon || ''}\n` +
- `${emoji.unicode.split(ZERO_WIDTH_JOINER).join("\n")}`).toLowerCase();
// Add mapping from unicode to Emoji object
// The 'unicode' field that we use in emojibase has either
@@ -88,12 +91,7 @@ EMOJIBASE.forEach((emoji: IEmojiWithFilterString) => {
EMOTICON_TO_EMOJI.set(emoji.emoticon, emoji);
}
- if (emoji.shortcodes) {
- // Add mapping from each shortcode to Emoji object
- emoji.shortcodes.forEach(shortcode => {
- SHORTCODE_TO_EMOJI.set(shortcode, emoji);
- });
- }
+ return emoji;
});
/**
@@ -107,5 +105,3 @@ EMOJIBASE.forEach((emoji: IEmojiWithFilterString) => {
function stripVariation(str) {
return str.replace(/[\uFE00-\uFE0F]$/, "");
}
-
-export const EMOJI: IEmoji[] = EMOJIBASE;
diff --git a/src/i18n/strings/cs.json b/src/i18n/strings/cs.json
index 27235665aa..266fa339d2 100644
--- a/src/i18n/strings/cs.json
+++ b/src/i18n/strings/cs.json
@@ -3316,5 +3316,89 @@
"If you have permissions, open the menu on any message and select Pin to stick them here.": "Pokud máte oprávnění, otevřete nabídku na libovolné zprávě a výběrem možnosti Připnout je sem vložte.",
"Nothing pinned, yet": "Zatím není nic připnuto",
"End-to-end encryption isn't enabled": "Není povoleno koncové šifrování",
- "Your private messages are normally encrypted, but this room isn't. Usually this is due to an unsupported device or method being used, like email invites. Enable encryption in settings.": "Vaše soukromé zprávy jsou obvykle šifrované, ale tato místnost není. Obvykle je to způsobeno nepodporovaným zařízením nebo použitou metodou, například emailovými pozvánkami. Zapněte šifrování v nastavení."
+ "Your private messages are normally encrypted, but this room isn't. Usually this is due to an unsupported device or method being used, like email invites. Enable encryption in settings.": "Vaše soukromé zprávy jsou obvykle šifrované, ale tato místnost není. Obvykle je to způsobeno nepodporovaným zařízením nebo použitou metodou, například emailovými pozvánkami. Zapněte šifrování v nastavení.",
+ "[number]": "[číslo]",
+ "To view %(spaceName)s, you need an invite": "Pro zobrazení %(spaceName)s potřebujete pozvánku",
+ "You can click on an avatar in the filter panel at any time to see only the rooms and people associated with that community.": "Kliknutím na avatar na panelu filtrů můžete kdykoli zobrazit pouze místnosti a lidi spojené s danou komunitou.",
+ "Move down": "Posun dolů",
+ "Move up": "Posun nahoru",
+ "Report": "Zpráva",
+ "Collapse reply thread": "Sbalit vlákno odpovědi",
+ "Show preview": "Zobrazit náhled",
+ "View source": "Zobrazit zdroj",
+ "Forward": "Vpřed",
+ "Settings - %(spaceName)s": "Nastavení - %(spaceName)s",
+ "Report the entire room": "Nahlásit celou místnost",
+ "Spam or propaganda": "Spam nebo propaganda",
+ "Illegal Content": "Nelegální obsah",
+ "Toxic Behaviour": "Nevhodné chování",
+ "Disagree": "Nesouhlasím",
+ "Please pick a nature and describe what makes this message abusive.": "Vyberte prosím charakter zprávy a popište, v čem je tato zpráva zneužitelná.",
+ "Any other reason. Please describe the problem.\nThis will be reported to the room moderators.": "Jakýkoli jiný důvod. Popište problém.\nTento problém bude nahlášen moderátorům místnosti.",
+ "This room is dedicated to illegal or toxic content or the moderators fail to moderate illegal or toxic content.\n This will be reported to the administrators of %(homeserver)s.": "Tato místnost je věnována nelegálnímu a nevhodnému obsahu nebo moderátoři nedokáží nelegální a nevhodný obsah moderovat.\nTato skutečnost bude nahlášena správcům %(homeserver)s.",
+ "This room is dedicated to illegal or toxic content or the moderators fail to moderate illegal or toxic content.\nThis will be reported to the administrators of %(homeserver)s. The administrators will NOT be able to read the encrypted content of this room.": "Tato místnost je věnována nelegálnímu a nevhodnému obsahu nebo moderátoři nedokáží nelegální a nevhodný obsah moderovat.\nTata skutečnost bude nahlášena správcům %(homeserver)s. Správci NEBUDOU moci číst zašifrovaný obsah této místnosti.",
+ "This user is spamming the room with ads, links to ads or to propaganda.\nThis will be reported to the room moderators.": "Tento uživatel spamuje místnost reklamami, odkazy na reklamy nebo propagandou.\nTato skutečnost bude nahlášena moderátorům místnosti.",
+ "This user is displaying illegal behaviour, for instance by doxing people or threatening violence.\nThis will be reported to the room moderators who may escalate this to legal authorities.": "Tento uživatel se chová nezákonně, například zveřejňuje osobní údaje o cizích lidech nebo vyhrožuje násilím.\nTato skutečnost bude nahlášena moderátorům místnosti, kteří to mohou předat právním orgánům.",
+ "What this user is writing is wrong.\nThis will be reported to the room moderators.": "To, co tento uživatel píše, je špatné.\nTato skutečnost bude nahlášena moderátorům místnosti.",
+ "This user is displaying toxic behaviour, for instance by insulting other users or sharing adult-only content in a family-friendly room or otherwise violating the rules of this room.\nThis will be reported to the room moderators.": "Tento uživatel se chová nevhodně, například uráží ostatní uživatele, sdílí obsah určený pouze pro dospělé v místnosti určené pro rodiny s dětmi nebo jinak porušuje pravidla této místnosti.\nTato skutečnost bude nahlášena moderátorům místnosti.",
+ "Please provide an address": "Uveďte prosím adresu",
+ "%(oneUser)schanged the server ACLs %(count)s times|one": "%(oneUser)szměnil ACL serveru",
+ "%(oneUser)schanged the server ACLs %(count)s times|other": "%(oneUser)szměnil %(count)s krát ACL serveru",
+ "%(severalUsers)schanged the server ACLs %(count)s times|one": "%(severalUsers)szměnili ACL serveru",
+ "%(severalUsers)schanged the server ACLs %(count)s times|other": "%(severalUsers)szměnili %(count)s krát ACL serveru",
+ "Message search initialisation failed, check your settings for more information": "Inicializace vyhledávání zpráv se nezdařila, zkontrolujte svá nastavení",
+ "Set addresses for this space so users can find this space through your homeserver (%(localDomain)s)": "Nastavte adresy pro tento prostor, aby jej uživatelé mohli najít prostřednictvím domovského serveru (%(localDomain)s)",
+ "To publish an address, it needs to be set as a local address first.": "Chcete-li adresu zveřejnit, je třeba ji nejprve nastavit jako místní adresu.",
+ "Published addresses can be used by anyone on any server to join your room.": "Zveřejněné adresy může použít kdokoli na jakémkoli serveru, aby se připojil k vaší místnosti.",
+ "Published addresses can be used by anyone on any server to join your space.": "Zveřejněné adresy může použít kdokoli na jakémkoli serveru, aby se připojil k vašemu prostoru.",
+ "This space has no local addresses": "Tento prostor nemá žádné místní adresy",
+ "Space information": "Informace o prostoru",
+ "Collapse": "Sbalit",
+ "Expand": "Rozbalit",
+ "Recommended for public spaces.": "Doporučeno pro veřejné prostory.",
+ "Allow people to preview your space before they join.": "Umožněte lidem prohlédnout si váš prostor ještě předtím, než se připojí.",
+ "Preview Space": "Nahlédnout do prostoru",
+ "only invited people can view and join": "prohlížet a připojit se mohou pouze pozvané osoby",
+ "anyone with the link can view and join": "kdokoli s odkazem může prohlížet a připojit se",
+ "Decide who can view and join %(spaceName)s.": "Rozhodněte, kdo může prohlížet a připojovat se k %(spaceName)s.",
+ "This may be useful for public spaces.": "To může být užitečné pro veřejné prostory.",
+ "Guests can join a space without having an account.": "Hosté se mohou připojit k prostoru, aniž by měli účet.",
+ "Enable guest access": "Povolit přístup hostům",
+ "Failed to update the history visibility of this space": "Nepodařilo se aktualizovat viditelnost historie tohoto prostoru",
+ "Failed to update the guest access of this space": "Nepodařilo se aktualizovat přístup hosta do tohoto prostoru",
+ "Failed to update the visibility of this space": "Nepodařilo se aktualizovat viditelnost tohoto prostoru",
+ "e.g. my-space": "např. můj-prostor",
+ "Silence call": "Tiché volání",
+ "Sound on": "Zvuk zapnutý",
+ "Show notification badges for People in Spaces": "Zobrazit odznaky oznámení v Lidi v prostorech",
+ "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.": "Pokud je zakázáno, můžete stále přidávat přímé zprávy do osobních prostorů. Pokud je povoleno, automaticky se zobrazí všichni, kteří jsou členy daného prostoru.",
+ "Show all rooms in Home": "Zobrazit všechny místnosti na domácí obrazovce",
+ "Show people in spaces": "Zobrazit lidi v prostorech",
+ "Report to moderators prototype. In rooms that support moderation, the `report` button will let you report abuse to room moderators": "Prototyp Nahlášování moderátorům. V místnostech, které podporují moderování, vám tlačítko `nahlásit` umožní nahlásit zneužití moderátorům místnosti",
+ "%(senderName)s changed the pinned messages for the room.": "%(senderName)s změnil(a) připnuté zprávy v místnosti.",
+ "%(senderName)s kicked %(targetName)s": "%(senderName)s vykopl(a) uživatele %(targetName)s",
+ "%(senderName)s kicked %(targetName)s: %(reason)s": "%(senderName)s vykopl(a) uživatele %(targetName)s: %(reason)s",
+ "%(senderName)s withdrew %(targetName)s's invitation": "%(senderName)s zrušil(a) pozvání pro uživatele %(targetName)s",
+ "%(senderName)s withdrew %(targetName)s's invitation: %(reason)s": "%(senderName)s zrušil(a) pozvání pro uživatele %(targetName)s: %(reason)s",
+ "%(senderName)s unbanned %(targetName)s": "%(senderName)s přijal(a) zpět uživatele %(targetName)s",
+ "%(targetName)s left the room": "%(targetName)s opustil(a) místnost",
+ "%(targetName)s left the room: %(reason)s": "%(targetName)s opustil(a) místnost: %(reason)s",
+ "%(targetName)s rejected the invitation": "%(targetName)s odmítl(a) pozvání",
+ "%(targetName)s joined the room": "%(targetName)s vstoupil(a) do místnosti",
+ "%(senderName)s made no change": "%(senderName)s neprovedl(a) žádnou změnu",
+ "%(senderName)s set a profile picture": "%(senderName)s si nastavil(a) profilový obrázek",
+ "%(senderName)s changed their profile picture": "%(senderName)s změnil(a) svůj profilový obrázek",
+ "%(senderName)s removed their profile picture": "%(senderName)s odstranil(a) svůj profilový obrázek",
+ "%(senderName)s removed their display name (%(oldDisplayName)s)": "%(senderName)s odstranil(a) své zobrazované jméno (%(oldDisplayName)s)",
+ "%(senderName)s set their display name to %(displayName)s": "%(senderName)s si změnil(a) zobrazované jméno na %(displayName)s",
+ "%(oldDisplayName)s changed their display name to %(displayName)s": "%(oldDisplayName)s si změnil(a) zobrazované jméno na %(displayName)s",
+ "%(senderName)s banned %(targetName)s": "%(senderName)s vykázal(a) %(targetName)s",
+ "%(senderName)s banned %(targetName)s: %(reason)s": "%(senderName)s vykázal(a) %(targetName)s: %(reason)s",
+ "%(senderName)s invited %(targetName)s": "%(senderName)s pozval(a) %(targetName)s",
+ "%(targetName)s accepted an invitation": "%(targetName)s přijal(a) pozvání",
+ "%(targetName)s accepted the invitation for %(displayName)s": "%(targetName)s přijal(a) pozvání do %(displayName)s",
+ "Some invites couldn't be sent": "Některé pozvánky nebylo možné odeslat",
+ "We sent the others, but the below people couldn't be invited to ": "Poslali jsme ostatním, ale níže uvedení lidé nemohli být pozváni do ",
+ "Visibility": "Viditelnost",
+ "Address": "Adresa"
}
diff --git a/src/i18n/strings/de_DE.json b/src/i18n/strings/de_DE.json
index c09b92dcbc..1def5b300e 100644
--- a/src/i18n/strings/de_DE.json
+++ b/src/i18n/strings/de_DE.json
@@ -708,7 +708,7 @@
"Messages containing keywords": "Nachrichten mit Schlüsselwörtern",
"Error saving email notification preferences": "Fehler beim Speichern der E-Mail-Benachrichtigungseinstellungen",
"Tuesday": "Dienstag",
- "Enter keywords separated by a comma:": "Gib die Schlüsselwörter durch einen Beistrich getrennt ein:",
+ "Enter keywords separated by a comma:": "Gib die Schlüsselwörter durch ein Komma getrennt ein:",
"Forward Message": "Weiterleiten",
"You have successfully set a password and an email address!": "Du hast erfolgreich ein Passwort und eine E-Mail-Adresse gesetzt!",
"Remove %(name)s from the directory?": "Soll der Raum %(name)s aus dem Verzeichnis entfernt werden?",
@@ -734,7 +734,7 @@
"Invite to this room": "In diesen Raum einladen",
"Wednesday": "Mittwoch",
"You cannot delete this message. (%(code)s)": "Diese Nachricht kann nicht gelöscht werden. (%(code)s)",
- "Quote": "Zitat",
+ "Quote": "Zitieren",
"Send logs": "Protokolldateien übermitteln",
"All messages": "Alle Nachrichten",
"Call invitation": "Anrufe",
@@ -786,7 +786,7 @@
"Every page you use in the app": "Jede Seite, die du in der App benutzt",
"e.g. ": "z. B. ",
"Your device resolution": "Deine Bildschirmauflösung",
- "Popout widget": "Widget ausklinken",
+ "Popout widget": "Widget in eigenem Fenster öffnen",
"Always show encryption icons": "Immer Verschlüsselungssymbole zeigen",
"Unable to load event that was replied to, it either does not exist or you do not have permission to view it.": "Das Ereignis, auf das geantwortet wurde, konnte nicht geladen werden. Entweder es existiert nicht oder du hast keine Berechtigung, dieses anzusehen.",
"Send Logs": "Sende Protokoll",
@@ -1760,10 +1760,10 @@
"%(brand)s is missing some components required for securely caching encrypted messages locally. If you'd like to experiment with this feature, build a custom %(brand)s Desktop with search components added.": "Um verschlüsselte Nachrichten lokal zu durchsuchen, benötigt %(brand)s weitere Komponenten. Wenn du diese Funktion testen möchtest, kannst du dir deine eigene Version von %(brand)s Desktop mit der integrierten Suchfunktion kompilieren.",
"Backup has a valid signature from this user": "Die Sicherung hat eine gültige Signatur dieses Benutzers",
"Backup has a invalid signature from this user": "Die Sicherung hat eine ungültige Signatur von diesem Benutzer",
- "Backup has a valid signature from verified session ": "Die Sicherung hat eine gültige Signatur von einer verifizierten Sitzung ",
- "Backup has a valid signature from unverified session ": "Die Sicherung hat eine gültige Signatur von einer nicht verifizierten Sitzung ",
- "Backup has an invalid signature from verified session ": "Die Sicherung hat eine ungültige Signatur von einer verifizierten Sitzung ",
- "Backup has an invalid signature from unverified session ": "Die Sicherung hat eine ungültige Signatur von einer nicht verifizierten Sitzung ",
+ "Backup has a valid signature from verified session ": "Die Sicherung hat eine gültige Signatur von der verifizierten Sitzung \"\"",
+ "Backup has a valid signature from unverified session ": "Die Sicherung hat eine gültige Signatur von der nicht verifizierten Sitzung \"\"",
+ "Backup has an invalid signature from verified session ": "Die Sicherung hat eine ungültige Signatur von der verifizierten Sitzung \"\"",
+ "Backup has an invalid signature from unverified session ": "Die Sicherung hat eine ungültige Signatur von der nicht verifizierten Sitzung \"\"",
"Your keys are not being backed up from this session.": "Deine Schlüssel werden von dieser Sitzung nicht gesichert.",
"You are currently using to discover and be discoverable by existing contacts you know. You can change your identity server below.": "Zur Zeit verwendest du , um Kontakte zu finden und von anderen gefunden zu werden. Du kannst deinen Identitätsserver weiter unten ändern.",
"Invalid theme schema.": "Ungültiges Designschema.",
@@ -3123,7 +3123,7 @@
"Add some details to help people recognise it.": "Gib einige Infos über deinen neuen Space an.",
"Spaces are new ways to group rooms and people. To join an existing space you'll need an invite.": "Mit Matrix-Spaces kannst du Räume und Personen gruppieren. Um einen existierenden Space zu betreten, musst du eingeladen werden.",
"Spaces prototype. Incompatible with Communities, Communities v2 and Custom Tags. Requires compatible homeserver for some features.": "Spaces Prototyp. Inkompatibel mit Communities, Communities v2 und benutzerdefinierte Tags. Für einige Funktionen wird ein kompatibler Heimserver benötigt.",
- "Invite to this space": "In diesen Space enladen",
+ "Invite to this space": "In diesen Space einladen",
"Verify this login to access your encrypted messages and prove to others that this login is really you.": "Verifiziere diese Anmeldung um deine Identität zu bestätigen und Zugriff auf verschlüsselte Nachrichten zu erhalten.",
"What projects are you working on?": "An welchen Projekten arbeitest du gerade?",
"Failed to invite the following users to your space: %(csvUsers)s": "Die folgenden Leute konnten nicht eingeladen werden: %(csvUsers)s",
@@ -3372,5 +3372,85 @@
"Teammates might not be able to view or join any private rooms you make.": "Mitglieder werden private Räume möglicherweise weder sehen noch betreten können.",
"Error - Mixed content": "Fehler - Uneinheitlicher Inhalt",
"Kick, ban, or invite people to your active room, and make you leave": "Den aktiven Raum verlassen, Leute einladen, kicken oder bannen",
- "Kick, ban, or invite people to this room, and make you leave": "Diesen Raum verlassen, Leute einladen, kicken oder bannen"
+ "Kick, ban, or invite people to this room, and make you leave": "Diesen Raum verlassen, Leute einladen, kicken oder bannen",
+ "View source": "Rohdaten anzeigen",
+ "What this user is writing is wrong.\nThis will be reported to the room moderators.": "Die Person verbreitet Falschinformation.\nDies wird an die Raummoderation gemeldet.",
+ "[number]": "[Nummer]",
+ "To view %(spaceName)s, you need an invite": "Du musst eingeladen sein, um %(spaceName)s zu sehen",
+ "Move down": "Nach unten",
+ "Move up": "Nach oben",
+ "Report": "Melden",
+ "Collapse reply thread": "Antworten verbergen",
+ "Show preview": "Vorschau zeigen",
+ "Forward": "Weiterleiten",
+ "Settings - %(spaceName)s": "Einstellungen - %(spaceName)s",
+ "Report the entire room": "Den ganzen Raum melden",
+ "Spam or propaganda": "Spam oder Propaganda",
+ "Illegal Content": "Illegale Inhalte",
+ "Toxic Behaviour": "Toxisches Verhalten",
+ "Disagree": "Ablehnen",
+ "Please pick a nature and describe what makes this message abusive.": "Bitte wähle eine Kategorie aus und beschreibe, was die Nachricht missbräuchlich macht.",
+ "Any other reason. Please describe the problem.\nThis will be reported to the room moderators.": "Anderer Grund. Bitte beschreibe das Problem.\nDies wird an die Raummoderation gemeldet.",
+ "This user is spamming the room with ads, links to ads or to propaganda.\nThis will be reported to the room moderators.": "Dieser Benutzer spammt den Raum mit Werbung, Links zu Werbung oder Propaganda.\nDies wird an die Raummoderation gemeldet.",
+ "This user is displaying toxic behaviour, for instance by insulting other users or sharing adult-only content in a family-friendly room or otherwise violating the rules of this room.\nThis will be reported to the room moderators.": "Dieser Benutzer zeigt toxisches Verhalten. Darunter fällt unter anderem Beleidigen anderer Personen, Teilen von NSFW-Inhalten in familienfreundlichen Räumen oder das anderwertige Missachten von Regeln des Raumes.\nDies wird an die Raum-Mods gemeldet.",
+ "Please provide an address": "Bitte gib eine Adresse an",
+ "%(oneUser)schanged the server ACLs %(count)s times|one": "%(oneUser)s hat die Server-ACLs geändert",
+ "%(oneUser)schanged the server ACLs %(count)s times|other": "%(oneUser)s hat die Server-ACLs %(count)s-mal geändert",
+ "%(severalUsers)schanged the server ACLs %(count)s times|one": "%(severalUsers)s haben die Server-ACLs geändert",
+ "%(severalUsers)schanged the server ACLs %(count)s times|other": "%(severalUsers)s haben die Server-ACLs %(count)s-mal geändert",
+ "Set addresses for this space so users can find this space through your homeserver (%(localDomain)s)": "Füge Adressen für diesen Space hinzu, damit andere Leute ihn über deinen Homeserver (%(localDomain)s) finden können",
+ "To publish an address, it needs to be set as a local address first.": "Damit du die Adresse veröffentlichen kannst, musst du sie zuerst als lokale Adresse hinzufügen.",
+ "Published addresses can be used by anyone on any server to join your room.": "Veröffentlichte Adressen erlauben jedem, dem Raum beizutreten.",
+ "Published addresses can be used by anyone on any server to join your space.": "Veröffentlichte Adressen erlauben jedem, dem Space beizutreten.",
+ "This space has no local addresses": "Dieser Space hat keine lokale Adresse",
+ "Space information": "Information über den Space",
+ "Collapse": "Verbergen",
+ "Expand": "Erweitern",
+ "Recommended for public spaces.": "Empfohlen für öffentliche Spaces.",
+ "Allow people to preview your space before they join.": "Personen können den Space vor dem Beitreten erkunden.",
+ "Preview Space": "Space-Vorschau erlauben",
+ "only invited people can view and join": "Nur eingeladene Personen können beitreten",
+ "anyone with the link can view and join": "Alle, die den Einladungslink besitzen, können beitreten",
+ "Decide who can view and join %(spaceName)s.": "Konfiguriere, wer %(spaceName)s sehen und beitreten kann.",
+ "Visibility": "Sichtbarkeit",
+ "This may be useful for public spaces.": "Sinnvoll für öffentliche Spaces.",
+ "Guests can join a space without having an account.": "Gäste ohne Account können den Space betreten.",
+ "Enable guest access": "Gastzugriff",
+ "Failed to update the history visibility of this space": "Verlaufssichtbarkeit des Space konnte nicht geändert werden",
+ "Failed to update the guest access of this space": "Gastzugriff des Space konnte nicht geändert werden",
+ "Failed to update the visibility of this space": "Sichtbarkeit des Space konnte nicht geändert werden",
+ "Address": "Adresse",
+ "e.g. my-space": "z.B. Mein-Space",
+ "Sound on": "Ton an",
+ "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.": "Falls deaktiviert, kannst du trotzdem Direktnachrichten in privaten Spaces hinzufügen. Falls aktiviert, wirst du alle Mitglieder des Spaces sehen.",
+ "Show people in spaces": "Personen in Spaces anzeigen",
+ "Show all rooms in Home": "Alle Räume auf der Startseite zeigen",
+ "Report to moderators prototype. In rooms that support moderation, the `report` button will let you report abuse to room moderators": "Inhalte an Mods melden. In Räumen, die Moderation unterstützen, kannst du so unerwünschte Inhalte direkt der Raummoderation melden",
+ "%(senderName)s changed the pinned messages for the room.": "%(senderName)s hat die angehefteten Nachrichten geändert.",
+ "%(senderName)s kicked %(targetName)s": "%(senderName)s hat %(targetName)s gekickt",
+ "%(senderName)s kicked %(targetName)s: %(reason)s": "%(senderName)s hat %(targetName)s gekickt: %(reason)s",
+ "%(senderName)s withdrew %(targetName)s's invitation": "%(senderName)s hat die Einladung für %(targetName)s zurückgezogen",
+ "%(senderName)s withdrew %(targetName)s's invitation: %(reason)s": "%(senderName)s hat die Einladung für %(targetName)s zurückgezogen: %(reason)s",
+ "%(senderName)s unbanned %(targetName)s": "%(senderName)s hat %(targetName)s entbannt",
+ "%(targetName)s left the room": "%(targetName)s hat den Raum verlassen",
+ "%(targetName)s left the room: %(reason)s": "%(targetName)s hat den Raum verlassen: %(reason)s",
+ "%(targetName)s rejected the invitation": "%(targetName)s hat die Einladung abgelehnt",
+ "%(targetName)s joined the room": "%(targetName)s hat den Raum betreten",
+ "%(senderName)s made no change": "%(senderName)s hat keine Änderungen gemacht",
+ "%(senderName)s set a profile picture": "%(senderName)s hat das Profilbild gesetzt",
+ "%(senderName)s changed their profile picture": "%(senderName)s hat das Profilbild geändert",
+ "%(senderName)s removed their profile picture": "%(senderName)s hat das Profilbild entfernt",
+ "%(senderName)s removed their display name (%(oldDisplayName)s)": "%(senderName)s hat den alten Nicknamen %(oldDisplayName)s entfernt",
+ "%(senderName)s set their display name to %(displayName)s": "%(senderName)s hat den Nicknamen zu %(displayName)s geändert",
+ "%(oldDisplayName)s changed their display name to %(displayName)s": "%(oldDisplayName)s hat den Nicknamen zu%(displayName)s geändert",
+ "%(senderName)s banned %(targetName)s": "%(senderName)s hat %(targetName)s gebannt",
+ "%(senderName)s banned %(targetName)s: %(reason)s": "%(senderName)s hat %(targetName)s gebannt: %(reason)s",
+ "%(targetName)s accepted an invitation": "%(targetName)s hat die Einladung akzeptiert",
+ "%(targetName)s accepted the invitation for %(displayName)s": "%(targetName)s hat die Einladung für %(displayName)s akzeptiert",
+ "Some invites couldn't be sent": "Einige Einladungen konnten nicht versendet werden",
+ "We sent the others, but the below people couldn't be invited to ": "Die anderen wurden gesendet, aber die folgenden Leute konnten leider nicht in eingeladen werden",
+ "Message search initialisation failed, check your settings for more information": "Initialisierung der Nachrichtensuche fehlgeschlagen. Öffne die Einstellungen für mehr Information.",
+ "This room is dedicated to illegal or toxic content or the moderators fail to moderate illegal or toxic content.\n This will be reported to the administrators of %(homeserver)s.": "Der Raum beinhaltet illegale oder toxische Nachrichten und die Raummoderation verhindert es nicht.\nDies wird an die Betreiber von %(homeserver)s gemeldet werden.",
+ "This room is dedicated to illegal or toxic content or the moderators fail to moderate illegal or toxic content.\nThis will be reported to the administrators of %(homeserver)s. The administrators will NOT be able to read the encrypted content of this room.": "Der Raum beinhaltet illegale oder toxische Nachrichten und die Raummoderation verhindert es nicht.\nDies wird an die Betreiber von %(homeserver)s gemeldet werden. Diese können jedoch die verschlüsselten Nachrichten nicht lesen.",
+ "This user is displaying illegal behaviour, for instance by doxing people or threatening violence.\nThis will be reported to the room moderators who may escalate this to legal authorities.": "Diese Person zeigt illegales Verhalten, beispielsweise das Leaken persönlicher Daten oder Gewaltdrohungen.\nDies wird an die Raummoderation gemeldet, welche dies an die Justiz weitergeben kann."
}
diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json
index f0599c7e49..1116e4cdc1 100644
--- a/src/i18n/strings/en_EN.json
+++ b/src/i18n/strings/en_EN.json
@@ -65,6 +65,9 @@
"You cannot place a call with yourself.": "You cannot place a call with yourself.",
"Unable to look up phone number": "Unable to look up phone number",
"There was an error looking up the phone number": "There was an error looking up the phone number",
+ "Unable to transfer call": "Unable to transfer call",
+ "Transfer Failed": "Transfer Failed",
+ "Failed to transfer call": "Failed to transfer call",
"Call in Progress": "Call in Progress",
"A call is currently being placed!": "A call is currently being placed!",
"Permission Required": "Permission Required",
@@ -538,22 +541,8 @@
"%(senderName)s changed the alternative addresses for this room.": "%(senderName)s changed the alternative addresses for this room.",
"%(senderName)s changed the main and alternative addresses for this room.": "%(senderName)s changed the main and alternative addresses for this room.",
"%(senderName)s changed the addresses for this room.": "%(senderName)s changed the addresses for this room.",
- "Someone": "Someone",
- "(not supported by this browser)": "(not supported by this browser)",
- "%(senderName)s answered the call.": "%(senderName)s answered the call.",
- "(could not connect media)": "(could not connect media)",
- "(connection failed)": "(connection failed)",
- "(their device couldn't start the camera / microphone)": "(their device couldn't start the camera / microphone)",
- "(an error occurred)": "(an error occurred)",
- "(no answer)": "(no answer)",
- "(unknown failure: %(reason)s)": "(unknown failure: %(reason)s)",
- "%(senderName)s ended the call.": "%(senderName)s ended the call.",
- "%(senderName)s declined the call.": "%(senderName)s declined the call.",
- "%(senderName)s placed a voice call.": "%(senderName)s placed a voice call.",
- "%(senderName)s placed a voice call. (not supported by this browser)": "%(senderName)s placed a voice call. (not supported by this browser)",
- "%(senderName)s placed a video call.": "%(senderName)s placed a video call.",
- "%(senderName)s placed a video call. (not supported by this browser)": "%(senderName)s placed a video call. (not supported by this browser)",
"%(senderName)s revoked the invitation for %(targetDisplayName)s to join the room.": "%(senderName)s revoked the invitation for %(targetDisplayName)s to join the room.",
+ "Someone": "Someone",
"%(senderName)s sent an invitation to %(targetDisplayName)s to join the room.": "%(senderName)s sent an invitation to %(targetDisplayName)s to join the room.",
"%(senderName)s made future room history visible to all room members, from the point they are invited.": "%(senderName)s made future room history visible to all room members, from the point they are invited.",
"%(senderName)s made future room history visible to all room members, from the point they joined.": "%(senderName)s made future room history visible to all room members, from the point they joined.",
@@ -668,6 +657,7 @@
"This homeserver has exceeded one of its resource limits.": "This homeserver has exceeded one of its resource limits.",
"Please contact your service administrator to continue using the service.": "Please contact your service administrator to continue using the service.",
"Unable to connect to Homeserver. Retrying...": "Unable to connect to Homeserver. Retrying...",
+ "Attachment": "Attachment",
"%(items)s and %(count)s others|other": "%(items)s and %(count)s others",
"%(items)s and %(count)s others|one": "%(items)s and one other",
"%(items)s and %(lastItem)s": "%(items)s and %(lastItem)s",
@@ -695,6 +685,7 @@
"Error leaving room": "Error leaving room",
"Unrecognised address": "Unrecognised address",
"You do not have permission to invite people to this room.": "You do not have permission to invite people to this room.",
+ "User %(userId)s is already invited to the room": "User %(userId)s is already invited to the room",
"User %(userId)s is already in the room": "User %(userId)s is already in the room",
"User %(user_id)s does not exist": "User %(user_id)s does not exist",
"User %(user_id)s may or may not exist": "User %(user_id)s may or may not exist",
@@ -819,6 +810,7 @@
"Offline encrypted messaging using dehydrated devices": "Offline encrypted messaging using dehydrated devices",
"Enable advanced debugging for the room list": "Enable advanced debugging for the room list",
"Show info about bridges in room settings": "Show info about bridges in room settings",
+ "New layout switcher (with message bubbles)": "New layout switcher (with message bubbles)",
"Font size": "Font size",
"Use custom size": "Use custom size",
"Enable Emoji suggestions while typing": "Enable Emoji suggestions while typing",
@@ -840,8 +832,8 @@
"Enable big emoji in chat": "Enable big emoji in chat",
"Send typing notifications": "Send typing notifications",
"Show typing notifications": "Show typing notifications",
- "Use Command + F to search": "Use Command + F to search",
- "Use Ctrl + F to search": "Use Ctrl + F to search",
+ "Use Command + F to search timeline": "Use Command + F to search timeline",
+ "Use Ctrl + F to search timeline": "Use Ctrl + F to search timeline",
"Use Command + Enter to send a message": "Use Command + Enter to send a message",
"Use Ctrl + Enter to send a message": "Use Ctrl + Enter to send a message",
"Automatically replace plain text Emoji": "Automatically replace plain text Emoji",
@@ -909,7 +901,6 @@
"Fill Screen": "Fill Screen",
"Return to call": "Return to call",
"%(name)s on hold": "%(name)s on hold",
- "Dial pad": "Dial pad",
"Unknown caller": "Unknown caller",
"Incoming voice call": "Incoming voice call",
"Incoming video call": "Incoming video call",
@@ -918,19 +909,11 @@
"Silence call": "Silence call",
"Decline": "Decline",
"Accept": "Accept",
- "Pause": "Pause",
- "Play": "Play",
"The other party cancelled the verification.": "The other party cancelled the verification.",
"Verified!": "Verified!",
"You've successfully verified this user.": "You've successfully verified this user.",
"Secure messages with this user are end-to-end encrypted and not able to be read by third parties.": "Secure messages with this user are end-to-end encrypted and not able to be read by third parties.",
"Got It": "Got It",
- "Verify this session by completing one of the following:": "Verify this session by completing one of the following:",
- "Scan this unique code": "Scan this unique code",
- "or": "or",
- "Compare unique emoji": "Compare unique emoji",
- "Compare a unique set of emoji if you don't have a camera on either device": "Compare a unique set of emoji if you don't have a camera on either device",
- "Start": "Start",
"Confirm the emoji below are displayed on both sessions, in the same order:": "Confirm the emoji below are displayed on both sessions, in the same order:",
"Verify this user by confirming the following emoji appear on their screen.": "Verify this user by confirming the following emoji appear on their screen.",
"Verify this session by confirming the following number appears on its screen.": "Verify this session by confirming the following number appears on its screen.",
@@ -1138,33 +1121,24 @@
"Connecting to integration manager...": "Connecting to integration manager...",
"Cannot connect to integration manager": "Cannot connect to integration manager",
"The integration manager is offline or it cannot reach your homeserver.": "The integration manager is offline or it cannot reach your homeserver.",
- "Error saving email notification preferences": "Error saving email notification preferences",
- "An error occurred whilst saving your email notification preferences.": "An error occurred whilst saving your email notification preferences.",
- "Keywords": "Keywords",
- "Enter keywords separated by a comma:": "Enter keywords separated by a comma:",
- "Failed to change settings": "Failed to change settings",
- "Can't update user notification settings": "Can't update user notification settings",
- "Failed to update keywords": "Failed to update keywords",
- "Messages containing keywords": "Messages containing keywords",
- "Notify for all other messages/rooms": "Notify for all other messages/rooms",
- "Notify me for anything else": "Notify me for anything else",
- "Enable notifications for this account": "Enable notifications for this account",
- "Clear notifications": "Clear notifications",
- "All notifications are currently disabled for all targets.": "All notifications are currently disabled for all targets.",
- "Enable email notifications": "Enable email notifications",
- "Add an email address to configure email notifications": "Add an email address to configure email notifications",
- "Notifications on the following keywords follow rules which can’t be displayed here:": "Notifications on the following keywords follow rules which can’t be displayed here:",
- "Unable to fetch notification target list": "Unable to fetch notification target list",
- "Notification targets": "Notification targets",
- "Advanced notification settings": "Advanced notification settings",
- "There are advanced notifications which are not shown here.": "There are advanced notifications which are not shown here.",
- "You might have configured them in a client other than %(brand)s. You cannot tune them in %(brand)s but they still apply.": "You might have configured them in a client other than %(brand)s. You cannot tune them in %(brand)s but they still apply.",
+ "Messages containing keywords": "Messages containing keywords",
+ "Error saving notification preferences": "Error saving notification preferences",
+ "An error occurred whilst saving your notification preferences.": "An error occurred whilst saving your notification preferences.",
+ "Enable for this account": "Enable for this account",
+ "Enable email notifications for %(email)s": "Enable email notifications for %(email)s",
"Enable desktop notifications for this session": "Enable desktop notifications for this session",
"Show message in desktop notification": "Show message in desktop notification",
"Enable audible notifications for this session": "Enable audible notifications for this session",
+ "Clear notifications": "Clear notifications",
+ "Keyword": "Keyword",
+ "New keyword": "New keyword",
+ "Global": "Global",
+ "Mentions & keywords": "Mentions & keywords",
"Off": "Off",
"On": "On",
"Noisy": "Noisy",
+ "Notification targets": "Notification targets",
+ "There was an error loading your notification settings.": "There was an error loading your notification settings.",
"Failed to save your profile": "Failed to save your profile",
"The operation could not be completed": "The operation could not be completed",
"Upgrade to your own domain": "Upgrade to your own domain",
@@ -1209,9 +1183,9 @@
"Secret storage:": "Secret storage:",
"ready": "ready",
"not ready": "not ready",
- "Identity Server URL must be HTTPS": "Identity Server URL must be HTTPS",
- "Not a valid Identity Server (status code %(code)s)": "Not a valid Identity Server (status code %(code)s)",
- "Could not connect to Identity Server": "Could not connect to Identity Server",
+ "Identity server URL must be HTTPS": "Identity server URL must be HTTPS",
+ "Not a valid identity server (status code %(code)s)": "Not a valid identity server (status code %(code)s)",
+ "Could not connect to identity server": "Could not connect to identity server",
"Checking server": "Checking server",
"Change identity server": "Change identity server",
"Disconnect from the identity server and connect to instead?": "Disconnect from the identity server and connect to instead?",
@@ -1228,20 +1202,20 @@
"Disconnect anyway": "Disconnect anyway",
"You are still sharing your personal data on the identity server .": "You are still sharing your personal data on the identity server .",
"We recommend that you remove your email addresses and phone numbers from the identity server before disconnecting.": "We recommend that you remove your email addresses and phone numbers from the identity server before disconnecting.",
- "Identity Server (%(server)s)": "Identity Server (%(server)s)",
+ "Identity server (%(server)s)": "Identity server (%(server)s)",
"You are currently using to discover and be discoverable by existing contacts you know. You can change your identity server below.": "You are currently using to discover and be discoverable by existing contacts you know. You can change your identity server below.",
"If you don't want to use to discover and be discoverable by existing contacts you know, enter another identity server below.": "If you don't want to use to discover and be discoverable by existing contacts you know, enter another identity server below.",
- "Identity Server": "Identity Server",
+ "Identity server": "Identity server",
"You are not currently using an identity server. To discover and be discoverable by existing contacts you know, add one below.": "You are not currently using an identity server. To discover and be discoverable by existing contacts you know, add one below.",
"Disconnecting from your identity server will mean you won't be discoverable by other users and you won't be able to invite others by email or phone.": "Disconnecting from your identity server will mean you won't be discoverable by other users and you won't be able to invite others by email or phone.",
"Using an identity server is optional. If you choose not to use an identity server, you won't be discoverable by other users and you won't be able to invite others by email or phone.": "Using an identity server is optional. If you choose not to use an identity server, you won't be discoverable by other users and you won't be able to invite others by email or phone.",
"Do not use an identity server": "Do not use an identity server",
"Enter a new identity server": "Enter a new identity server",
"Change": "Change",
- "Use an Integration Manager (%(serverName)s) to manage bots, widgets, and sticker packs.": "Use an Integration Manager (%(serverName)s) to manage bots, widgets, and sticker packs.",
- "Use an Integration Manager to manage bots, widgets, and sticker packs.": "Use an Integration Manager to manage bots, widgets, and sticker packs.",
+ "Use an integration manager (%(serverName)s) to manage bots, widgets, and sticker packs.": "Use an integration manager (%(serverName)s) to manage bots, widgets, and sticker packs.",
+ "Use an integration manager to manage bots, widgets, and sticker packs.": "Use an integration manager to manage bots, widgets, and sticker packs.",
"Manage integrations": "Manage integrations",
- "Integration Managers receive configuration data, and can modify widgets, send room invites, and set power levels on your behalf.": "Integration Managers receive configuration data, and can modify widgets, send room invites, and set power levels on your behalf.",
+ "Integration managers receive configuration data, and can modify widgets, send room invites, and set power levels on your behalf.": "Integration managers receive configuration data, and can modify widgets, send room invites, and set power levels on your behalf.",
"Add": "Add",
"Error encountered (%(errorDetail)s).": "Error encountered (%(errorDetail)s).",
"Checking for an update...": "Checking for an update...",
@@ -1259,6 +1233,10 @@
"Custom theme URL": "Custom theme URL",
"Add theme": "Add theme",
"Theme": "Theme",
+ "Message layout": "Message layout",
+ "IRC": "IRC",
+ "Modern": "Modern",
+ "Message bubbles": "Message bubbles",
"Set the name of a font installed on your system & %(brand)s will attempt to use it.": "Set the name of a font installed on your system & %(brand)s will attempt to use it.",
"Enable experimental, compact IRC style layout": "Enable experimental, compact IRC style layout",
"Customise your appearance": "Customise your appearance",
@@ -1295,7 +1273,7 @@
"%(brand)s version:": "%(brand)s version:",
"olm version:": "olm version:",
"Homeserver is": "Homeserver is",
- "Identity Server is": "Identity Server is",
+ "Identity server is": "Identity server is",
"Access Token": "Access Token",
"Your access token gives full access to your account. Do not share it with anyone.": "Your access token gives full access to your account. Do not share it with anyone.",
"Copy": "Copy",
@@ -1341,7 +1319,12 @@
"Show tray icon and minimize window to it on close": "Show tray icon and minimize window to it on close",
"Preferences": "Preferences",
"Room list": "Room list",
+ "Keyboard shortcuts": "Keyboard shortcuts",
+ "To view all keyboard shortcuts, click here.": "To view all keyboard shortcuts, click here.",
+ "Displaying time": "Displaying time",
"Composer": "Composer",
+ "Code blocks": "Code blocks",
+ "Images, GIFs and videos": "Images, GIFs and videos",
"Timeline": "Timeline",
"Autocomplete delay (ms)": "Autocomplete delay (ms)",
"Read Marker lifetime (ms)": "Read Marker lifetime (ms)",
@@ -1367,17 +1350,17 @@
"Where you’re logged in": "Where you’re logged in",
"Manage the names of and sign out of your sessions below or verify them in your User Profile.": "Manage the names of and sign out of your sessions below or verify them in your User Profile.",
"A session's public name is visible to people you communicate with": "A session's public name is visible to people you communicate with",
+ "Default Device": "Default Device",
"No media permissions": "No media permissions",
"You may need to manually permit %(brand)s to access your microphone/webcam": "You may need to manually permit %(brand)s to access your microphone/webcam",
"Missing media permissions, click the button below to request.": "Missing media permissions, click the button below to request.",
"Request media permissions": "Request media permissions",
- "No Audio Outputs detected": "No Audio Outputs detected",
- "No Microphones detected": "No Microphones detected",
- "No Webcams detected": "No Webcams detected",
- "Default Device": "Default Device",
"Audio Output": "Audio Output",
+ "No Audio Outputs detected": "No Audio Outputs detected",
"Microphone": "Microphone",
+ "No Microphones detected": "No Microphones detected",
"Camera": "Camera",
+ "No Webcams detected": "No Webcams detected",
"Voice & Video": "Voice & Video",
"This room is not accessible by remote Matrix servers": "This room is not accessible by remote Matrix servers",
"Warning: Upgrading a room will not automatically migrate room members to the new version of the room. We'll post a link to the new room in the old version of the room - room members will have to click this link to join the new room.": "Warning: Upgrading a room will not automatically migrate room members to the new version of the room. We'll post a link to the new room in the old version of the room - room members will have to click this link to join the new room.",
@@ -1443,11 +1426,11 @@
"Only people who have been invited": "Only people who have been invited",
"Anyone who knows the room's link, apart from guests": "Anyone who knows the room's link, apart from guests",
"Anyone who knows the room's link, including guests": "Anyone who knows the room's link, including guests",
- "Changes to who can read history will only apply to future messages in this room. The visibility of existing history will be unchanged.": "Changes to who can read history will only apply to future messages in this room. The visibility of existing history will be unchanged.",
- "Anyone": "Anyone",
"Members only (since the point in time of selecting this option)": "Members only (since the point in time of selecting this option)",
"Members only (since they were invited)": "Members only (since they were invited)",
"Members only (since they joined)": "Members only (since they joined)",
+ "Anyone": "Anyone",
+ "Changes to who can read history will only apply to future messages in this room. The visibility of existing history will be unchanged.": "Changes to who can read history will only apply to future messages in this room. The visibility of existing history will be unchanged.",
"Who can read history?": "Who can read history?",
"Security & Privacy": "Security & Privacy",
"Once enabled, encryption cannot be disabled.": "Once enabled, encryption cannot be disabled.",
@@ -1511,6 +1494,8 @@
"Your message was sent": "Your message was sent",
"Failed to send": "Failed to send",
"Scroll to most recent messages": "Scroll to most recent messages",
+ "Show %(count)s other previews|other": "Show %(count)s other previews",
+ "Show %(count)s other previews|one": "Show %(count)s other preview",
"Close preview": "Close preview",
"and %(count)s others...|other": "and %(count)s others...",
"and %(count)s others...|one": "and one other...",
@@ -1665,6 +1650,7 @@
"Favourite": "Favourite",
"Low Priority": "Low Priority",
"Invite People": "Invite People",
+ "Copy Room Link": "Copy Room Link",
"Leave Room": "Leave Room",
"Room options": "Room options",
"%(count)s unread messages including mentions.|other": "%(count)s unread messages including mentions.",
@@ -1830,6 +1816,12 @@
"Edit devices": "Edit devices",
"Security": "Security",
"The session you are trying to verify doesn't support scanning a QR code or emoji verification, which is what %(brand)s supports. Try with a different client.": "The session you are trying to verify doesn't support scanning a QR code or emoji verification, which is what %(brand)s supports. Try with a different client.",
+ "Scan this unique code": "Scan this unique code",
+ "Compare unique emoji": "Compare unique emoji",
+ "Compare a unique set of emoji if you don't have a camera on either device": "Compare a unique set of emoji if you don't have a camera on either device",
+ "Start": "Start",
+ "or": "or",
+ "Verify this session by completing one of the following:": "Verify this session by completing one of the following:",
"Verify by scanning": "Verify by scanning",
"Ask %(displayName)s to scan your code:": "Ask %(displayName)s to scan your code:",
"If you can't scan the code above, verify by comparing unique emoji.": "If you can't scan the code above, verify by comparing unique emoji.",
@@ -1852,6 +1844,18 @@
"You cancelled verification.": "You cancelled verification.",
"Verification cancelled": "Verification cancelled",
"Compare emoji": "Compare emoji",
+ "Connected": "Connected",
+ "This call has ended": "This call has ended",
+ "Could not connect media": "Could not connect media",
+ "Connection failed": "Connection failed",
+ "Their device couldn't start the camera or microphone": "Their device couldn't start the camera or microphone",
+ "An unknown error occurred": "An unknown error occurred",
+ "No answer": "No answer",
+ "Unknown failure: %(reason)s)": "Unknown failure: %(reason)s)",
+ "This call has failed": "This call has failed",
+ "You missed this call": "You missed this call",
+ "Call back": "Call back",
+ "The call is in an unknown state!": "The call is in an unknown state!",
"Sunday": "Sunday",
"Monday": "Monday",
"Tuesday": "Tuesday",
@@ -1861,6 +1865,8 @@
"Saturday": "Saturday",
"Today": "Today",
"Yesterday": "Yesterday",
+ "Downloading": "Downloading",
+ "Download": "Download",
"View Source": "View Source",
"Messages here are end-to-end encrypted. Verify %(displayName)s in their profile - tap on their avatar.": "Messages here are end-to-end encrypted. Verify %(displayName)s in their profile - tap on their avatar.",
"Messages in this room are end-to-end encrypted. When people join, you can verify them in their profile, just tap on their avatar.": "Messages in this room are end-to-end encrypted. When people join, you can verify them in their profile, just tap on their avatar.",
@@ -1868,19 +1874,20 @@
"Ignored attempt to disable encryption": "Ignored attempt to disable encryption",
"Encryption not enabled": "Encryption not enabled",
"The encryption used by this room isn't supported.": "The encryption used by this room isn't supported.",
- "Error decrypting audio": "Error decrypting audio",
+ "Error processing audio message": "Error processing audio message",
"React": "React",
"Edit": "Edit",
"Retry": "Retry",
"Reply": "Reply",
"Message Actions": "Message Actions",
- "Attachment": "Attachment",
"Error decrypting attachment": "Error decrypting attachment",
"Decrypt %(text)s": "Decrypt %(text)s",
"Download %(text)s": "Download %(text)s",
"Invalid file%(extra)s": "Invalid file%(extra)s",
"Error decrypting image": "Error decrypting image",
"Show image": "Show image",
+ "Sticker": "Sticker",
+ "Image": "Image",
"Join the conference at the top of this room": "Join the conference at the top of this room",
"Join the conference from the room information card on the right": "Join the conference from the room information card on the right",
"Video conference ended by %(senderName)s": "Video conference ended by %(senderName)s",
@@ -1961,7 +1968,7 @@
"%(brand)s URL": "%(brand)s URL",
"Room ID": "Room ID",
"Widget ID": "Widget ID",
- "Using this widget may share data with %(widgetDomain)s & your Integration Manager.": "Using this widget may share data with %(widgetDomain)s & your Integration Manager.",
+ "Using this widget may share data with %(widgetDomain)s & your integration manager.": "Using this widget may share data with %(widgetDomain)s & your integration manager.",
"Using this widget may share data with %(widgetDomain)s.": "Using this widget may share data with %(widgetDomain)s.",
"Widgets do not use message encryption.": "Widgets do not use message encryption.",
"Widget added by": "Widget added by",
@@ -1993,7 +2000,6 @@
"Zoom in": "Zoom in",
"Rotate Left": "Rotate Left",
"Rotate Right": "Rotate Right",
- "Download": "Download",
"Information": "Information",
"Language Dropdown": "Language Dropdown",
"%(nameList)s %(transitionList)s": "%(nameList)s %(transitionList)s",
@@ -2279,7 +2285,7 @@
"Integrations are disabled": "Integrations are disabled",
"Enable 'Manage Integrations' in Settings to do this.": "Enable 'Manage Integrations' in Settings to do this.",
"Integrations not allowed": "Integrations not allowed",
- "Your %(brand)s doesn't allow you to use an Integration Manager to do this. Please contact an admin.": "Your %(brand)s doesn't allow you to use an Integration Manager to do this. Please contact an admin.",
+ "Your %(brand)s doesn't allow you to use an integration manager to do this. Please contact an admin.": "Your %(brand)s doesn't allow you to use an integration manager to do this. Please contact an admin.",
"To continue, use Single Sign On to prove your identity.": "To continue, use Single Sign On to prove your identity.",
"Confirm to continue": "Confirm to continue",
"Click the button below to confirm your identity.": "Click the button below to confirm your identity.",
@@ -2288,7 +2294,6 @@
"Something went wrong trying to invite the users.": "Something went wrong trying to invite the users.",
"We couldn't invite those users. Please check the users you want to invite and try again.": "We couldn't invite those users. Please check the users you want to invite and try again.",
"A call can only be transferred to a single user.": "A call can only be transferred to a single user.",
- "Failed to transfer call": "Failed to transfer call",
"Failed to find the following users": "Failed to find the following users",
"The following users might not exist or are invalid, and cannot be invited: %(csvNames)s": "The following users might not exist or are invalid, and cannot be invited: %(csvNames)s",
"Recent Conversations": "Recent Conversations",
@@ -2311,6 +2316,8 @@
"Invited people will be able to read old messages.": "Invited people will be able to read old messages.",
"Transfer": "Transfer",
"Consult first": "Consult first",
+ "User Directory": "User Directory",
+ "Dial pad": "Dial pad",
"a new master key signature": "a new master key signature",
"a new cross-signing key signature": "a new cross-signing key signature",
"a device cross-signing signature": "a device cross-signing signature",
@@ -2344,15 +2351,6 @@
"Message edits": "Message edits",
"Modal Widget": "Modal Widget",
"Data on this screen is shared with %(widgetDomain)s": "Data on this screen is shared with %(widgetDomain)s",
- "Your account is not secure": "Your account is not secure",
- "Your password": "Your password",
- "This session, or the other session": "This session, or the other session",
- "The internet connection either session is using": "The internet connection either session is using",
- "We recommend you change your password and Security Key in Settings immediately": "We recommend you change your password and Security Key in Settings immediately",
- "New session": "New session",
- "Use this session to verify your new one, granting it access to encrypted messages:": "Use this session to verify your new one, granting it access to encrypted messages:",
- "If you didn’t sign in to this session, your account may be compromised.": "If you didn’t sign in to this session, your account may be compromised.",
- "This wasn't me": "This wasn't me",
"Doesn't look like a valid email address": "Doesn't look like a valid email address",
"Continuing without email": "Continuing without email",
"Just a heads up, if you don't add an email and forget your password, you could permanently lose access to your account.": "Just a heads up, if you don't add an email and forget your password, you could permanently lose access to your account.",
@@ -2443,7 +2441,7 @@
"Missing session data": "Missing session data",
"Some session data, including encrypted message keys, is missing. Sign out and sign in to fix this, restoring keys from backup.": "Some session data, including encrypted message keys, is missing. Sign out and sign in to fix this, restoring keys from backup.",
"Your browser likely removed this data when running low on disk space.": "Your browser likely removed this data when running low on disk space.",
- "Integration Manager": "Integration Manager",
+ "Integration manager": "Integration manager",
"Find others by phone or email": "Find others by phone or email",
"Be found by phone or email": "Be found by phone or email",
"Use bots, bridges, widgets and sticker packs": "Use bots, bridges, widgets and sticker packs",
@@ -2602,6 +2600,9 @@
"Use email or phone to optionally be discoverable by existing contacts.": "Use email or phone to optionally be discoverable by existing contacts.",
"Use email to optionally be discoverable by existing contacts.": "Use email to optionally be discoverable by existing contacts.",
"Sign in with SSO": "Sign in with SSO",
+ "Unnamed audio": "Unnamed audio",
+ "Pause": "Pause",
+ "Play": "Play",
"Couldn't load page": "Couldn't load page",
"You must register to use this functionality": "You must register to use this functionality",
"You must join the room to see its files": "You must join the room to see its files",
@@ -2671,6 +2672,8 @@
"Are you sure you want to leave the space '%(spaceName)s'?": "Are you sure you want to leave the space '%(spaceName)s'?",
"Are you sure you want to leave the room '%(roomName)s'?": "Are you sure you want to leave the room '%(roomName)s'?",
"Failed to forget room %(errCode)s": "Failed to forget room %(errCode)s",
+ "Unable to copy room link": "Unable to copy room link",
+ "Unable to copy a link to the room to the clipboard.": "Unable to copy a link to the room to the clipboard.",
"Signed Out": "Signed Out",
"For security, this session has been signed out. Please sign in again.": "For security, this session has been signed out. Please sign in again.",
"Terms and Conditions": "Terms and Conditions",
diff --git a/src/i18n/strings/es.json b/src/i18n/strings/es.json
index c1fb8e6542..a06de53821 100644
--- a/src/i18n/strings/es.json
+++ b/src/i18n/strings/es.json
@@ -3339,5 +3339,86 @@
"Error loading Widget": "Error al cargar el widget",
"Pinned messages": "Mensajes fijados",
"If you have permissions, open the menu on any message and select Pin to stick them here.": "Si tienes permisos, abre el menú de cualquier mensaje y selecciona Fijar para colocarlo aquí.",
- "Nothing pinned, yet": "Nada fijado, todavía"
+ "Nothing pinned, yet": "Nada fijado, todavía",
+ "%(senderName)s removed their display name (%(oldDisplayName)s)": "%(senderName)s se ha quitado el nombre personalizado (%(oldDisplayName)s)",
+ "%(senderName)s set their display name to %(displayName)s": "%(senderName)s ha elegido %(displayName)s como su nombre",
+ "%(senderName)s changed the pinned messages for the room.": "%(senderName)s ha cambiado los mensajes fijados de la sala.",
+ "%(senderName)s kicked %(targetName)s": "%(senderName)s ha echado a %(targetName)s",
+ "%(senderName)s kicked %(targetName)s: %(reason)s": "%(senderName)s ha echado a %(targetName)s: %(reason)s",
+ "Disagree": "No estoy de acuerdo",
+ "[number]": "[número]",
+ "To view %(spaceName)s, you need an invite": "Para ver %(spaceName)s, necesitas que te inviten.",
+ "You can click on an avatar in the filter panel at any time to see only the rooms and people associated with that community.": "Haz clic sobre una imagen en el panel de filtro para ver solo las salas y personas asociadas con una comunidad.",
+ "Move down": "Bajar",
+ "Move up": "Subir",
+ "Report": "Reportar",
+ "Collapse reply thread": "Ocultar respuestas",
+ "Show preview": "Mostrar vista previa",
+ "View source": "Ver código fuente",
+ "Forward": "Reenviar",
+ "Settings - %(spaceName)s": "Ajustes - %(spaceName)s",
+ "Report the entire room": "Reportar la sala entera",
+ "Spam or propaganda": "Publicidad no deseada o propaganda",
+ "Illegal Content": "Contenido ilegal",
+ "Toxic Behaviour": "Comportamiento tóxico",
+ "Please pick a nature and describe what makes this message abusive.": "Por favor, escoge una categoría y explica por qué el mensaje es abusivo.",
+ "Any other reason. Please describe the problem.\nThis will be reported to the room moderators.": "Otro motivo. Por favor, describe el problema.\nSe avisará a los moderadores de la sala.",
+ "This room is dedicated to illegal or toxic content or the moderators fail to moderate illegal or toxic content.\n This will be reported to the administrators of %(homeserver)s.": "Esta sala está dedicada a un tema ilegal o contenido tóxico, o bien los moderadores no están tomando medidas frente a este tipo de contenido.\nSe avisará a los administradores de %(homeserver)s.",
+ "This room is dedicated to illegal or toxic content or the moderators fail to moderate illegal or toxic content.\nThis will be reported to the administrators of %(homeserver)s. The administrators will NOT be able to read the encrypted content of this room.": "Esta sala está dedicada a un tema ilegal o contenido tóxico, o bien los moderadores no están tomando medidas frente a este tipo de contenido.\nSe avisará a los administradores de %(homeserver)s, pero no podrán leer el contenido cifrado de la sala.",
+ "This user is spamming the room with ads, links to ads or to propaganda.\nThis will be reported to the room moderators.": "Esta persona está mandando publicidad no deseada o propaganda.\nSe avisará a los moderadores de la sala.",
+ "This user is displaying illegal behaviour, for instance by doxing people or threatening violence.\nThis will be reported to the room moderators who may escalate this to legal authorities.": "Esta persona está comportándose de manera posiblemente ilegal. Por ejemplo, amenazando con violencia física o con revelar datos personales.\nSe avisará a los moderadores de la sala, que podrían denunciar los hechos.",
+ "This user is displaying toxic behaviour, for instance by insulting other users or sharing adult-only content in a family-friendly room or otherwise violating the rules of this room.\nThis will be reported to the room moderators.": "Esta persona está teniendo un comportamiento tóxico. Por ejemplo, insultando al resto, compartiendo contenido explícito en una sala para todos los públicos, o incumpliendo las normas de la sala en general.\nSe avisará a los moderadores de la sala.",
+ "What this user is writing is wrong.\nThis will be reported to the room moderators.": "Lo que esta persona está escribiendo no está bien.\nSe avisará a los moderadores de la sala.",
+ "Please provide an address": "Por favor, elige una dirección",
+ "%(oneUser)schanged the server ACLs %(count)s times|one": "%(oneUser)s ha cambiado los permisos del servidor",
+ "%(oneUser)schanged the server ACLs %(count)s times|other": "%(oneUser)s ha cambiado los permisos del servidor %(count)s veces",
+ "%(severalUsers)schanged the server ACLs %(count)s times|one": "%(severalUsers)s ha cambiado los permisos del servidor",
+ "%(severalUsers)schanged the server ACLs %(count)s times|other": "%(severalUsers)s ha cambiado los permisos del servidor %(count)s veces",
+ "Message search initialisation failed, check your settings for more information": "Ha fallado el sistema de búsqueda de mensajes. Comprueba tus ajustes para más información.",
+ "Set addresses for this space so users can find this space through your homeserver (%(localDomain)s)": "Elige una dirección para este espacio y los usuarios de tu servidor base (%(localDomain)s) podrán encontrarlo a través del buscador",
+ "To publish an address, it needs to be set as a local address first.": "Para publicar una dirección, primero debe ser añadida como dirección local.",
+ "Published addresses can be used by anyone on any server to join your room.": "Las direcciones publicadas pueden usarse por cualquiera para unirse a tu sala, independientemente de su servidor base.",
+ "Published addresses can be used by anyone on any server to join your space.": "Los espacios publicados pueden usarse por cualquiera, independientemente de su servidor base.",
+ "This space has no local addresses": "Este espacio no tiene direcciones locales",
+ "Space information": "Información del espacio",
+ "Collapse": "Colapsar",
+ "Expand": "Expandir",
+ "Recommended for public spaces.": "Recomendado para espacios públicos.",
+ "Allow people to preview your space before they join.": "Permitir que se pueda ver una vista previa del espacio antes de unirse a él.",
+ "Preview Space": "Previsualizar espacio",
+ "only invited people can view and join": "solo las personas invitadas pueden verlo y unirse",
+ "anyone with the link can view and join": "cualquiera con el enlace puede verlo y unirse",
+ "Decide who can view and join %(spaceName)s.": "Decide quién puede ver y unirse a %(spaceName)s.",
+ "Visibility": "Visibilidad",
+ "Guests can join a space without having an account.": "Las personas sin cuenta podrían unirse al espacio sin invitación.",
+ "This may be useful for public spaces.": "Esto puede ser útil para espacios públicos.",
+ "Enable guest access": "Permitir acceso a personas sin cuenta",
+ "Failed to update the history visibility of this space": "No se ha podido cambiar la visibilidad del historial de este espacio",
+ "Failed to update the guest access of this space": "No se ha podido cambiar el acceso a este espacio",
+ "Failed to update the visibility of this space": "No se ha podido cambiar la visibilidad del espacio",
+ "Address": "Dirección",
+ "e.g. my-space": "ej.: mi-espacio",
+ "Silence call": "Silenciar llamada",
+ "Sound on": "Sonido activado",
+ "Show notification badges for People in Spaces": "Mostrar indicador de notificaciones en la parte de gente en los espacios",
+ "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.": "Si lo desactivas, todavía podrás añadir mensajes directos a tus espacios personales. Si lo activas, aparecerá todo el mundo que pertenezca al espacio.",
+ "Show people in spaces": "Mostrar gente en los espacios",
+ "Show all rooms in Home": "Mostrar todas las salas en la pantalla de inicio",
+ "Report to moderators prototype. In rooms that support moderation, the `report` button will let you report abuse to room moderators": "Prototipo de reportes a los moderadores. En las salas que lo permitan, verás el botón «reportar», que te permitirá avisar de mensajes abusivos a los moderadores de la sala.",
+ "%(senderName)s withdrew %(targetName)s's invitation": "%(senderName)s ha anulado la invitación a %(targetName)s",
+ "%(senderName)s withdrew %(targetName)s's invitation: %(reason)s": "%(senderName)s ha anulado la invitación a %(targetName)s: %(reason)s",
+ "%(targetName)s left the room": "%(targetName)s ha salido de la sala",
+ "%(targetName)s left the room: %(reason)s": "%(targetName)s ha salido de la sala: %(reason)s",
+ "%(targetName)s rejected the invitation": "%(targetName)s ha rechazado la invitación",
+ "%(targetName)s joined the room": "%(targetName)s se ha unido a la sala",
+ "%(senderName)s made no change": "%(senderName)s no ha hecho ningún cambio",
+ "%(senderName)s set a profile picture": "%(senderName)s se ha puesto una foto de perfil",
+ "%(senderName)s changed their profile picture": "%(senderName)s ha cambiado su foto de perfil",
+ "%(senderName)s removed their profile picture": "%(senderName)s ha eliminado su foto de perfil",
+ "%(oldDisplayName)s changed their display name to %(displayName)s": "%(oldDisplayName)s ha cambiado su nombre a %(displayName)s",
+ "%(senderName)s invited %(targetName)s": "%(senderName)s ha invitado a %(targetName)s",
+ "%(targetName)s accepted an invitation": "%(targetName)s ha aceptado una invitación",
+ "%(targetName)s accepted the invitation for %(displayName)s": "%(targetName)s ha aceptado la invitación a %(displayName)s",
+ "We sent the others, but the below people couldn't be invited to ": "Hemos enviado el resto, pero no hemos podido invitar las siguientes personas a la sala ",
+ "Some invites couldn't be sent": "No se han podido enviar algunas invitaciones"
}
diff --git a/src/i18n/strings/et.json b/src/i18n/strings/et.json
index a466922bf9..ce262233b8 100644
--- a/src/i18n/strings/et.json
+++ b/src/i18n/strings/et.json
@@ -3371,5 +3371,84 @@
"Sent": "Saadetud",
"You don't have permission to do this": "Sul puuduvad selleks toiminguks õigused",
"Error - Mixed content": "Viga - erinev sisu",
- "Error loading Widget": "Viga vidina laadimisel"
+ "Error loading Widget": "Viga vidina laadimisel",
+ "%(senderName)s changed the pinned messages for the room.": "%(senderName)s muutis selle jututoa klammerdatud sõnumeid.",
+ "%(senderName)s kicked %(targetName)s": "%(senderName)s müksas kasutajat %(targetName)s",
+ "%(senderName)s kicked %(targetName)s: %(reason)s": "%(senderName)s müksas kasutajat %(targetName)s: %(reason)s",
+ "%(senderName)s withdrew %(targetName)s's invitation": "%(senderName)s võttis tagasi %(targetName)s kutse",
+ "%(senderName)s withdrew %(targetName)s's invitation: %(reason)s": "%(senderName)s võttis tagasi %(targetName)s kutse: %(reason)s",
+ "%(senderName)s unbanned %(targetName)s": "%(senderName)s taastas ligipääsu kasutajale %(targetName)s",
+ "%(targetName)s left the room": "%(targetName)s lahkus jututoast",
+ "%(targetName)s left the room: %(reason)s": "%(targetName)s lahkus jututoast: %(reason)s",
+ "%(targetName)s rejected the invitation": "%(targetName)s lükkas kutse tagasi",
+ "%(targetName)s joined the room": "%(targetName)s liitus jututoaga",
+ "%(senderName)s made no change": "%(senderName)s ei teinud muutusi",
+ "%(senderName)s set a profile picture": "%(senderName)s määras oma profiilipildi",
+ "%(senderName)s changed their profile picture": "%(senderName)s muutis oma profiilipilti",
+ "%(senderName)s removed their profile picture": "%(senderName)s eemaldas oma profiilipildi",
+ "%(senderName)s removed their display name (%(oldDisplayName)s)": "%(senderName)s eemaldas oma kuvatava nime (%(oldDisplayName)s)",
+ "%(senderName)s set their display name to %(displayName)s": "%(senderName)s määras oma kuvatava nime %(displayName)s-ks",
+ "%(oldDisplayName)s changed their display name to %(displayName)s": "%(oldDisplayName)s muutis oma kuvatava nime %(displayName)s-ks",
+ "%(senderName)s banned %(targetName)s": "%(senderName)s keelas ligipääsu kasutajale %(targetName)s",
+ "%(senderName)s banned %(targetName)s: %(reason)s": "%(senderName)s keelas ligipääsu kasutajale %(targetName)s: %(reason)s",
+ "%(targetName)s accepted an invitation": "%(targetName)s võttis kutse vastu",
+ "%(targetName)s accepted the invitation for %(displayName)s": "%(targetName)s võttis vastu kutse %(displayName)s nimel",
+ "Some invites couldn't be sent": "Mõnede kutsete saatmine ei õnnestunud",
+ "Visibility": "Nähtavus",
+ "This may be useful for public spaces.": "Seda saad kasutada näiteks avalike kogukonnakeskuste puhul.",
+ "Guests can join a space without having an account.": "Külalised võivad liituda kogukonnakeskusega ilma kasutajakontota.",
+ "Enable guest access": "Luba ligipääs külalistele",
+ "Failed to update the history visibility of this space": "Ei õnnestunud selle kogukonnakekuse ajaloo loetavust uuendada",
+ "Failed to update the guest access of this space": "Ei õnnestunud selle kogukonnakekuse külaliste ligipääsureegleid uuendada",
+ "Failed to update the visibility of this space": "Kogukonnakeskuse nähtavust ei õnnestunud uuendada",
+ "Address": "Aadress",
+ "e.g. my-space": "näiteks minu kogukond",
+ "Silence call": "Vaigista kõne",
+ "Sound on": "Lõlita heli sisse",
+ "To publish an address, it needs to be set as a local address first.": "Aadressi avaldamiseks peab ta esmalt olema määratud kohalikuks aadressiks.",
+ "Published addresses can be used by anyone on any server to join your room.": "Avaldatud aadresse saab igaüks igast serverist kasutada liitumiseks sinu jututoaga.",
+ "Published addresses can be used by anyone on any server to join your space.": "Avaldatud aadresse saab igaüks igast serverist kasutada liitumiseks sinu kogukonnakeskusega.",
+ "This space has no local addresses": "Sellel kogukonnakeskusel puuduvad kohalikud aadressid",
+ "Space information": "Kogukonnakeskuse teave",
+ "Collapse": "ahenda",
+ "Expand": "laienda",
+ "Recommended for public spaces.": "Soovitame avalike kogukonnakeskuste puhul.",
+ "Allow people to preview your space before they join.": "Luba huvilistel enne liitumist näha kogukonnakeskuse eelvaadet.",
+ "Preview Space": "Kogukonnakeskuse eelvaade",
+ "only invited people can view and join": "igaüks, kellel on kutse, saab liituda ja näha sisu",
+ "anyone with the link can view and join": "igaüks, kellel on link, saab liituda ja näha sisu",
+ "Decide who can view and join %(spaceName)s.": "Otsusta kes saada näha ja liituda %(spaceName)s kogukonnaga.",
+ "Show people in spaces": "Näita kogukonnakeskuses osalejaid",
+ "Show all rooms in Home": "Näita kõiki jututubasid avalehel",
+ "Set addresses for this space so users can find this space through your homeserver (%(localDomain)s)": "Selleks et teised kasutajad saaks seda kogukonda leida oma koduserveri kaudu (%(localDomain)s) seadista talle aadressid",
+ "%(oneUser)schanged the server ACLs %(count)s times|one": "%(oneUser)s kasutaja muutis serveri pääsuloendit",
+ "%(oneUser)schanged the server ACLs %(count)s times|other": "%(oneUser)s kasutaja muutis serveri pääsuloendit %(count)s korda",
+ "%(severalUsers)schanged the server ACLs %(count)s times|other": "%(severalUsers)s kasutajat muutsid serveri pääsuloendit %(count)s korda",
+ "%(severalUsers)schanged the server ACLs %(count)s times|one": "%(severalUsers)s kasutajat muutsid serveri pääsuloendit",
+ "Message search initialisation failed, check your settings for more information": "Sõnumite otsingu ettevalmistamine ei õnnestunud, lisateavet leiad rakenduse seadistustest",
+ "To view %(spaceName)s, you need an invite": "%(spaceName)s kogukonnaga tutvumiseks vajad sa kutset",
+ "You can click on an avatar in the filter panel at any time to see only the rooms and people associated with that community.": "Koondvaates võid alati klõpsida tunnuspilti ning näha vaid selle kogukonnaga seotud jututubasid ja inimesi.",
+ "Move down": "Liiguta alla",
+ "Move up": "Liiguta üles",
+ "Report": "Teata sisust",
+ "Collapse reply thread": "Ahenda vastuste jutulõng",
+ "Show preview": "Näita eelvaadet",
+ "View source": "Vaata algset teavet",
+ "Forward": "Edasi",
+ "Settings - %(spaceName)s": "Seadistused - %(spaceName)s",
+ "Toxic Behaviour": "Ebasobilik käitumine",
+ "Report the entire room": "Teata tervest jututoast",
+ "Spam or propaganda": "Spämm või propaganda",
+ "Illegal Content": "Seadustega keelatud sisu",
+ "Disagree": "Ma ei nõustu sisuga",
+ "This room is dedicated to illegal or toxic content or the moderators fail to moderate illegal or toxic content.\n This will be reported to the administrators of %(homeserver)s.": "See jututuba tundub olema keskendunud seadusevastase või ohtliku sisu levitamisele, kuid võib-olla ka ei suuda moderaatorid sellist sisu kõrvaldada.\n%(homeserver)s koduserveri haldajad saavad selle kohta teate.",
+ "This room is dedicated to illegal or toxic content or the moderators fail to moderate illegal or toxic content.\nThis will be reported to the administrators of %(homeserver)s. The administrators will NOT be able to read the encrypted content of this room.": "See jututuba tundub olema keskendunud seadusevastase või ohtliku sisu levitamisele, kuid võib-olla ka ei suuda moderaatorid sellist sisu kõrvaldada.\n%(homeserver)s koduserveri haldajad saavad selle kohta teate, aga kuna jututoa sisu on krüptitud, siis nad ei pruugi saada seda lugeda.",
+ "This user is displaying illegal behaviour, for instance by doxing people or threatening violence.\nThis will be reported to the room moderators who may escalate this to legal authorities.": "Selle kasutaja tegevus on seadusevastane, milleks võib olla doksimine ehk teiste eraeluliste andmete avaldamine või vägivallaga ähvardamine.\nJututoa moderaatorid saavad selle kohta teate ning nad võivad sellest teatada ka ametivõimudele.",
+ "Please pick a nature and describe what makes this message abusive.": "Palun vali rikkumise olemus ja kirjelda mis teeb selle sõnumi kuritahtlikuks.",
+ "Any other reason. Please describe the problem.\nThis will be reported to the room moderators.": "Mõni muu põhjus. Palun kirjelda seda detailsemalt.\nJututoa moderaatorid saavad selle kohta teate.",
+ "What this user is writing is wrong.\nThis will be reported to the room moderators.": "Selle kasutaja loodud sisu on vale.\nJututoa moderaatorid saavad selle kohta teate.",
+ "This user is spamming the room with ads, links to ads or to propaganda.\nThis will be reported to the room moderators.": "See kasutaja spämmib jututuba reklaamidega, reklaamlinkidega või propagandaga.\nJututoa moderaatorid saavad selle kohta teate.",
+ "This user is displaying toxic behaviour, for instance by insulting other users or sharing adult-only content in a family-friendly room or otherwise violating the rules of this room.\nThis will be reported to the room moderators.": "Selle kasutaja tegevus on äärmiselt ebasobilik, milleks võib olla teiste jututoas osalejate solvamine, peresõbralikku jututuppa täiskasvanutele mõeldud sisu lisamine või muul viisil jututoa reeglite rikkumine.\nJututoa moderaatorid saavad selle kohta teate.",
+ "Please provide an address": "Palun sisesta aadress",
+ "Report to moderators prototype. In rooms that support moderation, the `report` button will let you report abuse to room moderators": "Meie esimene katsetus modereerimisega. Kui jututoas on modereerimine toetatud, siis „Teata moderaatorile“ nupust võid saada teate ebasobiliku sisu kohta"
}
diff --git a/src/i18n/strings/fr.json b/src/i18n/strings/fr.json
index 16373f0853..9d047887ba 100644
--- a/src/i18n/strings/fr.json
+++ b/src/i18n/strings/fr.json
@@ -2530,7 +2530,7 @@
"Send feedback": "Envoyer un commentaire",
"PRO TIP: If you start a bug, please submit debug logs to help us track down the problem.": "CONSEIL : si vous rapportez un bug, merci d’envoyer les journaux de débogage pour nous aider à identifier le problème.",
"Please view existing bugs on Github first. No match? Start a new one.": "Merci de regarder d’abord les bugs déjà répertoriés sur Github. Pas de résultat ? Rapportez un nouveau bug.",
- "Report a bug": "Rapporter un bug",
+ "Report a bug": "Signaler un bug",
"There are two ways you can provide feedback and help us improve %(brand)s.": "Il y a deux manières pour que vous puissiez faire vos retour et nous aider à améliorer %(brand)s.",
"Comment": "Commentaire",
"Add comment": "Ajouter un commentaire",
@@ -3375,5 +3375,86 @@
"If you have permissions, open the menu on any message and select Pin to stick them here.": "Si vous avez les permissions, ouvrez le menu de n’importe quel message et sélectionnez Épingler pour les afficher ici.",
"Nothing pinned, yet": "Rien d’épinglé, pour l’instant",
"End-to-end encryption isn't enabled": "Le chiffrement de bout en bout n’est pas activé",
- "Your private messages are normally encrypted, but this room isn't. Usually this is due to an unsupported device or method being used, like email invites. Enable encryption in settings.": "Vous messages privés sont normalement chiffrés, mais ce salon ne l’est pas. Ceci est souvent du à un appareil ou une méthode qui ne le prend pas en charge, comme les invitations par e-mail. Activer le chiffrement dans les paramètres."
+ "Your private messages are normally encrypted, but this room isn't. Usually this is due to an unsupported device or method being used, like email invites. Enable encryption in settings.": "Vous messages privés sont normalement chiffrés, mais ce salon ne l’est pas. Ceci est souvent du à un appareil ou une méthode qui ne le prend pas en charge, comme les invitations par e-mail. Activer le chiffrement dans les paramètres.",
+ "Any other reason. Please describe the problem.\nThis will be reported to the room moderators.": "Toute autre raison. Veuillez décrire le problème.\nCeci sera signalé aux modérateurs du salon.",
+ "This user is spamming the room with ads, links to ads or to propaganda.\nThis will be reported to the room moderators.": "Cet utilisateur inonde le salon de publicités ou liens vers des publicités, ou vers de la propagande.\nCeci sera signalé aux modérateurs du salon.",
+ "This user is displaying illegal behaviour, for instance by doxing people or threatening violence.\nThis will be reported to the room moderators who may escalate this to legal authorities.": "Cet utilisateur fait preuve d’un comportement illicite, par exemple en publiant des informations personnelles d’autres ou en proférant des menaces.\nCeci sera signalé aux modérateurs du salon qui pourront l’escalader aux autorités.",
+ "This user is displaying toxic behaviour, for instance by insulting other users or sharing adult-only content in a family-friendly room or otherwise violating the rules of this room.\nThis will be reported to the room moderators.": "Cet utilisateur fait preuve d’un comportement toxique, par exemple en insultant les autres ou en partageant du contenu pour adultes dans un salon familial, ou en violant les règles de ce salon.\nCeci sera signalé aux modérateurs du salon.",
+ "What this user is writing is wrong.\nThis will be reported to the room moderators.": "Ce que cet utilisateur écrit est déplacé.\nCeci sera signalé aux modérateurs du salon.",
+ "Report to moderators prototype. In rooms that support moderation, the `report` button will let you report abuse to room moderators": "Prototype de signalement aux modérateurs. Dans les salons qui prennent en charge la modération, le bouton `Signaler` vous permettra de dénoncer les abus aux modérateurs du salon",
+ "[number]": "[numéro]",
+ "To view %(spaceName)s, you need an invite": "Pour afficher %(spaceName)s, vous avez besoin d’une invitation",
+ "You can click on an avatar in the filter panel at any time to see only the rooms and people associated with that community.": "Vous pouvez cliquer sur un avatar dans le panneau de filtrage à n’importe quel moment pour n’afficher que les salons et personnes associés à cette communauté.",
+ "Move down": "Descendre",
+ "Move up": "Remonter",
+ "Report": "Signaler",
+ "Collapse reply thread": "Masquer le fil de réponse",
+ "Show preview": "Afficher l’aperçu",
+ "View source": "Afficher la source",
+ "Forward": "Transférer",
+ "Settings - %(spaceName)s": "Paramètres - %(spaceName)s",
+ "Report the entire room": "Signaler le salon entier",
+ "Spam or propaganda": "Publicité ou propagande",
+ "Illegal Content": "Contenu illicite",
+ "Toxic Behaviour": "Comportement toxique",
+ "Disagree": "Désaccord",
+ "Please pick a nature and describe what makes this message abusive.": "Veuillez choisir la nature du rapport et décrire ce qui rend ce message abusif.",
+ "Please provide an address": "Veuillez fournir une adresse",
+ "%(oneUser)schanged the server ACLs %(count)s times|one": "%(oneUser)s a changé les listes de contrôle d’accès (ACLs) du serveur",
+ "%(oneUser)schanged the server ACLs %(count)s times|other": "%(oneUser)s a changé les liste de contrôle d’accès (ACLs) %(count)s fois",
+ "%(severalUsers)schanged the server ACLs %(count)s times|one": "%(severalUsers)s ont changé les listes de contrôle d’accès (ACLs) du serveur",
+ "%(severalUsers)schanged the server ACLs %(count)s times|other": "%(severalUsers)s ont changé les liste de contrôle d’accès (ACLs) %(count)s fois",
+ "Message search initialisation failed, check your settings for more information": "Échec de l’initialisation de la recherche de messages, vérifiez vos paramètres pour plus d’information",
+ "Set addresses for this space so users can find this space through your homeserver (%(localDomain)s)": "Définissez les adresses de cet espace pour que les utilisateurs puissent le trouver avec votre serveur d’accueil (%(localDomain)s)",
+ "To publish an address, it needs to be set as a local address first.": "Pour publier une adresse, elle doit d’abord être définie comme adresse locale.",
+ "Published addresses can be used by anyone on any server to join your room.": "Les adresses publiées peuvent être utilisées par tout le monde sur tous les serveurs pour rejoindre votre salon.",
+ "Published addresses can be used by anyone on any server to join your space.": "Les adresses publiées peuvent être utilisées par tout le monde sur tous les serveurs pour rejoindre votre espace.",
+ "This space has no local addresses": "Cet espace n’a pas d’adresse locale",
+ "Space information": "Informations de l’espace",
+ "Collapse": "Réduire",
+ "Expand": "Développer",
+ "Recommended for public spaces.": "Recommandé pour les espaces publics.",
+ "Allow people to preview your space before they join.": "Permettre aux personnes d’avoir un aperçu de l’espace avant de le rejoindre.",
+ "Preview Space": "Aperçu de l’espace",
+ "only invited people can view and join": "seules les personnes invitées peuvent visualiser et rejoindre",
+ "anyone with the link can view and join": "quiconque avec le lien peut visualiser et rejoindre",
+ "Decide who can view and join %(spaceName)s.": "Décider qui peut visualiser et rejoindre %(spaceName)s.",
+ "Visibility": "Visibilité",
+ "This may be useful for public spaces.": "Ceci peut être utile pour les espaces publics.",
+ "Guests can join a space without having an account.": "Les visiteurs peuvent rejoindre un espace sans disposer d’un compte.",
+ "Enable guest access": "Activer l’accès visiteur",
+ "Failed to update the history visibility of this space": "Échec de la mise à jour de la visibilité de l’historique pour cet espace",
+ "Failed to update the guest access of this space": "Échec de la mise à jour de l’accès visiteur de cet espace",
+ "Failed to update the visibility of this space": "Échec de la mise à jour de la visibilité de cet espace",
+ "Address": "Adresse",
+ "e.g. my-space": "par ex. mon-espace",
+ "Silence call": "Mettre l’appel en sourdine",
+ "Sound on": "Son activé",
+ "Show notification badges for People in Spaces": "Afficher les badges de notification pour les personnes dans les espaces",
+ "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.": "Si désactivé, vous pouvez toujours ajouter des messages directs aux espaces personnels. Si activé, vous verrez automatiquement tous les membres de cet espace.",
+ "Show people in spaces": "Afficher les personnes dans les espaces",
+ "Show all rooms in Home": "Afficher tous les salons dans Accueil",
+ "%(senderName)s changed the pinned messages for the room.": "%(senderName)s a changé les messages épinglés du salon.",
+ "%(senderName)s kicked %(targetName)s": "%(senderName)s a expulsé %(targetName)s",
+ "%(senderName)s kicked %(targetName)s: %(reason)s": "%(senderName)s a explusé %(targetName)s : %(reason)s",
+ "%(senderName)s withdrew %(targetName)s's invitation": "%(senderName)s a annulé l’invitation de %(targetName)s",
+ "%(senderName)s withdrew %(targetName)s's invitation: %(reason)s": "%(senderName)s a annulé l’invitation de %(targetName)s : %(reason)s",
+ "%(senderName)s unbanned %(targetName)s": "%(senderName)s a révoqué le bannissement de %(targetName)s",
+ "%(targetName)s left the room": "%(targetName)s a quitté le salon",
+ "%(targetName)s left the room: %(reason)s": "%(targetName)s a quitté le salon : %(reason)s",
+ "%(targetName)s rejected the invitation": "%(targetName)s a rejeté l’invitation",
+ "%(targetName)s joined the room": "%(targetName)s a rejoint le salon",
+ "%(senderName)s made no change": "%(senderName)s n’a fait aucun changement",
+ "%(senderName)s set a profile picture": "%(senderName)s a défini une image de profil",
+ "%(senderName)s changed their profile picture": "%(senderName)s a changé son image de profil",
+ "%(senderName)s removed their profile picture": "%(senderName)s a supprimé son image de profil",
+ "%(senderName)s removed their display name (%(oldDisplayName)s)": "%(senderName)s a supprimé son nom d’affichage (%(oldDisplayName)s)",
+ "%(senderName)s set their display name to %(displayName)s": "%(senderName)s a défini son nom affiché comme %(displayName)s",
+ "%(oldDisplayName)s changed their display name to %(displayName)s": "%(oldDisplayName)s a changé son nom d’affichage en %(displayName)s",
+ "%(senderName)s banned %(targetName)s": "%(senderName)s a banni %(targetName)s",
+ "%(senderName)s banned %(targetName)s: %(reason)s": "%(senderName)s a banni %(targetName)s : %(reason)s",
+ "%(targetName)s accepted an invitation": "%(targetName)s a accepté une invitation",
+ "%(targetName)s accepted the invitation for %(displayName)s": "%(targetName)s a accepté l’invitation pour %(displayName)s",
+ "Some invites couldn't be sent": "Certaines invitations n’ont pas pu être envoyées",
+ "We sent the others, but the below people couldn't be invited to ": "Nous avons envoyé les invitations, mais les personnes ci-dessous n’ont pas pu être invitées à rejoindre "
}
diff --git a/src/i18n/strings/hu.json b/src/i18n/strings/hu.json
index cb749f12a5..683f825187 100644
--- a/src/i18n/strings/hu.json
+++ b/src/i18n/strings/hu.json
@@ -2002,7 +2002,7 @@
"Enter a server name": "Add meg a szerver nevét",
"Looks good": "Jól néz ki",
"Can't find this server or its room list": "A szerver vagy a szoba listája nem található",
- "All rooms": "Minden szoba",
+ "All rooms": "Kezdő tér",
"Your server": "Matrix szervered",
"Are you sure you want to remove %(serverName)s": "Biztos, hogy eltávolítja: %(serverName)s",
"Remove server": "Szerver törlése",
@@ -3393,5 +3393,88 @@
"Error loading Widget": "Kisalkalmazás betöltési hiba",
"Pinned messages": "Kitűzött üzenetek",
"Nothing pinned, yet": "Semmi sincs kitűzve egyenlőre",
- "End-to-end encryption isn't enabled": "Végpontok közötti titkosítás nincs engedélyezve"
+ "End-to-end encryption isn't enabled": "Végpontok közötti titkosítás nincs engedélyezve",
+ "Show people in spaces": "Emberek megjelenítése a terekben",
+ "Show all rooms in Home": "Minden szoba megjelenítése a Kezdő téren",
+ "%(senderName)s changed the pinned messages for the room.": "%(senderName)s megváltoztatta a szoba kitűzött szövegeit.",
+ "%(senderName)s kicked %(targetName)s": "%(senderName)s kirúgta: %(targetName)s",
+ "%(senderName)s kicked %(targetName)s: %(reason)s": "%(senderName)s kirúgta őt: %(targetName)s, ok: %(reason)s",
+ "%(senderName)s withdrew %(targetName)s's invitation": "%(senderName)s visszavonta %(targetName)s meghívóját",
+ "%(senderName)s withdrew %(targetName)s's invitation: %(reason)s": "%(senderName)s visszavonta %(targetName)s meghívóját, ok: %(reason)s",
+ "%(senderName)s unbanned %(targetName)s": "%(senderName)s visszaengedte %(targetName)s felhasználót",
+ "%(targetName)s left the room": "%(targetName)s elhagyta a szobát",
+ "%(targetName)s left the room: %(reason)s": "%(targetName)s elhagyta a szobát, ok: %(reason)s",
+ "%(targetName)s rejected the invitation": "%(targetName)s elutasította a meghívót",
+ "%(targetName)s joined the room": "%(targetName)s belépett a szobába",
+ "%(senderName)s made no change": "%(senderName)s nem változtatott semmit",
+ "%(senderName)s set a profile picture": "%(senderName)s profil képet állított be",
+ "%(senderName)s changed their profile picture": "%(senderName)s megváltoztatta a profil képét",
+ "%(senderName)s removed their profile picture": "%(senderName)s törölte a profil képét",
+ "%(senderName)s removed their display name (%(oldDisplayName)s)": "%(senderName)s törölte a megjelenítési nevet (%(oldDisplayName)s)",
+ "%(senderName)s set their display name to %(displayName)s": "%(senderName)s a megjelenítési nevét megváltoztatta erre: %(displayName)s",
+ "%(oldDisplayName)s changed their display name to %(displayName)s": "%(oldDisplayName)s megváltoztatta a nevét erre: %(displayName)s",
+ "%(senderName)s banned %(targetName)s": "%(senderName)s kitiltotta őt: %(targetName)s",
+ "%(senderName)s banned %(targetName)s: %(reason)s": "%(senderName)s kitiltotta őt: %(targetName)s, ok: %(reason)s",
+ "%(targetName)s accepted an invitation": "%(targetName)s elfogadta a meghívást",
+ "%(targetName)s accepted the invitation for %(displayName)s": "%(targetName)s elfogadta a meghívást ide: %(displayName)s",
+ "Some invites couldn't be sent": "Néhány meghívót nem sikerült elküldeni",
+ "You can click on an avatar in the filter panel at any time to see only the rooms and people associated with that community.": "Bármikor a szűrő panelen a profilképre kattintva megtekinthető, hogy melyik szobák és emberek tartoznak ehhez a közösséghez.",
+ "Please pick a nature and describe what makes this message abusive.": "Az üzenet természetének kiválasztása vagy annak megadása, hogy miért elítélendő.",
+ "This room is dedicated to illegal or toxic content or the moderators fail to moderate illegal or toxic content.\n This will be reported to the administrators of %(homeserver)s.": "Ez a szoba illegális vagy mérgező tartalmat közvetít vagy a moderátorok képtelenek ezeket megfelelően kezelni.\nEzek a szerver (%(homeserver)s) üzemeltetője felé jelzésre kerülnek.",
+ "This room is dedicated to illegal or toxic content or the moderators fail to moderate illegal or toxic content.\nThis will be reported to the administrators of %(homeserver)s. The administrators will NOT be able to read the encrypted content of this room.": "Ez a szoba illegális vagy mérgező tartalmat közvetít vagy a moderátorok képtelenek ezeket megfelelően kezelni.\nEzek a szerver (%(homeserver)s) üzemeltetője felé jelzésre kerülnek. Az adminisztrátorok nem tudják olvasni a titkosított szobák tartalmát.",
+ "This user is spamming the room with ads, links to ads or to propaganda.\nThis will be reported to the room moderators.": "A felhasználó kéretlen reklámokkal, reklám hivatkozásokkal vagy propagandával bombázza a szobát.\nEz moderátorok felé jelzésre kerül.",
+ "This user is displaying illegal behaviour, for instance by doxing people or threatening violence.\nThis will be reported to the room moderators who may escalate this to legal authorities.": "A felhasználó illegális viselkedést valósít meg, például kipécézett valakit vagy tettlegességgel fenyeget.\nEz moderátorok felé jelzésre kerül akik akár hivatalos személyek felé továbbíthatják ezt.",
+ "This user is displaying toxic behaviour, for instance by insulting other users or sharing adult-only content in a family-friendly room or otherwise violating the rules of this room.\nThis will be reported to the room moderators.": "A felhasználó mérgező viselkedést jelenít meg, például más felhasználókat inzultál vagy felnőtt tartalmat oszt meg egy családbarát szobában vagy más módon sérti meg a szoba szabályait.\nEz moderátorok felé jelzésre kerül.",
+ "%(oneUser)schanged the server ACLs %(count)s times|one": "%(oneUser)smegváltoztatta a szerver ACL-eket",
+ "%(oneUser)schanged the server ACLs %(count)s times|other": "%(oneUser)s %(count)s alkalommal megváltoztatta a kiszolgáló ACL-t",
+ "%(severalUsers)schanged the server ACLs %(count)s times|other": "%(severalUsers)s %(count)s alkalommal megváltoztatta a kiszolgáló ACL-t",
+ "Message search initialisation failed, check your settings for more information": "Üzenek keresés kezdő beállítása sikertelen, ellenőrizze a beállításait további információkért",
+ "Set addresses for this space so users can find this space through your homeserver (%(localDomain)s)": "Cím beállítása ehhez a térhez, hogy a felhasználók a matrix szerveren megtalálhassák (%(localDomain)s)",
+ "To publish an address, it needs to be set as a local address first.": "A cím publikálásához először helyi címet kell beállítani.",
+ "Published addresses can be used by anyone on any server to join your space.": "A nyilvánosságra hozott címet bárki bármelyik szerverről használhatja a térbe való belépéshez.",
+ "Published addresses can be used by anyone on any server to join your room.": "A nyilvánosságra hozott címet bárki bármelyik szerverről használhatja a szobához való belépéshez.",
+ "Failed to update the history visibility of this space": "A tér régi üzeneteinek láthatóság állítása nem sikerült",
+ "Failed to update the guest access of this space": "A tér vendég hozzáférésének állítása sikertelen",
+ "Show notification badges for People in Spaces": "Értesítés címkék megjelenítése a Tereken lévő embereknél",
+ "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.": "Még akkor is ha tiltva van, közvetlen üzenetet lehet küldeni Személyes Terekbe. Ha engedélyezve van, egyből látszik mindenki aki tagja a Térnek.",
+ "Report to moderators prototype. In rooms that support moderation, the `report` button will let you report abuse to room moderators": "Jelzés a moderátornak prototípus. A moderálást támogató szobákban a „jelzés” gombbal jelenthető a kifogásolt tartalom a szoba moderátorainak",
+ "We sent the others, but the below people couldn't be invited to ": "Az alábbi embereket nem sikerül meghívni ide: , de a többi meghívó elküldve",
+ "[number]": "[szám]",
+ "To view %(spaceName)s, you need an invite": "A %(spaceName)s megjelenítéséhez meghívó szükséges",
+ "Move down": "Mozgatás le",
+ "Move up": "Mozgatás fel",
+ "Report": "Jelentés",
+ "Collapse reply thread": "Beszélgetés szál becsukása",
+ "Show preview": "Előnézet megjelenítése",
+ "View source": "Forrás megtekintése",
+ "Forward": "Továbbítás",
+ "Settings - %(spaceName)s": "Beállítások - %(spaceName)s",
+ "Report the entire room": "Az egész szoba jelentése",
+ "Spam or propaganda": "Kéretlen reklám vagy propaganda",
+ "Illegal Content": "Jogosulatlan tartalom",
+ "Toxic Behaviour": "Mérgező viselkedés",
+ "Disagree": "Nem értek egyet",
+ "Any other reason. Please describe the problem.\nThis will be reported to the room moderators.": "Bármi más ok. Írja le a problémát.\nEz lesz elküldve a szoba moderátorának.",
+ "What this user is writing is wrong.\nThis will be reported to the room moderators.": "Amit ez a felhasználó ír az rossz.\nErről a szoba moderátorának jelentés készül.",
+ "Please provide an address": "Kérem adja meg a címet",
+ "%(severalUsers)schanged the server ACLs %(count)s times|one": "%(severalUsers)smegváltoztatta a szerver ACL-eket",
+ "This space has no local addresses": "Ennek a térnek nincs helyi címe",
+ "Space information": "Tér információk",
+ "Collapse": "Bezár",
+ "Expand": "Kinyit",
+ "Recommended for public spaces.": "Nyilvános terekhez ajánlott.",
+ "Allow people to preview your space before they join.": "Tér előnézetének engedélyezése mielőtt belépnének.",
+ "Preview Space": "Tér előnézete",
+ "only invited people can view and join": "csak meghívott emberek láthatják és léphetnek be",
+ "anyone with the link can view and join": "bárki aki ismeri a hivatkozást láthatja és beléphet",
+ "Decide who can view and join %(spaceName)s.": "Döntse el ki láthatja és léphet be ide: %(spaceName)s.",
+ "Visibility": "Láthatóság",
+ "This may be useful for public spaces.": "Nyilvános tereknél ez hasznos lehet.",
+ "Guests can join a space without having an account.": "Vendégek fiók nélkül is beléphetnek a térbe.",
+ "Enable guest access": "Vendég hozzáférés engedélyezése",
+ "Failed to update the visibility of this space": "A tér láthatóságának állítása sikertelen",
+ "Address": "Cím",
+ "e.g. my-space": "pl. én-terem",
+ "Silence call": "Némít",
+ "Sound on": "Hang be"
}
diff --git a/src/i18n/strings/it.json b/src/i18n/strings/it.json
index 207ff24d58..2d98072f78 100644
--- a/src/i18n/strings/it.json
+++ b/src/i18n/strings/it.json
@@ -3398,5 +3398,88 @@
"If you have permissions, open the menu on any message and select Pin to stick them here.": "Se ne hai il permesso, apri il menu di qualsiasi messaggio e seleziona Fissa per ancorarlo qui.",
"Pinned messages": "Messaggi ancorati",
"End-to-end encryption isn't enabled": "La crittografia end-to-end non è attiva",
- "Your private messages are normally encrypted, but this room isn't. Usually this is due to an unsupported device or method being used, like email invites. Enable encryption in settings.": "I tuoi messaggi privati normalmente sono cifrati, ma questa stanza non lo è. Di solito ciò è dovuto ad un dispositivo non supportato o dal metodo usato, come gli inviti per email. Attiva la crittografia nelle impostazioni."
+ "Your private messages are normally encrypted, but this room isn't. Usually this is due to an unsupported device or method being used, like email invites. Enable encryption in settings.": "I tuoi messaggi privati normalmente sono cifrati, ma questa stanza non lo è. Di solito ciò è dovuto ad un dispositivo non supportato o dal metodo usato, come gli inviti per email. Attiva la crittografia nelle impostazioni.",
+ "Report": "Segnala",
+ "Show preview": "Mostra anteprima",
+ "View source": "Visualizza sorgente",
+ "Settings - %(spaceName)s": "Impostazioni - %(spaceName)s",
+ "Report the entire room": "Segnala l'intera stanza",
+ "Spam or propaganda": "Spam o propaganda",
+ "Illegal Content": "Contenuto illegale",
+ "Toxic Behaviour": "Cattivo comportamento",
+ "Please pick a nature and describe what makes this message abusive.": "Scegli la natura del problema e descrivi cosa rende questo messaggio un abuso.",
+ "Please provide an address": "Inserisci un indirizzo",
+ "This space has no local addresses": "Questo spazio non ha indirizzi locali",
+ "Space information": "Informazioni spazio",
+ "Collapse": "Riduci",
+ "Expand": "Espandi",
+ "Preview Space": "Anteprima spazio",
+ "only invited people can view and join": "solo gli invitati possono vedere ed entrare",
+ "anyone with the link can view and join": "chiunque abbia il link può vedere ed entrare",
+ "Decide who can view and join %(spaceName)s.": "Decidi chi può vedere ed entrare in %(spaceName)s.",
+ "Visibility": "Visibilità",
+ "This may be useful for public spaces.": "Può tornare utile per gli spazi pubblici.",
+ "Guests can join a space without having an account.": "Gli ospiti possono entrare in uno spazio senza avere un account.",
+ "Enable guest access": "Attiva accesso ospiti",
+ "Address": "Indirizzo",
+ "e.g. my-space": "es. mio-spazio",
+ "Silence call": "Silenzia la chiamata",
+ "Sound on": "Audio attivo",
+ "Show people in spaces": "Mostra persone negli spazi",
+ "Show all rooms in Home": "Mostra tutte le stanze nella pagina principale",
+ "Report to moderators prototype. In rooms that support moderation, the `report` button will let you report abuse to room moderators": "Prototipo di segnalazione ai moderatori. Nelle stanze che supportano la moderazione, il pulsante `segnala` ti permetterà di notificare un abuso ai moderatori della stanza",
+ "%(senderName)s changed the pinned messages for the room.": "%(senderName)s ha cambiato i messaggi ancorati della stanza.",
+ "%(senderName)s kicked %(targetName)s": "%(senderName)s ha buttato fuori %(targetName)s",
+ "%(senderName)s kicked %(targetName)s: %(reason)s": "%(senderName)s ha buttato fuori %(targetName)s: %(reason)s",
+ "%(senderName)s withdrew %(targetName)s's invitation": "%(senderName)s ha revocato l'invito per %(targetName)s",
+ "%(senderName)s withdrew %(targetName)s's invitation: %(reason)s": "%(senderName)s ha revocato l'invito per %(targetName)s: %(reason)s",
+ "%(senderName)s unbanned %(targetName)s": "%(senderName)s ha riammesso %(targetName)s",
+ "%(targetName)s left the room": "%(targetName)s ha lasciato la stanza",
+ "[number]": "[numero]",
+ "To view %(spaceName)s, you need an invite": "Per vedere %(spaceName)s ti serve un invito",
+ "Move down": "Sposta giù",
+ "Move up": "Sposta su",
+ "Collapse reply thread": "Riduci finestra di risposta",
+ "%(oldDisplayName)s changed their display name to %(displayName)s": "%(oldDisplayName)s ha modificato il proprio nome in %(displayName)s",
+ "%(senderName)s banned %(targetName)s": "%(senderName)s ha bandito %(targetName)s",
+ "%(senderName)s banned %(targetName)s: %(reason)s": "%(senderName)s ha bandito %(targetName)s: %(reason)s",
+ "%(targetName)s accepted an invitation": "%(targetName)s ha accettato un invito",
+ "%(targetName)s accepted the invitation for %(displayName)s": "%(targetName)s ha accettato l'invito per %(displayName)s",
+ "Some invites couldn't be sent": "Alcuni inviti non sono stati spediti",
+ "We sent the others, but the below people couldn't be invited to ": "Abbiamo inviato gli altri, ma non è stato possibile invitare le seguenti persone in ",
+ "You can click on an avatar in the filter panel at any time to see only the rooms and people associated with that community.": "Puoi cliccare un avatar nella pannello dei filtri quando vuoi per vedere solo le stanze e le persone associate a quella comunità.",
+ "Forward": "Inoltra",
+ "Disagree": "Rifiuta",
+ "Any other reason. Please describe the problem.\nThis will be reported to the room moderators.": "Altri motivi. Si prega di descrivere il problema.\nVerrà segnalato ai moderatori della stanza.",
+ "This room is dedicated to illegal or toxic content or the moderators fail to moderate illegal or toxic content.\n This will be reported to the administrators of %(homeserver)s.": "Questa stanza è dedicata a contenuti illegali o dannosi, oppure i moderatori non riescono a censurare questo tipo di contenuti.\nVerrà segnalata agli amministratori di %(homeserver)s.",
+ "This room is dedicated to illegal or toxic content or the moderators fail to moderate illegal or toxic content.\nThis will be reported to the administrators of %(homeserver)s. The administrators will NOT be able to read the encrypted content of this room.": "Questa stanza è dedicata a contenuti illegali o dannosi, oppure i moderatori non riescono a censurare questo tipo di contenuti.\nVerrà segnalata agli amministratori di %(homeserver)s. Gli amministratori NON potranno leggere i contenuti cifrati di questa stanza.",
+ "This user is spamming the room with ads, links to ads or to propaganda.\nThis will be reported to the room moderators.": "Questo utente sta facendo spam nella stanza con pubblicità, collegamenti ad annunci o a propagande.\nVerrà segnalato ai moderatori della stanza.",
+ "This user is displaying illegal behaviour, for instance by doxing people or threatening violence.\nThis will be reported to the room moderators who may escalate this to legal authorities.": "Questo utente sta mostrando un comportamento illegale, ad esempio facendo doxing o minacciando violenza.\nVerrà segnalato ai moderatori della stanza che potrebbero portarlo in ambito legale.",
+ "This user is displaying toxic behaviour, for instance by insulting other users or sharing adult-only content in a family-friendly room or otherwise violating the rules of this room.\nThis will be reported to the room moderators.": "Questo utente sta mostrando un cattivo comportamento, ad esempio insultando altri utenti o condividendo contenuti per adulti in una stanza per tutti , oppure violando le regole della stessa.\nVerrà segnalato ai moderatori della stanza.",
+ "What this user is writing is wrong.\nThis will be reported to the room moderators.": "Questo utente sta scrivendo cose sbagliate.\nVerrà segnalato ai moderatori della stanza.",
+ "%(oneUser)schanged the server ACLs %(count)s times|one": "%(oneUser)sha cambiato le ACL del server",
+ "%(oneUser)schanged the server ACLs %(count)s times|other": "%(oneUser)sha cambiato le ACL del server %(count)s volte",
+ "%(severalUsers)schanged the server ACLs %(count)s times|one": "%(severalUsers)shanno cambiato le ACL del server",
+ "%(severalUsers)schanged the server ACLs %(count)s times|other": "%(severalUsers)shanno cambiato le ACL del server %(count)s volte",
+ "Message search initialisation failed, check your settings for more information": "Inizializzazione ricerca messaggi fallita, controlla le impostazioni per maggiori informazioni",
+ "Set addresses for this space so users can find this space through your homeserver (%(localDomain)s)": "Imposta gli indirizzi per questo spazio affinché gli utenti lo trovino attraverso il tuo homeserver (%(localDomain)s)",
+ "To publish an address, it needs to be set as a local address first.": "Per pubblicare un indirizzo, deve prima essere impostato come indirizzo locale.",
+ "Published addresses can be used by anyone on any server to join your room.": "Gli indirizzi pubblicati possono essere usati da chiunque su tutti i server per entrare nella tua stanza.",
+ "Published addresses can be used by anyone on any server to join your space.": "Gli indirizzi pubblicati possono essere usati da chiunque su tutti i server per entrare nel tuo spazio.",
+ "Recommended for public spaces.": "Consigliato per gli spazi pubblici.",
+ "Allow people to preview your space before they join.": "Permetti a chiunque di vedere l'anteprima dello spazio prima di unirsi.",
+ "Failed to update the history visibility of this space": "Aggiornamento visibilità cronologia dello spazio fallito",
+ "Failed to update the guest access of this space": "Aggiornamento accesso ospiti dello spazio fallito",
+ "Failed to update the visibility of this space": "Aggiornamento visibilità dello spazio fallito",
+ "Show notification badges for People in Spaces": "Mostra messaggi di notifica per le persone negli spazi",
+ "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.": "Se disattivato, puoi comunque aggiungere messaggi diretti agli spazi personali. Se attivato, vedrai automaticamente qualunque membro dello spazio.",
+ "%(targetName)s left the room: %(reason)s": "%(targetName)s ha abbandonato la stanza: %(reason)s",
+ "%(targetName)s rejected the invitation": "%(targetName)s ha rifiutato l'invito",
+ "%(targetName)s joined the room": "%(targetName)s è entrato/a nella stanza",
+ "%(senderName)s made no change": "%(senderName)s non ha fatto modifiche",
+ "%(senderName)s set a profile picture": "%(senderName)s ha impostato un'immagine del profilo",
+ "%(senderName)s changed their profile picture": "%(senderName)s ha cambiato la propria immagine del profilo",
+ "%(senderName)s removed their profile picture": "%(senderName)s ha rimosso la propria immagine del profilo",
+ "%(senderName)s removed their display name (%(oldDisplayName)s)": "%(senderName)s ha rimosso il proprio nome (%(oldDisplayName)s)",
+ "%(senderName)s set their display name to %(displayName)s": "%(senderName)s ha impostato il proprio nome a %(displayName)s"
}
diff --git a/src/i18n/strings/ja.json b/src/i18n/strings/ja.json
index 180d63f33e..e395c51254 100644
--- a/src/i18n/strings/ja.json
+++ b/src/i18n/strings/ja.json
@@ -2503,5 +2503,6 @@
"Support": "サポート",
"You can change these anytime.": "ここで入力した情報はいつでも編集できます。",
"Add some details to help people recognise it.": "情報を入力してください。",
- "View dev tools": "開発者ツールを表示"
+ "View dev tools": "開発者ツールを表示",
+ "To view %(spaceName)s, you need an invite": "%(spaceName)s を閲覧するには招待が必要です"
}
diff --git a/src/i18n/strings/lt.json b/src/i18n/strings/lt.json
index e216c2de5a..4449ef97c2 100644
--- a/src/i18n/strings/lt.json
+++ b/src/i18n/strings/lt.json
@@ -2185,5 +2185,241 @@
"Frequently Used": "Dažnai Naudojama",
"Something went wrong when trying to get your communities.": "Kažkas nepavyko bandant gauti jūsų bendruomenes.",
"Can't load this message": "Nepavyko įkelti šios žinutės",
- "Submit logs": "Pateikti žurnalus"
+ "Submit logs": "Pateikti žurnalus",
+ "Botswana": "Botsvana",
+ "Bosnia": "Bosnija",
+ "Bolivia": "Bolivija",
+ "Bhutan": "Butanas",
+ "Bermuda": "Bermudai",
+ "Benin": "Beninas",
+ "Belize": "Belizas",
+ "Belarus": "Baltarusija",
+ "Barbados": "Barbadosas",
+ "Bahrain": "Bahreinas",
+ "Your Security Key has been copied to your clipboard, paste it to:": "Jūsų Saugumo Raktas buvo nukopijuotas į iškarpinę, įklijuokite jį į:",
+ "Great! This Security Phrase looks strong enough.": "Puiku! Ši Saugumo Frazė atrodo pakankamai stipri.",
+ "Revoke permissions": "Atšaukti leidimus",
+ "Take a picture": "Padarykite nuotrauką",
+ "Start audio stream": "Pradėti garso transliaciją",
+ "Failed to start livestream": "Nepavyko pradėti tiesioginės transliacijos",
+ "Unable to start audio streaming.": "Nepavyksta pradėti garso transliacijos.",
+ "Set a new status...": "Nustatykite naują būseną...",
+ "Set status": "Nustatyti būseną",
+ "Clear status": "Išvalyti būseną",
+ "Resend %(unsentCount)s reaction(s)": "Pakartotinai išsiųsti %(unsentCount)s reakciją (-as)",
+ "Hold": "Sulaikyti",
+ "Resume": "Tęsti",
+ "If you've forgotten your Security Key you can ": "Jei pamiršote Saugumo Raktą, galite ",
+ "Access your secure message history and set up secure messaging by entering your Security Key.": "Prieikite prie savo saugių žinučių istorijos ir nustatykite saugių žinučių siuntimą įvesdami Saugumo Raktą.",
+ "This looks like a valid Security Key!": "Atrodo, kad tai tinkamas Saugumo Raktas!",
+ "Not a valid Security Key": "Netinkamas Saugumo Raktas",
+ "Enter Security Key": "Įveskite Saugumo Raktą",
+ "If you've forgotten your Security Phrase you can use your Security Key or set up new recovery options": "Jei pamiršote savo Saugumo Frazę, galite panaudoti savo Saugumo Raktą arba nustatyti naujas atkūrimo parinktis",
+ "Access your secure message history and set up secure messaging by entering your Security Phrase.": "Pasiekite savo saugių žinučių istoriją ir nustatykite saugių žinučių siuntimą įvesdami Saugumo Frazę.",
+ "Enter Security Phrase": "Įveskite Saugumo Frazę",
+ "Keys restored": "Raktai atkurti",
+ "Backup could not be decrypted with this Security Phrase: please verify that you entered the correct Security Phrase.": "Atsarginės kopijos nepavyko iššifruoti naudojant šią Saugumo Frazę: prašome patikrinti, ar įvedėte teisingą Saugumo Frazę.",
+ "Incorrect Security Phrase": "Neteisinga Saugumo Frazė",
+ "Backup could not be decrypted with this Security Key: please verify that you entered the correct Security Key.": "Atsarginės kopijos nepavyko iššifruoti naudojant šį Saugumo Raktą: prašome patikrinti, ar įvedėte teisingą Saugumo Raktą.",
+ "Security Key mismatch": "Saugumo Rakto nesutapimas",
+ "Unable to load backup status": "Nepavyksta įkelti atsarginės kopijos būsenos",
+ "%(completed)s of %(total)s keys restored": "%(completed)s iš %(total)s raktų atkurta",
+ "Fetching keys from server...": "Gauname raktus iš serverio...",
+ "Unable to set up keys": "Nepavyksta nustatyti raktų",
+ "Use your Security Key to continue.": "Naudokite Saugumo Raktą kad tęsti.",
+ "Security Key": "Saugumo Raktas",
+ "Unable to access secret storage. Please verify that you entered the correct Security Phrase.": "Nepavyksta pasiekti slaptosios saugyklos. Prašome patvirtinti kad teisingai įvedėte Saugumo Frazę.",
+ "If you reset everything, you will restart with no trusted sessions, no trusted users, and might not be able to see past messages.": "Jei viską nustatysite iš naujo, paleisite iš naujo be patikimų seansų, be patikimų vartotojų ir galbūt negalėsite matyti ankstesnių žinučių.",
+ "Only do this if you have no other device to complete verification with.": "Taip darykite tik tuo atveju, jei neturite kito prietaiso, kuriuo galėtumėte užbaigti patikrinimą.",
+ "Reset everything": "Iš naujo nustatyti viską",
+ "Forgotten or lost all recovery methods? Reset all": "Pamiršote arba praradote visus atkūrimo metodus? Iš naujo nustatyti viską",
+ "Invalid Security Key": "Klaidingas Saugumo Raktas",
+ "Wrong Security Key": "Netinkamas Saugumo Raktas",
+ "Looks good!": "Atrodo gerai!",
+ "Wrong file type": "Netinkamas failo tipas",
+ "Remember this": "Prisiminkite tai",
+ "The widget will verify your user ID, but won't be able to perform actions for you:": "Šis valdiklis patvirtins jūsų vartotojo ID, bet negalės už jus atlikti veiksmų:",
+ "Allow this widget to verify your identity": "Leiskite šiam valdikliui patvirtinti jūsų tapatybę",
+ "Remember my selection for this widget": "Prisiminti mano pasirinkimą šiam valdikliui",
+ "Decline All": "Atmesti Visus",
+ "Approve": "Patvirtinti",
+ "This widget would like to:": "Šis valdiklis norėtų:",
+ "Approve widget permissions": "Patvirtinti valdiklio leidimus",
+ "Verification Request": "Patikrinimo Užklausa",
+ "Verify other login": "Patikrinkite kitą prisijungimą",
+ "Document": "Dokumentas",
+ "Summary": "Santrauka",
+ "Service": "Paslauga",
+ "To continue you need to accept the terms of this service.": "Norėdami tęsti, turite sutikti su šios paslaugos sąlygomis.",
+ "Be found by phone or email": "Tapkite randami telefonu arba el. paštu",
+ "Find others by phone or email": "Ieškokite kitų telefonu arba el. paštu",
+ "Save Changes": "Išsaugoti Pakeitimus",
+ "Saving...": "Išsaugoma...",
+ "Link to selected message": "Nuoroda į pasirinktą pranešimą",
+ "Share Community": "Dalintis Bendruomene",
+ "Share User": "Dalintis Vartotoju",
+ "Please check your email and click on the link it contains. Once this is done, click continue.": "Patikrinkite savo el. laišką ir spustelėkite jame esančią nuorodą. Kai tai padarysite, spauskite tęsti.",
+ "Verification Pending": "Laukiama Patikrinimo",
+ "Clearing your browser's storage may fix the problem, but will sign you out and cause any encrypted chat history to become unreadable.": "Išvalius naršyklės saugyklą, problema gali būti išspręsta, tačiau jus atjungs ir užšifruotų pokalbių istorija taps neperskaitoma.",
+ "Clear Storage and Sign Out": "Išvalyti Saugyklą ir Atsijungti",
+ "Reset event store": "Iš naujo nustatyti įvykių saugyklą",
+ "If you do, please note that none of your messages will be deleted, but the search experience might be degraded for a few moments whilst the index is recreated": "Jei to norite, atkreipkite dėmesį, kad nė viena iš jūsų žinučių nebus ištrinta, tačiau keletą akimirkų, kol bus atkurtas indeksas, gali sutrikti paieška",
+ "You most likely do not want to reset your event index store": "Tikriausiai nenorite iš naujo nustatyti įvykių indekso saugyklos",
+ "Reset event store?": "Iš naujo nustatyti įvykių saugyklą?",
+ "About homeservers": "Apie namų serverius",
+ "Learn more": "Sužinokite daugiau",
+ "Use your preferred Matrix homeserver if you have one, or host your own.": "Naudokite pageidaujamą Matrix namų serverį, jei tokį turite, arba talpinkite savo.",
+ "Other homeserver": "Kitas namų serveris",
+ "We call the places where you can host your account ‘homeservers’.": "Vietas, kuriose galite talpinti savo paskyrą, vadiname 'namų serveriais'.",
+ "Sign into your homeserver": "Prisijunkite prie savo namų serverio",
+ "Matrix.org is the biggest public homeserver in the world, so it’s a good place for many.": "Matrix.org yra didžiausias viešasis namų serveris pasaulyje, todėl tai gera vieta daugeliui.",
+ "Specify a homeserver": "Nurodykite namų serverį",
+ "Invalid URL": "Netinkamas URL",
+ "Unable to validate homeserver": "Nepavyksta patvirtinti namų serverio",
+ "Recent changes that have not yet been received": "Naujausi pakeitimai, kurie dar nebuvo gauti",
+ "The server is not configured to indicate what the problem is (CORS).": "Serveris nėra sukonfigūruotas taip, kad būtų galima nurodyti, kokia yra problema (CORS).",
+ "A connection error occurred while trying to contact the server.": "Bandant susisiekti su serveriu įvyko ryšio klaida.",
+ "The server has denied your request.": "Serveris atmetė jūsų užklausą.",
+ "The server is offline.": "Serveris yra išjungtas.",
+ "A browser extension is preventing the request.": "Naršyklės plėtinys užkerta kelią užklausai.",
+ "Your firewall or anti-virus is blocking the request.": "Jūsų užkarda arba antivirusinė programa blokuoja užklausą.",
+ "The server (%(serverName)s) took too long to respond.": "Serveris (%(serverName)s) užtruko per ilgai atsakydamas.",
+ "Server isn't responding": "Serveris neatsako",
+ "You're all caught up.": "Jūs jau viską pasivijote.",
+ "You'll upgrade this room from to .": "Atnaujinsite šį kambarį iš į .",
+ "This usually only affects how the room is processed on the server. If you're having problems with your %(brand)s, please report a bug.": "Paprastai tai turi įtakos tik tam, kaip kambarys apdorojamas serveryje. Jei turite problemų su %(brand)s, praneškite apie klaidą.",
+ "Upgrade private room": "Atnaujinti privatų kambarį",
+ "Automatically invite users": "Automatiškai pakviesti vartotojus",
+ "Just a heads up, if you don't add an email and forget your password, you could permanently lose access to your account.": "Įspėjame, kad nepridėję el. pašto ir pamiršę slaptažodį galite visam laikui prarasti prieigą prie savo paskyros.",
+ "Continuing without email": "Tęsiama be el. pašto",
+ "Doesn't look like a valid email address": "Neatrodo kaip tinkamas el. pašto adresas",
+ "We recommend you change your password and Security Key in Settings immediately": "Rekomenduojame nedelsiant pakeisti slaptažodį ir Saugumo Raktą nustatymuose",
+ "Your password": "Jūsų slaptažodis",
+ "Your account is not secure": "Jūsų paskyra nėra saugi",
+ "Data on this screen is shared with %(widgetDomain)s": "Duomenimis šiame ekrane yra dalinamasi su %(widgetDomain)s",
+ "Message edits": "Žinutės redagavimai",
+ "Your homeserver doesn't seem to support this feature.": "Panašu, kad jūsų namų serveris nepalaiko šios galimybės.",
+ "If they don't match, the security of your communication may be compromised.": "Jei jie nesutampa, gali būti pažeistas jūsų komunikacijos saugumas.",
+ "Clear cache and resync": "Išvalyti talpyklą ir sinchronizuoti iš naujo",
+ "Signature upload failed": "Parašo įkėlimas nepavyko",
+ "Signature upload success": "Parašo įkėlimas sėkmingas",
+ "Unable to upload": "Nepavyksta įkelti",
+ "Cancelled signature upload": "Atšauktas parašo įkėlimas",
+ "Upload completed": "Įkėlimas baigtas",
+ "%(brand)s encountered an error during upload of:": "%(brand)s aptiko klaidą įkeliant:",
+ "a key signature": "rakto parašas",
+ "a new master key signature": "naujas pagrindinio rakto parašas",
+ "Transfer": "Perkelti",
+ "Invited people will be able to read old messages.": "Pakviesti asmenys galės skaityti senus pranešimus.",
+ "Invite to %(roomName)s": "Pakvietimas į %(roomName)s",
+ "Or send invite link": "Arba atsiųskite kvietimo nuorodą",
+ "If you can't see who you’re looking for, send them your invite link below.": "Jei nematote ieškomo asmens, atsiųskite jam žemiau pateiktą kvietimo nuorodą.",
+ "Some suggestions may be hidden for privacy.": "Kai kurie pasiūlymai gali būti paslėpti dėl privatumo.",
+ "Go": "Eiti",
+ "This won't invite them to %(communityName)s. To invite someone to %(communityName)s, click here": "Tai nepakvies jų į %(communityName)s. Norėdami pakviesti ką nors į %(communityName)s, spustelėkite čia",
+ "Start a conversation with someone using their name or username (like ).": "Pradėkite pokalbį su asmeniu naudodami jo vardą arba vartotojo vardą (pvz., ).",
+ "Start a conversation with someone using their name, email address or username (like ).": "Pradėkite pokalbį su kažkuo naudodami jų vardą, el. pašto adresą arba vartotojo vardą (pvz., ).",
+ "May include members not in %(communityName)s": "Gali apimti narius, neįtrauktus į %(communityName)s",
+ "Suggestions": "Pasiūlymai",
+ "Recent Conversations": "Pastarieji pokalbiai",
+ "The following users might not exist or are invalid, and cannot be invited: %(csvNames)s": "Toliau išvardyti vartotojai gali neegzistuoti arba būti negaliojantys, todėl jų negalima pakviesti: %(csvNames)s",
+ "Failed to find the following users": "Nepavyko rasti šių vartotojų",
+ "Failed to transfer call": "Nepavyko perduoti skambučio",
+ "A call can only be transferred to a single user.": "Skambutį galima perduoti tik vienam naudotojui.",
+ "We couldn't invite those users. Please check the users you want to invite and try again.": "Negalėjome pakviesti šių vartotojų. Patikrinkite vartotojus, kuriuos norite pakviesti, ir bandykite dar kartą.",
+ "Something went wrong trying to invite the users.": "Bandant pakviesti vartotojus kažkas nepavyko.",
+ "We couldn't create your DM.": "Negalėjome sukurti jūsų AŽ.",
+ "Invite by email": "Kviesti el. paštu",
+ "Click the button below to confirm your identity.": "Spustelėkite toliau esantį mygtuką, kad patvirtintumėte savo tapatybę.",
+ "Confirm to continue": "Patvirtinkite, kad tęstumėte",
+ "Incoming Verification Request": "Įeinantis Patikrinimo Prašymas",
+ "Minimize dialog": "Sumažinti dialogą",
+ "Maximize dialog": "Maksimaliai padidinti dialogą",
+ "You should know": "Turėtumėte žinoti",
+ "Terms of Service": "Paslaugų Teikimo Sąlygos",
+ "Privacy Policy": "Privatumo Politika",
+ "Cookie Policy": "Slapukų Politika",
+ "Learn more in our , and .": "Sužinokite daugiau mūsų , ir .",
+ "Continuing temporarily allows the %(hostSignupBrand)s setup process to access your account to fetch verified email addresses. This data is not stored.": "Tęsiant laikinai leidžiama %(hostSignupBrand)s sąrankos procesui prisijungti prie jūsų paskyros ir gauti patikrintus el. pašto adresus. Šie duomenys nėra saugomi.",
+ "Failed to connect to your homeserver. Please close this dialog and try again.": "Nepavyko prisijungti prie namų serverio. Uždarykite šį dialogą ir bandykite dar kartą.",
+ "Abort": "Nutraukti",
+ "Search for rooms or people": "Ieškoti kambarių ar žmonių",
+ "Message preview": "Žinutės peržiūra",
+ "Forward message": "Persiųsti žinutę",
+ "Open link": "Atidaryti nuorodą",
+ "Sent": "Išsiųsta",
+ "Sending": "Siunčiama",
+ "You don't have permission to do this": "Jūs neturite leidimo tai daryti",
+ "There are two ways you can provide feedback and help us improve %(brand)s.": "Yra du būdai, kaip galite pateikti atsiliepimus ir padėti mums patobulinti %(brand)s.",
+ "Comment": "Komentaras",
+ "Add comment": "Pridėti komentarą",
+ "Please go into as much detail as you like, so we can track down the problem.": "Pateikite kuo daugiau informacijos, kad galėtume nustatyti problemą.",
+ "Tell us below how you feel about %(brand)s so far.": "Toliau papasakokite mums, ką iki šiol manote apie %(brand)s.",
+ "Rate %(brand)s": "Vertinti %(brand)s",
+ "Feedback sent": "Atsiliepimas išsiųstas",
+ "Level": "Lygis",
+ "Setting:": "Nustatymas:",
+ "Value": "Reikšmė",
+ "Setting ID": "Nustatymo ID",
+ "Failed to save settings": "Nepavyko išsaugoti nustatymų",
+ "Settings Explorer": "Nustatymų Naršyklė",
+ "There was an error finding this widget.": "Įvyko klaida ieškant šio valdiklio.",
+ "Active Widgets": "Aktyvūs Valdikliai",
+ "Verification Requests": "Patikrinimo Prašymai",
+ "View Servers in Room": "Peržiūrėti serverius Kambaryje",
+ "Server did not return valid authentication information.": "Serveris negrąžino galiojančios autentifikavimo informacijos.",
+ "Server did not require any authentication": "Serveris nereikalavo jokio autentifikavimo",
+ "There was a problem communicating with the server. Please try again.": "Kilo problemų bendraujant su serveriu. Bandykite dar kartą.",
+ "Confirm account deactivation": "Patvirtinkite paskyros deaktyvavimą",
+ "Create a room in %(communityName)s": "Sukurti kambarį %(communityName)s bendruomenėje",
+ "You might disable this if the room will be used for collaborating with external teams who have their own homeserver. This cannot be changed later.": "Šią funkciją galite išjungti, jei kambarys bus naudojamas bendradarbiavimui su išorės komandomis, turinčiomis savo namų serverį. Vėliau to pakeisti negalima.",
+ "Something went wrong whilst creating your community": "Kuriant bendruomenę kažkas nepavyko",
+ "Add image (optional)": "Pridėti nuotrauką (nebūtina)",
+ "Enter name": "Įveskite pavadinimą",
+ "What's the name of your community or team?": "Koks jūsų bendruomenės ar komandos pavadinimas?",
+ "You can change this later if needed.": "Jei reikės, vėliau tai galite pakeisti.",
+ "Use this when referencing your community to others. The community ID cannot be changed.": "Naudokite tai, kai apie savo bendruomenę sakote kitiems. Bendruomenės ID negalima keisti.",
+ "Community ID: +:%(domain)s": "Bendruomenės ID: +:%(domain)s",
+ "There was an error creating your community. The name may be taken or the server is unable to process your request.": "Klaida kuriant jūsų bendruomenę. Pavadinimas gali būti užimtas arba serveris negali apdoroti jūsų užklausos.",
+ "Clear all data": "Išvalyti visus duomenis",
+ "Removing…": "Pašalinama…",
+ "Send %(count)s invites|one": "Siųsti %(count)s pakvietimą",
+ "Send %(count)s invites|other": "Siųsti %(count)s pakvietimus",
+ "Hide": "Slėpti",
+ "Add another email": "Pridėti dar vieną el. paštą",
+ "Reminder: Your browser is unsupported, so your experience may be unpredictable.": "Primename: Jūsų naršyklė yra nepalaikoma, todėl jūsų patirtis gali būti nenuspėjama.",
+ "Send feedback": "Siųsti atsiliepimą",
+ "You may contact me if you have any follow up questions": "Jei turite papildomų klausimų, galite susisiekti su manimi",
+ "To leave the beta, visit your settings.": "Norėdami išeiti iš beta versijos, apsilankykite savo nustatymuose.",
+ "%(featureName)s beta feedback": "%(featureName)s beta atsiliepimas",
+ "Thank you for your feedback, we really appreciate it.": "Dėkojame už jūsų atsiliepimą, mes tai labai vertiname.",
+ "Beta feedback": "Beta atsiliepimai",
+ "Close dialog": "Uždaryti dialogą",
+ "This version of %(brand)s does not support viewing some encrypted files": "Ši %(brand)s versija nepalaiko kai kurių užšifruotų failų peržiūros",
+ "Use the Desktop app to search encrypted messages": "Naudokite Kompiuterio programą kad ieškoti užšifruotų žinučių",
+ "Use the Desktop app to see all encrypted files": "Naudokite Kompiuterio programą kad matytumėte visus užšifruotus failus",
+ "Error - Mixed content": "Klaida - Maišytas turinys",
+ "Error loading Widget": "Klaida kraunant Valdiklį",
+ "This widget may use cookies.": "Šiame valdiklyje gali būti naudojami slapukai.",
+ "Widget added by": "Valdiklį pridėjo",
+ "Widget ID": "Valdiklio ID",
+ "Room ID": "Kambario ID",
+ "Your user ID": "Jūsų vartotojo ID",
+ "Sri Lanka": "Šri Lanka",
+ "Spain": "Ispanija",
+ "South Korea": "Pietų Korėja",
+ "South Africa": "Pietų Afrika",
+ "Slovakia": "Slovakija",
+ "Singapore": "Singapūras",
+ "Philippines": "Filipinai",
+ "Pakistan": "Pakistanas",
+ "Norway": "Norvegija",
+ "North Korea": "Šiaurės Korėja",
+ "Nigeria": "Nigerija",
+ "Niger": "Nigeris",
+ "Nicaragua": "Nikaragva",
+ "New Zealand": "Naujoji Zelandija",
+ "New Caledonia": "Naujoji Kaledonija",
+ "Netherlands": "Nyderlandai",
+ "Cayman Islands": "Kaimanų Salos"
}
diff --git a/src/i18n/strings/nl.json b/src/i18n/strings/nl.json
index 1818a64e54..72168eb5ff 100644
--- a/src/i18n/strings/nl.json
+++ b/src/i18n/strings/nl.json
@@ -1750,7 +1750,7 @@
"exists": "aanwezig",
"Sign In or Create Account": "Meld u aan of maak een account aan",
"Use your account or create a new one to continue.": "Gebruik uw bestaande account of maak een nieuwe aan om verder te gaan.",
- "Create Account": "Registeren",
+ "Create Account": "Registreren",
"Displays information about a user": "Geeft informatie weer over een gebruiker",
"Order rooms by name": "Gesprekken sorteren op naam",
"Show rooms with unread notifications first": "Gesprekken met ongelezen meldingen eerst tonen",
@@ -2617,7 +2617,7 @@
"Remain on your screen when viewing another room, when running": "Blijft op uw scherm wanneer u een andere gesprek bekijkt, zolang het beschikbaar is",
"(their device couldn't start the camera / microphone)": "(hun toestel kon de camera / microfoon niet starten)",
"🎉 All servers are banned from participating! This room can no longer be used.": "🎉 Alle servers zijn verbannen van deelname! Dit gesprek kan niet langer gebruikt worden.",
- "%(senderDisplayName)s changed the server ACLs for this room.": "%(senderDisplayName)s vernaderde de server ACL's voor dit gesprek.",
+ "%(senderDisplayName)s changed the server ACLs for this room.": "%(senderDisplayName)s veranderde de server ACL's voor dit gesprek.",
"%(senderDisplayName)s set the server ACLs for this room.": "%(senderDisplayName)s stelde de server ACL's voor dit gesprek in.",
"Converts the room to a DM": "Verandert dit groepsgesprek in een DM",
"Converts the DM to a room": "Verandert deze DM in een groepsgesprek",
@@ -3285,5 +3285,89 @@
"If you have permissions, open the menu on any message and select Pin to stick them here.": "Als u de rechten heeft, open dan het menu op elk bericht en selecteer Vastprikken om ze hier te zetten.",
"Nothing pinned, yet": "Nog niks vastgeprikt",
"End-to-end encryption isn't enabled": "Eind-tot-eind-versleuteling is uitgeschakeld",
- "Your private messages are normally encrypted, but this room isn't. Usually this is due to an unsupported device or method being used, like email invites. Enable encryption in settings.": "Uw privéberichten zijn normaal gesproken versleuteld, maar dit gesprek niet. Meestal is dit te wijten aan een niet-ondersteund apparaat of methode die wordt gebruikt, zoals e-mailuitnodigingen. Versleuting inschakelen in instellingen."
+ "Your private messages are normally encrypted, but this room isn't. Usually this is due to an unsupported device or method being used, like email invites. Enable encryption in settings.": "Uw privéberichten zijn normaal gesproken versleuteld, maar dit gesprek niet. Meestal is dit te wijten aan een niet-ondersteund apparaat of methode die wordt gebruikt, zoals e-mailuitnodigingen. Versleuting inschakelen in instellingen.",
+ "[number]": "[number]",
+ "To view %(spaceName)s, you need an invite": "Om %(spaceName)s te bekijken heeft u een uitnodiging nodig",
+ "You can click on an avatar in the filter panel at any time to see only the rooms and people associated with that community.": "U kunt op elk moment op een avatar klikken in het filterpaneel om alleen de gesprekken en personen te zien die geassocieerd zijn met die gemeenschap.",
+ "Move down": "Omlaag",
+ "Move up": "Omhoog",
+ "Report": "Melden",
+ "Collapse reply thread": "Antwoorddraad invouwen",
+ "Show preview": "Preview weergeven",
+ "View source": "Bron bekijken",
+ "Forward": "Vooruit",
+ "Settings - %(spaceName)s": "Instellingen - %(spaceName)s",
+ "Report the entire room": "Rapporteer het hele gesprek",
+ "Spam or propaganda": "Spam of propaganda",
+ "Illegal Content": "Illegale Inhoud",
+ "Toxic Behaviour": "Giftig Gedrag",
+ "Disagree": "Niet mee eens",
+ "Please pick a nature and describe what makes this message abusive.": "Kies een reden en beschrijf wat dit bericht kwetsend maakt.",
+ "Any other reason. Please describe the problem.\nThis will be reported to the room moderators.": "Een andere reden. Beschrijf alstublieft het probleem.\nDit zal gerapporteerd worden aan de gesprekmoderators.",
+ "This room is dedicated to illegal or toxic content or the moderators fail to moderate illegal or toxic content.\n This will be reported to the administrators of %(homeserver)s.": "Dit gesprek is gewijd aan illegale of giftige inhoud of de moderators falen om illegale of giftige inhoud te modereren.\nDit zal gerapporteerd worden aan de beheerders van %(homeserver)s.",
+ "This room is dedicated to illegal or toxic content or the moderators fail to moderate illegal or toxic content.\nThis will be reported to the administrators of %(homeserver)s. The administrators will NOT be able to read the encrypted content of this room.": "Dit gesprek is gewijd aan illegale of giftige inhoud of de moderators falen om illegale of giftige inhoud te modereren.\nDit zal gerapporteerd worden aan de beheerders van %(homeserver)s. De beheerders zullen NIET in staat zijn om de versleutelde inhoud van dit gesprek te lezen.",
+ "This user is spamming the room with ads, links to ads or to propaganda.\nThis will be reported to the room moderators.": "Deze persoon spamt de kamer met advertenties, links naar advertenties of propaganda.\nDit zal gerapporteerd worden aan de moderators van dit gesprek.",
+ "This user is displaying illegal behaviour, for instance by doxing people or threatening violence.\nThis will be reported to the room moderators who may escalate this to legal authorities.": "Deze persoon vertoont illegaal gedrag, bijvoorbeeld door doxing van personen of te dreigen met geweld.\nDit zal gerapporteerd worden aan de moderators van dit gesprek die dit kunnen doorzetten naar de gerechtelijke autoriteiten.",
+ "What this user is writing is wrong.\nThis will be reported to the room moderators.": "Wat deze persoon schrijft is verkeerd.\nDit zal worden gerapporteerd aan de gesprekmoderators.",
+ "This user is displaying toxic behaviour, for instance by insulting other users or sharing adult-only content in a family-friendly room or otherwise violating the rules of this room.\nThis will be reported to the room moderators.": "Deze persoon vertoont giftig gedrag, bijvoorbeeld door het beledigen van andere personen of het delen van inhoud voor volwassenen in een gezinsvriendelijke gesprek of het op een andere manier overtreden van de regels van dit gesprek.\nDit zal worden gerapporteerd aan de gesprekmoderators.",
+ "Please provide an address": "Geef een adres op",
+ "%(oneUser)schanged the server ACLs %(count)s times|one": "%(oneUser)s veranderde de server ACLs",
+ "%(oneUser)schanged the server ACLs %(count)s times|other": "%(oneUser)s veranderde de server ACLs %(count)s keer",
+ "%(severalUsers)schanged the server ACLs %(count)s times|one": "%(severalUsers)s veranderden de server ACLs",
+ "%(severalUsers)schanged the server ACLs %(count)s times|other": "%(severalUsers)s veranderden de server ACLs %(count)s keer",
+ "Message search initialisation failed, check your settings for more information": "Bericht zoeken initialisatie mislukt, controleer uw instellingen voor meer informatie",
+ "Set addresses for this space so users can find this space through your homeserver (%(localDomain)s)": "Stel adressen in voor deze space zodat personen deze ruimte kunnen vinden via uw homeserver (%(localDomain)s)",
+ "To publish an address, it needs to be set as a local address first.": "Om een adres te publiceren, moet het eerst als een lokaaladres worden ingesteld.",
+ "Published addresses can be used by anyone on any server to join your room.": "Gepubliceerde adressen kunnen door iedereen op elke server gebruikt worden om bij uw gesprek te komen.",
+ "Published addresses can be used by anyone on any server to join your space.": "Gepubliceerde adressen kunnen door iedereen op elke server gebruikt worden om uw space te betreden.",
+ "This space has no local addresses": "Deze space heeft geen lokaaladres",
+ "Space information": "Space informatie",
+ "Collapse": "Invouwen",
+ "Expand": "Uitvouwen",
+ "Recommended for public spaces.": "Aanbevolen voor openbare spaces.",
+ "Allow people to preview your space before they join.": "Personen toestaan een voorbeeld van uw space te zien voor deelname.",
+ "Preview Space": "Voorbeeld Space",
+ "only invited people can view and join": "alleen uitgenodigde personen kunnen lezen en deelnemen",
+ "anyone with the link can view and join": "iedereen met een link kan lezen en deelnemen",
+ "Decide who can view and join %(spaceName)s.": "Bepaal wie kan lezen en deelnemen aan %(spaceName)s.",
+ "Visibility": "Zichtbaarheid",
+ "This may be useful for public spaces.": "Dit kan nuttig zijn voor openbare spaces.",
+ "Guests can join a space without having an account.": "Gasten kunnen deelnemen aan een space zonder een account.",
+ "Enable guest access": "Gastentoegang inschakelen",
+ "Failed to update the history visibility of this space": "Het bijwerken van de geschiedenis leesbaarheid voor deze space is mislukt",
+ "Failed to update the guest access of this space": "Het bijwerken van de gastentoegang van deze space is niet gelukt",
+ "Failed to update the visibility of this space": "Het bijwerken van de zichtbaarheid van deze space is mislukt",
+ "Address": "Adres",
+ "e.g. my-space": "v.b. mijn-space",
+ "Silence call": "Oproep dempen",
+ "Sound on": "Geluid aan",
+ "Show notification badges for People in Spaces": "Toon meldingsbadge voor personen 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.": "Indien uitgeschakeld, kunt u nog steeds directe gesprekken toevoegen aan persoonlijke spaces. Indien ingeschakeld, ziet u automatisch iedereen die lid is van de space.",
+ "Show people in spaces": "Toon personen in spaces",
+ "Show all rooms in Home": "Toon alle gesprekken in Home",
+ "Report to moderators prototype. In rooms that support moderation, the `report` button will let you report abuse to room moderators": "Meld aan moderators prototype. In gesprekken die moderatie ondersteunen, kunt u met de `melden` knop misbruik melden aan de gesprekmoderators",
+ "%(senderName)s changed the pinned messages for the room.": "%(senderName)s heeft de vastgeprikte berichten voor het gesprek gewijzigd.",
+ "%(senderName)s kicked %(targetName)s": "%(senderName)s heeft %(targetName)s verwijderd",
+ "%(senderName)s kicked %(targetName)s: %(reason)s": "%(senderName)s heeft %(targetName)s verbannen: %(reason)s",
+ "%(senderName)s withdrew %(targetName)s's invitation": "%(senderName)s heeft de uitnodiging van %(targetName)s ingetrokken",
+ "%(senderName)s withdrew %(targetName)s's invitation: %(reason)s": "%(senderName)s heeft de uitnodiging van %(targetName)s ingetrokken: %(reason)s",
+ "%(senderName)s unbanned %(targetName)s": "%(senderName)s heeft %(targetName)s ontbannen",
+ "%(targetName)s left the room": "%(targetName)s heeft het gesprek verlaten",
+ "%(targetName)s left the room: %(reason)s": "%(targetName)s heeft het gesprek verlaten: %(reason)s",
+ "%(targetName)s rejected the invitation": "%(targetName)s heeft de uitnodiging geweigerd",
+ "%(targetName)s joined the room": "%(targetName)s is tot het gesprek toegetreden",
+ "%(senderName)s made no change": "%(senderName)s maakte geen wijziging",
+ "%(senderName)s set a profile picture": "%(senderName)s profielfoto is ingesteld",
+ "%(senderName)s changed their profile picture": "%(senderName)s profielfoto is gewijzigd",
+ "%(senderName)s removed their profile picture": "%(senderName)s profielfoto is verwijderd",
+ "%(senderName)s removed their display name (%(oldDisplayName)s)": "%(senderName)s weergavenaam (%(oldDisplayName)s) is verwijderd",
+ "%(senderName)s set their display name to %(displayName)s": "%(senderName)s heeft de weergavenaam %(displayName)s aangenomen",
+ "%(oldDisplayName)s changed their display name to %(displayName)s": "%(oldDisplayName)s heeft %(displayName)s als weergavenaam aangenomen",
+ "%(senderName)s banned %(targetName)s": "%(senderName)s verbande %(targetName)s",
+ "%(senderName)s banned %(targetName)s: %(reason)s": "%(senderName)s verbande %(targetName)s: %(reason)s",
+ "%(senderName)s invited %(targetName)s": "%(senderName)s nodigde %(targetName)s uit",
+ "%(targetName)s accepted an invitation": "%(targetName)s accepteerde de uitnodiging",
+ "%(targetName)s accepted the invitation for %(displayName)s": "%(targetName)s accepteerde de uitnodiging voor %(displayName)s",
+ "Some invites couldn't be sent": "Sommige uitnodigingen konden niet verstuurd worden",
+ "We sent the others, but the below people couldn't be invited to ": "De anderen zijn verstuurd, maar de volgende mensen konden niet worden uitgenodigd voor "
}
diff --git a/src/i18n/strings/pl.json b/src/i18n/strings/pl.json
index 641247e6ee..784307acff 100644
--- a/src/i18n/strings/pl.json
+++ b/src/i18n/strings/pl.json
@@ -438,7 +438,7 @@
"%(senderName)s changed the pinned messages for the room.": "%(senderName)s zmienił(a) przypiętą wiadomość dla tego pokoju.",
"Message Pinning": "Przypinanie wiadomości",
"Send": "Wyślij",
- "Mirror local video feed": "Powiel lokalne wideo",
+ "Mirror local video feed": "Lustrzane odbicie wideo",
"Enable inline URL previews by default": "Włącz domyślny podgląd URL w tekście",
"Enable URL previews for this room (only affects you)": "Włącz podgląd URL dla tego pokoju (dotyczy tylko Ciebie)",
"Enable URL previews by default for participants in this room": "Włącz domyślny podgląd URL dla uczestników w tym pokoju",
diff --git a/src/i18n/strings/si.json b/src/i18n/strings/si.json
index 5a81da879f..0fc3f38ca7 100644
--- a/src/i18n/strings/si.json
+++ b/src/i18n/strings/si.json
@@ -5,5 +5,8 @@
"Confirm adding this email address by using Single Sign On to prove your identity.": "ඔබගේ අනන්යතාවය සනාථ කිරීම සඳහා තනි පුරනය භාවිතා කිරීමෙන් මෙම විද්යුත් තැපැල් ලිපිනය එක් කිරීම තහවුරු කරන්න.",
"Confirm": "තහවුරු කරන්න",
"Add Email Address": "විද්යුත් තැපැල් ලිපිනය එක් කරන්න",
- "Sign In": "පිවිසෙන්න"
+ "Sign In": "පිවිසෙන්න",
+ "Dismiss": "ඉවතලන්න",
+ "Explore rooms": "කාමර බලන්න",
+ "Create Account": "ගිණුමක් සාදන්න"
}
diff --git a/src/i18n/strings/sq.json b/src/i18n/strings/sq.json
index b2101151e1..e6f27a955d 100644
--- a/src/i18n/strings/sq.json
+++ b/src/i18n/strings/sq.json
@@ -1520,7 +1520,7 @@
"Please fill why you're reporting.": "Ju lutemi, plotësoni arsyen pse po raportoni.",
"Report Content to Your Homeserver Administrator": "Raportoni Lëndë te Përgjegjësi i Shërbyesit Tuaj Home",
"Reporting this message will send its unique 'event ID' to the administrator of your homeserver. If messages in this room are encrypted, your homeserver administrator will not be able to read the message text or view any files or images.": "Raportimi i këtij mesazhi do të shkaktojë dërgimin e 'ID-së së aktit' unike te përgjegjësi i shërbyesit tuaj Home. Nëse mesazhet në këtë dhomë fshehtëzohen, përgjegjësi i shërbyesit tuaj Home s’do të jetë në gjendje të lexojë tekstin e mesazhit apo të shohë çfarëdo kartelë apo figurë.",
- "Send report": "Dërgoje raportin",
+ "Send report": "Dërgoje njoftimin",
"To continue you need to accept the terms of this service.": "Që të vazhdohet, lypset të pranoni kushtet e këtij shërbimi.",
"Document": "Dokument",
"Report Content": "Raportoni Lëndë",
@@ -3386,5 +3386,87 @@
"If you have permissions, open the menu on any message and select Pin to stick them here.": "Nëse keni leje, hapni menunë për çfarëdo mesazhi dhe përzgjidhni Fiksoje, për ta ngjitur këtu.",
"Nothing pinned, yet": "Ende pa fiksuar gjë",
"End-to-end encryption isn't enabled": "Fshehtëzimi skaj-më-skaj s’është i aktivizuar",
- "Your private messages are normally encrypted, but this room isn't. Usually this is due to an unsupported device or method being used, like email invites. Enable encryption in settings.": "Mesazhet tuaja private normalisht fshehtëzohen, por kjo dhomë nuk fshehtëzohet. Zakonisht kjo vjen si pasojë e përdorimit të një pajisjeje apo metode të pambuluar, bie fjala, ftesa me email. Aktivizoni fshehtëzimin që nga rregullimet."
+ "Your private messages are normally encrypted, but this room isn't. Usually this is due to an unsupported device or method being used, like email invites. Enable encryption in settings.": "Mesazhet tuaja private normalisht fshehtëzohen, por kjo dhomë nuk fshehtëzohet. Zakonisht kjo vjen si pasojë e përdorimit të një pajisjeje apo metode të pambuluar, bie fjala, ftesa me email. Aktivizoni fshehtëzimin që nga rregullimet.",
+ "Sound on": "Me zë",
+ "[number]": "[numër]",
+ "To view %(spaceName)s, you need an invite": "Që të shihni %(spaceName)s, ju duhet një ftesë",
+ "You can click on an avatar in the filter panel at any time to see only the rooms and people associated with that community.": "Për të parë vetëm dhomat dhe personat e përshoqëruar asaj bashkësie, mund të klikoni në çfarëdo kohe mbi një avatar te paneli i filtrimeve.",
+ "Move down": "Zbrite",
+ "Move up": "Ngjite",
+ "Report": "Raportoje",
+ "Collapse reply thread": "Tkurre rrjedhën e përgjigjeve",
+ "Show preview": "Shfaq paraparje",
+ "View source": "Shihni burimin",
+ "Settings - %(spaceName)s": "Rregullime - %(spaceName)s",
+ "Report the entire room": "Raporto krejt dhomën",
+ "Spam or propaganda": "Mesazh i padëshiruar ose propagandë",
+ "Illegal Content": "Lëndë e Paligjshme",
+ "Toxic Behaviour": "Sjellje Toksike",
+ "Disagree": "S’pajtohem",
+ "Please pick a nature and describe what makes this message abusive.": "Ju lutemi, zgjidhni një karakterizim dhe përshkruani se ç’e bën këtë mesazh abuziv.",
+ "Any other reason. Please describe the problem.\nThis will be reported to the room moderators.": "Çfarëdo arsye tjetër. Ju lutemi, përshkruani problemin.\nKjo do t’u raportohet moderatorëve të dhomës.",
+ "This room is dedicated to illegal or toxic content or the moderators fail to moderate illegal or toxic content.\n This will be reported to the administrators of %(homeserver)s.": "Kjo dhomë merret me lëndë të paligjshme ose toksike, ose moderatorët nuk moderojnë lëndë të paligjshme ose toksike.\nKjo do t’u njoftohet përgjegjësve të %(homeserver)s.",
+ "This room is dedicated to illegal or toxic content or the moderators fail to moderate illegal or toxic content.\nThis will be reported to the administrators of %(homeserver)s. The administrators will NOT be able to read the encrypted content of this room.": "Kjo dhomë merret me lëndë të paligjshme ose toksike, ose moderatorët nuk moderojnë lëndë të paligjshme ose toksike.\nKjo do t’u njoftohet përgjegjësve të %(homeserver)s. Përgjegjësit NUK do të jenë në gjendje të lexojnë lëndë të fshehtëzuar të kësaj dhome.",
+ "This user is spamming the room with ads, links to ads or to propaganda.\nThis will be reported to the room moderators.": "Ky përdorues dërgon në dhomë reklama të padëshiruara, lidhje për te reklama të tilla ose te propagandë e padëshiruar.\nKjo do t’u njoftohet përgjegjësve të dhomës.",
+ "This user is displaying illegal behaviour, for instance by doxing people or threatening violence.\nThis will be reported to the room moderators who may escalate this to legal authorities.": "Ky përdorues shfaq sjellje të paligjshme, bie fjala, duke zbuluar identitet personash ose duke kërcënuar me dhunë.\nKjo do t’u njoftohet përgjegjësve të dhomës, të cilët mund ta përshkallëzojnë punën drejt autoriteteve ligjore.",
+ "This user is displaying toxic behaviour, for instance by insulting other users or sharing adult-only content in a family-friendly room or otherwise violating the rules of this room.\nThis will be reported to the room moderators.": "Ky përdorues shfaq sjellje të paligjshme, bie fjala, duke fyer përdorues të tjerë ose duke dhënë lëndë vetëm për të rritur në një dhomë të menduar për familje, ose duke shkelur në mënyra të tjera rregullat e kësaj dhome.\nKjo do t’u njoftohet përgjegjësve të dhomës.",
+ "What this user is writing is wrong.\nThis will be reported to the room moderators.": "Ajo ç’shkruan ky përdorues është gabim.\nKjo do t’u njoftohet përgjegjësve të dhomës.",
+ "Please provide an address": "Ju lutemi, jepni një adresë",
+ "%(oneUser)schanged the server ACLs %(count)s times|one": "%(oneUser)sndryshoi ACL-ra shërbyesi",
+ "%(oneUser)schanged the server ACLs %(count)s times|other": "%(oneUser)sndryshoi ACL-ra shërbyesi %(count)s herë",
+ "%(severalUsers)schanged the server ACLs %(count)s times|one": "%(severalUsers)sndryshuan ACL-ra shërbyesi",
+ "%(severalUsers)schanged the server ACLs %(count)s times|other": "%(severalUsers)sndryshuan ACL-ra shërbyesi %(count)s herë",
+ "Message search initialisation failed, check your settings for more information": "Dështoi gatitja e kërkimit në mesazhe, për më tepër hollësi, shihni rregullimet tuaja",
+ "Set addresses for this space so users can find this space through your homeserver (%(localDomain)s)": "Caktoni adresa për këtë hapësirë, që kështu përdoruesit të gjejnë këtë dhomë përmes shërbyesit tuaj Home (%(localDomain)s)",
+ "To publish an address, it needs to be set as a local address first.": "Që të bëni publike një adresë, lypset të ujdiset së pari si një adresë vendore.",
+ "Published addresses can be used by anyone on any server to join your room.": "Adresat e publikuara mund të përdoren nga cilido, në cilindo shërbyes, për të hyrë në dhomën tuaj.",
+ "Published addresses can be used by anyone on any server to join your space.": "Adresat e publikuara mund të përdoren nga cilido, në cilindo shërbyes, për të hyrë në hapësirën tuaj.",
+ "This space has no local addresses": "Kjo hapësirë s’ka adresa vendore",
+ "Space information": "Hollësi hapësire",
+ "Collapse": "Tkurre",
+ "Expand": "Zgjeroje",
+ "Recommended for public spaces.": "E rekomanduar për hapësira publike.",
+ "Allow people to preview your space before they join.": "Lejojini personat të parashohin hapësirën tuaj para se të hyjnë në të.",
+ "Preview Space": "Parashiheni Hapësirën",
+ "only invited people can view and join": "vetëm personat e ftuar mund ta shohin dhe hyjnë në të",
+ "anyone with the link can view and join": "kushdo me lidhjen mund të shohë dhomën dhe të hyjë në të",
+ "Decide who can view and join %(spaceName)s.": "Vendosni se cilët mund të shohin dhe marrin pjesë te %(spaceName)s.",
+ "Visibility": "Dukshmëri",
+ "This may be useful for public spaces.": "Kjo mund të jetë e dobishme për hapësira publike.",
+ "Guests can join a space without having an account.": "Mysafirët mund të hyjnë në një hapësirë pa pasur llogari.",
+ "Enable guest access": "Lejo hyrje si vizitor",
+ "Failed to update the history visibility of this space": "S’arrihet të përditësohet dukshmëria e historikut të kësaj hapësire",
+ "Failed to update the guest access of this space": "S’arrihet të përditësohet hyrja e mysafirëve të kësaj hapësire",
+ "Failed to update the visibility of this space": "S’arrihet të përditësohet dukshmëria e kësaj hapësire",
+ "Address": "Adresë",
+ "e.g. my-space": "p.sh., hapësira-ime",
+ "Silence call": "Heshtoje thirrjen",
+ "Show notification badges for People in Spaces": "Shfaq stema njoftimesh për Persona në Hapësira",
+ "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.": "Në u çaktivizoftë, prapë mundeni të shtoni krejt Mesazhet e Drejtpërdrejtë te Hapësira Personale. Në u aktivizoftë, do të shihni automatikisht cilindo që është anëtar i Hapësirës.",
+ "Show people in spaces": "Shfaq persona në hapësira",
+ "Show all rooms in Home": "Shfaq krejt dhomat te Home",
+ "Report to moderators prototype. In rooms that support moderation, the `report` button will let you report abuse to room moderators": "Prototip “Njoftojuani moderatorëve”. Në dhoma që mbulojnë moderim, butoni `raportojeni` do t’ju lejojë t’u njoftoni abuzim moderatorëve të dhomës",
+ "%(senderName)s changed the pinned messages for the room.": "%(senderName)s ndryshoi mesazhin e fiksuar për këtë dhomë.",
+ "%(senderName)s kicked %(targetName)s": "%(senderName)s përzuri %(targetName)s.",
+ "%(senderName)s kicked %(targetName)s: %(reason)s": "%(senderName)s përzuri %(targetName)s: %(reason)s",
+ "%(senderName)s withdrew %(targetName)s's invitation": "%(senderName)s tërhoqi mbrapsht ftesën për %(targetName)s",
+ "%(senderName)s withdrew %(targetName)s's invitation: %(reason)s": "%(senderName)s tërhoqi mbrapsht ftesën për %(targetName)s: %(reason)s",
+ "%(senderName)s unbanned %(targetName)s": "%(senderName)s hoqi dëbimin për %(targetName)s",
+ "%(targetName)s left the room": "%(targetName)s doli nga dhoma",
+ "%(targetName)s left the room: %(reason)s": "%(targetName)s doli nga dhoma: %(reason)s",
+ "%(targetName)s rejected the invitation": "%(targetName)s hodhi tej ftesën",
+ "%(targetName)s joined the room": "%(targetName)s hyri në dhomë",
+ "%(senderName)s made no change": "%(senderName)s s’bëri ndryshime",
+ "%(senderName)s set a profile picture": "%(senderName)s caktoi një foto profili",
+ "%(senderName)s changed their profile picture": "%(senderName)s ndryshoi foton e vet të profilit",
+ "%(senderName)s removed their profile picture": "%(senderName)s hoqi foton e vet të profilit",
+ "%(senderName)s removed their display name (%(oldDisplayName)s)": "%(senderName)s hoqi emrin e vet në ekran (%(oldDisplayName)s).",
+ "%(senderName)s set their display name to %(displayName)s": "%(senderName)s caktoi për veten emër ekrani %(displayName)s",
+ "%(oldDisplayName)s changed their display name to %(displayName)s": "%(oldDisplayName)s ndryshoi emrin e vet në ekran si %(displayName)s",
+ "%(senderName)s banned %(targetName)s": "%(senderName)s dëboi %(targetName)s.",
+ "%(senderName)s banned %(targetName)s: %(reason)s": "%(senderName)s dëboi %(targetName)s: %(reason)s",
+ "%(targetName)s accepted an invitation": "%(targetName)s pranoi një ftesë",
+ "%(targetName)s accepted the invitation for %(displayName)s": "%(targetName)s pranoi ftesën për %(displayName)s",
+ "Some invites couldn't be sent": "S’u dërguan dot disa nga ftesat",
+ "We sent the others, but the below people couldn't be invited to ": "I dërguam të tjerat, por personat më poshtë s’u ftuan dot te "
}
diff --git a/src/i18n/strings/sv.json b/src/i18n/strings/sv.json
index 6033b561bd..b36af42f5e 100644
--- a/src/i18n/strings/sv.json
+++ b/src/i18n/strings/sv.json
@@ -2117,7 +2117,7 @@
"Use this session to verify your new one, granting it access to encrypted messages:": "Använd den här sessionen för att verifiera en ny och ge den åtkomst till krypterade meddelanden:",
"If you didn’t sign in to this session, your account may be compromised.": "Om det inte var du som loggade in i den här sessionen så kan ditt konto vara äventyrat.",
"This wasn't me": "Det var inte jag",
- "Please fill why you're reporting.": "Vänligen fyll i varför du rapporterar.",
+ "Please fill why you're reporting.": "Vänligen fyll i varför du anmäler.",
"Report Content to Your Homeserver Administrator": "Rapportera innehåll till din hemserveradministratör",
"Reporting this message will send its unique 'event ID' to the administrator of your homeserver. If messages in this room are encrypted, your homeserver administrator will not be able to read the message text or view any files or images.": "Att rapportera det här meddelandet kommer att skicka dess unika 'händelse-ID' till administratören för din hemserver. Om meddelanden i det här rummet är krypterade kommer din hemserveradministratör inte att kunna läsa meddelandetexten eller se några filer eller bilder.",
"Send report": "Skicka rapport",
@@ -3329,5 +3329,32 @@
"If you have permissions, open the menu on any message and select Pin to stick them here.": "Om du har behörighet, öppna menyn på ett meddelande och välj Fäst för att fösta dem här.",
"Nothing pinned, yet": "Inget fäst än",
"End-to-end encryption isn't enabled": "Totalsträckskryptering är inte aktiverat",
- "Your private messages are normally encrypted, but this room isn't. Usually this is due to an unsupported device or method being used, like email invites. Enable encryption in settings.": "Dina privata meddelanden är normalt krypterade, men det här rummet är inte det. Oftast så beror detta på att en enhet eller metod som används ej stöds, som e-postinbjudningar. Aktivera kryptering i inställningarna."
+ "Your private messages are normally encrypted, but this room isn't. Usually this is due to an unsupported device or method being used, like email invites. Enable encryption in settings.": "Dina privata meddelanden är normalt krypterade, men det här rummet är inte det. Oftast så beror detta på att en enhet eller metod som används ej stöds, som e-postinbjudningar. Aktivera kryptering i inställningarna.",
+ "%(senderName)s changed the pinned messages for the room.": "%(senderName)s ändrade fästa meddelanden för rummet.",
+ "%(senderName)s kicked %(targetName)s": "%(senderName)s kickade %(targetName)s",
+ "%(senderName)s kicked %(targetName)s: %(reason)s": "%(senderName)s kickade %(targetName)s: %(reason)s",
+ "%(senderName)s withdrew %(targetName)s's invitation": "%(senderName)s drog tillbaka inbjudan för %(targetName)s",
+ "%(senderName)s withdrew %(targetName)s's invitation: %(reason)s": "%(senderName)s drog tillbaka inbjudan för %(targetName)s: %(reason)s",
+ "%(senderName)s unbanned %(targetName)s": "%(senderName)s avbannade %(targetName)s",
+ "%(targetName)s left the room": "%(targetName)s lämnade rummet",
+ "%(targetName)s left the room: %(reason)s": "%(targetName)s lämnade rummet: %(reason)s",
+ "%(targetName)s rejected the invitation": "%(targetName)s avböjde inbjudan",
+ "%(targetName)s joined the room": "%(targetName)s gick med i rummet",
+ "%(senderName)s made no change": "%(senderName)s gjorde ingen ändring",
+ "%(senderName)s set a profile picture": "%(senderName)s satte en profilbild",
+ "%(senderName)s changed their profile picture": "%(senderName)s bytte sin profilbild",
+ "%(senderName)s removed their profile picture": "%(senderName)s tog bort sin profilbild",
+ "%(senderName)s removed their display name (%(oldDisplayName)s)": "%(senderName)s tog bort sitt visningsnamn %(oldDisplayName)s",
+ "%(senderName)s set their display name to %(displayName)s": "%(senderName)s satte sitt visningsnamn till %(displayName)s",
+ "%(oldDisplayName)s changed their display name to %(displayName)s": "%(oldDisplayName)s ändrade sitt visningsnamn till %(displayName)s",
+ "%(senderName)s banned %(targetName)s": "%(senderName)s bannade %(targetName)s",
+ "%(senderName)s banned %(targetName)s: %(reason)s": "%(senderName)s bannade %(targetName)s: %(reason)s",
+ "%(senderName)s invited %(targetName)s": "%(senderName)s bjöd in %(targetName)s",
+ "%(targetName)s accepted an invitation": "%(targetName)s accepterade inbjudan",
+ "%(targetName)s accepted the invitation for %(displayName)s": "%(targetName)s accepterade inbjudan för %(displayName)s",
+ "Some invites couldn't be sent": "Vissa inbjudningar kunde inte skickas",
+ "We sent the others, but the below people couldn't be invited to ": "Vi skickade de andra, men personerna nedan kunde inte bjudas in till ",
+ "What this user is writing is wrong.\nThis will be reported to the room moderators.": "Vad användaren skriver är fel.\nDet här kommer att anmälas till rumsmoderatorerna.",
+ "Report to moderators prototype. In rooms that support moderation, the `report` button will let you report abuse to room moderators": "Prototyp av anmälan till moderatorer. I rum som söder moderering så kommer `anmäl`-knappen att låta dig anmäla olämpligt beteende till rummets moderatorer",
+ "Report": "Rapportera"
}
diff --git a/src/i18n/strings/tr.json b/src/i18n/strings/tr.json
index c5316ee2df..0458d3226a 100644
--- a/src/i18n/strings/tr.json
+++ b/src/i18n/strings/tr.json
@@ -2517,5 +2517,32 @@
"Remain on your screen while running": "Uygulama çalışırken lütfen başka uygulamaya geçmeyin",
"Your homeserver was unreachable and was not able to log you in. Please try again. If this continues, please contact your homeserver administrator.": "Ana sunucunuza erişilemedi ve oturum açmanıza izin verilmedi. Lütfen yeniden deneyin. Eğer hata devam ederse ana sunucunuzun yöneticisine bildirin.",
"Your homeserver rejected your log in attempt. This could be due to things just taking too long. Please try again. If this continues, please contact your homeserver administrator.": "Ana sunucunuz oturum açma isteğinizi reddetti. Bunun nedeni bağlantı yavaşlığı olabilir. Lütfen yeniden deneyin. Eğer hata devam ederse ana sunucunuzun yöneticisine bildirin.",
- "Try again": "Yeniden deneyin"
+ "Try again": "Yeniden deneyin",
+ "%(senderName)s changed the pinned messages for the room.": "%(senderName)s odadaki ileti sabitlemelerini değiştirdi.",
+ "%(senderName)s kicked %(targetName)s": "%(senderName)s, %(targetName)s kullanıcısını attı",
+ "%(senderName)s kicked %(targetName)s: %(reason)s": "%(senderName)s, %(targetName)s kullanıcısını attı: %(reason)s",
+ "%(senderName)s withdrew %(targetName)s's invitation": "%(senderName)s, %(targetName)s kullanıcısının davetini geri çekti",
+ "%(senderName)s withdrew %(targetName)s's invitation: %(reason)s": "%(senderName)s,%(targetName)s kullanıcısının davetini geri çekti: %(reason)s",
+ "%(targetName)s left the room": "%(targetName)s odadan çıktı",
+ "%(targetName)s left the room: %(reason)s": "%(targetName)s odadan çıktı: %(reason)s",
+ "%(targetName)s rejected the invitation": "%(targetName)s daveti geri çevirdi",
+ "%(targetName)s joined the room": "%(targetName)s odaya katıldı",
+ "%(senderName)s made no change": " ",
+ "%(senderName)s set a profile picture": "%(senderName)s profil resmi belirledi",
+ "%(senderName)s changed their profile picture": "%(senderName)s profil resmini değiştirdi",
+ "%(senderName)s removed their profile picture": "%(senderName)s profil resmini kaldırdı",
+ "%(senderName)s removed their display name (%(oldDisplayName)s)": "%(senderName)s, %(oldDisplayName)s görünür adını kaldırdı",
+ "%(senderName)s set their display name to %(displayName)s": "%(senderName)s görünür adını %(displayName)s yaptı",
+ "%(oldDisplayName)s changed their display name to %(displayName)s": "%(oldDisplayName)s görünür adını %(displayName)s yaptı",
+ "%(senderName)s invited %(targetName)s": "%(targetName)s kullanıcılarını %(senderName)s davet etti",
+ "%(senderName)s unbanned %(targetName)s": "%(targetName) tarafından %(senderName)s yasakları kaldırıldı",
+ "%(senderName)s banned %(targetName)s": "%(senderName)s %(targetName)s kullanıcısını yasakladı: %(reason)s",
+ "%(senderName)s banned %(targetName)s: %(reason)s": "%(senderName)s %(targetName) kullanıcısını yasakladı: %(reason)s",
+ "Some invites couldn't be sent": "Bazı davetler gönderilemiyor",
+ "We sent the others, but the below people couldn't be invited to ": "Başkalarına davetler iletilmekle beraber, aşağıdakiler odasına davet edilemedi",
+ "We asked the browser to remember which homeserver you use to let you sign in, but unfortunately your browser has forgotten it. Go to the sign in page and try again.": "Tarayıcınıza bağlandığınız ana sunucuyu anımsamasını söyledik ama ne yazık ki tarayıcınız bunu unutmuş. Lütfen giriş sayfasına gidip tekrar deneyin.",
+ "We couldn't log you in": "Sizin girişinizi yapamadık",
+ "You're already in a call with this person.": "Bu kişi ile halihazırda çağrıdasınız.",
+ "The user you called is busy.": "Aradığınız kullanıcı meşgul.",
+ "User Busy": "Kullanıcı Meşgul"
}
diff --git a/src/i18n/strings/vi.json b/src/i18n/strings/vi.json
index eebbaef3d0..aec8580ef1 100644
--- a/src/i18n/strings/vi.json
+++ b/src/i18n/strings/vi.json
@@ -1,7 +1,7 @@
{
"This email address is already in use": "Email này hiện đã được sử dụng",
"This phone number is already in use": "Số điện thoại này hiện đã được sử dụng",
- "Failed to verify email address: make sure you clicked the link in the email": "Xác thực email thất bại: hãy đảm bảo bạn nhấp đúng đường dẫn đã gửi vào email",
+ "Failed to verify email address: make sure you clicked the link in the email": "Xác thực email thất bại: Hãy đảm bảo bạn nhấp đúng đường dẫn đã gửi vào email",
"The platform you're on": "Nền tảng bạn đang tham gia",
"The version of %(brand)s": "Phiên bản của %(brand)s",
"Your language of choice": "Ngôn ngữ bạn chọn",
@@ -9,9 +9,9 @@
"Whether or not you're logged in (we don't record your username)": "Dù bạn có đăng nhập hay không (chúng tôi không lưu tên đăng nhập của bạn)",
"Whether or not you're using the Richtext mode of the Rich Text Editor": "Dù bạn có dùng chức năng Richtext của Rich Text Editor hay không",
"Whether or not you're using the 'breadcrumbs' feature (avatars above the room list)": "Dù bạn có dùng chức năng breadcrumbs hay không (avatar trên danh sách phòng)",
- "e.g. %(exampleValue)s": "ví dụ %(exampleValue)s",
+ "e.g. %(exampleValue)s": "Ví dụ %(exampleValue)s",
"Every page you use in the app": "Mọi trang bạn dùng trong app",
- "e.g. ": "ví dụ ",
+ "e.g. ": "Ví dụ ",
"Your device resolution": "Độ phân giải thiết bị",
"Analytics": "Phân tích",
"The information being sent to us to help make %(brand)s better includes:": "Thông tin gửi lên máy chủ giúp cải thiện %(brand)s bao gồm:",
@@ -84,7 +84,7 @@
"Dismiss": "Bỏ qua",
"%(brand)s does not have permission to send you notifications - please check your browser settings": "%(brand)s không có đủ quyền để gửi notification - vui lòng kiểm tra thiết lập trình duyệt",
"%(brand)s was not given permission to send notifications - please try again": "%(brand)s không được cấp quyền để gửi notification - vui lòng thử lại",
- "Unable to enable Notifications": "Không thể bật Notification",
+ "Unable to enable Notifications": "Không thể bật thông báo",
"This email address was not found": "Địa chỉ email này không tồn tại trong hệ thống",
"Your email address does not appear to be associated with a Matrix ID on this Homeserver.": "Email của bạn không được liên kết với một mã Matrix ID nào trên Homeserver này.",
"Register": "Đăng ký",
@@ -206,7 +206,7 @@
"%(names)s and %(count)s others are typing …|one": "%(names)s và một người khác đang gõ …",
"%(names)s and %(lastPerson)s are typing …": "%(names)s và %(lastPerson)s đang gõ …",
"Cannot reach homeserver": "Không thể kết nối tới máy chủ",
- "Ensure you have a stable internet connection, or get in touch with the server admin": "Đảm bảo bạn có kết nối Internet ổn địn, hoặc liên hệ Admin để được hỗ trợ",
+ "Ensure you have a stable internet connection, or get in touch with the server admin": "Đảm bảo bạn có kết nối Internet ổn định, hoặc liên hệ quản trị viên để được hỗ trợ",
"Your %(brand)s is misconfigured": "Hệ thống %(brand)s của bạn bị thiết lập sai",
"Cannot reach identity server": "Không thể kết nối server định danh",
"You can register, but some features will be unavailable until the identity server is back online. If you keep seeing this warning, check your configuration or contact a server admin.": "Bạn có thể đăng ký, nhưng một vài chức năng sẽ không sử đụng dược cho đến khi server định danh hoạt động trở lại. Nếu bạn thấy thông báo này, hãy kiểm tra thiết lập hoặc liên hệ Admin.",
@@ -295,5 +295,52 @@
"Enable widget screenshots on supported widgets": "Bật widget chụp màn hình cho các widget có hỗ trợ",
"Sign In": "Đăng nhập",
"Explore rooms": "Khám phá phòng chat",
- "Create Account": "Tạo tài khoản"
+ "Create Account": "Tạo tài khoản",
+ "Theme": "Giao diện",
+ "Your password": "Mật khẩu của bạn",
+ "Success": "Thành công",
+ "Ignore": "Không chấp nhận",
+ "Bug reporting": "Báo cáo lỗi",
+ "Vietnam": "Việt Nam",
+ "Video Call": "Gọi Video",
+ "Voice call": "Gọi thoại",
+ "%(senderName)s started a call": "%(senderName)s đã bắt đầu một cuộc gọi",
+ "You started a call": "Bạn đã bắt đầu một cuộc gọi",
+ "Call ended": "Cuộc gọi kết thúc",
+ "%(senderName)s ended the call": "%(senderName)s đã kết thúc cuộc gọi",
+ "You ended the call": "Bạn đã kết thúc cuộc gọi",
+ "Call in progress": "Cuộc gọi đang diễn ra",
+ "%(senderName)s joined the call": "%(senderName)s đã tham gia cuộc gọi",
+ "You joined the call": "Bạn đã tham gia cuộc gọi",
+ "Feedback": "Phản hồi",
+ "Invites": "Mời",
+ "Video call": "Gọi video",
+ "This account has been deactivated.": "Tài khoản này đã bị vô hiệu hoá.",
+ "Start": "Bắt đầu",
+ "or": "hoặc",
+ "Secure messages with this user are end-to-end encrypted and not able to be read by third parties.": "Các tin nhắn với người dùng này được mã hóa đầu cuối và các bên thứ ba không thể đọc được.",
+ "You've successfully verified this user.": "Bạn đã xác minh thành công người dùng này.",
+ "Verified!": "Đã xác minh!",
+ "Play": "Phát",
+ "Pause": "Tạm ngừng",
+ "Accept": "Chấp nhận",
+ "Decline": "Từ chối",
+ "Are you sure?": "Bạn có chắc không?",
+ "Confirm Removal": "Xác Nhận Loại Bỏ",
+ "Removing…": "Đang xóa…",
+ "Removing...": "Đang xóa...",
+ "Try scrolling up in the timeline to see if there are any earlier ones.": "Thử cuộn lên trong dòng thời gian để xem có cái nào trước đó không.",
+ "No recent messages by %(user)s found": "Không tìm thấy tin nhắn gần đây của %(user)s",
+ "Failed to ban user": "Đã có lỗi khi chặn người dùng",
+ "Are you sure you want to leave the room '%(roomName)s'?": "Bạn có chắc chắn rằng bạn muốn rời '%(roomName)s' chứ?",
+ "Use an email address to recover your account": "Sử dụng địa chỉ email của bạn để khôi phục tài khoản của bạn",
+ "Sign in": "Đăng nhập",
+ "Confirm adding phone number": "Xác nhận việc thêm số điện thoại",
+ "Confirm adding this phone number by using Single Sign On to prove your identity.": "Xác nhận việc thêm số điện thoại này bằng cách sử dụng Single Sign On để chứng minh danh tính của bạn",
+ "Add Email Address": "Thêm Địa Chỉ Email",
+ "Click the button below to confirm adding this email address.": "Nhấn vào nút dưới đây để xác nhận việc thêm địa chỉ email này.",
+ "Confirm adding email": "Xác nhận việc thêm email",
+ "Add Phone Number": "Thêm Số Điện Thoại",
+ "Click the button below to confirm adding this phone number.": "Nhấn vào nút dưới đây để xác nhận việc thêm số điện thoại này.",
+ "Confirm": "Xác nhận"
}
diff --git a/src/i18n/strings/zh_Hans.json b/src/i18n/strings/zh_Hans.json
index 7aa0d75539..88ebb8f4cf 100644
--- a/src/i18n/strings/zh_Hans.json
+++ b/src/i18n/strings/zh_Hans.json
@@ -3298,5 +3298,90 @@
"If you have permissions, open the menu on any message and select Pin to stick them here.": "如果你拥有权限,请打开任何消息的菜单并选择置顶将它们粘贴至此。",
"Nothing pinned, yet": "没有置顶",
"End-to-end encryption isn't enabled": "未启用端对端加密",
- "Your private messages are normally encrypted, but this room isn't. Usually this is due to an unsupported device or method being used, like email invites. Enable encryption in settings.": "你的私人信息通常是被加密的,但此聊天室并未加密。一般而言,这可能是因为使用了不受支持的设备或方法,如电子邮件邀请。在设置中启用加密。"
+ "Your private messages are normally encrypted, but this room isn't. Usually this is due to an unsupported device or method being used, like email invites. Enable encryption in settings.": "你的私人信息通常是被加密的,但此聊天室并未加密。一般而言,这可能是因为使用了不受支持的设备或方法,如电子邮件邀请。在设置中启用加密。",
+ "[number]": "[number]",
+ "To view %(spaceName)s, you need an invite": "你需要得到邀请方可查看 %(spaceName)s",
+ "You can click on an avatar in the filter panel at any time to see only the rooms and people associated with that community.": "你可以随时在过滤器面板中点击头像来查看与该社群相关的聊天室和人员。",
+ "Move down": "向下移动",
+ "Move up": "向上移动",
+ "Report": "报告",
+ "Collapse reply thread": "折叠回复链",
+ "Show preview": "显示预览",
+ "View source": "查看来源",
+ "Forward": "转发",
+ "Settings - %(spaceName)s": "设置 - %(spaceName)s",
+ "Report the entire room": "报告整个聊天室",
+ "Spam or propaganda": "垃圾信息或宣传",
+ "Illegal Content": "违法内容",
+ "Toxic Behaviour": "不良行为",
+ "This room is dedicated to illegal or toxic content or the moderators fail to moderate illegal or toxic content.\nThis will be reported to the administrators of %(homeserver)s. The administrators will NOT be able to read the encrypted content of this room.": "此聊天室致力于违法或不良行为,或协管员无法节制违法或不良行为。\n这将报告给 %(homeserver)s 的管理员。管理员无法阅读此聊天室的加密内容。",
+ "This room is dedicated to illegal or toxic content or the moderators fail to moderate illegal or toxic content.\n This will be reported to the administrators of %(homeserver)s.": "此聊天室致力于违法或不良行为,或协管员无法节制违法或不良行为。\n这将报告给 %(homeserver)s 的管理员。",
+ "Disagree": "不同意",
+ "Please pick a nature and describe what makes this message abusive.": "请选择性质并描述为什么此消息是滥用。",
+ "Any other reason. Please describe the problem.\nThis will be reported to the room moderators.": "任何其他原因。请描述问题。\n这将报告给聊天室协管员。",
+ "This user is spamming the room with ads, links to ads or to propaganda.\nThis will be reported to the room moderators.": "此用户正在聊天室中滥发广告、广告链接或宣传。\n这将报告给聊天室协管员。",
+ "This user is displaying illegal behaviour, for instance by doxing people or threatening violence.\nThis will be reported to the room moderators who may escalate this to legal authorities.": "此用户正在做出违法行为,如对他人施暴,或威胁使用暴力。\n这将报告给聊天室协管员,他们可能会将其报告给执法部门。",
+ "This user is displaying toxic behaviour, for instance by insulting other users or sharing adult-only content in a family-friendly room or otherwise violating the rules of this room.\nThis will be reported to the room moderators.": "此用户正在做出不良行为,如在侮辱其他用户,或在全年龄向的聊天室中分享成人内容,亦或是其他违反聊天室规则的行为。\n这将报告给聊天室协管员。",
+ "What this user is writing is wrong.\nThis will be reported to the room moderators.": "此用户所写的是错误内容。\n这将会报告给聊天室协管员。",
+ "Please provide an address": "请提供地址",
+ "%(oneUser)schanged the server ACLs %(count)s times|one": "%(oneUser)s 已更改服务器访问控制列表",
+ "%(oneUser)schanged the server ACLs %(count)s times|other": "%(oneUser)s 已更改服务器访问控制列表 %(count)s 次",
+ "%(severalUsers)schanged the server ACLs %(count)s times|one": "%(severalUsers)s 已更改服务器访问控制列表",
+ "%(severalUsers)schanged the server ACLs %(count)s times|other": "%(severalUsers)s 已更改服务器的访问控制列表 %(count)s 此",
+ "Message search initialisation failed, check your settings for more information": "消息搜索初始化失败,请检查你的设置以获取更多信息",
+ "Set addresses for this space so users can find this space through your homeserver (%(localDomain)s)": "设置此空间的地址,这样用户就能通过你的主服务器找到此空间(%(localDomain)s)",
+ "To publish an address, it needs to be set as a local address first.": "要公布地址,首先需要将其设为本地地址。",
+ "Published addresses can be used by anyone on any server to join your room.": "任何服务器上的人均可通过公布的地址加入你的聊天室。",
+ "Published addresses can be used by anyone on any server to join your space.": "任何服务器上的人均可通过公布的地址加入你的空间。",
+ "This space has no local addresses": "此空间没有本地地址",
+ "Space information": "空间信息",
+ "Collapse": "折叠",
+ "Expand": "展开",
+ "Recommended for public spaces.": "建议用于公开空间。",
+ "Allow people to preview your space before they join.": "允许在加入前预览你的空间。",
+ "Preview Space": "预览空间",
+ "only invited people can view and join": "只有被邀请才能查看和加入",
+ "Invite only": "仅邀请",
+ "anyone with the link can view and join": "任何拥有此链接的人均可查看和加入",
+ "Decide who can view and join %(spaceName)s.": "这决定了谁可以查看和加入 %(spaceName)s。",
+ "Visibility": "可见性",
+ "This may be useful for public spaces.": "这可能对公开空间有所帮助。",
+ "Guests can join a space without having an account.": "游客无需账号即可加入空间。",
+ "Enable guest access": "启用游客访问权限",
+ "Failed to update the history visibility of this space": "更新此空间的历史记录可见性失败",
+ "Failed to update the guest access of this space": "更新此空间的游客访问权限失败",
+ "Failed to update the visibility of this space": "更新此空间的可见性失败",
+ "Address": "地址",
+ "e.g. my-space": "例如:my-space",
+ "Silence call": "通话静音",
+ "Sound on": "开启声音",
+ "Show notification badges for 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.": "如果禁用,你仍可以将私聊添加至个人空间。若启用,你将自动看见空间中的每位成员。",
+ "Show people in spaces": "显示空间中的人",
+ "Show all rooms in Home": "在主页显示所有聊天室",
+ "Report to moderators prototype. In rooms that support moderation, the `report` button will let you report abuse to room moderators": "向协管员报告的范例。在管理支持的聊天室中,你可以通过「报告」按钮向聊天室协管员报告滥用行为",
+ "%(senderName)s changed the pinned messages for the room.": "%(senderName)s 已更改此聊天室的固定消息。",
+ "%(senderName)s kicked %(targetName)s": "%(senderName)s 已移除 %(targetName)s",
+ "%(senderName)s kicked %(targetName)s: %(reason)s": "%(senderName)s 已移除 %(targetName)s:%(reason)s",
+ "%(senderName)s withdrew %(targetName)s's invitation": "%(senderName)s 已撤回向 %(targetName)s 的邀请",
+ "%(senderName)s withdrew %(targetName)s's invitation: %(reason)s": "%(senderName)s 已撤回向 %(targetName)s 的邀请:%(reason)s",
+ "%(senderName)s unbanned %(targetName)s": "%(senderName)s 已取消封禁 %(targetName)s",
+ "%(targetName)s left the room": "%(targetName)s 已离开聊天室",
+ "%(targetName)s left the room: %(reason)s": "%(targetName)s 已离开聊天室:%(reason)s",
+ "%(targetName)s rejected the invitation": "%(targetName)s 已拒绝邀请",
+ "%(targetName)s joined the room": "%(targetName)s 已加入聊天室",
+ "%(senderName)s made no change": "%(senderName)s 未发生更改",
+ "%(senderName)s set a profile picture": "%(senderName)s 已设置资料图片",
+ "%(senderName)s changed their profile picture": "%(senderName)s 已更改他们的资料图片",
+ "%(senderName)s removed their profile picture": "%(senderName)s 已移除他们的资料图片",
+ "%(senderName)s removed their display name (%(oldDisplayName)s)": "%(senderName)s 已将他们的昵称移除(%(oldDisplayName)s)",
+ "%(senderName)s set their display name to %(displayName)s": "%(senderName)s 已将他们的昵称设置为 %(displayName)s",
+ "%(oldDisplayName)s changed their display name to %(displayName)s": "%(oldDisplayName)s 已将他们的昵称更改为 %(displayName)s",
+ "%(senderName)s banned %(targetName)s": "%(senderName)s 已封禁 %(targetName)s",
+ "%(senderName)s banned %(targetName)s: %(reason)s": "%(senderName)s 已封禁 %(targetName)s: %(reason)s",
+ "%(senderName)s invited %(targetName)s": "%(senderName)s 已邀请 %(targetName)s",
+ "%(targetName)s accepted an invitation": "%(targetName)s 已接受邀请",
+ "%(targetName)s accepted the invitation for %(displayName)s": "%(targetName)s 已接受 %(displayName)s 的邀请",
+ "Some invites couldn't be sent": "部分邀请无法送达",
+ "We sent the others, but the below people couldn't be invited to ": "我们已向其他人发送邀请,除了以下无法邀请至 的人"
}
diff --git a/src/i18n/strings/zh_Hant.json b/src/i18n/strings/zh_Hant.json
index d9429fc1c3..03cebcb083 100644
--- a/src/i18n/strings/zh_Hant.json
+++ b/src/i18n/strings/zh_Hant.json
@@ -3401,5 +3401,88 @@
"If you have permissions, open the menu on any message and select Pin to stick them here.": "如果您有權限,請開啟任何訊息的選單,並選取釘選以將它們貼到這裡。",
"Nothing pinned, yet": "尚未釘選任何東西",
"End-to-end encryption isn't enabled": "端到端加密未啟用",
- "Your private messages are normally encrypted, but this room isn't. Usually this is due to an unsupported device or method being used, like email invites. Enable encryption in settings.": "您的私人訊息通常是被加密的,但此聊天室不是。一般來說,這可能是因為使用了不支援的裝置或方法,例如電子郵件邀請。在設定中啟用加密。"
+ "Your private messages are normally encrypted, but this room isn't. Usually this is due to an unsupported device or method being used, like email invites. Enable encryption in settings.": "您的私人訊息通常是被加密的,但此聊天室不是。一般來說,這可能是因為使用了不支援的裝置或方法,例如電子郵件邀請。在設定中啟用加密。",
+ "[number]": "[number]",
+ "To view %(spaceName)s, you need an invite": "要檢視 %(spaceName)s,您需要邀請",
+ "You can click on an avatar in the filter panel at any time to see only the rooms and people associated with that community.": "您可以隨時在過濾器面板中點擊大頭照來僅檢視與該社群相關的聊天室與夥伴。",
+ "Move down": "向下移動",
+ "Move up": "向上移動",
+ "Report": "回報",
+ "Collapse reply thread": "折疊回覆討論串",
+ "Show preview": "顯示預覽",
+ "View source": "檢視來源",
+ "Forward": "轉寄",
+ "Settings - %(spaceName)s": "設定 - %(spaceName)s",
+ "Report the entire room": "回報整個聊天室",
+ "Spam or propaganda": "垃圾郵件或宣傳",
+ "Illegal Content": "違法內容",
+ "Toxic Behaviour": "有問題的行為",
+ "Disagree": "不同意",
+ "Please pick a nature and describe what makes this message abusive.": "請挑選性質並描述此訊息為什麼是濫用。",
+ "Any other reason. Please describe the problem.\nThis will be reported to the room moderators.": "任何其他理由。請描述問題。\n將會回報給聊天室管理員。",
+ "This room is dedicated to illegal or toxic content or the moderators fail to moderate illegal or toxic content.\nThis will be reported to the administrators of %(homeserver)s. The administrators will NOT be able to read the encrypted content of this room.": "此聊天室有違法或有問題的內容,或是管理員無法審核違法或有問題的內容。\n將會回報給 %(homeserver)s 的管理員。管理員無法閱讀此聊天室的加密內容。",
+ "This room is dedicated to illegal or toxic content or the moderators fail to moderate illegal or toxic content.\n This will be reported to the administrators of %(homeserver)s.": "此聊天室有違法或有問題的內容,或是管理員無法審核違法或有問題的內容。\n 將會回報給 %(homeserver)s 的管理員。",
+ "This user is spamming the room with ads, links to ads or to propaganda.\nThis will be reported to the room moderators.": "該使用者正在向聊天室傳送廣告、廣告連結或宣傳。\n將會回報給聊天室管理員。",
+ "This user is displaying illegal behaviour, for instance by doxing people or threatening violence.\nThis will be reported to the room moderators who may escalate this to legal authorities.": "該使用者正顯示違法行為,例如對他人施暴,或威脅使用暴力。\n將會回報給聊天室管理員,他們可能會將其回報給執法單位。",
+ "This user is displaying toxic behaviour, for instance by insulting other users or sharing adult-only content in a family-friendly room or otherwise violating the rules of this room.\nThis will be reported to the room moderators.": "該使用者正顯示不良行為,例如侮辱其他使用者,或是在適合全年齡的聊天室中分享成人內容,又或是其他違反此聊天室規則的行為。\n將會回報給聊天室管理員。",
+ "What this user is writing is wrong.\nThis will be reported to the room moderators.": "該使用者所寫的內容是錯誤的。\n將會回報給聊天室管理員。",
+ "Please provide an address": "請提供地址",
+ "%(oneUser)schanged the server ACLs %(count)s times|one": "%(oneUser)s 變更了伺服器 ACL",
+ "%(oneUser)schanged the server ACLs %(count)s times|other": "%(oneUser)s 變更了伺服器 ACL %(count)s 次",
+ "%(severalUsers)schanged the server ACLs %(count)s times|one": "%(severalUsers)s 變更了伺服器 ACL",
+ "%(severalUsers)schanged the server ACLs %(count)s times|other": "%(severalUsers)s 變更了伺服器 ACL %(count)s 次",
+ "Message search initialisation failed, check your settings for more information": "訊息搜尋初始化失敗,請檢查您的設定以取得更多資訊",
+ "Set addresses for this space so users can find this space through your homeserver (%(localDomain)s)": "設定此空間的地址,這樣使用者就能透過您的家伺服器找到此空間(%(localDomain)s)",
+ "To publish an address, it needs to be set as a local address first.": "要發佈地址,其必須先設定為本機地址。",
+ "Published addresses can be used by anyone on any server to join your room.": "任何伺服器上的人都可以使用已發佈的地址加入您的聊天室。",
+ "Published addresses can be used by anyone on any server to join your space.": "任何伺服器上的人都可以使用已發佈的地址加入您的空間。",
+ "This space has no local addresses": "此空間沒有本機地址",
+ "Space information": "空間資訊",
+ "Collapse": "折疊",
+ "Expand": "展開",
+ "Recommended for public spaces.": "推薦用於公開空間。",
+ "Allow people to preview your space before they join.": "允許人們在加入前預覽您的空間。",
+ "Preview Space": "預覽空間",
+ "only invited people can view and join": "僅有受邀的人才能檢視與加入",
+ "anyone with the link can view and join": "任何知道連結的人都可以檢視並加入",
+ "Decide who can view and join %(spaceName)s.": "決定誰可以檢視並加入 %(spaceName)s。",
+ "Visibility": "能見度",
+ "This may be useful for public spaces.": "這可能對公開空間很有用。",
+ "Guests can join a space without having an account.": "訪客毋需帳號即可加入空間。",
+ "Enable guest access": "啟用訪客存取權",
+ "Failed to update the history visibility of this space": "未能更新此空間的歷史紀錄能見度",
+ "Failed to update the guest access of this space": "未能更新此空間的訪客存取權限",
+ "Failed to update the visibility of this space": "未能更新此空間的能見度",
+ "Address": "地址",
+ "e.g. my-space": "例如:my-space",
+ "Silence call": "通話靜音",
+ "Sound on": "開啟聲音",
+ "Show notification badges for 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.": "若停用,您仍然可以將直接訊息新增至個人空間中。若啟用,您將自動看到空間中的每個成員。",
+ "Show people in spaces": "顯示空間中的人",
+ "Show all rooms in Home": "在首頁顯示所有聊天室",
+ "Report to moderators prototype. In rooms that support moderation, the `report` button will let you report abuse to room moderators": "向管理員回報的範本。在支援管理的聊天室中,「回報」按鈕讓您可以回報濫用行為給聊天室管理員",
+ "%(senderName)s changed the pinned messages for the room.": "%(senderName)s 變更了聊天室的釘選訊息。",
+ "%(senderName)s kicked %(targetName)s": "%(senderName)s 踢掉了 %(targetName)s",
+ "%(senderName)s kicked %(targetName)s: %(reason)s": "%(senderName)s 踢掉了 %(targetName)s:%(reason)s",
+ "%(senderName)s withdrew %(targetName)s's invitation": "%(senderName)s 撤回了 %(targetName)s 的邀請",
+ "%(senderName)s withdrew %(targetName)s's invitation: %(reason)s": "%(senderName)s 撤回了 %(targetName)s 的邀請:%(reason)s",
+ "%(senderName)s unbanned %(targetName)s": "%(senderName)s 取消封鎖了 %(targetName)s",
+ "%(targetName)s left the room": "%(targetName)s 離開聊天室",
+ "%(targetName)s left the room: %(reason)s": "%(targetName)s 離開了聊天室:%(reason)s",
+ "%(targetName)s rejected the invitation": "%(targetName)s 回絕了邀請",
+ "%(targetName)s joined the room": "%(targetName)s 加入了聊天室",
+ "%(senderName)s made no change": "%(senderName)s 未變更",
+ "%(senderName)s set a profile picture": "%(senderName)s 設定了個人檔案照片",
+ "%(senderName)s changed their profile picture": "%(senderName)s 變更了他們的個人檔案照片",
+ "%(senderName)s removed their profile picture": "%(senderName)s 移除了他們的個人檔案照片",
+ "%(senderName)s removed their display name (%(oldDisplayName)s)": "%(senderName)s 移除了他們的顯示名稱(%(oldDisplayName)s)",
+ "%(senderName)s set their display name to %(displayName)s": "%(senderName)s 將他們的顯示名稱設定為 %(displayName)s",
+ "%(oldDisplayName)s changed their display name to %(displayName)s": "%(oldDisplayName)s 變更了他們的顯示名稱為 %(displayName)s",
+ "%(senderName)s banned %(targetName)s": "%(senderName)s 封鎖了 %(targetName)s",
+ "%(senderName)s banned %(targetName)s: %(reason)s": "%(senderName)s 封鎖了 %(targetName)s:%(reason)s",
+ "%(targetName)s accepted an invitation": "%(targetName)s 接受了邀請",
+ "%(targetName)s accepted the invitation for %(displayName)s": "%(targetName)s 已接受 %(displayName)s 的邀請",
+ "Some invites couldn't be sent": "部份邀請無法傳送",
+ "We sent the others, but the below people couldn't be invited to ": "我們已將邀請傳送給其他人,但以下的人無法邀請至 "
}
diff --git a/src/indexing/BaseEventIndexManager.ts b/src/indexing/BaseEventIndexManager.ts
index 4bae3e7c1d..64576e4412 100644
--- a/src/indexing/BaseEventIndexManager.ts
+++ b/src/indexing/BaseEventIndexManager.ts
@@ -14,47 +14,16 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
+import { IMatrixProfile, IEventWithRoomId as IMatrixEvent, IResultRoomEvents } from "matrix-js-sdk/src/@types/search";
+import { Direction } from "matrix-js-sdk/src";
+
// The following interfaces take their names and member names from seshat and the spec
/* eslint-disable camelcase */
-
-export interface IMatrixEvent {
- type: string;
- sender: string;
- content: {};
- event_id: string;
- origin_server_ts: number;
- unsigned?: {};
- roomId: string;
-}
-
-export interface IMatrixProfile {
- avatar_url: string;
- displayname: string;
-}
-
export interface ICrawlerCheckpoint {
roomId: string;
token: string;
fullCrawl?: boolean;
- direction: string;
-}
-
-export interface IResultContext {
- events_before: [IMatrixEvent];
- events_after: [IMatrixEvent];
- profile_info: Map;
-}
-
-export interface IResultsElement {
- rank: number;
- result: IMatrixEvent;
- context: IResultContext;
-}
-
-export interface ISearchResult {
- count: number;
- results: [IResultsElement];
- highlights: [string];
+ direction: Direction;
}
export interface ISearchArgs {
@@ -63,6 +32,8 @@ export interface ISearchArgs {
after_limit: number;
order_by_recency: boolean;
room_id?: string;
+ limit: number;
+ next_batch?: string;
}
export interface IEventAndProfile {
@@ -205,10 +176,10 @@ export default abstract class BaseEventIndexManager {
* @param {ISearchArgs} searchArgs The search configuration for the search,
* sets the search term and determines the search result contents.
*
- * @return {Promise<[ISearchResult]>} A promise that will resolve to an array
+ * @return {Promise} A promise that will resolve to an array
* of search results once the search is done.
*/
- async searchEventIndex(searchArgs: ISearchArgs): Promise {
+ async searchEventIndex(searchArgs: ISearchArgs): Promise {
throw new Error("Unimplemented");
}
diff --git a/src/indexing/EventIndex.ts b/src/indexing/EventIndex.ts
index 43b3de42ed..a7142010f2 100644
--- a/src/indexing/EventIndex.ts
+++ b/src/indexing/EventIndex.ts
@@ -16,16 +16,17 @@ limitations under the License.
import { EventEmitter } from "events";
import { RoomMember } from 'matrix-js-sdk/src/models/room-member';
-import { EventTimeline } from 'matrix-js-sdk/src/models/event-timeline';
+import { Direction, EventTimeline } from 'matrix-js-sdk/src/models/event-timeline';
import { Room } from 'matrix-js-sdk/src/models/room';
import { MatrixEvent } from 'matrix-js-sdk/src/models/event';
import { EventTimelineSet } from 'matrix-js-sdk/src/models/event-timeline-set';
import { RoomState } from 'matrix-js-sdk/src/models/room-state';
import { TimelineWindow } from 'matrix-js-sdk/src/timeline-window';
+import { sleep } from "matrix-js-sdk/src/utils";
+import { IResultRoomEvents } from "matrix-js-sdk/src/@types/search";
import PlatformPeg from "../PlatformPeg";
import { MatrixClientPeg } from "../MatrixClientPeg";
-import { sleep } from "../utils/promise";
import SettingsStore from "../settings/SettingsStore";
import { SettingLevel } from "../settings/SettingLevel";
import { ICrawlerCheckpoint, ILoadArgs, ISearchArgs } from "./BaseEventIndexManager";
@@ -66,7 +67,6 @@ export default class EventIndex extends EventEmitter {
client.on('sync', this.onSync);
client.on('Room.timeline', this.onRoomTimeline);
- client.on('Event.decrypted', this.onEventDecrypted);
client.on('Room.timelineReset', this.onTimelineReset);
client.on('Room.redaction', this.onRedaction);
client.on('RoomState.events', this.onRoomStateEvent);
@@ -81,7 +81,6 @@ export default class EventIndex extends EventEmitter {
client.removeListener('sync', this.onSync);
client.removeListener('Room.timeline', this.onRoomTimeline);
- client.removeListener('Event.decrypted', this.onEventDecrypted);
client.removeListener('Room.timelineReset', this.onTimelineReset);
client.removeListener('Room.redaction', this.onRedaction);
client.removeListener('RoomState.events', this.onRoomStateEvent);
@@ -109,19 +108,19 @@ export default class EventIndex extends EventEmitter {
// our message crawler.
await Promise.all(encryptedRooms.map(async (room) => {
const timeline = room.getLiveTimeline();
- const token = timeline.getPaginationToken("b");
+ const token = timeline.getPaginationToken(Direction.Backward);
const backCheckpoint: ICrawlerCheckpoint = {
roomId: room.roomId,
token: token,
- direction: "b",
+ direction: Direction.Backward,
fullCrawl: true,
};
const forwardCheckpoint: ICrawlerCheckpoint = {
roomId: room.roomId,
token: token,
- direction: "f",
+ direction: Direction.Forward,
};
try {
@@ -220,18 +219,6 @@ export default class EventIndex extends EventEmitter {
}
};
- /*
- * The Event.decrypted listener.
- *
- * Checks if the event was marked for addition in the Room.timeline
- * listener, if so queues it up to be added to the index.
- */
- private onEventDecrypted = async (ev: MatrixEvent, err: Error) => {
- // If the event isn't in our live event set, ignore it.
- if (err) return;
- await this.addLiveEventToIndex(ev);
- };
-
/*
* The Room.redaction listener.
*
@@ -371,7 +358,7 @@ export default class EventIndex extends EventEmitter {
if (!room) return;
const timeline = room.getLiveTimeline();
- const token = timeline.getPaginationToken("b");
+ const token = timeline.getPaginationToken(Direction.Backward);
if (!token) {
// The room doesn't contain any tokens, meaning the live timeline
@@ -384,7 +371,7 @@ export default class EventIndex extends EventEmitter {
roomId: room.roomId,
token: token,
fullCrawl: fullCrawl,
- direction: "b",
+ direction: Direction.Backward,
};
console.log("EventIndex: Adding checkpoint", checkpoint);
@@ -671,10 +658,10 @@ export default class EventIndex extends EventEmitter {
* @param {ISearchArgs} searchArgs The search configuration for the search,
* sets the search term and determines the search result contents.
*
- * @return {Promise<[SearchResult]>} A promise that will resolve to an array
+ * @return {Promise} A promise that will resolve to an array
* of search results once the search is done.
*/
- public async search(searchArgs: ISearchArgs) {
+ public async search(searchArgs: ISearchArgs): Promise {
const indexManager = PlatformPeg.get().getEventIndexingManager();
return indexManager.searchEventIndex(searchArgs);
}
@@ -862,7 +849,7 @@ export default class EventIndex extends EventEmitter {
* @returns {Promise} Resolves to a boolean which is true if more
* events were successfully retrieved.
*/
- public paginateTimelineWindow(room: Room, timelineWindow: TimelineWindow, direction: string, limit: number) {
+ public paginateTimelineWindow(room: Room, timelineWindow: TimelineWindow, direction: Direction, limit: number) {
const tl = timelineWindow.getTimelineIndex(direction);
if (!tl) return Promise.resolve(false);
diff --git a/src/languageHandler.tsx b/src/languageHandler.tsx
index a15eb4a4b7..e7329e4f2e 100644
--- a/src/languageHandler.tsx
+++ b/src/languageHandler.tsx
@@ -67,7 +67,7 @@ export function getUserLanguage(): string {
// Function which only purpose is to mark that a string is translatable
// Does not actually do anything. It's helpful for automatic extraction of translatable strings
-export function _td(s: string): string {
+export function _td(s: string): string { // eslint-disable-line @typescript-eslint/naming-convention
return s;
}
@@ -132,6 +132,8 @@ export type TranslatedString = string | React.ReactNode;
*
* @return a React component if any non-strings were used in substitutions, otherwise a string
*/
+// eslint-next-line @typescript-eslint/naming-convention
+// eslint-nexline @typescript-eslint/naming-convention
export function _t(text: string, variables?: IVariables): string;
export function _t(text: string, variables: IVariables, tags: Tags): React.ReactNode;
export function _t(text: string, variables?: IVariables, tags?: Tags): TranslatedString {
@@ -151,7 +153,7 @@ export function _t(text: string, variables?: IVariables, tags?: Tags): Translate
if (typeof substituted === 'string') {
return `@@${text}##${substituted}@@`;
} else {
- return {substituted};
+ return { substituted };
}
} else {
return substituted;
diff --git a/src/mjolnir/Mjolnir.ts b/src/mjolnir/Mjolnir.ts
index 21616eece3..fd30909798 100644
--- a/src/mjolnir/Mjolnir.ts
+++ b/src/mjolnir/Mjolnir.ts
@@ -14,6 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
+import { MatrixEvent } from "matrix-js-sdk/src/models/event";
import { MatrixClientPeg } from "../MatrixClientPeg";
import { ALL_RULE_TYPES, BanList } from "./BanList";
import SettingsStore from "../settings/SettingsStore";
@@ -21,19 +22,17 @@ import { _t } from "../languageHandler";
import dis from "../dispatcher/dispatcher";
import { SettingLevel } from "../settings/SettingLevel";
import { Preset } from "matrix-js-sdk/src/@types/partials";
+import { ActionPayload } from "../dispatcher/payloads";
// TODO: Move this and related files to the js-sdk or something once finalized.
export class Mjolnir {
- static _instance: Mjolnir = null;
+ private static instance: Mjolnir = null;
- _lists: BanList[] = [];
- _roomIds: string[] = [];
- _mjolnirWatchRef = null;
- _dispatcherRef = null;
-
- constructor() {
- }
+ private _lists: BanList[] = []; // eslint-disable-line @typescript-eslint/naming-convention
+ private _roomIds: string[] = []; // eslint-disable-line @typescript-eslint/naming-convention
+ private mjolnirWatchRef: string = null;
+ private dispatcherRef: string = null;
get roomIds(): string[] {
return this._roomIds;
@@ -44,16 +43,16 @@ export class Mjolnir {
}
start() {
- this._mjolnirWatchRef = SettingsStore.watchSetting("mjolnirRooms", null, this._onListsChanged.bind(this));
+ this.mjolnirWatchRef = SettingsStore.watchSetting("mjolnirRooms", null, this.onListsChanged.bind(this));
- this._dispatcherRef = dis.register(this._onAction);
+ this.dispatcherRef = dis.register(this.onAction);
dis.dispatch({
action: 'do_after_sync_prepared',
deferred_action: { action: 'setup_mjolnir' },
});
}
- _onAction = (payload) => {
+ private onAction = (payload: ActionPayload) => {
if (payload['action'] === 'setup_mjolnir') {
console.log("Setting up Mjolnir: after sync");
this.setup();
@@ -62,23 +61,23 @@ export class Mjolnir {
setup() {
if (!MatrixClientPeg.get()) return;
- this._updateLists(SettingsStore.getValue("mjolnirRooms"));
- MatrixClientPeg.get().on("RoomState.events", this._onEvent);
+ this.updateLists(SettingsStore.getValue("mjolnirRooms"));
+ MatrixClientPeg.get().on("RoomState.events", this.onEvent);
}
stop() {
- if (this._mjolnirWatchRef) {
- SettingsStore.unwatchSetting(this._mjolnirWatchRef);
- this._mjolnirWatchRef = null;
+ if (this.mjolnirWatchRef) {
+ SettingsStore.unwatchSetting(this.mjolnirWatchRef);
+ this.mjolnirWatchRef = null;
}
- if (this._dispatcherRef) {
- dis.unregister(this._dispatcherRef);
- this._dispatcherRef = null;
+ if (this.dispatcherRef) {
+ dis.unregister(this.dispatcherRef);
+ this.dispatcherRef = null;
}
if (!MatrixClientPeg.get()) return;
- MatrixClientPeg.get().removeListener("RoomState.events", this._onEvent);
+ MatrixClientPeg.get().removeListener("RoomState.events", this.onEvent);
}
async getOrCreatePersonalList(): Promise {
@@ -132,20 +131,20 @@ export class Mjolnir {
this._lists = this._lists.filter(b => b.roomId !== roomId);
}
- _onEvent = (event) => {
+ private onEvent = (event: MatrixEvent) => {
if (!MatrixClientPeg.get()) return;
if (!this._roomIds.includes(event.getRoomId())) return;
if (!ALL_RULE_TYPES.includes(event.getType())) return;
- this._updateLists(this._roomIds);
+ this.updateLists(this._roomIds);
};
- _onListsChanged(settingName, roomId, atLevel, newValue) {
+ private onListsChanged(settingName: string, roomId: string, atLevel: SettingLevel, newValue: string[]) {
// We know that ban lists are only recorded at one level so we don't need to re-eval them
- this._updateLists(newValue);
+ this.updateLists(newValue);
}
- _updateLists(listRoomIds: string[]) {
+ private updateLists(listRoomIds: string[]) {
if (!MatrixClientPeg.get()) return;
console.log("Updating Mjolnir ban lists to: " + listRoomIds);
@@ -182,10 +181,10 @@ export class Mjolnir {
}
static sharedInstance(): Mjolnir {
- if (!Mjolnir._instance) {
- Mjolnir._instance = new Mjolnir();
+ if (!Mjolnir.instance) {
+ Mjolnir.instance = new Mjolnir();
}
- return Mjolnir._instance;
+ return Mjolnir.instance;
}
}
diff --git a/src/models/IUpload.ts b/src/models/IUpload.ts
index 5b376e9330..1b5a13e394 100644
--- a/src/models/IUpload.ts
+++ b/src/models/IUpload.ts
@@ -14,11 +14,13 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
+import { IAbortablePromise } from "matrix-js-sdk/src/@types/partials";
+
export interface IUpload {
fileName: string;
roomId: string;
total: number;
loaded: number;
- promise: Promise;
+ promise: IAbortablePromise;
canceled?: boolean;
}
diff --git a/src/notifications/ContentRules.ts b/src/notifications/ContentRules.ts
index 5f1281e58c..2b45065568 100644
--- a/src/notifications/ContentRules.ts
+++ b/src/notifications/ContentRules.ts
@@ -1,6 +1,5 @@
/*
-Copyright 2016 OpenMarket Ltd
-Copyright 2019, 2020 The Matrix.org Foundation C.I.C.
+Copyright 2016 - 2021 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -15,13 +14,13 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
-import { PushRuleVectorState, State } from "./PushRuleVectorState";
-import { IExtendedPushRule, IRuleSets } from "./types";
+import { PushRuleVectorState, VectorState } from "./PushRuleVectorState";
+import { IAnnotatedPushRule, IPushRules, PushRuleKind } from "matrix-js-sdk/src/@types/PushRules";
export interface IContentRules {
- vectorState: State;
- rules: IExtendedPushRule[];
- externalRules: IExtendedPushRule[];
+ vectorState: VectorState;
+ rules: IAnnotatedPushRule[];
+ externalRules: IAnnotatedPushRule[];
}
export const SCOPE = "global";
@@ -39,9 +38,9 @@ export class ContentRules {
* externalRules: a list of other keyword rules, with states other than
* vectorState
*/
- static parseContentRules(rulesets: IRuleSets): IContentRules {
+ public static parseContentRules(rulesets: IPushRules): IContentRules {
// first categorise the keyword rules in terms of their actions
- const contentRules = this._categoriseContentRules(rulesets);
+ const contentRules = ContentRules.categoriseContentRules(rulesets);
// Decide which content rules to display in Vector UI.
// Vector displays a single global rule for a list of keywords
@@ -59,7 +58,7 @@ export class ContentRules {
if (contentRules.loud.length) {
return {
- vectorState: State.Loud,
+ vectorState: VectorState.Loud,
rules: contentRules.loud,
externalRules: [
...contentRules.loud_but_disabled,
@@ -70,33 +69,33 @@ export class ContentRules {
};
} else if (contentRules.loud_but_disabled.length) {
return {
- vectorState: State.Off,
+ vectorState: VectorState.Off,
rules: contentRules.loud_but_disabled,
externalRules: [...contentRules.on, ...contentRules.on_but_disabled, ...contentRules.other],
};
} else if (contentRules.on.length) {
return {
- vectorState: State.On,
+ vectorState: VectorState.On,
rules: contentRules.on,
externalRules: [...contentRules.on_but_disabled, ...contentRules.other],
};
} else if (contentRules.on_but_disabled.length) {
return {
- vectorState: State.Off,
+ vectorState: VectorState.Off,
rules: contentRules.on_but_disabled,
externalRules: contentRules.other,
};
} else {
return {
- vectorState: State.On,
+ vectorState: VectorState.On,
rules: [],
externalRules: contentRules.other,
};
}
}
- static _categoriseContentRules(rulesets: IRuleSets) {
- const contentRules: Record<"on"|"on_but_disabled"|"loud"|"loud_but_disabled"|"other", IExtendedPushRule[]> = {
+ private static categoriseContentRules(rulesets: IPushRules) {
+ const contentRules: Record<"on"|"on_but_disabled"|"loud"|"loud_but_disabled"|"other", IAnnotatedPushRule[]> = {
on: [],
on_but_disabled: [],
loud: [],
@@ -109,7 +108,7 @@ export class ContentRules {
const r = rulesets.global[kind][i];
// check it's not a default rule
- if (r.rule_id[0] === '.' || kind !== "content") {
+ if (r.rule_id[0] === '.' || kind !== PushRuleKind.ContentSpecific) {
continue;
}
@@ -117,14 +116,14 @@ export class ContentRules {
r.kind = kind;
switch (PushRuleVectorState.contentRuleVectorStateKind(r)) {
- case State.On:
+ case VectorState.On:
if (r.enabled) {
contentRules.on.push(r);
} else {
contentRules.on_but_disabled.push(r);
}
break;
- case State.Loud:
+ case VectorState.Loud:
if (r.enabled) {
contentRules.loud.push(r);
} else {
diff --git a/src/notifications/NotificationUtils.ts b/src/notifications/NotificationUtils.ts
index 1d5356e16b..3f07c56972 100644
--- a/src/notifications/NotificationUtils.ts
+++ b/src/notifications/NotificationUtils.ts
@@ -1,6 +1,5 @@
/*
-Copyright 2016 OpenMarket Ltd
-Copyright 2019, 2020 The Matrix.org Foundation C.I.C.
+Copyright 2016 - 2021 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -15,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
-import { Action, Actions } from "./types";
+import { PushRuleAction, PushRuleActionName, TweakHighlight, TweakSound } from "matrix-js-sdk/src/@types/PushRules";
interface IEncodedActions {
notify: boolean;
@@ -30,23 +29,23 @@ export class NotificationUtils {
// "highlight: true/false,
// }
// to a list of push actions.
- static encodeActions(action: IEncodedActions) {
+ static encodeActions(action: IEncodedActions): PushRuleAction[] {
const notify = action.notify;
const sound = action.sound;
const highlight = action.highlight;
if (notify) {
- const actions: Action[] = [Actions.Notify];
+ const actions: PushRuleAction[] = [PushRuleActionName.Notify];
if (sound) {
- actions.push({ "set_tweak": "sound", "value": sound });
+ actions.push({ "set_tweak": "sound", "value": sound } as TweakSound);
}
if (highlight) {
- actions.push({ "set_tweak": "highlight" });
+ actions.push({ "set_tweak": "highlight" } as TweakHighlight);
} else {
- actions.push({ "set_tweak": "highlight", "value": false });
+ actions.push({ "set_tweak": "highlight", "value": false } as TweakHighlight);
}
return actions;
} else {
- return [Actions.DontNotify];
+ return [PushRuleActionName.DontNotify];
}
}
@@ -56,16 +55,16 @@ export class NotificationUtils {
// "highlight: true/false,
// }
// If the actions couldn't be decoded then returns null.
- static decodeActions(actions: Action[]): IEncodedActions {
+ static decodeActions(actions: PushRuleAction[]): IEncodedActions {
let notify = false;
let sound = null;
let highlight = false;
for (let i = 0; i < actions.length; ++i) {
const action = actions[i];
- if (action === Actions.Notify) {
+ if (action === PushRuleActionName.Notify) {
notify = true;
- } else if (action === Actions.DontNotify) {
+ } else if (action === PushRuleActionName.DontNotify) {
notify = false;
} else if (typeof action === "object") {
if (action.set_tweak === "sound") {
diff --git a/src/notifications/PushRuleVectorState.ts b/src/notifications/PushRuleVectorState.ts
index 78c7e4b43b..34f7dcf786 100644
--- a/src/notifications/PushRuleVectorState.ts
+++ b/src/notifications/PushRuleVectorState.ts
@@ -1,6 +1,5 @@
/*
-Copyright 2016 OpenMarket Ltd
-Copyright 2019, 2020 The Matrix.org Foundation C.I.C.
+Copyright 2016 - 2021 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -17,9 +16,9 @@ limitations under the License.
import { StandardActions } from "./StandardActions";
import { NotificationUtils } from "./NotificationUtils";
-import { IPushRule } from "./types";
+import { IPushRule } from "matrix-js-sdk/src/@types/PushRules";
-export enum State {
+export enum VectorState {
/** The push rule is disabled */
Off = "off",
/** The user will receive push notification for this rule */
@@ -31,26 +30,26 @@ export enum State {
export class PushRuleVectorState {
// Backwards compatibility (things should probably be using the enum above instead)
- static OFF = State.Off;
- static ON = State.On;
- static LOUD = State.Loud;
+ static OFF = VectorState.Off;
+ static ON = VectorState.On;
+ static LOUD = VectorState.Loud;
/**
* Enum for state of a push rule as defined by the Vector UI.
* @readonly
* @enum {string}
*/
- static states = State;
+ static states = VectorState;
/**
* Convert a PushRuleVectorState to a list of actions
*
* @return [object] list of push-rule actions
*/
- static actionsFor(pushRuleVectorState: State) {
- if (pushRuleVectorState === State.On) {
+ static actionsFor(pushRuleVectorState: VectorState) {
+ if (pushRuleVectorState === VectorState.On) {
return StandardActions.ACTION_NOTIFY;
- } else if (pushRuleVectorState === State.Loud) {
+ } else if (pushRuleVectorState === VectorState.Loud) {
return StandardActions.ACTION_HIGHLIGHT_DEFAULT_SOUND;
}
}
@@ -62,7 +61,7 @@ export class PushRuleVectorState {
* category or in PushRuleVectorState.LOUD, regardless of its enabled
* state. Returns null if it does not match these categories.
*/
- static contentRuleVectorStateKind(rule: IPushRule): State {
+ static contentRuleVectorStateKind(rule: IPushRule): VectorState {
const decoded = NotificationUtils.decodeActions(rule.actions);
if (!decoded) {
@@ -80,10 +79,10 @@ export class PushRuleVectorState {
let stateKind = null;
switch (tweaks) {
case 0:
- stateKind = State.On;
+ stateKind = VectorState.On;
break;
case 2:
- stateKind = State.Loud;
+ stateKind = VectorState.Loud;
break;
}
return stateKind;
diff --git a/src/notifications/VectorPushRulesDefinitions.js b/src/notifications/VectorPushRulesDefinitions.ts
similarity index 66%
rename from src/notifications/VectorPushRulesDefinitions.js
rename to src/notifications/VectorPushRulesDefinitions.ts
index df41f23ec5..a8c617e786 100644
--- a/src/notifications/VectorPushRulesDefinitions.js
+++ b/src/notifications/VectorPushRulesDefinitions.ts
@@ -1,6 +1,5 @@
/*
-Copyright 2016 OpenMarket Ltd
-Copyright 2019 The Matrix.org Foundation C.I.C.
+Copyright 2016 - 2021 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -17,18 +16,33 @@ limitations under the License.
import { _td } from '../languageHandler';
import { StandardActions } from "./StandardActions";
-import { PushRuleVectorState } from "./PushRuleVectorState";
+import { PushRuleVectorState, VectorState } from "./PushRuleVectorState";
import { NotificationUtils } from "./NotificationUtils";
+import { PushRuleAction, PushRuleKind } from "matrix-js-sdk/src/@types/PushRules";
+
+type StateToActionsMap = {
+ [state in VectorState]?: PushRuleAction[];
+};
+
+interface IProps {
+ kind: PushRuleKind;
+ description: string;
+ vectorStateToActions: StateToActionsMap;
+}
class VectorPushRuleDefinition {
- constructor(opts) {
+ private kind: PushRuleKind;
+ private description: string;
+ public readonly vectorStateToActions: StateToActionsMap;
+
+ constructor(opts: IProps) {
this.kind = opts.kind;
this.description = opts.description;
this.vectorStateToActions = opts.vectorStateToActions;
}
// Translate the rule actions and its enabled value into vector state
- ruleToVectorState(rule) {
+ public ruleToVectorState(rule): VectorPushRuleDefinition {
let enabled = false;
if (rule) {
enabled = rule.enabled;
@@ -69,56 +83,56 @@ class VectorPushRuleDefinition {
export const VectorPushRulesDefinitions = {
// Messages containing user's display name
".m.rule.contains_display_name": new VectorPushRuleDefinition({
- kind: "override",
+ kind: PushRuleKind.Override,
description: _td("Messages containing my display name"), // passed through _t() translation in src/components/views/settings/Notifications.js
vectorStateToActions: { // The actions for each vector state, or null to disable the rule.
- on: StandardActions.ACTION_NOTIFY,
- loud: StandardActions.ACTION_HIGHLIGHT_DEFAULT_SOUND,
- off: StandardActions.ACTION_DISABLED,
+ [VectorState.On]: StandardActions.ACTION_NOTIFY,
+ [VectorState.Loud]: StandardActions.ACTION_HIGHLIGHT_DEFAULT_SOUND,
+ [VectorState.Off]: StandardActions.ACTION_DISABLED,
},
}),
// Messages containing user's username (localpart/MXID)
".m.rule.contains_user_name": new VectorPushRuleDefinition({
- kind: "override",
+ kind: PushRuleKind.Override,
description: _td("Messages containing my username"), // passed through _t() translation in src/components/views/settings/Notifications.js
vectorStateToActions: { // The actions for each vector state, or null to disable the rule.
- on: StandardActions.ACTION_NOTIFY,
- loud: StandardActions.ACTION_HIGHLIGHT_DEFAULT_SOUND,
- off: StandardActions.ACTION_DISABLED,
+ [VectorState.On]: StandardActions.ACTION_NOTIFY,
+ [VectorState.Loud]: StandardActions.ACTION_HIGHLIGHT_DEFAULT_SOUND,
+ [VectorState.Off]: StandardActions.ACTION_DISABLED,
},
}),
// Messages containing @room
".m.rule.roomnotif": new VectorPushRuleDefinition({
- kind: "override",
+ kind: PushRuleKind.Override,
description: _td("Messages containing @room"), // passed through _t() translation in src/components/views/settings/Notifications.js
vectorStateToActions: { // The actions for each vector state, or null to disable the rule.
- on: StandardActions.ACTION_NOTIFY,
- loud: StandardActions.ACTION_HIGHLIGHT,
- off: StandardActions.ACTION_DISABLED,
+ [VectorState.On]: StandardActions.ACTION_NOTIFY,
+ [VectorState.Loud]: StandardActions.ACTION_HIGHLIGHT,
+ [VectorState.Off]: StandardActions.ACTION_DISABLED,
},
}),
// Messages just sent to the user in a 1:1 room
".m.rule.room_one_to_one": new VectorPushRuleDefinition({
- kind: "underride",
+ kind: PushRuleKind.Underride,
description: _td("Messages in one-to-one chats"), // passed through _t() translation in src/components/views/settings/Notifications.js
vectorStateToActions: {
- on: StandardActions.ACTION_NOTIFY,
- loud: StandardActions.ACTION_NOTIFY_DEFAULT_SOUND,
- off: StandardActions.ACTION_DONT_NOTIFY,
+ [VectorState.On]: StandardActions.ACTION_NOTIFY,
+ [VectorState.Loud]: StandardActions.ACTION_NOTIFY_DEFAULT_SOUND,
+ [VectorState.Off]: StandardActions.ACTION_DONT_NOTIFY,
},
}),
// Encrypted messages just sent to the user in a 1:1 room
".m.rule.encrypted_room_one_to_one": new VectorPushRuleDefinition({
- kind: "underride",
+ kind: PushRuleKind.Underride,
description: _td("Encrypted messages in one-to-one chats"), // passed through _t() translation in src/components/views/settings/Notifications.js
vectorStateToActions: {
- on: StandardActions.ACTION_NOTIFY,
- loud: StandardActions.ACTION_NOTIFY_DEFAULT_SOUND,
- off: StandardActions.ACTION_DONT_NOTIFY,
+ [VectorState.On]: StandardActions.ACTION_NOTIFY,
+ [VectorState.Loud]: StandardActions.ACTION_NOTIFY_DEFAULT_SOUND,
+ [VectorState.Off]: StandardActions.ACTION_DONT_NOTIFY,
},
}),
@@ -126,12 +140,12 @@ export const VectorPushRulesDefinitions = {
// 1:1 room messages are catched by the .m.rule.room_one_to_one rule if any defined
// By opposition, all other room messages are from group chat rooms.
".m.rule.message": new VectorPushRuleDefinition({
- kind: "underride",
+ kind: PushRuleKind.Underride,
description: _td("Messages in group chats"), // passed through _t() translation in src/components/views/settings/Notifications.js
vectorStateToActions: {
- on: StandardActions.ACTION_NOTIFY,
- loud: StandardActions.ACTION_NOTIFY_DEFAULT_SOUND,
- off: StandardActions.ACTION_DONT_NOTIFY,
+ [VectorState.On]: StandardActions.ACTION_NOTIFY,
+ [VectorState.Loud]: StandardActions.ACTION_NOTIFY_DEFAULT_SOUND,
+ [VectorState.Off]: StandardActions.ACTION_DONT_NOTIFY,
},
}),
@@ -139,57 +153,57 @@ export const VectorPushRulesDefinitions = {
// Encrypted 1:1 room messages are catched by the .m.rule.encrypted_room_one_to_one rule if any defined
// By opposition, all other room messages are from group chat rooms.
".m.rule.encrypted": new VectorPushRuleDefinition({
- kind: "underride",
+ kind: PushRuleKind.Underride,
description: _td("Encrypted messages in group chats"), // passed through _t() translation in src/components/views/settings/Notifications.js
vectorStateToActions: {
- on: StandardActions.ACTION_NOTIFY,
- loud: StandardActions.ACTION_NOTIFY_DEFAULT_SOUND,
- off: StandardActions.ACTION_DONT_NOTIFY,
+ [VectorState.On]: StandardActions.ACTION_NOTIFY,
+ [VectorState.Loud]: StandardActions.ACTION_NOTIFY_DEFAULT_SOUND,
+ [VectorState.Off]: StandardActions.ACTION_DONT_NOTIFY,
},
}),
// Invitation for the user
".m.rule.invite_for_me": new VectorPushRuleDefinition({
- kind: "underride",
+ kind: PushRuleKind.Underride,
description: _td("When I'm invited to a room"), // passed through _t() translation in src/components/views/settings/Notifications.js
vectorStateToActions: {
- on: StandardActions.ACTION_NOTIFY,
- loud: StandardActions.ACTION_NOTIFY_DEFAULT_SOUND,
- off: StandardActions.ACTION_DISABLED,
+ [VectorState.On]: StandardActions.ACTION_NOTIFY,
+ [VectorState.Loud]: StandardActions.ACTION_NOTIFY_DEFAULT_SOUND,
+ [VectorState.Off]: StandardActions.ACTION_DISABLED,
},
}),
// Incoming call
".m.rule.call": new VectorPushRuleDefinition({
- kind: "underride",
+ kind: PushRuleKind.Underride,
description: _td("Call invitation"), // passed through _t() translation in src/components/views/settings/Notifications.js
vectorStateToActions: {
- on: StandardActions.ACTION_NOTIFY,
- loud: StandardActions.ACTION_NOTIFY_RING_SOUND,
- off: StandardActions.ACTION_DISABLED,
+ [VectorState.On]: StandardActions.ACTION_NOTIFY,
+ [VectorState.Loud]: StandardActions.ACTION_NOTIFY_RING_SOUND,
+ [VectorState.Off]: StandardActions.ACTION_DISABLED,
},
}),
// Notifications from bots
".m.rule.suppress_notices": new VectorPushRuleDefinition({
- kind: "override",
+ kind: PushRuleKind.Override,
description: _td("Messages sent by bot"), // passed through _t() translation in src/components/views/settings/Notifications.js
vectorStateToActions: {
// .m.rule.suppress_notices is a "negative" rule, we have to invert its enabled value for vector UI
- on: StandardActions.ACTION_DISABLED,
- loud: StandardActions.ACTION_NOTIFY_DEFAULT_SOUND,
- off: StandardActions.ACTION_DONT_NOTIFY,
+ [VectorState.On]: StandardActions.ACTION_DISABLED,
+ [VectorState.Loud]: StandardActions.ACTION_NOTIFY_DEFAULT_SOUND,
+ [VectorState.Off]: StandardActions.ACTION_DONT_NOTIFY,
},
}),
// Room upgrades (tombstones)
".m.rule.tombstone": new VectorPushRuleDefinition({
- kind: "override",
+ kind: PushRuleKind.Override,
description: _td("When rooms are upgraded"), // passed through _t() translation in src/components/views/settings/Notifications.js
vectorStateToActions: { // The actions for each vector state, or null to disable the rule.
- on: StandardActions.ACTION_NOTIFY,
- loud: StandardActions.ACTION_HIGHLIGHT,
- off: StandardActions.ACTION_DISABLED,
+ [VectorState.On]: StandardActions.ACTION_NOTIFY,
+ [VectorState.Loud]: StandardActions.ACTION_HIGHLIGHT,
+ [VectorState.Off]: StandardActions.ACTION_DISABLED,
},
}),
};
diff --git a/src/notifications/index.js b/src/notifications/index.ts
similarity index 100%
rename from src/notifications/index.js
rename to src/notifications/index.ts
diff --git a/src/notifications/types.ts b/src/notifications/types.ts
deleted file mode 100644
index ea46552947..0000000000
--- a/src/notifications/types.ts
+++ /dev/null
@@ -1,114 +0,0 @@
-/*
-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.
-*/
-
-export enum NotificationSetting {
- AllMessages = "all_messages", // .m.rule.message = notify
- DirectMessagesMentionsKeywords = "dm_mentions_keywords", // .m.rule.message = mark_unread. This is the new default.
- MentionsKeywordsOnly = "mentions_keywords", // .m.rule.message = mark_unread; .m.rule.room_one_to_one = mark_unread
- Never = "never", // .m.rule.master = enabled (dont_notify)
-}
-
-export interface ISoundTweak {
- // eslint-disable-next-line camelcase
- set_tweak: "sound";
- value: string;
-}
-export interface IHighlightTweak {
- // eslint-disable-next-line camelcase
- set_tweak: "highlight";
- value?: boolean;
-}
-
-export type Tweak = ISoundTweak | IHighlightTweak;
-
-export enum Actions {
- Notify = "notify",
- DontNotify = "dont_notify", // no-op
- Coalesce = "coalesce", // unused
- MarkUnread = "mark_unread", // new
-}
-
-export type Action = Actions | Tweak;
-
-// Push rule kinds in descending priority order
-export enum Kind {
- Override = "override",
- ContentSpecific = "content",
- RoomSpecific = "room",
- SenderSpecific = "sender",
- Underride = "underride",
-}
-
-export interface IEventMatchCondition {
- kind: "event_match";
- key: string;
- pattern: string;
-}
-
-export interface IContainsDisplayNameCondition {
- kind: "contains_display_name";
-}
-
-export interface IRoomMemberCountCondition {
- kind: "room_member_count";
- is: string;
-}
-
-export interface ISenderNotificationPermissionCondition {
- kind: "sender_notification_permission";
- key: string;
-}
-
-export type Condition =
- IEventMatchCondition |
- IContainsDisplayNameCondition |
- IRoomMemberCountCondition |
- ISenderNotificationPermissionCondition;
-
-export enum RuleIds {
- MasterRule = ".m.rule.master", // The master rule (all notifications disabling)
- MessageRule = ".m.rule.message",
- EncryptedMessageRule = ".m.rule.encrypted",
- RoomOneToOneRule = ".m.rule.room_one_to_one",
- EncryptedRoomOneToOneRule = ".m.rule.room_one_to_one",
-}
-
-export interface IPushRule {
- enabled: boolean;
- // eslint-disable-next-line camelcase
- rule_id: RuleIds | string;
- actions: Action[];
- default: boolean;
- conditions?: Condition[]; // only applicable to `underride` and `override` rules
- pattern?: string; // only applicable to `content` rules
-}
-
-// push rule extended with kind, used by ContentRules and js-sdk's pushprocessor
-export interface IExtendedPushRule extends IPushRule {
- kind: Kind;
-}
-
-export interface IPushRuleSet {
- override: IPushRule[];
- content: IPushRule[];
- room: IPushRule[];
- sender: IPushRule[];
- underride: IPushRule[];
-}
-
-export interface IRuleSets {
- global: IPushRuleSet;
-}
diff --git a/src/performance/index.ts b/src/performance/index.ts
index 1e24839370..cb808f9173 100644
--- a/src/performance/index.ts
+++ b/src/performance/index.ts
@@ -17,15 +17,15 @@ limitations under the License.
import { PerformanceEntryNames } from "./entry-names";
interface GetEntriesOptions {
- name?: string,
- type?: string,
+ name?: string;
+ type?: string;
}
type PerformanceCallbackFunction = (entry: PerformanceEntry[]) => void;
interface PerformanceDataListener {
- entryNames?: string[],
- callback: PerformanceCallbackFunction
+ entryNames?: string[];
+ callback: PerformanceCallbackFunction;
}
export default class PerformanceMonitor {
diff --git a/src/phonenumber.ts b/src/phonenumber.ts
index ea008cf2f0..51d12babed 100644
--- a/src/phonenumber.ts
+++ b/src/phonenumber.ts
@@ -42,7 +42,13 @@ export const getEmojiFlag = (countryCode: string) => {
return String.fromCodePoint(...countryCode.split('').map(l => UNICODE_BASE + l.charCodeAt(0)));
};
-export const COUNTRIES = [
+export interface PhoneNumberCountryDefinition {
+ iso2: string;
+ name: string;
+ prefix: string;
+}
+
+export const COUNTRIES: PhoneNumberCountryDefinition[] = [
{
"iso2": "GB",
"name": _td("United Kingdom"),
diff --git a/src/rageshake/submit-rageshake.ts b/src/rageshake/submit-rageshake.ts
index 5e0b2c3c4d..fd84f479ad 100644
--- a/src/rageshake/submit-rageshake.ts
+++ b/src/rageshake/submit-rageshake.ts
@@ -93,15 +93,15 @@ async function collectBugReport(opts: IOpts = {}, gzipLogs = true) {
body.append("cross_signing_supported_by_hs",
String(await client.doesServerSupportUnstableFeature("org.matrix.e2e_cross_signing")));
body.append("cross_signing_key", crossSigning.getId());
- body.append("cross_signing_pk_in_secret_storage",
+ body.append("cross_signing_privkey_in_secret_storage",
String(!!(await crossSigning.isStoredInSecretStorage(secretStorage))));
const pkCache = client.getCrossSigningCacheCallbacks();
- body.append("cross_signing_master_pk_cached",
+ body.append("cross_signing_master_privkey_cached",
String(!!(pkCache && await pkCache.getCrossSigningKeyCache("master"))));
- body.append("cross_signing_self_signing_pk_cached",
+ body.append("cross_signing_self_signing_privkey_cached",
String(!!(pkCache && await pkCache.getCrossSigningKeyCache("self_signing"))));
- body.append("cross_signing_user_signing_pk_cached",
+ body.append("cross_signing_user_signing_privkey_cached",
String(!!(pkCache && await pkCache.getCrossSigningKeyCache("user_signing"))));
body.append("secret_storage_ready", String(await client.isSecretStorageReady()));
@@ -203,7 +203,7 @@ export default async function sendBugReport(bugReportEndpoint: string, opts: IOp
const body = await collectBugReport(opts);
progressCallback(_t("Uploading logs"));
- await _submitReport(bugReportEndpoint, body, progressCallback);
+ await submitReport(bugReportEndpoint, body, progressCallback);
}
/**
@@ -289,10 +289,10 @@ export async function submitFeedback(
body.append(k, extraData[k]);
}
- await _submitReport(SdkConfig.get().bug_report_endpoint_url, body, () => {});
+ await submitReport(SdkConfig.get().bug_report_endpoint_url, body, () => {});
}
-function _submitReport(endpoint: string, body: FormData, progressCallback: (string) => void) {
+function submitReport(endpoint: string, body: FormData, progressCallback: (str: string) => void) {
return new Promise((resolve, reject) => {
const req = new XMLHttpRequest();
req.open("POST", endpoint);
diff --git a/src/ratelimitedfunc.js b/src/ratelimitedfunc.js
deleted file mode 100644
index 3df3db615e..0000000000
--- a/src/ratelimitedfunc.js
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
-Copyright 2016 OpenMarket Ltd
-
-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.
-*/
-
-/**
- * 'debounces' a function to only execute every n milliseconds.
- * Useful when react-sdk gets many, many events but only wants
- * to update the interface once for all of them.
- *
- * Note that the function must not take arguments, since the args
- * could be different for each invocation of the function.
- *
- * The returned function has a 'cancelPendingCall' property which can be called
- * on unmount or similar to cancel any pending update.
- */
-
-import {throttle} from "lodash";
-
-export default function ratelimitedfunc(fn, time) {
- const throttledFn = throttle(fn, time, {
- leading: true,
- trailing: true,
- });
- const _bind = throttledFn.bind;
- throttledFn.bind = function() {
- const boundFn = _bind.apply(throttledFn, arguments);
- boundFn.cancelPendingCall = throttledFn.cancelPendingCall;
- return boundFn;
- };
-
- throttledFn.cancelPendingCall = function() {
- throttledFn.cancel();
- };
- return throttledFn;
-}
diff --git a/src/settings/Layout.ts b/src/settings/Layout.ts
index 3a42b2b510..d4e1f06c0a 100644
--- a/src/settings/Layout.ts
+++ b/src/settings/Layout.ts
@@ -1,5 +1,6 @@
/*
Copyright 2021 Šimon Brandner
+Copyright 2021 Quirin Götz
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -19,7 +20,8 @@ import PropTypes from 'prop-types';
/* TODO: This should be later reworked into something more generic */
export enum Layout {
IRC = "irc",
- Group = "group"
+ Group = "group",
+ Bubble = "bubble",
}
/* We need this because multiple components are still using JavaScript */
diff --git a/src/settings/Settings.tsx b/src/settings/Settings.tsx
index 96820d0ff2..f0bdb2e0e5 100644
--- a/src/settings/Settings.tsx
+++ b/src/settings/Settings.tsx
@@ -41,6 +41,7 @@ import { Layout } from "./Layout";
import ReducedMotionController from './controllers/ReducedMotionController';
import IncompatibleController from "./controllers/IncompatibleController";
import SdkConfig from "../SdkConfig";
+import NewLayoutSwitcherController from './controllers/NewLayoutSwitcherController';
// These are just a bunch of helper arrays to avoid copy/pasting a bunch of times
const LEVELS_ROOM_SETTINGS = [
@@ -321,6 +322,13 @@ export const SETTINGS: {[setting: string]: ISetting} = {
displayName: _td("Show info about bridges in room settings"),
default: false,
},
+ "feature_new_layout_switcher": {
+ isFeature: true,
+ supportedLevels: LEVELS_FEATURE,
+ displayName: _td("New layout switcher (with message bubbles)"),
+ default: false,
+ controller: new NewLayoutSwitcherController(),
+ },
"RoomList.backgroundImage": {
supportedLevels: LEVELS_ACCOUNT_SETTINGS,
default: null,
@@ -455,7 +463,7 @@ export const SETTINGS: {[setting: string]: ISetting} = {
},
"ctrlFForSearch": {
supportedLevels: LEVELS_ACCOUNT_SETTINGS,
- displayName: isMac ? _td("Use Command + F to search") : _td("Use Ctrl + F to search"),
+ displayName: isMac ? _td("Use Command + F to search timeline") : _td("Use Ctrl + F to search timeline"),
default: false,
},
"MessageComposerInput.ctrlEnterToSend": {
@@ -812,7 +820,7 @@ export const SETTINGS: {[setting: string]: ISetting} = {
[UIFeature.IdentityServer]: {
supportedLevels: LEVELS_UI_FEATURE,
default: true,
- // Identity Server (Discovery) Settings make no sense if 3PIDs in general are hidden
+ // Identity server (discovery) settings make no sense if 3PIDs in general are hidden
controller: new UIFeatureController(UIFeature.ThirdPartyID),
},
[UIFeature.ThirdPartyID]: {
diff --git a/src/settings/controllers/NewLayoutSwitcherController.ts b/src/settings/controllers/NewLayoutSwitcherController.ts
new file mode 100644
index 0000000000..b1d6cac55e
--- /dev/null
+++ b/src/settings/controllers/NewLayoutSwitcherController.ts
@@ -0,0 +1,26 @@
+/*
+Copyright 2021 The Matrix.org Foundation C.I.C.
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+ http://www.apache.org/licenses/LICENSE-2.0
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+import SettingController from "./SettingController";
+import { SettingLevel } from "../SettingLevel";
+import SettingsStore from "../SettingsStore";
+import { Layout } from "../Layout";
+
+export default class NewLayoutSwitcherController extends SettingController {
+ public onChange(level: SettingLevel, roomId: string, newValue: any) {
+ // On disabling switch back to Layout.Group if Layout.Bubble
+ if (!newValue && SettingsStore.getValue("layout") == Layout.Bubble) {
+ SettingsStore.setValue("layout", null, SettingLevel.DEVICE, Layout.Group);
+ }
+ }
+}
diff --git a/src/settings/handlers/AccountSettingsHandler.ts b/src/settings/handlers/AccountSettingsHandler.ts
index 60ec849883..9c937ebd88 100644
--- a/src/settings/handlers/AccountSettingsHandler.ts
+++ b/src/settings/handlers/AccountSettingsHandler.ts
@@ -123,12 +123,13 @@ export default class AccountSettingsHandler extends MatrixClientBackedSettingsHa
return preferredValue;
}
- public setValue(settingName: string, roomId: string, newValue: any): Promise {
+ public async setValue(settingName: string, roomId: string, newValue: any): Promise {
// Special case URL previews
if (settingName === "urlPreviewsEnabled") {
const content = this.getSettings("org.matrix.preview_urls") || {};
content['disable'] = !newValue;
- return MatrixClientPeg.get().setAccountData("org.matrix.preview_urls", content);
+ await MatrixClientPeg.get().setAccountData("org.matrix.preview_urls", content);
+ return;
}
// Special case for breadcrumbs
@@ -141,26 +142,29 @@ export default class AccountSettingsHandler extends MatrixClientBackedSettingsHa
if (!content) content = {}; // If we still don't have content, make some
content['recent_rooms'] = newValue;
- return MatrixClientPeg.get().setAccountData(BREADCRUMBS_EVENT_TYPE, content);
+ await MatrixClientPeg.get().setAccountData(BREADCRUMBS_EVENT_TYPE, content);
+ return;
}
// Special case recent emoji
if (settingName === "recent_emoji") {
const content = this.getSettings(RECENT_EMOJI_EVENT_TYPE) || {};
content["recent_emoji"] = newValue;
- return MatrixClientPeg.get().setAccountData(RECENT_EMOJI_EVENT_TYPE, content);
+ await MatrixClientPeg.get().setAccountData(RECENT_EMOJI_EVENT_TYPE, content);
+ return;
}
// Special case integration manager provisioning
if (settingName === "integrationProvisioning") {
const content = this.getSettings(INTEG_PROVISIONING_EVENT_TYPE) || {};
content['enabled'] = newValue;
- return MatrixClientPeg.get().setAccountData(INTEG_PROVISIONING_EVENT_TYPE, content);
+ await MatrixClientPeg.get().setAccountData(INTEG_PROVISIONING_EVENT_TYPE, content);
+ return;
}
const content = this.getSettings() || {};
content[settingName] = newValue;
- return MatrixClientPeg.get().setAccountData("im.vector.web.settings", content);
+ await MatrixClientPeg.get().setAccountData("im.vector.web.settings", content);
}
public canSetValue(settingName: string, roomId: string): boolean {
diff --git a/src/settings/handlers/RoomAccountSettingsHandler.ts b/src/settings/handlers/RoomAccountSettingsHandler.ts
index e0345fde8c..a5ebfae621 100644
--- a/src/settings/handlers/RoomAccountSettingsHandler.ts
+++ b/src/settings/handlers/RoomAccountSettingsHandler.ts
@@ -86,22 +86,24 @@ export default class RoomAccountSettingsHandler extends MatrixClientBackedSettin
return settings[settingName];
}
- public setValue(settingName: string, roomId: string, newValue: any): Promise {
+ public async setValue(settingName: string, roomId: string, newValue: any): Promise {
// Special case URL previews
if (settingName === "urlPreviewsEnabled") {
const content = this.getSettings(roomId, "org.matrix.room.preview_urls") || {};
content['disable'] = !newValue;
- return MatrixClientPeg.get().setRoomAccountData(roomId, "org.matrix.room.preview_urls", content);
+ await MatrixClientPeg.get().setRoomAccountData(roomId, "org.matrix.room.preview_urls", content);
+ return;
}
// Special case allowed widgets
if (settingName === "allowedWidgets") {
- return MatrixClientPeg.get().setRoomAccountData(roomId, ALLOWED_WIDGETS_EVENT_TYPE, newValue);
+ await MatrixClientPeg.get().setRoomAccountData(roomId, ALLOWED_WIDGETS_EVENT_TYPE, newValue);
+ return;
}
const content = this.getSettings(roomId) || {};
content[settingName] = newValue;
- return MatrixClientPeg.get().setRoomAccountData(roomId, "im.vector.web.settings", content);
+ await MatrixClientPeg.get().setRoomAccountData(roomId, "im.vector.web.settings", content);
}
public canSetValue(settingName: string, roomId: string): boolean {
diff --git a/src/settings/handlers/RoomSettingsHandler.ts b/src/settings/handlers/RoomSettingsHandler.ts
index 3315e40a65..974f94062c 100644
--- a/src/settings/handlers/RoomSettingsHandler.ts
+++ b/src/settings/handlers/RoomSettingsHandler.ts
@@ -87,17 +87,18 @@ export default class RoomSettingsHandler extends MatrixClientBackedSettingsHandl
return settings[settingName];
}
- public setValue(settingName: string, roomId: string, newValue: any): Promise {
+ public async setValue(settingName: string, roomId: string, newValue: any): Promise {
// Special case URL previews
if (settingName === "urlPreviewsEnabled") {
const content = this.getSettings(roomId, "org.matrix.room.preview_urls") || {};
content['disable'] = !newValue;
- return MatrixClientPeg.get().sendStateEvent(roomId, "org.matrix.room.preview_urls", content);
+ await MatrixClientPeg.get().sendStateEvent(roomId, "org.matrix.room.preview_urls", content);
+ return;
}
const content = this.getSettings(roomId) || {};
content[settingName] = newValue;
- return MatrixClientPeg.get().sendStateEvent(roomId, "im.vector.web.settings", content, "");
+ await MatrixClientPeg.get().sendStateEvent(roomId, "im.vector.web.settings", content, "");
}
public canSetValue(settingName: string, roomId: string): boolean {
diff --git a/src/stores/BreadcrumbsStore.ts b/src/stores/BreadcrumbsStore.ts
index a3b07435c6..aceaf8b898 100644
--- a/src/stores/BreadcrumbsStore.ts
+++ b/src/stores/BreadcrumbsStore.ts
@@ -22,6 +22,7 @@ import defaultDispatcher from "../dispatcher/dispatcher";
import { arrayHasDiff } from "../utils/arrays";
import { isNullOrUndefined } from "matrix-js-sdk/src/utils";
import { SettingLevel } from "../settings/SettingLevel";
+import SpaceStore from "./SpaceStore";
const MAX_ROOMS = 20; // arbitrary
const AUTOJOIN_WAIT_THRESHOLD_MS = 90000; // 90s, the time we wait for an autojoined room to show up
@@ -122,7 +123,7 @@ export class BreadcrumbsStore extends AsyncStoreWithClient {
}
private async appendRoom(room: Room) {
- if (SettingsStore.getValue("feature_spaces") && room.isSpaceRoom()) return; // hide space rooms
+ if (SpaceStore.spacesEnabled && room.isSpaceRoom()) return; // hide space rooms
let updated = false;
const rooms = (this.state.rooms || []).slice(); // cheap clone
diff --git a/src/stores/GroupFilterOrderStore.js b/src/stores/GroupFilterOrderStore.js
index e6401f21f8..e81d1b81f7 100644
--- a/src/stores/GroupFilterOrderStore.js
+++ b/src/stores/GroupFilterOrderStore.js
@@ -49,7 +49,7 @@ class GroupFilterOrderStore extends Store {
this.__emitChange();
}
- __onDispatch(payload) {
+ __onDispatch(payload) { // eslint-disable-line @typescript-eslint/naming-convention
switch (payload.action) {
// Initialise state after initial sync
case 'view_room': {
diff --git a/src/stores/LifecycleStore.ts b/src/stores/LifecycleStore.ts
index 5381fc58ed..7db50af7a1 100644
--- a/src/stores/LifecycleStore.ts
+++ b/src/stores/LifecycleStore.ts
@@ -44,7 +44,7 @@ class LifecycleStore extends Store {
this.__emitChange();
}
- protected __onDispatch(payload: ActionPayload) {
+ protected __onDispatch(payload: ActionPayload) { // eslint-disable-line @typescript-eslint/naming-convention
switch (payload.action) {
case 'do_after_sync_prepared':
this.setState({
diff --git a/src/stores/RightPanelStore.ts b/src/stores/RightPanelStore.ts
index 1b5e9a3413..b6f91bf835 100644
--- a/src/stores/RightPanelStore.ts
+++ b/src/stores/RightPanelStore.ts
@@ -22,7 +22,6 @@ import { RightPanelPhases, RIGHT_PANEL_PHASES_NO_ARGS } from "./RightPanelStoreP
import { ActionPayload } from "../dispatcher/payloads";
import { Action } from '../dispatcher/actions';
import { SettingLevel } from "../settings/SettingLevel";
-import RoomViewStore from './RoomViewStore';
interface RightPanelStoreState {
// Whether or not to show the right panel at all. We split out rooms and groups
@@ -68,6 +67,7 @@ const MEMBER_INFO_PHASES = [
export default class RightPanelStore extends Store {
private static instance: RightPanelStore;
private state: RightPanelStoreState;
+ private lastRoomId: string;
constructor() {
super(dis);
@@ -144,11 +144,13 @@ export default class RightPanelStore extends Store {
this.__emitChange();
}
- __onDispatch(payload: ActionPayload) {
+ __onDispatch(payload: ActionPayload) { // eslint-disable-line @typescript-eslint/naming-convention
switch (payload.action) {
case 'view_room':
+ if (payload.room_id === this.lastRoomId) break; // skip this transition, probably a permalink
+ // fallthrough
case 'view_group':
- if (payload.room_id === RoomViewStore.getRoomId()) break; // skip this transition, probably a permalink
+ this.lastRoomId = payload.room_id;
// Reset to the member list if we're viewing member info
if (MEMBER_INFO_PHASES.includes(this.state.lastRoomPhase)) {
diff --git a/src/stores/RoomScrollStateStore.js b/src/stores/RoomScrollStateStore.js
deleted file mode 100644
index 07848283d1..0000000000
--- a/src/stores/RoomScrollStateStore.js
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
-Copyright 2017 New Vector Ltd
-
-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.
-*/
-
-/**
- * Stores where the user has scrolled to in each room
- */
-class RoomScrollStateStore {
- constructor() {
- // A map from room id to scroll state.
- //
- // If there is no special scroll state (ie, we are following the live
- // timeline), the scroll state is null. Otherwise, it is an object with
- // the following properties:
- //
- // focussedEvent: the ID of the 'focussed' event. Typically this is
- // the last event fully visible in the viewport, though if we
- // have done an explicit scroll to an explicit event, it will be
- // that event.
- //
- // pixelOffset: the number of pixels the window is scrolled down
- // from the focussedEvent.
- this._scrollStateMap = {};
- }
-
- getScrollState(roomId) {
- return this._scrollStateMap[roomId];
- }
-
- setScrollState(roomId, scrollState) {
- this._scrollStateMap[roomId] = scrollState;
- }
-}
-
-if (global.mx_RoomScrollStateStore === undefined) {
- global.mx_RoomScrollStateStore = new RoomScrollStateStore();
-}
-export default global.mx_RoomScrollStateStore;
diff --git a/src/stores/RoomScrollStateStore.ts b/src/stores/RoomScrollStateStore.ts
new file mode 100644
index 0000000000..1d8d4eb8fd
--- /dev/null
+++ b/src/stores/RoomScrollStateStore.ts
@@ -0,0 +1,53 @@
+/*
+Copyright 2017 New Vector Ltd
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+export interface ScrollState {
+ focussedEvent: string;
+ pixelOffset: number;
+}
+
+/**
+ * Stores where the user has scrolled to in each room
+ */
+export class RoomScrollStateStore {
+ // A map from room id to scroll state.
+ //
+ // If there is no special scroll state (ie, we are following the live
+ // timeline), the scroll state is null. Otherwise, it is an object with
+ // the following properties:
+ //
+ // focussedEvent: the ID of the 'focussed' event. Typically this is
+ // the last event fully visible in the viewport, though if we
+ // have done an explicit scroll to an explicit event, it will be
+ // that event.
+ //
+ // pixelOffset: the number of pixels the window is scrolled down
+ // from the focussedEvent.
+ private scrollStateMap = new Map();
+
+ public getScrollState(roomId: string): ScrollState {
+ return this.scrollStateMap.get(roomId);
+ }
+
+ setScrollState(roomId: string, scrollState: ScrollState): void {
+ this.scrollStateMap.set(roomId, scrollState);
+ }
+}
+
+if (window.mxRoomScrollStateStore === undefined) {
+ window.mxRoomScrollStateStore = new RoomScrollStateStore();
+}
+export default window.mxRoomScrollStateStore;
diff --git a/src/stores/RoomViewStore.tsx b/src/stores/RoomViewStore.tsx
index 87978df471..f2a7c135a3 100644
--- a/src/stores/RoomViewStore.tsx
+++ b/src/stores/RoomViewStore.tsx
@@ -96,7 +96,7 @@ class RoomViewStore extends Store {
this.__emitChange();
}
- __onDispatch(payload) {
+ __onDispatch(payload) { // eslint-disable-line @typescript-eslint/naming-convention
switch (payload.action) {
// view_room:
// - room_alias: '#somealias:matrix.org'
@@ -164,6 +164,7 @@ class RoomViewStore extends Store {
}
break;
case 'open_room_settings': {
+ // FIXME: Using an import will result in test failures
const RoomSettingsDialog = sdk.getComponent("dialogs.RoomSettingsDialog");
Modal.createTrackedDialog('Room settings', '', RoomSettingsDialog, {
roomId: payload.room_id || this.state.roomId,
@@ -324,8 +325,8 @@ class RoomViewStore extends Store {
msg = _t("There was an error joining the room");
} else if (err.errcode === 'M_INCOMPATIBLE_ROOM_VERSION') {
msg =
- {_t("Sorry, your homeserver is too old to participate in this room.")}
- {_t("Please contact your homeserver administrator.")}
+ { _t("Sorry, your homeserver is too old to participate in this room.") }
+ { _t("Please contact your homeserver administrator.") }
;
} else if (err.httpStatus === 404) {
const invitingUserId = this.getInvitingUserId(this.state.roomId);
@@ -340,6 +341,7 @@ class RoomViewStore extends Store {
}
}
+ // FIXME: Using an import will result in test failures
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
Modal.createTrackedDialog('Failed to join room', '', ErrorDialog, {
title: _t("Failed to join room"),
@@ -427,7 +429,7 @@ class RoomViewStore extends Store {
}
}
-let singletonRoomViewStore = null;
+let singletonRoomViewStore: RoomViewStore = null;
if (!singletonRoomViewStore) {
singletonRoomViewStore = new RoomViewStore();
}
diff --git a/src/stores/SetupEncryptionStore.ts b/src/stores/SetupEncryptionStore.ts
index e969c64853..7197374502 100644
--- a/src/stores/SetupEncryptionStore.ts
+++ b/src/stores/SetupEncryptionStore.ts
@@ -17,7 +17,7 @@ limitations under the License.
import EventEmitter from 'events';
import { VerificationRequest } from "matrix-js-sdk/src/crypto/verification/request/VerificationRequest";
import { IKeyBackupInfo } from "matrix-js-sdk/src/crypto/keybackup";
-import { ISecretStorageKeyInfo } from "matrix-js-sdk/src/matrix";
+import { ISecretStorageKeyInfo } from "matrix-js-sdk/src/crypto/api";
import { PHASE_DONE as VERIF_PHASE_DONE } from "matrix-js-sdk/src/crypto/verification/request/VerificationRequest";
import { MatrixClientPeg } from '../MatrixClientPeg';
diff --git a/src/stores/SpaceStore.tsx b/src/stores/SpaceStore.tsx
index 514f8418b8..03907fd46b 100644
--- a/src/stores/SpaceStore.tsx
+++ b/src/stores/SpaceStore.tsx
@@ -18,6 +18,7 @@ import { ListIteratee, Many, sortBy, throttle } from "lodash";
import { EventType, RoomType } from "matrix-js-sdk/src/@types/event";
import { Room } from "matrix-js-sdk/src/models/room";
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
+import { ISpaceSummaryRoom } from "matrix-js-sdk/src/@types/spaces";
import { AsyncStoreWithClient } from "./AsyncStoreWithClient";
import defaultDispatcher from "../dispatcher/dispatcher";
@@ -31,7 +32,6 @@ import { RoomNotificationStateStore } from "./notifications/RoomNotificationStat
import { DefaultTagID } from "./room-list/models";
import { EnhancedMap, mapDiff } from "../utils/maps";
import { setHasDiff } from "../utils/sets";
-import { ISpaceSummaryEvent, ISpaceSummaryRoom } from "../components/structures/SpaceRoomDirectory";
import RoomViewStore from "./RoomViewStore";
import { Action } from "../dispatcher/actions";
import { arrayHasDiff } from "../utils/arrays";
@@ -60,7 +60,13 @@ export interface ISuggestedRoom extends ISpaceSummaryRoom {
const MAX_SUGGESTED_ROOMS = 20;
-const homeSpaceKey = SettingsStore.getValue("feature_spaces.all_rooms") ? "ALL_ROOMS" : "HOME_SPACE";
+// All of these settings cause the page to reload and can be costly if read frequently, so read them here only
+const spacesEnabled = SettingsStore.getValue("feature_spaces");
+const spacesTweakAllRoomsEnabled = SettingsStore.getValue("feature_spaces.all_rooms");
+const spacesTweakSpaceMemberDMsEnabled = SettingsStore.getValue("feature_spaces.space_member_dms");
+const spacesTweakSpaceDMBadgesEnabled = SettingsStore.getValue("feature_spaces.space_dm_badges");
+
+const homeSpaceKey = spacesTweakAllRoomsEnabled ? "ALL_ROOMS" : "HOME_SPACE";
const getSpaceContextKey = (space?: Room) => `mx_space_context_${space?.roomId || homeSpaceKey}`;
const partitionSpacesAndRooms = (arr: Room[]): [Room[], Room[]] => { // [spaces, rooms]
@@ -220,10 +226,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient {
public fetchSuggestedRooms = async (space: Room, limit = MAX_SUGGESTED_ROOMS): Promise => {
try {
- const data: {
- rooms: ISpaceSummaryRoom[];
- events: ISpaceSummaryEvent[];
- } = await this.matrixClient.getSpaceSummary(space.roomId, 0, true, false, limit);
+ const data = await this.matrixClient.getSpaceSummary(space.roomId, 0, true, false, limit);
const viaMap = new EnhancedMap>();
data.events.forEach(ev => {
@@ -299,7 +302,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient {
}
public getSpaceFilteredRoomIds = (space: Room | null): Set => {
- if (!space && SettingsStore.getValue("feature_spaces.all_rooms")) {
+ if (!space && spacesTweakAllRoomsEnabled) {
return new Set(this.matrixClient.getVisibleRooms().map(r => r.roomId));
}
return this.spaceFilteredRooms.get(space?.roomId || HOME_SPACE) || new Set();
@@ -396,7 +399,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient {
};
private showInHomeSpace = (room: Room) => {
- if (SettingsStore.getValue("feature_spaces.all_rooms")) return true;
+ if (spacesTweakAllRoomsEnabled) 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
@@ -428,7 +431,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient {
const oldFilteredRooms = this.spaceFilteredRooms;
this.spaceFilteredRooms = new Map();
- if (!SettingsStore.getValue("feature_spaces.all_rooms")) {
+ if (!spacesTweakAllRoomsEnabled) {
// put all room invites in the Home Space
const invites = visibleRooms.filter(r => !r.isSpaceRoom() && r.getMyMembership() === "invite");
this.spaceFilteredRooms.set(HOME_SPACE, new Set(invites.map(room => room.roomId)));
@@ -455,7 +458,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient {
const roomIds = new Set(childRooms.map(r => r.roomId));
const space = this.matrixClient?.getRoom(spaceId);
- if (SettingsStore.getValue("feature_spaces.space_member_dms")) {
+ if (spacesTweakSpaceMemberDMsEnabled) {
// Add relevant DMs
space?.getMembers().forEach(member => {
if (member.membership !== "join" && member.membership !== "invite") return;
@@ -489,7 +492,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient {
// Update NotificationStates
this.getNotificationState(s)?.setRooms(visibleRooms.filter(room => {
if (roomIds.has(room.roomId)) {
- if (s !== HOME_SPACE && SettingsStore.getValue("feature_spaces.space_dm_badges")) return true;
+ if (s !== HOME_SPACE && spacesTweakSpaceDMBadgesEnabled) return true;
return !DMRoomMap.shared().getUserIdForRoomId(room.roomId)
|| RoomListStore.instance.getTagsForRoom(room).includes(DefaultTagID.Favourite);
@@ -588,7 +591,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient {
// TODO confirm this after implementing parenting behaviour
if (room.isSpaceRoom()) {
this.onSpaceUpdate();
- } else if (!SettingsStore.getValue("feature_spaces.all_rooms")) {
+ } else if (!spacesTweakAllRoomsEnabled) {
this.onRoomUpdate(room);
}
this.emit(room.roomId);
@@ -612,7 +615,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient {
if (order !== lastOrder) {
this.notifyIfOrderChanged();
}
- } else if (ev.getType() === EventType.Tag && !SettingsStore.getValue("feature_spaces.all_rooms")) {
+ } else if (ev.getType() === EventType.Tag && !spacesTweakAllRoomsEnabled) {
// 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 || {};
@@ -652,13 +655,13 @@ export class SpaceStoreClass extends AsyncStoreWithClient {
}
protected async onNotReady() {
- if (!SettingsStore.getValue("feature_spaces")) return;
+ if (!SpaceStore.spacesEnabled) return;
if (this.matrixClient) {
this.matrixClient.removeListener("Room", this.onRoom);
this.matrixClient.removeListener("Room.myMembership", this.onRoom);
this.matrixClient.removeListener("Room.accountData", this.onRoomAccountData);
this.matrixClient.removeListener("RoomState.events", this.onRoomState);
- if (!SettingsStore.getValue("feature_spaces.all_rooms")) {
+ if (!spacesTweakAllRoomsEnabled) {
this.matrixClient.removeListener("accountData", this.onAccountData);
}
}
@@ -666,12 +669,12 @@ export class SpaceStoreClass extends AsyncStoreWithClient {
}
protected async onReady() {
- if (!SettingsStore.getValue("feature_spaces")) return;
+ if (!spacesEnabled) return;
this.matrixClient.on("Room", this.onRoom);
this.matrixClient.on("Room.myMembership", this.onRoom);
this.matrixClient.on("Room.accountData", this.onRoomAccountData);
this.matrixClient.on("RoomState.events", this.onRoomState);
- if (!SettingsStore.getValue("feature_spaces.all_rooms")) {
+ if (!spacesTweakAllRoomsEnabled) {
this.matrixClient.on("accountData", this.onAccountData);
}
@@ -685,7 +688,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient {
}
protected async onAction(payload: ActionPayload) {
- if (!SettingsStore.getValue("feature_spaces")) return;
+ if (!spacesEnabled) return;
switch (payload.action) {
case "view_room": {
// Don't auto-switch rooms when reacting to a context-switch
@@ -699,7 +702,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient {
// as it will cause you to end up in the wrong room
this.setActiveSpace(room, false);
} else if (
- (!SettingsStore.getValue("feature_spaces.all_rooms") || this.activeSpace) &&
+ (!spacesTweakAllRoomsEnabled || this.activeSpace) &&
!this.getSpaceFilteredRoomIds(this.activeSpace).has(roomId)
) {
this.switchToRelatedSpace(roomId);
@@ -791,6 +794,11 @@ export class SpaceStoreClass extends AsyncStoreWithClient {
}
export default class SpaceStore {
+ public static spacesEnabled = spacesEnabled;
+ public static spacesTweakAllRoomsEnabled = spacesTweakAllRoomsEnabled;
+ public static spacesTweakSpaceMemberDMsEnabled = spacesTweakSpaceMemberDMsEnabled;
+ public static spacesTweakSpaceDMBadgesEnabled = spacesTweakSpaceDMBadgesEnabled;
+
private static internalInstance = new SpaceStoreClass();
public static get instance(): SpaceStoreClass {
diff --git a/src/stores/ThreepidInviteStore.ts b/src/stores/ThreepidInviteStore.ts
index 74a5f5f8ec..d0cf40941c 100644
--- a/src/stores/ThreepidInviteStore.ts
+++ b/src/stores/ThreepidInviteStore.ts
@@ -45,6 +45,16 @@ export interface IThreepidInvite {
inviterName: string;
}
+// Any data about the room that would normally come from the homeserver
+// but has been passed out-of-band, eg. the room name and avatar URL
+// from an email invite (a workaround for the fact that we can't
+// get this information from the HS using an email invite).
+export interface IOOBData {
+ name?: string; // The room's name
+ avatarUrl?: string; // The mxc:// avatar URL for the room
+ inviterName?: string; // The display name of the person who invited us to the room
+}
+
const STORAGE_PREFIX = "mx_threepid_invite_";
export default class ThreepidInviteStore extends EventEmitter {
diff --git a/src/stores/TypingStore.ts b/src/stores/TypingStore.ts
index 447f41c7ae..9781c93eb4 100644
--- a/src/stores/TypingStore.ts
+++ b/src/stores/TypingStore.ts
@@ -27,10 +27,10 @@ const TYPING_SERVER_TIMEOUT = 30000;
export default class TypingStore {
private typingStates: {
[roomId: string]: {
- isTyping: boolean,
- userTimer: Timer,
- serverTimer: Timer,
- },
+ isTyping: boolean;
+ userTimer: Timer;
+ serverTimer: Timer;
+ };
};
constructor() {
diff --git a/src/stores/UIStore.ts b/src/stores/UIStore.ts
index 86312f79d7..b89af2f7c0 100644
--- a/src/stores/UIStore.ts
+++ b/src/stores/UIStore.ts
@@ -15,7 +15,11 @@ limitations under the License.
*/
import EventEmitter from "events";
-import ResizeObserver from 'resize-observer-polyfill';
+// XXX: resize-observer-polyfill has types that now conflict with typescript's
+// own DOM types: https://github.com/que-etc/resize-observer-polyfill/issues/80
+// Using require here rather than import is a horrenous workaround. We should
+// be able to remove the polyfill once Safari 14 is released.
+const ResizeObserverPolyfill = require('resize-observer-polyfill'); // eslint-disable-line @typescript-eslint/no-var-requires
import ResizeObserverEntry from 'resize-observer-polyfill/src/ResizeObserverEntry';
export enum UI_EVENTS {
@@ -43,7 +47,7 @@ export default class UIStore extends EventEmitter {
// eslint-disable-next-line no-restricted-properties
this.windowHeight = window.innerHeight;
- this.resizeObserver = new ResizeObserver(this.resizeObserverCallback);
+ this.resizeObserver = new ResizeObserverPolyfill(this.resizeObserverCallback);
this.resizeObserver.observe(document.body);
}
diff --git a/src/stores/WidgetEchoStore.ts b/src/stores/WidgetEchoStore.ts
index 0b0be50541..d3ef9df023 100644
--- a/src/stores/WidgetEchoStore.ts
+++ b/src/stores/WidgetEchoStore.ts
@@ -26,8 +26,8 @@ import { WidgetType } from "../widgets/WidgetType";
class WidgetEchoStore extends EventEmitter {
private roomWidgetEcho: {
[roomId: string]: {
- [widgetId: string]: IWidget,
- },
+ [widgetId: string]: IWidget;
+ };
};
constructor() {
diff --git a/src/stores/room-list/MessagePreviewStore.ts b/src/stores/room-list/MessagePreviewStore.ts
index 99f24cfbe7..44ec173e08 100644
--- a/src/stores/room-list/MessagePreviewStore.ts
+++ b/src/stores/room-list/MessagePreviewStore.ts
@@ -63,7 +63,7 @@ const PREVIEWS = {
const MAX_EVENTS_BACKWARDS = 50;
// type merging ftw
-type TAG_ANY = "im.vector.any";
+type TAG_ANY = "im.vector.any"; // eslint-disable-line @typescript-eslint/naming-convention
const TAG_ANY: TAG_ANY = "im.vector.any";
interface IState {
diff --git a/src/stores/room-list/RoomListStore.ts b/src/stores/room-list/RoomListStore.ts
index e26c80bb2d..3913a2220f 100644
--- a/src/stores/room-list/RoomListStore.ts
+++ b/src/stores/room-list/RoomListStore.ts
@@ -35,6 +35,7 @@ import { NameFilterCondition } from "./filters/NameFilterCondition";
import { RoomNotificationStateStore } from "../notifications/RoomNotificationStateStore";
import { VisibilityProvider } from "./filters/VisibilityProvider";
import { SpaceWatcher } from "./SpaceWatcher";
+import SpaceStore from "../SpaceStore";
interface IState {
tagsEnabled?: boolean;
@@ -73,10 +74,11 @@ export class RoomListStoreClass extends AsyncStoreWithClient {
constructor() {
super(defaultDispatcher);
+ this.setMaxListeners(20); // CustomRoomTagStore + RoomList + LeftPanel + 8xRoomSubList + spares
}
private setupWatchers() {
- if (SettingsStore.getValue("feature_spaces")) {
+ if (SpaceStore.spacesEnabled) {
this.spaceWatcher = new SpaceWatcher(this);
} else {
this.tagWatcher = new TagWatcher(this);
@@ -130,8 +132,8 @@ export class RoomListStoreClass extends AsyncStoreWithClient {
// Update any settings here, as some may have happened before we were logically ready.
console.log("Regenerating room lists: Startup");
await this.readAndCacheSettingsFromStore();
- await this.regenerateAllLists({ trigger: false });
- await this.handleRVSUpdate({ trigger: false }); // fake an RVS update to adjust sticky room, if needed
+ this.regenerateAllLists({ trigger: false });
+ this.handleRVSUpdate({ trigger: false }); // fake an RVS update to adjust sticky room, if needed
this.updateFn.mark(); // we almost certainly want to trigger an update.
this.updateFn.trigger();
@@ -148,7 +150,7 @@ export class RoomListStoreClass extends AsyncStoreWithClient {
await this.updateState({
tagsEnabled,
});
- await this.updateAlgorithmInstances();
+ this.updateAlgorithmInstances();
}
/**
@@ -156,23 +158,23 @@ export class RoomListStoreClass extends AsyncStoreWithClient {
* @param trigger Set to false to prevent a list update from being sent. Should only
* be used if the calling code will manually trigger the update.
*/
- private async handleRVSUpdate({ trigger = true }) {
+ private handleRVSUpdate({ trigger = true }) {
if (!this.matrixClient) return; // We assume there won't be RVS updates without a client
const activeRoomId = RoomViewStore.getRoomId();
if (!activeRoomId && this.algorithm.stickyRoom) {
- await this.algorithm.setStickyRoom(null);
+ this.algorithm.setStickyRoom(null);
} else if (activeRoomId) {
const activeRoom = this.matrixClient.getRoom(activeRoomId);
if (!activeRoom) {
console.warn(`${activeRoomId} is current in RVS but missing from client - clearing sticky room`);
- await this.algorithm.setStickyRoom(null);
+ this.algorithm.setStickyRoom(null);
} else if (activeRoom !== this.algorithm.stickyRoom) {
if (SettingsStore.getValue("advancedRoomListLogging")) {
// TODO: Remove debug: https://github.com/vector-im/element-web/issues/14602
console.log(`Changing sticky room to ${activeRoomId}`);
}
- await this.algorithm.setStickyRoom(activeRoom);
+ this.algorithm.setStickyRoom(activeRoom);
}
}
@@ -224,7 +226,7 @@ export class RoomListStoreClass extends AsyncStoreWithClient {
console.log("Regenerating room lists: Settings changed");
await this.readAndCacheSettingsFromStore();
- await this.regenerateAllLists({ trigger: false }); // regenerate the lists now
+ this.regenerateAllLists({ trigger: false }); // regenerate the lists now
this.updateFn.trigger();
}
}
@@ -366,7 +368,7 @@ export class RoomListStoreClass extends AsyncStoreWithClient {
// TODO: Remove debug: https://github.com/vector-im/element-web/issues/14602
console.log(`[RoomListDebug] Clearing sticky room due to room upgrade`);
}
- await this.algorithm.setStickyRoom(null);
+ this.algorithm.setStickyRoom(null);
}
// Note: we hit the algorithm instead of our handleRoomUpdate() function to
@@ -375,7 +377,7 @@ export class RoomListStoreClass extends AsyncStoreWithClient {
// TODO: Remove debug: https://github.com/vector-im/element-web/issues/14602
console.log(`[RoomListDebug] Removing previous room from room list`);
}
- await this.algorithm.handleRoomUpdate(prevRoom, RoomUpdateCause.RoomRemoved);
+ this.algorithm.handleRoomUpdate(prevRoom, RoomUpdateCause.RoomRemoved);
}
}
@@ -431,7 +433,7 @@ export class RoomListStoreClass extends AsyncStoreWithClient {
return; // don't do anything on new/moved rooms which ought not to be shown
}
- const shouldUpdate = await this.algorithm.handleRoomUpdate(room, cause);
+ const shouldUpdate = this.algorithm.handleRoomUpdate(room, cause);
if (shouldUpdate) {
if (SettingsStore.getValue("advancedRoomListLogging")) {
// TODO: Remove debug: https://github.com/vector-im/element-web/issues/14602
@@ -460,13 +462,13 @@ export class RoomListStoreClass extends AsyncStoreWithClient {
// Reset the sticky room before resetting the known rooms so the algorithm
// doesn't freak out.
- await this.algorithm.setStickyRoom(null);
- await this.algorithm.setKnownRooms(rooms);
+ this.algorithm.setStickyRoom(null);
+ this.algorithm.setKnownRooms(rooms);
// Set the sticky room back, if needed, now that we have updated the store.
// This will use relative stickyness to the new room set.
if (stickyIsStillPresent) {
- await this.algorithm.setStickyRoom(currentSticky);
+ this.algorithm.setStickyRoom(currentSticky);
}
// Finally, mark an update and resume updates from the algorithm
@@ -475,12 +477,12 @@ export class RoomListStoreClass extends AsyncStoreWithClient {
}
public async setTagSorting(tagId: TagID, sort: SortAlgorithm) {
- await this.setAndPersistTagSorting(tagId, sort);
+ this.setAndPersistTagSorting(tagId, sort);
this.updateFn.trigger();
}
- private async setAndPersistTagSorting(tagId: TagID, sort: SortAlgorithm) {
- await this.algorithm.setTagSorting(tagId, sort);
+ private setAndPersistTagSorting(tagId: TagID, sort: SortAlgorithm) {
+ this.algorithm.setTagSorting(tagId, sort);
// TODO: Per-account? https://github.com/vector-im/element-web/issues/14114
localStorage.setItem(`mx_tagSort_${tagId}`, sort);
}
@@ -518,13 +520,13 @@ export class RoomListStoreClass extends AsyncStoreWithClient {
return tagSort;
}
- public async setListOrder(tagId: TagID, order: ListAlgorithm) {
- await this.setAndPersistListOrder(tagId, order);
+ public setListOrder(tagId: TagID, order: ListAlgorithm) {
+ this.setAndPersistListOrder(tagId, order);
this.updateFn.trigger();
}
- private async setAndPersistListOrder(tagId: TagID, order: ListAlgorithm) {
- await this.algorithm.setListOrdering(tagId, order);
+ private setAndPersistListOrder(tagId: TagID, order: ListAlgorithm) {
+ this.algorithm.setListOrdering(tagId, order);
// TODO: Per-account? https://github.com/vector-im/element-web/issues/14114
localStorage.setItem(`mx_listOrder_${tagId}`, order);
}
@@ -561,7 +563,7 @@ export class RoomListStoreClass extends AsyncStoreWithClient {
return listOrder;
}
- private async updateAlgorithmInstances() {
+ private updateAlgorithmInstances() {
// We'll require an update, so mark for one. Marking now also prevents the calls
// to setTagSorting and setListOrder from causing triggers.
this.updateFn.mark();
@@ -574,10 +576,10 @@ export class RoomListStoreClass extends AsyncStoreWithClient {
const listOrder = this.calculateListOrder(tag);
if (tagSort !== definedSort) {
- await this.setAndPersistTagSorting(tag, tagSort);
+ this.setAndPersistTagSorting(tag, tagSort);
}
if (listOrder !== definedOrder) {
- await this.setAndPersistListOrder(tag, listOrder);
+ this.setAndPersistListOrder(tag, listOrder);
}
}
}
@@ -608,9 +610,7 @@ export class RoomListStoreClass extends AsyncStoreWithClient {
// if spaces are enabled only consider the prefilter conditions when there are no runtime conditions
// for the search all spaces feature
- if (this.prefilterConditions.length > 0
- && (!SettingsStore.getValue("feature_spaces") || !this.filterConditions.length)
- ) {
+ if (this.prefilterConditions.length > 0 && (!SpaceStore.spacesEnabled || !this.filterConditions.length)) {
rooms = rooms.filter(r => {
for (const filter of this.prefilterConditions) {
if (!filter.isVisible(r)) {
@@ -632,7 +632,7 @@ export class RoomListStoreClass extends AsyncStoreWithClient {
* @param trigger Set to false to prevent a list update from being sent. Should only
* be used if the calling code will manually trigger the update.
*/
- public async regenerateAllLists({ trigger = true }) {
+ public regenerateAllLists({ trigger = true }) {
console.warn("Regenerating all room lists");
const rooms = this.getPlausibleRooms();
@@ -656,8 +656,8 @@ export class RoomListStoreClass extends AsyncStoreWithClient {
RoomListLayoutStore.instance.ensureLayoutExists(tagId);
}
- await this.algorithm.populateTags(sorts, orders);
- await this.algorithm.setKnownRooms(rooms);
+ this.algorithm.populateTags(sorts, orders);
+ this.algorithm.setKnownRooms(rooms);
this.initialListsGenerated = true;
@@ -682,7 +682,7 @@ export class RoomListStoreClass extends AsyncStoreWithClient {
} else {
this.filterConditions.push(filter);
// Runtime filters with spaces disable prefiltering for the search all spaces feature
- if (SettingsStore.getValue("feature_spaces")) {
+ if (SpaceStore.spacesEnabled) {
// this has to be awaited so that `setKnownRooms` is called in time for the `addFilterCondition` below
// this way the runtime filters are only evaluated on one dataset and not both.
await this.recalculatePrefiltering();
@@ -715,7 +715,7 @@ export class RoomListStoreClass extends AsyncStoreWithClient {
this.algorithm.removeFilterCondition(filter);
}
// Runtime filters with spaces disable prefiltering for the search all spaces feature
- if (SettingsStore.getValue("feature_spaces")) {
+ if (SpaceStore.spacesEnabled) {
promise = this.recalculatePrefiltering();
}
}
diff --git a/src/stores/room-list/SpaceWatcher.ts b/src/stores/room-list/SpaceWatcher.ts
index a1f7786578..1cec612e6f 100644
--- a/src/stores/room-list/SpaceWatcher.ts
+++ b/src/stores/room-list/SpaceWatcher.ts
@@ -19,7 +19,6 @@ import { Room } from "matrix-js-sdk/src/models/room";
import { RoomListStoreClass } from "./RoomListStore";
import { SpaceFilterCondition } from "./filters/SpaceFilterCondition";
import SpaceStore, { UPDATE_SELECTED_SPACE } from "../SpaceStore";
-import SettingsStore from "../../settings/SettingsStore";
/**
* Watches for changes in spaces to manage the filter on the provided RoomListStore
@@ -29,7 +28,7 @@ export class SpaceWatcher {
private activeSpace: Room = SpaceStore.instance.activeSpace;
constructor(private store: RoomListStoreClass) {
- if (!SettingsStore.getValue("feature_spaces.all_rooms")) {
+ if (!SpaceStore.spacesTweakAllRoomsEnabled) {
this.filter = new SpaceFilterCondition();
this.updateFilter();
store.addFilter(this.filter);
@@ -41,7 +40,7 @@ export class SpaceWatcher {
this.activeSpace = activeSpace;
if (this.filter) {
- if (activeSpace || !SettingsStore.getValue("feature_spaces.all_rooms")) {
+ if (activeSpace || !SpaceStore.spacesTweakAllRoomsEnabled) {
this.updateFilter();
} else {
this.store.removeFilter(this.filter);
diff --git a/src/stores/room-list/algorithms/Algorithm.ts b/src/stores/room-list/algorithms/Algorithm.ts
index 024c484c41..eb6ffe6dcf 100644
--- a/src/stores/room-list/algorithms/Algorithm.ts
+++ b/src/stores/room-list/algorithms/Algorithm.ts
@@ -16,8 +16,9 @@ limitations under the License.
import { Room } from "matrix-js-sdk/src/models/room";
import { isNullOrUndefined } from "matrix-js-sdk/src/utils";
-import DMRoomMap from "../../../utils/DMRoomMap";
import { EventEmitter } from "events";
+
+import DMRoomMap from "../../../utils/DMRoomMap";
import { arrayDiff, arrayHasDiff } from "../../../utils/arrays";
import { DefaultTagID, RoomUpdateCause, TagID } from "../models";
import {
@@ -34,6 +35,7 @@ import { OrderingAlgorithm } from "./list-ordering/OrderingAlgorithm";
import { getListAlgorithmInstance } from "./list-ordering";
import SettingsStore from "../../../settings/SettingsStore";
import { VisibilityProvider } from "../filters/VisibilityProvider";
+import SpaceStore from "../../SpaceStore";
/**
* Fired when the Algorithm has determined a list has been updated.
@@ -121,8 +123,12 @@ export class Algorithm extends EventEmitter {
* Awaitable version of the sticky room setter.
* @param val The new room to sticky.
*/
- public async setStickyRoom(val: Room) {
- await this.updateStickyRoom(val);
+ public setStickyRoom(val: Room) {
+ try {
+ this.updateStickyRoom(val);
+ } catch (e) {
+ console.warn("Failed to update sticky room", e);
+ }
}
public getTagSorting(tagId: TagID): SortAlgorithm {
@@ -130,13 +136,13 @@ export class Algorithm extends EventEmitter {
return this.sortAlgorithms[tagId];
}
- public async setTagSorting(tagId: TagID, sort: SortAlgorithm) {
+ public setTagSorting(tagId: TagID, sort: SortAlgorithm) {
if (!tagId) throw new Error("Tag ID must be defined");
if (!sort) throw new Error("Algorithm must be defined");
this.sortAlgorithms[tagId] = sort;
const algorithm: OrderingAlgorithm = this.algorithms[tagId];
- await algorithm.setSortAlgorithm(sort);
+ algorithm.setSortAlgorithm(sort);
this._cachedRooms[tagId] = algorithm.orderedRooms;
this.recalculateFilteredRoomsForTag(tagId); // update filter to re-sort the list
this.recalculateStickyRoom(tagId); // update sticky room to make sure it appears if needed
@@ -147,7 +153,7 @@ export class Algorithm extends EventEmitter {
return this.listAlgorithms[tagId];
}
- public async setListOrdering(tagId: TagID, order: ListAlgorithm) {
+ public setListOrdering(tagId: TagID, order: ListAlgorithm) {
if (!tagId) throw new Error("Tag ID must be defined");
if (!order) throw new Error("Algorithm must be defined");
this.listAlgorithms[tagId] = order;
@@ -155,7 +161,7 @@ export class Algorithm extends EventEmitter {
const algorithm = getListAlgorithmInstance(order, tagId, this.sortAlgorithms[tagId]);
this.algorithms[tagId] = algorithm;
- await algorithm.setRooms(this._cachedRooms[tagId]);
+ algorithm.setRooms(this._cachedRooms[tagId]);
this._cachedRooms[tagId] = algorithm.orderedRooms;
this.recalculateFilteredRoomsForTag(tagId); // update filter to re-sort the list
this.recalculateStickyRoom(tagId); // update sticky room to make sure it appears if needed
@@ -182,31 +188,25 @@ export class Algorithm extends EventEmitter {
}
}
- private async handleFilterChange() {
- await this.recalculateFilteredRooms();
+ private handleFilterChange() {
+ this.recalculateFilteredRooms();
// re-emit the update so the list store can fire an off-cycle update if needed
if (this.updatesInhibited) return;
this.emit(FILTER_CHANGED);
}
- private async updateStickyRoom(val: Room) {
- try {
- return await this.doUpdateStickyRoom(val);
- } finally {
- this._lastStickyRoom = null; // clear to indicate we're done changing
- }
+ private updateStickyRoom(val: Room) {
+ this.doUpdateStickyRoom(val);
+ this._lastStickyRoom = null; // clear to indicate we're done changing
}
- private async doUpdateStickyRoom(val: Room) {
- if (SettingsStore.getValue("feature_spaces") && val?.isSpaceRoom() && val.getMyMembership() !== "invite") {
+ private doUpdateStickyRoom(val: Room) {
+ if (SpaceStore.spacesEnabled && val?.isSpaceRoom() && val.getMyMembership() !== "invite") {
// no-op sticky rooms for spaces - they're effectively virtual rooms
val = null;
}
- // Note throughout: We need async so we can wait for handleRoomUpdate() to do its thing,
- // otherwise we risk duplicating rooms.
-
if (val && !VisibilityProvider.instance.isRoomVisible(val)) {
val = null; // the room isn't visible - lie to the rest of this function
}
@@ -222,7 +222,7 @@ export class Algorithm extends EventEmitter {
this._stickyRoom = null; // clear before we go to update the algorithm
// Lie to the algorithm and re-add the room to the algorithm
- await this.handleRoomUpdate(stickyRoom, RoomUpdateCause.NewRoom);
+ this.handleRoomUpdate(stickyRoom, RoomUpdateCause.NewRoom);
return;
}
return;
@@ -268,10 +268,10 @@ export class Algorithm extends EventEmitter {
// referential checks as the references can differ through the lifecycle.
if (lastStickyRoom && lastStickyRoom.room && lastStickyRoom.room.roomId !== val.roomId) {
// Lie to the algorithm and re-add the room to the algorithm
- await this.handleRoomUpdate(lastStickyRoom.room, RoomUpdateCause.NewRoom);
+ this.handleRoomUpdate(lastStickyRoom.room, RoomUpdateCause.NewRoom);
}
// Lie to the algorithm and remove the room from it's field of view
- await this.handleRoomUpdate(val, RoomUpdateCause.RoomRemoved);
+ this.handleRoomUpdate(val, RoomUpdateCause.RoomRemoved);
// Check for tag & position changes while we're here. We also check the room to ensure
// it is still the same room.
@@ -461,9 +461,8 @@ export class Algorithm extends EventEmitter {
* them.
* @param {ITagSortingMap} tagSortingMap The tags to generate.
* @param {IListOrderingMap} listOrderingMap The ordering of those tags.
- * @returns {Promise<*>} A promise which resolves when complete.
*/
- public async populateTags(tagSortingMap: ITagSortingMap, listOrderingMap: IListOrderingMap): Promise {
+ public populateTags(tagSortingMap: ITagSortingMap, listOrderingMap: IListOrderingMap): void {
if (!tagSortingMap) throw new Error(`Sorting map cannot be null or empty`);
if (!listOrderingMap) throw new Error(`Ordering ma cannot be null or empty`);
if (arrayHasDiff(Object.keys(tagSortingMap), Object.keys(listOrderingMap))) {
@@ -512,9 +511,8 @@ export class Algorithm extends EventEmitter {
* Seeds the Algorithm with a set of rooms. The algorithm will discard all
* previously known information and instead use these rooms instead.
* @param {Room[]} rooms The rooms to force the algorithm to use.
- * @returns {Promise<*>} A promise which resolves when complete.
*/
- public async setKnownRooms(rooms: Room[]): Promise {
+ public setKnownRooms(rooms: Room[]): void {
if (isNullOrUndefined(rooms)) throw new Error(`Array of rooms cannot be null`);
if (!this.sortAlgorithms) throw new Error(`Cannot set known rooms without a tag sorting map`);
@@ -528,7 +526,7 @@ export class Algorithm extends EventEmitter {
// Before we go any further we need to clear (but remember) the sticky room to
// avoid accidentally duplicating it in the list.
const oldStickyRoom = this._stickyRoom;
- await this.updateStickyRoom(null);
+ if (oldStickyRoom) this.updateStickyRoom(null);
this.rooms = rooms;
@@ -540,7 +538,7 @@ export class Algorithm extends EventEmitter {
// If we can avoid doing work, do so.
if (!rooms.length) {
- await this.generateFreshTags(newTags); // just in case it wants to do something
+ this.generateFreshTags(newTags); // just in case it wants to do something
this.cachedRooms = newTags;
return;
}
@@ -577,7 +575,7 @@ export class Algorithm extends EventEmitter {
}
}
- await this.generateFreshTags(newTags);
+ this.generateFreshTags(newTags);
this.cachedRooms = newTags; // this recalculates the filtered rooms for us
this.updateTagsFromCache();
@@ -586,7 +584,7 @@ export class Algorithm extends EventEmitter {
// it was. It's entirely possible that it changed lists though, so if it did then
// we also have to update the position of it.
if (oldStickyRoom && oldStickyRoom.room) {
- await this.updateStickyRoom(oldStickyRoom.room);
+ this.updateStickyRoom(oldStickyRoom.room);
if (this._stickyRoom && this._stickyRoom.room) { // just in case the update doesn't go according to plan
if (this._stickyRoom.tag !== oldStickyRoom.tag) {
// We put the sticky room at the top of the list to treat it as an obvious tag change.
@@ -651,16 +649,15 @@ export class Algorithm extends EventEmitter {
* @param {ITagMap} updatedTagMap The tag map which needs populating. Each tag
* will already have the rooms which belong to it - they just need ordering. Must
* be mutated in place.
- * @returns {Promise<*>} A promise which resolves when complete.
*/
- private async generateFreshTags(updatedTagMap: ITagMap): Promise {
+ private generateFreshTags(updatedTagMap: ITagMap): void {
if (!this.algorithms) throw new Error("Not ready: no algorithms to determine tags from");
for (const tag of Object.keys(updatedTagMap)) {
const algorithm: OrderingAlgorithm = this.algorithms[tag];
if (!algorithm) throw new Error(`No algorithm for ${tag}`);
- await algorithm.setRooms(updatedTagMap[tag]);
+ algorithm.setRooms(updatedTagMap[tag]);
updatedTagMap[tag] = algorithm.orderedRooms;
}
}
@@ -672,11 +669,10 @@ export class Algorithm extends EventEmitter {
* may no-op this request if no changes are required.
* @param {Room} room The room which might have affected sorting.
* @param {RoomUpdateCause} cause The reason for the update being triggered.
- * @returns {Promise} A promise which resolve to true or false
- * depending on whether or not getOrderedRooms() should be called after
- * processing.
+ * @returns {Promise} A boolean of whether or not getOrderedRooms()
+ * should be called after processing.
*/
- public async handleRoomUpdate(room: Room, cause: RoomUpdateCause): Promise {
+ public handleRoomUpdate(room: Room, cause: RoomUpdateCause): boolean {
if (SettingsStore.getValue("advancedRoomListLogging")) {
// TODO: Remove debug: https://github.com/vector-im/element-web/issues/14602
console.log(`Handle room update for ${room.roomId} called with cause ${cause}`);
@@ -684,9 +680,9 @@ export class Algorithm extends EventEmitter {
if (!this.algorithms) throw new Error("Not ready: no algorithms to determine tags from");
// Note: check the isSticky against the room ID just in case the reference is wrong
- const isSticky = this._stickyRoom && this._stickyRoom.room && this._stickyRoom.room.roomId === room.roomId;
+ const isSticky = this._stickyRoom?.room?.roomId === room.roomId;
if (cause === RoomUpdateCause.NewRoom) {
- const isForLastSticky = this._lastStickyRoom && this._lastStickyRoom.room === room;
+ const isForLastSticky = this._lastStickyRoom?.room === room;
const roomTags = this.roomIdsToTags[room.roomId];
const hasTags = roomTags && roomTags.length > 0;
@@ -743,7 +739,7 @@ export class Algorithm extends EventEmitter {
}
const algorithm: OrderingAlgorithm = this.algorithms[rmTag];
if (!algorithm) throw new Error(`No algorithm for ${rmTag}`);
- await algorithm.handleRoomUpdate(room, RoomUpdateCause.RoomRemoved);
+ algorithm.handleRoomUpdate(room, RoomUpdateCause.RoomRemoved);
this._cachedRooms[rmTag] = algorithm.orderedRooms;
this.recalculateFilteredRoomsForTag(rmTag); // update filter to re-sort the list
this.recalculateStickyRoom(rmTag); // update sticky room to make sure it moves if needed
@@ -755,7 +751,7 @@ export class Algorithm extends EventEmitter {
}
const algorithm: OrderingAlgorithm = this.algorithms[addTag];
if (!algorithm) throw new Error(`No algorithm for ${addTag}`);
- await algorithm.handleRoomUpdate(room, RoomUpdateCause.NewRoom);
+ algorithm.handleRoomUpdate(room, RoomUpdateCause.NewRoom);
this._cachedRooms[addTag] = algorithm.orderedRooms;
}
@@ -788,7 +784,7 @@ export class Algorithm extends EventEmitter {
};
} else {
// We have to clear the lock as the sticky room change will trigger updates.
- await this.setStickyRoom(room);
+ this.setStickyRoom(room);
}
}
}
@@ -851,7 +847,7 @@ export class Algorithm extends EventEmitter {
const algorithm: OrderingAlgorithm = this.algorithms[tag];
if (!algorithm) throw new Error(`No algorithm for ${tag}`);
- await algorithm.handleRoomUpdate(room, cause);
+ algorithm.handleRoomUpdate(room, cause);
this._cachedRooms[tag] = algorithm.orderedRooms;
// Flag that we've done something
diff --git a/src/stores/room-list/algorithms/list-ordering/ImportanceAlgorithm.ts b/src/stores/room-list/algorithms/list-ordering/ImportanceAlgorithm.ts
index 80bdf74afb..1d35df331d 100644
--- a/src/stores/room-list/algorithms/list-ordering/ImportanceAlgorithm.ts
+++ b/src/stores/room-list/algorithms/list-ordering/ImportanceAlgorithm.ts
@@ -94,15 +94,15 @@ export class ImportanceAlgorithm extends OrderingAlgorithm {
return state.color;
}
- public async setRooms(rooms: Room[]): Promise {
+ public setRooms(rooms: Room[]): void {
if (this.sortingAlgorithm === SortAlgorithm.Manual) {
- this.cachedOrderedRooms = await sortRoomsWithAlgorithm(rooms, this.tagId, this.sortingAlgorithm);
+ this.cachedOrderedRooms = sortRoomsWithAlgorithm(rooms, this.tagId, this.sortingAlgorithm);
} else {
// Every other sorting type affects the categories, not the whole tag.
const categorized = this.categorizeRooms(rooms);
for (const category of Object.keys(categorized)) {
const roomsToOrder = categorized[category];
- categorized[category] = await sortRoomsWithAlgorithm(roomsToOrder, this.tagId, this.sortingAlgorithm);
+ categorized[category] = sortRoomsWithAlgorithm(roomsToOrder, this.tagId, this.sortingAlgorithm);
}
const newlyOrganized: Room[] = [];
@@ -118,12 +118,12 @@ export class ImportanceAlgorithm extends OrderingAlgorithm {
}
}
- private async handleSplice(room: Room, cause: RoomUpdateCause): Promise {
+ private handleSplice(room: Room, cause: RoomUpdateCause): boolean {
if (cause === RoomUpdateCause.NewRoom) {
const category = this.getRoomCategory(room);
this.alterCategoryPositionBy(category, 1, this.indices);
this.cachedOrderedRooms.splice(this.indices[category], 0, room); // splice in the new room (pre-adjusted)
- await this.sortCategory(category);
+ this.sortCategory(category);
} else if (cause === RoomUpdateCause.RoomRemoved) {
const roomIdx = this.getRoomIndex(room);
if (roomIdx === -1) {
@@ -141,55 +141,49 @@ export class ImportanceAlgorithm extends OrderingAlgorithm {
return true;
}
- public async handleRoomUpdate(room: Room, cause: RoomUpdateCause): Promise {
- try {
- await this.updateLock.acquireAsync();
-
- if (cause === RoomUpdateCause.NewRoom || cause === RoomUpdateCause.RoomRemoved) {
- return this.handleSplice(room, cause);
- }
-
- if (cause !== RoomUpdateCause.Timeline && cause !== RoomUpdateCause.ReadReceipt) {
- throw new Error(`Unsupported update cause: ${cause}`);
- }
-
- const category = this.getRoomCategory(room);
- if (this.sortingAlgorithm === SortAlgorithm.Manual) {
- return; // Nothing to do here.
- }
-
- const roomIdx = this.getRoomIndex(room);
- if (roomIdx === -1) {
- throw new Error(`Room ${room.roomId} has no index in ${this.tagId}`);
- }
-
- // Try to avoid doing array operations if we don't have to: only move rooms within
- // the categories if we're jumping categories
- const oldCategory = this.getCategoryFromIndices(roomIdx, this.indices);
- if (oldCategory !== category) {
- // Move the room and update the indices
- this.moveRoomIndexes(1, oldCategory, category, this.indices);
- this.cachedOrderedRooms.splice(roomIdx, 1); // splice out the old index (fixed position)
- this.cachedOrderedRooms.splice(this.indices[category], 0, room); // splice in the new room (pre-adjusted)
- // Note: if moveRoomIndexes() is called after the splice then the insert operation
- // will happen in the wrong place. Because we would have already adjusted the index
- // for the category, we don't need to determine how the room is moving in the list.
- // If we instead tried to insert before updating the indices, we'd have to determine
- // whether the room was moving later (towards IDLE) or earlier (towards RED) from its
- // current position, as it'll affect the category's start index after we remove the
- // room from the array.
- }
-
- // Sort the category now that we've dumped the room in
- await this.sortCategory(category);
-
- return true; // change made
- } finally {
- await this.updateLock.release();
+ public handleRoomUpdate(room: Room, cause: RoomUpdateCause): boolean {
+ if (cause === RoomUpdateCause.NewRoom || cause === RoomUpdateCause.RoomRemoved) {
+ return this.handleSplice(room, cause);
}
+
+ if (cause !== RoomUpdateCause.Timeline && cause !== RoomUpdateCause.ReadReceipt) {
+ throw new Error(`Unsupported update cause: ${cause}`);
+ }
+
+ const category = this.getRoomCategory(room);
+ if (this.sortingAlgorithm === SortAlgorithm.Manual) {
+ return; // Nothing to do here.
+ }
+
+ const roomIdx = this.getRoomIndex(room);
+ if (roomIdx === -1) {
+ throw new Error(`Room ${room.roomId} has no index in ${this.tagId}`);
+ }
+
+ // Try to avoid doing array operations if we don't have to: only move rooms within
+ // the categories if we're jumping categories
+ const oldCategory = this.getCategoryFromIndices(roomIdx, this.indices);
+ if (oldCategory !== category) {
+ // Move the room and update the indices
+ this.moveRoomIndexes(1, oldCategory, category, this.indices);
+ this.cachedOrderedRooms.splice(roomIdx, 1); // splice out the old index (fixed position)
+ this.cachedOrderedRooms.splice(this.indices[category], 0, room); // splice in the new room (pre-adjusted)
+ // Note: if moveRoomIndexes() is called after the splice then the insert operation
+ // will happen in the wrong place. Because we would have already adjusted the index
+ // for the category, we don't need to determine how the room is moving in the list.
+ // If we instead tried to insert before updating the indices, we'd have to determine
+ // whether the room was moving later (towards IDLE) or earlier (towards RED) from its
+ // current position, as it'll affect the category's start index after we remove the
+ // room from the array.
+ }
+
+ // Sort the category now that we've dumped the room in
+ this.sortCategory(category);
+
+ return true; // change made
}
- private async sortCategory(category: NotificationColor) {
+ private sortCategory(category: NotificationColor) {
// This should be relatively quick because the room is usually inserted at the top of the
// category, and most popular sorting algorithms will deal with trying to keep the active
// room at the top/start of the category. For the few algorithms that will have to move the
@@ -201,7 +195,7 @@ export class ImportanceAlgorithm extends OrderingAlgorithm {
const startIdx = this.indices[category];
const numSort = nextCategoryStartIdx - startIdx; // splice() returns up to the max, so MAX_SAFE_INT is fine
const unsortedSlice = this.cachedOrderedRooms.splice(startIdx, numSort);
- const sorted = await sortRoomsWithAlgorithm(unsortedSlice, this.tagId, this.sortingAlgorithm);
+ const sorted = sortRoomsWithAlgorithm(unsortedSlice, this.tagId, this.sortingAlgorithm);
this.cachedOrderedRooms.splice(startIdx, 0, ...sorted);
}
diff --git a/src/stores/room-list/algorithms/list-ordering/NaturalAlgorithm.ts b/src/stores/room-list/algorithms/list-ordering/NaturalAlgorithm.ts
index cc2a28d892..91182dee16 100644
--- a/src/stores/room-list/algorithms/list-ordering/NaturalAlgorithm.ts
+++ b/src/stores/room-list/algorithms/list-ordering/NaturalAlgorithm.ts
@@ -29,42 +29,32 @@ export class NaturalAlgorithm extends OrderingAlgorithm {
super(tagId, initialSortingAlgorithm);
}
- public async setRooms(rooms: Room[]): Promise {
- this.cachedOrderedRooms = await sortRoomsWithAlgorithm(rooms, this.tagId, this.sortingAlgorithm);
+ public setRooms(rooms: Room[]): void {
+ this.cachedOrderedRooms = sortRoomsWithAlgorithm(rooms, this.tagId, this.sortingAlgorithm);
}
- public async handleRoomUpdate(room, cause): Promise {
- try {
- await this.updateLock.acquireAsync();
-
- const isSplice = cause === RoomUpdateCause.NewRoom || cause === RoomUpdateCause.RoomRemoved;
- const isInPlace = cause === RoomUpdateCause.Timeline || cause === RoomUpdateCause.ReadReceipt;
- if (!isSplice && !isInPlace) {
- throw new Error(`Unsupported update cause: ${cause}`);
- }
-
- if (cause === RoomUpdateCause.NewRoom) {
- this.cachedOrderedRooms.push(room);
- } else if (cause === RoomUpdateCause.RoomRemoved) {
- const idx = this.getRoomIndex(room);
- if (idx >= 0) {
- this.cachedOrderedRooms.splice(idx, 1);
- } else {
- console.warn(`Tried to remove unknown room from ${this.tagId}: ${room.roomId}`);
- }
- }
-
- // TODO: Optimize this to avoid useless operations: https://github.com/vector-im/element-web/issues/14457
- // For example, we can skip updates to alphabetic (sometimes) and manually ordered tags
- this.cachedOrderedRooms = await sortRoomsWithAlgorithm(
- this.cachedOrderedRooms,
- this.tagId,
- this.sortingAlgorithm,
- );
-
- return true;
- } finally {
- await this.updateLock.release();
+ public handleRoomUpdate(room, cause): boolean {
+ const isSplice = cause === RoomUpdateCause.NewRoom || cause === RoomUpdateCause.RoomRemoved;
+ const isInPlace = cause === RoomUpdateCause.Timeline || cause === RoomUpdateCause.ReadReceipt;
+ if (!isSplice && !isInPlace) {
+ throw new Error(`Unsupported update cause: ${cause}`);
}
+
+ if (cause === RoomUpdateCause.NewRoom) {
+ this.cachedOrderedRooms.push(room);
+ } else if (cause === RoomUpdateCause.RoomRemoved) {
+ const idx = this.getRoomIndex(room);
+ if (idx >= 0) {
+ this.cachedOrderedRooms.splice(idx, 1);
+ } else {
+ console.warn(`Tried to remove unknown room from ${this.tagId}: ${room.roomId}`);
+ }
+ }
+
+ // TODO: Optimize this to avoid useless operations: https://github.com/vector-im/element-web/issues/14457
+ // For example, we can skip updates to alphabetic (sometimes) and manually ordered tags
+ this.cachedOrderedRooms = sortRoomsWithAlgorithm(this.cachedOrderedRooms, this.tagId, this.sortingAlgorithm);
+
+ return true;
}
}
diff --git a/src/stores/room-list/algorithms/list-ordering/OrderingAlgorithm.ts b/src/stores/room-list/algorithms/list-ordering/OrderingAlgorithm.ts
index c47a35523c..9d7b5f9ddb 100644
--- a/src/stores/room-list/algorithms/list-ordering/OrderingAlgorithm.ts
+++ b/src/stores/room-list/algorithms/list-ordering/OrderingAlgorithm.ts
@@ -17,7 +17,6 @@ limitations under the License.
import { Room } from "matrix-js-sdk/src/models/room";
import { RoomUpdateCause, TagID } from "../../models";
import { SortAlgorithm } from "../models";
-import AwaitLock from "await-lock";
/**
* Represents a list ordering algorithm. Subclasses should populate the
@@ -26,7 +25,6 @@ import AwaitLock from "await-lock";
export abstract class OrderingAlgorithm {
protected cachedOrderedRooms: Room[];
protected sortingAlgorithm: SortAlgorithm;
- protected readonly updateLock = new AwaitLock();
protected constructor(protected tagId: TagID, initialSortingAlgorithm: SortAlgorithm) {
// noinspection JSIgnoredPromiseFromCall
@@ -45,21 +43,20 @@ export abstract class OrderingAlgorithm {
* @param newAlgorithm The new algorithm. Must be defined.
* @returns Resolves when complete.
*/
- public async setSortAlgorithm(newAlgorithm: SortAlgorithm) {
+ public setSortAlgorithm(newAlgorithm: SortAlgorithm) {
if (!newAlgorithm) throw new Error("A sorting algorithm must be defined");
this.sortingAlgorithm = newAlgorithm;
// Force regeneration of the rooms
- await this.setRooms(this.orderedRooms);
+ this.setRooms(this.orderedRooms);
}
/**
* Sets the rooms the algorithm should be handling, implying a reconstruction
* of the ordering.
* @param rooms The rooms to use going forward.
- * @returns Resolves when complete.
*/
- public abstract setRooms(rooms: Room[]): Promise;
+ public abstract setRooms(rooms: Room[]): void;
/**
* Handle a room update. The Algorithm will only call this for causes which
@@ -69,7 +66,7 @@ export abstract class OrderingAlgorithm {
* @param cause The cause of the update.
* @returns True if the update requires the Algorithm to update the presentation layers.
*/
- public abstract handleRoomUpdate(room: Room, cause: RoomUpdateCause): Promise;
+ public abstract handleRoomUpdate(room: Room, cause: RoomUpdateCause): boolean;
protected getRoomIndex(room: Room): number {
let roomIdx = this.cachedOrderedRooms.indexOf(room);
diff --git a/src/stores/room-list/algorithms/tag-sorting/AlphabeticAlgorithm.ts b/src/stores/room-list/algorithms/tag-sorting/AlphabeticAlgorithm.ts
index b016a4256c..45f6eaf843 100644
--- a/src/stores/room-list/algorithms/tag-sorting/AlphabeticAlgorithm.ts
+++ b/src/stores/room-list/algorithms/tag-sorting/AlphabeticAlgorithm.ts
@@ -23,7 +23,7 @@ import { compare } from "../../../../utils/strings";
* Sorts rooms according to the browser's determination of alphabetic.
*/
export class AlphabeticAlgorithm implements IAlgorithm {
- public async sortRooms(rooms: Room[], tagId: TagID): Promise {
+ public sortRooms(rooms: Room[], tagId: TagID): Room[] {
return rooms.sort((a, b) => {
return compare(a.name, b.name);
});
diff --git a/src/stores/room-list/algorithms/tag-sorting/IAlgorithm.ts b/src/stores/room-list/algorithms/tag-sorting/IAlgorithm.ts
index 6c22ee0c9c..588bbbffc9 100644
--- a/src/stores/room-list/algorithms/tag-sorting/IAlgorithm.ts
+++ b/src/stores/room-list/algorithms/tag-sorting/IAlgorithm.ts
@@ -25,7 +25,7 @@ export interface IAlgorithm {
* Sorts the given rooms according to the sorting rules of the algorithm.
* @param {Room[]} rooms The rooms to sort.
* @param {TagID} tagId The tag ID in which the rooms are being sorted.
- * @returns {Promise} Resolves to the sorted rooms.
+ * @returns {Room[]} Returns the sorted rooms.
*/
- sortRooms(rooms: Room[], tagId: TagID): Promise;
+ sortRooms(rooms: Room[], tagId: TagID): Room[];
}
diff --git a/src/stores/room-list/algorithms/tag-sorting/ManualAlgorithm.ts b/src/stores/room-list/algorithms/tag-sorting/ManualAlgorithm.ts
index b8c0357633..9be8ba5262 100644
--- a/src/stores/room-list/algorithms/tag-sorting/ManualAlgorithm.ts
+++ b/src/stores/room-list/algorithms/tag-sorting/ManualAlgorithm.ts
@@ -22,7 +22,7 @@ import { IAlgorithm } from "./IAlgorithm";
* Sorts rooms according to the tag's `order` property on the room.
*/
export class ManualAlgorithm implements IAlgorithm {
- public async sortRooms(rooms: Room[], tagId: TagID): Promise {
+ public sortRooms(rooms: Room[], tagId: TagID): Room[] {
const getOrderProp = (r: Room) => r.tags[tagId].order || 0;
return rooms.sort((a, b) => {
return getOrderProp(a) - getOrderProp(b);
diff --git a/src/stores/room-list/algorithms/tag-sorting/RecentAlgorithm.ts b/src/stores/room-list/algorithms/tag-sorting/RecentAlgorithm.ts
index 49cfd9e520..f47458d1b1 100644
--- a/src/stores/room-list/algorithms/tag-sorting/RecentAlgorithm.ts
+++ b/src/stores/room-list/algorithms/tag-sorting/RecentAlgorithm.ts
@@ -97,7 +97,7 @@ export const sortRooms = (rooms: Room[]): Room[] => {
* useful to the user.
*/
export class RecentAlgorithm implements IAlgorithm {
- public async sortRooms(rooms: Room[], tagId: TagID): Promise {
+ public sortRooms(rooms: Room[], tagId: TagID): Room[] {
return sortRooms(rooms);
}
}
diff --git a/src/stores/room-list/algorithms/tag-sorting/index.ts b/src/stores/room-list/algorithms/tag-sorting/index.ts
index c22865f5ba..368c76f111 100644
--- a/src/stores/room-list/algorithms/tag-sorting/index.ts
+++ b/src/stores/room-list/algorithms/tag-sorting/index.ts
@@ -46,8 +46,8 @@ export function getSortingAlgorithmInstance(algorithm: SortAlgorithm): IAlgorith
* @param {Room[]} rooms The rooms to sort.
* @param {TagID} tagId The tag in which the sorting is occurring.
* @param {SortAlgorithm} algorithm The algorithm to use for sorting.
- * @returns {Promise} Resolves to the sorted rooms.
+ * @returns {Room[]} Returns the sorted rooms.
*/
-export function sortRoomsWithAlgorithm(rooms: Room[], tagId: TagID, algorithm: SortAlgorithm): Promise {
+export function sortRoomsWithAlgorithm(rooms: Room[], tagId: TagID, algorithm: SortAlgorithm): Room[] {
return getSortingAlgorithmInstance(algorithm).sortRooms(rooms, tagId);
}
diff --git a/src/stores/room-list/filters/VisibilityProvider.ts b/src/stores/room-list/filters/VisibilityProvider.ts
index a6c55226b0..f63b622053 100644
--- a/src/stores/room-list/filters/VisibilityProvider.ts
+++ b/src/stores/room-list/filters/VisibilityProvider.ts
@@ -18,7 +18,7 @@ import { Room } from "matrix-js-sdk/src/models/room";
import CallHandler from "../../../CallHandler";
import { RoomListCustomisations } from "../../../customisations/RoomList";
import VoipUserMapper from "../../../VoipUserMapper";
-import SettingsStore from "../../../settings/SettingsStore";
+import SpaceStore from "../../SpaceStore";
export class VisibilityProvider {
private static internalInstance: VisibilityProvider;
@@ -50,7 +50,7 @@ export class VisibilityProvider {
}
// hide space rooms as they'll be shown in the SpacePanel
- if (SettingsStore.getValue("feature_spaces") && room.isSpaceRoom()) {
+ if (SpaceStore.spacesEnabled && room.isSpaceRoom()) {
return false;
}
diff --git a/src/stores/widgets/StopGapWidget.ts b/src/stores/widgets/StopGapWidget.ts
index 36791d3dd9..24869b5edc 100644
--- a/src/stores/widgets/StopGapWidget.ts
+++ b/src/stores/widgets/StopGapWidget.ts
@@ -51,7 +51,7 @@ import ThemeWatcher from "../../settings/watchers/ThemeWatcher";
import { getCustomTheme } from "../../theme";
import CountlyAnalytics from "../../CountlyAnalytics";
import { ElementWidgetCapabilities } from "./ElementWidgetCapabilities";
-import { MatrixEvent, IEvent } from "matrix-js-sdk/src/models/event";
+import { MatrixEvent } from "matrix-js-sdk/src/models/event";
import { ELEMENT_CLIENT_ID } from "../../identifiers";
import { getUserLanguage } from "../../languageHandler";
@@ -415,7 +415,7 @@ export class StopGapWidget extends EventEmitter {
private feedEvent(ev: MatrixEvent) {
if (!this.messaging) return;
- const raw = ev.event as IEvent;
+ const raw = ev.getEffectiveEvent();
this.messaging.feedEvent(raw).catch(e => {
console.error("Error sending event to widget: ", e);
});
diff --git a/src/stores/widgets/StopGapWidgetDriver.ts b/src/stores/widgets/StopGapWidgetDriver.ts
index fd064bae61..13cd260ef0 100644
--- a/src/stores/widgets/StopGapWidgetDriver.ts
+++ b/src/stores/widgets/StopGapWidgetDriver.ts
@@ -159,12 +159,12 @@ export class StopGapWidgetDriver extends WidgetDriver {
if (results.length >= limit) break;
const ev = events[i];
- if (ev.getType() !== eventType) continue;
+ if (ev.getType() !== eventType || ev.isState()) continue;
if (eventType === EventType.RoomMessage && msgtype && msgtype !== ev.getContent()['msgtype']) continue;
results.push(ev);
}
- return results.map(e => e.event);
+ return results.map(e => e.getEffectiveEvent());
}
public async readStateEvents(eventType: string, stateKey: string | undefined, limit: number): Promise