Conform more of the codebase to strictNullChecks (#10505

* Conform more of the codebase to `strictNullChecks`

* Iterate

* Conform more of the codebase to `strictNullChecks`

* Iterate

* Iterate

* Iterate

* Iterate
pull/28788/head^2
Michael Telatynski 2023-04-05 09:02:40 +01:00 committed by GitHub
parent 7503bf6b96
commit e5a314617a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 76 additions and 83 deletions

View File

@ -332,7 +332,7 @@ class MatrixClientPegClass implements IMatrixClientPeg {
homeserverUrl: this.matrixClient.baseUrl, homeserverUrl: this.matrixClient.baseUrl,
identityServerUrl: this.matrixClient.idBaseUrl, identityServerUrl: this.matrixClient.idBaseUrl,
userId: this.matrixClient.credentials.userId, userId: this.matrixClient.getSafeUserId(),
deviceId: this.matrixClient.getDeviceId() ?? undefined, deviceId: this.matrixClient.getDeviceId() ?? undefined,
accessToken: this.matrixClient.getAccessToken(), accessToken: this.matrixClient.getAccessToken(),
guest: this.matrixClient.isGuest(), guest: this.matrixClient.isGuest(),
@ -340,7 +340,7 @@ class MatrixClientPegClass implements IMatrixClientPeg {
} }
public getHomeserverName(): string { public getHomeserverName(): string {
const matches = /^@[^:]+:(.+)$/.exec(this.matrixClient.credentials.userId); const matches = /^@[^:]+:(.+)$/.exec(this.matrixClient.getSafeUserId());
if (matches === null || matches.length < 1) { if (matches === null || matches.length < 1) {
throw new Error("Failed to derive homeserver name from user ID!"); throw new Error("Failed to derive homeserver name from user ID!");
} }

View File

@ -102,7 +102,7 @@ const Tile: React.FC<ITileProps> = ({
}) => { }) => {
const cli = useContext(MatrixClientContext); const cli = useContext(MatrixClientContext);
const [joinedRoom, setJoinedRoom] = useState<Room | undefined>(() => { 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 : undefined; return cliRoom?.getMyMembership() === "join" ? cliRoom : undefined;
}); });
const joinedRoomName = useTypedEventEmitterState(joinedRoom, RoomEvent.Name, (room) => room?.name); const joinedRoomName = useTypedEventEmitterState(joinedRoom, RoomEvent.Name, (room) => room?.name);

View File

@ -30,7 +30,6 @@ import { ClientEvent } from "matrix-js-sdk/src/client";
import { Thread, ThreadEvent } from "matrix-js-sdk/src/models/thread"; import { Thread, ThreadEvent } from "matrix-js-sdk/src/models/thread";
import { ReceiptType } from "matrix-js-sdk/src/@types/read_receipts"; import { ReceiptType } from "matrix-js-sdk/src/@types/read_receipts";
import { MatrixError } from "matrix-js-sdk/src/http-api"; import { MatrixError } from "matrix-js-sdk/src/http-api";
import { ReadReceipt } from "matrix-js-sdk/src/models/read-receipt";
import { Relations } from "matrix-js-sdk/src/models/relations"; import { Relations } from "matrix-js-sdk/src/models/relations";
import SettingsStore from "../../settings/SettingsStore"; import SettingsStore from "../../settings/SettingsStore";
@ -515,23 +514,22 @@ class TimelinePanel extends React.Component<IProps, IState> {
if (count > 0) { if (count > 0) {
debuglog("Unpaginating", count, "in direction", dir); debuglog("Unpaginating", count, "in direction", dir);
this.timelineWindow.unpaginate(count, backwards); this.timelineWindow?.unpaginate(count, backwards);
const { events, liveEvents, firstVisibleEventIndex } = this.getEvents(); const { events, liveEvents, firstVisibleEventIndex } = this.getEvents();
this.buildLegacyCallEventGroupers(events); this.buildLegacyCallEventGroupers(events);
const newState: Partial<IState> = { this.setState({
events, events,
liveEvents, liveEvents,
firstVisibleEventIndex, firstVisibleEventIndex,
}; });
// We can now paginate in the unpaginated direction // We can now paginate in the unpaginated direction
if (backwards) { if (backwards) {
newState.canBackPaginate = true; this.setState({ canBackPaginate: true });
} else { } else {
newState.canForwardPaginate = true; this.setState({ canForwardPaginate: true });
} }
this.setState<null>(newState);
} }
}; };
@ -636,6 +634,7 @@ class TimelinePanel extends React.Component<IProps, IState> {
private doManageReadMarkers = debounce( private doManageReadMarkers = debounce(
() => { () => {
const rmPosition = this.getReadMarkerPosition(); const rmPosition = this.getReadMarkerPosition();
if (rmPosition === null) return;
// we hide the read marker when it first comes onto the screen, but if // we hide the read marker when it first comes onto the screen, but if
// it goes back off the top of the screen (presumably because the user // it goes back off the top of the screen (presumably because the user
// clicks on the 'jump to bottom' button), we need to re-enable it. // clicks on the 'jump to bottom' button), we need to re-enable it.
@ -1125,7 +1124,7 @@ class TimelinePanel extends React.Component<IProps, IState> {
return; return;
} }
const lastDisplayedEvent = this.state.events[lastDisplayedIndex]; const lastDisplayedEvent = this.state.events[lastDisplayedIndex];
this.setReadMarker(lastDisplayedEvent.getId(), lastDisplayedEvent.getTs()); this.setReadMarker(lastDisplayedEvent.getId()!, lastDisplayedEvent.getTs());
// the read-marker should become invisible, so that if the user scrolls // the read-marker should become invisible, so that if the user scrolls
// down, they don't see it. // down, they don't see it.
@ -1141,7 +1140,7 @@ class TimelinePanel extends React.Component<IProps, IState> {
// advance the read marker past any events we sent ourselves. // advance the read marker past any events we sent ourselves.
private advanceReadMarkerPastMyEvents(): void { private advanceReadMarkerPastMyEvents(): void {
if (!this.props.manageReadMarkers) return; if (!this.props.manageReadMarkers || !this.timelineWindow) return;
// we call `timelineWindow.getEvents()` rather than using // we call `timelineWindow.getEvents()` rather than using
// `this.state.liveEvents`, because React batches the update to the // `this.state.liveEvents`, because React batches the update to the
@ -1171,7 +1170,7 @@ class TimelinePanel extends React.Component<IProps, IState> {
i--; i--;
const ev = events[i]; const ev = events[i];
this.setReadMarker(ev.getId(), ev.getTs()); this.setReadMarker(ev.getId()!, ev.getTs());
} }
/* jump down to the bottom of this room, where new events are arriving /* jump down to the bottom of this room, where new events are arriving
@ -1280,6 +1279,7 @@ class TimelinePanel extends React.Component<IProps, IState> {
public getReadMarkerPosition = (): number | null => { 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;
if (!this.props.timelineSet.room) return null;
const ret = this.messagePanel.current.getReadMarkerPosition(); const ret = this.messagePanel.current.getReadMarkerPosition();
if (ret !== null) { if (ret !== null) {
@ -1629,7 +1629,7 @@ class TimelinePanel extends React.Component<IProps, IState> {
return 0; return 0;
} }
const userId = cli.credentials.userId; const userId = cli.getSafeUserId();
// get the user's membership at the last event by getting the timeline // get the user's membership at the last event by getting the timeline
// that the event belongs to, and traversing the timeline looking for // that the event belongs to, and traversing the timeline looking for
@ -1648,7 +1648,7 @@ class TimelinePanel extends React.Component<IProps, IState> {
continue; continue;
} }
userMembership = timeline.getState(EventTimeline.FORWARDS).getMember(userId)?.membership ?? "leave"; userMembership = timeline.getState(EventTimeline.FORWARDS)?.getMember(userId)?.membership ?? "leave";
const timelineEvents = timeline.getEvents(); const timelineEvents = timeline.getEvents();
for (let j = timelineEvents.length - 1; j >= 0; j--) { for (let j = timelineEvents.length - 1; j >= 0; j--) {
const event = timelineEvents[j]; const event = timelineEvents[j];
@ -1769,7 +1769,7 @@ class TimelinePanel extends React.Component<IProps, IState> {
for (let i = this.state.liveEvents.length - 1; i >= 0; --i) { for (let i = this.state.liveEvents.length - 1; i >= 0; --i) {
const ev = this.state.liveEvents[i]; const ev = this.state.liveEvents[i];
const node = messagePanel.getNodeForEventId(ev.getId()); const node = messagePanel.getNodeForEventId(ev.getId()!);
const isInView = isNodeInView(node); const isInView = isNodeInView(node);
// when we've reached the first visible event, and the previous // when we've reached the first visible event, and the previous
@ -1829,8 +1829,8 @@ class TimelinePanel extends React.Component<IProps, IState> {
} }
const myUserId = client.getSafeUserId(); const myUserId = client.getSafeUserId();
const receiptStore: ReadReceipt<any, any> = this.props.timelineSet.thread ?? this.props.timelineSet.room; const receiptStore = this.props.timelineSet.thread ?? this.props.timelineSet.room;
return receiptStore?.getEventReadUpTo(myUserId, ignoreSynthesized); return receiptStore?.getEventReadUpTo(myUserId, ignoreSynthesized) ?? null;
} }
private setReadMarker(eventId: string | null, eventTs: number, inhibitSetState = false): void { private setReadMarker(eventId: string | null, eventTs: number, inhibitSetState = false): void {
@ -1924,7 +1924,7 @@ class TimelinePanel extends React.Component<IProps, IState> {
// If the state is PREPARED or CATCHUP, we're still waiting for the js-sdk to sync with // If the state is PREPARED or CATCHUP, we're still waiting for the js-sdk to sync with
// the HS and fetch the latest events, so we are effectively forward paginating. // the HS and fetch the latest events, so we are effectively forward paginating.
const forwardPaginating = const forwardPaginating =
this.state.forwardPaginating || ["PREPARED", "CATCHUP"].includes(this.state.clientSyncState); this.state.forwardPaginating || ["PREPARED", "CATCHUP"].includes(this.state.clientSyncState!);
const events = this.state.firstVisibleEventIndex const events = this.state.firstVisibleEventIndex
? this.state.events.slice(this.state.firstVisibleEventIndex) ? this.state.events.slice(this.state.firstVisibleEventIndex)
: this.state.events; : this.state.events;
@ -1985,7 +1985,7 @@ function serializeEventIdsFromTimelineSets(timelineSets: EventTimelineSet[]): {
// Add a special label when it is the live timeline so we can tell // Add a special label when it is the live timeline so we can tell
// it apart from the others // it apart from the others
const isLiveTimeline = timeline === liveTimeline; const isLiveTimeline = timeline === liveTimeline;
timelineMap[isLiveTimeline ? "liveTimeline" : `${index}`] = timeline.getEvents().map((ev) => ev.getId()); timelineMap[isLiveTimeline ? "liveTimeline" : `${index}`] = timeline.getEvents().map((ev) => ev.getId()!);
}); });
return timelineMap; return timelineMap;

View File

@ -89,6 +89,7 @@ export const RoomGeneralContextMenu: React.FC<RoomGeneralContextMenuProps> = ({
}; };
const onTagRoom = (ev: ButtonEvent, tagId: TagID): void => { const onTagRoom = (ev: ButtonEvent, tagId: TagID): void => {
if (!cli) return;
if (tagId === DefaultTagID.Favourite || tagId === DefaultTagID.LowPriority) { if (tagId === DefaultTagID.Favourite || tagId === DefaultTagID.LowPriority) {
const inverseTag = tagId === DefaultTagID.Favourite ? DefaultTagID.LowPriority : DefaultTagID.Favourite; const inverseTag = tagId === DefaultTagID.Favourite ? DefaultTagID.LowPriority : DefaultTagID.Favourite;
const isApplied = RoomListStore.instance.getTagsForRoom(room).includes(tagId); const isApplied = RoomListStore.instance.getTagsForRoom(room).includes(tagId);

View File

@ -53,7 +53,7 @@ export default class ConfirmAndWaitRedactDialog extends React.PureComponent<IPro
}; };
} }
public onParentFinished = async (proceed: boolean): Promise<void> => { public onParentFinished = async (proceed?: boolean): Promise<void> => {
if (proceed) { if (proceed) {
this.setState({ isRedacting: true }); this.setState({ isRedacting: true });
try { try {

View File

@ -26,7 +26,8 @@ import ErrorDialog from "./ErrorDialog";
import TextInputDialog from "./TextInputDialog"; import TextInputDialog from "./TextInputDialog";
interface IProps { interface IProps {
onFinished: (success: boolean) => void; onFinished(success?: false, reason?: void): void;
onFinished(success: true, reason?: string): void;
} }
/* /*
@ -67,7 +68,7 @@ export function createRedactEventDialog({
Modal.createDialog( Modal.createDialog(
ConfirmRedactDialog, ConfirmRedactDialog,
{ {
onFinished: async (proceed: boolean, reason?: string): Promise<void> => { onFinished: async (proceed, reason): Promise<void> => {
if (!proceed) return; if (!proceed) return;
const cli = MatrixClientPeg.get(); const cli = MatrixClientPeg.get();

View File

@ -72,7 +72,7 @@ const KeySignatureUploadFailedDialog: React.FC<IProps> = ({ failures, source, co
}, [continuation, onFinished]); }, [continuation, onFinished]);
let body; let body;
if (!success && !cancelled && continuation && retry > 0) { if (!success && !cancelled && retry > 0) {
const reason = causes.get(source) || defaultCause; const reason = causes.get(source) || defaultCause;
const brand = SdkConfig.get().brand; const brand = SdkConfig.get().brand;

View File

@ -20,6 +20,7 @@ import { EventType, RelationType } from "matrix-js-sdk/src/@types/event";
import { defer } from "matrix-js-sdk/src/utils"; import { defer } from "matrix-js-sdk/src/utils";
import { logger } from "matrix-js-sdk/src/logger"; import { logger } from "matrix-js-sdk/src/logger";
import { MatrixClient } from "matrix-js-sdk/src/client"; import { MatrixClient } from "matrix-js-sdk/src/client";
import { MatrixError } from "matrix-js-sdk/src/http-api";
import { MatrixClientPeg } from "../../../MatrixClientPeg"; import { MatrixClientPeg } from "../../../MatrixClientPeg";
import { _t } from "../../../languageHandler"; import { _t } from "../../../languageHandler";
@ -37,12 +38,10 @@ interface IProps {
} }
interface IState { interface IState {
originalEvent: MatrixEvent; originalEvent: MatrixEvent | null;
error: { error: MatrixError | null;
errcode: string;
};
events: MatrixEvent[]; events: MatrixEvent[];
nextBatch: string; nextBatch: string | null;
isLoading: boolean; isLoading: boolean;
isTwelveHour: boolean; isTwelveHour: boolean;
} }
@ -65,9 +64,9 @@ export default class MessageEditHistoryDialog extends React.PureComponent<IProps
// bail out on backwards as we only paginate in one direction // bail out on backwards as we only paginate in one direction
return false; return false;
} }
const opts = { from: this.state.nextBatch }; const opts = { from: this.state.nextBatch ?? undefined };
const roomId = this.props.mxEvent.getRoomId(); const roomId = this.props.mxEvent.getRoomId()!;
const eventId = this.props.mxEvent.getId(); const eventId = this.props.mxEvent.getId()!;
const client = MatrixClientPeg.get(); const client = MatrixClientPeg.get();
const { resolve, reject, promise } = defer<boolean>(); const { resolve, reject, promise } = defer<boolean>();
@ -80,7 +79,7 @@ export default class MessageEditHistoryDialog extends React.PureComponent<IProps
if (error.errcode) { if (error.errcode) {
logger.error("fetching /relations failed with error", error); logger.error("fetching /relations failed with error", error);
} }
this.setState({ error }, () => reject(error)); this.setState({ error: error as MatrixError }, () => reject(error));
return promise; return promise;
} }
@ -88,9 +87,9 @@ export default class MessageEditHistoryDialog extends React.PureComponent<IProps
this.locallyRedactEventsIfNeeded(newEvents); this.locallyRedactEventsIfNeeded(newEvents);
this.setState( this.setState(
{ {
originalEvent: this.state.originalEvent || result.originalEvent, originalEvent: this.state.originalEvent ?? result.originalEvent ?? null,
events: this.state.events.concat(newEvents), events: this.state.events.concat(newEvents),
nextBatch: result.nextBatch, nextBatch: result.nextBatch ?? null,
isLoading: false, isLoading: false,
}, },
() => { () => {
@ -105,6 +104,7 @@ export default class MessageEditHistoryDialog extends React.PureComponent<IProps
const roomId = this.props.mxEvent.getRoomId(); const roomId = this.props.mxEvent.getRoomId();
const client = MatrixClientPeg.get(); const client = MatrixClientPeg.get();
const room = client.getRoom(roomId); const room = client.getRoom(roomId);
if (!room) return;
const pendingEvents = room.getPendingEvents(); const pendingEvents = room.getPendingEvents();
for (const e of newEvents) { for (const e of newEvents) {
const pendingRedaction = pendingEvents.find((pe) => { const pendingRedaction = pendingEvents.find((pe) => {
@ -133,7 +133,7 @@ export default class MessageEditHistoryDialog extends React.PureComponent<IProps
if (!lastEvent || wantsDateSeparator(lastEvent.getDate() || undefined, e.getDate() || undefined)) { if (!lastEvent || wantsDateSeparator(lastEvent.getDate() || undefined, e.getDate() || undefined)) {
nodes.push( nodes.push(
<li key={e.getTs() + "~"}> <li key={e.getTs() + "~"}>
<DateSeparator roomId={e.getRoomId()} ts={e.getTs()} /> <DateSeparator roomId={e.getRoomId()!} ts={e.getTs()} />
</li>, </li>,
); );
} }
@ -141,7 +141,7 @@ export default class MessageEditHistoryDialog extends React.PureComponent<IProps
nodes.push( nodes.push(
<EditHistoryMessage <EditHistoryMessage
key={e.getId()} key={e.getId()}
previousEdit={!isBaseEvent ? allEvents[i + 1] : null} previousEdit={!isBaseEvent ? allEvents[i + 1] : undefined}
isBaseEvent={isBaseEvent} isBaseEvent={isBaseEvent}
mxEvent={e} mxEvent={e}
isTwelveHour={this.state.isTwelveHour} isTwelveHour={this.state.isTwelveHour}

View File

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import * as React from "react"; import React, { ReactNode } from "react";
import BaseDialog from "./BaseDialog"; import BaseDialog from "./BaseDialog";
import { _t } from "../../../languageHandler"; import { _t } from "../../../languageHandler";
@ -46,7 +46,7 @@ export default class ServerOfflineDialog extends React.PureComponent<IProps> {
this.forceUpdate(); // no state to worry about this.forceUpdate(); // no state to worry about
}; };
private renderTimeline(): React.ReactElement[] { private renderTimeline(): ReactNode[] {
return EchoStore.instance.contexts.map((c, i) => { return EchoStore.instance.contexts.map((c, i) => {
if (!c.firstFailedTime) return null; // not useful if (!c.firstFailedTime) return null; // not useful
if (!(c instanceof RoomEchoContext)) if (!(c instanceof RoomEchoContext))

View File

@ -124,7 +124,7 @@ export const SlidingSyncOptionsDialog: React.FC<{ onFinished(enabled: boolean):
value={currentProxy} value={currentProxy}
button={_t("Enable")} button={_t("Enable")}
validator={validProxy} validator={validProxy}
onFinished={(enable: boolean, proxyUrl: string) => { onFinished={(enable, proxyUrl) => {
if (enable) { if (enable) {
SettingsStore.setValue("feature_sliding_sync_proxy_url", null, SettingLevel.DEVICE, proxyUrl); SettingsStore.setValue("feature_sliding_sync_proxy_url", null, SettingLevel.DEVICE, proxyUrl);
onFinished(true); onFinished(true);

View File

@ -33,7 +33,8 @@ interface IProps {
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?: false, text?: void): void;
onFinished(ok: true, text: string): void;
} }
interface IState { interface IState {

View File

@ -35,7 +35,6 @@ import React, {
import sanitizeHtml from "sanitize-html"; import sanitizeHtml from "sanitize-html";
import { KeyBindingAction } from "../../../../accessibility/KeyboardShortcuts"; import { KeyBindingAction } from "../../../../accessibility/KeyboardShortcuts";
import { Ref } from "../../../../accessibility/roving/types";
import { import {
findSiblingElement, findSiblingElement,
RovingTabIndexContext, RovingTabIndexContext,
@ -104,8 +103,8 @@ interface IProps {
onFinished(): void; onFinished(): void;
} }
function refIsForRecentlyViewed(ref: RefObject<HTMLElement>): boolean { function refIsForRecentlyViewed(ref?: RefObject<HTMLElement>): boolean {
return ref.current?.id?.startsWith("mx_SpotlightDialog_button_recentlyViewed_") === true; return ref?.current?.id?.startsWith("mx_SpotlightDialog_button_recentlyViewed_") === true;
} }
function getRoomTypes(showRooms: boolean, showSpaces: boolean): Set<RoomType | null> { function getRoomTypes(showRooms: boolean, showSpaces: boolean): Set<RoomType | null> {
@ -366,7 +365,7 @@ const SpotlightDialog: React.FC<IProps> = ({ initialText = "", initialFilter = n
return [ return [
...SpaceStore.instance.enabledMetaSpaces.map((spaceKey) => ({ ...SpaceStore.instance.enabledMetaSpaces.map((spaceKey) => ({
section: Section.Spaces, section: Section.Spaces,
filter: [], filter: [] as Filter[],
avatar: ( avatar: (
<div <div
className={classNames( className={classNames(
@ -456,6 +455,7 @@ const SpotlightDialog: React.FC<IProps> = ({ initialText = "", initialFilter = n
return memberComparator(a.member, b.member); return memberComparator(a.member, b.member);
} }
return 0;
}); });
} }
@ -474,17 +474,16 @@ const SpotlightDialog: React.FC<IProps> = ({ initialText = "", initialFilter = n
}; };
useEffect(() => { useEffect(() => {
setImmediate(() => { setImmediate(() => {
let ref: Ref | undefined; const ref = rovingContext.state.refs[0];
if (rovingContext.state.refs) { if (ref) {
ref = rovingContext.state.refs[0]; rovingContext.dispatch({
type: Type.SetFocus,
payload: { ref },
});
ref.current?.scrollIntoView?.({
block: "nearest",
});
} }
rovingContext.dispatch({
type: Type.SetFocus,
payload: { ref },
});
ref?.current?.scrollIntoView?.({
block: "nearest",
});
}); });
// we intentionally ignore changes to the rovingContext for the purpose of this hook // we intentionally ignore changes to the rovingContext for the purpose of this hook
// we only want to reset the focus whenever the results or filters change // we only want to reset the focus whenever the results or filters change
@ -635,7 +634,7 @@ const SpotlightDialog: React.FC<IProps> = ({ initialText = "", initialFilter = n
roomId: publicRoom.room_id, roomId: publicRoom.room_id,
autoJoin: !result.publicRoom.world_readable && !cli.isGuest(), autoJoin: !result.publicRoom.world_readable && !cli.isGuest(),
shouldPeek: result.publicRoom.world_readable || cli.isGuest(), shouldPeek: result.publicRoom.world_readable || cli.isGuest(),
viaServers: [config.roomServer], viaServers: config ? [config.roomServer] : undefined,
}, },
true, true,
ev.type !== "click", ev.type !== "click",

View File

@ -148,7 +148,7 @@ export const NetworkDropdown: React.FC<IProps> = ({ protocols, config, setConfig
const { allServers, homeServer, userDefinedServers, setUserDefinedServers } = useServers(); const { allServers, homeServer, userDefinedServers, setUserDefinedServers } = useServers();
const options: GenericDropdownMenuItem<IPublicRoomDirectoryConfig | null>[] = allServers.map((roomServer) => ({ const options: GenericDropdownMenuItem<IPublicRoomDirectoryConfig | null>[] = allServers.map((roomServer) => ({
key: { roomServer, instanceId: null }, key: { roomServer, instanceId: undefined },
label: roomServer, label: roomServer,
description: roomServer === homeServer ? _t("Your server") : null, description: roomServer === homeServer ? _t("Your server") : null,
options: [ options: [

View File

@ -154,7 +154,7 @@ export default class MPollBody extends React.Component<IBodyProps, IState> {
} }
public componentDidMount(): void { public componentDidMount(): void {
const room = this.context.getRoom(this.props.mxEvent.getRoomId()); const room = this.context?.getRoom(this.props.mxEvent.getRoomId());
const poll = room?.polls.get(this.props.mxEvent.getId()!); const poll = room?.polls.get(this.props.mxEvent.getId()!);
if (poll) { if (poll) {
this.setPollInstance(poll); this.setPollInstance(poll);
@ -291,8 +291,8 @@ export default class MPollBody extends React.Component<IBodyProps, IState> {
const votes = countVotes(userVotes, pollEvent); const votes = countVotes(userVotes, pollEvent);
const totalVotes = this.totalVotes(votes); const totalVotes = this.totalVotes(votes);
const winCount = Math.max(...votes.values()); const winCount = Math.max(...votes.values());
const userId = this.context.getUserId(); const userId = this.context.getSafeUserId();
const myVote = userVotes?.get(userId!)?.answers[0]; const myVote = userVotes?.get(userId)?.answers[0];
const disclosed = M_POLL_KIND_DISCLOSED.matches(pollEvent.kind.name); const disclosed = M_POLL_KIND_DISCLOSED.matches(pollEvent.kind.name);
// Disclosed: votes are hidden until I vote or the poll ends // Disclosed: votes are hidden until I vote or the poll ends

View File

@ -259,7 +259,7 @@ interface IFavouriteButtonProp {
const FavouriteButton: React.FC<IFavouriteButtonProp> = ({ mxEvent }) => { const FavouriteButton: React.FC<IFavouriteButtonProp> = ({ mxEvent }) => {
const { isFavourite, toggleFavourite } = useFavouriteMessages(); const { isFavourite, toggleFavourite } = useFavouriteMessages();
const eventId = mxEvent.getId(); const eventId = mxEvent.getId()!;
const classes = classNames("mx_MessageActionBar_iconButton mx_MessageActionBar_favouriteButton", { const classes = classNames("mx_MessageActionBar_iconButton mx_MessageActionBar_favouriteButton", {
mx_MessageActionBar_favouriteButton_fillstar: isFavourite(eventId), mx_MessageActionBar_favouriteButton_fillstar: isFavourite(eventId),
}); });

View File

@ -995,19 +995,8 @@ export const RoomAdminToolsContainer: React.FC<IBaseRoomProps> = ({
return <div />; return <div />;
}; };
const useIsSynapseAdmin = (cli: MatrixClient): boolean => { const useIsSynapseAdmin = (cli?: MatrixClient): boolean => {
const [isAdmin, setIsAdmin] = useState(false); return useAsyncMemo(async () => (cli ? cli.isSynapseAdministrator().catch(() => false) : false), [cli], false);
useEffect(() => {
cli.isSynapseAdministrator().then(
(isAdmin) => {
setIsAdmin(isAdmin);
},
() => {
setIsAdmin(false);
},
);
}, [cli]);
return isAdmin;
}; };
const useHomeserverSupportsCrossSigning = (cli: MatrixClient): boolean => { const useHomeserverSupportsCrossSigning = (cli: MatrixClient): boolean => {

View File

@ -250,7 +250,7 @@ export class MessageComposer extends React.Component<IProps, IState> {
// The members should already be loading, and loadMembersIfNeeded // The members should already be loading, and loadMembersIfNeeded
// will return the promise for the existing operation // will return the promise for the existing operation
this.props.room.loadMembersIfNeeded().then(() => { this.props.room.loadMembersIfNeeded().then(() => {
const me = this.props.room.getMember(MatrixClientPeg.get().getUserId()!); const me = this.props.room.getMember(MatrixClientPeg.get().getSafeUserId()) ?? undefined;
this.setState({ me }); this.setState({ me });
}); });
} }

View File

@ -64,7 +64,7 @@ type OverflowMenuCloser = () => void;
export const OverflowMenuContext = createContext<OverflowMenuCloser | null>(null); export const OverflowMenuContext = createContext<OverflowMenuCloser | null>(null);
const MessageComposerButtons: React.FC<IProps> = (props: IProps) => { const MessageComposerButtons: React.FC<IProps> = (props: IProps) => {
const matrixClient: MatrixClient = useContext(MatrixClientContext); const matrixClient = useContext(MatrixClientContext);
const { room, roomId, narrow } = useContext(RoomContext); const { room, roomId, narrow } = useContext(RoomContext);
const isWysiwygLabEnabled = useSettingValue<boolean>("feature_wysiwyg_composer"); const isWysiwygLabEnabled = useSettingValue<boolean>("feature_wysiwyg_composer");
@ -183,7 +183,7 @@ const UploadButtonContextProvider: React.FC<IUploadButtonProps> = ({ roomId, rel
const uploadInput = useRef<HTMLInputElement>(); const uploadInput = useRef<HTMLInputElement>();
const onUploadClick = (): void => { const onUploadClick = (): void => {
if (cli.isGuest()) { if (cli?.isGuest()) {
dis.dispatch({ action: "require_registration" }); dis.dispatch({ action: "require_registration" });
return; return;
} }

View File

@ -59,7 +59,7 @@ export default function EditWysiwygComposer({
const { editMessage, endEditing, onChange, isSaveDisabled } = useEditing(editorStateTransfer, initialContent); const { editMessage, endEditing, onChange, isSaveDisabled } = useEditing(editorStateTransfer, initialContent);
if (!isReady) { if (!isReady) {
return null; return <></>;
} }
return ( return (

View File

@ -439,9 +439,11 @@ export default class Notifications extends React.PureComponent<IProps, IState> {
append: true, append: true,
}); });
} else { } else {
const pusher = this.state.pushers.find((p) => p.kind === "email" && p.pushkey === email); const pusher = this.state.pushers?.find((p) => p.kind === "email" && p.pushkey === email);
pusher.kind = null; // flag for delete if (pusher) {
await MatrixClientPeg.get().setPusher(pusher); pusher.kind = null; // flag for delete
await MatrixClientPeg.get().setPusher(pusher);
}
} }
await this.refreshFromServer(); await this.refreshFromServer();

View File

@ -129,8 +129,8 @@ const SessionManagerTab: React.FC = () => {
const scrollIntoViewTimeoutRef = useRef<number>(); const scrollIntoViewTimeoutRef = useRef<number>();
const matrixClient = useContext(MatrixClientContext); const matrixClient = useContext(MatrixClientContext);
const userId = matrixClient.getUserId(); const userId = matrixClient?.getUserId();
const currentUserMember = (userId && matrixClient.getUser(userId)) || undefined; const currentUserMember = (userId && matrixClient?.getUser(userId)) || undefined;
const clientVersions = useAsyncMemo(() => matrixClient.getVersions(), [matrixClient]); const clientVersions = useAsyncMemo(() => matrixClient.getVersions(), [matrixClient]);
const onDeviceExpandToggle = (deviceId: ExtendedDevice["device_id"]): void => { const onDeviceExpandToggle = (deviceId: ExtendedDevice["device_id"]): void => {