Merge branch 'develop' into johannes/find-myself
This commit is contained in:
commit
d0e9331f07
612 changed files with 3608 additions and 2769 deletions
2
src/@types/global.d.ts
vendored
2
src/@types/global.d.ts
vendored
|
@ -218,7 +218,7 @@ declare global {
|
|||
processorCtor: (new (options?: AudioWorkletNodeOptions) => AudioWorkletProcessor) & {
|
||||
parameterDescriptors?: AudioParamDescriptor[];
|
||||
},
|
||||
);
|
||||
): void;
|
||||
|
||||
// eslint-disable-next-line no-var
|
||||
var grecaptcha:
|
||||
|
|
65
src/@types/opus-recorder.d.ts
vendored
Normal file
65
src/@types/opus-recorder.d.ts
vendored
Normal file
|
@ -0,0 +1,65 @@
|
|||
/*
|
||||
Copyright 2023 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
declare module "opus-recorder/dist/recorder.min.js" {
|
||||
export default class Recorder {
|
||||
public static isRecordingSupported(): boolean;
|
||||
|
||||
public constructor(config: {
|
||||
bufferLength?: number;
|
||||
encoderApplication?: number;
|
||||
encoderFrameSize?: number;
|
||||
encoderPath?: string;
|
||||
encoderSampleRate?: number;
|
||||
encoderBitRate?: number;
|
||||
maxFramesPerPage?: number;
|
||||
mediaTrackConstraints?: boolean;
|
||||
monitorGain?: number;
|
||||
numberOfChannels?: number;
|
||||
recordingGain?: number;
|
||||
resampleQuality?: number;
|
||||
streamPages?: boolean;
|
||||
wavBitDepth?: number;
|
||||
sourceNode?: MediaStreamAudioSourceNode;
|
||||
encoderComplexity?: number;
|
||||
});
|
||||
|
||||
public ondataavailable?(data: ArrayBuffer): void;
|
||||
|
||||
public readonly encodedSamplePosition: number;
|
||||
|
||||
public start(): Promise<void>;
|
||||
|
||||
public stop(): Promise<void>;
|
||||
|
||||
public close(): void;
|
||||
}
|
||||
}
|
||||
|
||||
declare module "opus-recorder/dist/encoderWorker.min.js" {
|
||||
const path: string;
|
||||
export default path;
|
||||
}
|
||||
|
||||
declare module "opus-recorder/dist/waveWorker.min.js" {
|
||||
const path: string;
|
||||
export default path;
|
||||
}
|
||||
|
||||
declare module "opus-recorder/dist/decoderWorker.min.js" {
|
||||
const path: string;
|
||||
export default path;
|
||||
}
|
|
@ -16,7 +16,7 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import { IRequestMsisdnTokenResponse, IRequestTokenResponse } from "matrix-js-sdk/src/matrix";
|
||||
import { IAuthData, IRequestMsisdnTokenResponse, IRequestTokenResponse } from "matrix-js-sdk/src/matrix";
|
||||
|
||||
import { MatrixClientPeg } from "./MatrixClientPeg";
|
||||
import Modal from "./Modal";
|
||||
|
@ -29,6 +29,12 @@ function getIdServerDomain(): string {
|
|||
return MatrixClientPeg.get().idBaseUrl.split("://")[1];
|
||||
}
|
||||
|
||||
export type Binding = {
|
||||
bind: boolean;
|
||||
label: string;
|
||||
errorTitle: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* Allows a user to add a third party identifier to their homeserver and,
|
||||
* optionally, the identity servers.
|
||||
|
@ -178,7 +184,7 @@ export default class AddThreepid {
|
|||
* with a "message" property which contains a human-readable message detailing why
|
||||
* the request failed.
|
||||
*/
|
||||
public async checkEmailLinkClicked(): Promise<any[]> {
|
||||
public async checkEmailLinkClicked(): Promise<[boolean, IAuthData | Error | null]> {
|
||||
try {
|
||||
if (await MatrixClientPeg.get().doesServerSupportSeparateAddAndBind()) {
|
||||
if (this.bind) {
|
||||
|
@ -220,16 +226,19 @@ export default class AddThreepid {
|
|||
continueKind: "primary",
|
||||
},
|
||||
};
|
||||
const { finished } = Modal.createDialog(InteractiveAuthDialog, {
|
||||
title: _t("Add Email Address"),
|
||||
matrixClient: MatrixClientPeg.get(),
|
||||
authData: e.data,
|
||||
makeRequest: this.makeAddThreepidOnlyRequest,
|
||||
aestheticsForStagePhases: {
|
||||
[SSOAuthEntry.LOGIN_TYPE]: dialogAesthetics,
|
||||
[SSOAuthEntry.UNSTABLE_LOGIN_TYPE]: dialogAesthetics,
|
||||
const { finished } = Modal.createDialog<[boolean, IAuthData | Error | null]>(
|
||||
InteractiveAuthDialog,
|
||||
{
|
||||
title: _t("Add Email Address"),
|
||||
matrixClient: MatrixClientPeg.get(),
|
||||
authData: e.data,
|
||||
makeRequest: this.makeAddThreepidOnlyRequest,
|
||||
aestheticsForStagePhases: {
|
||||
[SSOAuthEntry.LOGIN_TYPE]: dialogAesthetics,
|
||||
[SSOAuthEntry.UNSTABLE_LOGIN_TYPE]: dialogAesthetics,
|
||||
},
|
||||
},
|
||||
});
|
||||
);
|
||||
return finished;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -42,10 +42,7 @@ interface IState {
|
|||
export default class AsyncWrapper extends React.Component<IProps, IState> {
|
||||
private unmounted = false;
|
||||
|
||||
public state = {
|
||||
component: null,
|
||||
error: null,
|
||||
};
|
||||
public state: IState = {};
|
||||
|
||||
public componentDidMount(): void {
|
||||
// XXX: temporary logging to try to diagnose
|
||||
|
@ -77,7 +74,7 @@ export default class AsyncWrapper extends React.Component<IProps, IState> {
|
|||
this.props.onFinished(false);
|
||||
};
|
||||
|
||||
public render(): JSX.Element {
|
||||
public render(): React.ReactNode {
|
||||
if (this.state.component) {
|
||||
const Component = this.state.component;
|
||||
return <Component {...this.props} />;
|
||||
|
|
|
@ -138,7 +138,7 @@ export function getInitialLetter(name: string): string | undefined {
|
|||
}
|
||||
|
||||
export function avatarUrlForRoom(
|
||||
room: Room,
|
||||
room: Room | null,
|
||||
width: number,
|
||||
height: number,
|
||||
resizeMethod?: ResizeMethod,
|
||||
|
|
|
@ -197,7 +197,7 @@ export default abstract class BasePlatform {
|
|||
room: Room,
|
||||
ev?: MatrixEvent,
|
||||
): Notification {
|
||||
const notifBody = {
|
||||
const notifBody: NotificationOptions = {
|
||||
body: msg,
|
||||
silent: true, // we play our own sounds
|
||||
};
|
||||
|
|
|
@ -204,7 +204,7 @@ const transformTags: IExtendedSanitizeOptions["transformTags"] = {
|
|||
attribs.style += "height: 100%;";
|
||||
}
|
||||
|
||||
attribs.src = mediaFromMxc(src).getThumbnailOfSourceHttp(width, height);
|
||||
attribs.src = mediaFromMxc(src).getThumbnailOfSourceHttp(width, height)!;
|
||||
return { tagName, attribs };
|
||||
},
|
||||
"code": function (tagName: string, attribs: sanitizeHtml.Attributes) {
|
||||
|
@ -228,7 +228,7 @@ const transformTags: IExtendedSanitizeOptions["transformTags"] = {
|
|||
|
||||
// Sanitise and transform data-mx-color and data-mx-bg-color to their CSS
|
||||
// equivalents
|
||||
const customCSSMapper = {
|
||||
const customCSSMapper: Record<string, string> = {
|
||||
"data-mx-color": "color",
|
||||
"data-mx-bg-color": "background-color",
|
||||
// $customAttributeKey: $cssAttributeKey
|
||||
|
@ -352,7 +352,7 @@ const topicSanitizeHtmlParams: IExtendedSanitizeOptions = {
|
|||
};
|
||||
|
||||
abstract class BaseHighlighter<T extends React.ReactNode> {
|
||||
public constructor(public highlightClass: string, public highlightLink: string) {}
|
||||
public constructor(public highlightClass: string, public highlightLink?: string) {}
|
||||
|
||||
/**
|
||||
* apply the highlights to a section of text
|
||||
|
@ -504,7 +504,7 @@ function formatEmojis(message: string, isHtmlMessage: boolean): (JSX.Element | s
|
|||
export function bodyToHtml(content: IContent, highlights: Optional<string[]>, opts: IOptsReturnString): string;
|
||||
export function bodyToHtml(content: IContent, highlights: Optional<string[]>, opts: IOptsReturnNode): ReactNode;
|
||||
export function bodyToHtml(content: IContent, highlights: Optional<string[]>, opts: IOpts = {}): ReactNode | string {
|
||||
const isFormattedBody = content.format === "org.matrix.custom.html" && !!content.formatted_body;
|
||||
const isFormattedBody = content.format === "org.matrix.custom.html" && typeof content.formatted_body === "string";
|
||||
let bodyHasEmoji = false;
|
||||
let isHtmlMessage = false;
|
||||
|
||||
|
@ -514,7 +514,7 @@ export function bodyToHtml(content: IContent, highlights: Optional<string[]>, op
|
|||
}
|
||||
|
||||
let strippedBody: string;
|
||||
let safeBody: string; // safe, sanitised HTML, preferred over `strippedBody` which is fully plaintext
|
||||
let safeBody: string | undefined; // safe, sanitised HTML, preferred over `strippedBody` which is fully plaintext
|
||||
|
||||
try {
|
||||
// sanitizeHtml can hang if an unclosed HTML tag is thrown at it
|
||||
|
@ -529,7 +529,7 @@ export function bodyToHtml(content: IContent, highlights: Optional<string[]>, op
|
|||
|
||||
if (opts.stripReplyFallback && formattedBody) formattedBody = stripHTMLReply(formattedBody);
|
||||
strippedBody = opts.stripReplyFallback ? stripPlainReply(plainBody) : plainBody;
|
||||
bodyHasEmoji = mightContainEmoji(isFormattedBody ? formattedBody : plainBody);
|
||||
bodyHasEmoji = mightContainEmoji(isFormattedBody ? formattedBody! : plainBody);
|
||||
|
||||
const highlighter = safeHighlights?.length
|
||||
? new HtmlHighlighter("mx_EventTile_searchHighlight", opts.highlightLink)
|
||||
|
@ -543,11 +543,11 @@ export function bodyToHtml(content: IContent, highlights: Optional<string[]>, op
|
|||
// by an attempt to search for 'foobar'. Then again, the search query probably wouldn't work either
|
||||
// XXX: hacky bodge to temporarily apply a textFilter to the sanitizeParams structure.
|
||||
sanitizeParams.textFilter = function (safeText) {
|
||||
return highlighter.applyHighlights(safeText, safeHighlights).join("");
|
||||
return highlighter.applyHighlights(safeText, safeHighlights!).join("");
|
||||
};
|
||||
}
|
||||
|
||||
safeBody = sanitizeHtml(formattedBody, sanitizeParams);
|
||||
safeBody = sanitizeHtml(formattedBody!, sanitizeParams);
|
||||
const phtml = cheerio.load(safeBody, {
|
||||
// @ts-ignore: The `_useHtmlParser2` internal option is the
|
||||
// simplest way to both parse and render using `htmlparser2`.
|
||||
|
@ -574,7 +574,7 @@ export function bodyToHtml(content: IContent, highlights: Optional<string[]>, op
|
|||
safeBody = formatEmojis(safeBody, true).join("");
|
||||
}
|
||||
} else if (highlighter) {
|
||||
safeBody = highlighter.applyHighlights(plainBody, safeHighlights).join("");
|
||||
safeBody = highlighter.applyHighlights(plainBody, safeHighlights!).join("");
|
||||
}
|
||||
} finally {
|
||||
delete sanitizeParams.textFilter;
|
||||
|
@ -597,9 +597,7 @@ export function bodyToHtml(content: IContent, highlights: Optional<string[]>, op
|
|||
|
||||
const match = BIGEMOJI_REGEX.exec(contentBodyTrimmed);
|
||||
emojiBody =
|
||||
match &&
|
||||
match[0] &&
|
||||
match[0].length === contentBodyTrimmed.length &&
|
||||
match?.[0]?.length === contentBodyTrimmed.length &&
|
||||
// Prevent user pills expanding for users with only emoji in
|
||||
// their username. Permalinks (links in pills) can be any URL
|
||||
// now, so we just check for an HTTP-looking thing.
|
||||
|
@ -614,7 +612,7 @@ export function bodyToHtml(content: IContent, highlights: Optional<string[]>, op
|
|||
"markdown-body": isHtmlMessage && !emojiBody,
|
||||
});
|
||||
|
||||
let emojiBodyElements: JSX.Element[];
|
||||
let emojiBodyElements: JSX.Element[] | undefined;
|
||||
if (!safeBody && bodyHasEmoji) {
|
||||
emojiBodyElements = formatEmojis(strippedBody, false) as JSX.Element[];
|
||||
}
|
||||
|
@ -649,7 +647,7 @@ export function topicToHtml(
|
|||
allowExtendedHtml = false,
|
||||
): ReactNode {
|
||||
if (!SettingsStore.getValue("feature_html_topic")) {
|
||||
htmlTopic = null;
|
||||
htmlTopic = undefined;
|
||||
}
|
||||
|
||||
let isFormattedTopic = !!htmlTopic;
|
||||
|
@ -657,10 +655,10 @@ export function topicToHtml(
|
|||
let safeTopic = "";
|
||||
|
||||
try {
|
||||
topicHasEmoji = mightContainEmoji(isFormattedTopic ? htmlTopic : topic);
|
||||
topicHasEmoji = mightContainEmoji(isFormattedTopic ? htmlTopic! : topic);
|
||||
|
||||
if (isFormattedTopic) {
|
||||
safeTopic = sanitizeHtml(htmlTopic, allowExtendedHtml ? sanitizeHtmlParams : topicSanitizeHtmlParams);
|
||||
safeTopic = sanitizeHtml(htmlTopic!, allowExtendedHtml ? sanitizeHtmlParams : topicSanitizeHtmlParams);
|
||||
if (topicHasEmoji) {
|
||||
safeTopic = formatEmojis(safeTopic, true).join("");
|
||||
}
|
||||
|
@ -669,7 +667,7 @@ export function topicToHtml(
|
|||
isFormattedTopic = false; // Fall back to plain-text topic
|
||||
}
|
||||
|
||||
let emojiBodyElements: ReturnType<typeof formatEmojis>;
|
||||
let emojiBodyElements: ReturnType<typeof formatEmojis> | undefined;
|
||||
if (!isFormattedTopic && topicHasEmoji) {
|
||||
emojiBodyElements = formatEmojis(topic, false);
|
||||
}
|
||||
|
|
|
@ -169,10 +169,18 @@ export interface IConfigOptions {
|
|||
inline?: {
|
||||
left?: string;
|
||||
right?: string;
|
||||
pattern?: {
|
||||
tex?: string;
|
||||
latex?: string;
|
||||
};
|
||||
};
|
||||
display?: {
|
||||
left?: string;
|
||||
right?: string;
|
||||
pattern?: {
|
||||
tex?: string;
|
||||
latex?: string;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
@ -16,6 +16,8 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
|
||||
export const Key = {
|
||||
HOME: "Home",
|
||||
END: "End",
|
||||
|
@ -76,7 +78,7 @@ export const Key = {
|
|||
|
||||
export const IS_MAC = navigator.platform.toUpperCase().includes("MAC");
|
||||
|
||||
export function isOnlyCtrlOrCmdKeyEvent(ev: KeyboardEvent): boolean {
|
||||
export function isOnlyCtrlOrCmdKeyEvent(ev: React.KeyboardEvent | KeyboardEvent): boolean {
|
||||
if (IS_MAC) {
|
||||
return ev.metaKey && !ev.altKey && !ev.ctrlKey && !ev.shiftKey;
|
||||
} else {
|
||||
|
|
|
@ -158,9 +158,9 @@ export default class LegacyCallHandler extends EventEmitter {
|
|||
private transferees = new Map<string, MatrixCall>(); // callId (target) -> call (transferee)
|
||||
private audioPromises = new Map<AudioID, Promise<void>>();
|
||||
private audioElementsWithListeners = new Map<HTMLMediaElement, boolean>();
|
||||
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 supportsPstnProtocol: boolean | null = null;
|
||||
private pstnSupportPrefixed: boolean | null = null; // True if the server only support the prefixed pstn protocol
|
||||
private supportsSipNativeVirtual: boolean | null = null; // im.vector.protocol.sip_virtual and im.vector.protocol.sip_native
|
||||
|
||||
// Map of the asserted identity users after we've looked them up using the API.
|
||||
// We need to be be able to determine the mapped room synchronously, so we
|
||||
|
@ -187,7 +187,7 @@ export default class LegacyCallHandler extends EventEmitter {
|
|||
// check asserted identity: if we're not obeying asserted identity,
|
||||
// this map will never be populated, but we check anyway for sanity
|
||||
if (this.shouldObeyAssertedfIdentity()) {
|
||||
const nativeUser = this.assertedIdentityNativeUsers[call.callId];
|
||||
const nativeUser = this.assertedIdentityNativeUsers.get(call.callId);
|
||||
if (nativeUser) {
|
||||
const room = findDMForUser(MatrixClientPeg.get(), nativeUser);
|
||||
if (room) return room.roomId;
|
||||
|
@ -466,8 +466,8 @@ export default class LegacyCallHandler extends EventEmitter {
|
|||
return this.getAllActiveCallsNotInRoom(roomId);
|
||||
}
|
||||
|
||||
public getTransfereeForCallId(callId: string): MatrixCall {
|
||||
return this.transferees[callId];
|
||||
public getTransfereeForCallId(callId: string): MatrixCall | undefined {
|
||||
return this.transferees.get(callId);
|
||||
}
|
||||
|
||||
public play(audioId: AudioID): void {
|
||||
|
@ -621,7 +621,7 @@ export default class LegacyCallHandler extends EventEmitter {
|
|||
logger.log(`Asserted identity ${newAssertedIdentity} mapped to ${newNativeAssertedIdentity}`);
|
||||
|
||||
if (newNativeAssertedIdentity) {
|
||||
this.assertedIdentityNativeUsers[call.callId] = newNativeAssertedIdentity;
|
||||
this.assertedIdentityNativeUsers.set(call.callId, newNativeAssertedIdentity);
|
||||
|
||||
// If we don't already have a room with this user, make one. This will be slightly odd
|
||||
// if they called us because we'll be inviting them, but there's not much we can do about
|
||||
|
@ -917,7 +917,7 @@ export default class LegacyCallHandler extends EventEmitter {
|
|||
return;
|
||||
}
|
||||
if (transferee) {
|
||||
this.transferees[call.callId] = transferee;
|
||||
this.transferees.set(call.callId, transferee);
|
||||
}
|
||||
|
||||
this.setCallListeners(call);
|
||||
|
|
|
@ -91,12 +91,12 @@ export default class Login {
|
|||
}
|
||||
|
||||
public loginViaPassword(
|
||||
username: string,
|
||||
phoneCountry: string,
|
||||
phoneNumber: string,
|
||||
username: string | undefined,
|
||||
phoneCountry: string | undefined,
|
||||
phoneNumber: string | undefined,
|
||||
password: string,
|
||||
): Promise<IMatrixClientCreds> {
|
||||
const isEmail = username.indexOf("@") > 0;
|
||||
const isEmail = username?.indexOf("@") > 0;
|
||||
|
||||
let identifier;
|
||||
if (phoneCountry && phoneNumber) {
|
||||
|
|
|
@ -139,7 +139,7 @@ export default class Markdown {
|
|||
*/
|
||||
private repairLinks(parsed: commonmark.Node): commonmark.Node {
|
||||
const walker = parsed.walker();
|
||||
let event: commonmark.NodeWalkingStep = null;
|
||||
let event: commonmark.NodeWalkingStep | null = null;
|
||||
let text = "";
|
||||
let isInPara = false;
|
||||
let previousNode: commonmark.Node | null = null;
|
||||
|
@ -287,7 +287,7 @@ export default class Markdown {
|
|||
// However, if it's a blockquote, adds a p tag anyway
|
||||
// in order to avoid deviation to commonmark and unexpected
|
||||
// results when parsing the formatted HTML.
|
||||
if (node.parent.type === "block_quote" || isMultiLine(node)) {
|
||||
if (node.parent?.type === "block_quote" || isMultiLine(node)) {
|
||||
realParagraph.call(this, node, entering);
|
||||
}
|
||||
};
|
||||
|
|
|
@ -37,7 +37,7 @@ export enum MediaDeviceHandlerEvent {
|
|||
}
|
||||
|
||||
export default class MediaDeviceHandler extends EventEmitter {
|
||||
private static internalInstance;
|
||||
private static internalInstance?: MediaDeviceHandler;
|
||||
|
||||
public static get instance(): MediaDeviceHandler {
|
||||
if (!MediaDeviceHandler.internalInstance) {
|
||||
|
@ -67,7 +67,7 @@ export default class MediaDeviceHandler extends EventEmitter {
|
|||
public static async getDevices(): Promise<IMediaDevices> {
|
||||
try {
|
||||
const devices = await navigator.mediaDevices.enumerateDevices();
|
||||
const output = {
|
||||
const output: Record<MediaDeviceKindEnum, MediaDeviceInfo[]> = {
|
||||
[MediaDeviceKindEnum.AudioOutput]: [],
|
||||
[MediaDeviceKindEnum.AudioInput]: [],
|
||||
[MediaDeviceKindEnum.VideoInput]: [],
|
||||
|
|
|
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import React, { ReactInstance } from "react";
|
||||
import ReactDom from "react-dom";
|
||||
|
||||
interface IChildProps {
|
||||
|
@ -41,7 +41,7 @@ interface IProps {
|
|||
* automatic positional animation, look at react-shuffle or similar libraries.
|
||||
*/
|
||||
export default class NodeAnimator extends React.Component<IProps> {
|
||||
private nodes = {};
|
||||
private nodes: Record<string, ReactInstance> = {};
|
||||
private children: { [key: string]: React.DetailedReactHTMLElement<any, HTMLElement> };
|
||||
public static defaultProps: Partial<IProps> = {
|
||||
startStyles: [],
|
||||
|
@ -65,7 +65,7 @@ export default class NodeAnimator extends React.Component<IProps> {
|
|||
*/
|
||||
private applyStyles(node: HTMLElement, styles: React.CSSProperties): void {
|
||||
Object.entries(styles).forEach(([property, value]) => {
|
||||
node.style[property] = value;
|
||||
node.style[property as keyof Omit<CSSStyleDeclaration, "length" | "parentRule">] = value;
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -120,7 +120,7 @@ export default class NodeAnimator extends React.Component<IProps> {
|
|||
this.nodes[k] = node;
|
||||
}
|
||||
|
||||
public render(): JSX.Element {
|
||||
public render(): React.ReactNode {
|
||||
return <>{Object.values(this.children)}</>;
|
||||
}
|
||||
}
|
||||
|
|
158
src/Notifier.ts
158
src/Notifier.ts
|
@ -68,7 +68,7 @@ Override both the content body and the TextForEvent handler for specific msgtype
|
|||
This is useful when the content body contains fallback text that would explain that the client can't handle a particular
|
||||
type of tile.
|
||||
*/
|
||||
const msgTypeHandlers = {
|
||||
const msgTypeHandlers: Record<string, (event: MatrixEvent) => string> = {
|
||||
[MsgType.KeyVerificationRequest]: (event: MatrixEvent) => {
|
||||
const name = (event.sender || {}).name;
|
||||
return _t("%(name)s is requesting verification", { name });
|
||||
|
@ -95,22 +95,26 @@ const msgTypeHandlers = {
|
|||
},
|
||||
};
|
||||
|
||||
export const Notifier = {
|
||||
notifsByRoom: {},
|
||||
class NotifierClass {
|
||||
private notifsByRoom: Record<string, Notification[]> = {};
|
||||
|
||||
// A list of event IDs that we've received but need to wait until
|
||||
// they're decrypted until we decide whether to notify for them
|
||||
// or not
|
||||
pendingEncryptedEventIds: [],
|
||||
private pendingEncryptedEventIds: string[] = [];
|
||||
|
||||
notificationMessageForEvent: function (ev: MatrixEvent): string {
|
||||
private toolbarHidden?: boolean;
|
||||
private isSyncing?: boolean;
|
||||
|
||||
public notificationMessageForEvent(ev: MatrixEvent): string {
|
||||
if (msgTypeHandlers.hasOwnProperty(ev.getContent().msgtype)) {
|
||||
return msgTypeHandlers[ev.getContent().msgtype](ev);
|
||||
}
|
||||
return TextForEvent.textForEvent(ev);
|
||||
},
|
||||
}
|
||||
|
||||
_displayPopupNotification: function (ev: MatrixEvent, room: Room): void {
|
||||
// XXX: exported for tests
|
||||
public displayPopupNotification(ev: MatrixEvent, room: Room): void {
|
||||
const plaf = PlatformPeg.get();
|
||||
const cli = MatrixClientPeg.get();
|
||||
if (!plaf) {
|
||||
|
@ -165,9 +169,14 @@ export const Notifier = {
|
|||
if (this.notifsByRoom[ev.getRoomId()] === undefined) this.notifsByRoom[ev.getRoomId()] = [];
|
||||
this.notifsByRoom[ev.getRoomId()].push(notif);
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
getSoundForRoom: function (roomId: string) {
|
||||
public getSoundForRoom(roomId: string): {
|
||||
url: string;
|
||||
name: string;
|
||||
type: string;
|
||||
size: string;
|
||||
} | null {
|
||||
// We do no caching here because the SDK caches setting
|
||||
// and the browser will cache the sound.
|
||||
const content = SettingsStore.getValue("notificationSound", roomId);
|
||||
|
@ -193,9 +202,10 @@ export const Notifier = {
|
|||
type: content.type,
|
||||
size: content.size,
|
||||
};
|
||||
},
|
||||
}
|
||||
|
||||
_playAudioNotification: async function (ev: MatrixEvent, room: Room): Promise<void> {
|
||||
// XXX: Exported for tests
|
||||
public async playAudioNotification(ev: MatrixEvent, room: Room): Promise<void> {
|
||||
const cli = MatrixClientPeg.get();
|
||||
if (localNotificationsAreSilenced(cli)) {
|
||||
return;
|
||||
|
@ -224,39 +234,32 @@ export const Notifier = {
|
|||
} catch (ex) {
|
||||
logger.warn("Caught error when trying to fetch room notification sound:", ex);
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
start: function (this: typeof Notifier) {
|
||||
// do not re-bind in the case of repeated call
|
||||
this.boundOnEvent = this.boundOnEvent || this.onEvent.bind(this);
|
||||
this.boundOnSyncStateChange = this.boundOnSyncStateChange || this.onSyncStateChange.bind(this);
|
||||
this.boundOnRoomReceipt = this.boundOnRoomReceipt || this.onRoomReceipt.bind(this);
|
||||
this.boundOnEventDecrypted = this.boundOnEventDecrypted || this.onEventDecrypted.bind(this);
|
||||
|
||||
MatrixClientPeg.get().on(RoomEvent.Timeline, this.boundOnEvent);
|
||||
MatrixClientPeg.get().on(RoomEvent.Receipt, this.boundOnRoomReceipt);
|
||||
MatrixClientPeg.get().on(MatrixEventEvent.Decrypted, this.boundOnEventDecrypted);
|
||||
MatrixClientPeg.get().on(ClientEvent.Sync, this.boundOnSyncStateChange);
|
||||
public start(): void {
|
||||
MatrixClientPeg.get().on(RoomEvent.Timeline, this.onEvent);
|
||||
MatrixClientPeg.get().on(RoomEvent.Receipt, this.onRoomReceipt);
|
||||
MatrixClientPeg.get().on(MatrixEventEvent.Decrypted, this.onEventDecrypted);
|
||||
MatrixClientPeg.get().on(ClientEvent.Sync, this.onSyncStateChange);
|
||||
this.toolbarHidden = false;
|
||||
this.isSyncing = false;
|
||||
},
|
||||
}
|
||||
|
||||
stop: function (this: typeof Notifier) {
|
||||
public stop(): void {
|
||||
if (MatrixClientPeg.get()) {
|
||||
MatrixClientPeg.get().removeListener(RoomEvent.Timeline, this.boundOnEvent);
|
||||
MatrixClientPeg.get().removeListener(RoomEvent.Receipt, this.boundOnRoomReceipt);
|
||||
MatrixClientPeg.get().removeListener(MatrixEventEvent.Decrypted, this.boundOnEventDecrypted);
|
||||
MatrixClientPeg.get().removeListener(ClientEvent.Sync, this.boundOnSyncStateChange);
|
||||
MatrixClientPeg.get().removeListener(RoomEvent.Timeline, this.onEvent);
|
||||
MatrixClientPeg.get().removeListener(RoomEvent.Receipt, this.onRoomReceipt);
|
||||
MatrixClientPeg.get().removeListener(MatrixEventEvent.Decrypted, this.onEventDecrypted);
|
||||
MatrixClientPeg.get().removeListener(ClientEvent.Sync, this.onSyncStateChange);
|
||||
}
|
||||
this.isSyncing = false;
|
||||
},
|
||||
}
|
||||
|
||||
supportsDesktopNotifications: function () {
|
||||
const plaf = PlatformPeg.get();
|
||||
return plaf && plaf.supportsNotifications();
|
||||
},
|
||||
public supportsDesktopNotifications(): boolean {
|
||||
return PlatformPeg.get()?.supportsNotifications() ?? false;
|
||||
}
|
||||
|
||||
setEnabled: function (enable: boolean, callback?: () => void) {
|
||||
public setEnabled(enable: boolean, callback?: () => void): void {
|
||||
const plaf = PlatformPeg.get();
|
||||
if (!plaf) return;
|
||||
|
||||
|
@ -320,31 +323,30 @@ export const Notifier = {
|
|||
// set the notifications_hidden flag, as the user has knowingly interacted
|
||||
// with the setting we shouldn't nag them any further
|
||||
this.setPromptHidden(true);
|
||||
},
|
||||
}
|
||||
|
||||
isEnabled: function () {
|
||||
public isEnabled(): boolean {
|
||||
return this.isPossible() && SettingsStore.getValue("notificationsEnabled");
|
||||
},
|
||||
}
|
||||
|
||||
isPossible: function () {
|
||||
public isPossible(): boolean {
|
||||
const plaf = PlatformPeg.get();
|
||||
if (!plaf) return false;
|
||||
if (!plaf.supportsNotifications()) return false;
|
||||
if (!plaf?.supportsNotifications()) return false;
|
||||
if (!plaf.maySendNotifications()) return false;
|
||||
|
||||
return true; // possible, but not necessarily enabled
|
||||
},
|
||||
}
|
||||
|
||||
isBodyEnabled: function () {
|
||||
public isBodyEnabled(): boolean {
|
||||
return this.isEnabled() && SettingsStore.getValue("notificationBodyEnabled");
|
||||
},
|
||||
}
|
||||
|
||||
isAudioEnabled: function () {
|
||||
public isAudioEnabled(): boolean {
|
||||
// We don't route Audio via the HTML Notifications API so it is possible regardless of other things
|
||||
return SettingsStore.getValue("audioNotificationsEnabled");
|
||||
},
|
||||
}
|
||||
|
||||
setPromptHidden: function (this: typeof Notifier, hidden: boolean, persistent = true) {
|
||||
public setPromptHidden(hidden: boolean, persistent = true): void {
|
||||
this.toolbarHidden = hidden;
|
||||
|
||||
hideNotificationsToast();
|
||||
|
@ -353,9 +355,9 @@ export const Notifier = {
|
|||
if (persistent && global.localStorage) {
|
||||
global.localStorage.setItem("notifications_hidden", String(hidden));
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
shouldShowPrompt: function () {
|
||||
public shouldShowPrompt(): boolean {
|
||||
const client = MatrixClientPeg.get();
|
||||
if (!client) {
|
||||
return false;
|
||||
|
@ -366,25 +368,21 @@ export const Notifier = {
|
|||
this.supportsDesktopNotifications() &&
|
||||
!isPushNotifyDisabled() &&
|
||||
!this.isEnabled() &&
|
||||
!this._isPromptHidden()
|
||||
!this.isPromptHidden()
|
||||
);
|
||||
},
|
||||
}
|
||||
|
||||
_isPromptHidden: function (this: typeof Notifier) {
|
||||
private isPromptHidden(): boolean {
|
||||
// Check localStorage for any such meta data
|
||||
if (global.localStorage) {
|
||||
return global.localStorage.getItem("notifications_hidden") === "true";
|
||||
}
|
||||
|
||||
return this.toolbarHidden;
|
||||
},
|
||||
}
|
||||
|
||||
onSyncStateChange: function (
|
||||
this: typeof Notifier,
|
||||
state: SyncState,
|
||||
prevState?: SyncState,
|
||||
data?: ISyncStateData,
|
||||
) {
|
||||
// XXX: Exported for tests
|
||||
public onSyncStateChange = (state: SyncState, prevState?: SyncState, data?: ISyncStateData): void => {
|
||||
if (state === SyncState.Syncing) {
|
||||
this.isSyncing = true;
|
||||
} else if (state === SyncState.Stopped || state === SyncState.Error) {
|
||||
|
@ -395,16 +393,15 @@ export const Notifier = {
|
|||
if (![SyncState.Stopped, SyncState.Error].includes(state) && !data?.fromCache) {
|
||||
createLocalNotificationSettingsIfNeeded(MatrixClientPeg.get());
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
onEvent: function (
|
||||
this: typeof Notifier,
|
||||
private onEvent = (
|
||||
ev: MatrixEvent,
|
||||
room: Room | undefined,
|
||||
toStartOfTimeline: boolean | undefined,
|
||||
removed: boolean,
|
||||
data: IRoomTimelineData,
|
||||
) {
|
||||
): void => {
|
||||
if (!data.liveEvent) return; // only notify for new things, not old.
|
||||
if (!this.isSyncing) return; // don't alert for any messages initially
|
||||
if (ev.getSender() === MatrixClientPeg.get().getUserId()) return;
|
||||
|
@ -422,10 +419,10 @@ export const Notifier = {
|
|||
return;
|
||||
}
|
||||
|
||||
this._evaluateEvent(ev);
|
||||
},
|
||||
this.evaluateEvent(ev);
|
||||
};
|
||||
|
||||
onEventDecrypted: function (ev: MatrixEvent) {
|
||||
private onEventDecrypted = (ev: MatrixEvent): void => {
|
||||
// 'decrypted' means the decryption process has finished: it may have failed,
|
||||
// in which case it might decrypt soon if the keys arrive
|
||||
if (ev.isDecryptionFailure()) return;
|
||||
|
@ -434,10 +431,10 @@ export const Notifier = {
|
|||
if (idx === -1) return;
|
||||
|
||||
this.pendingEncryptedEventIds.splice(idx, 1);
|
||||
this._evaluateEvent(ev);
|
||||
},
|
||||
this.evaluateEvent(ev);
|
||||
};
|
||||
|
||||
onRoomReceipt: function (ev: MatrixEvent, room: Room) {
|
||||
private onRoomReceipt = (ev: MatrixEvent, room: Room): void => {
|
||||
if (room.getUnreadNotificationCount() === 0) {
|
||||
// ideally we would clear each notification when it was read,
|
||||
// but we have no way, given a read receipt, to know whether
|
||||
|
@ -453,12 +450,12 @@ export const Notifier = {
|
|||
}
|
||||
delete this.notifsByRoom[room.roomId];
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
_evaluateEvent: function (ev: MatrixEvent) {
|
||||
// XXX: exported for tests
|
||||
public evaluateEvent(ev: MatrixEvent): void {
|
||||
// Mute notifications for broadcast info events
|
||||
if (ev.getType() === VoiceBroadcastInfoEventType) return;
|
||||
|
||||
let roomId = ev.getRoomId();
|
||||
if (LegacyCallHandler.instance.getSupportsVirtualRooms()) {
|
||||
// Attempt to translate a virtual room to a native one
|
||||
|
@ -477,7 +474,7 @@ export const Notifier = {
|
|||
const actions = MatrixClientPeg.get().getPushActionsForEvent(ev);
|
||||
|
||||
if (actions?.notify) {
|
||||
this._performCustomEventHandling(ev);
|
||||
this.performCustomEventHandling(ev);
|
||||
|
||||
const store = SdkContextClass.instance.roomViewStore;
|
||||
const isViewingRoom = store.getRoomId() === room.roomId;
|
||||
|
@ -492,19 +489,19 @@ export const Notifier = {
|
|||
}
|
||||
|
||||
if (this.isEnabled()) {
|
||||
this._displayPopupNotification(ev, room);
|
||||
this.displayPopupNotification(ev, room);
|
||||
}
|
||||
if (actions.tweaks.sound && this.isAudioEnabled()) {
|
||||
PlatformPeg.get().loudNotification(ev, room);
|
||||
this._playAudioNotification(ev, room);
|
||||
this.playAudioNotification(ev, room);
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* Some events require special handling such as showing in-app toasts
|
||||
*/
|
||||
_performCustomEventHandling: function (ev: MatrixEvent) {
|
||||
private performCustomEventHandling(ev: MatrixEvent): void {
|
||||
if (ElementCall.CALL_EVENT_TYPE.names.includes(ev.getType()) && SettingsStore.getValue("feature_group_calls")) {
|
||||
ToastStore.sharedInstance().addOrReplaceToast({
|
||||
key: getIncomingCallToastKey(ev.getStateKey()),
|
||||
|
@ -514,11 +511,12 @@ export const Notifier = {
|
|||
props: { callEvent: ev },
|
||||
});
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if (!window.mxNotifier) {
|
||||
window.mxNotifier = Notifier;
|
||||
window.mxNotifier = new NotifierClass();
|
||||
}
|
||||
|
||||
export default window.mxNotifier;
|
||||
export const Notifier: NotifierClass = window.mxNotifier;
|
||||
|
|
|
@ -132,8 +132,8 @@ export class PosthogAnalytics {
|
|||
private anonymity = Anonymity.Disabled;
|
||||
// set true during the constructor if posthog config is present, otherwise false
|
||||
private readonly enabled: boolean = false;
|
||||
private static _instance = null;
|
||||
private platformSuperProperties = {};
|
||||
private static _instance: PosthogAnalytics | null = null;
|
||||
private platformSuperProperties: Properties = {};
|
||||
public static readonly ANALYTICS_EVENT_TYPE = "im.vector.analytics";
|
||||
private propertiesForNextEvent: Partial<Record<"$set" | "$set_once", UserProperties>> = {};
|
||||
private userPropertyCache: UserProperties = {};
|
||||
|
|
|
@ -120,7 +120,7 @@ export class PosthogScreenTracker extends PureComponent<{ screenName: ScreenName
|
|||
PosthogTrackers.instance.clearOverride(this.props.screenName);
|
||||
}
|
||||
|
||||
public render(): JSX.Element {
|
||||
public render(): React.ReactNode {
|
||||
return null; // no need to render anything, we just need to hook into the React lifecycle
|
||||
}
|
||||
}
|
||||
|
|
|
@ -33,9 +33,9 @@ enum State {
|
|||
}
|
||||
|
||||
class Presence {
|
||||
private unavailableTimer: Timer = null;
|
||||
private dispatcherRef: string = null;
|
||||
private state: State = null;
|
||||
private unavailableTimer: Timer | null = null;
|
||||
private dispatcherRef: string | null = null;
|
||||
private state: State | null = null;
|
||||
|
||||
/**
|
||||
* Start listening the user activity to evaluate his presence state.
|
||||
|
@ -73,14 +73,14 @@ class Presence {
|
|||
* Get the current presence state.
|
||||
* @returns {string} the presence state (see PRESENCE enum)
|
||||
*/
|
||||
public getState(): State {
|
||||
public getState(): State | null {
|
||||
return this.state;
|
||||
}
|
||||
|
||||
private onAction = (payload: ActionPayload): void => {
|
||||
if (payload.action === "user_activity") {
|
||||
this.setState(State.Online);
|
||||
this.unavailableTimer.restart();
|
||||
this.unavailableTimer?.restart();
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -46,7 +46,7 @@ export default class Resend {
|
|||
}
|
||||
|
||||
public static resend(event: MatrixEvent): Promise<void> {
|
||||
const room = MatrixClientPeg.get().getRoom(event.getRoomId());
|
||||
const room = MatrixClientPeg.get().getRoom(event.getRoomId())!;
|
||||
return MatrixClientPeg.get()
|
||||
.resendEvent(event, room)
|
||||
.then(
|
||||
|
|
|
@ -30,6 +30,6 @@ export function storeRoomAliasInCache(alias: string, id: string): void {
|
|||
aliasToIDMap.set(alias, id);
|
||||
}
|
||||
|
||||
export function getCachedRoomIDForAlias(alias: string): string {
|
||||
export function getCachedRoomIDForAlias(alias: string): string | undefined {
|
||||
return aliasToIDMap.get(alias);
|
||||
}
|
||||
|
|
|
@ -112,7 +112,7 @@ export function inviteUsersToRoom(
|
|||
): Promise<void> {
|
||||
return inviteMultipleToRoom(roomId, userIds, sendSharedHistoryKeys, progressCallback)
|
||||
.then((result) => {
|
||||
const room = MatrixClientPeg.get().getRoom(roomId);
|
||||
const room = MatrixClientPeg.get().getRoom(roomId)!;
|
||||
showAnyInviteErrors(result.states, room, result.inviter);
|
||||
})
|
||||
.catch((err) => {
|
||||
|
@ -175,14 +175,14 @@ export function showAnyInviteErrors(
|
|||
<BaseAvatar
|
||||
url={avatarUrl ? mediaFromMxc(avatarUrl).getSquareThumbnailHttp(24) : null}
|
||||
name={name}
|
||||
idName={user.userId}
|
||||
idName={user?.userId}
|
||||
width={36}
|
||||
height={36}
|
||||
/>
|
||||
</div>
|
||||
<div className="mx_InviteDialog_tile_nameStack">
|
||||
<span className="mx_InviteDialog_tile_nameStack_name">{name}</span>
|
||||
<span className="mx_InviteDialog_tile_nameStack_userId">{user.userId}</span>
|
||||
<span className="mx_InviteDialog_tile_nameStack_userId">{user?.userId}</span>
|
||||
</div>
|
||||
<div className="mx_InviteDialog_tile--inviterError_errorText">
|
||||
{inviter.getErrorText(addr)}
|
||||
|
|
|
@ -46,7 +46,7 @@ export function getRoomNotifsState(client: MatrixClient, roomId: string): RoomNo
|
|||
}
|
||||
|
||||
// for everything else, look at the room rule.
|
||||
let roomRule = null;
|
||||
let roomRule: IPushRule | undefined;
|
||||
try {
|
||||
roomRule = client.getRoomPushRule("global", roomId);
|
||||
} catch (err) {
|
||||
|
@ -106,7 +106,7 @@ export function getUnreadNotificationCount(room: Room, type: NotificationCountTy
|
|||
|
||||
function setRoomNotifsStateMuted(roomId: string): Promise<any> {
|
||||
const cli = MatrixClientPeg.get();
|
||||
const promises = [];
|
||||
const promises: Promise<unknown>[] = [];
|
||||
|
||||
// delete the room rule
|
||||
const roomRule = cli.getRoomPushRule("global", roomId);
|
||||
|
@ -137,7 +137,7 @@ function setRoomNotifsStateMuted(roomId: string): Promise<any> {
|
|||
|
||||
function setRoomNotifsStateUnmuted(roomId: string, newState: RoomNotifState): Promise<any> {
|
||||
const cli = MatrixClientPeg.get();
|
||||
const promises = [];
|
||||
const promises: Promise<unknown>[] = [];
|
||||
|
||||
const overrideMuteRule = findOverrideMuteRule(roomId);
|
||||
if (overrideMuteRule) {
|
||||
|
|
10
src/Rooms.ts
10
src/Rooms.ts
|
@ -29,13 +29,13 @@ import AliasCustomisations from "./customisations/Alias";
|
|||
* @param {Object} room The room object
|
||||
* @returns {string} A display alias for the given room
|
||||
*/
|
||||
export function getDisplayAliasForRoom(room: Room): string | undefined {
|
||||
export function getDisplayAliasForRoom(room: Room): string | null {
|
||||
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 {
|
||||
export function getDisplayAliasForAliasSet(canonicalAlias: string | null, altAliases: string[]): string | null {
|
||||
if (AliasCustomisations.getDisplayAliasForAliasSet) {
|
||||
return AliasCustomisations.getDisplayAliasForAliasSet(canonicalAlias, altAliases);
|
||||
}
|
||||
|
@ -45,7 +45,7 @@ export function getDisplayAliasForAliasSet(canonicalAlias: string, altAliases: s
|
|||
export function guessAndSetDMRoom(room: Room, isDirect: boolean): Promise<void> {
|
||||
let newTarget;
|
||||
if (isDirect) {
|
||||
const guessedUserId = guessDMRoomTargetId(room, MatrixClientPeg.get().getUserId());
|
||||
const guessedUserId = guessDMRoomTargetId(room, MatrixClientPeg.get().getUserId()!);
|
||||
newTarget = guessedUserId;
|
||||
} else {
|
||||
newTarget = null;
|
||||
|
@ -118,7 +118,7 @@ function guessDMRoomTargetId(room: Room, myUserId: string): string {
|
|||
|
||||
if (oldestTs === undefined || (user.events.member && user.events.member.getTs() < oldestTs)) {
|
||||
oldestUser = user;
|
||||
oldestTs = user.events.member.getTs();
|
||||
oldestTs = user.events.member?.getTs();
|
||||
}
|
||||
}
|
||||
if (oldestUser) return oldestUser.userId;
|
||||
|
@ -129,7 +129,7 @@ function guessDMRoomTargetId(room: Room, myUserId: string): string {
|
|||
|
||||
if (oldestTs === undefined || (user.events.member && user.events.member.getTs() < oldestTs)) {
|
||||
oldestUser = user;
|
||||
oldestTs = user.events.member.getTs();
|
||||
oldestTs = user.events.member?.getTs();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -182,7 +182,7 @@ async function getSecretStorageKey({
|
|||
|
||||
export async function getDehydrationKey(
|
||||
keyInfo: ISecretStorageKeyInfo,
|
||||
checkFunc: (Uint8Array) => void,
|
||||
checkFunc: (data: Uint8Array) => void,
|
||||
): Promise<Uint8Array> {
|
||||
const keyFromCustomisations = SecurityCustomisations.getSecretStorageKey?.();
|
||||
if (keyFromCustomisations) {
|
||||
|
@ -196,7 +196,7 @@ export async function getDehydrationKey(
|
|||
/* props= */
|
||||
{
|
||||
keyInfo,
|
||||
checkPrivateKey: async (input): Promise<boolean> => {
|
||||
checkPrivateKey: async (input: KeyParams): Promise<boolean> => {
|
||||
const key = await inputToKey(input);
|
||||
try {
|
||||
checkFunc(key);
|
||||
|
@ -290,7 +290,7 @@ export async function promptForBackupPassphrase(): Promise<Uint8Array> {
|
|||
RestoreKeyBackupDialog,
|
||||
{
|
||||
showSummary: false,
|
||||
keyCallback: (k) => (key = k),
|
||||
keyCallback: (k: Uint8Array) => (key = k),
|
||||
},
|
||||
null,
|
||||
/* priority = */ false,
|
||||
|
|
|
@ -697,11 +697,8 @@ export const Commands = [
|
|||
}
|
||||
|
||||
if (viaServers) {
|
||||
// For the join
|
||||
dispatch["opts"] = {
|
||||
// These are passed down to the js-sdk's /join call
|
||||
viaServers: viaServers,
|
||||
};
|
||||
// For the join, these are passed down to the js-sdk's /join call
|
||||
dispatch["opts"] = { viaServers };
|
||||
|
||||
// For if the join fails (rejoin button)
|
||||
dispatch["via_servers"] = viaServers;
|
||||
|
@ -1042,7 +1039,7 @@ export const Commands = [
|
|||
throw newTranslatableError("Session already verified!");
|
||||
} else {
|
||||
throw newTranslatableError(
|
||||
"WARNING: Session already verified, but keys do NOT MATCH!",
|
||||
"WARNING: session already verified, but keys do NOT MATCH!",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
14
src/Terms.ts
14
src/Terms.ts
|
@ -52,11 +52,13 @@ export type Policies = {
|
|||
[policy: string]: Policy;
|
||||
};
|
||||
|
||||
export type ServicePolicyPair = {
|
||||
policies: Policies;
|
||||
service: Service;
|
||||
};
|
||||
|
||||
export type TermsInteractionCallback = (
|
||||
policiesAndServicePairs: {
|
||||
service: Service;
|
||||
policies: Policies;
|
||||
}[],
|
||||
policiesAndServicePairs: ServicePolicyPair[],
|
||||
agreedUrls: string[],
|
||||
extraClassNames?: string,
|
||||
) => Promise<string[]>;
|
||||
|
@ -117,9 +119,9 @@ export async function startTermsFlow(
|
|||
// but then they'd assume they can un-check the boxes to un-agree to a policy,
|
||||
// but that is not a thing the API supports, so probably best to just show
|
||||
// things they've not agreed to yet.
|
||||
const unagreedPoliciesAndServicePairs = [];
|
||||
const unagreedPoliciesAndServicePairs: ServicePolicyPair[] = [];
|
||||
for (const { service, policies } of policiesAndServicePairs) {
|
||||
const unagreedPolicies = {};
|
||||
const unagreedPolicies: Policies = {};
|
||||
for (const [policyName, policy] of Object.entries(policies)) {
|
||||
let policyAgreed = false;
|
||||
for (const lang of Object.keys(policy)) {
|
||||
|
|
|
@ -33,7 +33,7 @@ import { RightPanelPhases } from "./stores/right-panel/RightPanelStorePhases";
|
|||
import defaultDispatcher from "./dispatcher/dispatcher";
|
||||
import { MatrixClientPeg } from "./MatrixClientPeg";
|
||||
import { ROOM_SECURITY_TAB } from "./components/views/dialogs/RoomSettingsDialog";
|
||||
import AccessibleButton from "./components/views/elements/AccessibleButton";
|
||||
import AccessibleButton, { ButtonEvent } from "./components/views/elements/AccessibleButton";
|
||||
import RightPanelStore from "./stores/right-panel/RightPanelStore";
|
||||
import { highlightEvent, isLocationEvent } from "./utils/EventUtils";
|
||||
import { ElementCall } from "./models/Call";
|
||||
|
@ -308,7 +308,7 @@ function textForServerACLEvent(ev: MatrixEvent): () => string | null {
|
|||
allow_ip_literals: prevContent.allow_ip_literals !== false,
|
||||
};
|
||||
|
||||
let getText = null;
|
||||
let getText: () => string = null;
|
||||
if (prev.deny.length === 0 && prev.allow.length === 0) {
|
||||
getText = () => _t("%(senderDisplayName)s set the server ACLs for this room.", { senderDisplayName });
|
||||
} else {
|
||||
|
@ -360,8 +360,8 @@ function textForCanonicalAliasEvent(ev: MatrixEvent): () => string | null {
|
|||
const oldAltAliases = ev.getPrevContent().alt_aliases || [];
|
||||
const newAlias = ev.getContent().alias;
|
||||
const newAltAliases = ev.getContent().alt_aliases || [];
|
||||
const removedAltAliases = oldAltAliases.filter((alias) => !newAltAliases.includes(alias));
|
||||
const addedAltAliases = newAltAliases.filter((alias) => !oldAltAliases.includes(alias));
|
||||
const removedAltAliases = oldAltAliases.filter((alias: string) => !newAltAliases.includes(alias));
|
||||
const addedAltAliases = newAltAliases.filter((alias: string) => !oldAltAliases.includes(alias));
|
||||
|
||||
if (!removedAltAliases.length && !addedAltAliases.length) {
|
||||
if (newAlias) {
|
||||
|
@ -533,8 +533,8 @@ function textForPinnedEvent(event: MatrixEvent, allowJSX: boolean): () => Render
|
|||
const senderName = getSenderName(event);
|
||||
const roomId = event.getRoomId();
|
||||
|
||||
const pinned = event.getContent().pinned ?? [];
|
||||
const previouslyPinned = event.getPrevContent().pinned ?? [];
|
||||
const pinned = event.getContent<{ pinned: string[] }>().pinned ?? [];
|
||||
const previouslyPinned: string[] = event.getPrevContent().pinned ?? [];
|
||||
const newlyPinned = pinned.filter((item) => previouslyPinned.indexOf(item) < 0);
|
||||
const newlyUnpinned = previouslyPinned.filter((item) => pinned.indexOf(item) < 0);
|
||||
|
||||
|
@ -550,7 +550,10 @@ function textForPinnedEvent(event: MatrixEvent, allowJSX: boolean): () => Render
|
|||
{ senderName },
|
||||
{
|
||||
a: (sub) => (
|
||||
<AccessibleButton kind="link_inline" onClick={(e) => highlightEvent(roomId, messageId)}>
|
||||
<AccessibleButton
|
||||
kind="link_inline"
|
||||
onClick={(e: ButtonEvent) => highlightEvent(roomId, messageId)}
|
||||
>
|
||||
{sub}
|
||||
</AccessibleButton>
|
||||
),
|
||||
|
@ -580,7 +583,10 @@ function textForPinnedEvent(event: MatrixEvent, allowJSX: boolean): () => Render
|
|||
{ senderName },
|
||||
{
|
||||
a: (sub) => (
|
||||
<AccessibleButton kind="link_inline" onClick={(e) => highlightEvent(roomId, messageId)}>
|
||||
<AccessibleButton
|
||||
kind="link_inline"
|
||||
onClick={(e: ButtonEvent) => highlightEvent(roomId, messageId)}
|
||||
>
|
||||
{sub}
|
||||
</AccessibleButton>
|
||||
),
|
||||
|
|
|
@ -168,7 +168,7 @@ export default class UserActivity {
|
|||
return this.activeRecentlyTimeout.isRunning();
|
||||
}
|
||||
|
||||
private onPageVisibilityChanged = (e): void => {
|
||||
private onPageVisibilityChanged = (e: Event): void => {
|
||||
if (this.document.visibilityState === "hidden") {
|
||||
this.activeNowTimeout.abort();
|
||||
this.activeRecentlyTimeout.abort();
|
||||
|
@ -182,11 +182,12 @@ export default class UserActivity {
|
|||
this.activeRecentlyTimeout.abort();
|
||||
};
|
||||
|
||||
private onUserActivity = (event: MouseEvent): void => {
|
||||
// XXX: exported for tests
|
||||
public onUserActivity = (event: Event): void => {
|
||||
// ignore anything if the window isn't focused
|
||||
if (!this.document.hasFocus()) return;
|
||||
|
||||
if (event.screenX && event.type === "mousemove") {
|
||||
if (event.type === "mousemove" && this.isMouseEvent(event)) {
|
||||
if (event.screenX === this.lastScreenX && event.screenY === this.lastScreenY) {
|
||||
// mouse hasn't actually moved
|
||||
return;
|
||||
|
@ -223,4 +224,8 @@ export default class UserActivity {
|
|||
}
|
||||
attachedTimers.forEach((t) => t.abort());
|
||||
}
|
||||
|
||||
private isMouseEvent(event: Event): event is MouseEvent {
|
||||
return event.type.startsWith("mouse");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -38,7 +38,7 @@ export default class VoipUserMapper {
|
|||
return window.mxVoipUserMapper;
|
||||
}
|
||||
|
||||
private async userToVirtualUser(userId: string): Promise<string> {
|
||||
private async userToVirtualUser(userId: string): Promise<string | null> {
|
||||
const results = await LegacyCallHandler.instance.sipVirtualLookup(userId);
|
||||
if (results.length === 0 || !results[0].fields.lookup_success) return null;
|
||||
return results[0].userid;
|
||||
|
@ -59,11 +59,11 @@ export default class VoipUserMapper {
|
|||
if (!virtualUser) return null;
|
||||
|
||||
const virtualRoomId = await ensureVirtualRoomExists(MatrixClientPeg.get(), virtualUser, roomId);
|
||||
MatrixClientPeg.get().setRoomAccountData(virtualRoomId, VIRTUAL_ROOM_EVENT_TYPE, {
|
||||
MatrixClientPeg.get().setRoomAccountData(virtualRoomId!, VIRTUAL_ROOM_EVENT_TYPE, {
|
||||
native_room: roomId,
|
||||
});
|
||||
|
||||
this.virtualToNativeRoomIdCache.set(virtualRoomId, roomId);
|
||||
this.virtualToNativeRoomIdCache.set(virtualRoomId!, roomId);
|
||||
|
||||
return virtualRoomId;
|
||||
}
|
||||
|
@ -72,9 +72,9 @@ export default class VoipUserMapper {
|
|||
* Gets the ID of the virtual room for a room, or null if the room has no
|
||||
* virtual room
|
||||
*/
|
||||
public async getVirtualRoomForRoom(roomId: string): Promise<Room | null> {
|
||||
public async getVirtualRoomForRoom(roomId: string): Promise<Room | undefined> {
|
||||
const virtualUser = await this.getVirtualUserForRoom(roomId);
|
||||
if (!virtualUser) return null;
|
||||
if (!virtualUser) return undefined;
|
||||
|
||||
return findDMForUser(MatrixClientPeg.get(), virtualUser);
|
||||
}
|
||||
|
@ -121,8 +121,12 @@ export default class VoipUserMapper {
|
|||
if (!LegacyCallHandler.instance.getSupportsVirtualRooms()) return;
|
||||
|
||||
const inviterId = invitedRoom.getDMInviter();
|
||||
if (!inviterId) {
|
||||
logger.error("Could not find DM inviter for room id: " + invitedRoom.roomId);
|
||||
}
|
||||
|
||||
logger.log(`Checking virtual-ness of room ID ${invitedRoom.roomId}, invited by ${inviterId}`);
|
||||
const result = await LegacyCallHandler.instance.sipNativeLookup(inviterId);
|
||||
const result = await LegacyCallHandler.instance.sipNativeLookup(inviterId!);
|
||||
if (result.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
@ -141,11 +145,11 @@ export default class VoipUserMapper {
|
|||
// (possibly we should only join if we've also joined the native room, then we'd also have
|
||||
// to make sure we joined virtual rooms on joining a native one)
|
||||
MatrixClientPeg.get().joinRoom(invitedRoom.roomId);
|
||||
}
|
||||
|
||||
// also put this room in the virtual room ID cache so isVirtualRoom return the right answer
|
||||
// in however long it takes for the echo of setAccountData to come down the sync
|
||||
this.virtualToNativeRoomIdCache.set(invitedRoom.roomId, nativeRoom.roomId);
|
||||
// also put this room in the virtual room ID cache so isVirtualRoom return the right answer
|
||||
// in however long it takes for the echo of setAccountData to come down the sync
|
||||
this.virtualToNativeRoomIdCache.set(invitedRoom.roomId, nativeRoom.roomId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,11 +21,11 @@ import { MatrixClientPeg } from "./MatrixClientPeg";
|
|||
import { _t } from "./languageHandler";
|
||||
|
||||
export function usersTypingApartFromMeAndIgnored(room: Room): RoomMember[] {
|
||||
return usersTyping(room, [MatrixClientPeg.get().getUserId()].concat(MatrixClientPeg.get().getIgnoredUsers()));
|
||||
return usersTyping(room, [MatrixClientPeg.get().getUserId()!].concat(MatrixClientPeg.get().getIgnoredUsers()));
|
||||
}
|
||||
|
||||
export function usersTypingApartFromMe(room: Room): RoomMember[] {
|
||||
return usersTyping(room, [MatrixClientPeg.get().getUserId()]);
|
||||
return usersTyping(room, [MatrixClientPeg.get().getUserId()!]);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -36,7 +36,7 @@ export function usersTypingApartFromMe(room: Room): RoomMember[] {
|
|||
* @returns {RoomMember[]} list of user objects who are typing.
|
||||
*/
|
||||
export function usersTyping(room: Room, exclude: string[] = []): RoomMember[] {
|
||||
const whoIsTyping = [];
|
||||
const whoIsTyping: RoomMember[] = [];
|
||||
|
||||
const memberKeys = Object.keys(room.currentState.members);
|
||||
for (const userId of memberKeys) {
|
||||
|
|
|
@ -27,6 +27,7 @@ import {
|
|||
KEYBOARD_SHORTCUTS,
|
||||
MAC_ONLY_SHORTCUTS,
|
||||
} from "./KeyboardShortcuts";
|
||||
import { IBaseSetting } from "../settings/Settings";
|
||||
|
||||
/**
|
||||
* This function gets the keyboard shortcuts that should be presented in the UI
|
||||
|
@ -103,7 +104,7 @@ export const getKeyboardShortcuts = (): IKeyboardShortcuts => {
|
|||
return true;
|
||||
})
|
||||
.reduce((o, key) => {
|
||||
o[key] = KEYBOARD_SHORTCUTS[key];
|
||||
o[key as KeyBindingAction] = KEYBOARD_SHORTCUTS[key as KeyBindingAction];
|
||||
return o;
|
||||
}, {} as IKeyboardShortcuts);
|
||||
};
|
||||
|
@ -112,7 +113,10 @@ export const getKeyboardShortcuts = (): IKeyboardShortcuts => {
|
|||
* Gets keyboard shortcuts that should be presented to the user in the UI.
|
||||
*/
|
||||
export const getKeyboardShortcutsForUI = (): IKeyboardShortcuts => {
|
||||
const entries = [...Object.entries(getUIOnlyShortcuts()), ...Object.entries(getKeyboardShortcuts())];
|
||||
const entries = [...Object.entries(getUIOnlyShortcuts()), ...Object.entries(getKeyboardShortcuts())] as [
|
||||
KeyBindingAction,
|
||||
IBaseSetting<KeyCombo>,
|
||||
][];
|
||||
|
||||
return entries.reduce((acc, [key, value]) => {
|
||||
acc[key] = value;
|
||||
|
@ -120,11 +124,11 @@ export const getKeyboardShortcutsForUI = (): IKeyboardShortcuts => {
|
|||
}, {} as IKeyboardShortcuts);
|
||||
};
|
||||
|
||||
export const getKeyboardShortcutValue = (name: string): KeyCombo | undefined => {
|
||||
export const getKeyboardShortcutValue = (name: KeyBindingAction): KeyCombo | undefined => {
|
||||
return getKeyboardShortcutsForUI()[name]?.default;
|
||||
};
|
||||
|
||||
export const getKeyboardShortcutDisplayName = (name: string): string | undefined => {
|
||||
export const getKeyboardShortcutDisplayName = (name: KeyBindingAction): string | undefined => {
|
||||
const keyboardShortcutDisplayName = getKeyboardShortcutsForUI()[name]?.displayName;
|
||||
return keyboardShortcutDisplayName && _t(keyboardShortcutDisplayName);
|
||||
return keyboardShortcutDisplayName && _t(keyboardShortcutDisplayName as string);
|
||||
};
|
||||
|
|
|
@ -156,10 +156,8 @@ export enum KeyBindingAction {
|
|||
|
||||
type KeyboardShortcutSetting = IBaseSetting<KeyCombo>;
|
||||
|
||||
export type IKeyboardShortcuts = {
|
||||
// TODO: We should figure out what to do with the keyboard shortcuts that are not handled by KeybindingManager
|
||||
[k in KeyBindingAction]?: KeyboardShortcutSetting;
|
||||
};
|
||||
// TODO: We should figure out what to do with the keyboard shortcuts that are not handled by KeybindingManager
|
||||
export type IKeyboardShortcuts = Partial<Record<KeyBindingAction, KeyboardShortcutSetting>>;
|
||||
|
||||
export interface ICategory {
|
||||
categoryLabel?: string;
|
||||
|
|
|
@ -25,6 +25,7 @@ import React, {
|
|||
Reducer,
|
||||
Dispatch,
|
||||
RefObject,
|
||||
ReactNode,
|
||||
} from "react";
|
||||
|
||||
import { getKeyBindingsManager } from "../KeyBindingsManager";
|
||||
|
@ -158,8 +159,8 @@ interface IProps {
|
|||
handleHomeEnd?: boolean;
|
||||
handleUpDown?: boolean;
|
||||
handleLeftRight?: boolean;
|
||||
children(renderProps: { onKeyDownHandler(ev: React.KeyboardEvent) });
|
||||
onKeyDown?(ev: React.KeyboardEvent, state: IState);
|
||||
children(renderProps: { onKeyDownHandler(ev: React.KeyboardEvent): void }): ReactNode;
|
||||
onKeyDown?(ev: React.KeyboardEvent, state: IState): void;
|
||||
}
|
||||
|
||||
export const findSiblingElement = (
|
||||
|
|
|
@ -25,7 +25,7 @@ import { getKeyBindingsManager } from "../../KeyBindingsManager";
|
|||
|
||||
interface IProps extends React.ComponentProps<typeof StyledCheckbox> {
|
||||
label?: string;
|
||||
onChange(); // we handle keyup/down ourselves so lose the ChangeEvent
|
||||
onChange(): void; // we handle keyup/down ourselves so lose the ChangeEvent
|
||||
onClose(): void; // gets called after onChange on KeyBindingAction.ActivateSelectedButton
|
||||
}
|
||||
|
||||
|
|
|
@ -25,7 +25,7 @@ import { getKeyBindingsManager } from "../../KeyBindingsManager";
|
|||
|
||||
interface IProps extends React.ComponentProps<typeof StyledRadioButton> {
|
||||
label?: string;
|
||||
onChange(); // we handle keyup/down ourselves so lose the ChangeEvent
|
||||
onChange(): void; // we handle keyup/down ourselves so lose the ChangeEvent
|
||||
onClose(): void; // gets called after onChange on KeyBindingAction.Enter
|
||||
}
|
||||
|
||||
|
|
|
@ -30,7 +30,7 @@ export const RovingAccessibleButton: React.FC<IProps> = ({ inputRef, onFocus, ..
|
|||
return (
|
||||
<AccessibleButton
|
||||
{...props}
|
||||
onFocus={(event) => {
|
||||
onFocus={(event: React.FocusEvent) => {
|
||||
onFocusInternal();
|
||||
onFocus?.(event);
|
||||
}}
|
||||
|
|
|
@ -31,7 +31,7 @@ export const RovingAccessibleTooltipButton: React.FC<IProps> = ({ inputRef, onFo
|
|||
return (
|
||||
<AccessibleTooltipButton
|
||||
{...props}
|
||||
onFocus={(event) => {
|
||||
onFocus={(event: React.FocusEvent) => {
|
||||
onFocusInternal();
|
||||
onFocus?.(event);
|
||||
}}
|
||||
|
|
|
@ -14,14 +14,14 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import React, { ReactElement } from "react";
|
||||
|
||||
import { useRovingTabIndex } from "../RovingTabIndex";
|
||||
import { FocusHandler, Ref } from "./types";
|
||||
|
||||
interface IProps {
|
||||
inputRef?: Ref;
|
||||
children(renderProps: { onFocus: FocusHandler; isActive: boolean; ref: Ref });
|
||||
children(renderProps: { onFocus: FocusHandler; isActive: boolean; ref: Ref }): ReactElement<any, any>;
|
||||
}
|
||||
|
||||
// Wrapper to allow use of useRovingTabIndex outside of React Functional Components.
|
||||
|
|
|
@ -54,7 +54,7 @@ export default class RoomListActions {
|
|||
oldIndex: number | null,
|
||||
newIndex: number | null,
|
||||
): AsyncActionPayload {
|
||||
let metaData = null;
|
||||
let metaData: Parameters<MatrixClient["setRoomTag"]>[2] | null = null;
|
||||
|
||||
// Is the tag ordered manually?
|
||||
const store = RoomListStore.instance;
|
||||
|
@ -81,7 +81,7 @@ export default class RoomListActions {
|
|||
return asyncAction(
|
||||
"RoomListActions.tagRoom",
|
||||
() => {
|
||||
const promises = [];
|
||||
const promises: Promise<any>[] = [];
|
||||
const roomId = room.roomId;
|
||||
|
||||
// Evil hack to get DMs behaving
|
||||
|
@ -120,7 +120,7 @@ export default class RoomListActions {
|
|||
if (newTag && newTag !== DefaultTagID.DM && (hasChangedSubLists || metaData)) {
|
||||
// metaData is the body of the PUT to set the tag, so it must
|
||||
// at least be an empty object.
|
||||
metaData = metaData || {};
|
||||
metaData = metaData || ({} as typeof metaData);
|
||||
|
||||
const promiseToAdd = matrixClient.setRoomTag(roomId, newTag, metaData).catch(function (err) {
|
||||
logger.error("Failed to add tag " + newTag + " to room: " + err);
|
||||
|
|
|
@ -14,7 +14,8 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import React, { ChangeEvent } from "react";
|
||||
import { Room } from "matrix-js-sdk/src/models/room";
|
||||
|
||||
import { _t } from "../../../../languageHandler";
|
||||
import SdkConfig from "../../../../SdkConfig";
|
||||
|
@ -27,6 +28,7 @@ import Field from "../../../../components/views/elements/Field";
|
|||
import BaseDialog from "../../../../components/views/dialogs/BaseDialog";
|
||||
import DialogButtons from "../../../../components/views/elements/DialogButtons";
|
||||
import { IDialogProps } from "../../../../components/views/dialogs/IDialogProps";
|
||||
import { IIndexStats } from "../../../../indexing/BaseEventIndexManager";
|
||||
|
||||
interface IProps extends IDialogProps {}
|
||||
|
||||
|
@ -43,7 +45,7 @@ interface IState {
|
|||
* Allows the user to introspect the event index state and disable it.
|
||||
*/
|
||||
export default class ManageEventIndexDialog extends React.Component<IProps, IState> {
|
||||
public constructor(props) {
|
||||
public constructor(props: IProps) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
|
@ -56,9 +58,9 @@ export default class ManageEventIndexDialog extends React.Component<IProps, ISta
|
|||
};
|
||||
}
|
||||
|
||||
public updateCurrentRoom = async (room): Promise<void> => {
|
||||
public updateCurrentRoom = async (room: Room): Promise<void> => {
|
||||
const eventIndex = EventIndexPeg.get();
|
||||
let stats;
|
||||
let stats: IIndexStats;
|
||||
|
||||
try {
|
||||
stats = await eventIndex.getStats();
|
||||
|
@ -136,12 +138,12 @@ export default class ManageEventIndexDialog extends React.Component<IProps, ISta
|
|||
Modal.createDialog(DisableEventIndexDialog, null, null, /* priority = */ false, /* static = */ true);
|
||||
};
|
||||
|
||||
private onCrawlerSleepTimeChange = (e): void => {
|
||||
this.setState({ crawlerSleepTime: e.target.value });
|
||||
private onCrawlerSleepTimeChange = (e: ChangeEvent<HTMLInputElement>): void => {
|
||||
this.setState({ crawlerSleepTime: parseInt(e.target.value, 10) });
|
||||
SettingsStore.setValue("crawlerSleepTime", null, SettingLevel.DEVICE, e.target.value);
|
||||
};
|
||||
|
||||
public render(): JSX.Element {
|
||||
public render(): React.ReactNode {
|
||||
const brand = SdkConfig.get().brand;
|
||||
|
||||
let crawlerState;
|
||||
|
|
|
@ -239,7 +239,7 @@ export default class CreateKeyBackupDialog extends React.PureComponent<IProps, I
|
|||
<form onSubmit={this.onPassPhraseNextClick}>
|
||||
<p>
|
||||
{_t(
|
||||
"<b>Warning</b>: You should only set up key backup from a trusted computer.",
|
||||
"<b>Warning</b>: you should only set up key backup from a trusted computer.",
|
||||
{},
|
||||
{ b: (sub) => <b>{sub}</b> },
|
||||
)}
|
||||
|
@ -459,7 +459,7 @@ export default class CreateKeyBackupDialog extends React.PureComponent<IProps, I
|
|||
}
|
||||
}
|
||||
|
||||
public render(): JSX.Element {
|
||||
public render(): React.ReactNode {
|
||||
let content;
|
||||
if (this.state.error) {
|
||||
content = (
|
||||
|
|
|
@ -20,7 +20,7 @@ import FileSaver from "file-saver";
|
|||
import { logger } from "matrix-js-sdk/src/logger";
|
||||
import { IKeyBackupInfo } from "matrix-js-sdk/src/crypto/keybackup";
|
||||
import { TrustInfo } from "matrix-js-sdk/src/crypto/backup";
|
||||
import { CrossSigningKeys } from "matrix-js-sdk/src/matrix";
|
||||
import { CrossSigningKeys, UIAFlow } from "matrix-js-sdk/src/matrix";
|
||||
import { IRecoveryKey } from "matrix-js-sdk/src/crypto/api";
|
||||
import { CryptoEvent } from "matrix-js-sdk/src/crypto";
|
||||
|
||||
|
@ -206,7 +206,7 @@ export default class CreateSecretStorageDialog extends React.PureComponent<IProp
|
|||
logger.log("uploadDeviceSigningKeys advertised no flows!");
|
||||
return;
|
||||
}
|
||||
const canUploadKeysWithPasswordOnly = error.data.flows.some((f) => {
|
||||
const canUploadKeysWithPasswordOnly = error.data.flows.some((f: UIAFlow) => {
|
||||
return f.stages.length === 1 && f.stages[0] === "m.login.password";
|
||||
});
|
||||
this.setState({
|
||||
|
@ -842,7 +842,7 @@ export default class CreateSecretStorageDialog extends React.PureComponent<IProp
|
|||
}
|
||||
}
|
||||
|
||||
public render(): JSX.Element {
|
||||
public render(): React.ReactNode {
|
||||
let content;
|
||||
if (this.state.error) {
|
||||
content = (
|
||||
|
|
|
@ -16,7 +16,7 @@ limitations under the License.
|
|||
*/
|
||||
|
||||
import FileSaver from "file-saver";
|
||||
import React from "react";
|
||||
import React, { ChangeEvent } from "react";
|
||||
import { MatrixClient } from "matrix-js-sdk/src/client";
|
||||
import { logger } from "matrix-js-sdk/src/logger";
|
||||
|
||||
|
@ -127,7 +127,7 @@ export default class ExportE2eKeysDialog extends React.Component<IProps, IState>
|
|||
} as Pick<IState, AnyPassphrase>);
|
||||
};
|
||||
|
||||
public render(): JSX.Element {
|
||||
public render(): React.ReactNode {
|
||||
const disableForm = this.state.phase === Phase.Exporting;
|
||||
|
||||
return (
|
||||
|
@ -163,7 +163,9 @@ export default class ExportE2eKeysDialog extends React.Component<IProps, IState>
|
|||
<Field
|
||||
label={_t("Enter passphrase")}
|
||||
value={this.state.passphrase1}
|
||||
onChange={(e) => this.onPassphraseChange(e, "passphrase1")}
|
||||
onChange={(e: ChangeEvent<HTMLInputElement>) =>
|
||||
this.onPassphraseChange(e, "passphrase1")
|
||||
}
|
||||
autoFocus={true}
|
||||
size={64}
|
||||
type="password"
|
||||
|
@ -174,7 +176,9 @@ export default class ExportE2eKeysDialog extends React.Component<IProps, IState>
|
|||
<Field
|
||||
label={_t("Confirm passphrase")}
|
||||
value={this.state.passphrase2}
|
||||
onChange={(e) => this.onPassphraseChange(e, "passphrase2")}
|
||||
onChange={(e: ChangeEvent<HTMLInputElement>) =>
|
||||
this.onPassphraseChange(e, "passphrase2")
|
||||
}
|
||||
size={64}
|
||||
type="password"
|
||||
disabled={disableForm}
|
||||
|
|
|
@ -73,9 +73,9 @@ export default class ImportE2eKeysDialog extends React.Component<IProps, IState>
|
|||
}
|
||||
|
||||
private onFormChange = (): void => {
|
||||
const files = this.file.current.files || [];
|
||||
const files = this.file.current.files;
|
||||
this.setState({
|
||||
enableSubmit: this.state.passphrase !== "" && files.length > 0,
|
||||
enableSubmit: this.state.passphrase !== "" && !!files?.length,
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -127,7 +127,7 @@ export default class ImportE2eKeysDialog extends React.Component<IProps, IState>
|
|||
return false;
|
||||
};
|
||||
|
||||
public render(): JSX.Element {
|
||||
public render(): React.ReactNode {
|
||||
const disableForm = this.state.phase !== Phase.Edit;
|
||||
|
||||
return (
|
||||
|
|
|
@ -48,13 +48,13 @@ export default class NewRecoveryMethodDialog extends React.PureComponent<IProps>
|
|||
{
|
||||
onFinished: this.props.onFinished,
|
||||
},
|
||||
null,
|
||||
undefined,
|
||||
/* priority = */ false,
|
||||
/* static = */ true,
|
||||
);
|
||||
};
|
||||
|
||||
public render(): JSX.Element {
|
||||
public render(): React.ReactNode {
|
||||
const title = <span className="mx_KeyBackupFailedDialog_title">{_t("New Recovery Method")}</span>;
|
||||
|
||||
const newMethodDetected = <p>{_t("A new Security Phrase and key for Secure Messages have been detected.")}</p>;
|
||||
|
|
|
@ -37,14 +37,14 @@ export default class RecoveryMethodRemovedDialog extends React.PureComponent<IPr
|
|||
this.props.onFinished();
|
||||
Modal.createDialogAsync(
|
||||
import("./CreateKeyBackupDialog") as unknown as Promise<ComponentType<{}>>,
|
||||
null,
|
||||
undefined,
|
||||
null,
|
||||
/* priority = */ false,
|
||||
/* static = */ true,
|
||||
);
|
||||
};
|
||||
|
||||
public render(): JSX.Element {
|
||||
public render(): React.ReactNode {
|
||||
const title = <span className="mx_KeyBackupFailedDialog_title">{_t("Recovery Method Removed")}</span>;
|
||||
|
||||
return (
|
||||
|
|
|
@ -91,7 +91,7 @@ export class PlaybackQueue {
|
|||
|
||||
public unsortedEnqueue(mxEvent: MatrixEvent, playback: Playback): void {
|
||||
// We don't ever detach our listeners: we expect the Playback to clean up for us
|
||||
this.playbacks.set(mxEvent.getId(), playback);
|
||||
this.playbacks.set(mxEvent.getId()!, playback);
|
||||
playback.on(UPDATE_EVENT, (state) => this.onPlaybackStateChange(playback, mxEvent, state));
|
||||
playback.clockInfo.liveData.onUpdate((clock) => this.onPlaybackClock(playback, mxEvent, clock));
|
||||
}
|
||||
|
@ -99,12 +99,12 @@ export class PlaybackQueue {
|
|||
private onPlaybackStateChange(playback: Playback, mxEvent: MatrixEvent, newState: PlaybackState): void {
|
||||
// Remember where the user got to in playback
|
||||
const wasLastPlaying = this.currentPlaybackId === mxEvent.getId();
|
||||
if (newState === PlaybackState.Stopped && this.clockStates.has(mxEvent.getId()) && !wasLastPlaying) {
|
||||
if (newState === PlaybackState.Stopped && this.clockStates.has(mxEvent.getId()!) && !wasLastPlaying) {
|
||||
// noinspection JSIgnoredPromiseFromCall
|
||||
playback.skipTo(this.clockStates.get(mxEvent.getId())!);
|
||||
playback.skipTo(this.clockStates.get(mxEvent.getId()!)!);
|
||||
} else if (newState === PlaybackState.Stopped) {
|
||||
// Remove the now-useless clock for some space savings
|
||||
this.clockStates.delete(mxEvent.getId());
|
||||
this.clockStates.delete(mxEvent.getId()!);
|
||||
|
||||
if (wasLastPlaying) {
|
||||
this.recentFullPlays.add(this.currentPlaybackId);
|
||||
|
@ -133,7 +133,7 @@ export class PlaybackQueue {
|
|||
// timeline is already most recent last, so we can iterate down that.
|
||||
const timeline = arrayFastClone(this.room.getLiveTimeline().getEvents());
|
||||
let scanForVoiceMessage = false;
|
||||
let nextEv: MatrixEvent;
|
||||
let nextEv: MatrixEvent | undefined;
|
||||
for (const event of timeline) {
|
||||
if (event.getId() === mxEvent.getId()) {
|
||||
scanForVoiceMessage = true;
|
||||
|
@ -149,8 +149,8 @@ export class PlaybackQueue {
|
|||
break; // Stop automatic playback: next useful event is not a voice message
|
||||
}
|
||||
|
||||
const havePlayback = this.playbacks.has(event.getId());
|
||||
const isRecentlyCompleted = this.recentFullPlays.has(event.getId());
|
||||
const havePlayback = this.playbacks.has(event.getId()!);
|
||||
const isRecentlyCompleted = this.recentFullPlays.has(event.getId()!);
|
||||
if (havePlayback && !isRecentlyCompleted) {
|
||||
nextEv = event;
|
||||
break;
|
||||
|
@ -164,7 +164,7 @@ export class PlaybackQueue {
|
|||
} else {
|
||||
this.playbackIdOrder = orderClone;
|
||||
|
||||
const instance = this.playbacks.get(nextEv.getId());
|
||||
const instance = this.playbacks.get(nextEv.getId()!);
|
||||
PlaybackManager.instance.pauseAllExcept(instance);
|
||||
|
||||
// This should cause a Play event, which will re-populate our playback order
|
||||
|
@ -196,7 +196,7 @@ export class PlaybackQueue {
|
|||
}
|
||||
}
|
||||
|
||||
this.currentPlaybackId = mxEvent.getId();
|
||||
this.currentPlaybackId = mxEvent.getId()!;
|
||||
if (order.length === 0 || order[order.length - 1] !== this.currentPlaybackId) {
|
||||
order.push(this.currentPlaybackId);
|
||||
}
|
||||
|
@ -214,7 +214,7 @@ export class PlaybackQueue {
|
|||
if (playback.currentState === PlaybackState.Decoding) return; // ignore pre-ready values
|
||||
|
||||
if (playback.currentState !== PlaybackState.Stopped) {
|
||||
this.clockStates.set(mxEvent.getId(), clocks[0]); // [0] is the current seek position
|
||||
this.clockStates.set(mxEvent.getId()!, clocks[0]); // [0] is the current seek position
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -43,7 +43,11 @@ class MxVoiceWorklet extends AudioWorkletProcessor {
|
|||
private nextAmplitudeSecond = 0;
|
||||
private amplitudeIndex = 0;
|
||||
|
||||
public process(inputs, outputs, parameters): boolean {
|
||||
public process(
|
||||
inputs: Float32Array[][],
|
||||
outputs: Float32Array[][],
|
||||
parameters: Record<string, Float32Array>,
|
||||
): boolean {
|
||||
const currentSecond = roundTimeToTargetFreq(currentTime);
|
||||
// We special case the first ping because there's a fairly good chance that we'll miss the zeroth
|
||||
// update. Firefox for instance takes 0.06 seconds (roughly) to call this function for the first
|
||||
|
|
|
@ -14,7 +14,6 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
// @ts-ignore
|
||||
import Recorder from "opus-recorder/dist/recorder.min.js";
|
||||
import encoderPath from "opus-recorder/dist/encoderWorker.min.js";
|
||||
import { SimpleObservable } from "matrix-widget-api";
|
||||
|
@ -78,7 +77,7 @@ export class VoiceRecording extends EventEmitter implements IDestroyable {
|
|||
private targetMaxLength: number | null = TARGET_MAX_LENGTH;
|
||||
public amplitudes: number[] = []; // at each second mark, generated
|
||||
private liveWaveform = new FixedRollingArray(RECORDING_PLAYBACK_SAMPLES, 0);
|
||||
public onDataAvailable: (data: ArrayBuffer) => void;
|
||||
public onDataAvailable?: (data: ArrayBuffer) => void;
|
||||
|
||||
public get contentType(): string {
|
||||
return "audio/ogg";
|
||||
|
@ -182,7 +181,7 @@ export class VoiceRecording extends EventEmitter implements IDestroyable {
|
|||
});
|
||||
|
||||
// not using EventEmitter here because it leads to detached bufferes
|
||||
this.recorder.ondataavailable = (data: ArrayBuffer) => this?.onDataAvailable(data);
|
||||
this.recorder.ondataavailable = (data: ArrayBuffer) => this.onDataAvailable?.(data);
|
||||
} catch (e) {
|
||||
logger.error("Error starting recording: ", e);
|
||||
if (e instanceof DOMException) {
|
||||
|
|
|
@ -69,7 +69,7 @@ export function decodeOgg(audioBuffer: ArrayBuffer): Promise<ArrayBuffer> {
|
|||
command: "encode",
|
||||
buffers: ev.data,
|
||||
},
|
||||
ev.data.map((b) => b.buffer),
|
||||
ev.data.map((b: Float32Array) => b.buffer),
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -70,7 +70,7 @@ export default abstract class AutocompleteProvider {
|
|||
* @param {boolean} force True if the user is forcing completion
|
||||
* @return {object} { command, range } where both objects fields are null if no match
|
||||
*/
|
||||
public getCurrentCommand(query: string, selection: ISelectionRange, force = false): ICommand | null {
|
||||
public getCurrentCommand(query: string, selection: ISelectionRange, force = false): Partial<ICommand> {
|
||||
let commandRegex = this.commandRegex;
|
||||
|
||||
if (force && this.shouldForceComplete()) {
|
||||
|
@ -78,7 +78,7 @@ export default abstract class AutocompleteProvider {
|
|||
}
|
||||
|
||||
if (!commandRegex) {
|
||||
return null;
|
||||
return {};
|
||||
}
|
||||
|
||||
commandRegex.lastIndex = 0;
|
||||
|
|
|
@ -95,7 +95,7 @@ export default class CommandProvider extends AutocompleteProvider {
|
|||
description={_t(result.description)}
|
||||
/>
|
||||
),
|
||||
range,
|
||||
range: range!,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
|
|
@ -19,7 +19,7 @@ limitations under the License.
|
|||
*/
|
||||
|
||||
import React from "react";
|
||||
import { uniq, sortBy } from "lodash";
|
||||
import { uniq, sortBy, ListIteratee } from "lodash";
|
||||
import EMOTICON_REGEX from "emojibase-regex/emoticon";
|
||||
import { Room } from "matrix-js-sdk/src/models/room";
|
||||
|
||||
|
@ -55,7 +55,11 @@ const SORTED_EMOJI: ISortedEmoji[] = EMOJI.sort((a, b) => {
|
|||
_orderBy: index,
|
||||
}));
|
||||
|
||||
function score(query: string, space: string): number {
|
||||
function score(query: string, space: string[] | string): number {
|
||||
if (Array.isArray(space)) {
|
||||
return Math.min(...space.map((s) => score(query, s)));
|
||||
}
|
||||
|
||||
const index = space.indexOf(query);
|
||||
if (index === -1) {
|
||||
return Infinity;
|
||||
|
@ -90,7 +94,7 @@ export default class EmojiProvider extends AutocompleteProvider {
|
|||
shouldMatchWordsOnly: true,
|
||||
});
|
||||
|
||||
this.recentlyUsed = Array.from(new Set(recent.get().map(getEmojiFromUnicode).filter(Boolean)));
|
||||
this.recentlyUsed = Array.from(new Set(recent.get().map(getEmojiFromUnicode).filter(Boolean))) as IEmoji[];
|
||||
}
|
||||
|
||||
public async getCompletions(
|
||||
|
@ -113,7 +117,7 @@ export default class EmojiProvider extends AutocompleteProvider {
|
|||
// Do second match with shouldMatchWordsOnly in order to match against 'name'
|
||||
completions = completions.concat(this.nameMatcher.match(matchedString));
|
||||
|
||||
let sorters = [];
|
||||
let sorters: ListIteratee<ISortedEmoji>[] = [];
|
||||
// make sure that emoticons come first
|
||||
sorters.push((c) => score(matchedString, c.emoji.emoticon || ""));
|
||||
|
||||
|
@ -148,7 +152,7 @@ export default class EmojiProvider extends AutocompleteProvider {
|
|||
<span>{c.emoji.unicode}</span>
|
||||
</PillCompletion>
|
||||
),
|
||||
range,
|
||||
range: range!,
|
||||
}));
|
||||
}
|
||||
return [];
|
||||
|
|
|
@ -40,12 +40,13 @@ export default class NotifProvider extends AutocompleteProvider {
|
|||
): Promise<ICompletion[]> {
|
||||
const client = MatrixClientPeg.get();
|
||||
|
||||
if (!this.room.currentState.mayTriggerNotifOfType("room", client.credentials.userId)) return [];
|
||||
if (!this.room.currentState.mayTriggerNotifOfType("room", client.credentials.userId!)) return [];
|
||||
|
||||
const { command, range } = this.getCurrentCommand(query, selection, force);
|
||||
if (
|
||||
command?.[0].length > 1 &&
|
||||
["@room", "@channel", "@everyone", "@here"].some((c) => c.startsWith(command[0]))
|
||||
command?.[0] &&
|
||||
command[0].length > 1 &&
|
||||
["@room", "@channel", "@everyone", "@here"].some((c) => c.startsWith(command![0]))
|
||||
) {
|
||||
return [
|
||||
{
|
||||
|
@ -58,7 +59,7 @@ export default class NotifProvider extends AutocompleteProvider {
|
|||
<RoomAvatar width={24} height={24} room={this.room} />
|
||||
</PillCompletion>
|
||||
),
|
||||
range,
|
||||
range: range!,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
|
|
@ -88,7 +88,7 @@ export default class QueryMatcher<T extends {}> {
|
|||
if (!this._items.has(key)) {
|
||||
this._items.set(key, []);
|
||||
}
|
||||
this._items.get(key).push({
|
||||
this._items.get(key)!.push({
|
||||
keyWeight: Number(index),
|
||||
object,
|
||||
});
|
||||
|
@ -104,7 +104,11 @@ export default class QueryMatcher<T extends {}> {
|
|||
if (query.length === 0) {
|
||||
return [];
|
||||
}
|
||||
const matches = [];
|
||||
const matches: {
|
||||
index: number;
|
||||
object: T;
|
||||
keyWeight: number;
|
||||
}[] = [];
|
||||
// Iterate through the map & check each key.
|
||||
// ES6 Map iteration order is defined to be insertion order, so results
|
||||
// here will come out in the order they were put in.
|
||||
|
|
|
@ -39,12 +39,12 @@ function canonicalScore(displayedAlias: string, room: Room): number {
|
|||
|
||||
function matcherObject(
|
||||
room: Room,
|
||||
displayedAlias: string | null,
|
||||
displayedAlias: string,
|
||||
matchName = "",
|
||||
): {
|
||||
room: Room;
|
||||
matchName: string;
|
||||
displayedAlias: string | null;
|
||||
displayedAlias: string;
|
||||
} {
|
||||
return {
|
||||
room,
|
||||
|
@ -81,7 +81,7 @@ export default class RoomProvider extends AutocompleteProvider {
|
|||
// the only reason we need to do this is because Fuse only matches on properties
|
||||
let matcherObjects = this.getRooms().reduce<ReturnType<typeof matcherObject>[]>((aliases, room) => {
|
||||
if (room.getCanonicalAlias()) {
|
||||
aliases = aliases.concat(matcherObject(room, room.getCanonicalAlias(), room.name));
|
||||
aliases = aliases.concat(matcherObject(room, room.getCanonicalAlias()!, room.name));
|
||||
}
|
||||
if (room.getAltAliases().length) {
|
||||
const altAliases = room.getAltAliases().map((alias) => matcherObject(room, alias));
|
||||
|
@ -122,7 +122,7 @@ export default class RoomProvider extends AutocompleteProvider {
|
|||
<RoomAvatar width={24} height={24} room={room.room} />
|
||||
</PillCompletion>
|
||||
),
|
||||
range,
|
||||
range: range!,
|
||||
}),
|
||||
)
|
||||
.filter((completion) => !!completion.completion && completion.completion.length > 0);
|
||||
|
|
|
@ -44,7 +44,7 @@ const FORCED_USER_REGEX = /[^/,:; \t\n]\S*/g;
|
|||
|
||||
export default class UserProvider extends AutocompleteProvider {
|
||||
public matcher: QueryMatcher<RoomMember>;
|
||||
public users: RoomMember[];
|
||||
public users: RoomMember[] | null;
|
||||
public room: Room;
|
||||
|
||||
public constructor(room: Room, renderingType?: TimelineRenderingType) {
|
||||
|
@ -54,7 +54,7 @@ export default class UserProvider extends AutocompleteProvider {
|
|||
renderingType,
|
||||
});
|
||||
this.room = room;
|
||||
this.matcher = new QueryMatcher([], {
|
||||
this.matcher = new QueryMatcher<RoomMember>([], {
|
||||
keys: ["name"],
|
||||
funcs: [(obj) => obj.userId.slice(1)], // index by user id minus the leading '@'
|
||||
shouldMatchWordsOnly: false,
|
||||
|
@ -73,7 +73,7 @@ export default class UserProvider extends AutocompleteProvider {
|
|||
|
||||
private onRoomTimeline = (
|
||||
ev: MatrixEvent,
|
||||
room: Room | null,
|
||||
room: Room | undefined,
|
||||
toStartOfTimeline: boolean,
|
||||
removed: boolean,
|
||||
data: IRoomTimelineData,
|
||||
|
@ -110,18 +110,15 @@ export default class UserProvider extends AutocompleteProvider {
|
|||
// lazy-load user list into matcher
|
||||
if (!this.users) this.makeUsers();
|
||||
|
||||
let completions = [];
|
||||
const { command, range } = this.getCurrentCommand(rawQuery, selection, force);
|
||||
|
||||
if (!command) return completions;
|
||||
|
||||
const fullMatch = command[0];
|
||||
const fullMatch = command?.[0];
|
||||
// Don't search if the query is a single "@"
|
||||
if (fullMatch && fullMatch !== "@") {
|
||||
// Don't include the '@' in our search query - it's only used as a way to trigger completion
|
||||
const query = fullMatch.startsWith("@") ? fullMatch.substring(1) : fullMatch;
|
||||
completions = this.matcher.match(query, limit).map((user) => {
|
||||
const description = UserIdentifierCustomisations.getDisplayUserIdentifier(user.userId, {
|
||||
return this.matcher.match(query, limit).map((user) => {
|
||||
const description = UserIdentifierCustomisations.getDisplayUserIdentifier?.(user.userId, {
|
||||
roomId: this.room.roomId,
|
||||
withDisplayName: true,
|
||||
});
|
||||
|
@ -132,18 +129,18 @@ export default class UserProvider extends AutocompleteProvider {
|
|||
completion: user.rawDisplayName,
|
||||
completionId: user.userId,
|
||||
type: "user",
|
||||
suffix: selection.beginning && range.start === 0 ? ": " : " ",
|
||||
suffix: selection.beginning && range!.start === 0 ? ": " : " ",
|
||||
href: makeUserPermalink(user.userId),
|
||||
component: (
|
||||
<PillCompletion title={displayName} description={description}>
|
||||
<MemberAvatar member={user} width={24} height={24} />
|
||||
</PillCompletion>
|
||||
),
|
||||
range,
|
||||
range: range!,
|
||||
};
|
||||
});
|
||||
}
|
||||
return completions;
|
||||
return [];
|
||||
}
|
||||
|
||||
public getName(): string {
|
||||
|
@ -152,10 +149,10 @@ export default class UserProvider extends AutocompleteProvider {
|
|||
|
||||
private makeUsers(): void {
|
||||
const events = this.room.getLiveTimeline().getEvents();
|
||||
const lastSpoken = {};
|
||||
const lastSpoken: Record<string, number> = {};
|
||||
|
||||
for (const event of events) {
|
||||
lastSpoken[event.getSender()] = event.getTs();
|
||||
lastSpoken[event.getSender()!] = event.getTs();
|
||||
}
|
||||
|
||||
const currentUserId = MatrixClientPeg.get().credentials.userId;
|
||||
|
@ -167,7 +164,7 @@ export default class UserProvider extends AutocompleteProvider {
|
|||
this.matcher.setObjects(this.users);
|
||||
}
|
||||
|
||||
public onUserSpoke(user: RoomMember): void {
|
||||
public onUserSpoke(user: RoomMember | null): void {
|
||||
if (!this.users) return;
|
||||
if (!user) return;
|
||||
if (user.userId === MatrixClientPeg.get().credentials.userId) return;
|
||||
|
|
|
@ -55,7 +55,7 @@ export default class AutoHideScrollbar<T extends keyof JSX.IntrinsicElements> ex
|
|||
}
|
||||
}
|
||||
|
||||
public render(): JSX.Element {
|
||||
public render(): React.ReactNode {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const { element, className, onScroll, tabIndex, wrappedRef, children, ...otherProps } = this.props;
|
||||
|
||||
|
|
|
@ -99,9 +99,9 @@ export interface IProps extends MenuProps {
|
|||
closeOnInteraction?: boolean;
|
||||
|
||||
// Function to be called on menu close
|
||||
onFinished();
|
||||
onFinished(): void;
|
||||
// on resize callback
|
||||
windowResize?();
|
||||
windowResize?(): void;
|
||||
}
|
||||
|
||||
interface IState {
|
||||
|
@ -119,8 +119,8 @@ export default class ContextMenu extends React.PureComponent<IProps, IState> {
|
|||
managed: true,
|
||||
};
|
||||
|
||||
public constructor(props, context) {
|
||||
super(props, context);
|
||||
public constructor(props: IProps) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
contextMenuElem: null,
|
||||
|
@ -387,13 +387,13 @@ export default class ContextMenu extends React.PureComponent<IProps, IState> {
|
|||
menuStyle["paddingRight"] = menuPaddingRight;
|
||||
}
|
||||
|
||||
const wrapperStyle = {};
|
||||
const wrapperStyle: CSSProperties = {};
|
||||
if (!isNaN(Number(zIndex))) {
|
||||
menuStyle["zIndex"] = zIndex + 1;
|
||||
wrapperStyle["zIndex"] = zIndex;
|
||||
}
|
||||
|
||||
let background;
|
||||
let background: JSX.Element;
|
||||
if (hasBackground) {
|
||||
background = (
|
||||
<div
|
||||
|
@ -624,7 +624,7 @@ export function createMenu(
|
|||
ElementClass: typeof React.Component,
|
||||
props: Record<string, any>,
|
||||
): { close: (...args: any[]) => void } {
|
||||
const onFinished = function (...args): void {
|
||||
const onFinished = function (...args: any[]): void {
|
||||
ReactDOM.unmountComponentAtNode(getOrCreateContainer());
|
||||
props?.onFinished?.apply(null, args);
|
||||
};
|
||||
|
|
|
@ -118,7 +118,7 @@ export default class EmbeddedPage extends React.PureComponent<IProps, IState> {
|
|||
}
|
||||
};
|
||||
|
||||
public render(): JSX.Element {
|
||||
public render(): React.ReactNode {
|
||||
// HACK: Workaround for the context's MatrixClient not updating.
|
||||
const client = this.context || MatrixClientPeg.get();
|
||||
const isGuest = client ? client.isGuest() : true;
|
||||
|
|
|
@ -43,7 +43,7 @@ interface IProps {
|
|||
}
|
||||
|
||||
interface IState {
|
||||
timelineSet: EventTimelineSet;
|
||||
timelineSet: EventTimelineSet | null;
|
||||
narrow: boolean;
|
||||
}
|
||||
|
||||
|
@ -59,7 +59,7 @@ class FilePanel extends React.Component<IProps, IState> {
|
|||
public noRoom: boolean;
|
||||
private card = createRef<HTMLDivElement>();
|
||||
|
||||
public state = {
|
||||
public state: IState = {
|
||||
timelineSet: null,
|
||||
narrow: false,
|
||||
};
|
||||
|
@ -223,7 +223,7 @@ class FilePanel extends React.Component<IProps, IState> {
|
|||
}
|
||||
}
|
||||
|
||||
public render(): JSX.Element {
|
||||
public render(): React.ReactNode {
|
||||
if (MatrixClientPeg.get().isGuest()) {
|
||||
return (
|
||||
<BaseCard className="mx_FilePanel mx_RoomView_messageListWrapper" onClose={this.props.onClose}>
|
||||
|
|
|
@ -79,6 +79,12 @@ export function GenericDropdownMenuGroup<T extends Key>({
|
|||
);
|
||||
}
|
||||
|
||||
function isGenericDropdownMenuGroupArray<T>(
|
||||
items: readonly GenericDropdownMenuItem<T>[],
|
||||
): items is GenericDropdownMenuGroup<T>[] {
|
||||
return isGenericDropdownMenuGroup(items[0]);
|
||||
}
|
||||
|
||||
function isGenericDropdownMenuGroup<T>(item: GenericDropdownMenuItem<T>): item is GenericDropdownMenuGroup<T> {
|
||||
return "options" in item;
|
||||
}
|
||||
|
@ -123,19 +129,19 @@ export function GenericDropdownMenu<T>({
|
|||
.flatMap((it) => (isGenericDropdownMenuGroup(it) ? [it, ...it.options] : [it]))
|
||||
.find((option) => (toKey ? toKey(option.key) === toKey(value) : option.key === value));
|
||||
let contextMenuOptions: JSX.Element;
|
||||
if (options && isGenericDropdownMenuGroup(options[0])) {
|
||||
if (options && isGenericDropdownMenuGroupArray(options)) {
|
||||
contextMenuOptions = (
|
||||
<>
|
||||
{options.map((group) => (
|
||||
<GenericDropdownMenuGroup
|
||||
key={toKey?.(group.key) ?? group.key}
|
||||
key={toKey?.(group.key) ?? (group.key as Key)}
|
||||
label={group.label}
|
||||
description={group.description}
|
||||
adornment={group.adornment}
|
||||
>
|
||||
{group.options.map((option) => (
|
||||
<GenericDropdownMenuOption
|
||||
key={toKey?.(option.key) ?? option.key}
|
||||
key={toKey?.(option.key) ?? (option.key as Key)}
|
||||
label={option.label}
|
||||
description={option.description}
|
||||
onClick={(ev: ButtonEvent) => {
|
||||
|
@ -156,7 +162,7 @@ export function GenericDropdownMenu<T>({
|
|||
<>
|
||||
{options.map((option) => (
|
||||
<GenericDropdownMenuOption
|
||||
key={toKey?.(option.key) ?? option.key}
|
||||
key={toKey?.(option.key) ?? (option.key as Key)}
|
||||
label={option.label}
|
||||
description={option.description}
|
||||
onClick={(ev: ButtonEvent) => {
|
||||
|
|
|
@ -22,7 +22,7 @@ interface IProps {
|
|||
}
|
||||
|
||||
export default class GenericErrorPage extends React.PureComponent<IProps> {
|
||||
public render(): JSX.Element {
|
||||
public render(): React.ReactNode {
|
||||
return (
|
||||
<div className="mx_GenericErrorPage">
|
||||
<div className="mx_GenericErrorPage_box">
|
||||
|
|
|
@ -177,7 +177,7 @@ export default class IndicatorScrollbar<T extends keyof JSX.IntrinsicElements> e
|
|||
}
|
||||
};
|
||||
|
||||
public render(): JSX.Element {
|
||||
public render(): React.ReactNode {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const { children, trackHorizontalOverflow, verticalScrollsHorizontally, ...otherProps } = this.props;
|
||||
|
||||
|
|
|
@ -80,7 +80,7 @@ interface IProps {
|
|||
// Called when the stage changes, or the stage's phase changes. First
|
||||
// argument is the stage, second is the phase. Some stages do not have
|
||||
// phases and will be counted as 0 (numeric).
|
||||
onStagePhaseChange?(stage: string, phase: string | number): void;
|
||||
onStagePhaseChange?(stage: AuthType, phase: number): void;
|
||||
}
|
||||
|
||||
interface IState {
|
||||
|
@ -99,7 +99,7 @@ export default class InteractiveAuthComponent extends React.Component<IProps, IS
|
|||
|
||||
private unmounted = false;
|
||||
|
||||
public constructor(props) {
|
||||
public constructor(props: IProps) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
|
@ -249,7 +249,7 @@ export default class InteractiveAuthComponent extends React.Component<IProps, IS
|
|||
this.authLogic.setEmailSid(sid);
|
||||
};
|
||||
|
||||
public render(): JSX.Element {
|
||||
public render(): React.ReactNode {
|
||||
const stage = this.state.authStage;
|
||||
if (!stage) {
|
||||
if (this.state.busy) {
|
||||
|
|
|
@ -68,7 +68,7 @@ interface IState {
|
|||
export default class LeftPanel extends React.Component<IProps, IState> {
|
||||
private listContainerRef = createRef<HTMLDivElement>();
|
||||
private roomListRef = createRef<RoomList>();
|
||||
private focusedElement = null;
|
||||
private focusedElement: Element = null;
|
||||
private isDoingStickyHeaders = false;
|
||||
|
||||
public constructor(props: IProps) {
|
||||
|
|
|
@ -136,8 +136,8 @@ class LoggedInView extends React.Component<IProps, IState> {
|
|||
protected backgroundImageWatcherRef: string;
|
||||
protected resizer: Resizer;
|
||||
|
||||
public constructor(props, context) {
|
||||
super(props, context);
|
||||
public constructor(props: IProps) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
syncErrorData: undefined,
|
||||
|
@ -229,8 +229,8 @@ class LoggedInView extends React.Component<IProps, IState> {
|
|||
};
|
||||
|
||||
private createResizer(): Resizer {
|
||||
let panelSize;
|
||||
let panelCollapsed;
|
||||
let panelSize: number;
|
||||
let panelCollapsed: boolean;
|
||||
const collapseConfig: ICollapseConfig = {
|
||||
// TODO decrease this once Spaces launches as it'll no longer need to include the 56px Community Panel
|
||||
toggleSize: 206 - 50,
|
||||
|
@ -341,7 +341,7 @@ class LoggedInView extends React.Component<IProps, IState> {
|
|||
const serverNoticeList = RoomListStore.instance.orderedLists[DefaultTagID.ServerNotice];
|
||||
if (!serverNoticeList) return;
|
||||
|
||||
const events = [];
|
||||
const events: MatrixEvent[] = [];
|
||||
let pinnedEventTs = 0;
|
||||
for (const room of serverNoticeList) {
|
||||
const pinStateEvent = room.currentState.getStateEvents("m.room.pinned_events", "");
|
||||
|
@ -369,7 +369,7 @@ class LoggedInView extends React.Component<IProps, IState> {
|
|||
e.getContent()["server_notice_type"] === "m.server_notice.usage_limit_reached"
|
||||
);
|
||||
});
|
||||
const usageLimitEventContent = usageLimitEvent && usageLimitEvent.getContent();
|
||||
const usageLimitEventContent = usageLimitEvent?.getContent<IUsageLimit>();
|
||||
this.calculateServerLimitToast(this.state.syncErrorData, usageLimitEventContent);
|
||||
this.setState({
|
||||
usageLimitEventContent,
|
||||
|
@ -422,13 +422,13 @@ class LoggedInView extends React.Component<IProps, IState> {
|
|||
We also listen with a native listener on the document to get keydown events when no element is focused.
|
||||
Bubbling is irrelevant here as the target is the body element.
|
||||
*/
|
||||
private onReactKeyDown = (ev): void => {
|
||||
private onReactKeyDown = (ev: React.KeyboardEvent): void => {
|
||||
// events caught while bubbling up on the root element
|
||||
// of this component, so something must be focused.
|
||||
this.onKeyDown(ev);
|
||||
};
|
||||
|
||||
private onNativeKeyDown = (ev): void => {
|
||||
private onNativeKeyDown = (ev: KeyboardEvent): void => {
|
||||
// only pass this if there is no focused element.
|
||||
// if there is, onKeyDown will be called by the
|
||||
// react keydown handler that respects the react bubbling order.
|
||||
|
@ -437,7 +437,7 @@ class LoggedInView extends React.Component<IProps, IState> {
|
|||
}
|
||||
};
|
||||
|
||||
private onKeyDown = (ev): void => {
|
||||
private onKeyDown = (ev: React.KeyboardEvent | KeyboardEvent): void => {
|
||||
let handled = false;
|
||||
|
||||
const roomAction = getKeyBindingsManager().getRoomAction(ev);
|
||||
|
@ -571,7 +571,7 @@ class LoggedInView extends React.Component<IProps, IState> {
|
|||
) {
|
||||
dis.dispatch<SwitchSpacePayload>({
|
||||
action: Action.SwitchSpace,
|
||||
num: ev.code.slice(5), // Cut off the first 5 characters - "Digit"
|
||||
num: parseInt(ev.code.slice(5), 10), // Cut off the first 5 characters - "Digit"
|
||||
});
|
||||
handled = true;
|
||||
}
|
||||
|
@ -615,13 +615,11 @@ class LoggedInView extends React.Component<IProps, IState> {
|
|||
* dispatch a page-up/page-down/etc to the appropriate component
|
||||
* @param {Object} ev The key event
|
||||
*/
|
||||
private onScrollKeyPressed = (ev): void => {
|
||||
if (this._roomView.current) {
|
||||
this._roomView.current.handleScrollKey(ev);
|
||||
}
|
||||
private onScrollKeyPressed = (ev: React.KeyboardEvent | KeyboardEvent): void => {
|
||||
this._roomView.current?.handleScrollKey(ev);
|
||||
};
|
||||
|
||||
public render(): JSX.Element {
|
||||
public render(): React.ReactNode {
|
||||
let pageElement;
|
||||
|
||||
switch (this.props.page_type) {
|
||||
|
|
|
@ -47,7 +47,7 @@ export default class MainSplit extends React.Component<IProps> {
|
|||
};
|
||||
|
||||
private loadSidePanelSize(): { height: string | number; width: number } {
|
||||
let rhsSize = parseInt(window.localStorage.getItem("mx_rhs_size"), 10);
|
||||
let rhsSize = parseInt(window.localStorage.getItem("mx_rhs_size")!, 10);
|
||||
|
||||
if (isNaN(rhsSize)) {
|
||||
rhsSize = 350;
|
||||
|
@ -59,7 +59,7 @@ export default class MainSplit extends React.Component<IProps> {
|
|||
};
|
||||
}
|
||||
|
||||
public render(): JSX.Element {
|
||||
public render(): React.ReactNode {
|
||||
const bodyView = React.Children.only(this.props.children);
|
||||
const panelView = this.props.panel;
|
||||
|
||||
|
|
|
@ -419,7 +419,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
|||
window.addEventListener("resize", this.onWindowResized);
|
||||
}
|
||||
|
||||
public componentDidUpdate(prevProps, prevState): void {
|
||||
public componentDidUpdate(prevProps: IProps, prevState: IState): void {
|
||||
if (this.shouldTrackPageChange(prevState, this.state)) {
|
||||
const durationMs = this.stopPageChangeTimer();
|
||||
PosthogTrackers.instance.trackPageChange(this.state.view, this.state.page_type, durationMs);
|
||||
|
@ -544,12 +544,11 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
|||
if (state.view === undefined) {
|
||||
throw new Error("setStateForNewView with no view!");
|
||||
}
|
||||
const newState = {
|
||||
currentUserId: null,
|
||||
this.setState({
|
||||
currentUserId: undefined,
|
||||
justRegistered: false,
|
||||
};
|
||||
Object.assign(newState, state);
|
||||
this.setState(newState);
|
||||
...state,
|
||||
} as IState);
|
||||
}
|
||||
|
||||
private onAction = (payload: ActionPayload): void => {
|
||||
|
@ -2022,7 +2021,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
|||
return fragmentAfterLogin;
|
||||
}
|
||||
|
||||
public render(): JSX.Element {
|
||||
public render(): React.ReactNode {
|
||||
const fragmentAfterLogin = this.getFragmentAfterLogin();
|
||||
let view = null;
|
||||
|
||||
|
|
|
@ -14,12 +14,12 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React, { createRef, KeyboardEvent, ReactNode, TransitionEvent } from "react";
|
||||
import React, { createRef, ReactNode, TransitionEvent } from "react";
|
||||
import ReactDOM from "react-dom";
|
||||
import classNames from "classnames";
|
||||
import { Room } from "matrix-js-sdk/src/models/room";
|
||||
import { EventType } from "matrix-js-sdk/src/@types/event";
|
||||
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
|
||||
import { EventStatus, MatrixEvent } from "matrix-js-sdk/src/models/event";
|
||||
import { logger } from "matrix-js-sdk/src/logger";
|
||||
import { RoomStateEvent } from "matrix-js-sdk/src/models/room-state";
|
||||
import { M_BEACON_INFO } from "matrix-js-sdk/src/@types/beacon";
|
||||
|
@ -34,7 +34,7 @@ import SettingsStore from "../../settings/SettingsStore";
|
|||
import RoomContext, { TimelineRenderingType } from "../../contexts/RoomContext";
|
||||
import { Layout } from "../../settings/enums/Layout";
|
||||
import { _t } from "../../languageHandler";
|
||||
import EventTile, { UnwrappedEventTile, GetRelationsForEvent, IReadReceiptProps } from "../views/rooms/EventTile";
|
||||
import EventTile, { GetRelationsForEvent, IReadReceiptProps, UnwrappedEventTile } from "../views/rooms/EventTile";
|
||||
import { hasText } from "../../TextForEvent";
|
||||
import IRCTimelineProfileResizer from "../views/elements/IRCTimelineProfileResizer";
|
||||
import DMRoomMap from "../../utils/DMRoomMap";
|
||||
|
@ -272,7 +272,7 @@ export default class MessagePanel extends React.Component<IProps, IState> {
|
|||
// A map to allow groupers to maintain consistent keys even if their first event is uprooted due to back-pagination.
|
||||
public grouperKeyMap = new WeakMap<MatrixEvent, string>();
|
||||
|
||||
public constructor(props, context) {
|
||||
public constructor(props: IProps, context: React.ContextType<typeof RoomContext>) {
|
||||
super(props, context);
|
||||
|
||||
this.state = {
|
||||
|
@ -308,7 +308,7 @@ export default class MessagePanel extends React.Component<IProps, IState> {
|
|||
SettingsStore.unwatchSetting(this.showTypingNotificationsWatcherRef);
|
||||
}
|
||||
|
||||
public componentDidUpdate(prevProps, prevState): void {
|
||||
public componentDidUpdate(prevProps: IProps, prevState: IState): void {
|
||||
if (prevProps.layout !== this.props.layout) {
|
||||
this.calculateRoomMembersCount();
|
||||
}
|
||||
|
@ -410,17 +410,13 @@ export default class MessagePanel extends React.Component<IProps, IState> {
|
|||
/* jump to the top of the content.
|
||||
*/
|
||||
public scrollToTop(): void {
|
||||
if (this.scrollPanel.current) {
|
||||
this.scrollPanel.current.scrollToTop();
|
||||
}
|
||||
this.scrollPanel.current?.scrollToTop();
|
||||
}
|
||||
|
||||
/* jump to the bottom of the content.
|
||||
*/
|
||||
public scrollToBottom(): void {
|
||||
if (this.scrollPanel.current) {
|
||||
this.scrollPanel.current.scrollToBottom();
|
||||
}
|
||||
this.scrollPanel.current?.scrollToBottom();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -428,10 +424,8 @@ export default class MessagePanel extends React.Component<IProps, IState> {
|
|||
*
|
||||
* @param {KeyboardEvent} ev: the keyboard event to handle
|
||||
*/
|
||||
public handleScrollKey(ev: KeyboardEvent): void {
|
||||
if (this.scrollPanel.current) {
|
||||
this.scrollPanel.current.handleScrollKey(ev);
|
||||
}
|
||||
public handleScrollKey(ev: React.KeyboardEvent | KeyboardEvent): void {
|
||||
this.scrollPanel.current?.handleScrollKey(ev);
|
||||
}
|
||||
|
||||
/* jump to the given event id.
|
||||
|
@ -752,7 +746,7 @@ export default class MessagePanel extends React.Component<IProps, IState> {
|
|||
const readReceipts = this.readReceiptsByEvent[eventId];
|
||||
|
||||
let isLastSuccessful = false;
|
||||
const isSentState = (s): boolean => !s || s === "sent";
|
||||
const isSentState = (s: EventStatus): boolean => !s || s === EventStatus.SENT;
|
||||
const isSent = isSentState(mxEv.getAssociatedStatus());
|
||||
const hasNextEvent = nextEvent && this.shouldShowEvent(nextEvent);
|
||||
if (!hasNextEvent && isSent) {
|
||||
|
@ -869,8 +863,14 @@ export default class MessagePanel extends React.Component<IProps, IState> {
|
|||
// should be shown next to that event. If a hidden event has read receipts,
|
||||
// they are folded into the receipts of the last shown event.
|
||||
private getReadReceiptsByShownEvent(): Record<string, IReadReceiptProps[]> {
|
||||
const receiptsByEvent = {};
|
||||
const receiptsByUserId = {};
|
||||
const receiptsByEvent: Record<string, IReadReceiptProps[]> = {};
|
||||
const receiptsByUserId: Record<
|
||||
string,
|
||||
{
|
||||
lastShownEventId: string;
|
||||
receipt: IReadReceiptProps;
|
||||
}
|
||||
> = {};
|
||||
|
||||
let lastShownEventId;
|
||||
for (const event of this.props.events) {
|
||||
|
@ -982,7 +982,7 @@ export default class MessagePanel extends React.Component<IProps, IState> {
|
|||
}
|
||||
}
|
||||
|
||||
public render(): JSX.Element {
|
||||
public render(): React.ReactNode {
|
||||
let topSpinner;
|
||||
let bottomSpinner;
|
||||
if (this.props.backPaginating) {
|
||||
|
|
|
@ -27,8 +27,8 @@ interface IState {
|
|||
}
|
||||
|
||||
export default class NonUrgentToastContainer extends React.PureComponent<IProps, IState> {
|
||||
public constructor(props, context) {
|
||||
super(props, context);
|
||||
public constructor(props: IProps) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
toasts: NonUrgentToastStore.instance.components,
|
||||
|
@ -45,7 +45,7 @@ export default class NonUrgentToastContainer extends React.PureComponent<IProps,
|
|||
this.setState({ toasts: NonUrgentToastStore.instance.components });
|
||||
};
|
||||
|
||||
public render(): JSX.Element {
|
||||
public render(): React.ReactNode {
|
||||
const toasts = this.state.toasts.map((t, i) => {
|
||||
return (
|
||||
<div className="mx_NonUrgentToastContainer_toast" key={`toast-${i}`}>
|
||||
|
|
|
@ -43,7 +43,7 @@ export default class NotificationPanel extends React.PureComponent<IProps, IStat
|
|||
|
||||
private card = React.createRef<HTMLDivElement>();
|
||||
|
||||
public constructor(props) {
|
||||
public constructor(props: IProps) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
|
@ -55,7 +55,7 @@ export default class NotificationPanel extends React.PureComponent<IProps, IStat
|
|||
this.setState({ narrow });
|
||||
};
|
||||
|
||||
public render(): JSX.Element {
|
||||
public render(): React.ReactNode {
|
||||
const emptyState = (
|
||||
<div className="mx_RightPanel_empty mx_NotificationPanel_empty">
|
||||
<h2>{_t("You're all caught up")}</h2>
|
||||
|
|
|
@ -245,7 +245,7 @@ export default class PictureInPictureDragger extends React.Component<IProps> {
|
|||
}
|
||||
};
|
||||
|
||||
public render(): JSX.Element {
|
||||
public render(): React.ReactNode {
|
||||
const style = {
|
||||
transform: `translateX(${this.translationX}px) translateY(${this.translationY}px)`,
|
||||
};
|
||||
|
|
|
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React, { MutableRefObject, useContext, useRef } from "react";
|
||||
import React, { MutableRefObject, ReactNode, useContext, useRef } from "react";
|
||||
import { CallEvent, CallState, MatrixCall } from "matrix-js-sdk/src/webrtc/call";
|
||||
import { logger } from "matrix-js-sdk/src/logger";
|
||||
import { Optional } from "matrix-events-sdk";
|
||||
|
@ -288,7 +288,7 @@ class PipContainerInner extends React.Component<IProps, IState> {
|
|||
);
|
||||
}
|
||||
|
||||
public render(): JSX.Element {
|
||||
public render(): ReactNode {
|
||||
const pipMode = true;
|
||||
let pipContent: Array<CreatePipChildren> = [];
|
||||
|
||||
|
|
|
@ -63,7 +63,7 @@ export default class RightPanel extends React.Component<IProps, IState> {
|
|||
public static contextType = MatrixClientContext;
|
||||
public context!: React.ContextType<typeof MatrixClientContext>;
|
||||
|
||||
public constructor(props, context) {
|
||||
public constructor(props: IProps, context: React.ContextType<typeof MatrixClientContext>) {
|
||||
super(props, context);
|
||||
|
||||
this.state = {
|
||||
|
@ -149,7 +149,7 @@ export default class RightPanel extends React.Component<IProps, IState> {
|
|||
this.setState({ searchQuery });
|
||||
};
|
||||
|
||||
public render(): JSX.Element {
|
||||
public render(): React.ReactNode {
|
||||
let card = <div />;
|
||||
const roomId = this.props.room?.roomId;
|
||||
const phase = this.props.overwriteCard?.phase ?? this.state.phase;
|
||||
|
|
|
@ -111,11 +111,11 @@ export const RoomSearchView = forwardRef<ScrollPanel, Props>(
|
|||
);
|
||||
if (!bundledRelationship || event.getThread()) continue;
|
||||
const room = client.getRoom(event.getRoomId());
|
||||
const thread = room.findThreadForEvent(event);
|
||||
const thread = room?.findThreadForEvent(event);
|
||||
if (thread) {
|
||||
event.setThread(thread);
|
||||
} else {
|
||||
room.createThread(event.getId(), event, [], true);
|
||||
room?.createThread(event.getId()!, event, [], true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -214,7 +214,7 @@ export const RoomSearchView = forwardRef<ScrollPanel, Props>(
|
|||
scrollPanel?.checkScroll();
|
||||
};
|
||||
|
||||
let lastRoomId: string;
|
||||
let lastRoomId: string | undefined;
|
||||
let mergedTimeline: MatrixEvent[] = [];
|
||||
let ourEventsIndexes: number[] = [];
|
||||
|
||||
|
|
|
@ -14,10 +14,11 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import React, { ReactNode } from "react";
|
||||
import { EventStatus, MatrixEvent } from "matrix-js-sdk/src/models/event";
|
||||
import { SyncState, ISyncStateData } from "matrix-js-sdk/src/sync";
|
||||
import { Room } from "matrix-js-sdk/src/models/room";
|
||||
import { MatrixError } from "matrix-js-sdk/src/matrix";
|
||||
|
||||
import { _t, _td } from "../../languageHandler";
|
||||
import Resend from "../../Resend";
|
||||
|
@ -192,10 +193,10 @@ export default class RoomStatusBar extends React.PureComponent<IProps, IState> {
|
|||
private getUnsentMessageContent(): JSX.Element {
|
||||
const unsentMessages = this.state.unsentMessages;
|
||||
|
||||
let title;
|
||||
let title: ReactNode;
|
||||
|
||||
let consentError = null;
|
||||
let resourceLimitError = null;
|
||||
let consentError: MatrixError | null = null;
|
||||
let resourceLimitError: MatrixError | null = null;
|
||||
for (const m of unsentMessages) {
|
||||
if (m.error && m.error.errcode === "M_CONSENT_NOT_GIVEN") {
|
||||
consentError = m.error;
|
||||
|
@ -212,7 +213,7 @@ export default class RoomStatusBar extends React.PureComponent<IProps, IState> {
|
|||
{},
|
||||
{
|
||||
consentLink: (sub) => (
|
||||
<a href={consentError.data && consentError.data.consent_uri} target="_blank">
|
||||
<a href={consentError!.data?.consent_uri} target="_blank">
|
||||
{sub}
|
||||
</a>
|
||||
),
|
||||
|
@ -271,7 +272,7 @@ export default class RoomStatusBar extends React.PureComponent<IProps, IState> {
|
|||
);
|
||||
}
|
||||
|
||||
public render(): JSX.Element {
|
||||
public render(): React.ReactNode {
|
||||
if (this.shouldShowConnectionError()) {
|
||||
return (
|
||||
<div className="mx_RoomStatusBar">
|
||||
|
|
|
@ -14,13 +14,13 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React, { ReactElement } from "react";
|
||||
import React, { ReactElement, ReactNode } from "react";
|
||||
|
||||
import { StaticNotificationState } from "../../stores/notifications/StaticNotificationState";
|
||||
import NotificationBadge from "../views/rooms/NotificationBadge";
|
||||
|
||||
interface RoomStatusBarUnsentMessagesProps {
|
||||
title: string;
|
||||
title: ReactNode;
|
||||
description?: string;
|
||||
notificationState: StaticNotificationState;
|
||||
buttons: ReactElement;
|
||||
|
|
|
@ -33,6 +33,7 @@ import { CryptoEvent } from "matrix-js-sdk/src/crypto";
|
|||
import { THREAD_RELATION_TYPE } from "matrix-js-sdk/src/models/thread";
|
||||
import { HistoryVisibility } from "matrix-js-sdk/src/@types/partials";
|
||||
import { ISearchResults } from "matrix-js-sdk/src/@types/search";
|
||||
import { IRoomTimelineData } from "matrix-js-sdk/src/models/event-timeline-set";
|
||||
|
||||
import shouldHideEvent from "../../shouldHideEvent";
|
||||
import { _t } from "../../languageHandler";
|
||||
|
@ -49,7 +50,7 @@ import RoomScrollStateStore, { ScrollState } from "../../stores/RoomScrollStateS
|
|||
import WidgetEchoStore from "../../stores/WidgetEchoStore";
|
||||
import SettingsStore from "../../settings/SettingsStore";
|
||||
import { Layout } from "../../settings/enums/Layout";
|
||||
import AccessibleButton from "../views/elements/AccessibleButton";
|
||||
import AccessibleButton, { ButtonEvent } from "../views/elements/AccessibleButton";
|
||||
import RoomContext, { TimelineRenderingType } from "../../contexts/RoomContext";
|
||||
import { E2EStatus, shieldStatusForRoom } from "../../utils/ShieldUtils";
|
||||
import { Action } from "../../dispatcher/actions";
|
||||
|
@ -223,6 +224,7 @@ export interface IRoomState {
|
|||
narrow: boolean;
|
||||
// List of undecryptable events currently visible on-screen
|
||||
visibleDecryptionFailures?: MatrixEvent[];
|
||||
msc3946ProcessDynamicPredecessor: boolean;
|
||||
}
|
||||
|
||||
interface LocalRoomViewProps {
|
||||
|
@ -416,6 +418,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
|
|||
liveTimeline: undefined,
|
||||
narrow: false,
|
||||
visibleDecryptionFailures: [],
|
||||
msc3946ProcessDynamicPredecessor: SettingsStore.getValue("feature_dynamic_room_predecessors"),
|
||||
};
|
||||
|
||||
this.dispatcherRef = dis.register(this.onAction);
|
||||
|
@ -467,6 +470,9 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
|
|||
),
|
||||
SettingsStore.watchSetting("urlPreviewsEnabled", null, this.onUrlPreviewsEnabledChange),
|
||||
SettingsStore.watchSetting("urlPreviewsEnabled_e2ee", null, this.onUrlPreviewsEnabledChange),
|
||||
SettingsStore.watchSetting("feature_dynamic_room_predecessors", null, (...[, , , value]) =>
|
||||
this.setState({ msc3946ProcessDynamicPredecessor: value as boolean }),
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
|
@ -851,7 +857,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
|
|||
window.addEventListener("beforeunload", this.onPageUnload);
|
||||
}
|
||||
|
||||
public shouldComponentUpdate(nextProps, nextState): boolean {
|
||||
public shouldComponentUpdate(nextProps: IRoomProps, nextState: IRoomState): boolean {
|
||||
const hasPropsDiff = objectHasDiff(this.props, nextProps);
|
||||
|
||||
const { upgradeRecommendation, ...state } = this.state;
|
||||
|
@ -953,7 +959,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
|
|||
});
|
||||
};
|
||||
|
||||
private onPageUnload = (event): string => {
|
||||
private onPageUnload = (event: BeforeUnloadEvent): string => {
|
||||
if (ContentMessages.sharedInstance().getCurrentUploads().length > 0) {
|
||||
return (event.returnValue = _t("You seem to be uploading files, are you sure you want to quit?"));
|
||||
} else if (this.getCallForRoom() && this.state.callState !== "ended") {
|
||||
|
@ -961,7 +967,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
|
|||
}
|
||||
};
|
||||
|
||||
private onReactKeyDown = (ev): void => {
|
||||
private onReactKeyDown = (ev: React.KeyboardEvent): void => {
|
||||
let handled = false;
|
||||
|
||||
const action = getKeyBindingsManager().getRoomAction(ev);
|
||||
|
@ -1125,7 +1131,13 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
|
|||
createRoomFromLocalRoom(this.context.client, this.state.room as LocalRoom);
|
||||
}
|
||||
|
||||
private onRoomTimeline = (ev: MatrixEvent, room: Room | null, toStartOfTimeline: boolean, removed, data): void => {
|
||||
private onRoomTimeline = (
|
||||
ev: MatrixEvent,
|
||||
room: Room | null,
|
||||
toStartOfTimeline: boolean,
|
||||
removed: boolean,
|
||||
data?: IRoomTimelineData,
|
||||
): void => {
|
||||
if (this.unmounted) return;
|
||||
|
||||
// ignore events for other rooms or the notification timeline set
|
||||
|
@ -1145,7 +1157,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
|
|||
|
||||
// ignore anything but real-time updates at the end of the room:
|
||||
// updates from pagination will happen when the paginate completes.
|
||||
if (toStartOfTimeline || !data || !data.liveEvent) return;
|
||||
if (toStartOfTimeline || !data?.liveEvent) return;
|
||||
|
||||
// no point handling anything while we're waiting for the join to finish:
|
||||
// we'll only be showing a spinner.
|
||||
|
@ -1697,7 +1709,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
|
|||
};
|
||||
|
||||
// update the read marker to match the read-receipt
|
||||
private forgetReadMarker = (ev): void => {
|
||||
private forgetReadMarker = (ev: ButtonEvent): void => {
|
||||
ev.stopPropagation();
|
||||
this.messagePanel.forgetReadMarker();
|
||||
};
|
||||
|
@ -1770,7 +1782,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
|
|||
*
|
||||
* We pass it down to the scroll panel.
|
||||
*/
|
||||
public handleScrollKey = (ev): void => {
|
||||
public handleScrollKey = (ev: React.KeyboardEvent | KeyboardEvent): void => {
|
||||
let panel: ScrollPanel | TimelinePanel;
|
||||
if (this.searchResultsPanel.current) {
|
||||
panel = this.searchResultsPanel.current;
|
||||
|
@ -1793,15 +1805,13 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
|
|||
|
||||
// this has to be a proper method rather than an unnamed function,
|
||||
// otherwise react calls it with null on each update.
|
||||
private gatherTimelinePanelRef = (r): void => {
|
||||
private gatherTimelinePanelRef = (r?: TimelinePanel): void => {
|
||||
this.messagePanel = r;
|
||||
};
|
||||
|
||||
private getOldRoom(): Room | null {
|
||||
const createEvent = this.state.room.currentState.getStateEvents(EventType.RoomCreate, "");
|
||||
if (!createEvent || !createEvent.getContent()["predecessor"]) return null;
|
||||
|
||||
return this.context.client.getRoom(createEvent.getContent()["predecessor"]["room_id"]);
|
||||
const { roomId } = this.state.room?.findPredecessor(this.state.msc3946ProcessDynamicPredecessor) || {};
|
||||
return this.context.client?.getRoom(roomId) || null;
|
||||
}
|
||||
|
||||
public getHiddenHighlightCount(): number {
|
||||
|
@ -1869,7 +1879,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
|
|||
);
|
||||
}
|
||||
|
||||
public render(): JSX.Element {
|
||||
public render(): React.ReactNode {
|
||||
if (this.state.room instanceof LocalRoom) {
|
||||
if (this.state.room.state === LocalRoomState.CREATING) {
|
||||
return this.renderLocalRoomCreateLoader();
|
||||
|
|
|
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React, { createRef, CSSProperties, ReactNode, KeyboardEvent } from "react";
|
||||
import React, { createRef, CSSProperties, ReactNode } from "react";
|
||||
import { logger } from "matrix-js-sdk/src/logger";
|
||||
|
||||
import SettingsStore from "../../settings/SettingsStore";
|
||||
|
@ -195,8 +195,8 @@ export default class ScrollPanel extends React.Component<IProps> {
|
|||
private heightUpdateInProgress: boolean;
|
||||
private divScroll: HTMLDivElement;
|
||||
|
||||
public constructor(props, context) {
|
||||
super(props, context);
|
||||
public constructor(props: IProps) {
|
||||
super(props);
|
||||
|
||||
this.props.resizeNotifier?.on("middlePanelResizedNoisy", this.onResize);
|
||||
|
||||
|
@ -440,9 +440,9 @@ export default class ScrollPanel extends React.Component<IProps> {
|
|||
// pagination.
|
||||
//
|
||||
// If backwards is true, we unpaginate (remove) tiles from the back (top).
|
||||
let tile;
|
||||
let tile: HTMLElement;
|
||||
for (let i = 0; i < tiles.length; i++) {
|
||||
tile = tiles[backwards ? i : tiles.length - 1 - i];
|
||||
tile = tiles[backwards ? i : tiles.length - 1 - i] as HTMLElement;
|
||||
// Subtract height of tile as if it were unpaginated
|
||||
excessHeight -= tile.clientHeight;
|
||||
//If removing the tile would lead to future pagination, break before setting scroll token
|
||||
|
@ -587,7 +587,7 @@ export default class ScrollPanel extends React.Component<IProps> {
|
|||
* Scroll up/down in response to a scroll key
|
||||
* @param {object} ev the keyboard event
|
||||
*/
|
||||
public handleScrollKey = (ev: KeyboardEvent): void => {
|
||||
public handleScrollKey = (ev: React.KeyboardEvent | KeyboardEvent): void => {
|
||||
const roomAction = getKeyBindingsManager().getRoomAction(ev);
|
||||
switch (roomAction) {
|
||||
case KeyBindingAction.ScrollUp:
|
||||
|
|
|
@ -24,7 +24,7 @@ import { getKeyBindingsManager } from "../../KeyBindingsManager";
|
|||
import { KeyBindingAction } from "../../accessibility/KeyboardShortcuts";
|
||||
|
||||
interface IProps extends HTMLProps<HTMLInputElement> {
|
||||
onSearch?: (query: string) => void;
|
||||
onSearch: (query: string) => void;
|
||||
onCleared?: (source?: string) => void;
|
||||
onKeyDown?: (ev: React.KeyboardEvent) => void;
|
||||
onFocus?: (ev: React.FocusEvent) => void;
|
||||
|
@ -62,7 +62,7 @@ export default class SearchBox extends React.Component<IProps, IState> {
|
|||
|
||||
private onSearch = throttle(
|
||||
(): void => {
|
||||
this.props.onSearch(this.search.current.value);
|
||||
this.props.onSearch(this.search.current?.value);
|
||||
},
|
||||
200,
|
||||
{ trailing: true, leading: true },
|
||||
|
@ -101,7 +101,7 @@ export default class SearchBox extends React.Component<IProps, IState> {
|
|||
}
|
||||
}
|
||||
|
||||
public render(): JSX.Element {
|
||||
public render(): React.ReactNode {
|
||||
/* eslint @typescript-eslint/no-unused-vars: ["error", { "ignoreRestSiblings": true }] */
|
||||
const {
|
||||
onSearch,
|
||||
|
|
|
@ -280,7 +280,7 @@ const Tile: React.FC<ITileProps> = ({
|
|||
);
|
||||
|
||||
if (showChildren) {
|
||||
const onChildrenKeyDown = (e): void => {
|
||||
const onChildrenKeyDown = (e: React.KeyboardEvent): void => {
|
||||
const action = getKeyBindingsManager().getAccessibilityAction(e);
|
||||
switch (action) {
|
||||
case KeyBindingAction.ArrowLeft:
|
||||
|
|
|
@ -318,7 +318,7 @@ const SpaceSetupFirstRooms: React.FC<{
|
|||
label={_t("Room name")}
|
||||
placeholder={placeholders[i]}
|
||||
value={roomNames[i]}
|
||||
onChange={(ev) => setRoomName(i, ev.target.value)}
|
||||
onChange={(ev: React.ChangeEvent<HTMLInputElement>) => setRoomName(i, ev.target.value)}
|
||||
autoFocus={i === 2}
|
||||
disabled={busy}
|
||||
autoComplete="off"
|
||||
|
@ -814,7 +814,7 @@ export default class SpaceRoomView extends React.PureComponent<IProps, IState> {
|
|||
}
|
||||
}
|
||||
|
||||
public render(): JSX.Element {
|
||||
public render(): React.ReactNode {
|
||||
const rightPanel =
|
||||
this.state.showRightPanel && this.state.phase === Phase.Landing ? (
|
||||
<RightPanel room={this.props.space} resizeNotifier={this.props.resizeNotifier} />
|
||||
|
|
|
@ -137,7 +137,7 @@ export default class ThreadView extends React.Component<IProps, IState> {
|
|||
});
|
||||
}
|
||||
|
||||
public componentDidUpdate(prevProps): void {
|
||||
public componentDidUpdate(prevProps: IProps): void {
|
||||
if (prevProps.mxEvent !== this.props.mxEvent) {
|
||||
this.setupThread(this.props.mxEvent);
|
||||
}
|
||||
|
@ -316,7 +316,7 @@ export default class ThreadView extends React.Component<IProps, IState> {
|
|||
};
|
||||
|
||||
private get threadRelation(): IEventRelation {
|
||||
const relation = {
|
||||
const relation: IEventRelation = {
|
||||
rel_type: THREAD_RELATION_TYPE.name,
|
||||
event_id: this.state.thread?.id,
|
||||
is_falling_back: true,
|
||||
|
@ -343,7 +343,7 @@ export default class ThreadView extends React.Component<IProps, IState> {
|
|||
);
|
||||
};
|
||||
|
||||
public render(): JSX.Element {
|
||||
public render(): React.ReactNode {
|
||||
const highlightedEventId = this.props.isInitialEventHighlighted ? this.props.initialEvent?.getId() : null;
|
||||
|
||||
const threadRelation = this.threadRelation;
|
||||
|
|
|
@ -1316,7 +1316,7 @@ class TimelinePanel extends React.Component<IProps, IState> {
|
|||
*
|
||||
* We pass it down to the scroll panel.
|
||||
*/
|
||||
public handleScrollKey = (ev: React.KeyboardEvent): void => {
|
||||
public handleScrollKey = (ev: React.KeyboardEvent | KeyboardEvent): void => {
|
||||
if (!this.messagePanel.current) return;
|
||||
|
||||
// jump to the live timeline on ctrl-end, rather than the end of the
|
||||
|
@ -1886,7 +1886,7 @@ class TimelinePanel extends React.Component<IProps, IState> {
|
|||
this.callEventGroupers = buildLegacyCallEventGroupers(this.callEventGroupers, events);
|
||||
}
|
||||
|
||||
public render(): JSX.Element {
|
||||
public render(): React.ReactNode {
|
||||
// just show a spinner while the timeline loads.
|
||||
//
|
||||
// put it in a div of the right class (mx_RoomView_messagePanel) so
|
||||
|
@ -1977,9 +1977,9 @@ class TimelinePanel extends React.Component<IProps, IState> {
|
|||
*
|
||||
* @return An event ID list for every timeline in every timelineSet
|
||||
*/
|
||||
function serializeEventIdsFromTimelineSets(timelineSets): { [key: string]: string[] }[] {
|
||||
function serializeEventIdsFromTimelineSets(timelineSets: EventTimelineSet[]): { [key: string]: string[] }[] {
|
||||
const serializedEventIdsInTimelineSet = timelineSets.map((timelineSet) => {
|
||||
const timelineMap = {};
|
||||
const timelineMap: Record<string, string[]> = {};
|
||||
|
||||
const timelines = timelineSet.getTimelines();
|
||||
const liveTimeline = timelineSet.getLiveTimeline();
|
||||
|
|
|
@ -25,8 +25,8 @@ interface IState {
|
|||
}
|
||||
|
||||
export default class ToastContainer extends React.Component<{}, IState> {
|
||||
public constructor(props, context) {
|
||||
super(props, context);
|
||||
public constructor(props: {}) {
|
||||
super(props);
|
||||
this.state = {
|
||||
toasts: ToastStore.sharedInstance().getToasts(),
|
||||
countSeen: ToastStore.sharedInstance().getCountSeen(),
|
||||
|
@ -50,7 +50,7 @@ export default class ToastContainer extends React.Component<{}, IState> {
|
|||
});
|
||||
};
|
||||
|
||||
public render(): JSX.Element {
|
||||
public render(): React.ReactNode {
|
||||
const totalCount = this.state.toasts.length;
|
||||
const isStacked = totalCount > 1;
|
||||
let toast;
|
||||
|
|
|
@ -57,7 +57,7 @@ export default class UploadBar extends React.PureComponent<IProps, IState> {
|
|||
private dispatcherRef: Optional<string>;
|
||||
private mounted = false;
|
||||
|
||||
public constructor(props) {
|
||||
public constructor(props: IProps) {
|
||||
super(props);
|
||||
|
||||
// Set initial state to any available upload in this room - we might be mounting
|
||||
|
@ -103,7 +103,7 @@ export default class UploadBar extends React.PureComponent<IProps, IState> {
|
|||
ContentMessages.sharedInstance().cancelUpload(this.state.currentUpload!);
|
||||
};
|
||||
|
||||
public render(): JSX.Element {
|
||||
public render(): React.ReactNode {
|
||||
if (!this.state.currentFile) {
|
||||
return null;
|
||||
}
|
||||
|
|
|
@ -429,7 +429,7 @@ export default class UserMenu extends React.Component<IProps, IState> {
|
|||
);
|
||||
};
|
||||
|
||||
public render(): JSX.Element {
|
||||
public render(): React.ReactNode {
|
||||
const avatarSize = 32; // should match border-radius of the avatar
|
||||
|
||||
const userId = MatrixClientPeg.get().getUserId();
|
||||
|
|
|
@ -18,6 +18,7 @@ limitations under the License.
|
|||
import React from "react";
|
||||
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
|
||||
import { RoomMember } from "matrix-js-sdk/src/models/room-member";
|
||||
import { MatrixClient } from "matrix-js-sdk/src/matrix";
|
||||
|
||||
import { MatrixClientPeg } from "../../MatrixClientPeg";
|
||||
import Modal from "../../Modal";
|
||||
|
@ -31,7 +32,7 @@ import { RightPanelPhases } from "../../stores/right-panel/RightPanelStorePhases
|
|||
import { UserOnboardingPage } from "../views/user-onboarding/UserOnboardingPage";
|
||||
|
||||
interface IProps {
|
||||
userId?: string;
|
||||
userId: string;
|
||||
resizeNotifier: ResizeNotifier;
|
||||
}
|
||||
|
||||
|
@ -66,7 +67,7 @@ export default class UserView extends React.Component<IProps, IState> {
|
|||
private async loadProfileInfo(): Promise<void> {
|
||||
const cli = MatrixClientPeg.get();
|
||||
this.setState({ loading: true });
|
||||
let profileInfo;
|
||||
let profileInfo: Awaited<ReturnType<MatrixClient["getProfileInfo"]>>;
|
||||
try {
|
||||
profileInfo = await cli.getProfileInfo(this.props.userId);
|
||||
} catch (err) {
|
||||
|
@ -83,7 +84,7 @@ export default class UserView extends React.Component<IProps, IState> {
|
|||
this.setState({ member, loading: false });
|
||||
}
|
||||
|
||||
public render(): JSX.Element {
|
||||
public render(): React.ReactNode {
|
||||
if (this.state.loading) {
|
||||
return <Spinner />;
|
||||
} else if (this.state.member) {
|
||||
|
|
|
@ -142,7 +142,7 @@ export default class ViewSource extends React.Component<IProps, IState> {
|
|||
return room.currentState.mayClientSendStateEvent(mxEvent.getType(), cli);
|
||||
}
|
||||
|
||||
public render(): JSX.Element {
|
||||
public render(): React.ReactNode {
|
||||
const mxEvent = this.props.mxEvent.replacingEvent() || this.props.mxEvent; // show the replacing event, not the original, if it is an edit
|
||||
|
||||
const isEditing = this.state.isEditing;
|
||||
|
|
|
@ -57,7 +57,7 @@ export default class CompleteSecurity extends React.Component<IProps, IState> {
|
|||
store.stop();
|
||||
}
|
||||
|
||||
public render(): JSX.Element {
|
||||
public render(): React.ReactNode {
|
||||
const { phase, lostKeys } = this.state;
|
||||
let icon;
|
||||
let title;
|
||||
|
|
|
@ -27,7 +27,7 @@ interface IProps {
|
|||
}
|
||||
|
||||
export default class E2eSetup extends React.Component<IProps> {
|
||||
public render(): JSX.Element {
|
||||
public render(): React.ReactNode {
|
||||
return (
|
||||
<AuthPage>
|
||||
<CompleteSecurityBody>
|
||||
|
|
|
@ -487,7 +487,7 @@ export default class ForgotPassword extends React.Component<Props, State> {
|
|||
);
|
||||
}
|
||||
|
||||
public render(): JSX.Element {
|
||||
public render(): React.ReactNode {
|
||||
let resetPasswordJsx: JSX.Element;
|
||||
|
||||
switch (this.state.phase) {
|
||||
|
|
|
@ -37,7 +37,7 @@ import SSOButtons from "../../views/elements/SSOButtons";
|
|||
import ServerPicker from "../../views/elements/ServerPicker";
|
||||
import AuthBody from "../../views/auth/AuthBody";
|
||||
import AuthHeader from "../../views/auth/AuthHeader";
|
||||
import AccessibleButton from "../../views/elements/AccessibleButton";
|
||||
import AccessibleButton, { ButtonEvent } from "../../views/elements/AccessibleButton";
|
||||
import { ValidatedServerConfig } from "../../../utils/ValidatedServerConfig";
|
||||
|
||||
// These are used in several places, and come from the js-sdk's autodiscovery
|
||||
|
@ -101,6 +101,11 @@ interface IState {
|
|||
serverDeadError?: ReactNode;
|
||||
}
|
||||
|
||||
type OnPasswordLogin = {
|
||||
(username: string, phoneCountry: undefined, phoneNumber: undefined, password: string): Promise<void>;
|
||||
(username: undefined, phoneCountry: string, phoneNumber: string, password: string): Promise<void>;
|
||||
};
|
||||
|
||||
/*
|
||||
* A wire component which glues together login UI components and Login logic
|
||||
*/
|
||||
|
@ -110,7 +115,7 @@ export default class LoginComponent extends React.PureComponent<IProps, IState>
|
|||
|
||||
private readonly stepRendererMap: Record<string, () => ReactNode>;
|
||||
|
||||
public constructor(props) {
|
||||
public constructor(props: IProps) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
|
@ -152,7 +157,7 @@ export default class LoginComponent extends React.PureComponent<IProps, IState>
|
|||
this.unmounted = true;
|
||||
}
|
||||
|
||||
public componentDidUpdate(prevProps): void {
|
||||
public componentDidUpdate(prevProps: IProps): void {
|
||||
if (
|
||||
prevProps.serverConfig.hsUrl !== this.props.serverConfig.hsUrl ||
|
||||
prevProps.serverConfig.isUrl !== this.props.serverConfig.isUrl
|
||||
|
@ -164,7 +169,12 @@ export default class LoginComponent extends React.PureComponent<IProps, IState>
|
|||
|
||||
public isBusy = (): boolean => this.state.busy || this.props.busy;
|
||||
|
||||
public onPasswordLogin = async (username, phoneCountry, phoneNumber, password): Promise<void> => {
|
||||
public onPasswordLogin: OnPasswordLogin = async (
|
||||
username: string | undefined,
|
||||
phoneCountry: string | undefined,
|
||||
phoneNumber: string | undefined,
|
||||
password: string,
|
||||
): Promise<void> => {
|
||||
if (!this.state.serverIsAlive) {
|
||||
this.setState({ busy: true });
|
||||
// Do a quick liveliness check on the URLs
|
||||
|
@ -207,10 +217,10 @@ export default class LoginComponent extends React.PureComponent<IProps, IState>
|
|||
if (this.unmounted) {
|
||||
return;
|
||||
}
|
||||
let errorText;
|
||||
let errorText: ReactNode;
|
||||
|
||||
// Some error strings only apply for logging in
|
||||
const usingEmail = username.indexOf("@") > 0;
|
||||
const usingEmail = username?.indexOf("@") > 0;
|
||||
if (error.httpStatus === 400 && usingEmail) {
|
||||
errorText = _t("This homeserver does not support login using email address.");
|
||||
} else if (error.errcode === "M_RESOURCE_LIMIT_EXCEEDED") {
|
||||
|
@ -264,11 +274,11 @@ export default class LoginComponent extends React.PureComponent<IProps, IState>
|
|||
);
|
||||
};
|
||||
|
||||
public onUsernameChanged = (username): void => {
|
||||
this.setState({ username: username });
|
||||
public onUsernameChanged = (username: string): void => {
|
||||
this.setState({ username });
|
||||
};
|
||||
|
||||
public onUsernameBlur = async (username): Promise<void> => {
|
||||
public onUsernameBlur = async (username: string): Promise<void> => {
|
||||
const doWellknownLookup = username[0] === "@";
|
||||
this.setState({
|
||||
username: username,
|
||||
|
@ -315,23 +325,21 @@ export default class LoginComponent extends React.PureComponent<IProps, IState>
|
|||
}
|
||||
};
|
||||
|
||||
public onPhoneCountryChanged = (phoneCountry): void => {
|
||||
this.setState({ phoneCountry: phoneCountry });
|
||||
public onPhoneCountryChanged = (phoneCountry: string): void => {
|
||||
this.setState({ phoneCountry });
|
||||
};
|
||||
|
||||
public onPhoneNumberChanged = (phoneNumber): void => {
|
||||
this.setState({
|
||||
phoneNumber: phoneNumber,
|
||||
});
|
||||
public onPhoneNumberChanged = (phoneNumber: string): void => {
|
||||
this.setState({ phoneNumber });
|
||||
};
|
||||
|
||||
public onRegisterClick = (ev): void => {
|
||||
public onRegisterClick = (ev: ButtonEvent): void => {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
this.props.onRegisterClick();
|
||||
};
|
||||
|
||||
public onTryRegisterClick = (ev): void => {
|
||||
public onTryRegisterClick = (ev: ButtonEvent): void => {
|
||||
const hasPasswordFlow = this.state.flows?.find((flow) => flow.type === "m.login.password");
|
||||
const ssoFlow = this.state.flows?.find((flow) => flow.type === "m.login.sso" || flow.type === "m.login.cas");
|
||||
// If has no password flow but an SSO flow guess that the user wants to register with SSO.
|
||||
|
@ -540,7 +548,7 @@ export default class LoginComponent extends React.PureComponent<IProps, IState>
|
|||
);
|
||||
};
|
||||
|
||||
private renderSsoStep = (loginType): JSX.Element => {
|
||||
private renderSsoStep = (loginType: "cas" | "sso"): JSX.Element => {
|
||||
const flow = this.state.flows.find((flow) => flow.type === "m.login." + loginType) as ISSOFlow;
|
||||
|
||||
return (
|
||||
|
@ -555,7 +563,7 @@ export default class LoginComponent extends React.PureComponent<IProps, IState>
|
|||
);
|
||||
};
|
||||
|
||||
public render(): JSX.Element {
|
||||
public render(): React.ReactNode {
|
||||
const loader =
|
||||
this.isBusy() && !this.state.busyLoggingIn ? (
|
||||
<div className="mx_Login_loader">
|
||||
|
|
|
@ -14,9 +14,9 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import { AuthType, createClient, IAuthData, IInputs } from "matrix-js-sdk/src/matrix";
|
||||
import { AuthType, createClient, IAuthData, IInputs, MatrixError } from "matrix-js-sdk/src/matrix";
|
||||
import React, { Fragment, ReactNode } from "react";
|
||||
import { IRequestTokenResponse, MatrixClient } from "matrix-js-sdk/src/client";
|
||||
import { IRegisterRequestParams, IRequestTokenResponse, MatrixClient } from "matrix-js-sdk/src/client";
|
||||
import classNames from "classnames";
|
||||
import { logger } from "matrix-js-sdk/src/logger";
|
||||
import { ISSOFlow, SSOAction } from "matrix-js-sdk/src/@types/auth";
|
||||
|
@ -125,7 +125,7 @@ export default class Registration extends React.Component<IProps, IState> {
|
|||
// `replaceClient` tracks latest serverConfig to spot when it changes under the async method which fetches flows
|
||||
private latestServerConfig: ValidatedServerConfig;
|
||||
|
||||
public constructor(props) {
|
||||
public constructor(props: IProps) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
|
@ -166,7 +166,7 @@ export default class Registration extends React.Component<IProps, IState> {
|
|||
}
|
||||
};
|
||||
|
||||
public componentDidUpdate(prevProps): void {
|
||||
public componentDidUpdate(prevProps: IProps): void {
|
||||
if (
|
||||
prevProps.serverConfig.hsUrl !== this.props.serverConfig.hsUrl ||
|
||||
prevProps.serverConfig.isUrl !== this.props.serverConfig.isUrl
|
||||
|
@ -307,7 +307,7 @@ export default class Registration extends React.Component<IProps, IState> {
|
|||
if (!success) {
|
||||
let errorText: ReactNode = (response as Error).message || (response as Error).toString();
|
||||
// can we give a better error message?
|
||||
if (response.errcode === "M_RESOURCE_LIMIT_EXCEEDED") {
|
||||
if (response instanceof MatrixError && response.errcode === "M_RESOURCE_LIMIT_EXCEEDED") {
|
||||
const errorTop = messageForResourceLimitError(response.data.limit_type, response.data.admin_contact, {
|
||||
"monthly_active_user": _td("This homeserver has hit its Monthly Active User limit."),
|
||||
"hs_blocked": _td("This homeserver has been blocked by its administrator."),
|
||||
|
@ -326,17 +326,17 @@ export default class Registration extends React.Component<IProps, IState> {
|
|||
<p>{errorDetail}</p>
|
||||
</div>
|
||||
);
|
||||
} else if (response.required_stages && response.required_stages.includes(AuthType.Msisdn)) {
|
||||
} else if ((response as IAuthData).required_stages?.includes(AuthType.Msisdn)) {
|
||||
let msisdnAvailable = false;
|
||||
for (const flow of response.available_flows) {
|
||||
for (const flow of (response as IAuthData).available_flows) {
|
||||
msisdnAvailable = msisdnAvailable || flow.stages.includes(AuthType.Msisdn);
|
||||
}
|
||||
if (!msisdnAvailable) {
|
||||
errorText = _t("This server does not support authentication with a phone number.");
|
||||
}
|
||||
} else if (response.errcode === "M_USER_IN_USE") {
|
||||
} else if (response instanceof MatrixError && response.errcode === "M_USER_IN_USE") {
|
||||
errorText = _t("Someone already has that username, please try another.");
|
||||
} else if (response.errcode === "M_THREEPID_IN_USE") {
|
||||
} else if (response instanceof MatrixError && response.errcode === "M_THREEPID_IN_USE") {
|
||||
errorText = _t("That e-mail address or phone number is already in use.");
|
||||
}
|
||||
|
||||
|
@ -348,11 +348,11 @@ export default class Registration extends React.Component<IProps, IState> {
|
|||
return;
|
||||
}
|
||||
|
||||
MatrixClientPeg.setJustRegisteredUserId(response.user_id);
|
||||
MatrixClientPeg.setJustRegisteredUserId((response as IAuthData).user_id);
|
||||
|
||||
const newState = {
|
||||
const newState: Partial<IState> = {
|
||||
doingUIAuth: false,
|
||||
registeredUsername: response.user_id,
|
||||
registeredUsername: (response as IAuthData).user_id,
|
||||
differentLoggedInUserId: null,
|
||||
completedNoSignin: false,
|
||||
// we're still busy until we get unmounted: don't show the registration form again
|
||||
|
@ -365,8 +365,10 @@ export default class Registration extends React.Component<IProps, IState> {
|
|||
// starting the registration process. This isn't perfect since it's possible
|
||||
// the user had a separate guest session they didn't actually mean to replace.
|
||||
const [sessionOwner, sessionIsGuest] = await Lifecycle.getStoredSessionOwner();
|
||||
if (sessionOwner && !sessionIsGuest && sessionOwner !== response.user_id) {
|
||||
logger.log(`Found a session for ${sessionOwner} but ${response.user_id} has just registered.`);
|
||||
if (sessionOwner && !sessionIsGuest && sessionOwner !== (response as IAuthData).user_id) {
|
||||
logger.log(
|
||||
`Found a session for ${sessionOwner} but ${(response as IAuthData).user_id} has just registered.`,
|
||||
);
|
||||
newState.differentLoggedInUserId = sessionOwner;
|
||||
}
|
||||
|
||||
|
@ -383,7 +385,7 @@ export default class Registration extends React.Component<IProps, IState> {
|
|||
// as the client that started registration may be gone by the time we've verified the email, and only the client
|
||||
// that verified the email is guaranteed to exist, we'll always do the login in that client.
|
||||
const hasEmail = Boolean(this.state.formVals.email);
|
||||
const hasAccessToken = Boolean(response.access_token);
|
||||
const hasAccessToken = Boolean((response as IAuthData).access_token);
|
||||
debuglog("Registration: ui auth finished:", { hasEmail, hasAccessToken });
|
||||
// don’t log in if we found a session for a different user
|
||||
if (!hasEmail && hasAccessToken && !newState.differentLoggedInUserId) {
|
||||
|
@ -391,11 +393,11 @@ export default class Registration extends React.Component<IProps, IState> {
|
|||
// the email, not the client that started the registration flow
|
||||
await this.props.onLoggedIn(
|
||||
{
|
||||
userId: response.user_id,
|
||||
deviceId: response.device_id,
|
||||
userId: (response as IAuthData).user_id,
|
||||
deviceId: (response as IAuthData).device_id,
|
||||
homeserverUrl: this.state.matrixClient.getHomeserverUrl(),
|
||||
identityServerUrl: this.state.matrixClient.getIdentityServerUrl(),
|
||||
accessToken: response.access_token,
|
||||
accessToken: (response as IAuthData).access_token,
|
||||
},
|
||||
this.state.formVals.password,
|
||||
);
|
||||
|
@ -406,7 +408,7 @@ export default class Registration extends React.Component<IProps, IState> {
|
|||
newState.completedNoSignin = true;
|
||||
}
|
||||
|
||||
this.setState(newState);
|
||||
this.setState(newState as IState);
|
||||
};
|
||||
|
||||
private setupPushers(): Promise<void> {
|
||||
|
@ -455,7 +457,7 @@ export default class Registration extends React.Component<IProps, IState> {
|
|||
};
|
||||
|
||||
private makeRegisterRequest = (auth: IAuthData | null): Promise<IAuthData> => {
|
||||
const registerParams = {
|
||||
const registerParams: IRegisterRequestParams = {
|
||||
username: this.state.formVals.username,
|
||||
password: this.state.formVals.password,
|
||||
initial_device_display_name: this.props.defaultDeviceDisplayName,
|
||||
|
@ -571,7 +573,7 @@ export default class Registration extends React.Component<IProps, IState> {
|
|||
}
|
||||
}
|
||||
|
||||
public render(): JSX.Element {
|
||||
public render(): React.ReactNode {
|
||||
let errorText;
|
||||
const err = this.state.errorText;
|
||||
if (err) {
|
||||
|
|
|
@ -45,7 +45,7 @@ interface IState {
|
|||
}
|
||||
|
||||
export default class SetupEncryptionBody extends React.Component<IProps, IState> {
|
||||
public constructor(props) {
|
||||
public constructor(props: IProps) {
|
||||
super(props);
|
||||
const store = SetupEncryptionStore.sharedInstance();
|
||||
store.on("update", this.onStoreUpdate);
|
||||
|
@ -141,7 +141,7 @@ export default class SetupEncryptionBody extends React.Component<IProps, IState>
|
|||
this.props.onFinished();
|
||||
};
|
||||
|
||||
public render(): JSX.Element {
|
||||
public render(): React.ReactNode {
|
||||
const { phase, lostKeys } = this.state;
|
||||
|
||||
if (this.state.verificationRequest) {
|
||||
|
|
|
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import React, { ChangeEvent, SyntheticEvent } from "react";
|
||||
import { logger } from "matrix-js-sdk/src/logger";
|
||||
import { Optional } from "matrix-events-sdk";
|
||||
import { ISSOFlow, LoginFlow, SSOAction } from "matrix-js-sdk/src/@types/auth";
|
||||
|
@ -44,7 +44,7 @@ enum LoginView {
|
|||
Unsupported,
|
||||
}
|
||||
|
||||
const STATIC_FLOWS_TO_VIEWS = {
|
||||
const STATIC_FLOWS_TO_VIEWS: Record<string, LoginView> = {
|
||||
"m.login.password": LoginView.Password,
|
||||
"m.login.cas": LoginView.CAS,
|
||||
"m.login.sso": LoginView.SSO,
|
||||
|
@ -133,7 +133,7 @@ export default class SoftLogout extends React.Component<IProps, IState> {
|
|||
this.setState({ flows, loginView: chosenView });
|
||||
}
|
||||
|
||||
private onPasswordChange = (ev): void => {
|
||||
private onPasswordChange = (ev: ChangeEvent<HTMLInputElement>): void => {
|
||||
this.setState({ password: ev.target.value });
|
||||
};
|
||||
|
||||
|
@ -141,7 +141,7 @@ export default class SoftLogout extends React.Component<IProps, IState> {
|
|||
dis.dispatch({ action: "start_password_recovery" });
|
||||
};
|
||||
|
||||
private onPasswordLogin = async (ev): Promise<void> => {
|
||||
private onPasswordLogin = async (ev: SyntheticEvent): Promise<void> => {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
|
||||
|
@ -326,7 +326,7 @@ export default class SoftLogout extends React.Component<IProps, IState> {
|
|||
);
|
||||
}
|
||||
|
||||
public render(): JSX.Element {
|
||||
public render(): React.ReactNode {
|
||||
return (
|
||||
<AuthPage>
|
||||
<AuthHeader />
|
||||
|
@ -339,7 +339,7 @@ export default class SoftLogout extends React.Component<IProps, IState> {
|
|||
<h2>{_t("Clear personal data")}</h2>
|
||||
<p>
|
||||
{_t(
|
||||
"Warning: Your personal data (including encryption keys) is still stored " +
|
||||
"Warning: your personal data (including encryption keys) is still stored " +
|
||||
"in this session. Clear it if you're finished using this session, or want to sign " +
|
||||
"in to another account.",
|
||||
)}
|
||||
|
|
|
@ -52,8 +52,8 @@ export const VerifyEmailModal: React.FC<Props> = ({
|
|||
<h1>{_t("Verify your email to continue")}</h1>
|
||||
<p>
|
||||
{_t(
|
||||
`We need to know it’s you before resetting your password.
|
||||
Click the link in the email we just sent to <b>%(email)s</b>`,
|
||||
"We need to know it’s you before resetting your password. " +
|
||||
"Click the link in the email we just sent to <b>%(email)s</b>",
|
||||
{
|
||||
email,
|
||||
},
|
||||
|
|
|
@ -43,7 +43,7 @@ export default class Clock extends React.Component<Props> {
|
|||
return currentFloor !== nextFloor;
|
||||
}
|
||||
|
||||
public render(): JSX.Element {
|
||||
public render(): React.ReactNode {
|
||||
return (
|
||||
<span aria-live={this.props["aria-live"]} role={this.props.role} className="mx_Clock">
|
||||
{this.props.formatFn(this.props.seconds)}
|
||||
|
|
|
@ -31,7 +31,7 @@ interface IState {
|
|||
* A clock which shows a clip's maximum duration.
|
||||
*/
|
||||
export default class DurationClock extends React.PureComponent<IProps, IState> {
|
||||
public constructor(props) {
|
||||
public constructor(props: IProps) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
|
@ -48,7 +48,7 @@ export default class DurationClock extends React.PureComponent<IProps, IState> {
|
|||
this.setState({ durationSeconds: time[1] });
|
||||
};
|
||||
|
||||
public render(): JSX.Element {
|
||||
public render(): React.ReactNode {
|
||||
return <Clock seconds={this.state.durationSeconds} />;
|
||||
}
|
||||
}
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue