Conform more code to strictNullChecks (#10368

* Conform more code to `strictNullChecks`

* Iterate
This commit is contained in:
Michael Telatynski 2023-03-14 11:09:35 +00:00 committed by GitHub
parent 05e3fb09d6
commit 8cb8cd4eb1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
24 changed files with 176 additions and 157 deletions

View file

@ -32,8 +32,19 @@ export type ComponentType = React.ComponentType<{
onFinished?(...args: any): void; onFinished?(...args: any): void;
}>; }>;
type Defaultize<P, D> = P extends any
? string extends keyof P
? P
: Pick<P, Exclude<keyof P, keyof D>> &
Partial<Pick<P, Extract<keyof P, keyof D>>> &
Partial<Pick<D, Exclude<keyof D, keyof P>>>
: never;
// Generic type which returns the props of the Modal component with the onFinished being optional. // Generic type which returns the props of the Modal component with the onFinished being optional.
export type ComponentProps<C extends ComponentType> = Omit<React.ComponentProps<C>, "onFinished"> & export type ComponentProps<C extends ComponentType> = Defaultize<
Omit<React.ComponentProps<C>, "onFinished">,
C["defaultProps"]
> &
Partial<Pick<React.ComponentProps<C>, "onFinished">>; Partial<Pick<React.ComponentProps<C>, "onFinished">>;
export interface IModal<C extends ComponentType> { export interface IModal<C extends ComponentType> {

View file

@ -1020,7 +1020,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
}); });
}; };
private onPageUnload = (event: BeforeUnloadEvent): string => { private onPageUnload = (event: BeforeUnloadEvent): string | undefined => {
if (ContentMessages.sharedInstance().getCurrentUploads().length > 0) { if (ContentMessages.sharedInstance().getCurrentUploads().length > 0) {
return (event.returnValue = _t("You seem to be uploading files, are you sure you want to quit?")); return (event.returnValue = _t("You seem to be uploading files, are you sure you want to quit?"));
} else if (this.getCallForRoom() && this.state.callState !== "ended") { } else if (this.getCallForRoom() && this.state.callState !== "ended") {
@ -1034,7 +1034,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
const action = getKeyBindingsManager().getRoomAction(ev); const action = getKeyBindingsManager().getRoomAction(ev);
switch (action) { switch (action) {
case KeyBindingAction.DismissReadMarker: case KeyBindingAction.DismissReadMarker:
this.messagePanel.forgetReadMarker(); this.messagePanel?.forgetReadMarker();
this.jumpToLiveTimeline(); this.jumpToLiveTimeline();
handled = true; handled = true;
break; break;
@ -1067,7 +1067,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
if (!roomId) return; if (!roomId) return;
const call = this.getCallForRoom(); const call = this.getCallForRoom();
this.setState({ callState: call ? call.state : null }); this.setState({ callState: call?.state });
}; };
private onAction = async (payload: ActionPayload): Promise<void> => { private onAction = async (payload: ActionPayload): Promise<void> => {
@ -1087,7 +1087,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
ContentMessages.sharedInstance().sendContentListToRoom( ContentMessages.sharedInstance().sendContentListToRoom(
[payload.file], [payload.file],
this.state.room.roomId, this.state.room.roomId,
null, undefined,
this.context.client, this.context.client,
); );
break; break;
@ -1117,7 +1117,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
if (!this.state.matrixClientIsReady) { if (!this.state.matrixClientIsReady) {
this.setState( this.setState(
{ {
matrixClientIsReady: this.context.client?.isInitialSyncComplete(), matrixClientIsReady: !!this.context.client?.isInitialSyncComplete(),
}, },
() => { () => {
// send another "initial" RVS update to trigger peeking if needed // send another "initial" RVS update to trigger peeking if needed
@ -1137,7 +1137,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
case Action.EditEvent: { case Action.EditEvent: {
// Quit early if we're trying to edit events in wrong rendering context // Quit early if we're trying to edit events in wrong rendering context
if (payload.timelineRenderingType !== this.state.timelineRenderingType) return; if (payload.timelineRenderingType !== this.state.timelineRenderingType) return;
const editState = payload.event ? new EditorStateTransfer(payload.event) : null; const editState = payload.event ? new EditorStateTransfer(payload.event) : undefined;
this.setState({ editState }, () => { this.setState({ editState }, () => {
if (payload.event) { if (payload.event) {
this.messagePanel?.scrollToEventIfNeeded(payload.event.getId()); this.messagePanel?.scrollToEventIfNeeded(payload.event.getId());
@ -1194,7 +1194,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
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,
@ -1228,7 +1228,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
this.handleEffects(ev); this.handleEffects(ev);
} }
if (ev.getSender() !== this.context.client.credentials.userId) { if (ev.getSender() !== this.context.client.getSafeUserId()) {
// update unread count when scrolled up // update unread count when scrolled up
if (!this.state.search && this.state.atEndOfLiveTimeline) { if (!this.state.search && this.state.atEndOfLiveTimeline) {
// no change // no change
@ -1325,7 +1325,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
}; };
private getRoomTombstone(room = this.state.room): MatrixEvent | undefined { private getRoomTombstone(room = this.state.room): MatrixEvent | undefined {
return room?.currentState.getStateEvents(EventType.RoomTombstone, ""); return room?.currentState.getStateEvents(EventType.RoomTombstone, "") ?? undefined;
} }
private async calculateRecommendedVersion(room: Room): Promise<void> { private async calculateRecommendedVersion(room: Room): Promise<void> {
@ -1336,7 +1336,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
private async loadMembersIfJoined(room: Room): Promise<void> { private async loadMembersIfJoined(room: Room): Promise<void> {
// lazy load members if enabled // lazy load members if enabled
if (this.context.client.hasLazyLoadMembersEnabled()) { if (this.context.client?.hasLazyLoadMembersEnabled()) {
if (room && room.getMyMembership() === "join") { if (room && room.getMyMembership() === "join") {
try { try {
await room.loadMembersIfNeeded(); await room.loadMembersIfNeeded();
@ -1415,7 +1415,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
}; };
private async updateE2EStatus(room: Room): Promise<void> { private async updateE2EStatus(room: Room): Promise<void> {
if (!this.context.client.isRoomEncrypted(room.roomId)) return; if (!this.context.client?.isRoomEncrypted(room.roomId)) return;
// If crypto is not currently enabled, we aren't tracking devices at all, // If crypto is not currently enabled, we aren't tracking devices at all,
// so we don't know what the answer is. Let's error on the safe side and show // so we don't know what the answer is. Let's error on the safe side and show
@ -2093,7 +2093,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
// We have successfully loaded this room, and are not previewing. // We have successfully loaded this room, and are not previewing.
// Display the "normal" room view. // Display the "normal" room view.
let activeCall = null; let activeCall: MatrixCall | null = null;
{ {
// New block because this variable doesn't need to hang around for the rest of the function // New block because this variable doesn't need to hang around for the rest of the function
const call = this.getCallForRoom(); const call = this.getCallForRoom();
@ -2102,7 +2102,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
} }
} }
let statusBar; let statusBar: JSX.Element | undefined;
let isStatusAreaExpanded = true; let isStatusAreaExpanded = true;
if (ContentMessages.sharedInstance().getCurrentUploads().length > 0) { if (ContentMessages.sharedInstance().getCurrentUploads().length > 0) {
@ -2301,7 +2301,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
/> />
); );
let topUnreadMessagesBar = null; let topUnreadMessagesBar: JSX.Element | undefined;
// Do not show TopUnreadMessagesBar if we have search results showing, it makes no sense // Do not show TopUnreadMessagesBar if we have search results showing, it makes no sense
if (this.state.showTopUnreadMessagesBar && !this.state.search) { if (this.state.showTopUnreadMessagesBar && !this.state.search) {
topUnreadMessagesBar = ( topUnreadMessagesBar = (
@ -2342,7 +2342,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
const showChatEffects = SettingsStore.getValue("showChatEffects"); const showChatEffects = SettingsStore.getValue("showChatEffects");
let mainSplitBody: React.ReactFragment; let mainSplitBody: JSX.Element | undefined;
let mainSplitContentClassName: string; let mainSplitContentClassName: string;
// Decide what to show in the main split // Decide what to show in the main split
switch (this.state.mainSplitContentType) { switch (this.state.mainSplitContentType) {
@ -2396,10 +2396,10 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
const mainSplitContentClasses = classNames("mx_RoomView_body", mainSplitContentClassName); const mainSplitContentClasses = classNames("mx_RoomView_body", mainSplitContentClassName);
let excludedRightPanelPhaseButtons = [RightPanelPhases.Timeline]; let excludedRightPanelPhaseButtons = [RightPanelPhases.Timeline];
let onAppsClick = this.onAppsClick; let onAppsClick: (() => void) | null = this.onAppsClick;
let onForgetClick = this.onForgetClick; let onForgetClick: (() => void) | null = this.onForgetClick;
let onSearchClick = this.onSearchClick; let onSearchClick: (() => void) | null = this.onSearchClick;
let onInviteClick = null; let onInviteClick: (() => void) | null = null;
let viewingCall = false; let viewingCall = false;
// Simplify the header for other main split types // Simplify the header for other main split types

View file

@ -100,9 +100,9 @@ const Tile: React.FC<ITileProps> = ({
children, children,
}) => { }) => {
const cli = useContext(MatrixClientContext); const cli = useContext(MatrixClientContext);
const [joinedRoom, setJoinedRoom] = useState<Room>(() => { const [joinedRoom, setJoinedRoom] = useState<Room | undefined>(() => {
const cliRoom = cli.getRoom(room.room_id); const cliRoom = cli.getRoom(room.room_id);
return cliRoom?.getMyMembership() === "join" ? cliRoom : null; return cliRoom?.getMyMembership() === "join" ? cliRoom : undefined;
}); });
const joinedRoomName = useTypedEventEmitterState(joinedRoom, RoomEvent.Name, (room) => room?.name); const joinedRoomName = useTypedEventEmitterState(joinedRoom, RoomEvent.Name, (room) => room?.name);
const name = const name =
@ -264,9 +264,9 @@ const Tile: React.FC<ITileProps> = ({
</React.Fragment> </React.Fragment>
); );
let childToggle: JSX.Element; let childToggle: JSX.Element | undefined;
let childSection: JSX.Element; let childSection: JSX.Element | undefined;
let onKeyDown: KeyboardEventHandler; let onKeyDown: KeyboardEventHandler | undefined;
if (children) { if (children) {
// the chevron is purposefully a div rather than a button as it should be ignored for a11y // the chevron is purposefully a div rather than a button as it should be ignored for a11y
childToggle = ( childToggle = (
@ -386,7 +386,7 @@ export const showRoom = (cli: MatrixClient, hierarchy: RoomHierarchy, roomId: st
}); });
}; };
export const joinRoom = (cli: MatrixClient, hierarchy: RoomHierarchy, roomId: string): Promise<unknown> => { export const joinRoom = async (cli: MatrixClient, hierarchy: RoomHierarchy, roomId: string): Promise<unknown> => {
// Don't let the user view a room they won't be able to either peek or join: // Don't let the user view a room they won't be able to either peek or join:
// fail earlier so they don't have to click back to the directory. // fail earlier so they don't have to click back to the directory.
if (cli.isGuest()) { if (cli.isGuest()) {
@ -394,24 +394,20 @@ export const joinRoom = (cli: MatrixClient, hierarchy: RoomHierarchy, roomId: st
return; return;
} }
const prom = cli.joinRoom(roomId, { try {
await cli.joinRoom(roomId, {
viaServers: Array.from(hierarchy.viaMap.get(roomId) || []), viaServers: Array.from(hierarchy.viaMap.get(roomId) || []),
}); });
} catch (err) {
SdkContextClass.instance.roomViewStore.showJoinRoomError(err, roomId);
return;
}
prom.then(
() => {
defaultDispatcher.dispatch<JoinRoomReadyPayload>({ defaultDispatcher.dispatch<JoinRoomReadyPayload>({
action: Action.JoinRoomReady, action: Action.JoinRoomReady,
roomId, roomId,
metricsTrigger: "SpaceHierarchy", metricsTrigger: "SpaceHierarchy",
}); });
},
(err) => {
SdkContextClass.instance.roomViewStore.showJoinRoomError(err, roomId);
},
);
return prom;
}; };
interface IHierarchyLevelProps { interface IHierarchyLevelProps {
@ -433,7 +429,7 @@ export const toLocalRoom = (cli: MatrixClient, room: IHierarchyRoom, hierarchy:
); );
// Pick latest room that is actually part of the hierarchy // Pick latest room that is actually part of the hierarchy
let cliRoom = null; let cliRoom: Room | null = null;
for (let idx = history.length - 1; idx >= 0; --idx) { for (let idx = history.length - 1; idx >= 0; --idx) {
if (hierarchy.roomMap.get(history[idx].roomId)) { if (hierarchy.roomMap.get(history[idx].roomId)) {
cliRoom = history[idx]; cliRoom = history[idx];
@ -448,7 +444,7 @@ export const toLocalRoom = (cli: MatrixClient, room: IHierarchyRoom, hierarchy:
room_type: cliRoom.getType(), room_type: cliRoom.getType(),
name: cliRoom.name, name: cliRoom.name,
topic: cliRoom.currentState.getStateEvents(EventType.RoomTopic, "")?.getContent().topic, topic: cliRoom.currentState.getStateEvents(EventType.RoomTopic, "")?.getContent().topic,
avatar_url: cliRoom.getMxcAvatarUrl(), avatar_url: cliRoom.getMxcAvatarUrl() ?? undefined,
canonical_alias: cliRoom.getCanonicalAlias() ?? undefined, canonical_alias: cliRoom.getCanonicalAlias() ?? undefined,
aliases: cliRoom.getAltAliases(), aliases: cliRoom.getAltAliases(),
world_readable: world_readable:
@ -476,7 +472,7 @@ export const HierarchyLevel: React.FC<IHierarchyLevelProps> = ({
}) => { }) => {
const cli = useContext(MatrixClientContext); const cli = useContext(MatrixClientContext);
const space = cli.getRoom(root.room_id); const space = cli.getRoom(root.room_id);
const hasPermissions = space?.currentState.maySendStateEvent(EventType.SpaceChild, cli.getUserId()); const hasPermissions = space?.currentState.maySendStateEvent(EventType.SpaceChild, cli.getSafeUserId());
const sortedChildren = sortBy(root.children_state, (ev) => { const sortedChildren = sortBy(root.children_state, (ev) => {
return getChildOrder(ev.content.order, ev.origin_server_ts, ev.state_key); return getChildOrder(ev.content.order, ev.origin_server_ts, ev.state_key);
@ -579,7 +575,7 @@ export const useRoomHierarchy = (
const loadMore = useCallback( const loadMore = useCallback(
async (pageSize?: number): Promise<void> => { async (pageSize?: number): Promise<void> => {
if (hierarchy.loading || !hierarchy.canLoadMore || hierarchy.noSupport || error) return; if (!hierarchy || hierarchy.loading || !hierarchy.canLoadMore || hierarchy.noSupport || error) return;
await hierarchy.load(pageSize).catch(setError); await hierarchy.load(pageSize).catch(setError);
setRooms(hierarchy.rooms); setRooms(hierarchy.rooms);
}, },
@ -673,7 +669,7 @@ const ManageButtons: React.FC<IManageButtonsProps> = ({ hierarchy, selected, set
onClick={async (): Promise<void> => { onClick={async (): Promise<void> => {
setRemoving(true); setRemoving(true);
try { try {
const userId = cli.getUserId(); const userId = cli.getSafeUserId();
for (const [parentId, childId] of selectedRelations) { for (const [parentId, childId] of selectedRelations) {
await cli.sendStateEvent(parentId, EventType.SpaceChild, {}, childId); await cli.sendStateEvent(parentId, EventType.SpaceChild, {}, childId);
@ -759,7 +755,7 @@ const SpaceHierarchy: React.FC<IProps> = ({ space, initialText = "", showRoom, a
const visited = new Set<string>(); const visited = new Set<string>();
const queue = [...directMatches.map((r) => r.room_id)]; const queue = [...directMatches.map((r) => r.room_id)];
while (queue.length) { while (queue.length) {
const roomId = queue.pop(); const roomId = queue.pop()!;
visited.add(roomId); visited.add(roomId);
hierarchy.backRefs.get(roomId)?.forEach((parentId) => { hierarchy.backRefs.get(roomId)?.forEach((parentId) => {
if (!visited.has(parentId)) { if (!visited.has(parentId)) {
@ -797,7 +793,7 @@ const SpaceHierarchy: React.FC<IProps> = ({ space, initialText = "", showRoom, a
return; return;
} }
const parentSet = selected.get(parentId); const parentSet = selected.get(parentId)!;
if (!parentSet.has(childId)) { if (!parentSet.has(childId)) {
setSelected(new Map(selected.set(parentId, new Set([...parentSet, childId])))); setSelected(new Map(selected.set(parentId, new Set([...parentSet, childId]))));
return; return;
@ -816,9 +812,9 @@ const SpaceHierarchy: React.FC<IProps> = ({ space, initialText = "", showRoom, a
} else { } else {
const hasPermissions = const hasPermissions =
space?.getMyMembership() === "join" && space?.getMyMembership() === "join" &&
space.currentState.maySendStateEvent(EventType.SpaceChild, cli.getUserId()); space.currentState.maySendStateEvent(EventType.SpaceChild, cli.getSafeUserId());
let results: JSX.Element; let results: JSX.Element | undefined;
if (filteredRoomSet.size) { if (filteredRoomSet.size) {
results = ( results = (
<> <>
@ -843,7 +839,7 @@ const SpaceHierarchy: React.FC<IProps> = ({ space, initialText = "", showRoom, a
); );
} }
let loader: JSX.Element; let loader: JSX.Element | undefined;
if (hierarchy.canLoadMore) { if (hierarchy.canLoadMore) {
loader = ( loader = (
<div ref={loaderRef}> <div ref={loaderRef}>

View file

@ -43,12 +43,12 @@ interface IProps {
interface IState { interface IState {
sendLogs: boolean; sendLogs: boolean;
busy: boolean; busy: boolean;
err: string; err: string | null;
issueUrl: string; issueUrl: string;
text: string; text: string;
progress: string; progress: string | null;
downloadBusy: boolean; downloadBusy: boolean;
downloadProgress: string; downloadProgress: string | null;
} }
export default class BugReportDialog extends React.Component<IProps, IState> { export default class BugReportDialog extends React.Component<IProps, IState> {
@ -181,12 +181,12 @@ export default class BugReportDialog extends React.Component<IProps, IState> {
}; };
public render(): React.ReactNode { public render(): React.ReactNode {
let error = null; let error: JSX.Element | undefined;
if (this.state.err) { if (this.state.err) {
error = <div className="error">{this.state.err}</div>; error = <div className="error">{this.state.err}</div>;
} }
let progress = null; let progress: JSX.Element | undefined;
if (this.state.busy) { if (this.state.busy) {
progress = ( progress = (
<div className="progress"> <div className="progress">
@ -196,7 +196,7 @@ export default class BugReportDialog extends React.Component<IProps, IState> {
); );
} }
let warning; let warning: JSX.Element | undefined;
if (window.Modernizr && Object.values(window.Modernizr).some((support) => support === false)) { if (window.Modernizr && Object.values(window.Modernizr).some((support) => support === false)) {
warning = ( warning = (
<p> <p>

View file

@ -126,7 +126,7 @@ export default class CreateRoomDialog extends React.Component<IProps, IState> {
public componentDidMount(): void { public componentDidMount(): void {
// move focus to first field when showing dialog // move focus to first field when showing dialog
this.nameField.current.focus(); this.nameField.current?.focus();
} }
private onKeyDown = (event: KeyboardEvent): void => { private onKeyDown = (event: KeyboardEvent): void => {
@ -141,10 +141,9 @@ export default class CreateRoomDialog extends React.Component<IProps, IState> {
}; };
private onOk = async (): Promise<void> => { private onOk = async (): Promise<void> => {
if (!this.nameField.current) return;
const activeElement = document.activeElement as HTMLElement; const activeElement = document.activeElement as HTMLElement;
if (activeElement) { activeElement?.blur();
activeElement.blur();
}
await this.nameField.current.validate({ allowEmpty: false }); await this.nameField.current.validate({ allowEmpty: false });
if (this.aliasField.current) { if (this.aliasField.current) {
await this.aliasField.current.validate({ allowEmpty: false }); await this.aliasField.current.validate({ allowEmpty: false });
@ -155,7 +154,7 @@ export default class CreateRoomDialog extends React.Component<IProps, IState> {
if (this.state.nameIsValid && (!this.aliasField.current || this.aliasField.current.isValid)) { if (this.state.nameIsValid && (!this.aliasField.current || this.aliasField.current.isValid)) {
this.props.onFinished(true, this.roomCreateOptions()); this.props.onFinished(true, this.roomCreateOptions());
} else { } else {
let field; let field: RoomAliasField | Field | null = null;
if (!this.state.nameIsValid) { if (!this.state.nameIsValid) {
field = this.nameField.current; field = this.nameField.current;
} else if (this.aliasField.current && !this.aliasField.current.isValid) { } else if (this.aliasField.current && !this.aliasField.current.isValid) {
@ -163,7 +162,7 @@ export default class CreateRoomDialog extends React.Component<IProps, IState> {
} }
if (field) { if (field) {
field.focus(); field.focus();
field.validate({ allowEmpty: false, focused: true }); await field.validate({ allowEmpty: false, focused: true });
} }
} }
}; };
@ -202,7 +201,7 @@ export default class CreateRoomDialog extends React.Component<IProps, IState> {
private onNameValidate = async (fieldState: IFieldState): Promise<IValidationResult> => { private onNameValidate = async (fieldState: IFieldState): Promise<IValidationResult> => {
const result = await CreateRoomDialog.validateRoomName(fieldState); const result = await CreateRoomDialog.validateRoomName(fieldState);
this.setState({ nameIsValid: result.valid }); this.setState({ nameIsValid: !!result.valid });
return result; return result;
}; };
@ -219,9 +218,9 @@ export default class CreateRoomDialog extends React.Component<IProps, IState> {
public render(): React.ReactNode { public render(): React.ReactNode {
const isVideoRoom = this.props.type === RoomType.ElementVideo; const isVideoRoom = this.props.type === RoomType.ElementVideo;
let aliasField: JSX.Element; let aliasField: JSX.Element | undefined;
if (this.state.joinRule === JoinRule.Public) { if (this.state.joinRule === JoinRule.Public) {
const domain = MatrixClientPeg.get().getDomain(); const domain = MatrixClientPeg.get().getDomain()!;
aliasField = ( aliasField = (
<div className="mx_CreateRoomDialog_aliasContainer"> <div className="mx_CreateRoomDialog_aliasContainer">
<RoomAliasField <RoomAliasField
@ -234,7 +233,7 @@ export default class CreateRoomDialog extends React.Component<IProps, IState> {
); );
} }
let publicPrivateLabel: JSX.Element; let publicPrivateLabel: JSX.Element | undefined;
if (this.state.joinRule === JoinRule.Restricted) { if (this.state.joinRule === JoinRule.Restricted) {
publicPrivateLabel = ( publicPrivateLabel = (
<p> <p>
@ -242,7 +241,7 @@ export default class CreateRoomDialog extends React.Component<IProps, IState> {
"Everyone in <SpaceName/> will be able to find and join this room.", "Everyone in <SpaceName/> will be able to find and join this room.",
{}, {},
{ {
SpaceName: () => <b>{this.props.parentSpace.name}</b>, SpaceName: () => <b>{this.props.parentSpace?.name ?? _t("Unnamed Space")}</b>,
}, },
)} )}
&nbsp; &nbsp;
@ -256,7 +255,7 @@ export default class CreateRoomDialog extends React.Component<IProps, IState> {
"Anyone will be able to find and join this room, not just members of <SpaceName/>.", "Anyone will be able to find and join this room, not just members of <SpaceName/>.",
{}, {},
{ {
SpaceName: () => <b>{this.props.parentSpace.name}</b>, SpaceName: () => <b>{this.props.parentSpace?.name ?? _t("Unnamed Space")}</b>,
}, },
)} )}
&nbsp; &nbsp;
@ -281,7 +280,7 @@ export default class CreateRoomDialog extends React.Component<IProps, IState> {
); );
} }
let e2eeSection: JSX.Element; let e2eeSection: JSX.Element | undefined;
if (this.state.joinRule !== JoinRule.Public) { if (this.state.joinRule !== JoinRule.Public) {
let microcopy: string; let microcopy: string;
if (privateShouldBeEncrypted()) { if (privateShouldBeEncrypted()) {

View file

@ -44,7 +44,7 @@ const CreateSubspaceDialog: React.FC<IProps> = ({ space, onAddExistingSpaceClick
const spaceNameField = useRef<Field>(); const spaceNameField = useRef<Field>();
const [alias, setAlias] = useState(""); const [alias, setAlias] = useState("");
const spaceAliasField = useRef<RoomAliasField>(); const spaceAliasField = useRef<RoomAliasField>();
const [avatar, setAvatar] = useState<File>(null); const [avatar, setAvatar] = useState<File | undefined>();
const [topic, setTopic] = useState<string>(""); const [topic, setTopic] = useState<string>("");
const spaceJoinRule = space.getJoinRule(); const spaceJoinRule = space.getJoinRule();
@ -56,7 +56,7 @@ const CreateSubspaceDialog: React.FC<IProps> = ({ space, onAddExistingSpaceClick
const onCreateSubspaceClick = async (e: ButtonEvent): Promise<void> => { const onCreateSubspaceClick = async (e: ButtonEvent): Promise<void> => {
e.preventDefault(); e.preventDefault();
if (busy) return; if (busy || !spaceNameField.current || !spaceAliasField.current) return;
setBusy(true); setBusy(true);
// require & validate the space name field // require & validate the space name field
@ -83,7 +83,7 @@ const CreateSubspaceDialog: React.FC<IProps> = ({ space, onAddExistingSpaceClick
} }
}; };
let joinRuleMicrocopy: JSX.Element; let joinRuleMicrocopy: JSX.Element | undefined;
if (joinRule === JoinRule.Restricted) { if (joinRule === JoinRule.Restricted) {
joinRuleMicrocopy = ( joinRuleMicrocopy = (
<p> <p>

View file

@ -73,7 +73,7 @@ const useExportFormState = (): ExportConfig => {
const [exportType, setExportType] = useState(config.range ?? ExportType.Timeline); const [exportType, setExportType] = useState(config.range ?? ExportType.Timeline);
const [includeAttachments, setAttachments] = useState(config.includeAttachments ?? false); const [includeAttachments, setAttachments] = useState(config.includeAttachments ?? false);
const [numberOfMessages, setNumberOfMessages] = useState<number>(config.numberOfMessages ?? 100); const [numberOfMessages, setNumberOfMessages] = useState<number>(config.numberOfMessages ?? 100);
const [sizeLimit, setSizeLimit] = useState<number | null>(config.sizeMb ?? 8); const [sizeLimit, setSizeLimit] = useState<number>(config.sizeMb ?? 8);
return { return {
exportFormat, exportFormat,
@ -260,7 +260,7 @@ const ExportDialog: React.FC<IProps> = ({ room, onFinished }) => {
); );
}); });
let messageCount = null; let messageCount: JSX.Element | undefined;
if (exportType === ExportType.LastNMessages && setNumberOfMessages) { if (exportType === ExportType.LastNMessages && setNumberOfMessages) {
messageCount = ( messageCount = (
<Field <Field

View file

@ -96,7 +96,7 @@ export default class LogoutDialog extends React.Component<IProps, IState> {
dis.dispatch({ action: "logout" }); dis.dispatch({ action: "logout" });
} }
// close dialog // close dialog
this.props.onFinished(confirmed); this.props.onFinished(!!confirmed);
}; };
private onSetRecoveryMethodClick = (): void => { private onSetRecoveryMethodClick = (): void => {

View file

@ -70,7 +70,7 @@ export default class ModalWidgetDialog extends React.PureComponent<IProps, IStat
this.widget = new ElementWidget({ this.widget = new ElementWidget({
...this.props.widgetDefinition, ...this.props.widgetDefinition,
creatorUserId: MatrixClientPeg.get().getUserId(), creatorUserId: MatrixClientPeg.get().getSafeUserId(),
id: `modal_${this.props.sourceWidgetId}`, id: `modal_${this.props.sourceWidgetId}`,
}); });
this.possibleButtons = (this.props.widgetDefinition.buttons || []).map((b) => b.id); this.possibleButtons = (this.props.widgetDefinition.buttons || []).map((b) => b.id);
@ -78,21 +78,23 @@ export default class ModalWidgetDialog extends React.PureComponent<IProps, IStat
public componentDidMount(): void { public componentDidMount(): void {
const driver = new StopGapWidgetDriver([], this.widget, WidgetKind.Modal, false); const driver = new StopGapWidgetDriver([], this.widget, WidgetKind.Modal, false);
const messaging = new ClientWidgetApi(this.widget, this.appFrame.current, driver); const messaging = new ClientWidgetApi(this.widget, this.appFrame.current!, driver);
this.setState({ messaging }); this.setState({ messaging });
} }
public componentWillUnmount(): void { public componentWillUnmount(): void {
if (!this.state.messaging) return;
this.state.messaging.off("ready", this.onReady); this.state.messaging.off("ready", this.onReady);
this.state.messaging.off(`action:${WidgetApiFromWidgetAction.CloseModalWidget}`, this.onWidgetClose); this.state.messaging.off(`action:${WidgetApiFromWidgetAction.CloseModalWidget}`, this.onWidgetClose);
this.state.messaging.stop(); this.state.messaging.stop();
} }
private onReady = (): void => { private onReady = (): void => {
this.state.messaging.sendWidgetConfig(this.props.widgetDefinition); this.state.messaging?.sendWidgetConfig(this.props.widgetDefinition);
}; };
private onLoad = (): void => { private onLoad = (): void => {
if (!this.state.messaging) return;
this.state.messaging.once("ready", this.onReady); this.state.messaging.once("ready", this.onReady);
this.state.messaging.on(`action:${WidgetApiFromWidgetAction.CloseModalWidget}`, this.onWidgetClose); this.state.messaging.on(`action:${WidgetApiFromWidgetAction.CloseModalWidget}`, this.onWidgetClose);
this.state.messaging.on(`action:${WidgetApiFromWidgetAction.SetModalButtonEnabled}`, this.onButtonEnableToggle); this.state.messaging.on(`action:${WidgetApiFromWidgetAction.SetModalButtonEnabled}`, this.onButtonEnableToggle);
@ -106,7 +108,7 @@ export default class ModalWidgetDialog extends React.PureComponent<IProps, IStat
ev.preventDefault(); ev.preventDefault();
const isClose = ev.detail.data.button === BuiltInModalButtonID.Close; const isClose = ev.detail.data.button === BuiltInModalButtonID.Close;
if (isClose || !this.possibleButtons.includes(ev.detail.data.button)) { if (isClose || !this.possibleButtons.includes(ev.detail.data.button)) {
return this.state.messaging.transport.reply(ev.detail, { return this.state.messaging?.transport.reply(ev.detail, {
error: { message: "Invalid button" }, error: { message: "Invalid button" },
} as IWidgetApiErrorResponseData); } as IWidgetApiErrorResponseData);
} }
@ -121,15 +123,15 @@ export default class ModalWidgetDialog extends React.PureComponent<IProps, IStat
buttonIds = Array.from(tempSet); buttonIds = Array.from(tempSet);
} }
this.setState({ disabledButtonIds: buttonIds }); this.setState({ disabledButtonIds: buttonIds });
this.state.messaging.transport.reply(ev.detail, {} as IWidgetApiAcknowledgeResponseData); this.state.messaging?.transport.reply(ev.detail, {} as IWidgetApiAcknowledgeResponseData);
}; };
public render(): React.ReactNode { public render(): React.ReactNode {
const templated = this.widget.getCompleteUrl({ const templated = this.widget.getCompleteUrl({
widgetRoomId: this.props.widgetRoomId, widgetRoomId: this.props.widgetRoomId,
currentUserId: MatrixClientPeg.get().getUserId(), currentUserId: MatrixClientPeg.get().getSafeUserId(),
userDisplayName: OwnProfileStore.instance.displayName, userDisplayName: OwnProfileStore.instance.displayName ?? undefined,
userHttpAvatarUrl: OwnProfileStore.instance.getHttpAvatarUrl(), userHttpAvatarUrl: OwnProfileStore.instance.getHttpAvatarUrl() ?? undefined,
clientId: ELEMENT_CLIENT_ID, clientId: ELEMENT_CLIENT_ID,
clientTheme: SettingsStore.getValue("theme"), clientTheme: SettingsStore.getValue("theme"),
clientLanguage: getUserLanguage(), clientLanguage: getUserLanguage(),
@ -168,7 +170,7 @@ export default class ModalWidgetDialog extends React.PureComponent<IProps, IStat
} }
const onClick = (): void => { const onClick = (): void => {
this.state.messaging.notifyModalWidgetButtonClicked(def.id); this.state.messaging?.notifyModalWidgetButtonClicked(def.id);
}; };
const isDisabled = this.state.disabledButtonIds.includes(def.id); const isDisabled = this.state.disabledButtonIds.includes(def.id);
@ -201,7 +203,7 @@ export default class ModalWidgetDialog extends React.PureComponent<IProps, IStat
</div> </div>
<div> <div>
<iframe <iframe
title={this.widget.name} title={this.widget.name ?? undefined}
ref={this.appFrame} ref={this.appFrame}
sandbox="allow-forms allow-scripts allow-same-origin" sandbox="allow-forms allow-scripts allow-same-origin"
src={widgetUrl} src={widgetUrl}

View file

@ -99,8 +99,8 @@ export default class ReportEventDialog extends React.Component<IProps, IState> {
public constructor(props: IProps) { public constructor(props: IProps) {
super(props); super(props);
let moderatedByRoomId = null; let moderatedByRoomId: string | null = null;
let moderatedByUserId = null; let moderatedByUserId: string | null = null;
if (SettingsStore.getValue("feature_report_to_moderators")) { if (SettingsStore.getValue("feature_report_to_moderators")) {
// The client supports reporting to moderators. // The client supports reporting to moderators.
@ -111,7 +111,7 @@ export default class ReportEventDialog extends React.Component<IProps, IState> {
const room = client.getRoom(props.mxEvent.getRoomId()); const room = client.getRoom(props.mxEvent.getRoomId());
for (const stateEventType of MODERATED_BY_STATE_EVENT_TYPE) { for (const stateEventType of MODERATED_BY_STATE_EVENT_TYPE) {
const stateEvent = room.currentState.getStateEvents(stateEventType, stateEventType); const stateEvent = room?.currentState.getStateEvents(stateEventType, stateEventType);
if (!stateEvent) { if (!stateEvent) {
continue; continue;
} }
@ -177,9 +177,9 @@ export default class ReportEventDialog extends React.Component<IProps, IState> {
// A free-form text describing the abuse. // A free-form text describing the abuse.
reason: "", reason: "",
busy: false, busy: false,
err: null, err: undefined,
// If specified, the nature of the abuse, as specified by MSC3215. // If specified, the nature of the abuse, as specified by MSC3215.
nature: null, nature: undefined,
ignoreUserToo: false, // default false, for now. Could easily be argued as default true ignoreUserToo: false, // default false, for now. Could easily be argued as default true
}; };
} }
@ -233,14 +233,14 @@ export default class ReportEventDialog extends React.Component<IProps, IState> {
this.setState({ this.setState({
busy: true, busy: true,
err: null, err: undefined,
}); });
try { try {
const client = MatrixClientPeg.get(); const client = MatrixClientPeg.get();
const ev = this.props.mxEvent; const ev = this.props.mxEvent;
if (this.moderation && this.state.nature !== NonStandardValue.Admin) { if (this.moderation && this.state.nature !== NonStandardValue.Admin) {
const nature: Nature = this.state.nature; const nature = this.state.nature;
// Report to moderators through to the dedicated bot, // Report to moderators through to the dedicated bot,
// as configured in the room's state events. // as configured in the room's state events.
@ -274,12 +274,12 @@ export default class ReportEventDialog extends React.Component<IProps, IState> {
}; };
public render(): React.ReactNode { public render(): React.ReactNode {
let error = null; let error: JSX.Element | undefined;
if (this.state.err) { if (this.state.err) {
error = <div className="error">{this.state.err}</div>; error = <div className="error">{this.state.err}</div>;
} }
let progress = null; let progress: JSX.Element | undefined;
if (this.state.busy) { if (this.state.busy) {
progress = ( progress = (
<div className="progress"> <div className="progress">
@ -299,7 +299,7 @@ export default class ReportEventDialog extends React.Component<IProps, IState> {
); );
const adminMessageMD = SdkConfig.getObject("report_event")?.get("admin_message_md", "adminMessageMD"); const adminMessageMD = SdkConfig.getObject("report_event")?.get("admin_message_md", "adminMessageMD");
let adminMessage; let adminMessage: JSX.Element | undefined;
if (adminMessageMD) { if (adminMessageMD) {
const html = new Markdown(adminMessageMD).toHTML({ externalLinks: true }); const html = new Markdown(adminMessageMD).toHTML({ externalLinks: true });
adminMessage = <p dangerouslySetInnerHTML={{ __html: html }} />; adminMessage = <p dangerouslySetInnerHTML={{ __html: html }} />;

View file

@ -42,16 +42,20 @@ interface IProps {
onFinished(opts?: IFinishedOpts): void; onFinished(opts?: IFinishedOpts): void;
} }
interface Progress {
text: string;
progress: number;
total: number;
}
interface IState { interface IState {
inviteUsersToNewRoom: boolean; inviteUsersToNewRoom: boolean;
progressText?: string; progress?: Progress;
progress?: number;
total?: number;
} }
export default class RoomUpgradeWarningDialog extends React.Component<IProps, IState> { export default class RoomUpgradeWarningDialog extends React.Component<IProps, IState> {
private readonly isPrivate: boolean; private readonly isPrivate: boolean;
private readonly currentVersion: string; private readonly currentVersion?: string;
public constructor(props: IProps) { public constructor(props: IProps) {
super(props); super(props);
@ -66,8 +70,14 @@ export default class RoomUpgradeWarningDialog extends React.Component<IProps, IS
}; };
} }
private onProgressCallback = (progressText: string, progress: number, total: number): void => { private onProgressCallback = (text: string, progress: number, total: number): void => {
this.setState({ progressText, progress, total }); this.setState({
progress: {
text,
progress,
total,
},
});
}; };
private onContinue = async (): Promise<void> => { private onContinue = async (): Promise<void> => {
@ -98,7 +108,7 @@ export default class RoomUpgradeWarningDialog extends React.Component<IProps, IS
public render(): React.ReactNode { public render(): React.ReactNode {
const brand = SdkConfig.get().brand; const brand = SdkConfig.get().brand;
let inviteToggle = null; let inviteToggle: JSX.Element | undefined;
if (this.isPrivate) { if (this.isPrivate) {
inviteToggle = ( inviteToggle = (
<LabelledToggleSwitch <LabelledToggleSwitch
@ -144,11 +154,11 @@ export default class RoomUpgradeWarningDialog extends React.Component<IProps, IS
} }
let footer: JSX.Element; let footer: JSX.Element;
if (this.state.progressText) { if (this.state.progress) {
footer = ( footer = (
<span className="mx_RoomUpgradeWarningDialog_progress"> <span className="mx_RoomUpgradeWarningDialog_progress">
<ProgressBar value={this.state.progress} max={this.state.total} /> <ProgressBar value={this.state.progress.progress} max={this.state.progress.total} />
<div className="mx_RoomUpgradeWarningDialog_progressText">{this.state.progressText}</div> <div className="mx_RoomUpgradeWarningDialog_progressText">{this.state.progress.text}</div>
</span> </span>
); );
} else { } else {

View file

@ -96,7 +96,7 @@ export const SlidingSyncOptionsDialog: React.FC<{ onFinished(enabled: boolean):
rules: [ rules: [
{ {
key: "required", key: "required",
test: async ({ value }) => !!value || hasNativeSupport, test: async ({ value }) => !!value || !!hasNativeSupport,
invalid: () => _t("Your server lacks native support, you must specify a proxy"), invalid: () => _t("Your server lacks native support, you must specify a proxy"),
}, },
{ {
@ -104,7 +104,7 @@ export const SlidingSyncOptionsDialog: React.FC<{ onFinished(enabled: boolean):
final: true, final: true,
test: async (_, { error }) => !error, test: async (_, { error }) => !error,
valid: () => _t("Looks good"), valid: () => _t("Looks good"),
invalid: ({ error }) => error?.message, invalid: ({ error }) => error?.message ?? null,
}, },
], ],
}); });

View file

@ -23,14 +23,14 @@ import BaseDialog from "./BaseDialog";
import DialogButtons from "../elements/DialogButtons"; import DialogButtons from "../elements/DialogButtons";
interface IProps { interface IProps {
title?: string; title: string;
description?: React.ReactNode; description: React.ReactNode;
value?: string; value: string;
placeholder?: string; placeholder?: string;
button?: string; button?: string;
busyMessage?: string; // pass _td string busyMessage: string; // pass _td string
focus?: boolean; focus: boolean;
hasCancel?: boolean; hasCancel: boolean;
validator?: (fieldState: IFieldState) => Promise<IValidationResult>; // result of withValidation validator?: (fieldState: IFieldState) => Promise<IValidationResult>; // result of withValidation
fixedWidth?: boolean; fixedWidth?: boolean;
onFinished(ok?: boolean, text?: string): void; onFinished(ok?: boolean, text?: string): void;
@ -68,12 +68,13 @@ export default class TextInputDialog extends React.Component<IProps, IState> {
if (this.props.focus) { if (this.props.focus) {
// Set the cursor at the end of the text input // Set the cursor at the end of the text input
// this._field.current.value = this.props.value; // this._field.current.value = this.props.value;
this.field.current.focus(); this.field.current?.focus();
} }
} }
private onOk = async (ev: React.FormEvent): Promise<void> => { private onOk = async (ev: React.FormEvent): Promise<void> => {
ev.preventDefault(); ev.preventDefault();
if (!this.field.current) return;
if (this.props.validator) { if (this.props.validator) {
this.setState({ busy: true }); this.setState({ busy: true });
await this.field.current.validate({ allowEmpty: false }); await this.field.current.validate({ allowEmpty: false });
@ -101,7 +102,7 @@ export default class TextInputDialog extends React.Component<IProps, IState> {
private onValidate = async (fieldState: IFieldState): Promise<IValidationResult> => { private onValidate = async (fieldState: IFieldState): Promise<IValidationResult> => {
const result = await this.props.validator(fieldState); const result = await this.props.validator(fieldState);
this.setState({ this.setState({
valid: result.valid, valid: !!result.valid,
}); });
return result; return result;
}; };

View file

@ -26,8 +26,8 @@ import DialogButtons from "../elements/DialogButtons";
interface IProps { interface IProps {
file: File; file: File;
currentIndex?: number; currentIndex: number;
totalFiles?: number; totalFiles: number;
onFinished: (uploadConfirmed: boolean, uploadAll?: boolean) => void; onFinished: (uploadConfirmed: boolean, uploadAll?: boolean) => void;
} }
@ -37,6 +37,7 @@ export default class UploadConfirmDialog extends React.Component<IProps> {
public static defaultProps: Partial<IProps> = { public static defaultProps: Partial<IProps> = {
totalFiles: 1, totalFiles: 1,
currentIndex: 0,
}; };
public constructor(props: IProps) { public constructor(props: IProps) {
@ -77,8 +78,8 @@ export default class UploadConfirmDialog extends React.Component<IProps> {
} }
const fileId = `mx-uploadconfirmdialog-${this.props.file.name}`; const fileId = `mx-uploadconfirmdialog-${this.props.file.name}`;
let preview: JSX.Element; let preview: JSX.Element | undefined;
let placeholder: JSX.Element; let placeholder: JSX.Element | undefined;
if (this.mimeType.startsWith("image/")) { if (this.mimeType.startsWith("image/")) {
preview = ( preview = (
<img className="mx_UploadConfirmDialog_imagePreview" src={this.objectUrl} aria-labelledby={fileId} /> <img className="mx_UploadConfirmDialog_imagePreview" src={this.objectUrl} aria-labelledby={fileId} />
@ -96,7 +97,7 @@ export default class UploadConfirmDialog extends React.Component<IProps> {
placeholder = <FileIcon className="mx_UploadConfirmDialog_fileIcon" height={18} width={18} />; placeholder = <FileIcon className="mx_UploadConfirmDialog_fileIcon" height={18} width={18} />;
} }
let uploadAllButton; let uploadAllButton: JSX.Element | undefined;
if (this.props.currentIndex + 1 < this.props.totalFiles) { if (this.props.currentIndex + 1 < this.props.totalFiles) {
uploadAllButton = <button onClick={this.onUploadAllClick}>{_t("Upload all")}</button>; uploadAllButton = <button onClick={this.onUploadAllClick}>{_t("Upload all")}</button>;
} }

View file

@ -52,19 +52,19 @@ interface IProps {
} }
interface IState { interface IState {
backupInfo: IKeyBackupInfo; backupInfo: IKeyBackupInfo | null;
backupKeyStored: Record<string, ISecretStorageKeyInfo>; backupKeyStored: Record<string, ISecretStorageKeyInfo> | null;
loading: boolean; loading: boolean;
loadError: string; loadError: string | null;
restoreError: { restoreError: {
errcode: string; errcode: string;
}; } | null;
recoveryKey: string; recoveryKey: string;
recoverInfo: IKeyBackupRestoreResult; recoverInfo: IKeyBackupRestoreResult | null;
recoveryKeyValid: boolean; recoveryKeyValid: boolean;
forceRecoveryKey: boolean; forceRecoveryKey: boolean;
passPhrase: string; passPhrase: string;
restoreType: RestoreType; restoreType: RestoreType | null;
progress: { progress: {
stage: ProgressState; stage: ProgressState;
total?: number; total?: number;
@ -247,7 +247,7 @@ export default class RestoreKeyBackupDialog extends React.PureComponent<IProps,
} }
} }
private async restoreWithCachedKey(backupInfo?: IKeyBackupInfo): Promise<boolean> { private async restoreWithCachedKey(backupInfo: IKeyBackupInfo | null): Promise<boolean> {
if (!backupInfo) return false; if (!backupInfo) return false;
try { try {
const recoverInfo = await MatrixClientPeg.get().restoreKeyBackupWithCache( const recoverInfo = await MatrixClientPeg.get().restoreKeyBackupWithCache(
@ -275,7 +275,7 @@ export default class RestoreKeyBackupDialog extends React.PureComponent<IProps,
const cli = MatrixClientPeg.get(); const cli = MatrixClientPeg.get();
const backupInfo = await cli.getKeyBackupVersion(); const backupInfo = await cli.getKeyBackupVersion();
const has4S = await cli.hasSecretStorageKey(); const has4S = await cli.hasSecretStorageKey();
const backupKeyStored = has4S && (await cli.isKeyBackupKeyStored()); const backupKeyStored = has4S ? await cli.isKeyBackupKeyStored() : null;
this.setState({ this.setState({
backupInfo, backupInfo,
backupKeyStored, backupKeyStored,

View file

@ -685,7 +685,7 @@ const SpotlightDialog: React.FC<IProps> = ({ initialText = "", initialFilter = n
<Option <Option
id={`mx_SpotlightDialog_button_result_${result.name}`} id={`mx_SpotlightDialog_button_result_${result.name}`}
key={`${Section[result.section]}-${result.name}`} key={`${Section[result.section]}-${result.name}`}
onClick={result.onClick} onClick={result.onClick ?? null}
> >
{result.avatar} {result.avatar}
{result.name} {result.name}

View file

@ -141,7 +141,7 @@ function useHasCrossSigningKeys(
member: User, member: User,
canVerify: boolean, canVerify: boolean,
setUpdating: SetUpdating, setUpdating: SetUpdating,
): boolean { ): boolean | undefined {
return useAsyncMemo(async () => { return useAsyncMemo(async () => {
if (!canVerify) { if (!canVerify) {
return undefined; return undefined;
@ -196,7 +196,7 @@ export function DeviceItem({ userId, device }: { userId: string; device: IDevice
: device.getDisplayName(); : device.getDisplayName();
} }
let trustedLabel = null; let trustedLabel: string | undefined;
if (userTrust.isVerified()) trustedLabel = isVerified ? _t("Trusted") : _t("Not trusted"); if (userTrust.isVerified()) trustedLabel = isVerified ? _t("Trusted") : _t("Not trusted");
if (isVerified) { if (isVerified) {
@ -343,13 +343,12 @@ export const UserOptionsSection: React.FC<{
}> = ({ member, isIgnored, canInvite, isSpace }) => { }> = ({ member, isIgnored, canInvite, isSpace }) => {
const cli = useContext(MatrixClientContext); const cli = useContext(MatrixClientContext);
let ignoreButton = null; let ignoreButton: JSX.Element | undefined;
let insertPillButton = null; let insertPillButton: JSX.Element | undefined;
let inviteUserButton = null; let inviteUserButton: JSX.Element | undefined;
let readReceiptButton = null; let readReceiptButton: JSX.Element | undefined;
const isMe = member.userId === cli.getUserId(); const isMe = member.userId === cli.getUserId();
const onShareUserClick = (): void => { const onShareUserClick = (): void => {
Modal.createDialog(ShareDialog, { Modal.createDialog(ShareDialog, {
target: member, target: member,
@ -517,7 +516,7 @@ const warnSelfDemote = async (isSpace: boolean): Promise<boolean> => {
}); });
const [confirmed] = await finished; const [confirmed] = await finished;
return confirmed; return !!confirmed;
}; };
const GenericAdminToolsContainer: React.FC<{ const GenericAdminToolsContainer: React.FC<{
@ -600,7 +599,7 @@ export const RoomKickButton = ({
const cli = useContext(MatrixClientContext); const cli = useContext(MatrixClientContext);
// check if user can be kicked/disinvited // check if user can be kicked/disinvited
if (member.membership !== "invite" && member.membership !== "join") return null; if (member.membership !== "invite" && member.membership !== "join") return <></>;
const onKick = async (): Promise<void> => { const onKick = async (): Promise<void> => {
const commonProps = { const commonProps = {
@ -633,8 +632,8 @@ export const RoomKickButton = ({
const myMember = child.getMember(cli.credentials.userId || ""); const myMember = child.getMember(cli.credentials.userId || "");
const theirMember = child.getMember(member.userId); const theirMember = child.getMember(member.userId);
return ( return (
myMember && !!myMember &&
theirMember && !!theirMember &&
theirMember.membership === member.membership && theirMember.membership === member.membership &&
myMember.powerLevel > theirMember.powerLevel && myMember.powerLevel > theirMember.powerLevel &&
child.currentState.hasSufficientPowerLevelFor("kick", myMember.powerLevel) child.currentState.hasSufficientPowerLevelFor("kick", myMember.powerLevel)
@ -755,8 +754,8 @@ export const BanToggleButton = ({
const myMember = child.getMember(cli.credentials.userId || ""); const myMember = child.getMember(cli.credentials.userId || "");
const theirMember = child.getMember(member.userId); const theirMember = child.getMember(member.userId);
return ( return (
myMember && !!myMember &&
theirMember && !!theirMember &&
theirMember.membership === "ban" && theirMember.membership === "ban" &&
myMember.powerLevel > theirMember.powerLevel && myMember.powerLevel > theirMember.powerLevel &&
child.currentState.hasSufficientPowerLevelFor("ban", myMember.powerLevel) child.currentState.hasSufficientPowerLevelFor("ban", myMember.powerLevel)
@ -1555,7 +1554,7 @@ export const UserInfoHeader: React.FC<{
showPresence = enablePresenceByHsUrl[cli.baseUrl]; showPresence = enablePresenceByHsUrl[cli.baseUrl];
} }
let presenceLabel = null; let presenceLabel: JSX.Element | undefined;
if (showPresence) { if (showPresence) {
presenceLabel = ( presenceLabel = (
<PresenceLabel <PresenceLabel
@ -1614,7 +1613,7 @@ const UserInfo: React.FC<IProps> = ({ user, room, onClose, phase = RightPanelPha
const isRoomEncrypted = useIsEncrypted(cli, room); const isRoomEncrypted = useIsEncrypted(cli, room);
const devices = useDevices(user.userId); const devices = useDevices(user.userId);
let e2eStatus; let e2eStatus: E2EStatus | undefined;
if (isRoomEncrypted && devices) { if (isRoomEncrypted && devices) {
e2eStatus = getE2EStatus(cli, user.userId, devices); e2eStatus = getE2EStatus(cli, user.userId, devices);
} }
@ -1659,7 +1658,7 @@ const UserInfo: React.FC<IProps> = ({ user, room, onClose, phase = RightPanelPha
break; break;
} }
let closeLabel = undefined; let closeLabel: string | undefined;
if (phase === RightPanelPhases.EncryptionPanel) { if (phase === RightPanelPhases.EncryptionPanel) {
const verificationRequest = (props as React.ComponentProps<typeof EncryptionPanel>).verificationRequest; const verificationRequest = (props as React.ComponentProps<typeof EncryptionPanel>).verificationRequest;
if (verificationRequest && verificationRequest.pending) { if (verificationRequest && verificationRequest.pending) {

View file

@ -61,7 +61,7 @@ export const SpaceAvatar: React.FC<Pick<IProps, "avatarUrl" | "avatarDisabled" |
/> />
<AccessibleButton <AccessibleButton
onClick={() => { onClick={() => {
avatarUploadRef.current.value = ""; if (avatarUploadRef.current) avatarUploadRef.current.value = "";
setAvatarDataUrl(undefined); setAvatarDataUrl(undefined);
setAvatar(undefined); setAvatar(undefined);
}} }}

View file

@ -42,7 +42,7 @@ const getForceChatExportParameters = (): ForceChatExportParameters => {
// them all as optional. This allows customisers to only define and export the // them all as optional. This allows customisers to only define and export the
// customisations they need while still maintaining type safety. // customisations they need while still maintaining type safety.
export interface IChatExportCustomisations { export interface IChatExportCustomisations {
getForceChatExportParameters?: typeof getForceChatExportParameters; getForceChatExportParameters: typeof getForceChatExportParameters;
} }
// A real customisation module will define and export one or more of the // A real customisation module will define and export one or more of the

View file

@ -2736,6 +2736,7 @@
"Clear all data": "Clear all data", "Clear all data": "Clear all data",
"Please enter a name for the room": "Please enter a name for the room", "Please enter a name for the room": "Please enter a name for the room",
"Everyone in <SpaceName/> will be able to find and join this room.": "Everyone in <SpaceName/> will be able to find and join this room.", "Everyone in <SpaceName/> will be able to find and join this room.": "Everyone in <SpaceName/> will be able to find and join this room.",
"Unnamed Space": "Unnamed Space",
"You can change this at any time from room settings.": "You can change this at any time from room settings.", "You can change this at any time from room settings.": "You can change this at any time from room settings.",
"Anyone will be able to find and join this room, not just members of <SpaceName/>.": "Anyone will be able to find and join this room, not just members of <SpaceName/>.", "Anyone will be able to find and join this room, not just members of <SpaceName/>.": "Anyone will be able to find and join this room, not just members of <SpaceName/>.",
"Anyone will be able to find and join this room.": "Anyone will be able to find and join this room.", "Anyone will be able to find and join this room.": "Anyone will be able to find and join this room.",
@ -2871,7 +2872,6 @@
"Some suggestions may be hidden for privacy.": "Some suggestions may be hidden for privacy.", "Some suggestions may be hidden for privacy.": "Some suggestions may be hidden for privacy.",
"If you can't see who you're looking for, send them your invite link below.": "If you can't see who you're looking for, send them your invite link below.", "If you can't see who you're looking for, send them your invite link below.": "If you can't see who you're looking for, send them your invite link below.",
"Or send invite link": "Or send invite link", "Or send invite link": "Or send invite link",
"Unnamed Space": "Unnamed Space",
"Invite to %(roomName)s": "Invite to %(roomName)s", "Invite to %(roomName)s": "Invite to %(roomName)s",
"Invite someone using their name, email address, username (like <userId/>) or <a>share this space</a>.": "Invite someone using their name, email address, username (like <userId/>) or <a>share this space</a>.", "Invite someone using their name, email address, username (like <userId/>) or <a>share this space</a>.": "Invite someone using their name, email address, username (like <userId/>) or <a>share this space</a>.",
"Invite someone using their name, username (like <userId/>) or <a>share this space</a>.": "Invite someone using their name, username (like <userId/>) or <a>share this space</a>.", "Invite someone using their name, username (like <userId/>) or <a>share this space</a>.": "Invite someone using their name, username (like <userId/>) or <a>share this space</a>.",

View file

@ -214,7 +214,7 @@ async function collectBugReport(opts: IOpts = {}, gzipLogs = true): Promise<Form
* *
* @return {Promise<string>} URL returned by the rageshake server * @return {Promise<string>} URL returned by the rageshake server
*/ */
export default async function sendBugReport(bugReportEndpoint: string, opts: IOpts = {}): Promise<string> { export default async function sendBugReport(bugReportEndpoint?: string, opts: IOpts = {}): Promise<string> {
if (!bugReportEndpoint) { if (!bugReportEndpoint) {
throw new Error("No bug report endpoint has been set."); throw new Error("No bug report endpoint has been set.");
} }

View file

@ -177,7 +177,7 @@ async function getContexts(): Promise<Contexts> {
}; };
} }
export async function sendSentryReport(userText: string, issueUrl: string, error: Error): Promise<void> { export async function sendSentryReport(userText: string, issueUrl: string, error?: Error): Promise<void> {
const sentryConfig = SdkConfig.getObject("sentry"); const sentryConfig = SdkConfig.getObject("sentry");
if (!sentryConfig) return; if (!sentryConfig) return;

View file

@ -76,14 +76,14 @@ export function pillifyLinks(nodes: ArrayLike<Element>, mxEvent: MatrixEvent, pi
// to clear the pills from the last run of pillifyLinks // to clear the pills from the last run of pillifyLinks
!node.parentElement.classList.contains("mx_AtRoomPill") !node.parentElement.classList.contains("mx_AtRoomPill")
) { ) {
let currentTextNode = node as Node as Text; let currentTextNode = node as Node as Text | null;
const roomNotifTextNodes = []; const roomNotifTextNodes = [];
// Take a textNode and break it up to make all the instances of @room their // Take a textNode and break it up to make all the instances of @room their
// own textNode, adding those nodes to roomNotifTextNodes // own textNode, adding those nodes to roomNotifTextNodes
while (currentTextNode !== null) { while (currentTextNode !== null) {
const roomNotifPos = pillRoomNotifPos(currentTextNode.textContent); const roomNotifPos = pillRoomNotifPos(currentTextNode.textContent);
let nextTextNode = null; let nextTextNode: Text | null = null;
if (roomNotifPos > -1) { if (roomNotifPos > -1) {
let roomTextNode = currentTextNode; let roomTextNode = currentTextNode;

View file

@ -793,7 +793,7 @@ describe("<RoomKickButton />", () => {
}, },
}; };
expect(callback(mockRoom)).toBe(null); expect(callback(mockRoom)).toBe(false);
expect(callback(mockRoom)).toBe(true); expect(callback(mockRoom)).toBe(true);
}); });
}); });
@ -915,7 +915,7 @@ describe("<BanToggleButton />", () => {
}, },
}; };
expect(callback(mockRoom)).toBe(null); expect(callback(mockRoom)).toBe(false);
expect(callback(mockRoom)).toBe(true); expect(callback(mockRoom)).toBe(true);
}); });
}); });