Conform more of the codebase to strictNullChecks
(#10350
* Conform more of the codebase to `strictNullChecks` * Iterate * Generics ftw * Iterate
This commit is contained in:
parent
d53e91802d
commit
127a3b667c
53 changed files with 279 additions and 263 deletions
|
@ -52,3 +52,5 @@ export type KeysStartingWith<Input extends object, Str extends string> = {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
[P in keyof Input]: P extends `${Str}${infer _X}` ? P : never; // we don't use _X
|
[P in keyof Input]: P extends `${Str}${infer _X}` ? P : never; // we don't use _X
|
||||||
}[keyof Input];
|
}[keyof Input];
|
||||||
|
|
||||||
|
export type NonEmptyArray<T> = [T, ...T[]];
|
||||||
|
|
|
@ -26,7 +26,11 @@ import { SSOAuthEntry } from "./components/views/auth/InteractiveAuthEntryCompon
|
||||||
import InteractiveAuthDialog from "./components/views/dialogs/InteractiveAuthDialog";
|
import InteractiveAuthDialog from "./components/views/dialogs/InteractiveAuthDialog";
|
||||||
|
|
||||||
function getIdServerDomain(): string {
|
function getIdServerDomain(): string {
|
||||||
return MatrixClientPeg.get().idBaseUrl.split("://")[1];
|
const idBaseUrl = MatrixClientPeg.get().idBaseUrl;
|
||||||
|
if (!idBaseUrl) {
|
||||||
|
throw new Error("Identity server not set");
|
||||||
|
}
|
||||||
|
return idBaseUrl.split("://")[1];
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Binding = {
|
export type Binding = {
|
||||||
|
@ -190,6 +194,9 @@ export default class AddThreepid {
|
||||||
if (this.bind) {
|
if (this.bind) {
|
||||||
const authClient = new IdentityAuthClient();
|
const authClient = new IdentityAuthClient();
|
||||||
const identityAccessToken = await authClient.getAccessToken();
|
const identityAccessToken = await authClient.getAccessToken();
|
||||||
|
if (!identityAccessToken) {
|
||||||
|
throw new Error("No identity access token found");
|
||||||
|
}
|
||||||
await MatrixClientPeg.get().bindThreePid({
|
await MatrixClientPeg.get().bindThreePid({
|
||||||
sid: this.sessionId,
|
sid: this.sessionId,
|
||||||
client_secret: this.clientSecret,
|
client_secret: this.clientSecret,
|
||||||
|
@ -279,7 +286,9 @@ export default class AddThreepid {
|
||||||
* with a "message" property which contains a human-readable message detailing why
|
* with a "message" property which contains a human-readable message detailing why
|
||||||
* the request failed.
|
* the request failed.
|
||||||
*/
|
*/
|
||||||
public async haveMsisdnToken(msisdnToken: string): Promise<any[] | undefined> {
|
public async haveMsisdnToken(
|
||||||
|
msisdnToken: string,
|
||||||
|
): Promise<[success?: boolean, result?: IAuthData | Error | null] | undefined> {
|
||||||
const authClient = new IdentityAuthClient();
|
const authClient = new IdentityAuthClient();
|
||||||
const supportsSeparateAddAndBind = await MatrixClientPeg.get().doesServerSupportSeparateAddAndBind();
|
const supportsSeparateAddAndBind = await MatrixClientPeg.get().doesServerSupportSeparateAddAndBind();
|
||||||
|
|
||||||
|
|
|
@ -39,7 +39,7 @@ export const ContextMenuButton: React.FC<IProps> = ({
|
||||||
<AccessibleButton
|
<AccessibleButton
|
||||||
{...props}
|
{...props}
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
onContextMenu={onContextMenu || onClick}
|
onContextMenu={onContextMenu ?? onClick ?? undefined}
|
||||||
title={label}
|
title={label}
|
||||||
aria-label={label}
|
aria-label={label}
|
||||||
aria-haspopup={true}
|
aria-haspopup={true}
|
||||||
|
|
|
@ -37,7 +37,7 @@ export const ContextMenuTooltipButton: React.FC<IProps> = ({
|
||||||
<AccessibleTooltipButton
|
<AccessibleTooltipButton
|
||||||
{...props}
|
{...props}
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
onContextMenu={onContextMenu || onClick}
|
onContextMenu={onContextMenu ?? onClick ?? undefined}
|
||||||
aria-haspopup={true}
|
aria-haspopup={true}
|
||||||
aria-expanded={isExpanded}
|
aria-expanded={isExpanded}
|
||||||
forceHide={isExpanded}
|
forceHide={isExpanded}
|
||||||
|
|
|
@ -26,7 +26,7 @@ import { _t } from "../languageHandler";
|
||||||
import { AsyncActionPayload } from "../dispatcher/payloads";
|
import { AsyncActionPayload } from "../dispatcher/payloads";
|
||||||
import RoomListStore from "../stores/room-list/RoomListStore";
|
import RoomListStore from "../stores/room-list/RoomListStore";
|
||||||
import { SortAlgorithm } from "../stores/room-list/algorithms/models";
|
import { SortAlgorithm } from "../stores/room-list/algorithms/models";
|
||||||
import { DefaultTagID } from "../stores/room-list/models";
|
import { DefaultTagID, TagID } from "../stores/room-list/models";
|
||||||
import ErrorDialog from "../components/views/dialogs/ErrorDialog";
|
import ErrorDialog from "../components/views/dialogs/ErrorDialog";
|
||||||
|
|
||||||
export default class RoomListActions {
|
export default class RoomListActions {
|
||||||
|
@ -49,10 +49,10 @@ export default class RoomListActions {
|
||||||
public static tagRoom(
|
public static tagRoom(
|
||||||
matrixClient: MatrixClient,
|
matrixClient: MatrixClient,
|
||||||
room: Room,
|
room: Room,
|
||||||
oldTag: string,
|
oldTag: TagID | null,
|
||||||
newTag: string,
|
newTag: TagID | null,
|
||||||
oldIndex: number | null,
|
oldIndex?: number,
|
||||||
newIndex: number | null,
|
newIndex?: number,
|
||||||
): AsyncActionPayload {
|
): AsyncActionPayload {
|
||||||
let metaData: Parameters<MatrixClient["setRoomTag"]>[2] | null = null;
|
let metaData: Parameters<MatrixClient["setRoomTag"]>[2] | null = null;
|
||||||
|
|
||||||
|
|
|
@ -106,7 +106,7 @@ export class PlaybackQueue {
|
||||||
// Remove the now-useless clock for some space savings
|
// Remove the now-useless clock for some space savings
|
||||||
this.clockStates.delete(mxEvent.getId()!);
|
this.clockStates.delete(mxEvent.getId()!);
|
||||||
|
|
||||||
if (wasLastPlaying) {
|
if (wasLastPlaying && this.currentPlaybackId) {
|
||||||
this.recentFullPlays.add(this.currentPlaybackId);
|
this.recentFullPlays.add(this.currentPlaybackId);
|
||||||
const orderClone = arrayFastClone(this.playbackIdOrder);
|
const orderClone = arrayFastClone(this.playbackIdOrder);
|
||||||
const last = orderClone.pop();
|
const last = orderClone.pop();
|
||||||
|
@ -188,8 +188,8 @@ export class PlaybackQueue {
|
||||||
if (order.length === 0 || order[order.length - 1] !== this.currentPlaybackId) {
|
if (order.length === 0 || order[order.length - 1] !== this.currentPlaybackId) {
|
||||||
const lastInstance = this.playbacks.get(this.currentPlaybackId);
|
const lastInstance = this.playbacks.get(this.currentPlaybackId);
|
||||||
if (
|
if (
|
||||||
lastInstance.currentState === PlaybackState.Playing ||
|
lastInstance &&
|
||||||
lastInstance.currentState === PlaybackState.Paused
|
[PlaybackState.Playing, PlaybackState.Paused].includes(lastInstance.currentState)
|
||||||
) {
|
) {
|
||||||
order.push(this.currentPlaybackId);
|
order.push(this.currentPlaybackId);
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,6 +35,9 @@ export async function getThreepidsWithBindStatus(
|
||||||
try {
|
try {
|
||||||
const authClient = new IdentityAuthClient();
|
const authClient = new IdentityAuthClient();
|
||||||
const identityAccessToken = await authClient.getAccessToken({ check: false });
|
const identityAccessToken = await authClient.getAccessToken({ check: false });
|
||||||
|
if (!identityAccessToken) {
|
||||||
|
throw new Error("No identity access token found");
|
||||||
|
}
|
||||||
|
|
||||||
// Restructure for lookup query
|
// Restructure for lookup query
|
||||||
const query = threepids.map(({ medium, address }): [string, string] => [medium, address]);
|
const query = threepids.map(({ medium, address }): [string, string] => [medium, address]);
|
||||||
|
|
|
@ -29,7 +29,7 @@ export type IProps<T extends keyof JSX.IntrinsicElements> = Omit<DynamicHtmlElem
|
||||||
onWheel?: (event: WheelEvent) => void;
|
onWheel?: (event: WheelEvent) => void;
|
||||||
style?: React.CSSProperties;
|
style?: React.CSSProperties;
|
||||||
tabIndex?: number;
|
tabIndex?: number;
|
||||||
wrappedRef?: (ref: HTMLDivElement) => void;
|
wrappedRef?: (ref: HTMLDivElement | null) => void;
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -99,7 +99,7 @@ class FilePanel extends React.Component<IProps, IState> {
|
||||||
|
|
||||||
const timeline = this.state.timelineSet.getLiveTimeline();
|
const timeline = this.state.timelineSet.getLiveTimeline();
|
||||||
if (ev.getType() !== "m.room.message") return;
|
if (ev.getType() !== "m.room.message") return;
|
||||||
if (["m.file", "m.image", "m.video", "m.audio"].indexOf(ev.getContent().msgtype) == -1) {
|
if (!["m.file", "m.image", "m.video", "m.audio"].includes(ev.getContent().msgtype!)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -176,7 +176,7 @@ class FilePanel extends React.Component<IProps, IState> {
|
||||||
// the event index to fulfill the pagination request. Asking the server
|
// the event index to fulfill the pagination request. Asking the server
|
||||||
// to paginate won't ever work since the server can't correctly filter
|
// to paginate won't ever work since the server can't correctly filter
|
||||||
// out events containing URLs
|
// out events containing URLs
|
||||||
if (client.isRoomEncrypted(roomId) && eventIndex !== null) {
|
if (room && client.isRoomEncrypted(roomId) && eventIndex !== null) {
|
||||||
return eventIndex.paginateTimelineWindow(room, timelineWindow, direction, limit);
|
return eventIndex.paginateTimelineWindow(room, timelineWindow, direction, limit);
|
||||||
} else {
|
} else {
|
||||||
return timelineWindow.paginate(direction, limit);
|
return timelineWindow.paginate(direction, limit);
|
||||||
|
|
|
@ -177,19 +177,20 @@ export function GenericDropdownMenu<T>({
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
const contextMenu = menuDisplayed ? (
|
const contextMenu =
|
||||||
<ContextMenu
|
menuDisplayed && button.current ? (
|
||||||
onFinished={closeMenu}
|
<ContextMenu
|
||||||
chevronFace={ChevronFace.Top}
|
onFinished={closeMenu}
|
||||||
wrapperClassName={classNames("mx_GenericDropdownMenu_wrapper", className)}
|
chevronFace={ChevronFace.Top}
|
||||||
{...aboveLeftOf(button.current.getBoundingClientRect())}
|
wrapperClassName={classNames("mx_GenericDropdownMenu_wrapper", className)}
|
||||||
>
|
{...aboveLeftOf(button.current.getBoundingClientRect())}
|
||||||
{contextMenuOptions}
|
>
|
||||||
{AdditionalOptions && (
|
{contextMenuOptions}
|
||||||
<AdditionalOptions menuDisplayed={menuDisplayed} openMenu={openMenu} closeMenu={closeMenu} />
|
{AdditionalOptions && (
|
||||||
)}
|
<AdditionalOptions menuDisplayed={menuDisplayed} openMenu={openMenu} closeMenu={closeMenu} />
|
||||||
</ContextMenu>
|
)}
|
||||||
) : null;
|
</ContextMenu>
|
||||||
|
) : null;
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<ContextMenuButton
|
<ContextMenuButton
|
||||||
|
|
|
@ -44,7 +44,7 @@ export default class IndicatorScrollbar<T extends keyof JSX.IntrinsicElements> e
|
||||||
> {
|
> {
|
||||||
private autoHideScrollbar = createRef<AutoHideScrollbar<any>>();
|
private autoHideScrollbar = createRef<AutoHideScrollbar<any>>();
|
||||||
private scrollElement: HTMLDivElement;
|
private scrollElement: HTMLDivElement;
|
||||||
private likelyTrackpadUser: boolean = null;
|
private likelyTrackpadUser: boolean | null = null;
|
||||||
private checkAgainForTrackpad = 0; // ts in milliseconds to recheck this._likelyTrackpadUser
|
private checkAgainForTrackpad = 0; // ts in milliseconds to recheck this._likelyTrackpadUser
|
||||||
|
|
||||||
public constructor(props: IProps<T>) {
|
public constructor(props: IProps<T>) {
|
||||||
|
|
|
@ -271,6 +271,7 @@ export default class LeftPanel extends React.Component<IProps, IState> {
|
||||||
// add appropriate sticky classes to wrapper so it has
|
// add appropriate sticky classes to wrapper so it has
|
||||||
// the necessary top/bottom padding to put the sticky header in
|
// the necessary top/bottom padding to put the sticky header in
|
||||||
const listWrapper = list.parentElement; // .mx_LeftPanel_roomListWrapper
|
const listWrapper = list.parentElement; // .mx_LeftPanel_roomListWrapper
|
||||||
|
if (!listWrapper) return;
|
||||||
if (lastTopHeader) {
|
if (lastTopHeader) {
|
||||||
listWrapper.classList.add("mx_LeftPanel_roomListWrapper_stickyTop");
|
listWrapper.classList.add("mx_LeftPanel_roomListWrapper_stickyTop");
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -85,23 +85,23 @@ export default class LegacyCallEventGrouper extends EventEmitter {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private get invite(): MatrixEvent {
|
private get invite(): MatrixEvent | undefined {
|
||||||
return [...this.events].find((event) => event.getType() === EventType.CallInvite);
|
return [...this.events].find((event) => event.getType() === EventType.CallInvite);
|
||||||
}
|
}
|
||||||
|
|
||||||
private get hangup(): MatrixEvent {
|
private get hangup(): MatrixEvent | undefined {
|
||||||
return [...this.events].find((event) => event.getType() === EventType.CallHangup);
|
return [...this.events].find((event) => event.getType() === EventType.CallHangup);
|
||||||
}
|
}
|
||||||
|
|
||||||
private get reject(): MatrixEvent {
|
private get reject(): MatrixEvent | undefined {
|
||||||
return [...this.events].find((event) => event.getType() === EventType.CallReject);
|
return [...this.events].find((event) => event.getType() === EventType.CallReject);
|
||||||
}
|
}
|
||||||
|
|
||||||
private get selectAnswer(): MatrixEvent {
|
private get selectAnswer(): MatrixEvent | undefined {
|
||||||
return [...this.events].find((event) => event.getType() === EventType.CallSelectAnswer);
|
return [...this.events].find((event) => event.getType() === EventType.CallSelectAnswer);
|
||||||
}
|
}
|
||||||
|
|
||||||
public get isVoice(): boolean {
|
public get isVoice(): boolean | undefined {
|
||||||
const invite = this.invite;
|
const invite = this.invite;
|
||||||
if (!invite) return;
|
if (!invite) return;
|
||||||
|
|
||||||
|
@ -114,7 +114,7 @@ export default class LegacyCallEventGrouper extends EventEmitter {
|
||||||
return this.call?.hangupReason ?? this.hangup?.getContent()?.reason ?? null;
|
return this.call?.hangupReason ?? this.hangup?.getContent()?.reason ?? null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public get rejectParty(): string {
|
public get rejectParty(): string | undefined {
|
||||||
return this.reject?.getSender();
|
return this.reject?.getSender();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -170,7 +170,7 @@ class LoggedInView extends React.Component<IProps, IState> {
|
||||||
monitorSyncedPushRules(this._matrixClient.getAccountData("m.push_rules"), this._matrixClient);
|
monitorSyncedPushRules(this._matrixClient.getAccountData("m.push_rules"), this._matrixClient);
|
||||||
this._matrixClient.on(ClientEvent.Sync, this.onSync);
|
this._matrixClient.on(ClientEvent.Sync, this.onSync);
|
||||||
// Call `onSync` with the current state as well
|
// Call `onSync` with the current state as well
|
||||||
this.onSync(this._matrixClient.getSyncState(), null, this._matrixClient.getSyncStateData());
|
this.onSync(this._matrixClient.getSyncState(), null, this._matrixClient.getSyncStateData() ?? undefined);
|
||||||
this._matrixClient.on(RoomStateEvent.Events, this.onRoomStateEvents);
|
this._matrixClient.on(RoomStateEvent.Events, this.onRoomStateEvents);
|
||||||
|
|
||||||
this.layoutWatcherRef = SettingsStore.watchSetting("layout", null, this.onCompactLayoutChanged);
|
this.layoutWatcherRef = SettingsStore.watchSetting("layout", null, this.onCompactLayoutChanged);
|
||||||
|
@ -271,11 +271,11 @@ class LoggedInView extends React.Component<IProps, IState> {
|
||||||
}
|
}
|
||||||
|
|
||||||
private loadResizerPreferences(): void {
|
private loadResizerPreferences(): void {
|
||||||
let lhsSize = parseInt(window.localStorage.getItem("mx_lhs_size"), 10);
|
let lhsSize = parseInt(window.localStorage.getItem("mx_lhs_size")!, 10);
|
||||||
if (isNaN(lhsSize)) {
|
if (isNaN(lhsSize)) {
|
||||||
lhsSize = 350;
|
lhsSize = 350;
|
||||||
}
|
}
|
||||||
this.resizer.forHandleWithId("lp-resizer").resize(lhsSize);
|
this.resizer.forHandleWithId("lp-resizer")?.resize(lhsSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
private onAccountData = (event: MatrixEvent): void => {
|
private onAccountData = (event: MatrixEvent): void => {
|
||||||
|
@ -291,13 +291,13 @@ class LoggedInView extends React.Component<IProps, IState> {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
private onSync = (syncState: SyncState, oldSyncState?: SyncState, data?: ISyncStateData): void => {
|
private onSync = (syncState: SyncState | null, oldSyncState: SyncState | null, data?: ISyncStateData): void => {
|
||||||
const oldErrCode = (this.state.syncErrorData?.error as MatrixError)?.errcode;
|
const oldErrCode = (this.state.syncErrorData?.error as MatrixError)?.errcode;
|
||||||
const newErrCode = (data?.error as MatrixError)?.errcode;
|
const newErrCode = (data?.error as MatrixError)?.errcode;
|
||||||
if (syncState === oldSyncState && oldErrCode === newErrCode) return;
|
if (syncState === oldSyncState && oldErrCode === newErrCode) return;
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
syncErrorData: syncState === SyncState.Error ? data : null,
|
syncErrorData: syncState === SyncState.Error ? data : undefined,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (oldSyncState === SyncState.Prepared && syncState === SyncState.Syncing) {
|
if (oldSyncState === SyncState.Prepared && syncState === SyncState.Syncing) {
|
||||||
|
@ -355,7 +355,7 @@ class LoggedInView extends React.Component<IProps, IState> {
|
||||||
const pinnedEventIds = pinStateEvent.getContent().pinned.slice(0, MAX_PINNED_NOTICES_PER_ROOM);
|
const pinnedEventIds = pinStateEvent.getContent().pinned.slice(0, MAX_PINNED_NOTICES_PER_ROOM);
|
||||||
for (const eventId of pinnedEventIds) {
|
for (const eventId of pinnedEventIds) {
|
||||||
const timeline = await this._matrixClient.getEventTimeline(room.getUnfilteredTimelineSet(), eventId);
|
const timeline = await this._matrixClient.getEventTimeline(room.getUnfilteredTimelineSet(), eventId);
|
||||||
const event = timeline.getEvents().find((ev) => ev.getId() === eventId);
|
const event = timeline?.getEvents().find((ev) => ev.getId() === eventId);
|
||||||
if (event) events.push(event);
|
if (event) events.push(event);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -390,7 +390,7 @@ class LoggedInView extends React.Component<IProps, IState> {
|
||||||
if (inputableElement?.focus) {
|
if (inputableElement?.focus) {
|
||||||
inputableElement.focus();
|
inputableElement.focus();
|
||||||
} else {
|
} else {
|
||||||
const inThread = !!document.activeElement.closest(".mx_ThreadView");
|
const inThread = !!document.activeElement?.closest(".mx_ThreadView");
|
||||||
// refocusing during a paste event will make the paste end up in the newly focused element,
|
// refocusing during a paste event will make the paste end up in the newly focused element,
|
||||||
// so dispatch synchronously before paste happens
|
// so dispatch synchronously before paste happens
|
||||||
dis.dispatch(
|
dis.dispatch(
|
||||||
|
@ -533,11 +533,11 @@ class LoggedInView extends React.Component<IProps, IState> {
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
case KeyBindingAction.PreviousVisitedRoomOrSpace:
|
case KeyBindingAction.PreviousVisitedRoomOrSpace:
|
||||||
PlatformPeg.get().navigateForwardBack(true);
|
PlatformPeg.get()?.navigateForwardBack(true);
|
||||||
handled = true;
|
handled = true;
|
||||||
break;
|
break;
|
||||||
case KeyBindingAction.NextVisitedRoomOrSpace:
|
case KeyBindingAction.NextVisitedRoomOrSpace:
|
||||||
PlatformPeg.get().navigateForwardBack(false);
|
PlatformPeg.get()?.navigateForwardBack(false);
|
||||||
handled = true;
|
handled = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -555,7 +555,7 @@ class LoggedInView extends React.Component<IProps, IState> {
|
||||||
);
|
);
|
||||||
SettingsStore.setValue(
|
SettingsStore.setValue(
|
||||||
"showHiddenEventsInTimeline",
|
"showHiddenEventsInTimeline",
|
||||||
undefined,
|
null,
|
||||||
SettingLevel.DEVICE,
|
SettingLevel.DEVICE,
|
||||||
!hiddenEventVisibility,
|
!hiddenEventVisibility,
|
||||||
);
|
);
|
||||||
|
@ -567,7 +567,7 @@ class LoggedInView extends React.Component<IProps, IState> {
|
||||||
|
|
||||||
if (
|
if (
|
||||||
!handled &&
|
!handled &&
|
||||||
PlatformPeg.get().overrideBrowserShortcuts() &&
|
PlatformPeg.get()?.overrideBrowserShortcuts() &&
|
||||||
ev.code.startsWith("Digit") &&
|
ev.code.startsWith("Digit") &&
|
||||||
ev.code !== "Digit0" && // this is the shortcut for reset zoom, don't override it
|
ev.code !== "Digit0" && // this is the shortcut for reset zoom, don't override it
|
||||||
isOnlyCtrlOrCmdKeyEvent(ev)
|
isOnlyCtrlOrCmdKeyEvent(ev)
|
||||||
|
@ -599,7 +599,7 @@ class LoggedInView extends React.Component<IProps, IState> {
|
||||||
// If the user is entering a printable character outside of an input field
|
// If the user is entering a printable character outside of an input field
|
||||||
// redirect it to the composer for them.
|
// redirect it to the composer for them.
|
||||||
if (!isClickShortcut && isPrintable && !getInputableElement(ev.target as HTMLElement)) {
|
if (!isClickShortcut && isPrintable && !getInputableElement(ev.target as HTMLElement)) {
|
||||||
const inThread = !!document.activeElement.closest(".mx_ThreadView");
|
const inThread = !!document.activeElement?.closest(".mx_ThreadView");
|
||||||
// synchronous dispatch so we focus before key generates input
|
// synchronous dispatch so we focus before key generates input
|
||||||
dis.dispatch(
|
dis.dispatch(
|
||||||
{
|
{
|
||||||
|
|
|
@ -187,9 +187,9 @@ interface IState {
|
||||||
// The ID of the room we're viewing. This is either populated directly
|
// The ID of the room we're viewing. This is either populated directly
|
||||||
// in the case where we view a room by ID or by RoomView when it resolves
|
// in the case where we view a room by ID or by RoomView when it resolves
|
||||||
// what ID an alias points at.
|
// what ID an alias points at.
|
||||||
currentRoomId?: string;
|
currentRoomId: string | null;
|
||||||
// If we're trying to just view a user ID (i.e. /user URL), this is it
|
// If we're trying to just view a user ID (i.e. /user URL), this is it
|
||||||
currentUserId?: string;
|
currentUserId: string | null;
|
||||||
// this is persisted as mx_lhs_size, loaded in LoggedInView
|
// this is persisted as mx_lhs_size, loaded in LoggedInView
|
||||||
collapseLhs: boolean;
|
collapseLhs: boolean;
|
||||||
// Parameters used in the registration dance with the IS
|
// Parameters used in the registration dance with the IS
|
||||||
|
@ -202,7 +202,7 @@ interface IState {
|
||||||
// When showing Modal dialogs we need to set aria-hidden on the root app element
|
// When showing Modal dialogs we need to set aria-hidden on the root app element
|
||||||
// and disable it when there are no dialogs
|
// and disable it when there are no dialogs
|
||||||
hideToSRUsers: boolean;
|
hideToSRUsers: boolean;
|
||||||
syncError?: Error;
|
syncError: Error | null;
|
||||||
resizeNotifier: ResizeNotifier;
|
resizeNotifier: ResizeNotifier;
|
||||||
serverConfig?: ValidatedServerConfig;
|
serverConfig?: ValidatedServerConfig;
|
||||||
ready: boolean;
|
ready: boolean;
|
||||||
|
@ -248,6 +248,8 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||||
this.state = {
|
this.state = {
|
||||||
view: Views.LOADING,
|
view: Views.LOADING,
|
||||||
collapseLhs: false,
|
collapseLhs: false,
|
||||||
|
currentRoomId: null,
|
||||||
|
currentUserId: null,
|
||||||
|
|
||||||
hideToSRUsers: false,
|
hideToSRUsers: false,
|
||||||
|
|
||||||
|
@ -469,9 +471,9 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||||
);
|
);
|
||||||
}, 1000);
|
}, 1000);
|
||||||
|
|
||||||
private getFallbackHsUrl(): string {
|
private getFallbackHsUrl(): string | null {
|
||||||
if (this.props.serverConfig?.isDefault) {
|
if (this.props.serverConfig?.isDefault) {
|
||||||
return this.props.config.fallback_hs_url;
|
return this.props.config.fallback_hs_url ?? null;
|
||||||
} else {
|
} else {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -480,7 +482,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||||
private getServerProperties(): { serverConfig: ValidatedServerConfig } {
|
private getServerProperties(): { serverConfig: ValidatedServerConfig } {
|
||||||
let props = this.state.serverConfig;
|
let props = this.state.serverConfig;
|
||||||
if (!props) props = this.props.serverConfig; // for unit tests
|
if (!props) props = this.props.serverConfig; // for unit tests
|
||||||
if (!props) props = SdkConfig.get("validated_server_config");
|
if (!props) props = SdkConfig.get("validated_server_config")!;
|
||||||
return { serverConfig: props };
|
return { serverConfig: props };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -709,7 +711,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||||
Modal.createDialog(
|
Modal.createDialog(
|
||||||
UserSettingsDialog,
|
UserSettingsDialog,
|
||||||
{ initialTabId: tabPayload.initialTabId as UserTab },
|
{ initialTabId: tabPayload.initialTabId as UserTab },
|
||||||
/*className=*/ null,
|
/*className=*/ undefined,
|
||||||
/*isPriority=*/ false,
|
/*isPriority=*/ false,
|
||||||
/*isStatic=*/ true,
|
/*isStatic=*/ true,
|
||||||
);
|
);
|
||||||
|
@ -978,7 +980,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||||
this.setState(
|
this.setState(
|
||||||
{
|
{
|
||||||
view: Views.LOGGED_IN,
|
view: Views.LOGGED_IN,
|
||||||
currentRoomId: roomInfo.room_id || null,
|
currentRoomId: roomInfo.room_id ?? null,
|
||||||
page_type: PageType.RoomView,
|
page_type: PageType.RoomView,
|
||||||
threepidInvite: roomInfo.threepid_invite,
|
threepidInvite: roomInfo.threepid_invite,
|
||||||
roomOobData: roomInfo.oob_data,
|
roomOobData: roomInfo.oob_data,
|
||||||
|
@ -1063,7 +1065,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||||
|
|
||||||
const [shouldCreate, opts] = await modal.finished;
|
const [shouldCreate, opts] = await modal.finished;
|
||||||
if (shouldCreate) {
|
if (shouldCreate) {
|
||||||
createRoom(opts);
|
createRoom(opts!);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1122,7 +1124,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||||
// Show a warning if there are additional complications.
|
// Show a warning if there are additional complications.
|
||||||
const warnings: JSX.Element[] = [];
|
const warnings: JSX.Element[] = [];
|
||||||
|
|
||||||
const memberCount = roomToLeave.currentState.getJoinedMemberCount();
|
const memberCount = roomToLeave?.currentState.getJoinedMemberCount();
|
||||||
if (memberCount === 1) {
|
if (memberCount === 1) {
|
||||||
warnings.push(
|
warnings.push(
|
||||||
<span className="warning" key="only_member_warning">
|
<span className="warning" key="only_member_warning">
|
||||||
|
@ -1137,7 +1139,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||||
return warnings;
|
return warnings;
|
||||||
}
|
}
|
||||||
|
|
||||||
const joinRules = roomToLeave.currentState.getStateEvents("m.room.join_rules", "");
|
const joinRules = roomToLeave?.currentState.getStateEvents("m.room.join_rules", "");
|
||||||
if (joinRules) {
|
if (joinRules) {
|
||||||
const rule = joinRules.getContent().join_rule;
|
const rule = joinRules.getContent().join_rule;
|
||||||
if (rule !== "public") {
|
if (rule !== "public") {
|
||||||
|
@ -1165,9 +1167,11 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||||
<span>
|
<span>
|
||||||
{isSpace
|
{isSpace
|
||||||
? _t("Are you sure you want to leave the space '%(spaceName)s'?", {
|
? _t("Are you sure you want to leave the space '%(spaceName)s'?", {
|
||||||
spaceName: roomToLeave.name,
|
spaceName: roomToLeave?.name ?? _t("Unnamed Space"),
|
||||||
})
|
})
|
||||||
: _t("Are you sure you want to leave the room '%(roomName)s'?", { roomName: roomToLeave.name })}
|
: _t("Are you sure you want to leave the room '%(roomName)s'?", {
|
||||||
|
roomName: roomToLeave?.name ?? _t("Unnamed Room"),
|
||||||
|
})}
|
||||||
{warnings}
|
{warnings}
|
||||||
</span>
|
</span>
|
||||||
),
|
),
|
||||||
|
@ -1311,9 +1315,9 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||||
this.setStateForNewView({ view: Views.LOGGED_IN });
|
this.setStateForNewView({ view: Views.LOGGED_IN });
|
||||||
// If a specific screen is set to be shown after login, show that above
|
// If a specific screen is set to be shown after login, show that above
|
||||||
// all else, as it probably means the user clicked on something already.
|
// all else, as it probably means the user clicked on something already.
|
||||||
if (this.screenAfterLogin && this.screenAfterLogin.screen) {
|
if (this.screenAfterLogin?.screen) {
|
||||||
this.showScreen(this.screenAfterLogin.screen, this.screenAfterLogin.params);
|
this.showScreen(this.screenAfterLogin.screen, this.screenAfterLogin.params);
|
||||||
this.screenAfterLogin = null;
|
this.screenAfterLogin = undefined;
|
||||||
} else if (MatrixClientPeg.currentUserIsJustRegistered()) {
|
} else if (MatrixClientPeg.currentUserIsJustRegistered()) {
|
||||||
MatrixClientPeg.setJustRegisteredUserId(null);
|
MatrixClientPeg.setJustRegisteredUserId(null);
|
||||||
|
|
||||||
|
@ -1403,7 +1407,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||||
// result in view_home_page, _user_settings or _room_directory
|
// result in view_home_page, _user_settings or _room_directory
|
||||||
if (this.screenAfterLogin && this.screenAfterLogin.screen) {
|
if (this.screenAfterLogin && this.screenAfterLogin.screen) {
|
||||||
this.showScreen(this.screenAfterLogin.screen, this.screenAfterLogin.params);
|
this.showScreen(this.screenAfterLogin.screen, this.screenAfterLogin.params);
|
||||||
this.screenAfterLogin = null;
|
this.screenAfterLogin = undefined;
|
||||||
} else if (localStorage && localStorage.getItem("mx_last_room_id")) {
|
} else if (localStorage && localStorage.getItem("mx_last_room_id")) {
|
||||||
// Before defaulting to directory, show the last viewed room
|
// Before defaulting to directory, show the last viewed room
|
||||||
this.viewLastRoom();
|
this.viewLastRoom();
|
||||||
|
@ -1419,7 +1423,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||||
private viewLastRoom(): void {
|
private viewLastRoom(): void {
|
||||||
dis.dispatch<ViewRoomPayload>({
|
dis.dispatch<ViewRoomPayload>({
|
||||||
action: Action.ViewRoom,
|
action: Action.ViewRoom,
|
||||||
room_id: localStorage.getItem("mx_last_room_id"),
|
room_id: localStorage.getItem("mx_last_room_id") ?? undefined,
|
||||||
metricsTrigger: undefined, // other
|
metricsTrigger: undefined, // other
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -1486,12 +1490,12 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||||
return this.loggedInView.current.canResetTimelineInRoom(roomId);
|
return this.loggedInView.current.canResetTimelineInRoom(roomId);
|
||||||
});
|
});
|
||||||
|
|
||||||
cli.on(ClientEvent.Sync, (state: SyncState, prevState?: SyncState, data?: ISyncStateData) => {
|
cli.on(ClientEvent.Sync, (state: SyncState, prevState: SyncState | null, data?: ISyncStateData) => {
|
||||||
if (state === SyncState.Error || state === SyncState.Reconnecting) {
|
if (state === SyncState.Error || state === SyncState.Reconnecting) {
|
||||||
if (data.error instanceof InvalidStoreError) {
|
if (data?.error instanceof InvalidStoreError) {
|
||||||
Lifecycle.handleInvalidStoreError(data.error);
|
Lifecycle.handleInvalidStoreError(data.error);
|
||||||
}
|
}
|
||||||
this.setState({ syncError: data.error });
|
this.setState({ syncError: data?.error ?? null });
|
||||||
} else if (this.state.syncError) {
|
} else if (this.state.syncError) {
|
||||||
this.setState({ syncError: null });
|
this.setState({ syncError: null });
|
||||||
}
|
}
|
||||||
|
@ -1559,12 +1563,12 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||||
cancelButton: _t("Dismiss"),
|
cancelButton: _t("Dismiss"),
|
||||||
onFinished: (confirmed) => {
|
onFinished: (confirmed) => {
|
||||||
if (confirmed) {
|
if (confirmed) {
|
||||||
const wnd = window.open(consentUri, "_blank");
|
const wnd = window.open(consentUri, "_blank")!;
|
||||||
wnd.opener = null;
|
wnd.opener = null;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
null,
|
undefined,
|
||||||
true,
|
true,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -1655,7 +1659,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||||
{
|
{
|
||||||
verifier: request.verifier,
|
verifier: request.verifier,
|
||||||
},
|
},
|
||||||
null,
|
undefined,
|
||||||
/* priority = */ false,
|
/* priority = */ false,
|
||||||
/* static = */ true,
|
/* static = */ true,
|
||||||
);
|
);
|
||||||
|
@ -1774,7 +1778,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||||
}
|
}
|
||||||
|
|
||||||
const type = screen === "start_sso" ? "sso" : "cas";
|
const type = screen === "start_sso" ? "sso" : "cas";
|
||||||
PlatformPeg.get().startSingleSignOn(cli, type, this.getFragmentAfterLogin());
|
PlatformPeg.get()?.startSingleSignOn(cli, type, this.getFragmentAfterLogin());
|
||||||
} else if (screen.indexOf("room/") === 0) {
|
} else if (screen.indexOf("room/") === 0) {
|
||||||
// Rooms can have the following formats:
|
// Rooms can have the following formats:
|
||||||
// #room_alias:domain or !opaque_id:domain
|
// #room_alias:domain or !opaque_id:domain
|
||||||
|
@ -1786,7 +1790,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||||
eventOffset = domainOffset + room.substring(domainOffset).indexOf("/");
|
eventOffset = domainOffset + room.substring(domainOffset).indexOf("/");
|
||||||
}
|
}
|
||||||
const roomString = room.substring(0, eventOffset);
|
const roomString = room.substring(0, eventOffset);
|
||||||
let eventId = room.substring(eventOffset + 1); // empty string if no event id given
|
let eventId: string | undefined = room.substring(eventOffset + 1); // empty string if no event id given
|
||||||
|
|
||||||
// Previously we pulled the eventID from the segments in such a way
|
// Previously we pulled the eventID from the segments in such a way
|
||||||
// where if there was no eventId then we'd get undefined. However, we
|
// where if there was no eventId then we'd get undefined. However, we
|
||||||
|
@ -1797,9 +1801,9 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||||
|
|
||||||
// TODO: Handle encoded room/event IDs: https://github.com/vector-im/element-web/issues/9149
|
// TODO: Handle encoded room/event IDs: https://github.com/vector-im/element-web/issues/9149
|
||||||
|
|
||||||
let threepidInvite: IThreepidInvite;
|
let threepidInvite: IThreepidInvite | undefined;
|
||||||
// if we landed here from a 3PID invite, persist it
|
// if we landed here from a 3PID invite, persist it
|
||||||
if (params.signurl && params.email) {
|
if (params?.signurl && params?.email) {
|
||||||
threepidInvite = ThreepidInviteStore.instance.storeInvite(
|
threepidInvite = ThreepidInviteStore.instance.storeInvite(
|
||||||
roomString,
|
roomString,
|
||||||
params as IThreepidInviteWireFormat,
|
params as IThreepidInviteWireFormat,
|
||||||
|
@ -1816,8 +1820,8 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||||
// to other levels. If there's just one ?via= then params.via is a
|
// to other levels. If there's just one ?via= then params.via is a
|
||||||
// single string. If someone does something like ?via=one.com&via=two.com
|
// single string. If someone does something like ?via=one.com&via=two.com
|
||||||
// then params.via is an array of strings.
|
// then params.via is an array of strings.
|
||||||
let via = [];
|
let via: string[] = [];
|
||||||
if (params.via) {
|
if (params?.via) {
|
||||||
if (typeof params.via === "string") via = [params.via];
|
if (typeof params.via === "string") via = [params.via];
|
||||||
else via = params.via;
|
else via = params.via;
|
||||||
}
|
}
|
||||||
|
@ -1855,7 +1859,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||||
dis.dispatch({
|
dis.dispatch({
|
||||||
action: "view_user_info",
|
action: "view_user_info",
|
||||||
userId: userId,
|
userId: userId,
|
||||||
subAction: params.action,
|
subAction: params?.action,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
logger.info("Ignoring showScreen for '%s'", screen);
|
logger.info("Ignoring showScreen for '%s'", screen);
|
||||||
|
@ -1949,8 +1953,8 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||||
const numUnreadRooms = notificationState.numUnreadStates; // we know that states === rooms here
|
const numUnreadRooms = notificationState.numUnreadStates; // we know that states === rooms here
|
||||||
|
|
||||||
if (PlatformPeg.get()) {
|
if (PlatformPeg.get()) {
|
||||||
PlatformPeg.get().setErrorStatus(state === SyncState.Error);
|
PlatformPeg.get()!.setErrorStatus(state === SyncState.Error);
|
||||||
PlatformPeg.get().setNotificationCount(numUnreadRooms);
|
PlatformPeg.get()!.setNotificationCount(numUnreadRooms);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.subTitleStatus = "";
|
this.subTitleStatus = "";
|
||||||
|
@ -1971,7 +1975,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
|
||||||
};
|
};
|
||||||
|
|
||||||
private makeRegistrationUrl = (params: QueryDict): string => {
|
private makeRegistrationUrl = (params: QueryDict): string => {
|
||||||
if (this.props.startingFragmentQueryParams.referrer) {
|
if (this.props.startingFragmentQueryParams?.referrer) {
|
||||||
params.referrer = this.props.startingFragmentQueryParams.referrer;
|
params.referrer = this.props.startingFragmentQueryParams.referrer;
|
||||||
}
|
}
|
||||||
return this.props.makeRegistrationUrl(params);
|
return this.props.makeRegistrationUrl(params);
|
||||||
|
|
|
@ -163,7 +163,7 @@ interface IProps {
|
||||||
stickyBottom?: boolean;
|
stickyBottom?: boolean;
|
||||||
|
|
||||||
// className for the panel
|
// className for the panel
|
||||||
className: string;
|
className?: string;
|
||||||
|
|
||||||
// show twelve hour timestamps
|
// show twelve hour timestamps
|
||||||
isTwelveHour?: boolean;
|
isTwelveHour?: boolean;
|
||||||
|
@ -177,7 +177,7 @@ interface IProps {
|
||||||
// which layout to use
|
// which layout to use
|
||||||
layout?: Layout;
|
layout?: Layout;
|
||||||
|
|
||||||
resizeNotifier: ResizeNotifier;
|
resizeNotifier?: ResizeNotifier;
|
||||||
permalinkCreator?: RoomPermalinkCreator;
|
permalinkCreator?: RoomPermalinkCreator;
|
||||||
editState?: EditorStateTransfer;
|
editState?: EditorStateTransfer;
|
||||||
|
|
||||||
|
@ -345,12 +345,12 @@ export default class MessagePanel extends React.Component<IProps, IState> {
|
||||||
};
|
};
|
||||||
|
|
||||||
/* get the DOM node representing the given event */
|
/* get the DOM node representing the given event */
|
||||||
public getNodeForEventId(eventId: string): HTMLElement {
|
public getNodeForEventId(eventId: string): HTMLElement | undefined {
|
||||||
if (!this.eventTiles) {
|
if (!this.eventTiles) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.eventTiles[eventId]?.ref?.current;
|
return this.eventTiles[eventId]?.ref?.current ?? undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
public getTileForEventId(eventId?: string): UnwrappedEventTile | undefined {
|
public getTileForEventId(eventId?: string): UnwrappedEventTile | undefined {
|
||||||
|
@ -362,7 +362,7 @@ export default class MessagePanel extends React.Component<IProps, IState> {
|
||||||
|
|
||||||
/* return true if the content is fully scrolled down right now; else false.
|
/* return true if the content is fully scrolled down right now; else false.
|
||||||
*/
|
*/
|
||||||
public isAtBottom(): boolean {
|
public isAtBottom(): boolean | undefined {
|
||||||
return this.scrollPanel.current?.isAtBottom();
|
return this.scrollPanel.current?.isAtBottom();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -371,7 +371,7 @@ export default class MessagePanel extends React.Component<IProps, IState> {
|
||||||
*
|
*
|
||||||
* returns null if we are not mounted.
|
* returns null if we are not mounted.
|
||||||
*/
|
*/
|
||||||
public getScrollState(): IScrollState {
|
public getScrollState(): IScrollState | null {
|
||||||
return this.scrollPanel.current?.getScrollState() ?? null;
|
return this.scrollPanel.current?.getScrollState() ?? null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -381,7 +381,7 @@ export default class MessagePanel extends React.Component<IProps, IState> {
|
||||||
// -1: read marker is above the window
|
// -1: read marker is above the window
|
||||||
// 0: read marker is within the window
|
// 0: read marker is within the window
|
||||||
// +1: read marker is below the window
|
// +1: read marker is below the window
|
||||||
public getReadMarkerPosition(): number {
|
public getReadMarkerPosition(): number | null {
|
||||||
const readMarker = this.readMarkerNode.current;
|
const readMarker = this.readMarkerNode.current;
|
||||||
const messageWrapper = this.scrollPanel.current;
|
const messageWrapper = this.scrollPanel.current;
|
||||||
|
|
||||||
|
@ -633,9 +633,8 @@ export default class MessagePanel extends React.Component<IProps, IState> {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ret = [];
|
const ret: ReactNode[] = [];
|
||||||
|
let prevEvent: MatrixEvent | null = null; // the last event we showed
|
||||||
let prevEvent = null; // the last event we showed
|
|
||||||
|
|
||||||
// Note: the EventTile might still render a "sent/sending receipt" independent of
|
// Note: the EventTile might still render a "sent/sending receipt" independent of
|
||||||
// this information. When not providing read receipt information, the tile is likely
|
// this information. When not providing read receipt information, the tile is likely
|
||||||
|
@ -645,7 +644,7 @@ export default class MessagePanel extends React.Component<IProps, IState> {
|
||||||
this.readReceiptsByEvent = this.getReadReceiptsByShownEvent();
|
this.readReceiptsByEvent = this.getReadReceiptsByShownEvent();
|
||||||
}
|
}
|
||||||
|
|
||||||
let grouper: BaseGrouper = null;
|
let grouper: BaseGrouper | null = null;
|
||||||
|
|
||||||
for (let i = 0; i < events.length; i++) {
|
for (let i = 0; i < events.length; i++) {
|
||||||
const { event: mxEv, shouldShow } = events[i];
|
const { event: mxEv, shouldShow } = events[i];
|
||||||
|
@ -695,14 +694,14 @@ export default class MessagePanel extends React.Component<IProps, IState> {
|
||||||
}
|
}
|
||||||
|
|
||||||
public getTilesForEvent(
|
public getTilesForEvent(
|
||||||
prevEvent: MatrixEvent,
|
prevEvent: MatrixEvent | null,
|
||||||
mxEv: MatrixEvent,
|
mxEv: MatrixEvent,
|
||||||
last = false,
|
last = false,
|
||||||
isGrouped = false,
|
isGrouped = false,
|
||||||
nextEvent?: MatrixEvent,
|
nextEvent?: MatrixEvent,
|
||||||
nextEventWithTile?: MatrixEvent,
|
nextEventWithTile?: MatrixEvent,
|
||||||
): ReactNode[] {
|
): ReactNode[] {
|
||||||
const ret = [];
|
const ret: ReactNode[] = [];
|
||||||
|
|
||||||
const isEditing = this.props.editState?.getEvent().getId() === mxEv.getId();
|
const isEditing = this.props.editState?.getEvent().getId() === mxEv.getId();
|
||||||
// local echoes have a fake date, which could even be yesterday. Treat them as 'today' for the date separators.
|
// local echoes have a fake date, which could even be yesterday. Treat them as 'today' for the date separators.
|
||||||
|
@ -806,7 +805,7 @@ export default class MessagePanel extends React.Component<IProps, IState> {
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
public wantsDateSeparator(prevEvent: MatrixEvent, nextEventDate: Date): boolean {
|
public wantsDateSeparator(prevEvent: MatrixEvent | null, nextEventDate: Date): boolean {
|
||||||
if (this.context.timelineRenderingType === TimelineRenderingType.ThreadsList) {
|
if (this.context.timelineRenderingType === TimelineRenderingType.ThreadsList) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -820,7 +819,7 @@ export default class MessagePanel extends React.Component<IProps, IState> {
|
||||||
|
|
||||||
// Get a list of read receipts that should be shown next to this event
|
// Get a list of read receipts that should be shown next to this event
|
||||||
// Receipts are objects which have a 'userId', 'roomMember' and 'ts'.
|
// Receipts are objects which have a 'userId', 'roomMember' and 'ts'.
|
||||||
private getReadReceiptsForEvent(event: MatrixEvent): IReadReceiptProps[] {
|
private getReadReceiptsForEvent(event: MatrixEvent): IReadReceiptProps[] | null {
|
||||||
const myUserId = MatrixClientPeg.get().credentials.userId;
|
const myUserId = MatrixClientPeg.get().credentials.userId;
|
||||||
|
|
||||||
// get list of read receipts, sorted most recent first
|
// get list of read receipts, sorted most recent first
|
||||||
|
@ -939,7 +938,7 @@ export default class MessagePanel extends React.Component<IProps, IState> {
|
||||||
private onTypingShown = (): void => {
|
private onTypingShown = (): void => {
|
||||||
const scrollPanel = this.scrollPanel.current;
|
const scrollPanel = this.scrollPanel.current;
|
||||||
// this will make the timeline grow, so checkScroll
|
// this will make the timeline grow, so checkScroll
|
||||||
scrollPanel.checkScroll();
|
scrollPanel?.checkScroll();
|
||||||
if (scrollPanel && scrollPanel.getScrollState().stuckAtBottom) {
|
if (scrollPanel && scrollPanel.getScrollState().stuckAtBottom) {
|
||||||
scrollPanel.preventShrinking();
|
scrollPanel.preventShrinking();
|
||||||
}
|
}
|
||||||
|
@ -1018,7 +1017,7 @@ export default class MessagePanel extends React.Component<IProps, IState> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let ircResizer = null;
|
let ircResizer: JSX.Element | undefined;
|
||||||
if (this.props.layout == Layout.IRC) {
|
if (this.props.layout == Layout.IRC) {
|
||||||
ircResizer = (
|
ircResizer = (
|
||||||
<IRCTimelineProfileResizer
|
<IRCTimelineProfileResizer
|
||||||
|
@ -1076,7 +1075,7 @@ abstract class BaseGrouper {
|
||||||
public constructor(
|
public constructor(
|
||||||
public readonly panel: MessagePanel,
|
public readonly panel: MessagePanel,
|
||||||
public readonly event: MatrixEvent,
|
public readonly event: MatrixEvent,
|
||||||
public readonly prevEvent: MatrixEvent,
|
public readonly prevEvent: MatrixEvent | null,
|
||||||
public readonly lastShownEvent: MatrixEvent,
|
public readonly lastShownEvent: MatrixEvent,
|
||||||
public readonly nextEvent?: MatrixEvent,
|
public readonly nextEvent?: MatrixEvent,
|
||||||
public readonly nextEventTile?: MatrixEvent,
|
public readonly nextEventTile?: MatrixEvent,
|
||||||
|
@ -1261,7 +1260,7 @@ class MainGrouper extends BaseGrouper {
|
||||||
public constructor(
|
public constructor(
|
||||||
public readonly panel: MessagePanel,
|
public readonly panel: MessagePanel,
|
||||||
public readonly event: MatrixEvent,
|
public readonly event: MatrixEvent,
|
||||||
public readonly prevEvent: MatrixEvent,
|
public readonly prevEvent: MatrixEvent | null,
|
||||||
public readonly lastShownEvent: MatrixEvent,
|
public readonly lastShownEvent: MatrixEvent,
|
||||||
nextEvent: MatrixEvent,
|
nextEvent: MatrixEvent,
|
||||||
nextEventTile: MatrixEvent,
|
nextEventTile: MatrixEvent,
|
||||||
|
@ -1341,7 +1340,7 @@ class MainGrouper extends BaseGrouper {
|
||||||
}
|
}
|
||||||
|
|
||||||
let highlightInSummary = false;
|
let highlightInSummary = false;
|
||||||
let eventTiles = this.events
|
let eventTiles: ReactNode[] | null = this.events
|
||||||
.map((e, i) => {
|
.map((e, i) => {
|
||||||
if (e.getId() === panel.props.highlightedEventId) {
|
if (e.getId() === panel.props.highlightedEventId) {
|
||||||
highlightInSummary = true;
|
highlightInSummary = true;
|
||||||
|
|
|
@ -149,7 +149,7 @@ interface IProps {
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export interface IScrollState {
|
export interface IScrollState {
|
||||||
stuckAtBottom: boolean;
|
stuckAtBottom?: boolean;
|
||||||
trackedNode?: HTMLElement;
|
trackedNode?: HTMLElement;
|
||||||
trackedScrollToken?: string;
|
trackedScrollToken?: string;
|
||||||
bottomOffset?: number;
|
bottomOffset?: number;
|
||||||
|
@ -173,7 +173,7 @@ export default class ScrollPanel extends React.Component<IProps> {
|
||||||
onScroll: function () {},
|
onScroll: function () {},
|
||||||
};
|
};
|
||||||
|
|
||||||
private readonly pendingFillRequests: Record<"b" | "f", boolean> = {
|
private readonly pendingFillRequests: Record<"b" | "f", boolean | null> = {
|
||||||
b: null,
|
b: null,
|
||||||
f: null,
|
f: null,
|
||||||
};
|
};
|
||||||
|
@ -190,7 +190,7 @@ export default class ScrollPanel extends React.Component<IProps> {
|
||||||
private pendingFillDueToPropsUpdate: boolean;
|
private pendingFillDueToPropsUpdate: boolean;
|
||||||
private scrollState: IScrollState;
|
private scrollState: IScrollState;
|
||||||
private preventShrinkingState: IPreventShrinkingState;
|
private preventShrinkingState: IPreventShrinkingState;
|
||||||
private unfillDebouncer: number;
|
private unfillDebouncer: number | null;
|
||||||
private bottomGrowth: number;
|
private bottomGrowth: number;
|
||||||
private minListHeight: number;
|
private minListHeight: number;
|
||||||
private heightUpdateInProgress: boolean;
|
private heightUpdateInProgress: boolean;
|
||||||
|
|
|
@ -24,6 +24,7 @@ import { _t } from "../../languageHandler";
|
||||||
import AutoHideScrollbar from "./AutoHideScrollbar";
|
import AutoHideScrollbar from "./AutoHideScrollbar";
|
||||||
import AccessibleButton from "../views/elements/AccessibleButton";
|
import AccessibleButton from "../views/elements/AccessibleButton";
|
||||||
import { PosthogScreenTracker, ScreenName } from "../../PosthogTrackers";
|
import { PosthogScreenTracker, ScreenName } from "../../PosthogTrackers";
|
||||||
|
import { NonEmptyArray } from "../../@types/common";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents a tab for the TabbedView.
|
* Represents a tab for the TabbedView.
|
||||||
|
@ -40,7 +41,7 @@ export class Tab {
|
||||||
public constructor(
|
public constructor(
|
||||||
public readonly id: string,
|
public readonly id: string,
|
||||||
public readonly label: string,
|
public readonly label: string,
|
||||||
public readonly icon: string,
|
public readonly icon: string | null,
|
||||||
public readonly body: React.ReactNode,
|
public readonly body: React.ReactNode,
|
||||||
public readonly screenName?: ScreenName,
|
public readonly screenName?: ScreenName,
|
||||||
) {}
|
) {}
|
||||||
|
@ -52,7 +53,7 @@ export enum TabLocation {
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
tabs: Tab[];
|
tabs: NonEmptyArray<Tab>;
|
||||||
initialTabId?: string;
|
initialTabId?: string;
|
||||||
tabLocation: TabLocation;
|
tabLocation: TabLocation;
|
||||||
onChange?: (tabId: string) => void;
|
onChange?: (tabId: string) => void;
|
||||||
|
@ -69,7 +70,7 @@ export default class TabbedView extends React.Component<IProps, IState> {
|
||||||
|
|
||||||
const initialTabIdIsValid = props.tabs.find((tab) => tab.id === props.initialTabId);
|
const initialTabIdIsValid = props.tabs.find((tab) => tab.id === props.initialTabId);
|
||||||
this.state = {
|
this.state = {
|
||||||
activeTabId: initialTabIdIsValid ? props.initialTabId : props.tabs[0]?.id,
|
activeTabId: initialTabIdIsValid ? props.initialTabId! : props.tabs[0].id,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -101,7 +102,7 @@ export default class TabbedView extends React.Component<IProps, IState> {
|
||||||
|
|
||||||
if (this.state.activeTabId === tab.id) classes += "mx_TabbedView_tabLabel_active";
|
if (this.state.activeTabId === tab.id) classes += "mx_TabbedView_tabLabel_active";
|
||||||
|
|
||||||
let tabIcon = null;
|
let tabIcon: JSX.Element | undefined;
|
||||||
if (tab.icon) {
|
if (tab.icon) {
|
||||||
tabIcon = <span className={`mx_TabbedView_maskedIcon ${tab.icon}`} />;
|
tabIcon = <span className={`mx_TabbedView_maskedIcon ${tab.icon}`} />;
|
||||||
}
|
}
|
||||||
|
@ -141,9 +142,11 @@ export default class TabbedView extends React.Component<IProps, IState> {
|
||||||
mx_TabbedView_tabsOnTop: this.props.tabLocation == TabLocation.TOP,
|
mx_TabbedView_tabsOnTop: this.props.tabLocation == TabLocation.TOP,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const screenName = tab?.screenName ?? this.props.screenName;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={tabbedViewClasses}>
|
<div className={tabbedViewClasses}>
|
||||||
<PosthogScreenTracker screenName={tab?.screenName ?? this.props.screenName} />
|
{screenName && <PosthogScreenTracker screenName={screenName} />}
|
||||||
<div className="mx_TabbedView_tabLabels">{labels}</div>
|
<div className="mx_TabbedView_tabLabels">{labels}</div>
|
||||||
{panel}
|
{panel}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -200,7 +200,7 @@ interface IState {
|
||||||
forwardPaginating: boolean;
|
forwardPaginating: boolean;
|
||||||
|
|
||||||
// cache of matrixClient.getSyncState() (but from the 'sync' event)
|
// cache of matrixClient.getSyncState() (but from the 'sync' event)
|
||||||
clientSyncState: SyncState;
|
clientSyncState: SyncState | null;
|
||||||
|
|
||||||
// should the event tiles have twelve hour times
|
// should the event tiles have twelve hour times
|
||||||
isTwelveHour: boolean;
|
isTwelveHour: boolean;
|
||||||
|
@ -268,7 +268,7 @@ class TimelinePanel extends React.Component<IProps, IState> {
|
||||||
// but for now we just do it per room for simplicity.
|
// but for now we just do it per room for simplicity.
|
||||||
let initialReadMarker: string | null = null;
|
let initialReadMarker: string | null = null;
|
||||||
if (this.props.manageReadMarkers) {
|
if (this.props.manageReadMarkers) {
|
||||||
const readmarker = this.props.timelineSet.room.getAccountData("m.fully_read");
|
const readmarker = this.props.timelineSet.room?.getAccountData("m.fully_read");
|
||||||
if (readmarker) {
|
if (readmarker) {
|
||||||
initialReadMarker = readmarker.getContent().event_id;
|
initialReadMarker = readmarker.getContent().event_id;
|
||||||
} else {
|
} else {
|
||||||
|
@ -414,7 +414,7 @@ class TimelinePanel extends React.Component<IProps, IState> {
|
||||||
// Get the list of actually rendered events seen in the DOM.
|
// Get the list of actually rendered events seen in the DOM.
|
||||||
// This is useful to know for sure what's being shown on screen.
|
// This is useful to know for sure what's being shown on screen.
|
||||||
// And we can suss out any corrupted React `key` problems.
|
// And we can suss out any corrupted React `key` problems.
|
||||||
let renderedEventIds: string[];
|
let renderedEventIds: string[] | undefined;
|
||||||
try {
|
try {
|
||||||
const messagePanel = this.messagePanel.current;
|
const messagePanel = this.messagePanel.current;
|
||||||
if (messagePanel) {
|
if (messagePanel) {
|
||||||
|
@ -422,7 +422,7 @@ class TimelinePanel extends React.Component<IProps, IState> {
|
||||||
if (messagePanelNode) {
|
if (messagePanelNode) {
|
||||||
const actuallyRenderedEvents = messagePanelNode.querySelectorAll("[data-event-id]");
|
const actuallyRenderedEvents = messagePanelNode.querySelectorAll("[data-event-id]");
|
||||||
renderedEventIds = [...actuallyRenderedEvents].map((renderedEvent) => {
|
renderedEventIds = [...actuallyRenderedEvents].map((renderedEvent) => {
|
||||||
return renderedEvent.getAttribute("data-event-id");
|
return renderedEvent.getAttribute("data-event-id")!;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -432,8 +432,8 @@ class TimelinePanel extends React.Component<IProps, IState> {
|
||||||
|
|
||||||
// Get the list of events and threads for the room as seen by the
|
// Get the list of events and threads for the room as seen by the
|
||||||
// matrix-js-sdk.
|
// matrix-js-sdk.
|
||||||
let serializedEventIdsFromTimelineSets: { [key: string]: string[] }[];
|
let serializedEventIdsFromTimelineSets: { [key: string]: string[] }[] | undefined;
|
||||||
let serializedEventIdsFromThreadsTimelineSets: { [key: string]: string[] }[];
|
let serializedEventIdsFromThreadsTimelineSets: { [key: string]: string[] }[] | undefined;
|
||||||
const serializedThreadsMap: { [key: string]: any } = {};
|
const serializedThreadsMap: { [key: string]: any } = {};
|
||||||
if (room) {
|
if (room) {
|
||||||
const timelineSets = room.getTimelineSets();
|
const timelineSets = room.getTimelineSets();
|
||||||
|
@ -469,15 +469,15 @@ class TimelinePanel extends React.Component<IProps, IState> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let timelineWindowEventIds: string[];
|
let timelineWindowEventIds: string[] | undefined;
|
||||||
try {
|
try {
|
||||||
timelineWindowEventIds = this.timelineWindow.getEvents().map((ev) => ev.getId());
|
timelineWindowEventIds = this.timelineWindow?.getEvents().map((ev) => ev.getId()!);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
logger.error(`onDumpDebugLogs: Failed to get event IDs from the timelineWindow`, err);
|
logger.error(`onDumpDebugLogs: Failed to get event IDs from the timelineWindow`, err);
|
||||||
}
|
}
|
||||||
let pendingEventIds: string[];
|
let pendingEventIds: string[] | undefined;
|
||||||
try {
|
try {
|
||||||
pendingEventIds = this.props.timelineSet.getPendingEvents().map((ev) => ev.getId());
|
pendingEventIds = this.props.timelineSet.getPendingEvents().map((ev) => ev.getId()!);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
logger.error(`onDumpDebugLogs: Failed to get pending event IDs`, err);
|
logger.error(`onDumpDebugLogs: Failed to get pending event IDs`, err);
|
||||||
}
|
}
|
||||||
|
@ -491,10 +491,10 @@ class TimelinePanel extends React.Component<IProps, IState> {
|
||||||
`\tserializedEventIdsFromThreadsTimelineSets=` +
|
`\tserializedEventIdsFromThreadsTimelineSets=` +
|
||||||
`${JSON.stringify(serializedEventIdsFromThreadsTimelineSets)}\n` +
|
`${JSON.stringify(serializedEventIdsFromThreadsTimelineSets)}\n` +
|
||||||
`\tserializedThreadsMap=${JSON.stringify(serializedThreadsMap)}\n` +
|
`\tserializedThreadsMap=${JSON.stringify(serializedThreadsMap)}\n` +
|
||||||
`\ttimelineWindowEventIds(${timelineWindowEventIds.length})=${JSON.stringify(
|
`\ttimelineWindowEventIds(${timelineWindowEventIds?.length})=${JSON.stringify(
|
||||||
timelineWindowEventIds,
|
timelineWindowEventIds,
|
||||||
)}\n` +
|
)}\n` +
|
||||||
`\tpendingEventIds(${pendingEventIds.length})=${JSON.stringify(pendingEventIds)}`,
|
`\tpendingEventIds(${pendingEventIds?.length})=${JSON.stringify(pendingEventIds)}`,
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -560,7 +560,7 @@ class TimelinePanel extends React.Component<IProps, IState> {
|
||||||
return Promise.resolve(false);
|
return Promise.resolve(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this.timelineWindow.canPaginate(dir)) {
|
if (!this.timelineWindow?.canPaginate(dir)) {
|
||||||
debuglog("can't", dir, "paginate any further");
|
debuglog("can't", dir, "paginate any further");
|
||||||
this.setState<null>({ [canPaginateKey]: false });
|
this.setState<null>({ [canPaginateKey]: false });
|
||||||
return Promise.resolve(false);
|
return Promise.resolve(false);
|
||||||
|
@ -576,7 +576,7 @@ class TimelinePanel extends React.Component<IProps, IState> {
|
||||||
|
|
||||||
return this.onPaginationRequest(this.timelineWindow, dir, PAGINATE_SIZE).then((r) => {
|
return this.onPaginationRequest(this.timelineWindow, dir, PAGINATE_SIZE).then((r) => {
|
||||||
if (this.unmounted) {
|
if (this.unmounted) {
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
debuglog("paginate complete backwards:" + backwards + "; success:" + r);
|
debuglog("paginate complete backwards:" + backwards + "; success:" + r);
|
||||||
|
@ -595,7 +595,7 @@ class TimelinePanel extends React.Component<IProps, IState> {
|
||||||
// paginate in the other where we previously could not.
|
// paginate in the other where we previously could not.
|
||||||
const otherDirection = backwards ? EventTimeline.FORWARDS : EventTimeline.BACKWARDS;
|
const otherDirection = backwards ? EventTimeline.FORWARDS : EventTimeline.BACKWARDS;
|
||||||
const canPaginateOtherWayKey = backwards ? "canForwardPaginate" : "canBackPaginate";
|
const canPaginateOtherWayKey = backwards ? "canForwardPaginate" : "canBackPaginate";
|
||||||
if (!this.state[canPaginateOtherWayKey] && this.timelineWindow.canPaginate(otherDirection)) {
|
if (!this.state[canPaginateOtherWayKey] && this.timelineWindow?.canPaginate(otherDirection)) {
|
||||||
debuglog("can now", otherDirection, "paginate again");
|
debuglog("can now", otherDirection, "paginate again");
|
||||||
newState[canPaginateOtherWayKey] = true;
|
newState[canPaginateOtherWayKey] = true;
|
||||||
}
|
}
|
||||||
|
@ -666,7 +666,7 @@ class TimelinePanel extends React.Component<IProps, IState> {
|
||||||
|
|
||||||
private onRoomTimeline = (
|
private onRoomTimeline = (
|
||||||
ev: MatrixEvent,
|
ev: MatrixEvent,
|
||||||
room: Room | null,
|
room: Room | undefined,
|
||||||
toStartOfTimeline: boolean,
|
toStartOfTimeline: boolean,
|
||||||
removed: boolean,
|
removed: boolean,
|
||||||
data: IRoomTimelineData,
|
data: IRoomTimelineData,
|
||||||
|
@ -1008,7 +1008,7 @@ class TimelinePanel extends React.Component<IProps, IState> {
|
||||||
if (
|
if (
|
||||||
currentRREventId &&
|
currentRREventId &&
|
||||||
currentRREventIndex === null &&
|
currentRREventIndex === null &&
|
||||||
this.timelineWindow.canPaginate(EventTimeline.FORWARDS)
|
this.timelineWindow?.canPaginate(EventTimeline.FORWARDS)
|
||||||
) {
|
) {
|
||||||
shouldSendRR = false;
|
shouldSendRR = false;
|
||||||
}
|
}
|
||||||
|
@ -1149,7 +1149,7 @@ class TimelinePanel extends React.Component<IProps, IState> {
|
||||||
const events = this.timelineWindow.getEvents();
|
const events = this.timelineWindow.getEvents();
|
||||||
|
|
||||||
// first find where the current RM is
|
// first find where the current RM is
|
||||||
let i;
|
let i: number;
|
||||||
for (i = 0; i < events.length; i++) {
|
for (i = 0; i < events.length; i++) {
|
||||||
if (events[i].getId() == this.state.readMarkerEventId) {
|
if (events[i].getId() == this.state.readMarkerEventId) {
|
||||||
break;
|
break;
|
||||||
|
@ -1182,7 +1182,7 @@ class TimelinePanel extends React.Component<IProps, IState> {
|
||||||
//
|
//
|
||||||
// Otherwise, reload the timeline rather than trying to paginate
|
// Otherwise, reload the timeline rather than trying to paginate
|
||||||
// through all of space-time.
|
// through all of space-time.
|
||||||
if (this.timelineWindow.canPaginate(EventTimeline.FORWARDS)) {
|
if (this.timelineWindow?.canPaginate(EventTimeline.FORWARDS)) {
|
||||||
this.loadTimeline();
|
this.loadTimeline();
|
||||||
} else {
|
} else {
|
||||||
this.messagePanel.current?.scrollToBottom();
|
this.messagePanel.current?.scrollToBottom();
|
||||||
|
@ -1231,7 +1231,7 @@ class TimelinePanel extends React.Component<IProps, IState> {
|
||||||
|
|
||||||
// Look up the timestamp if we can find it
|
// Look up the timestamp if we can find it
|
||||||
const tl = this.props.timelineSet.getTimelineForEvent(rmId ?? "");
|
const tl = this.props.timelineSet.getTimelineForEvent(rmId ?? "");
|
||||||
let rmTs: number;
|
let rmTs: number | undefined;
|
||||||
if (tl) {
|
if (tl) {
|
||||||
const event = tl.getEvents().find((e) => {
|
const event = tl.getEvents().find((e) => {
|
||||||
return e.getId() == rmId;
|
return e.getId() == rmId;
|
||||||
|
@ -1264,7 +1264,7 @@ class TimelinePanel extends React.Component<IProps, IState> {
|
||||||
*
|
*
|
||||||
* returns null if we are not mounted.
|
* returns null if we are not mounted.
|
||||||
*/
|
*/
|
||||||
public getScrollState = (): IScrollState => {
|
public getScrollState = (): IScrollState | null => {
|
||||||
if (!this.messagePanel.current) {
|
if (!this.messagePanel.current) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -1277,7 +1277,7 @@ class TimelinePanel extends React.Component<IProps, IState> {
|
||||||
// -1: read marker is above the window
|
// -1: read marker is above the window
|
||||||
// 0: read marker is visible
|
// 0: read marker is visible
|
||||||
// +1: read marker is below the window
|
// +1: read marker is below the window
|
||||||
public getReadMarkerPosition = (): number => {
|
public getReadMarkerPosition = (): number | null => {
|
||||||
if (!this.props.manageReadMarkers) return null;
|
if (!this.props.manageReadMarkers) return null;
|
||||||
if (!this.messagePanel.current) return null;
|
if (!this.messagePanel.current) return null;
|
||||||
|
|
||||||
|
@ -1449,7 +1449,7 @@ class TimelinePanel extends React.Component<IProps, IState> {
|
||||||
this.setState({ timelineLoading: false });
|
this.setState({ timelineLoading: false });
|
||||||
logger.error(`Error loading timeline panel at ${this.props.timelineSet.room?.roomId}/${eventId}`, error);
|
logger.error(`Error loading timeline panel at ${this.props.timelineSet.room?.roomId}/${eventId}`, error);
|
||||||
|
|
||||||
let onFinished: () => void;
|
let onFinished: (() => void) | undefined;
|
||||||
|
|
||||||
// if we were given an event ID, then when the user closes the
|
// if we were given an event ID, then when the user closes the
|
||||||
// dialog, let's jump to the end of the timeline. If we weren't,
|
// dialog, let's jump to the end of the timeline. If we weren't,
|
||||||
|
@ -1745,7 +1745,7 @@ class TimelinePanel extends React.Component<IProps, IState> {
|
||||||
const wrapperRect = messagePanelNode.getBoundingClientRect();
|
const wrapperRect = messagePanelNode.getBoundingClientRect();
|
||||||
const myUserId = MatrixClientPeg.get().credentials.userId;
|
const myUserId = MatrixClientPeg.get().credentials.userId;
|
||||||
|
|
||||||
const isNodeInView = (node: HTMLElement): boolean => {
|
const isNodeInView = (node?: HTMLElement): boolean => {
|
||||||
if (node) {
|
if (node) {
|
||||||
const boundingRect = node.getBoundingClientRect();
|
const boundingRect = node.getBoundingClientRect();
|
||||||
if (
|
if (
|
||||||
|
@ -1828,7 +1828,7 @@ class TimelinePanel extends React.Component<IProps, IState> {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const myUserId = client.credentials.userId;
|
const myUserId = client.getSafeUserId();
|
||||||
const receiptStore: ReadReceipt<any, any> = this.props.timelineSet.thread ?? this.props.timelineSet.room;
|
const receiptStore: ReadReceipt<any, any> = this.props.timelineSet.thread ?? this.props.timelineSet.room;
|
||||||
return receiptStore?.getEventReadUpTo(myUserId, ignoreSynthesized);
|
return receiptStore?.getEventReadUpTo(myUserId, ignoreSynthesized);
|
||||||
}
|
}
|
||||||
|
@ -1943,7 +1943,7 @@ class TimelinePanel extends React.Component<IProps, IState> {
|
||||||
canBackPaginate={this.state.canBackPaginate && this.state.firstVisibleEventIndex === 0}
|
canBackPaginate={this.state.canBackPaginate && this.state.firstVisibleEventIndex === 0}
|
||||||
showUrlPreview={this.props.showUrlPreview}
|
showUrlPreview={this.props.showUrlPreview}
|
||||||
showReadReceipts={this.props.showReadReceipts}
|
showReadReceipts={this.props.showReadReceipts}
|
||||||
ourUserId={MatrixClientPeg.get().credentials.userId}
|
ourUserId={MatrixClientPeg.get().getSafeUserId()}
|
||||||
stickyBottom={stickyBottom}
|
stickyBottom={stickyBottom}
|
||||||
onScroll={this.onMessageListScroll}
|
onScroll={this.onMessageListScroll}
|
||||||
onFillRequest={this.onMessageListFillRequest}
|
onFillRequest={this.onMessageListFillRequest}
|
||||||
|
|
|
@ -145,7 +145,7 @@ export default class UserMenu extends React.Component<IProps, IState> {
|
||||||
} else {
|
} else {
|
||||||
const theme = SettingsStore.getValue("theme");
|
const theme = SettingsStore.getValue("theme");
|
||||||
if (theme.startsWith("custom-")) {
|
if (theme.startsWith("custom-")) {
|
||||||
return getCustomTheme(theme.substring("custom-".length)).is_dark;
|
return !!getCustomTheme(theme.substring("custom-".length)).is_dark;
|
||||||
}
|
}
|
||||||
return theme === "dark";
|
return theme === "dark";
|
||||||
}
|
}
|
||||||
|
@ -236,7 +236,7 @@ export default class UserMenu extends React.Component<IProps, IState> {
|
||||||
SettingsStore.setValue("theme", null, SettingLevel.DEVICE, newTheme); // set at same level as Appearance tab
|
SettingsStore.setValue("theme", null, SettingLevel.DEVICE, newTheme); // set at same level as Appearance tab
|
||||||
};
|
};
|
||||||
|
|
||||||
private onSettingsOpen = (ev: ButtonEvent, tabId: string): void => {
|
private onSettingsOpen = (ev: ButtonEvent, tabId?: string): void => {
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
ev.stopPropagation();
|
ev.stopPropagation();
|
||||||
|
|
||||||
|
@ -319,7 +319,7 @@ export default class UserMenu extends React.Component<IProps, IState> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let homeButton = null;
|
let homeButton: JSX.Element | undefined;
|
||||||
if (this.hasHomePage) {
|
if (this.hasHomePage) {
|
||||||
homeButton = (
|
homeButton = (
|
||||||
<IconizedContextMenuOption
|
<IconizedContextMenuOption
|
||||||
|
@ -330,7 +330,7 @@ export default class UserMenu extends React.Component<IProps, IState> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let feedbackButton;
|
let feedbackButton: JSX.Element | undefined;
|
||||||
if (SettingsStore.getValue(UIFeature.Feedback)) {
|
if (SettingsStore.getValue(UIFeature.Feedback)) {
|
||||||
feedbackButton = (
|
feedbackButton = (
|
||||||
<IconizedContextMenuOption
|
<IconizedContextMenuOption
|
||||||
|
@ -357,7 +357,7 @@ export default class UserMenu extends React.Component<IProps, IState> {
|
||||||
<IconizedContextMenuOption
|
<IconizedContextMenuOption
|
||||||
iconClassName="mx_UserMenu_iconSettings"
|
iconClassName="mx_UserMenu_iconSettings"
|
||||||
label={_t("All settings")}
|
label={_t("All settings")}
|
||||||
onClick={(e) => this.onSettingsOpen(e, null)}
|
onClick={(e) => this.onSettingsOpen(e)}
|
||||||
/>
|
/>
|
||||||
{feedbackButton}
|
{feedbackButton}
|
||||||
<IconizedContextMenuOption
|
<IconizedContextMenuOption
|
||||||
|
@ -376,7 +376,7 @@ export default class UserMenu extends React.Component<IProps, IState> {
|
||||||
<IconizedContextMenuOption
|
<IconizedContextMenuOption
|
||||||
iconClassName="mx_UserMenu_iconSettings"
|
iconClassName="mx_UserMenu_iconSettings"
|
||||||
label={_t("Settings")}
|
label={_t("Settings")}
|
||||||
onClick={(e) => this.onSettingsOpen(e, null)}
|
onClick={(e) => this.onSettingsOpen(e)}
|
||||||
/>
|
/>
|
||||||
{feedbackButton}
|
{feedbackButton}
|
||||||
</IconizedContextMenuOptionList>
|
</IconizedContextMenuOptionList>
|
||||||
|
@ -395,9 +395,12 @@ export default class UserMenu extends React.Component<IProps, IState> {
|
||||||
{OwnProfileStore.instance.displayName}
|
{OwnProfileStore.instance.displayName}
|
||||||
</span>
|
</span>
|
||||||
<span className="mx_UserMenu_contextMenu_userId">
|
<span className="mx_UserMenu_contextMenu_userId">
|
||||||
{UserIdentifierCustomisations.getDisplayUserIdentifier(MatrixClientPeg.get().getUserId(), {
|
{UserIdentifierCustomisations.getDisplayUserIdentifier(
|
||||||
withDisplayName: true,
|
MatrixClientPeg.get().getSafeUserId(),
|
||||||
})}
|
{
|
||||||
|
withDisplayName: true,
|
||||||
|
},
|
||||||
|
)}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -426,7 +429,7 @@ export default class UserMenu extends React.Component<IProps, IState> {
|
||||||
const displayName = OwnProfileStore.instance.displayName || userId;
|
const displayName = OwnProfileStore.instance.displayName || userId;
|
||||||
const avatarUrl = OwnProfileStore.instance.getHttpAvatarUrl(avatarSize);
|
const avatarUrl = OwnProfileStore.instance.getHttpAvatarUrl(avatarSize);
|
||||||
|
|
||||||
let name: JSX.Element;
|
let name: JSX.Element | undefined;
|
||||||
if (!this.props.isPanelCollapsed) {
|
if (!this.props.isPanelCollapsed) {
|
||||||
name = <div className="mx_UserMenu_name">{displayName}</div>;
|
name = <div className="mx_UserMenu_name">{displayName}</div>;
|
||||||
}
|
}
|
||||||
|
|
|
@ -68,7 +68,7 @@ export default class ViewSource extends React.Component<IProps, IState> {
|
||||||
};
|
};
|
||||||
if (isEncrypted) {
|
if (isEncrypted) {
|
||||||
const copyDecryptedFunc = (): string => {
|
const copyDecryptedFunc = (): string => {
|
||||||
return stringify(decryptedEventSource);
|
return stringify(decryptedEventSource || {});
|
||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
@ -117,7 +117,7 @@ export default class ViewSource extends React.Component<IProps, IState> {
|
||||||
return (
|
return (
|
||||||
<MatrixClientContext.Consumer>
|
<MatrixClientContext.Consumer>
|
||||||
{(cli) => (
|
{(cli) => (
|
||||||
<DevtoolsContext.Provider value={{ room: cli.getRoom(roomId) }}>
|
<DevtoolsContext.Provider value={{ room: cli.getRoom(roomId)! }}>
|
||||||
<StateEventEditor onBack={this.onBack} mxEvent={mxEvent} />
|
<StateEventEditor onBack={this.onBack} mxEvent={mxEvent} />
|
||||||
</DevtoolsContext.Provider>
|
</DevtoolsContext.Provider>
|
||||||
)}
|
)}
|
||||||
|
@ -128,7 +128,7 @@ export default class ViewSource extends React.Component<IProps, IState> {
|
||||||
return (
|
return (
|
||||||
<MatrixClientContext.Consumer>
|
<MatrixClientContext.Consumer>
|
||||||
{(cli) => (
|
{(cli) => (
|
||||||
<DevtoolsContext.Provider value={{ room: cli.getRoom(roomId) }}>
|
<DevtoolsContext.Provider value={{ room: cli.getRoom(roomId)! }}>
|
||||||
<TimelineEventEditor onBack={this.onBack} mxEvent={mxEvent} />
|
<TimelineEventEditor onBack={this.onBack} mxEvent={mxEvent} />
|
||||||
</DevtoolsContext.Provider>
|
</DevtoolsContext.Provider>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -395,7 +395,7 @@ export default class ForgotPassword extends React.Component<Props, State> {
|
||||||
button: _t("Continue"),
|
button: _t("Continue"),
|
||||||
});
|
});
|
||||||
const [confirmed] = await finished;
|
const [confirmed] = await finished;
|
||||||
return confirmed;
|
return !!confirmed;
|
||||||
}
|
}
|
||||||
|
|
||||||
public renderCheckEmail(): JSX.Element {
|
public renderCheckEmail(): JSX.Element {
|
||||||
|
|
|
@ -368,7 +368,7 @@ export default class LoginComponent extends React.PureComponent<IProps, IState>
|
||||||
isDefaultServer = true;
|
isDefaultServer = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
const fallbackHsUrl = isDefaultServer ? this.props.fallbackHsUrl : null;
|
const fallbackHsUrl = isDefaultServer ? this.props.fallbackHsUrl! : null;
|
||||||
|
|
||||||
const loginLogic = new Login(hsUrl, isUrl, fallbackHsUrl, {
|
const loginLogic = new Login(hsUrl, isUrl, fallbackHsUrl, {
|
||||||
defaultDeviceDisplayName: this.props.defaultDeviceDisplayName,
|
defaultDeviceDisplayName: this.props.defaultDeviceDisplayName,
|
||||||
|
@ -514,7 +514,7 @@ export default class LoginComponent extends React.PureComponent<IProps, IState>
|
||||||
// this is the ideal order we want to show the flows in
|
// this is the ideal order we want to show the flows in
|
||||||
const order = ["m.login.password", "m.login.sso"];
|
const order = ["m.login.password", "m.login.sso"];
|
||||||
|
|
||||||
const flows = filterBoolean(order.map((type) => this.state.flows.find((flow) => flow.type === type)));
|
const flows = filterBoolean(order.map((type) => this.state.flows?.find((flow) => flow.type === type)));
|
||||||
return (
|
return (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
{flows.map((flow) => {
|
{flows.map((flow) => {
|
||||||
|
@ -546,7 +546,7 @@ export default class LoginComponent extends React.PureComponent<IProps, IState>
|
||||||
};
|
};
|
||||||
|
|
||||||
private renderSsoStep = (loginType: "cas" | "sso"): JSX.Element => {
|
private renderSsoStep = (loginType: "cas" | "sso"): JSX.Element => {
|
||||||
const flow = this.state.flows.find((flow) => flow.type === "m.login." + loginType) as ISSOFlow;
|
const flow = this.state.flows?.find((flow) => flow.type === "m.login." + loginType) as ISSOFlow;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SSOButtons
|
<SSOButtons
|
||||||
|
@ -554,7 +554,7 @@ export default class LoginComponent extends React.PureComponent<IProps, IState>
|
||||||
flow={flow}
|
flow={flow}
|
||||||
loginType={loginType}
|
loginType={loginType}
|
||||||
fragmentAfterLogin={this.props.fragmentAfterLogin}
|
fragmentAfterLogin={this.props.fragmentAfterLogin}
|
||||||
primary={!this.state.flows.find((flow) => flow.type === "m.login.password")}
|
primary={!this.state.flows?.find((flow) => flow.type === "m.login.password")}
|
||||||
action={SSOAction.LOGIN}
|
action={SSOAction.LOGIN}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
|
@ -88,7 +88,7 @@ export default class SetupEncryptionBody extends React.Component<IProps, IState>
|
||||||
|
|
||||||
private onVerifyClick = (): void => {
|
private onVerifyClick = (): void => {
|
||||||
const cli = MatrixClientPeg.get();
|
const cli = MatrixClientPeg.get();
|
||||||
const userId = cli.getUserId();
|
const userId = cli.getSafeUserId();
|
||||||
const requestPromise = cli.requestVerification(userId);
|
const requestPromise = cli.requestVerification(userId);
|
||||||
|
|
||||||
// We need to call onFinished now to close this dialog, and
|
// We need to call onFinished now to close this dialog, and
|
||||||
|
@ -212,7 +212,7 @@ export default class SetupEncryptionBody extends React.Component<IProps, IState>
|
||||||
{useRecoveryKeyButton}
|
{useRecoveryKeyButton}
|
||||||
</div>
|
</div>
|
||||||
<div className="mx_SetupEncryptionBody_reset">
|
<div className="mx_SetupEncryptionBody_reset">
|
||||||
{_t("Forgotten or lost all recovery methods? <a>Reset all</a>", null, {
|
{_t("Forgotten or lost all recovery methods? <a>Reset all</a>", undefined, {
|
||||||
a: (sub) => (
|
a: (sub) => (
|
||||||
<AccessibleButton
|
<AccessibleButton
|
||||||
kind="link_inline"
|
kind="link_inline"
|
||||||
|
@ -228,7 +228,7 @@ export default class SetupEncryptionBody extends React.Component<IProps, IState>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else if (phase === Phase.Done) {
|
} else if (phase === Phase.Done) {
|
||||||
let message;
|
let message: JSX.Element;
|
||||||
if (this.state.backupInfo) {
|
if (this.state.backupInfo) {
|
||||||
message = (
|
message = (
|
||||||
<p>
|
<p>
|
||||||
|
|
|
@ -23,7 +23,7 @@ import { _t } from "../../../languageHandler";
|
||||||
import dis from "../../../dispatcher/dispatcher";
|
import dis from "../../../dispatcher/dispatcher";
|
||||||
import * as Lifecycle from "../../../Lifecycle";
|
import * as Lifecycle from "../../../Lifecycle";
|
||||||
import Modal from "../../../Modal";
|
import Modal from "../../../Modal";
|
||||||
import { MatrixClientPeg } from "../../../MatrixClientPeg";
|
import { IMatrixClientCreds, MatrixClientPeg } from "../../../MatrixClientPeg";
|
||||||
import { sendLoginRequest } from "../../../Login";
|
import { sendLoginRequest } from "../../../Login";
|
||||||
import AuthPage from "../../views/auth/AuthPage";
|
import AuthPage from "../../views/auth/AuthPage";
|
||||||
import { SSO_HOMESERVER_URL_KEY, SSO_ID_SERVER_URL_KEY } from "../../../BasePlatform";
|
import { SSO_HOMESERVER_URL_KEY, SSO_ID_SERVER_URL_KEY } from "../../../BasePlatform";
|
||||||
|
@ -159,7 +159,7 @@ export default class SoftLogout extends React.Component<IProps, IState> {
|
||||||
device_id: MatrixClientPeg.get().getDeviceId(),
|
device_id: MatrixClientPeg.get().getDeviceId(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let credentials = null;
|
let credentials: IMatrixClientCreds;
|
||||||
try {
|
try {
|
||||||
credentials = await sendLoginRequest(hsUrl, isUrl, loginType, loginParams);
|
credentials = await sendLoginRequest(hsUrl, isUrl, loginType, loginParams);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
@ -192,7 +192,7 @@ export default class SoftLogout extends React.Component<IProps, IState> {
|
||||||
device_id: MatrixClientPeg.get().getDeviceId(),
|
device_id: MatrixClientPeg.get().getDeviceId(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let credentials = null;
|
let credentials: IMatrixClientCreds;
|
||||||
try {
|
try {
|
||||||
credentials = await sendLoginRequest(hsUrl, isUrl, loginType, loginParams);
|
credentials = await sendLoginRequest(hsUrl, isUrl, loginType, loginParams);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
@ -212,7 +212,7 @@ export default class SoftLogout extends React.Component<IProps, IState> {
|
||||||
}
|
}
|
||||||
|
|
||||||
private renderPasswordForm(introText: Optional<string>): JSX.Element {
|
private renderPasswordForm(introText: Optional<string>): JSX.Element {
|
||||||
let error: JSX.Element = null;
|
let error: JSX.Element | undefined;
|
||||||
if (this.state.errorText) {
|
if (this.state.errorText) {
|
||||||
error = <span className="mx_Login_error">{this.state.errorText}</span>;
|
error = <span className="mx_Login_error">{this.state.errorText}</span>;
|
||||||
}
|
}
|
||||||
|
@ -267,7 +267,7 @@ export default class SoftLogout extends React.Component<IProps, IState> {
|
||||||
return <Spinner />;
|
return <Spinner />;
|
||||||
}
|
}
|
||||||
|
|
||||||
let introText = null; // null is translated to something area specific in this function
|
let introText: string | null = null; // null is translated to something area specific in this function
|
||||||
if (this.state.keyBackupNeeded) {
|
if (this.state.keyBackupNeeded) {
|
||||||
introText = _t(
|
introText = _t(
|
||||||
"Regain access to your account and recover encryption keys stored in this session. " +
|
"Regain access to your account and recover encryption keys stored in this session. " +
|
||||||
|
|
|
@ -27,7 +27,7 @@ interface Props {
|
||||||
export function AuthHeaderDisplay({ title, icon, serverPicker, children }: PropsWithChildren<Props>): JSX.Element {
|
export function AuthHeaderDisplay({ title, icon, serverPicker, children }: PropsWithChildren<Props>): JSX.Element {
|
||||||
const context = useContext(AuthHeaderContext);
|
const context = useContext(AuthHeaderContext);
|
||||||
if (!context) {
|
if (!context) {
|
||||||
return null;
|
return <></>;
|
||||||
}
|
}
|
||||||
const current = context.state.length ? context.state[0] : null;
|
const current = context.state.length ? context.state[0] : null;
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -61,7 +61,7 @@ const RoomContextMenu: React.FC<IProps> = ({ room, onFinished, ...props }) => {
|
||||||
RoomListStore.instance.getTagsForRoom(room),
|
RoomListStore.instance.getTagsForRoom(room),
|
||||||
);
|
);
|
||||||
|
|
||||||
let leaveOption: JSX.Element;
|
let leaveOption: JSX.Element | undefined;
|
||||||
if (roomTags.includes(DefaultTagID.Archived)) {
|
if (roomTags.includes(DefaultTagID.Archived)) {
|
||||||
const onForgetRoomClick = (ev: ButtonEvent): void => {
|
const onForgetRoomClick = (ev: ButtonEvent): void => {
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
|
@ -112,7 +112,7 @@ const RoomContextMenu: React.FC<IProps> = ({ room, onFinished, ...props }) => {
|
||||||
const isVideoRoom =
|
const isVideoRoom =
|
||||||
videoRoomsEnabled && (room.isElementVideoRoom() || (elementCallVideoRoomsEnabled && room.isCallRoom()));
|
videoRoomsEnabled && (room.isElementVideoRoom() || (elementCallVideoRoomsEnabled && room.isCallRoom()));
|
||||||
|
|
||||||
let inviteOption: JSX.Element;
|
let inviteOption: JSX.Element | undefined;
|
||||||
if (room.canInvite(cli.getUserId()!) && !isDm) {
|
if (room.canInvite(cli.getUserId()!) && !isDm) {
|
||||||
const onInviteClick = (ev: ButtonEvent): void => {
|
const onInviteClick = (ev: ButtonEvent): void => {
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
|
@ -136,9 +136,9 @@ const RoomContextMenu: React.FC<IProps> = ({ room, onFinished, ...props }) => {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let favouriteOption: JSX.Element;
|
let favouriteOption: JSX.Element | undefined;
|
||||||
let lowPriorityOption: JSX.Element;
|
let lowPriorityOption: JSX.Element | undefined;
|
||||||
let notificationOption: JSX.Element;
|
let notificationOption: JSX.Element | undefined;
|
||||||
if (room.getMyMembership() === "join") {
|
if (room.getMyMembership() === "join") {
|
||||||
const isFavorite = roomTags.includes(DefaultTagID.Favourite);
|
const isFavorite = roomTags.includes(DefaultTagID.Favourite);
|
||||||
favouriteOption = (
|
favouriteOption = (
|
||||||
|
@ -208,8 +208,8 @@ const RoomContextMenu: React.FC<IProps> = ({ room, onFinished, ...props }) => {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let peopleOption: JSX.Element;
|
let peopleOption: JSX.Element | undefined;
|
||||||
let copyLinkOption: JSX.Element;
|
let copyLinkOption: JSX.Element | undefined;
|
||||||
if (!isDm) {
|
if (!isDm) {
|
||||||
peopleOption = (
|
peopleOption = (
|
||||||
<IconizedContextMenuOption
|
<IconizedContextMenuOption
|
||||||
|
@ -247,7 +247,7 @@ const RoomContextMenu: React.FC<IProps> = ({ room, onFinished, ...props }) => {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let filesOption: JSX.Element;
|
let filesOption: JSX.Element | undefined;
|
||||||
if (!isVideoRoom) {
|
if (!isVideoRoom) {
|
||||||
filesOption = (
|
filesOption = (
|
||||||
<IconizedContextMenuOption
|
<IconizedContextMenuOption
|
||||||
|
@ -266,9 +266,9 @@ const RoomContextMenu: React.FC<IProps> = ({ room, onFinished, ...props }) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const pinningEnabled = useFeatureEnabled("feature_pinning");
|
const pinningEnabled = useFeatureEnabled("feature_pinning");
|
||||||
const pinCount = usePinnedEvents(pinningEnabled && room)?.length;
|
const pinCount = usePinnedEvents(pinningEnabled ? room : undefined)?.length;
|
||||||
|
|
||||||
let pinsOption: JSX.Element;
|
let pinsOption: JSX.Element | undefined;
|
||||||
if (pinningEnabled && !isVideoRoom) {
|
if (pinningEnabled && !isVideoRoom) {
|
||||||
pinsOption = (
|
pinsOption = (
|
||||||
<IconizedContextMenuOption
|
<IconizedContextMenuOption
|
||||||
|
@ -288,7 +288,7 @@ const RoomContextMenu: React.FC<IProps> = ({ room, onFinished, ...props }) => {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let widgetsOption: JSX.Element;
|
let widgetsOption: JSX.Element | undefined;
|
||||||
if (!isVideoRoom) {
|
if (!isVideoRoom) {
|
||||||
widgetsOption = (
|
widgetsOption = (
|
||||||
<IconizedContextMenuOption
|
<IconizedContextMenuOption
|
||||||
|
@ -306,7 +306,7 @@ const RoomContextMenu: React.FC<IProps> = ({ room, onFinished, ...props }) => {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let exportChatOption: JSX.Element;
|
let exportChatOption: JSX.Element | undefined;
|
||||||
if (!isVideoRoom) {
|
if (!isVideoRoom) {
|
||||||
exportChatOption = (
|
exportChatOption = (
|
||||||
<IconizedContextMenuOption
|
<IconizedContextMenuOption
|
||||||
|
|
|
@ -40,6 +40,7 @@ import DecoratedRoomAvatar from "../avatars/DecoratedRoomAvatar";
|
||||||
import QueryMatcher from "../../../autocomplete/QueryMatcher";
|
import QueryMatcher from "../../../autocomplete/QueryMatcher";
|
||||||
import LazyRenderList from "../elements/LazyRenderList";
|
import LazyRenderList from "../elements/LazyRenderList";
|
||||||
import { useSettingValue } from "../../../hooks/useSettings";
|
import { useSettingValue } from "../../../hooks/useSettings";
|
||||||
|
import { filterBoolean } from "../../../utils/arrays";
|
||||||
|
|
||||||
// These values match CSS
|
// These values match CSS
|
||||||
const ROW_HEIGHT = 32 + 12;
|
const ROW_HEIGHT = 32 + 12;
|
||||||
|
@ -56,7 +57,7 @@ interface IProps {
|
||||||
export const Entry: React.FC<{
|
export const Entry: React.FC<{
|
||||||
room: Room;
|
room: Room;
|
||||||
checked: boolean;
|
checked: boolean;
|
||||||
onChange(value: boolean): void;
|
onChange?(value: boolean): void;
|
||||||
}> = ({ room, checked, onChange }) => {
|
}> = ({ room, checked, onChange }) => {
|
||||||
return (
|
return (
|
||||||
<label className="mx_AddExistingToSpace_entry">
|
<label className="mx_AddExistingToSpace_entry">
|
||||||
|
@ -67,7 +68,7 @@ export const Entry: React.FC<{
|
||||||
)}
|
)}
|
||||||
<span className="mx_AddExistingToSpace_entry_name">{room.name}</span>
|
<span className="mx_AddExistingToSpace_entry_name">{room.name}</span>
|
||||||
<StyledCheckbox
|
<StyledCheckbox
|
||||||
onChange={onChange ? (e) => onChange(e.currentTarget.checked) : null}
|
onChange={onChange ? (e) => onChange(e.currentTarget.checked) : undefined}
|
||||||
checked={checked}
|
checked={checked}
|
||||||
disabled={!onChange}
|
disabled={!onChange}
|
||||||
/>
|
/>
|
||||||
|
@ -150,8 +151,8 @@ export const AddExistingToSpace: React.FC<IAddExistingToSpaceProps> = ({
|
||||||
});
|
});
|
||||||
|
|
||||||
const [selectedToAdd, setSelectedToAdd] = useState(new Set<Room>());
|
const [selectedToAdd, setSelectedToAdd] = useState(new Set<Room>());
|
||||||
const [progress, setProgress] = useState<number>(null);
|
const [progress, setProgress] = useState<number | null>(null);
|
||||||
const [error, setError] = useState<Error>(null);
|
const [error, setError] = useState<Error | null>(null);
|
||||||
const [query, setQuery] = useState("");
|
const [query, setQuery] = useState("");
|
||||||
const lcQuery = query.toLowerCase().trim();
|
const lcQuery = query.toLowerCase().trim();
|
||||||
|
|
||||||
|
@ -164,7 +165,7 @@ export const AddExistingToSpace: React.FC<IAddExistingToSpaceProps> = ({
|
||||||
if (lcQuery) {
|
if (lcQuery) {
|
||||||
const matcher = new QueryMatcher<Room>(visibleRooms, {
|
const matcher = new QueryMatcher<Room>(visibleRooms, {
|
||||||
keys: ["name"],
|
keys: ["name"],
|
||||||
funcs: [(r) => [r.getCanonicalAlias(), ...r.getAltAliases()].filter(Boolean)],
|
funcs: [(r) => filterBoolean([r.getCanonicalAlias(), ...r.getAltAliases()])],
|
||||||
shouldMatchWordsOnly: false,
|
shouldMatchWordsOnly: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -172,7 +173,7 @@ export const AddExistingToSpace: React.FC<IAddExistingToSpaceProps> = ({
|
||||||
}
|
}
|
||||||
|
|
||||||
const joinRule = space.getJoinRule();
|
const joinRule = space.getJoinRule();
|
||||||
return sortRooms(rooms).reduce(
|
return sortRooms(rooms).reduce<[spaces: Room[], rooms: Room[], dms: Room[]]>(
|
||||||
(arr, room) => {
|
(arr, room) => {
|
||||||
if (room.isSpaceRoom()) {
|
if (room.isSpaceRoom()) {
|
||||||
if (room !== space && !existingSubspacesSet.has(room)) {
|
if (room !== space && !existingSubspacesSet.has(room)) {
|
||||||
|
@ -289,7 +290,7 @@ export const AddExistingToSpace: React.FC<IAddExistingToSpaceProps> = ({
|
||||||
}
|
}
|
||||||
setSelectedToAdd(new Set(selectedToAdd));
|
setSelectedToAdd(new Set(selectedToAdd));
|
||||||
}
|
}
|
||||||
: null;
|
: undefined;
|
||||||
|
|
||||||
// only count spaces when alone as they're shown on a separate modal all on their own
|
// only count spaces when alone as they're shown on a separate modal all on their own
|
||||||
const numSpaces = spacesRenderer && !dmsRenderer && !roomsRenderer ? spaces.length : 0;
|
const numSpaces = spacesRenderer && !dmsRenderer && !roomsRenderer ? spaces.length : 0;
|
||||||
|
@ -373,7 +374,7 @@ const defaultRendererFactory =
|
||||||
? (checked: boolean) => {
|
? (checked: boolean) => {
|
||||||
onChange(checked, room);
|
onChange(checked, room);
|
||||||
}
|
}
|
||||||
: null
|
: undefined
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
@ -397,7 +398,7 @@ export const SubspaceSelector: React.FC<ISubspaceSelectorProps> = ({ title, spac
|
||||||
return [
|
return [
|
||||||
space,
|
space,
|
||||||
...SpaceStore.instance.getChildSpaces(space.roomId).filter((space) => {
|
...SpaceStore.instance.getChildSpaces(space.roomId).filter((space) => {
|
||||||
return space.currentState.maySendStateEvent(EventType.SpaceChild, space.client.credentials.userId);
|
return space.currentState.maySendStateEvent(EventType.SpaceChild, space.client.getSafeUserId());
|
||||||
}),
|
}),
|
||||||
];
|
];
|
||||||
}, [space]);
|
}, [space]);
|
||||||
|
|
|
@ -153,12 +153,11 @@ export default class BaseDialog extends React.Component<IProps> {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MatrixClientContext.Provider value={this.matrixClient}>
|
<MatrixClientContext.Provider value={this.matrixClient}>
|
||||||
<PosthogScreenTracker screenName={this.props.screenName} />
|
{this.props.screenName && <PosthogScreenTracker screenName={this.props.screenName} />}
|
||||||
<FocusLock
|
<FocusLock
|
||||||
returnFocus={true}
|
returnFocus={true}
|
||||||
lockProps={lockProps}
|
lockProps={lockProps}
|
||||||
className={classNames({
|
className={classNames(this.props.className, {
|
||||||
[this.props.className]: true,
|
|
||||||
mx_Dialog_fixedWidth: this.props.fixedWidth,
|
mx_Dialog_fixedWidth: this.props.fixedWidth,
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
|
|
|
@ -74,6 +74,7 @@ import { InviteKind } from "./InviteDialogTypes";
|
||||||
import Modal from "../../../Modal";
|
import Modal from "../../../Modal";
|
||||||
import dis from "../../../dispatcher/dispatcher";
|
import dis from "../../../dispatcher/dispatcher";
|
||||||
import { privateShouldBeEncrypted } from "../../../utils/rooms";
|
import { privateShouldBeEncrypted } from "../../../utils/rooms";
|
||||||
|
import { NonEmptyArray } from "../../../@types/common";
|
||||||
|
|
||||||
// we have a number of types defined from the Matrix spec which can't reasonably be altered here.
|
// we have a number of types defined from the Matrix spec which can't reasonably be altered here.
|
||||||
/* eslint-disable camelcase */
|
/* eslint-disable camelcase */
|
||||||
|
@ -1421,10 +1422,9 @@ export default class InviteDialog extends React.PureComponent<Props, IInviteDial
|
||||||
|
|
||||||
let dialogContent;
|
let dialogContent;
|
||||||
if (this.props.kind === InviteKind.CallTransfer) {
|
if (this.props.kind === InviteKind.CallTransfer) {
|
||||||
const tabs: Tab[] = [];
|
const tabs: NonEmptyArray<Tab> = [
|
||||||
tabs.push(
|
|
||||||
new Tab(TabId.UserDirectory, _td("User Directory"), "mx_InviteDialog_userDirectoryIcon", usersSection),
|
new Tab(TabId.UserDirectory, _td("User Directory"), "mx_InviteDialog_userDirectoryIcon", usersSection),
|
||||||
);
|
];
|
||||||
|
|
||||||
const backspaceButton = <DialPadBackspaceButton onBackspacePress={this.onDeletePress} />;
|
const backspaceButton = <DialPadBackspaceButton onBackspacePress={this.onDeletePress} />;
|
||||||
|
|
||||||
|
|
|
@ -34,6 +34,7 @@ import BaseDialog from "./BaseDialog";
|
||||||
import { Action } from "../../../dispatcher/actions";
|
import { Action } from "../../../dispatcher/actions";
|
||||||
import { VoipRoomSettingsTab } from "../settings/tabs/room/VoipRoomSettingsTab";
|
import { VoipRoomSettingsTab } from "../settings/tabs/room/VoipRoomSettingsTab";
|
||||||
import { ActionPayload } from "../../../dispatcher/payloads";
|
import { ActionPayload } from "../../../dispatcher/payloads";
|
||||||
|
import { NonEmptyArray } from "../../../@types/common";
|
||||||
|
|
||||||
export const ROOM_GENERAL_TAB = "ROOM_GENERAL_TAB";
|
export const ROOM_GENERAL_TAB = "ROOM_GENERAL_TAB";
|
||||||
export const ROOM_VOIP_TAB = "ROOM_VOIP_TAB";
|
export const ROOM_VOIP_TAB = "ROOM_VOIP_TAB";
|
||||||
|
@ -85,11 +86,11 @@ export default class RoomSettingsDialog extends React.Component<IProps, IState>
|
||||||
|
|
||||||
private onRoomName = (): void => {
|
private onRoomName = (): void => {
|
||||||
this.setState({
|
this.setState({
|
||||||
roomName: MatrixClientPeg.get().getRoom(this.props.roomId).name,
|
roomName: MatrixClientPeg.get().getRoom(this.props.roomId)?.name ?? "",
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
private getTabs(): Tab[] {
|
private getTabs(): NonEmptyArray<Tab> {
|
||||||
const tabs: Tab[] = [];
|
const tabs: Tab[] = [];
|
||||||
|
|
||||||
tabs.push(
|
tabs.push(
|
||||||
|
@ -178,7 +179,7 @@ export default class RoomSettingsDialog extends React.Component<IProps, IState>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return tabs;
|
return tabs as NonEmptyArray<Tab>;
|
||||||
}
|
}
|
||||||
|
|
||||||
public render(): React.ReactNode {
|
public render(): React.ReactNode {
|
||||||
|
|
|
@ -26,6 +26,7 @@ import SettingsStore from "../../../settings/SettingsStore";
|
||||||
import { SettingLevel } from "../../../settings/SettingLevel";
|
import { SettingLevel } from "../../../settings/SettingLevel";
|
||||||
import RoomName from "../elements/RoomName";
|
import RoomName from "../elements/RoomName";
|
||||||
import { SpacePreferenceTab } from "../../../dispatcher/payloads/OpenSpacePreferencesPayload";
|
import { SpacePreferenceTab } from "../../../dispatcher/payloads/OpenSpacePreferencesPayload";
|
||||||
|
import { NonEmptyArray } from "../../../@types/common";
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
space: Room;
|
space: Room;
|
||||||
|
@ -69,7 +70,7 @@ const SpacePreferencesAppearanceTab: React.FC<Pick<IProps, "space">> = ({ space
|
||||||
};
|
};
|
||||||
|
|
||||||
const SpacePreferencesDialog: React.FC<IProps> = ({ space, initialTabId, onFinished }) => {
|
const SpacePreferencesDialog: React.FC<IProps> = ({ space, initialTabId, onFinished }) => {
|
||||||
const tabs = [
|
const tabs: NonEmptyArray<Tab> = [
|
||||||
new Tab(
|
new Tab(
|
||||||
SpacePreferenceTab.Appearance,
|
SpacePreferenceTab.Appearance,
|
||||||
_td("Appearance"),
|
_td("Appearance"),
|
||||||
|
|
|
@ -30,6 +30,7 @@ import { UIFeature } from "../../../settings/UIFeature";
|
||||||
import AdvancedRoomSettingsTab from "../settings/tabs/room/AdvancedRoomSettingsTab";
|
import AdvancedRoomSettingsTab from "../settings/tabs/room/AdvancedRoomSettingsTab";
|
||||||
import RolesRoomSettingsTab from "../settings/tabs/room/RolesRoomSettingsTab";
|
import RolesRoomSettingsTab from "../settings/tabs/room/RolesRoomSettingsTab";
|
||||||
import { Action } from "../../../dispatcher/actions";
|
import { Action } from "../../../dispatcher/actions";
|
||||||
|
import { NonEmptyArray } from "../../../@types/common";
|
||||||
|
|
||||||
export enum SpaceSettingsTab {
|
export enum SpaceSettingsTab {
|
||||||
General = "SPACE_GENERAL_TAB",
|
General = "SPACE_GENERAL_TAB",
|
||||||
|
@ -79,7 +80,7 @@ const SpaceSettingsDialog: React.FC<IProps> = ({ matrixClient: cli, space, onFin
|
||||||
<AdvancedRoomSettingsTab roomId={space.roomId} closeSettingsFn={onFinished} />,
|
<AdvancedRoomSettingsTab roomId={space.roomId} closeSettingsFn={onFinished} />,
|
||||||
)
|
)
|
||||||
: null,
|
: null,
|
||||||
].filter(Boolean);
|
].filter(Boolean) as NonEmptyArray<Tab>;
|
||||||
}, [cli, space, onFinished]);
|
}, [cli, space, onFinished]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -36,6 +36,7 @@ import SidebarUserSettingsTab from "../settings/tabs/user/SidebarUserSettingsTab
|
||||||
import KeyboardUserSettingsTab from "../settings/tabs/user/KeyboardUserSettingsTab";
|
import KeyboardUserSettingsTab from "../settings/tabs/user/KeyboardUserSettingsTab";
|
||||||
import SessionManagerTab from "../settings/tabs/user/SessionManagerTab";
|
import SessionManagerTab from "../settings/tabs/user/SessionManagerTab";
|
||||||
import { UserTab } from "./UserTab";
|
import { UserTab } from "./UserTab";
|
||||||
|
import { NonEmptyArray } from "../../../@types/common";
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
initialTabId?: UserTab;
|
initialTabId?: UserTab;
|
||||||
|
@ -80,7 +81,7 @@ export default class UserSettingsDialog extends React.Component<IProps, IState>
|
||||||
this.setState({ newSessionManagerEnabled: newValue });
|
this.setState({ newSessionManagerEnabled: newValue });
|
||||||
};
|
};
|
||||||
|
|
||||||
private getTabs(): Tab[] {
|
private getTabs(): NonEmptyArray<Tab> {
|
||||||
const tabs: Tab[] = [];
|
const tabs: Tab[] = [];
|
||||||
|
|
||||||
tabs.push(
|
tabs.push(
|
||||||
|
@ -207,7 +208,7 @@ export default class UserSettingsDialog extends React.Component<IProps, IState>
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
return tabs;
|
return tabs as NonEmptyArray<Tab>;
|
||||||
}
|
}
|
||||||
|
|
||||||
public render(): React.ReactNode {
|
public render(): React.ReactNode {
|
||||||
|
|
|
@ -23,6 +23,7 @@ import DialogButtons from "./DialogButtons";
|
||||||
import AccessibleButton from "./AccessibleButton";
|
import AccessibleButton from "./AccessibleButton";
|
||||||
import TabbedView, { Tab, TabLocation } from "../../structures/TabbedView";
|
import TabbedView, { Tab, TabLocation } from "../../structures/TabbedView";
|
||||||
import PlatformPeg from "../../../PlatformPeg";
|
import PlatformPeg from "../../../PlatformPeg";
|
||||||
|
import { NonEmptyArray } from "../../../@types/common";
|
||||||
|
|
||||||
export function getDesktopCapturerSources(): Promise<Array<DesktopCapturerSource>> {
|
export function getDesktopCapturerSources(): Promise<Array<DesktopCapturerSource>> {
|
||||||
const options: GetSourcesOptions = {
|
const options: GetSourcesOptions = {
|
||||||
|
@ -80,7 +81,7 @@ export interface PickerIState {
|
||||||
selectedSource: DesktopCapturerSource | null;
|
selectedSource: DesktopCapturerSource | null;
|
||||||
}
|
}
|
||||||
export interface PickerIProps {
|
export interface PickerIProps {
|
||||||
onFinished(sourceId: string): void;
|
onFinished(sourceId?: string): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class DesktopCapturerSourcePicker extends React.Component<PickerIProps, PickerIState> {
|
export default class DesktopCapturerSourcePicker extends React.Component<PickerIProps, PickerIState> {
|
||||||
|
@ -129,7 +130,7 @@ export default class DesktopCapturerSourcePicker extends React.Component<PickerI
|
||||||
};
|
};
|
||||||
|
|
||||||
private onCloseClick = (): void => {
|
private onCloseClick = (): void => {
|
||||||
this.props.onFinished(null);
|
this.props.onFinished();
|
||||||
};
|
};
|
||||||
|
|
||||||
private getTab(type: "screen" | "window", label: string): Tab {
|
private getTab(type: "screen" | "window", label: string): Tab {
|
||||||
|
@ -150,7 +151,7 @@ export default class DesktopCapturerSourcePicker extends React.Component<PickerI
|
||||||
}
|
}
|
||||||
|
|
||||||
public render(): React.ReactNode {
|
public render(): React.ReactNode {
|
||||||
const tabs = [
|
const tabs: NonEmptyArray<Tab> = [
|
||||||
this.getTab("screen", _t("Share entire screen")),
|
this.getTab("screen", _t("Share entire screen")),
|
||||||
this.getTab("window", _t("Application window")),
|
this.getTab("window", _t("Application window")),
|
||||||
];
|
];
|
||||||
|
|
|
@ -25,7 +25,7 @@ import { objectHasDiff } from "../../../utils/objects";
|
||||||
|
|
||||||
const CUSTOM_VALUE = "SELECT_VALUE_CUSTOM";
|
const CUSTOM_VALUE = "SELECT_VALUE_CUSTOM";
|
||||||
|
|
||||||
interface IProps {
|
interface Props<K extends undefined | string> {
|
||||||
value: number;
|
value: number;
|
||||||
// The maximum value that can be set with the power selector
|
// The maximum value that can be set with the power selector
|
||||||
maxValue: number;
|
maxValue: number;
|
||||||
|
@ -35,13 +35,14 @@ interface IProps {
|
||||||
|
|
||||||
// should the user be able to change the value? false by default.
|
// should the user be able to change the value? false by default.
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
onChange?: (value: number, powerLevelKey: string) => void;
|
|
||||||
|
|
||||||
// Optional key to pass as the second argument to `onChange`
|
|
||||||
powerLevelKey?: string;
|
|
||||||
|
|
||||||
// The name to annotate the selector with
|
// The name to annotate the selector with
|
||||||
label?: string;
|
label?: string;
|
||||||
|
|
||||||
|
onChange(value: number, powerLevelKey: K extends undefined ? void : K): void;
|
||||||
|
|
||||||
|
// Optional key to pass as the second argument to `onChange`
|
||||||
|
powerLevelKey: K extends undefined ? void : K;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IState {
|
interface IState {
|
||||||
|
@ -54,13 +55,13 @@ interface IState {
|
||||||
custom?: boolean;
|
custom?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class PowerSelector extends React.Component<IProps, IState> {
|
export default class PowerSelector<K extends undefined | string> extends React.Component<Props<K>, IState> {
|
||||||
public static defaultProps: Partial<IProps> = {
|
public static defaultProps: Partial<Props<any>> = {
|
||||||
maxValue: Infinity,
|
maxValue: Infinity,
|
||||||
usersDefault: 0,
|
usersDefault: 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
public constructor(props: IProps) {
|
public constructor(props: Props<K>) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
|
@ -77,7 +78,7 @@ export default class PowerSelector extends React.Component<IProps, IState> {
|
||||||
this.initStateFromProps();
|
this.initStateFromProps();
|
||||||
}
|
}
|
||||||
|
|
||||||
public componentDidUpdate(prevProps: Readonly<IProps>): void {
|
public componentDidUpdate(prevProps: Readonly<Props<K>>): void {
|
||||||
if (objectHasDiff(this.props, prevProps)) {
|
if (objectHasDiff(this.props, prevProps)) {
|
||||||
this.initStateFromProps();
|
this.initStateFromProps();
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,7 +31,7 @@ const defaultOptions: QRCodeToDataURLOptions = {
|
||||||
};
|
};
|
||||||
|
|
||||||
const QRCode: React.FC<IProps> = ({ data, className, ...options }) => {
|
const QRCode: React.FC<IProps> = ({ data, className, ...options }) => {
|
||||||
const [dataUri, setUri] = React.useState<string>(null);
|
const [dataUri, setUri] = React.useState<string | null>(null);
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
let cancelled = false;
|
let cancelled = false;
|
||||||
toDataURL(data, { ...defaultOptions, ...options }).then((uri) => {
|
toDataURL(data, { ...defaultOptions, ...options }).then((uri) => {
|
||||||
|
|
|
@ -63,7 +63,7 @@ interface IState {
|
||||||
// The loaded events to be rendered as linear-replies
|
// The loaded events to be rendered as linear-replies
|
||||||
events: MatrixEvent[];
|
events: MatrixEvent[];
|
||||||
// The latest loaded event which has not yet been shown
|
// The latest loaded event which has not yet been shown
|
||||||
loadedEv: MatrixEvent;
|
loadedEv: MatrixEvent | null;
|
||||||
// Whether the component is still loading more events
|
// Whether the component is still loading more events
|
||||||
loading: boolean;
|
loading: boolean;
|
||||||
// Whether as error was encountered fetching a replied to event.
|
// Whether as error was encountered fetching a replied to event.
|
||||||
|
@ -145,7 +145,7 @@ export default class ReplyChain extends React.Component<IProps, IState> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async getNextEvent(ev: MatrixEvent): Promise<MatrixEvent> {
|
private async getNextEvent(ev: MatrixEvent): Promise<MatrixEvent | null> {
|
||||||
try {
|
try {
|
||||||
const inReplyToEventId = getParentEventId(ev);
|
const inReplyToEventId = getParentEventId(ev);
|
||||||
return await this.getEvent(inReplyToEventId);
|
return await this.getEvent(inReplyToEventId);
|
||||||
|
@ -154,7 +154,7 @@ export default class ReplyChain extends React.Component<IProps, IState> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async getEvent(eventId: string): Promise<MatrixEvent> {
|
private async getEvent(eventId: string): Promise<MatrixEvent | null> {
|
||||||
if (!eventId) return null;
|
if (!eventId) return null;
|
||||||
const event = this.room.findEventById(eventId);
|
const event = this.room.findEventById(eventId);
|
||||||
if (event) return event;
|
if (event) return event;
|
||||||
|
@ -168,7 +168,7 @@ export default class ReplyChain extends React.Component<IProps, IState> {
|
||||||
// Return null as it is falsy and thus should be treated as an error (as the event cannot be resolved).
|
// Return null as it is falsy and thus should be treated as an error (as the event cannot be resolved).
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return this.room.findEventById(eventId);
|
return this.room.findEventById(eventId) ?? null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public canCollapse = (): boolean => {
|
public canCollapse = (): boolean => {
|
||||||
|
@ -182,7 +182,7 @@ export default class ReplyChain extends React.Component<IProps, IState> {
|
||||||
private onQuoteClick = async (event: ButtonEvent): Promise<void> => {
|
private onQuoteClick = async (event: ButtonEvent): Promise<void> => {
|
||||||
const events = [this.state.loadedEv, ...this.state.events];
|
const events = [this.state.loadedEv, ...this.state.events];
|
||||||
|
|
||||||
let loadedEv = null;
|
let loadedEv: MatrixEvent | null = null;
|
||||||
if (events.length > 0) {
|
if (events.length > 0) {
|
||||||
loadedEv = await this.getNextEvent(events[0]);
|
loadedEv = await this.getNextEvent(events[0]);
|
||||||
}
|
}
|
||||||
|
@ -200,7 +200,7 @@ export default class ReplyChain extends React.Component<IProps, IState> {
|
||||||
}
|
}
|
||||||
|
|
||||||
public render(): React.ReactNode {
|
public render(): React.ReactNode {
|
||||||
let header = null;
|
let header: JSX.Element | undefined;
|
||||||
if (this.state.err) {
|
if (this.state.err) {
|
||||||
header = (
|
header = (
|
||||||
<blockquote className="mx_ReplyChain mx_ReplyChain_error">
|
<blockquote className="mx_ReplyChain mx_ReplyChain_error">
|
||||||
|
|
|
@ -81,7 +81,7 @@ export default class Slider extends React.Component<IProps> {
|
||||||
/>
|
/>
|
||||||
));
|
));
|
||||||
|
|
||||||
let selection = null;
|
let selection: JSX.Element | undefined;
|
||||||
|
|
||||||
if (!this.props.disabled) {
|
if (!this.props.disabled) {
|
||||||
const offset = this.offset(this.props.values, this.props.value);
|
const offset = this.offset(this.props.values, this.props.value);
|
||||||
|
|
|
@ -21,7 +21,7 @@ import { _t } from "../../../languageHandler";
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
// The number of elements to show before truncating. If negative, no truncation is done.
|
// The number of elements to show before truncating. If negative, no truncation is done.
|
||||||
truncateAt?: number;
|
truncateAt: number;
|
||||||
// The className to apply to the wrapping div
|
// The className to apply to the wrapping div
|
||||||
className?: string;
|
className?: string;
|
||||||
// A function that returns the children to be rendered into the element.
|
// A function that returns the children to be rendered into the element.
|
||||||
|
@ -34,7 +34,7 @@ interface IProps {
|
||||||
getChildCount?: () => number;
|
getChildCount?: () => number;
|
||||||
// A function which will be invoked when an overflow element is required.
|
// A function which will be invoked when an overflow element is required.
|
||||||
// This will be inserted after the children.
|
// This will be inserted after the children.
|
||||||
createOverflowElement?: (overflowCount: number, totalCount: number) => React.ReactNode;
|
createOverflowElement: (overflowCount: number, totalCount: number) => React.ReactNode;
|
||||||
children?: ReactNode;
|
children?: ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -71,8 +71,8 @@ export default class TruncatedList extends React.Component<IProps> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public render(): React.ReactNode {
|
public render(): ReactNode {
|
||||||
let overflowNode = null;
|
let overflowNode: ReactNode | undefined;
|
||||||
|
|
||||||
const totalChildren = this.getChildCount();
|
const totalChildren = this.getChildCount();
|
||||||
let upperBound = totalChildren;
|
let upperBound = totalChildren;
|
||||||
|
|
|
@ -37,6 +37,7 @@ import RoomContext, { TimelineRenderingType } from "../../../contexts/RoomContex
|
||||||
import { ReadPinsEventId } from "./types";
|
import { ReadPinsEventId } from "./types";
|
||||||
import Heading from "../typography/Heading";
|
import Heading from "../typography/Heading";
|
||||||
import { RoomPermalinkCreator } from "../../../utils/permalinks/Permalinks";
|
import { RoomPermalinkCreator } from "../../../utils/permalinks/Permalinks";
|
||||||
|
import { filterBoolean } from "../../../utils/arrays";
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
room: Room;
|
room: Room;
|
||||||
|
@ -44,7 +45,7 @@ interface IProps {
|
||||||
onClose(): void;
|
onClose(): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const usePinnedEvents = (room: Room): string[] => {
|
export const usePinnedEvents = (room?: Room): string[] => {
|
||||||
const [pinnedEvents, setPinnedEvents] = useState<string[]>([]);
|
const [pinnedEvents, setPinnedEvents] = useState<string[]>([]);
|
||||||
|
|
||||||
const update = useCallback(
|
const update = useCallback(
|
||||||
|
@ -173,8 +174,7 @@ const PinnedMessagesCard: React.FC<IProps> = ({ room, onClose, permalinkCreator
|
||||||
};
|
};
|
||||||
|
|
||||||
// show them in reverse, with latest pinned at the top
|
// show them in reverse, with latest pinned at the top
|
||||||
content = pinnedEvents
|
content = filterBoolean(pinnedEvents)
|
||||||
.filter(Boolean)
|
|
||||||
.reverse()
|
.reverse()
|
||||||
.map((ev) => (
|
.map((ev) => (
|
||||||
<PinnedEventTile
|
<PinnedEventTile
|
||||||
|
|
|
@ -325,7 +325,7 @@ const RoomSummaryCard: React.FC<IProps> = ({ room, permalinkCreator, onClose })
|
||||||
|
|
||||||
const memberCount = useRoomMemberCount(room);
|
const memberCount = useRoomMemberCount(room);
|
||||||
const pinningEnabled = useFeatureEnabled("feature_pinning");
|
const pinningEnabled = useFeatureEnabled("feature_pinning");
|
||||||
const pinCount = usePinnedEvents(pinningEnabled && room)?.length;
|
const pinCount = usePinnedEvents(pinningEnabled ? room : undefined)?.length;
|
||||||
|
|
||||||
const isPollHistoryEnabled = useFeatureEnabled("feature_poll_history");
|
const isPollHistoryEnabled = useFeatureEnabled("feature_poll_history");
|
||||||
|
|
||||||
|
|
|
@ -44,7 +44,7 @@ const crossSigningRoomTitles: { [key in E2EState]?: string } = {
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
isUser?: boolean;
|
isUser?: boolean;
|
||||||
status?: E2EState | E2EStatus;
|
status: E2EState | E2EStatus;
|
||||||
className?: string;
|
className?: string;
|
||||||
size?: number;
|
size?: number;
|
||||||
onClick?: () => void;
|
onClick?: () => void;
|
||||||
|
@ -76,7 +76,7 @@ const E2EIcon: React.FC<IProps> = ({
|
||||||
className,
|
className,
|
||||||
);
|
);
|
||||||
|
|
||||||
let e2eTitle;
|
let e2eTitle: string | undefined;
|
||||||
if (isUser) {
|
if (isUser) {
|
||||||
e2eTitle = crossSigningUserTitles[status];
|
e2eTitle = crossSigningUserTitles[status];
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -43,7 +43,7 @@ const PRESENCE_CLASS: Record<PresenceState, string> = {
|
||||||
unavailable: "mx_EntityTile_unavailable",
|
unavailable: "mx_EntityTile_unavailable",
|
||||||
};
|
};
|
||||||
|
|
||||||
function presenceClassForMember(presenceState: PresenceState, lastActiveAgo: number, showPresence: boolean): string {
|
function presenceClassForMember(presenceState?: PresenceState, lastActiveAgo?: number, showPresence?: boolean): string {
|
||||||
if (showPresence === false) {
|
if (showPresence === false) {
|
||||||
return "mx_EntityTile_online_beenactive";
|
return "mx_EntityTile_online_beenactive";
|
||||||
}
|
}
|
||||||
|
@ -74,7 +74,7 @@ interface IProps {
|
||||||
presenceLastTs?: number;
|
presenceLastTs?: number;
|
||||||
presenceCurrentlyActive?: boolean;
|
presenceCurrentlyActive?: boolean;
|
||||||
showInviteButton?: boolean;
|
showInviteButton?: boolean;
|
||||||
onClick?(): void;
|
onClick(): void;
|
||||||
suppressOnHover?: boolean;
|
suppressOnHover?: boolean;
|
||||||
showPresence?: boolean;
|
showPresence?: boolean;
|
||||||
subtextLabel?: string;
|
subtextLabel?: string;
|
||||||
|
@ -108,7 +108,7 @@ export default class EntityTile extends React.PureComponent<IProps, IState> {
|
||||||
public render(): React.ReactNode {
|
public render(): React.ReactNode {
|
||||||
const mainClassNames: Record<string, boolean> = {
|
const mainClassNames: Record<string, boolean> = {
|
||||||
mx_EntityTile: true,
|
mx_EntityTile: true,
|
||||||
mx_EntityTile_noHover: this.props.suppressOnHover,
|
mx_EntityTile_noHover: !!this.props.suppressOnHover,
|
||||||
};
|
};
|
||||||
if (this.props.className) mainClassNames[this.props.className] = true;
|
if (this.props.className) mainClassNames[this.props.className] = true;
|
||||||
|
|
||||||
|
@ -127,7 +127,7 @@ export default class EntityTile extends React.PureComponent<IProps, IState> {
|
||||||
? Date.now() - (this.props.presenceLastTs - this.props.presenceLastActiveAgo)
|
? Date.now() - (this.props.presenceLastTs - this.props.presenceLastActiveAgo)
|
||||||
: -1;
|
: -1;
|
||||||
|
|
||||||
let presenceLabel = null;
|
let presenceLabel: JSX.Element | undefined;
|
||||||
if (this.props.showPresence) {
|
if (this.props.showPresence) {
|
||||||
presenceLabel = (
|
presenceLabel = (
|
||||||
<PresenceLabel
|
<PresenceLabel
|
||||||
|
|
|
@ -39,7 +39,7 @@ export default class PresenceLabel extends React.Component<IProps> {
|
||||||
|
|
||||||
// Return duration as a string using appropriate time units
|
// Return duration as a string using appropriate time units
|
||||||
// XXX: This would be better handled using a culture-aware library, but we don't use one yet.
|
// XXX: This would be better handled using a culture-aware library, but we don't use one yet.
|
||||||
private getDuration(time: number): string {
|
private getDuration(time: number): string | undefined {
|
||||||
if (!time) return;
|
if (!time) return;
|
||||||
const t = Math.round(time / 1000);
|
const t = Math.round(time / 1000);
|
||||||
const s = t % 60;
|
const s = t % 60;
|
||||||
|
@ -61,11 +61,11 @@ export default class PresenceLabel extends React.Component<IProps> {
|
||||||
return _t("%(duration)sd", { duration: d });
|
return _t("%(duration)sd", { duration: d });
|
||||||
}
|
}
|
||||||
|
|
||||||
private getPrettyPresence(presence: string, activeAgo: number, currentlyActive: boolean): string {
|
private getPrettyPresence(presence?: string, activeAgo?: number, currentlyActive?: boolean): string {
|
||||||
// for busy presence, we ignore the 'currentlyActive' flag: they're busy whether
|
// for busy presence, we ignore the 'currentlyActive' flag: they're busy whether
|
||||||
// they're active or not. It can be set while the user is active in which case
|
// they're active or not. It can be set while the user is active in which case
|
||||||
// the 'active ago' ends up being 0.
|
// the 'active ago' ends up being 0.
|
||||||
if (BUSY_PRESENCE_NAME.matches(presence)) return _t("Busy");
|
if (presence && BUSY_PRESENCE_NAME.matches(presence)) return _t("Busy");
|
||||||
|
|
||||||
if (!currentlyActive && activeAgo !== undefined && activeAgo > 0) {
|
if (!currentlyActive && activeAgo !== undefined && activeAgo > 0) {
|
||||||
const duration = this.getDuration(activeAgo);
|
const duration = this.getDuration(activeAgo);
|
||||||
|
|
|
@ -129,9 +129,9 @@ interface IProps {
|
||||||
|
|
||||||
interface IState {
|
interface IState {
|
||||||
verifying: boolean;
|
verifying: boolean;
|
||||||
verifyError: string;
|
verifyError: string | null;
|
||||||
verifyMsisdn: string;
|
verifyMsisdn: string;
|
||||||
addTask: AddThreepid;
|
addTask: AddThreepid | null;
|
||||||
continueDisabled: boolean;
|
continueDisabled: boolean;
|
||||||
phoneCountry: string;
|
phoneCountry: string;
|
||||||
newPhoneNumber: string;
|
newPhoneNumber: string;
|
||||||
|
@ -205,7 +205,7 @@ export default class PhoneNumbers extends React.Component<IProps, IState> {
|
||||||
const token = this.state.newPhoneNumberCode;
|
const token = this.state.newPhoneNumberCode;
|
||||||
const address = this.state.verifyMsisdn;
|
const address = this.state.verifyMsisdn;
|
||||||
this.state.addTask
|
this.state.addTask
|
||||||
.haveMsisdnToken(token)
|
?.haveMsisdnToken(token)
|
||||||
.then(([finished]) => {
|
.then(([finished]) => {
|
||||||
let newPhoneNumber = this.state.newPhoneNumber;
|
let newPhoneNumber = this.state.newPhoneNumber;
|
||||||
if (finished) {
|
if (finished) {
|
||||||
|
|
|
@ -21,6 +21,7 @@ import { RoomState, RoomStateEvent } from "matrix-js-sdk/src/models/room-state";
|
||||||
import { logger } from "matrix-js-sdk/src/logger";
|
import { logger } from "matrix-js-sdk/src/logger";
|
||||||
import { throttle, get } from "lodash";
|
import { throttle, get } from "lodash";
|
||||||
import { compare } from "matrix-js-sdk/src/utils";
|
import { compare } from "matrix-js-sdk/src/utils";
|
||||||
|
import { IContent } from "matrix-js-sdk/src/models/event";
|
||||||
|
|
||||||
import { _t, _td } from "../../../../../languageHandler";
|
import { _t, _td } from "../../../../../languageHandler";
|
||||||
import { MatrixClientPeg } from "../../../../../MatrixClientPeg";
|
import { MatrixClientPeg } from "../../../../../MatrixClientPeg";
|
||||||
|
@ -171,8 +172,8 @@ export default class RolesRoomSettingsTab extends React.Component<IProps> {
|
||||||
private onPowerLevelsChanged = (value: number, powerLevelKey: string): void => {
|
private onPowerLevelsChanged = (value: number, powerLevelKey: string): void => {
|
||||||
const client = MatrixClientPeg.get();
|
const client = MatrixClientPeg.get();
|
||||||
const room = client.getRoom(this.props.roomId);
|
const room = client.getRoom(this.props.roomId);
|
||||||
const plEvent = room.currentState.getStateEvents(EventType.RoomPowerLevels, "");
|
const plEvent = room?.currentState.getStateEvents(EventType.RoomPowerLevels, "");
|
||||||
let plContent = plEvent ? plEvent.getContent() || {} : {};
|
let plContent = plEvent?.getContent() ?? {};
|
||||||
|
|
||||||
// Clone the power levels just in case
|
// Clone the power levels just in case
|
||||||
plContent = Object.assign({}, plContent);
|
plContent = Object.assign({}, plContent);
|
||||||
|
@ -185,7 +186,7 @@ export default class RolesRoomSettingsTab extends React.Component<IProps> {
|
||||||
plContent["events"][powerLevelKey.slice(eventsLevelPrefix.length)] = value;
|
plContent["events"][powerLevelKey.slice(eventsLevelPrefix.length)] = value;
|
||||||
} else {
|
} else {
|
||||||
const keyPath = powerLevelKey.split(".");
|
const keyPath = powerLevelKey.split(".");
|
||||||
let parentObj;
|
let parentObj: IContent | undefined;
|
||||||
let currentObj = plContent;
|
let currentObj = plContent;
|
||||||
for (const key of keyPath) {
|
for (const key of keyPath) {
|
||||||
if (!currentObj[key]) {
|
if (!currentObj[key]) {
|
||||||
|
@ -213,8 +214,8 @@ export default class RolesRoomSettingsTab extends React.Component<IProps> {
|
||||||
private onUserPowerLevelChanged = (value: number, powerLevelKey: string): void => {
|
private onUserPowerLevelChanged = (value: number, powerLevelKey: string): void => {
|
||||||
const client = MatrixClientPeg.get();
|
const client = MatrixClientPeg.get();
|
||||||
const room = client.getRoom(this.props.roomId);
|
const room = client.getRoom(this.props.roomId);
|
||||||
const plEvent = room.currentState.getStateEvents(EventType.RoomPowerLevels, "");
|
const plEvent = room?.currentState.getStateEvents(EventType.RoomPowerLevels, "");
|
||||||
let plContent = plEvent ? plEvent.getContent() || {} : {};
|
let plContent = plEvent?.getContent() ?? {};
|
||||||
|
|
||||||
// Clone the power levels just in case
|
// Clone the power levels just in case
|
||||||
plContent = Object.assign({}, plContent);
|
plContent = Object.assign({}, plContent);
|
||||||
|
|
|
@ -113,7 +113,7 @@ export default class WidgetStore extends AsyncStoreWithClient<IState> {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private loadRoomWidgets(room: Room): void {
|
private loadRoomWidgets(room: Room | null): void {
|
||||||
if (!room) return;
|
if (!room) return;
|
||||||
const roomInfo = this.roomMap.get(room.roomId) || <IRoomWidgets>{};
|
const roomInfo = this.roomMap.get(room.roomId) || <IRoomWidgets>{};
|
||||||
roomInfo.widgets = [];
|
roomInfo.widgets = [];
|
||||||
|
|
|
@ -18,7 +18,7 @@ import { Room } from "matrix-js-sdk/src/matrix";
|
||||||
|
|
||||||
import { LocalRoom, LOCAL_ROOM_ID_PREFIX } from "../../models/LocalRoom";
|
import { LocalRoom, LOCAL_ROOM_ID_PREFIX } from "../../models/LocalRoom";
|
||||||
|
|
||||||
export function isLocalRoom(roomOrID: Room | string): boolean {
|
export function isLocalRoom(roomOrID?: Room | string): boolean {
|
||||||
if (typeof roomOrID === "string") {
|
if (typeof roomOrID === "string") {
|
||||||
return roomOrID.startsWith(LOCAL_ROOM_ID_PREFIX);
|
return roomOrID.startsWith(LOCAL_ROOM_ID_PREFIX);
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,6 +18,7 @@ import React from "react";
|
||||||
import { act, fireEvent, render } from "@testing-library/react";
|
import { act, fireEvent, render } from "@testing-library/react";
|
||||||
|
|
||||||
import TabbedView, { Tab, TabLocation } from "../../../src/components/structures/TabbedView";
|
import TabbedView, { Tab, TabLocation } from "../../../src/components/structures/TabbedView";
|
||||||
|
import { NonEmptyArray } from "../../../src/@types/common";
|
||||||
|
|
||||||
describe("<TabbedView />", () => {
|
describe("<TabbedView />", () => {
|
||||||
const generalTab = new Tab("GENERAL", "General", "general", <div>general</div>);
|
const generalTab = new Tab("GENERAL", "General", "general", <div>general</div>);
|
||||||
|
@ -25,7 +26,7 @@ describe("<TabbedView />", () => {
|
||||||
const securityTab = new Tab("SECURITY", "Security", "security", <div>security</div>);
|
const securityTab = new Tab("SECURITY", "Security", "security", <div>security</div>);
|
||||||
const defaultProps = {
|
const defaultProps = {
|
||||||
tabLocation: TabLocation.LEFT,
|
tabLocation: TabLocation.LEFT,
|
||||||
tabs: [generalTab, labsTab, securityTab],
|
tabs: [generalTab, labsTab, securityTab] as NonEmptyArray<Tab>,
|
||||||
};
|
};
|
||||||
const getComponent = (props = {}): React.ReactElement => <TabbedView {...defaultProps} {...props} />;
|
const getComponent = (props = {}): React.ReactElement => <TabbedView {...defaultProps} {...props} />;
|
||||||
|
|
||||||
|
@ -58,11 +59,6 @@ describe("<TabbedView />", () => {
|
||||||
expect(getActiveTabBody(container)?.textContent).toEqual("security");
|
expect(getActiveTabBody(container)?.textContent).toEqual("security");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("renders without error when there are no tabs", () => {
|
|
||||||
const { container } = render(getComponent({ tabs: [] }));
|
|
||||||
expect(container).toMatchSnapshot();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("sets active tab on tab click", () => {
|
it("sets active tab on tab click", () => {
|
||||||
const { container, getByTestId } = render(getComponent());
|
const { container, getByTestId } = render(getComponent());
|
||||||
|
|
||||||
|
|
|
@ -69,15 +69,3 @@ exports[`<TabbedView /> renders tabs 1`] = `
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`<TabbedView /> renders without error when there are no tabs 1`] = `
|
|
||||||
<div>
|
|
||||||
<div
|
|
||||||
class="mx_TabbedView mx_TabbedView_tabsOnLeft"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="mx_TabbedView_tabLabels"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
|
|
|
@ -38,7 +38,7 @@ exports[`<PollHistoryDialog /> renders a list of active polls when there are pol
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
aria-labelledby="mx_BaseDialog_title"
|
aria-labelledby="mx_BaseDialog_title"
|
||||||
class="undefined mx_Dialog_fixedWidth"
|
class="mx_Dialog_fixedWidth"
|
||||||
data-focus-lock-disabled="false"
|
data-focus-lock-disabled="false"
|
||||||
role="dialog"
|
role="dialog"
|
||||||
>
|
>
|
||||||
|
|
|
@ -190,8 +190,8 @@ describe("BreadcrumbsStore", () => {
|
||||||
/**
|
/**
|
||||||
* Create as many fake rooms in an array as you ask for.
|
* Create as many fake rooms in an array as you ask for.
|
||||||
*/
|
*/
|
||||||
function fakeRooms(howMany: number): Array<Room> {
|
function fakeRooms(howMany: number): Room[] {
|
||||||
const ret = [];
|
const ret: Room[] = [];
|
||||||
for (let i = 0; i < howMany; i++) {
|
for (let i = 0; i < howMany; i++) {
|
||||||
ret.push(fakeRoom());
|
ret.push(fakeRoom());
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue