) }
{ otherVariables.map((item, index) =>
diff --git a/src/Avatar.ts b/src/Avatar.ts
index 198d4162a0..c0ecb19eaf 100644
--- a/src/Avatar.ts
+++ b/src/Avatar.ts
@@ -18,6 +18,7 @@ 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";
@@ -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) {
diff --git a/src/CallHandler.tsx b/src/CallHandler.tsx
index f90854ee64..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',
@@ -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 {
@@ -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;
}
@@ -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") }
{ _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"),
diff --git a/src/DeviceListener.ts b/src/DeviceListener.ts
index d033063677..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
@@ -163,32 +164,32 @@ export default class DeviceListener {
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();
@@ -206,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;
@@ -235,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);
@@ -256,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 a37b7f0ac9..b125ddeeb5 100644
--- a/src/HtmlUtils.tsx
+++ b/src/HtmlUtils.tsx
@@ -33,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";
@@ -79,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 {
diff --git a/src/IdentityAuthClient.js b/src/IdentityAuthClient.js
index 447c5edd30..e91e1d72cf 100644
--- a/src/IdentityAuthClient.js
+++ b/src/IdentityAuthClient.js
@@ -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/MatrixClientPeg.ts b/src/MatrixClientPeg.ts
index e9364b1b47..f43351aab2 100644
--- a/src/MatrixClientPeg.ts
+++ b/src/MatrixClientPeg.ts
@@ -105,7 +105,7 @@ 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'
@@ -300,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/Modal.tsx b/src/Modal.tsx
index 55fc871d67..1e84078ddb 100644
--- a/src/Modal.tsx
+++ b/src/Modal.tsx
@@ -378,7 +378,7 @@ export class ModalManager {
const dialog = (
{ _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"),
},
);
@@ -522,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
@@ -1069,7 +1069,7 @@ export const Commands = [
command: "msg",
description: _td("Sends a message to the given user"),
args: "",
- runFn: function(_, args) {
+ runFn: function(roomId, args) {
if (args) {
// matches the first whitespace delimited group and then the rest of the string
const matches = args.match(/^(\S+?)(?: +(.*))?$/s);
diff --git a/src/TextForEvent.tsx b/src/TextForEvent.tsx
index 0056a37c85..7bad8eb50e 100644
--- a/src/TextForEvent.tsx
+++ b/src/TextForEvent.tsx
@@ -14,7 +14,6 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React from 'react';
-import { MatrixClientPeg } from './MatrixClientPeg';
import { _t } from './languageHandler';
import * as Roles from './Roles';
import { isValid3pidInvite } from "./RoomInvite";
@@ -318,90 +317,6 @@ function textForCanonicalAliasEvent(ev: MatrixEvent): () => string | null {
});
}
-function textForCallAnswerEvent(event: MatrixEvent): () => string | null {
- return () => {
- const senderName = event.sender ? event.sender.name : _t('Someone');
- const supported = MatrixClientPeg.get().supportsVoip() ? '' : _t('(not supported by this browser)');
- return _t('%(senderName)s answered the call.', { senderName }) + ' ' + supported;
- };
-}
-
-function textForCallHangupEvent(event: MatrixEvent): () => string | null {
- const getSenderName = () => event.sender ? event.sender.name : _t('Someone');
- const eventContent = event.getContent();
- let getReason = () => "";
- if (!MatrixClientPeg.get().supportsVoip()) {
- getReason = () => _t('(not supported by this browser)');
- } else if (eventContent.reason) {
- if (eventContent.reason === "ice_failed") {
- // We couldn't establish a connection at all
- getReason = () => _t('(could not connect media)');
- } else if (eventContent.reason === "ice_timeout") {
- // We established a connection but it died
- getReason = () => _t('(connection failed)');
- } else if (eventContent.reason === "user_media_failed") {
- // The other side couldn't open capture devices
- getReason = () => _t("(their device couldn't start the camera / microphone)");
- } else if (eventContent.reason === "unknown_error") {
- // An error code the other side doesn't have a way to express
- // (as opposed to an error code they gave but we don't know about,
- // in which case we show the error code)
- getReason = () => _t("(an error occurred)");
- } else if (eventContent.reason === "invite_timeout") {
- getReason = () => _t('(no answer)');
- } else if (eventContent.reason === "user hangup" || eventContent.reason === "user_hangup") {
- // workaround for https://github.com/vector-im/element-web/issues/5178
- // it seems Android randomly sets a reason of "user hangup" which is
- // interpreted as an error code :(
- // https://github.com/vector-im/riot-android/issues/2623
- // Also the correct hangup code as of VoIP v1 (with underscore)
- getReason = () => '';
- } else {
- getReason = () => _t('(unknown failure: %(reason)s)', { reason: eventContent.reason });
- }
- }
- return () => _t('%(senderName)s ended the call.', { senderName: getSenderName() }) + ' ' + getReason();
-}
-
-function textForCallRejectEvent(event: MatrixEvent): () => string | null {
- return () => {
- const senderName = event.sender ? event.sender.name : _t('Someone');
- return _t('%(senderName)s declined the call.', { senderName });
- };
-}
-
-function textForCallInviteEvent(event: MatrixEvent): () => string | null {
- const getSenderName = () => event.sender ? event.sender.name : _t('Someone');
- // FIXME: Find a better way to determine this from the event?
- let isVoice = true;
- if (event.getContent().offer && event.getContent().offer.sdp &&
- event.getContent().offer.sdp.indexOf('m=video') !== -1) {
- isVoice = false;
- }
- const isSupported = MatrixClientPeg.get().supportsVoip();
-
- // This ladder could be reduced down to a couple string variables, however other languages
- // can have a hard time translating those strings. In an effort to make translations easier
- // and more accurate, we break out the string-based variables to a couple booleans.
- if (isVoice && isSupported) {
- return () => _t("%(senderName)s placed a voice call.", {
- senderName: getSenderName(),
- });
- } else if (isVoice && !isSupported) {
- return () => _t("%(senderName)s placed a voice call. (not supported by this browser)", {
- senderName: getSenderName(),
- });
- } else if (!isVoice && isSupported) {
- return () => _t("%(senderName)s placed a video call.", {
- senderName: getSenderName(),
- });
- } else if (!isVoice && !isSupported) {
- return () => _t("%(senderName)s placed a video call. (not supported by this browser)", {
- senderName: getSenderName(),
- });
- }
-}
-
function textForThreePidInviteEvent(event: MatrixEvent): () => string | null {
const senderName = event.sender ? event.sender.name : event.getSender();
@@ -652,10 +567,6 @@ interface IHandlers {
const handlers: IHandlers = {
'm.room.message': textForMessageEvent,
- 'm.call.invite': textForCallInviteEvent,
- 'm.call.answer': textForCallAnswerEvent,
- 'm.call.hangup': textForCallHangupEvent,
- 'm.call.reject': textForCallRejectEvent,
};
const stateHandlers: IHandlers = {
diff --git a/src/accessibility/KeyboardShortcuts.tsx b/src/accessibility/KeyboardShortcuts.tsx
index c5cf85facd..9cc7b60c99 100644
--- a/src/accessibility/KeyboardShortcuts.tsx
+++ b/src/accessibility/KeyboardShortcuts.tsx
@@ -370,8 +370,8 @@ export const toggleDialog = () => {
const sections = categoryOrder.map(category => {
const list = shortcuts[category];
return
diff --git a/src/components/views/dialogs/DeactivateAccountDialog.tsx b/src/components/views/dialogs/DeactivateAccountDialog.tsx
index b2ac849314..7221df222f 100644
--- a/src/components/views/dialogs/DeactivateAccountDialog.tsx
+++ b/src/components/views/dialogs/DeactivateAccountDialog.tsx
@@ -172,11 +172,11 @@ export default class DeactivateAccountDialog extends React.Component;
}
- let auth =
{_t("Loading...")}
;
+ let auth =
{ _t("Loading...") }
;
if (this.state.authData && this.state.authEnabled) {
auth = (
- {this.state.bodyText}
+ { this.state.bodyText }
- {_t(
+ { _t(
"Please forget all messages I have sent when my account is deactivated " +
"(Warning: this will cause future users to see an incomplete view " +
"of conversations)",
{},
{ b: (sub) => { sub } },
- )}
+ ) }
- {error}
- {auth}
+ { error }
+ { auth }
- {_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("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(
+ { _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 }
@@ -267,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;
@@ -290,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;
@@ -317,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 }
);
@@ -328,12 +328,12 @@ class DMRoomTile extends React.PureComponent {
return (
{ _t(
"Use an identity server to invite by email. " +
"Use the default (%(defaultIdentityServerName)s) " +
"or manage in Settings.",
@@ -1249,20 +1249,20 @@ 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 })
;
}
}
@@ -100,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") }
- {_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 },
+ }) }
- {_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.",
- )}
+ ) }
{ _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 681817ca86..b1cc9c773d 100644
--- a/src/components/views/elements/EventListSummary.tsx
+++ b/src/components/views/elements/EventListSummary.tsx
@@ -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 (
-
;
@@ -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 5230042c38..972dac909a 100644
--- a/src/components/views/elements/SpellCheckLanguagesDropdown.tsx
+++ b/src/components/views/elements/SpellCheckLanguagesDropdown.tsx
@@ -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,10 +76,8 @@ export default class SpellCheckLanguagesDropdown extends React.Component
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 dc38055c10..fd8abc0dbe 100644
--- a/src/components/views/settings/SetIdServer.tsx
+++ b/src/components/views/settings/SetIdServer.tsx
@@ -134,7 +134,7 @@ export default class SetIdServer extends React.Component {
{ _t("Checking server") }
;
} else if (this.state.error) {
- return {this.state.error};
+ return { this.state.error };
} else {
return null;
}
@@ -193,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"),
@@ -224,10 +224,10 @@ export default class SetIdServer extends React.Component {
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.") }
),
@@ -243,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"),
});
@@ -278,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(
"You are still sharing your personal data on the identity " +
"server .", {}, messageElements,
- )}
-
{_t(
+ ) }
+
{ _t(
"We recommend that you remove your email addresses and phone numbers " +
"from the identity server before disconnecting.",
- )}
+ ) }
;
danger = true;
button = _t("Disconnect anyway");
@@ -361,13 +361,13 @@ export default class SetIdServer extends React.Component {
"You are currently using to discover and be discoverable by " +
"existing contacts you know. You can change your identity server below.",
{},
- { server: sub => {abbreviateUrl(idServerUrl)} },
+ { server: sub => { abbreviateUrl(idServerUrl) } },
);
if (this.props.missingTerms) {
bodyText = _t(
"If you don't want to use to discover and be discoverable by existing " +
"contacts you know, enter another identity server below.",
- {}, { server: sub => {abbreviateUrl(idServerUrl)} },
+ {}, { server: sub => { abbreviateUrl(idServerUrl) } },
);
}
} else {
@@ -399,9 +399,9 @@ export default class SetIdServer extends React.Component {
discoButtonContent = ;
}
discoSection =
);
@@ -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 78d8fecf3b..88bc2046ce 100644
--- a/src/components/views/settings/tabs/room/SecurityRoomSettingsTab.tsx
+++ b/src/components/views/settings/tabs/room/SecurityRoomSettingsTab.tsx
@@ -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.') }
- {_t("Who can read history?")}
+ { _t("Who can read history?") }
);
}
@@ -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 (
- {threepidSection}
+ { 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.',
{
@@ -208,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 }) }
@@ -235,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.",
- )}
+ ) }
@@ -348,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.") }
@@ -377,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/CallView.tsx b/src/components/views/voip/CallView.tsx
index 64c101a284..8bdd6e0f55 100644
--- a/src/components/views/voip/CallView.tsx
+++ b/src/components/views/voip/CallView.tsx
@@ -465,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 =
);
}
@@ -615,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/createRoom.ts b/src/createRoom.ts
index afbeb7fdb9..effc6ec1ac 100644
--- a/src/createRoom.ts
+++ b/src/createRoom.ts
@@ -17,6 +17,7 @@ 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';
@@ -247,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);
@@ -324,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/editor/parts.ts b/src/editor/parts.ts
index 351df5062f..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"] {
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/en_EN.json b/src/i18n/strings/en_EN.json
index ae97c0787f..e1f3dcc828 100644
--- a/src/i18n/strings/en_EN.json
+++ b/src/i18n/strings/en_EN.json
@@ -541,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.",
@@ -824,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",
@@ -1147,8 +1134,8 @@
"New keyword": "New keyword",
"Global": "Global",
"Mentions & keywords": "Mentions & keywords",
- "On": "On",
"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.",
@@ -1246,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",
@@ -1658,7 +1649,7 @@
"Favourite": "Favourite",
"Low Priority": "Low Priority",
"Invite People": "Invite People",
- "Copy Link": "Copy Link",
+ "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.",
@@ -1852,6 +1843,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 +1864,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.",
@@ -1994,7 +1999,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",
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/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 b629ddafd8..fd84f479ad 100644
--- a/src/rageshake/submit-rageshake.ts
+++ b/src/rageshake/submit-rageshake.ts
@@ -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/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 830ea9e32e..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,
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/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 521d124bad..b6f91bf835 100644
--- a/src/stores/RightPanelStore.ts
+++ b/src/stores/RightPanelStore.ts
@@ -144,7 +144,7 @@ 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
diff --git a/src/stores/RoomViewStore.tsx b/src/stores/RoomViewStore.tsx
index 1a85ff59b1..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'
@@ -325,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.") }
,
button: _t("Update"),
onFinished: (update) => {
if (update && PlatformPeg.get()) {
diff --git a/src/usercontent/index.js b/src/usercontent/index.js
index 13f38cc31a..c03126ec80 100644
--- a/src/usercontent/index.js
+++ b/src/usercontent/index.js
@@ -1,6 +1,13 @@
+let hasCalled = false;
function remoteRender(event) {
const data = event.data;
+ // If we're handling secondary calls, start from scratch
+ if (hasCalled) {
+ document.body.replaceWith(document.createElement("BODY"));
+ }
+ hasCalled = true;
+
const img = document.createElement("span"); // we'll mask it as an image
img.id = "img";
diff --git a/src/utils/AutoDiscoveryUtils.tsx b/src/utils/AutoDiscoveryUtils.tsx
index 6c0c8b2e13..bad87db2b9 100644
--- a/src/utils/AutoDiscoveryUtils.tsx
+++ b/src/utils/AutoDiscoveryUtils.tsx
@@ -90,7 +90,7 @@ export default class AutoDiscoveryUtils {
href="https://github.com/vector-im/element-web/blob/master/docs/config.md"
target="_blank"
rel="noreferrer noopener"
- >{sub};
+ >{ sub };
},
},
);
@@ -130,8 +130,8 @@ export default class AutoDiscoveryUtils {
serverErrorIsFatal: isFatalError,
serverDeadError: (
- {title}
-
{body}
+ { title }
+
{ body }
),
};
diff --git a/src/utils/ErrorUtils.tsx b/src/utils/ErrorUtils.tsx
index c39ee21f09..4253564ffd 100644
--- a/src/utils/ErrorUtils.tsx
+++ b/src/utils/ErrorUtils.tsx
@@ -44,7 +44,7 @@ export function messageForResourceLimitError(
const linkSub = sub => {
if (adminContact) {
- return {sub};
+ return { sub };
} else {
return sub;
}
@@ -76,12 +76,12 @@ export function messageForSyncError(err: MatrixError | Error): ReactNode {
},
);
return
-
{limitError}
-
{adminContact}
+
{ limitError }
+
{ adminContact }
;
} else {
return
- {_t("Unable to connect to Homeserver. Retrying...")}
+ { _t("Unable to connect to Homeserver. Retrying...") }
;
}
}
diff --git a/src/utils/EventUtils.ts b/src/utils/EventUtils.ts
index 849e546485..e2af1c7464 100644
--- a/src/utils/EventUtils.ts
+++ b/src/utils/EventUtils.ts
@@ -111,14 +111,19 @@ export function getEventDisplayInfo(mxEvent: MatrixEvent): {
let tileHandler = getHandlerTile(mxEvent);
// Info messages are basically information about commands processed on a room
- let isBubbleMessage = eventType.startsWith("m.key.verification") ||
- (eventType === EventType.RoomMessage && msgtype && msgtype.startsWith("m.key.verification")) ||
- (eventType === EventType.RoomCreate) ||
- (eventType === EventType.RoomEncryption) ||
- (tileHandler === "messages.MJitsiWidgetEvent");
+ let isBubbleMessage = (
+ eventType.startsWith("m.key.verification") ||
+ (eventType === EventType.RoomMessage && msgtype && msgtype.startsWith("m.key.verification")) ||
+ (eventType === EventType.RoomCreate) ||
+ (eventType === EventType.RoomEncryption) ||
+ (eventType === EventType.CallInvite) ||
+ (tileHandler === "messages.MJitsiWidgetEvent")
+ );
let isInfoMessage = (
- !isBubbleMessage && eventType !== EventType.RoomMessage &&
- eventType !== EventType.Sticker && eventType !== EventType.RoomCreate
+ !isBubbleMessage &&
+ eventType !== EventType.RoomMessage &&
+ eventType !== EventType.Sticker &&
+ eventType !== EventType.RoomCreate
);
// If we're showing hidden events in the timeline, we should use the
diff --git a/src/utils/LazyValue.ts b/src/utils/LazyValue.ts
new file mode 100644
index 0000000000..9cdcda489a
--- /dev/null
+++ b/src/utils/LazyValue.ts
@@ -0,0 +1,59 @@
+/*
+Copyright 2021 The Matrix.org Foundation C.I.C.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+/**
+ * Utility class for lazily getting a variable.
+ */
+export class LazyValue {
+ private val: T;
+ private prom: Promise;
+ private done = false;
+
+ public constructor(private getFn: () => Promise) {
+ }
+
+ /**
+ * Whether or not a cached value is present.
+ */
+ public get present(): boolean {
+ // we use a tracking variable just in case the final value is falsey
+ return this.done;
+ }
+
+ /**
+ * Gets the value without invoking a get. May be undefined until the
+ * value is fetched properly.
+ */
+ public get cachedValue(): T {
+ return this.val;
+ }
+
+ /**
+ * Gets a promise which resolves to the value, eventually.
+ */
+ public get value(): Promise {
+ if (this.prom) return this.prom;
+ this.prom = this.getFn();
+
+ // Fork the promise chain to avoid accidentally making it return undefined always.
+ this.prom.then(v => {
+ this.val = v;
+ this.done = true;
+ });
+
+ return this.prom;
+ }
+}
diff --git a/src/utils/MediaEventHelper.ts b/src/utils/MediaEventHelper.ts
new file mode 100644
index 0000000000..cf34d5dea4
--- /dev/null
+++ b/src/utils/MediaEventHelper.ts
@@ -0,0 +1,119 @@
+/*
+Copyright 2021 The Matrix.org Foundation C.I.C.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+import { MatrixEvent } from "matrix-js-sdk/src";
+import { LazyValue } from "./LazyValue";
+import { Media, mediaFromContent } from "../customisations/Media";
+import { decryptFile } from "./DecryptFile";
+import { IMediaEventContent } from "../customisations/models/IMediaEventContent";
+import { IDestroyable } from "./IDestroyable";
+import { EventType, MsgType } from "matrix-js-sdk/src/@types/event";
+
+// TODO: We should consider caching the blobs. https://github.com/vector-im/element-web/issues/17192
+
+export class MediaEventHelper implements IDestroyable {
+ // Either an HTTP or Object URL (when encrypted) to the media.
+ public readonly sourceUrl: LazyValue;
+ public readonly thumbnailUrl: LazyValue;
+
+ // Either the raw or decrypted (when encrypted) contents of the file.
+ public readonly sourceBlob: LazyValue;
+ public readonly thumbnailBlob: LazyValue;
+
+ public readonly media: Media;
+
+ public constructor(private event: MatrixEvent) {
+ this.sourceUrl = new LazyValue(this.prepareSourceUrl);
+ this.thumbnailUrl = new LazyValue(this.prepareThumbnailUrl);
+ this.sourceBlob = new LazyValue(this.fetchSource);
+ this.thumbnailBlob = new LazyValue(this.fetchThumbnail);
+
+ this.media = mediaFromContent(this.event.getContent());
+ }
+
+ public get fileName(): string {
+ return this.event.getContent().body || "download";
+ }
+
+ public destroy() {
+ if (this.media.isEncrypted) {
+ if (this.sourceUrl.present) URL.revokeObjectURL(this.sourceUrl.cachedValue);
+ if (this.thumbnailUrl.present) URL.revokeObjectURL(this.thumbnailUrl.cachedValue);
+ }
+ }
+
+ private prepareSourceUrl = async () => {
+ if (this.media.isEncrypted) {
+ const blob = await this.sourceBlob.value;
+ return URL.createObjectURL(blob);
+ } else {
+ return this.media.srcHttp;
+ }
+ };
+
+ private prepareThumbnailUrl = async () => {
+ if (this.media.isEncrypted) {
+ const blob = await this.thumbnailBlob.value;
+ return URL.createObjectURL(blob);
+ } else {
+ return this.media.thumbnailHttp;
+ }
+ };
+
+ private fetchSource = () => {
+ if (this.media.isEncrypted) {
+ return decryptFile(this.event.getContent().file);
+ }
+ return this.media.downloadSource().then(r => r.blob());
+ };
+
+ private fetchThumbnail = () => {
+ if (!this.media.hasThumbnail) return Promise.resolve(null);
+
+ if (this.media.isEncrypted) {
+ const content = this.event.getContent();
+ if (content.info?.thumbnail_file) {
+ return decryptFile(content.info.thumbnail_file);
+ } else {
+ // "Should never happen"
+ console.warn("Media claims to have thumbnail and is encrypted, but no thumbnail_file found");
+ return Promise.resolve(null);
+ }
+ }
+
+ return fetch(this.media.thumbnailHttp).then(r => r.blob());
+ };
+
+ public static isEligible(event: MatrixEvent): boolean {
+ if (!event) return false;
+ if (event.isRedacted()) return false;
+ if (event.getType() === EventType.Sticker) return true;
+ if (event.getType() !== EventType.RoomMessage) return false;
+
+ const content = event.getContent();
+ const mediaMsgTypes: string[] = [
+ MsgType.Video,
+ MsgType.Audio,
+ MsgType.Image,
+ MsgType.File,
+ ];
+ if (mediaMsgTypes.includes(content.msgtype)) return true;
+ if (typeof(content.url) === 'string') return true;
+
+ // Finally, it's probably not media
+ return false;
+ }
+}
diff --git a/src/widgets/CapabilityText.tsx b/src/widgets/CapabilityText.tsx
index 304a340247..63e34eea7a 100644
--- a/src/widgets/CapabilityText.tsx
+++ b/src/widgets/CapabilityText.tsx
@@ -20,7 +20,7 @@ import { EventType, MsgType } from "matrix-js-sdk/src/@types/event";
import { ElementWidgetCapabilities } from "../stores/widgets/ElementWidgetCapabilities";
import React from "react";
-type GENERIC_WIDGET_KIND = "generic";
+type GENERIC_WIDGET_KIND = "generic"; // eslint-disable-line @typescript-eslint/naming-convention
const GENERIC_WIDGET_KIND: GENERIC_WIDGET_KIND = "generic";
interface ISendRecvStaticCapText {
@@ -176,7 +176,7 @@ export class CapabilityText {
primary: _t("Send %(eventType)s events as you in this room", {
eventType: eventCap.eventType,
}, {
- b: sub => {sub},
+ b: sub => { sub },
}),
byline: CapabilityText.bylineFor(eventCap),
};
@@ -185,7 +185,7 @@ export class CapabilityText {
primary: _t("See %(eventType)s events posted to this room", {
eventType: eventCap.eventType,
}, {
- b: sub => {sub},
+ b: sub => { sub },
}),
byline: CapabilityText.bylineFor(eventCap),
};
@@ -196,7 +196,7 @@ export class CapabilityText {
primary: _t("Send %(eventType)s events as you in your active room", {
eventType: eventCap.eventType,
}, {
- b: sub => {sub},
+ b: sub => { sub },
}),
byline: CapabilityText.bylineFor(eventCap),
};
@@ -205,7 +205,7 @@ export class CapabilityText {
primary: _t("See %(eventType)s events posted to your active room", {
eventType: eventCap.eventType,
}, {
- b: sub => {sub},
+ b: sub => { sub },
}),
byline: CapabilityText.bylineFor(eventCap),
};
@@ -216,7 +216,7 @@ export class CapabilityText {
// We don't have enough context to render this capability specially, so we'll present it as-is
return {
primary: _t("The %(capability)s capability", { capability }, {
- b: sub => {sub},
+ b: sub => { sub },
}),
};
}
@@ -324,13 +324,13 @@ export class CapabilityText {
primary = _t("Send %(msgtype)s messages as you in this room", {
msgtype: eventCap.keyStr,
}, {
- b: sub => {sub},
+ b: sub => { sub },
});
} else {
primary = _t("Send %(msgtype)s messages as you in your active room", {
msgtype: eventCap.keyStr,
}, {
- b: sub => {sub},
+ b: sub => { sub },
});
}
} else {
@@ -338,13 +338,13 @@ export class CapabilityText {
primary = _t("See %(msgtype)s messages posted to this room", {
msgtype: eventCap.keyStr,
}, {
- b: sub => {sub},
+ b: sub => { sub },
});
} else {
primary = _t("See %(msgtype)s messages posted to your active room", {
msgtype: eventCap.keyStr,
}, {
- b: sub => {sub},
+ b: sub => { sub },
});
}
}
diff --git a/test/accessibility/RovingTabIndex-test.js b/test/accessibility/RovingTabIndex-test.js
index bead5c3158..72d4253710 100644
--- a/test/accessibility/RovingTabIndex-test.js
+++ b/test/accessibility/RovingTabIndex-test.js
@@ -48,7 +48,7 @@ const button4 = d;
describe("RovingTabIndex", () => {
it("RovingTabIndexProvider renders children as expected", () => {
const wrapper = mount(
- {() =>