Conform more of the codebase to strictNullChecks (#10672)
* Conform more of the codebase to `strictNullChecks` * Iterate * Iterate * Iterate * Iterate * Conform more of the codebase to `strictNullChecks` * Iterate * Update record key
This commit is contained in:
parent
792a39a39b
commit
be5928cb64
34 changed files with 143 additions and 115 deletions
|
@ -26,7 +26,7 @@ import { isLocalRoom } from "./utils/localRoom/isLocalRoom";
|
||||||
|
|
||||||
// Not to be used for BaseAvatar urls as that has similar default avatar fallback already
|
// Not to be used for BaseAvatar urls as that has similar default avatar fallback already
|
||||||
export function avatarUrlForMember(
|
export function avatarUrlForMember(
|
||||||
member: RoomMember,
|
member: RoomMember | undefined,
|
||||||
width: number,
|
width: number,
|
||||||
height: number,
|
height: number,
|
||||||
resizeMethod: ResizeMethod,
|
resizeMethod: ResizeMethod,
|
||||||
|
|
|
@ -176,7 +176,10 @@ async function localSearch(
|
||||||
searchArgs.room_id = roomId;
|
searchArgs.room_id = roomId;
|
||||||
}
|
}
|
||||||
|
|
||||||
const localResult = await eventIndex.search(searchArgs);
|
const localResult = await eventIndex!.search(searchArgs);
|
||||||
|
if (!localResult) {
|
||||||
|
throw new Error("Local search failed");
|
||||||
|
}
|
||||||
|
|
||||||
searchArgs.next_batch = localResult.next_batch;
|
searchArgs.next_batch = localResult.next_batch;
|
||||||
|
|
||||||
|
@ -225,7 +228,11 @@ async function localPagination(searchResult: ISeshatSearchResults): Promise<ISes
|
||||||
|
|
||||||
const searchArgs = searchResult.seshatQuery;
|
const searchArgs = searchResult.seshatQuery;
|
||||||
|
|
||||||
const localResult = await eventIndex.search(searchArgs);
|
const localResult = await eventIndex!.search(searchArgs);
|
||||||
|
if (!localResult) {
|
||||||
|
throw new Error("Local search pagination failed");
|
||||||
|
}
|
||||||
|
|
||||||
searchResult.seshatQuery.next_batch = localResult.next_batch;
|
searchResult.seshatQuery.next_batch = localResult.next_batch;
|
||||||
|
|
||||||
// We only need to restore the encryption state for the new results, so
|
// We only need to restore the encryption state for the new results, so
|
||||||
|
@ -477,7 +484,7 @@ function combineResponses(
|
||||||
// Set the response next batch token to one of the tokens from the sources,
|
// Set the response next batch token to one of the tokens from the sources,
|
||||||
// this makes sure that if we exhaust one of the sources we continue with
|
// this makes sure that if we exhaust one of the sources we continue with
|
||||||
// the other one.
|
// the other one.
|
||||||
if (previousSearchResult.seshatQuery.next_batch) {
|
if (previousSearchResult.seshatQuery?.next_batch) {
|
||||||
response.next_batch = previousSearchResult.seshatQuery.next_batch;
|
response.next_batch = previousSearchResult.seshatQuery.next_batch;
|
||||||
} else if (previousSearchResult.serverSideNextBatch) {
|
} else if (previousSearchResult.serverSideNextBatch) {
|
||||||
response.next_batch = previousSearchResult.serverSideNextBatch;
|
response.next_batch = previousSearchResult.serverSideNextBatch;
|
||||||
|
@ -535,13 +542,13 @@ async function combinedPagination(searchResult: ISeshatSearchResults): Promise<I
|
||||||
const searchArgs = searchResult.seshatQuery;
|
const searchArgs = searchResult.seshatQuery;
|
||||||
const oldestEventFrom = searchResult.oldestEventFrom;
|
const oldestEventFrom = searchResult.oldestEventFrom;
|
||||||
|
|
||||||
let localResult: IResultRoomEvents;
|
let localResult: IResultRoomEvents | undefined;
|
||||||
let serverSideResult: ISearchResponse;
|
let serverSideResult: ISearchResponse | undefined;
|
||||||
|
|
||||||
// Fetch events from the local index if we have a token for it and if it's
|
// Fetch events from the local index if we have a token for it and if it's
|
||||||
// the local indexes turn or the server has exhausted its results.
|
// the local indexes turn or the server has exhausted its results.
|
||||||
if (searchArgs.next_batch && (!searchResult.serverSideNextBatch || oldestEventFrom === "server")) {
|
if (searchArgs?.next_batch && (!searchResult.serverSideNextBatch || oldestEventFrom === "server")) {
|
||||||
localResult = await eventIndex.search(searchArgs);
|
localResult = await eventIndex!.search(searchArgs);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fetch events from the server if we have a token for it and if it's the
|
// Fetch events from the server if we have a token for it and if it's the
|
||||||
|
@ -551,7 +558,7 @@ async function combinedPagination(searchResult: ISeshatSearchResults): Promise<I
|
||||||
serverSideResult = await client.search(body);
|
serverSideResult = await client.search(body);
|
||||||
}
|
}
|
||||||
|
|
||||||
let serverEvents: IResultRoomEvents;
|
let serverEvents: IResultRoomEvents | undefined;
|
||||||
|
|
||||||
if (serverSideResult) {
|
if (serverSideResult) {
|
||||||
serverEvents = serverSideResult.search_categories.room_events;
|
serverEvents = serverSideResult.search_categories.room_events;
|
||||||
|
|
|
@ -51,8 +51,7 @@ export default class RoomListActions {
|
||||||
room: Room,
|
room: Room,
|
||||||
oldTag: TagID | null,
|
oldTag: TagID | null,
|
||||||
newTag: TagID | null,
|
newTag: TagID | null,
|
||||||
oldIndex?: number,
|
newIndex: number,
|
||||||
newIndex?: number,
|
|
||||||
): AsyncActionPayload {
|
): AsyncActionPayload {
|
||||||
let metaData: Parameters<MatrixClient["setRoomTag"]>[2] | null = null;
|
let metaData: Parameters<MatrixClient["setRoomTag"]>[2] | null = null;
|
||||||
|
|
||||||
|
@ -63,12 +62,8 @@ export default class RoomListActions {
|
||||||
|
|
||||||
newList.sort((a, b) => a.tags[newTag].order - b.tags[newTag].order);
|
newList.sort((a, b) => a.tags[newTag].order - b.tags[newTag].order);
|
||||||
|
|
||||||
// If the room was moved "down" (increasing index) in the same list we
|
const indexBefore = newIndex - 1;
|
||||||
// need to use the orders of the tiles with indices shifted by +1
|
const indexAfter = newIndex;
|
||||||
const offset = newTag === oldTag && oldIndex < newIndex ? 1 : 0;
|
|
||||||
|
|
||||||
const indexBefore = offset + newIndex - 1;
|
|
||||||
const indexAfter = offset + newIndex;
|
|
||||||
|
|
||||||
const prevOrder = indexBefore <= 0 ? 0 : newList[indexBefore].tags[newTag].order;
|
const prevOrder = indexBefore <= 0 ? 0 : newList[indexBefore].tags[newTag].order;
|
||||||
const nextOrder = indexAfter >= newList.length ? 1 : newList[indexAfter].tags[newTag].order;
|
const nextOrder = indexAfter >= newList.length ? 1 : newList[indexAfter].tags[newTag].order;
|
||||||
|
|
|
@ -883,6 +883,7 @@ export default class MessagePanel extends React.Component<IProps, IState> {
|
||||||
|
|
||||||
const existingReceipts = receiptsByEvent.get(lastShownEventId) || [];
|
const existingReceipts = receiptsByEvent.get(lastShownEventId) || [];
|
||||||
const newReceipts = this.getReadReceiptsForEvent(event);
|
const newReceipts = this.getReadReceiptsForEvent(event);
|
||||||
|
if (!newReceipts) continue;
|
||||||
receiptsByEvent.set(lastShownEventId, existingReceipts.concat(newReceipts));
|
receiptsByEvent.set(lastShownEventId, existingReceipts.concat(newReceipts));
|
||||||
|
|
||||||
// Record these receipts along with their last shown event ID for
|
// Record these receipts along with their last shown event ID for
|
||||||
|
@ -1218,7 +1219,7 @@ class CreationGrouper extends BaseGrouper {
|
||||||
key="roomcreationsummary"
|
key="roomcreationsummary"
|
||||||
events={this.events}
|
events={this.events}
|
||||||
onToggle={panel.onHeightChanged} // Update scroll state
|
onToggle={panel.onHeightChanged} // Update scroll state
|
||||||
summaryMembers={[ev.sender]}
|
summaryMembers={ev.sender ? [ev.sender] : undefined}
|
||||||
summaryText={summaryText}
|
summaryText={summaryText}
|
||||||
layout={this.panel.props.layout}
|
layout={this.panel.props.layout}
|
||||||
>
|
>
|
||||||
|
|
|
@ -627,7 +627,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
|
||||||
mainSplitContentType: room ? this.getMainSplitContentType(room) : undefined,
|
mainSplitContentType: room ? this.getMainSplitContentType(room) : undefined,
|
||||||
initialEventId: undefined, // default to clearing this, will get set later in the method if needed
|
initialEventId: undefined, // default to clearing this, will get set later in the method if needed
|
||||||
showRightPanel: this.context.rightPanelStore.isOpenForRoom(roomId),
|
showRightPanel: this.context.rightPanelStore.isOpenForRoom(roomId),
|
||||||
activeCall: CallStore.instance.getActiveCall(roomId),
|
activeCall: roomId ? CallStore.instance.getActiveCall(roomId) : null,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (
|
if (
|
||||||
|
@ -1071,6 +1071,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
|
||||||
};
|
};
|
||||||
|
|
||||||
private onAction = async (payload: ActionPayload): Promise<void> => {
|
private onAction = async (payload: ActionPayload): Promise<void> => {
|
||||||
|
if (!this.context.client) return;
|
||||||
switch (payload.action) {
|
switch (payload.action) {
|
||||||
case "message_sent":
|
case "message_sent":
|
||||||
this.checkDesktopNotifications();
|
this.checkDesktopNotifications();
|
||||||
|
@ -1228,7 +1229,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
|
||||||
this.handleEffects(ev);
|
this.handleEffects(ev);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ev.getSender() !== this.context.client.getSafeUserId()) {
|
if (this.context.client && 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
|
||||||
|
@ -1469,7 +1470,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
|
||||||
};
|
};
|
||||||
|
|
||||||
private updatePermissions(room: Room): void {
|
private updatePermissions(room: Room): void {
|
||||||
if (room) {
|
if (room && this.context.client) {
|
||||||
const me = this.context.client.getSafeUserId();
|
const me = this.context.client.getSafeUserId();
|
||||||
const canReact =
|
const canReact =
|
||||||
room.getMyMembership() === "join" && room.currentState.maySendEvent(EventType.Reaction, me);
|
room.getMyMembership() === "join" && room.currentState.maySendEvent(EventType.Reaction, me);
|
||||||
|
@ -1956,6 +1957,8 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
|
||||||
}
|
}
|
||||||
|
|
||||||
public render(): React.ReactNode {
|
public render(): React.ReactNode {
|
||||||
|
if (!this.context.client) return null;
|
||||||
|
|
||||||
if (this.state.room instanceof LocalRoom) {
|
if (this.state.room instanceof LocalRoom) {
|
||||||
if (this.state.room.state === LocalRoomState.CREATING) {
|
if (this.state.room.state === LocalRoomState.CREATING) {
|
||||||
return this.renderLocalRoomCreateLoader(this.state.room);
|
return this.renderLocalRoomCreateLoader(this.state.room);
|
||||||
|
@ -2064,7 +2067,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
|
||||||
const inviteEvent = myMember ? myMember.events.member : null;
|
const inviteEvent = myMember ? myMember.events.member : null;
|
||||||
let inviterName = _t("Unknown");
|
let inviterName = _t("Unknown");
|
||||||
if (inviteEvent) {
|
if (inviteEvent) {
|
||||||
inviterName = inviteEvent.sender?.name ?? inviteEvent.getSender();
|
inviterName = inviteEvent.sender?.name ?? inviteEvent.getSender()!;
|
||||||
}
|
}
|
||||||
|
|
||||||
// We deliberately don't try to peek into invites, even if we have permission to peek
|
// We deliberately don't try to peek into invites, even if we have permission to peek
|
||||||
|
|
|
@ -786,7 +786,7 @@ export default class ScrollPanel extends React.Component<IProps> {
|
||||||
const scrollState = this.scrollState;
|
const scrollState = this.scrollState;
|
||||||
const trackedNode = scrollState.trackedNode;
|
const trackedNode = scrollState.trackedNode;
|
||||||
|
|
||||||
if (!trackedNode?.parentElement) {
|
if (!trackedNode?.parentElement && this.itemlist.current) {
|
||||||
let node: HTMLElement | undefined = undefined;
|
let node: HTMLElement | undefined = undefined;
|
||||||
const messages = this.itemlist.current.children;
|
const messages = this.itemlist.current.children;
|
||||||
const scrollToken = scrollState.trackedScrollToken;
|
const scrollToken = scrollState.trackedScrollToken;
|
||||||
|
@ -890,7 +890,7 @@ export default class ScrollPanel extends React.Component<IProps> {
|
||||||
public clearPreventShrinking = (): void => {
|
public clearPreventShrinking = (): void => {
|
||||||
const messageList = this.itemlist.current;
|
const messageList = this.itemlist.current;
|
||||||
const balanceElement = messageList && messageList.parentElement;
|
const balanceElement = messageList && messageList.parentElement;
|
||||||
if (balanceElement) balanceElement.style.paddingBottom = null;
|
if (balanceElement) balanceElement.style.removeProperty("paddingBottom");
|
||||||
this.preventShrinkingState = null;
|
this.preventShrinkingState = null;
|
||||||
debuglog("prevent shrinking cleared");
|
debuglog("prevent shrinking cleared");
|
||||||
};
|
};
|
||||||
|
@ -904,7 +904,7 @@ export default class ScrollPanel extends React.Component<IProps> {
|
||||||
what it was when marking.
|
what it was when marking.
|
||||||
*/
|
*/
|
||||||
public updatePreventShrinking = (): void => {
|
public updatePreventShrinking = (): void => {
|
||||||
if (this.preventShrinkingState) {
|
if (this.preventShrinkingState && this.itemlist.current) {
|
||||||
const sn = this.getScrollNode();
|
const sn = this.getScrollNode();
|
||||||
const scrollState = this.scrollState;
|
const scrollState = this.scrollState;
|
||||||
const messageList = this.itemlist.current;
|
const messageList = this.itemlist.current;
|
||||||
|
@ -922,7 +922,7 @@ export default class ScrollPanel extends React.Component<IProps> {
|
||||||
if (!shouldClear) {
|
if (!shouldClear) {
|
||||||
const currentOffset = messageList.clientHeight - (offsetNode.offsetTop + offsetNode.clientHeight);
|
const currentOffset = messageList.clientHeight - (offsetNode.offsetTop + offsetNode.clientHeight);
|
||||||
const offsetDiff = offsetFromBottom - currentOffset;
|
const offsetDiff = offsetFromBottom - currentOffset;
|
||||||
if (offsetDiff > 0) {
|
if (offsetDiff > 0 && balanceElement) {
|
||||||
balanceElement.style.paddingBottom = `${offsetDiff}px`;
|
balanceElement.style.paddingBottom = `${offsetDiff}px`;
|
||||||
debuglog("update prevent shrinking ", offsetDiff, "px from bottom");
|
debuglog("update prevent shrinking ", offsetDiff, "px from bottom");
|
||||||
} else if (offsetDiff < 0) {
|
} else if (offsetDiff < 0) {
|
||||||
|
|
|
@ -79,7 +79,8 @@ export default class UserView extends React.Component<IProps, IState> {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const fakeEvent = new MatrixEvent({ type: "m.room.member", content: profileInfo });
|
const fakeEvent = new MatrixEvent({ type: "m.room.member", content: profileInfo });
|
||||||
const member = new RoomMember(null, this.props.userId);
|
// We pass an empty string room ID here, this is slight abuse of the class to simplify code
|
||||||
|
const member = new RoomMember("", this.props.userId);
|
||||||
member.setMembershipEvent(fakeEvent);
|
member.setMembershipEvent(fakeEvent);
|
||||||
this.setState({ member, loading: false });
|
this.setState({ member, loading: false });
|
||||||
}
|
}
|
||||||
|
|
|
@ -169,8 +169,8 @@ const RoomContextMenu: React.FC<IProps> = ({ room, onFinished, ...props }) => {
|
||||||
);
|
);
|
||||||
|
|
||||||
const echoChamber = EchoChamber.forRoom(room);
|
const echoChamber = EchoChamber.forRoom(room);
|
||||||
let notificationLabel: string;
|
let notificationLabel: string | undefined;
|
||||||
let iconClassName: string;
|
let iconClassName: string | undefined;
|
||||||
switch (echoChamber.notificationVolume) {
|
switch (echoChamber.notificationVolume) {
|
||||||
case RoomNotifState.AllMessages:
|
case RoomNotifState.AllMessages:
|
||||||
notificationLabel = _t("Default");
|
notificationLabel = _t("Default");
|
||||||
|
@ -337,7 +337,7 @@ const RoomContextMenu: React.FC<IProps> = ({ room, onFinished, ...props }) => {
|
||||||
const isApplied = RoomListStore.instance.getTagsForRoom(room).includes(tagId);
|
const isApplied = RoomListStore.instance.getTagsForRoom(room).includes(tagId);
|
||||||
const removeTag = isApplied ? tagId : inverseTag;
|
const removeTag = isApplied ? tagId : inverseTag;
|
||||||
const addTag = isApplied ? null : tagId;
|
const addTag = isApplied ? null : tagId;
|
||||||
dis.dispatch(RoomListActions.tagRoom(cli, room, removeTag, addTag, undefined, 0));
|
dis.dispatch(RoomListActions.tagRoom(cli, room, removeTag, addTag, 0));
|
||||||
} else {
|
} else {
|
||||||
logger.warn(`Unexpected tag ${tagId} applied to ${room.roomId}`);
|
logger.warn(`Unexpected tag ${tagId} applied to ${room.roomId}`);
|
||||||
}
|
}
|
||||||
|
|
|
@ -100,7 +100,7 @@ export const RoomGeneralContextMenu: React.FC<RoomGeneralContextMenuProps> = ({
|
||||||
const isApplied = RoomListStore.instance.getTagsForRoom(room).includes(tagId);
|
const isApplied = RoomListStore.instance.getTagsForRoom(room).includes(tagId);
|
||||||
const removeTag = isApplied ? tagId : inverseTag;
|
const removeTag = isApplied ? tagId : inverseTag;
|
||||||
const addTag = isApplied ? null : tagId;
|
const addTag = isApplied ? null : tagId;
|
||||||
dis.dispatch(RoomListActions.tagRoom(cli, room, removeTag, addTag, undefined, 0));
|
dis.dispatch(RoomListActions.tagRoom(cli, room, removeTag, addTag, 0));
|
||||||
} else {
|
} else {
|
||||||
logger.warn(`Unexpected tag ${tagId} applied to ${room.roomId}`);
|
logger.warn(`Unexpected tag ${tagId} applied to ${room.roomId}`);
|
||||||
}
|
}
|
||||||
|
|
|
@ -260,7 +260,7 @@ export default class ReportEventDialog extends React.Component<IProps, IState> {
|
||||||
|
|
||||||
// if the user should also be ignored, do that
|
// if the user should also be ignored, do that
|
||||||
if (this.state.ignoreUserToo) {
|
if (this.state.ignoreUserToo) {
|
||||||
await client.setIgnoredUsers([...client.getIgnoredUsers(), ev.getSender()]);
|
await client.setIgnoredUsers([...client.getIgnoredUsers(), ev.getSender()!]);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.props.onFinished(true);
|
this.props.onFinished(true);
|
||||||
|
@ -309,8 +309,8 @@ export default class ReportEventDialog extends React.Component<IProps, IState> {
|
||||||
// Display report-to-moderator dialog.
|
// Display report-to-moderator dialog.
|
||||||
// We let the user pick a nature.
|
// We let the user pick a nature.
|
||||||
const client = MatrixClientPeg.get();
|
const client = MatrixClientPeg.get();
|
||||||
const homeServerName = SdkConfig.get("validated_server_config").hsName;
|
const homeServerName = SdkConfig.get("validated_server_config")!.hsName;
|
||||||
let subtitle;
|
let subtitle: string;
|
||||||
switch (this.state.nature) {
|
switch (this.state.nature) {
|
||||||
case Nature.Disagreement:
|
case Nature.Disagreement:
|
||||||
subtitle = _t(
|
subtitle = _t(
|
||||||
|
|
|
@ -130,7 +130,7 @@ export default class AccessSecretStorageDialog extends React.PureComponent<IProp
|
||||||
|
|
||||||
// also clear the file upload control so that the user can upload the same file
|
// also clear the file upload control so that the user can upload the same file
|
||||||
// the did before (otherwise the onchange wouldn't fire)
|
// the did before (otherwise the onchange wouldn't fire)
|
||||||
if (this.fileUpload.current) this.fileUpload.current.value = null;
|
if (this.fileUpload.current) this.fileUpload.current.value = "";
|
||||||
|
|
||||||
// We don't use Field's validation here because a) we want it in a separate place rather
|
// We don't use Field's validation here because a) we want it in a separate place rather
|
||||||
// than in a tooltip and b) we want it to display feedback based on the uploaded file
|
// than in a tooltip and b) we want it to display feedback based on the uploaded file
|
||||||
|
|
|
@ -324,7 +324,10 @@ export default class RestoreKeyBackupDialog extends React.PureComponent<IProps,
|
||||||
details = _t("Fetching keys from server…");
|
details = _t("Fetching keys from server…");
|
||||||
} else if (this.state.progress.stage === ProgressState.LoadKeys) {
|
} else if (this.state.progress.stage === ProgressState.LoadKeys) {
|
||||||
const { total, successes, failures } = this.state.progress;
|
const { total, successes, failures } = this.state.progress;
|
||||||
details = _t("%(completed)s of %(total)s keys restored", { total, completed: successes + failures });
|
details = _t("%(completed)s of %(total)s keys restored", {
|
||||||
|
total,
|
||||||
|
completed: (successes ?? 0) + (failures ?? 0),
|
||||||
|
});
|
||||||
} else if (this.state.progress.stage === ProgressState.PreFetch) {
|
} else if (this.state.progress.stage === ProgressState.PreFetch) {
|
||||||
details = _t("Fetching keys from server…");
|
details = _t("Fetching keys from server…");
|
||||||
}
|
}
|
||||||
|
|
|
@ -65,11 +65,11 @@ export const OverflowMenuContext = createContext<OverflowMenuCloser | null>(null
|
||||||
|
|
||||||
const MessageComposerButtons: React.FC<IProps> = (props: IProps) => {
|
const MessageComposerButtons: React.FC<IProps> = (props: IProps) => {
|
||||||
const matrixClient = useContext(MatrixClientContext);
|
const matrixClient = useContext(MatrixClientContext);
|
||||||
const { room, roomId, narrow } = useContext(RoomContext);
|
const { room, narrow } = useContext(RoomContext);
|
||||||
|
|
||||||
const isWysiwygLabEnabled = useSettingValue<boolean>("feature_wysiwyg_composer");
|
const isWysiwygLabEnabled = useSettingValue<boolean>("feature_wysiwyg_composer");
|
||||||
|
|
||||||
if (props.haveRecording) {
|
if (!matrixClient || !room || props.haveRecording) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -93,7 +93,7 @@ const MessageComposerButtons: React.FC<IProps> = (props: IProps) => {
|
||||||
voiceRecordingButton(props, narrow),
|
voiceRecordingButton(props, narrow),
|
||||||
startVoiceBroadcastButton(props),
|
startVoiceBroadcastButton(props),
|
||||||
props.showPollsButton ? pollButton(room, props.relation) : null,
|
props.showPollsButton ? pollButton(room, props.relation) : null,
|
||||||
showLocationButton(props, room, roomId, matrixClient),
|
showLocationButton(props, room, matrixClient),
|
||||||
];
|
];
|
||||||
} else {
|
} else {
|
||||||
mainButtons = [
|
mainButtons = [
|
||||||
|
@ -113,7 +113,7 @@ const MessageComposerButtons: React.FC<IProps> = (props: IProps) => {
|
||||||
voiceRecordingButton(props, narrow),
|
voiceRecordingButton(props, narrow),
|
||||||
startVoiceBroadcastButton(props),
|
startVoiceBroadcastButton(props),
|
||||||
props.showPollsButton ? pollButton(room, props.relation) : null,
|
props.showPollsButton ? pollButton(room, props.relation) : null,
|
||||||
showLocationButton(props, room, roomId, matrixClient),
|
showLocationButton(props, room, matrixClient),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -127,7 +127,7 @@ const MessageComposerButtons: React.FC<IProps> = (props: IProps) => {
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<UploadButtonContextProvider roomId={roomId} relation={props.relation}>
|
<UploadButtonContextProvider roomId={room.roomId} relation={props.relation}>
|
||||||
{mainButtons}
|
{mainButtons}
|
||||||
{moreButtons.length > 0 && (
|
{moreButtons.length > 0 && (
|
||||||
<AccessibleTooltipButton
|
<AccessibleTooltipButton
|
||||||
|
@ -346,18 +346,13 @@ class PollButton extends React.PureComponent<IPollButtonProps> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function showLocationButton(
|
function showLocationButton(props: IProps, room: Room, matrixClient: MatrixClient): ReactElement | null {
|
||||||
props: IProps,
|
const sender = room.getMember(matrixClient.getSafeUserId());
|
||||||
room: Room,
|
|
||||||
roomId: string,
|
|
||||||
matrixClient: MatrixClient,
|
|
||||||
): ReactElement | null {
|
|
||||||
const sender = room.getMember(matrixClient.getUserId()!);
|
|
||||||
|
|
||||||
return props.showLocationButton && sender ? (
|
return props.showLocationButton && sender ? (
|
||||||
<LocationButton
|
<LocationButton
|
||||||
key="location"
|
key="location"
|
||||||
roomId={roomId}
|
roomId={room.roomId}
|
||||||
relation={props.relation}
|
relation={props.relation}
|
||||||
sender={sender}
|
sender={sender}
|
||||||
menuPosition={props.menuPosition}
|
menuPosition={props.menuPosition}
|
||||||
|
|
|
@ -151,7 +151,7 @@ export default class RoomPreviewBar extends React.Component<IProps, IState> {
|
||||||
const result = await MatrixClientPeg.get().lookupThreePid(
|
const result = await MatrixClientPeg.get().lookupThreePid(
|
||||||
"email",
|
"email",
|
||||||
this.props.invitedEmail,
|
this.props.invitedEmail,
|
||||||
identityAccessToken,
|
identityAccessToken!,
|
||||||
);
|
);
|
||||||
this.setState({ invitedEmailMxid: result.mxid });
|
this.setState({ invitedEmailMxid: result.mxid });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
@ -243,8 +243,8 @@ export default class RoomPreviewBar extends React.Component<IProps, IState> {
|
||||||
if (!inviteEvent) {
|
if (!inviteEvent) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
const inviterUserId = inviteEvent.events.member.getSender();
|
const inviterUserId = inviteEvent.events.member?.getSender();
|
||||||
return room.currentState.getMember(inviterUserId);
|
return inviterUserId ? room.currentState.getMember(inviterUserId) : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private isDMInvite(): boolean {
|
private isDMInvite(): boolean {
|
||||||
|
@ -252,8 +252,8 @@ export default class RoomPreviewBar extends React.Component<IProps, IState> {
|
||||||
if (!myMember) {
|
if (!myMember) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
const memberContent = myMember.events.member.getContent();
|
const memberContent = myMember.events.member?.getContent();
|
||||||
return memberContent.membership === "invite" && memberContent.is_direct;
|
return memberContent?.membership === "invite" && memberContent.is_direct;
|
||||||
}
|
}
|
||||||
|
|
||||||
private makeScreenAfterLogin(): { screen: string; params: Record<string, any> } {
|
private makeScreenAfterLogin(): { screen: string; params: Record<string, any> } {
|
||||||
|
|
|
@ -64,7 +64,7 @@ export default class SearchResultTile extends React.Component<IProps> {
|
||||||
const eventId = resultEvent.getId();
|
const eventId = resultEvent.getId();
|
||||||
|
|
||||||
const ts1 = resultEvent.getTs();
|
const ts1 = resultEvent.getTs();
|
||||||
const ret = [<DateSeparator key={ts1 + "-search"} roomId={resultEvent.getRoomId()} ts={ts1} />];
|
const ret = [<DateSeparator key={ts1 + "-search"} roomId={resultEvent.getRoomId()!} ts={ts1} />];
|
||||||
const layout = SettingsStore.getValue("layout");
|
const layout = SettingsStore.getValue("layout");
|
||||||
const isTwelveHour = SettingsStore.getValue("showTwelveHourTimestamps");
|
const isTwelveHour = SettingsStore.getValue("showTwelveHourTimestamps");
|
||||||
const alwaysShowTimestamps = SettingsStore.getValue("alwaysShowTimestamps");
|
const alwaysShowTimestamps = SettingsStore.getValue("alwaysShowTimestamps");
|
||||||
|
|
|
@ -372,7 +372,10 @@ export class SendMessageComposer extends React.Component<ISendMessageComposerPro
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
this.currentlyComposedEditorState = this.model.serializeParts();
|
this.currentlyComposedEditorState = this.model.serializeParts();
|
||||||
} else if (this.sendHistoryManager.currentIndex + delta === this.sendHistoryManager.history.length) {
|
} else if (
|
||||||
|
this.currentlyComposedEditorState &&
|
||||||
|
this.sendHistoryManager.currentIndex + delta === this.sendHistoryManager.history.length
|
||||||
|
) {
|
||||||
// True when we return to the message being composed currently
|
// True when we return to the message being composed currently
|
||||||
this.model.reset(this.currentlyComposedEditorState);
|
this.model.reset(this.currentlyComposedEditorState);
|
||||||
this.sendHistoryManager.currentIndex = this.sendHistoryManager.history.length;
|
this.sendHistoryManager.currentIndex = this.sendHistoryManager.history.length;
|
||||||
|
@ -393,6 +396,7 @@ export class SendMessageComposer extends React.Component<ISendMessageComposerPro
|
||||||
|
|
||||||
private sendQuickReaction(): void {
|
private sendQuickReaction(): void {
|
||||||
const timeline = this.context.liveTimeline;
|
const timeline = this.context.liveTimeline;
|
||||||
|
if (!timeline) return;
|
||||||
const events = timeline.getEvents();
|
const events = timeline.getEvents();
|
||||||
const reaction = this.model.parts[1].text;
|
const reaction = this.model.parts[1].text;
|
||||||
for (let i = events.length - 1; i >= 0; i--) {
|
for (let i = events.length - 1; i >= 0; i--) {
|
||||||
|
@ -443,8 +447,8 @@ export class SendMessageComposer extends React.Component<ISendMessageComposerPro
|
||||||
isReply: !!this.props.replyToEvent,
|
isReply: !!this.props.replyToEvent,
|
||||||
inThread: this.props.relation?.rel_type === THREAD_RELATION_TYPE.name,
|
inThread: this.props.relation?.rel_type === THREAD_RELATION_TYPE.name,
|
||||||
};
|
};
|
||||||
if (posthogEvent.inThread) {
|
if (posthogEvent.inThread && this.props.relation!.event_id) {
|
||||||
const threadRoot = this.props.room.findEventById(this.props.relation.event_id);
|
const threadRoot = this.props.room.findEventById(this.props.relation!.event_id);
|
||||||
posthogEvent.startsThread = threadRoot?.getThread()?.events.length === 1;
|
posthogEvent.startsThread = threadRoot?.getThread()?.events.length === 1;
|
||||||
}
|
}
|
||||||
PosthogAnalytics.instance.trackEvent<ComposerEvent>(posthogEvent);
|
PosthogAnalytics.instance.trackEvent<ComposerEvent>(posthogEvent);
|
||||||
|
@ -480,7 +484,7 @@ export class SendMessageComposer extends React.Component<ISendMessageComposerPro
|
||||||
return; // errored
|
return; // errored
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cmd.category === CommandCategories.messages || cmd.category === CommandCategories.effects) {
|
if (content && [CommandCategories.messages, CommandCategories.effects].includes(cmd.category)) {
|
||||||
// Attach any mentions which might be contained in the command content.
|
// Attach any mentions which might be contained in the command content.
|
||||||
attachMentions(this.props.mxClient.getSafeUserId(), content, model, replyToEvent);
|
attachMentions(this.props.mxClient.getSafeUserId(), content, model, replyToEvent);
|
||||||
attachRelation(content, this.props.relation);
|
attachRelation(content, this.props.relation);
|
||||||
|
|
|
@ -86,7 +86,7 @@ export default class AdvancedRoomSettingsTab extends React.Component<IProps, ISt
|
||||||
|
|
||||||
private upgradeRoom = (): void => {
|
private upgradeRoom = (): void => {
|
||||||
const room = MatrixClientPeg.get().getRoom(this.props.roomId);
|
const room = MatrixClientPeg.get().getRoom(this.props.roomId);
|
||||||
Modal.createDialog(RoomUpgradeDialog, { room });
|
if (room) Modal.createDialog(RoomUpgradeDialog, { room });
|
||||||
};
|
};
|
||||||
|
|
||||||
private onOldRoomClicked = (e: ButtonEvent): void => {
|
private onOldRoomClicked = (e: ButtonEvent): void => {
|
||||||
|
|
|
@ -66,12 +66,19 @@ interface IState {
|
||||||
haveIdServer: boolean;
|
haveIdServer: boolean;
|
||||||
serverSupportsSeparateAddAndBind?: boolean;
|
serverSupportsSeparateAddAndBind?: boolean;
|
||||||
idServerHasUnsignedTerms: boolean;
|
idServerHasUnsignedTerms: boolean;
|
||||||
requiredPolicyInfo: {
|
requiredPolicyInfo:
|
||||||
|
| {
|
||||||
// This object is passed along to a component for handling
|
// This object is passed along to a component for handling
|
||||||
|
hasTerms: false;
|
||||||
|
policiesAndServices: null; // From the startTermsFlow callback
|
||||||
|
agreedUrls: null; // From the startTermsFlow callback
|
||||||
|
resolve: null; // Promise resolve function for startTermsFlow callback
|
||||||
|
}
|
||||||
|
| {
|
||||||
hasTerms: boolean;
|
hasTerms: boolean;
|
||||||
policiesAndServices: ServicePolicyPair[] | null; // From the startTermsFlow callback
|
policiesAndServices: ServicePolicyPair[];
|
||||||
agreedUrls: string[] | null; // From the startTermsFlow callback
|
agreedUrls: string[];
|
||||||
resolve: ((values: string[]) => void) | null; // Promise resolve function for startTermsFlow callback
|
resolve: (values: string[]) => void;
|
||||||
};
|
};
|
||||||
emails: IThreepid[];
|
emails: IThreepid[];
|
||||||
msisdns: IThreepid[];
|
msisdns: IThreepid[];
|
||||||
|
@ -191,19 +198,19 @@ export default class GeneralUserSettingsTab extends React.Component<IProps, ISta
|
||||||
}
|
}
|
||||||
|
|
||||||
private async checkTerms(): Promise<void> {
|
private async checkTerms(): Promise<void> {
|
||||||
if (!this.state.haveIdServer) {
|
// By starting the terms flow we get the logic for checking which terms the user has signed
|
||||||
|
// for free. So we might as well use that for our own purposes.
|
||||||
|
const idServerUrl = MatrixClientPeg.get().getIdentityServerUrl();
|
||||||
|
if (!this.state.haveIdServer || !idServerUrl) {
|
||||||
this.setState({ idServerHasUnsignedTerms: false });
|
this.setState({ idServerHasUnsignedTerms: false });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// By starting the terms flow we get the logic for checking which terms the user has signed
|
|
||||||
// for free. So we might as well use that for our own purposes.
|
|
||||||
const idServerUrl = MatrixClientPeg.get().getIdentityServerUrl();
|
|
||||||
const authClient = new IdentityAuthClient();
|
const authClient = new IdentityAuthClient();
|
||||||
try {
|
try {
|
||||||
const idAccessToken = await authClient.getAccessToken({ check: false });
|
const idAccessToken = await authClient.getAccessToken({ check: false });
|
||||||
await startTermsFlow(
|
await startTermsFlow(
|
||||||
[new Service(SERVICE_TYPES.IS, idServerUrl, idAccessToken)],
|
[new Service(SERVICE_TYPES.IS, idServerUrl, idAccessToken!)],
|
||||||
(policiesAndServices, agreedUrls, extraClassNames) => {
|
(policiesAndServices, agreedUrls, extraClassNames) => {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
this.setState({
|
this.setState({
|
||||||
|
|
|
@ -62,7 +62,7 @@ export default class AudioFeed extends React.Component<IProps, IState> {
|
||||||
// it fails.
|
// it fails.
|
||||||
// It seems reliable if you set the sink ID after setting the srcObject and then set the sink ID
|
// It seems reliable if you set the sink ID after setting the srcObject and then set the sink ID
|
||||||
// back to the default after the call is over - Dave
|
// back to the default after the call is over - Dave
|
||||||
element.setSinkId(audioOutput);
|
element!.setSinkId(audioOutput);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.error("Couldn't set requested audio output device: using default", e);
|
logger.error("Couldn't set requested audio output device: using default", e);
|
||||||
logger.warn("Couldn't set requested audio output device: using default", e);
|
logger.warn("Couldn't set requested audio output device: using default", e);
|
||||||
|
@ -103,7 +103,7 @@ export default class AudioFeed extends React.Component<IProps, IState> {
|
||||||
if (!element) return;
|
if (!element) return;
|
||||||
|
|
||||||
element.pause();
|
element.pause();
|
||||||
element.src = null;
|
element.removeAttribute("src");
|
||||||
|
|
||||||
// As per comment in componentDidMount, setting the sink ID back to the
|
// As per comment in componentDidMount, setting the sink ID back to the
|
||||||
// default once the call is over makes setSinkId work reliably. - Dave
|
// default once the call is over makes setSinkId work reliably. - Dave
|
||||||
|
|
|
@ -339,16 +339,17 @@ export default class LegacyCallView extends React.Component<IProps, IState> {
|
||||||
|
|
||||||
private onCallResumeClick = (): void => {
|
private onCallResumeClick = (): void => {
|
||||||
const userFacingRoomId = LegacyCallHandler.instance.roomIdForCall(this.props.call);
|
const userFacingRoomId = LegacyCallHandler.instance.roomIdForCall(this.props.call);
|
||||||
LegacyCallHandler.instance.setActiveCallRoomId(userFacingRoomId);
|
if (userFacingRoomId) LegacyCallHandler.instance.setActiveCallRoomId(userFacingRoomId);
|
||||||
};
|
};
|
||||||
|
|
||||||
private onTransferClick = (): void => {
|
private onTransferClick = (): void => {
|
||||||
const transfereeCall = LegacyCallHandler.instance.getTransfereeForCallId(this.props.call.callId);
|
const transfereeCall = LegacyCallHandler.instance.getTransfereeForCallId(this.props.call.callId);
|
||||||
this.props.call.transferToCall(transfereeCall);
|
if (transfereeCall) this.props.call.transferToCall(transfereeCall);
|
||||||
};
|
};
|
||||||
|
|
||||||
private onHangupClick = (): void => {
|
private onHangupClick = (): void => {
|
||||||
LegacyCallHandler.instance.hangupOrReject(LegacyCallHandler.instance.roomIdForCall(this.props.call));
|
const roomId = LegacyCallHandler.instance.roomIdForCall(this.props.call);
|
||||||
|
if (roomId) LegacyCallHandler.instance.hangupOrReject(roomId);
|
||||||
};
|
};
|
||||||
|
|
||||||
private onToggleSidebar = (): void => {
|
private onToggleSidebar = (): void => {
|
||||||
|
@ -451,13 +452,12 @@ export default class LegacyCallView extends React.Component<IProps, IState> {
|
||||||
|
|
||||||
let holdTransferContent: React.ReactNode;
|
let holdTransferContent: React.ReactNode;
|
||||||
if (transfereeCall) {
|
if (transfereeCall) {
|
||||||
const transferTargetRoom = MatrixClientPeg.get().getRoom(
|
const cli = MatrixClientPeg.get();
|
||||||
LegacyCallHandler.instance.roomIdForCall(call),
|
const callRoomId = LegacyCallHandler.instance.roomIdForCall(call);
|
||||||
);
|
const transferTargetRoom = callRoomId ? cli.getRoom(callRoomId) : null;
|
||||||
const transferTargetName = transferTargetRoom ? transferTargetRoom.name : _t("unknown person");
|
const transferTargetName = transferTargetRoom ? transferTargetRoom.name : _t("unknown person");
|
||||||
const transfereeRoom = MatrixClientPeg.get().getRoom(
|
const transfereeCallRoomId = LegacyCallHandler.instance.roomIdForCall(transfereeCall);
|
||||||
LegacyCallHandler.instance.roomIdForCall(transfereeCall),
|
const transfereeRoom = transfereeCallRoomId ? cli.getRoom(transfereeCallRoomId) : null;
|
||||||
);
|
|
||||||
const transfereeName = transfereeRoom ? transfereeRoom.name : _t("unknown person");
|
const transfereeName = transfereeRoom ? transfereeRoom.name : _t("unknown person");
|
||||||
|
|
||||||
holdTransferContent = (
|
holdTransferContent = (
|
||||||
|
@ -579,6 +579,8 @@ export default class LegacyCallView extends React.Component<IProps, IState> {
|
||||||
const callRoomId = LegacyCallHandler.instance.roomIdForCall(call);
|
const callRoomId = LegacyCallHandler.instance.roomIdForCall(call);
|
||||||
const secondaryCallRoomId = LegacyCallHandler.instance.roomIdForCall(secondaryCall);
|
const secondaryCallRoomId = LegacyCallHandler.instance.roomIdForCall(secondaryCall);
|
||||||
const callRoom = callRoomId ? client.getRoom(callRoomId) : null;
|
const callRoom = callRoomId ? client.getRoom(callRoomId) : null;
|
||||||
|
if (!callRoom) return null;
|
||||||
|
|
||||||
const secCallRoom = secondaryCallRoomId ? client.getRoom(secondaryCallRoomId) : null;
|
const secCallRoom = secondaryCallRoomId ? client.getRoom(secondaryCallRoomId) : null;
|
||||||
|
|
||||||
const callViewClasses = classNames({
|
const callViewClasses = classNames({
|
||||||
|
|
|
@ -150,7 +150,7 @@ export default class VideoFeed extends React.PureComponent<IProps, IState> {
|
||||||
if (!element) return;
|
if (!element) return;
|
||||||
|
|
||||||
element.pause();
|
element.pause();
|
||||||
element.src = null;
|
element.removeAttribute("src");
|
||||||
|
|
||||||
// As per comment in componentDidMount, setting the sink ID back to the
|
// As per comment in componentDidMount, setting the sink ID back to the
|
||||||
// default once the call is over makes setSinkId work reliably. - Dave
|
// default once the call is over makes setSinkId work reliably. - Dave
|
||||||
|
|
|
@ -235,8 +235,11 @@ export default class EventIndex extends EventEmitter {
|
||||||
const indexManager = PlatformPeg.get()?.getEventIndexingManager();
|
const indexManager = PlatformPeg.get()?.getEventIndexingManager();
|
||||||
if (!indexManager) return;
|
if (!indexManager) return;
|
||||||
|
|
||||||
|
const associatedId = ev.getAssociatedId();
|
||||||
|
if (!associatedId) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await indexManager.deleteEvent(ev.getAssociatedId());
|
await indexManager.deleteEvent(associatedId);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.log("EventIndex: Error deleting event from index", e);
|
logger.log("EventIndex: Error deleting event from index", e);
|
||||||
}
|
}
|
||||||
|
@ -519,10 +522,10 @@ export default class EventIndex extends EventEmitter {
|
||||||
const profiles: Record<string, IMatrixProfile> = {};
|
const profiles: Record<string, IMatrixProfile> = {};
|
||||||
|
|
||||||
stateEvents.forEach((ev) => {
|
stateEvents.forEach((ev) => {
|
||||||
if (ev.event.content && ev.event.content.membership === "join") {
|
if (ev.getContent().membership === "join") {
|
||||||
profiles[ev.event.sender] = {
|
profiles[ev.getSender()!] = {
|
||||||
displayname: ev.event.content.displayname,
|
displayname: ev.getContent().displayname,
|
||||||
avatar_url: ev.event.content.avatar_url,
|
avatar_url: ev.getContent().avatar_url,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -733,7 +736,7 @@ export default class EventIndex extends EventEmitter {
|
||||||
const matrixEvents = events.map((e) => {
|
const matrixEvents = events.map((e) => {
|
||||||
const matrixEvent = eventMapper(e.event);
|
const matrixEvent = eventMapper(e.event);
|
||||||
|
|
||||||
const member = new RoomMember(room.roomId, matrixEvent.getSender());
|
const member = new RoomMember(room.roomId, matrixEvent.getSender()!);
|
||||||
|
|
||||||
// We can't really reconstruct the whole room state from our
|
// We can't really reconstruct the whole room state from our
|
||||||
// EventIndex to calculate the correct display name. Use the
|
// EventIndex to calculate the correct display name. Use the
|
||||||
|
|
|
@ -213,7 +213,7 @@ export abstract class Call extends TypedEventEmitter<CallEvent, CallEventHandler
|
||||||
this.connectionState = ConnectionState.Connecting;
|
this.connectionState = ConnectionState.Connecting;
|
||||||
|
|
||||||
const { [MediaDeviceKindEnum.AudioInput]: audioInputs, [MediaDeviceKindEnum.VideoInput]: videoInputs } =
|
const { [MediaDeviceKindEnum.AudioInput]: audioInputs, [MediaDeviceKindEnum.VideoInput]: videoInputs } =
|
||||||
await MediaDeviceHandler.getDevices();
|
(await MediaDeviceHandler.getDevices())!;
|
||||||
|
|
||||||
let audioInput: MediaDeviceInfo | null = null;
|
let audioInput: MediaDeviceInfo | null = null;
|
||||||
if (!MediaDeviceHandler.startWithAudioMuted) {
|
if (!MediaDeviceHandler.startWithAudioMuted) {
|
||||||
|
@ -227,7 +227,7 @@ export abstract class Call extends TypedEventEmitter<CallEvent, CallEventHandler
|
||||||
}
|
}
|
||||||
|
|
||||||
const messagingStore = WidgetMessagingStore.instance;
|
const messagingStore = WidgetMessagingStore.instance;
|
||||||
this.messaging = messagingStore.getMessagingForUid(this.widgetUid);
|
this.messaging = messagingStore.getMessagingForUid(this.widgetUid) ?? null;
|
||||||
if (!this.messaging) {
|
if (!this.messaging) {
|
||||||
// The widget might still be initializing, so wait for it
|
// The widget might still be initializing, so wait for it
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -86,9 +86,9 @@ export default class Sizer {
|
||||||
|
|
||||||
public clearItemSize(item: HTMLElement): void {
|
public clearItemSize(item: HTMLElement): void {
|
||||||
if (this.vertical) {
|
if (this.vertical) {
|
||||||
item.style.height = null!;
|
item.style.removeProperty("height");
|
||||||
} else {
|
} else {
|
||||||
item.style.width = null!;
|
item.style.removeProperty("width");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -87,7 +87,7 @@ export class BreadcrumbsStore extends AsyncStoreWithClient<IState> {
|
||||||
await this.updateState({ enabled: SettingsStore.getValue("breadcrumbs", null) });
|
await this.updateState({ enabled: SettingsStore.getValue("breadcrumbs", null) });
|
||||||
}
|
}
|
||||||
} else if (payload.action === Action.ViewRoom) {
|
} else if (payload.action === Action.ViewRoom) {
|
||||||
if (payload.auto_join && !this.matrixClient.getRoom(payload.room_id)) {
|
if (payload.auto_join && payload.room_id && !this.matrixClient.getRoom(payload.room_id)) {
|
||||||
// Queue the room instead of pushing it immediately. We're probably just
|
// Queue the room instead of pushing it immediately. We're probably just
|
||||||
// waiting for a room join to complete.
|
// waiting for a room join to complete.
|
||||||
this.waitingRooms.push({ roomId: payload.room_id, addedTs: Date.now() });
|
this.waitingRooms.push({ roomId: payload.room_id, addedTs: Date.now() });
|
||||||
|
|
|
@ -426,6 +426,7 @@ export class OwnBeaconStore extends AsyncStoreWithClient<OwnBeaconStoreState> {
|
||||||
roomId: Room["roomId"],
|
roomId: Room["roomId"],
|
||||||
beaconInfoContent: MBeaconInfoEventContent,
|
beaconInfoContent: MBeaconInfoEventContent,
|
||||||
): Promise<void> => {
|
): Promise<void> => {
|
||||||
|
if (!this.matrixClient) return;
|
||||||
// explicitly stop any live beacons this user has
|
// explicitly stop any live beacons this user has
|
||||||
// to ensure they remain stopped
|
// to ensure they remain stopped
|
||||||
// if the new replacing beacon is redacted
|
// if the new replacing beacon is redacted
|
||||||
|
@ -435,7 +436,7 @@ export class OwnBeaconStore extends AsyncStoreWithClient<OwnBeaconStoreState> {
|
||||||
// eslint-disable-next-line camelcase
|
// eslint-disable-next-line camelcase
|
||||||
const { event_id } = await doMaybeLocalRoomAction(
|
const { event_id } = await doMaybeLocalRoomAction(
|
||||||
roomId,
|
roomId,
|
||||||
(actualRoomId: string) => this.matrixClient.unstable_createLiveBeacon(actualRoomId, beaconInfoContent),
|
(actualRoomId: string) => this.matrixClient!.unstable_createLiveBeacon(actualRoomId, beaconInfoContent),
|
||||||
this.matrixClient,
|
this.matrixClient,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -552,7 +553,7 @@ export class OwnBeaconStore extends AsyncStoreWithClient<OwnBeaconStoreState> {
|
||||||
const updateContent = makeBeaconInfoContent(timeout, live, description, assetType, timestamp);
|
const updateContent = makeBeaconInfoContent(timeout, live, description, assetType, timestamp);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await this.matrixClient.unstable_setLiveBeacon(beacon.roomId, updateContent);
|
await this.matrixClient!.unstable_setLiveBeacon(beacon.roomId, updateContent);
|
||||||
// cleanup any errors
|
// cleanup any errors
|
||||||
const hadError = this.beaconUpdateErrors.has(beacon.identifier);
|
const hadError = this.beaconUpdateErrors.has(beacon.identifier);
|
||||||
if (hadError) {
|
if (hadError) {
|
||||||
|
@ -576,7 +577,7 @@ export class OwnBeaconStore extends AsyncStoreWithClient<OwnBeaconStoreState> {
|
||||||
this.lastPublishedPositionTimestamp = Date.now();
|
this.lastPublishedPositionTimestamp = Date.now();
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
this.healthyLiveBeaconIds.map((beaconId) =>
|
this.healthyLiveBeaconIds.map((beaconId) =>
|
||||||
this.sendLocationToBeacon(this.beacons.get(beaconId), position),
|
this.beacons.has(beaconId) ? this.sendLocationToBeacon(this.beacons.get(beaconId)!, position) : null,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -589,7 +590,7 @@ export class OwnBeaconStore extends AsyncStoreWithClient<OwnBeaconStoreState> {
|
||||||
private sendLocationToBeacon = async (beacon: Beacon, { geoUri, timestamp }: TimedGeoUri): Promise<void> => {
|
private sendLocationToBeacon = async (beacon: Beacon, { geoUri, timestamp }: TimedGeoUri): Promise<void> => {
|
||||||
const content = makeBeaconContent(geoUri, timestamp, beacon.beaconInfoId);
|
const content = makeBeaconContent(geoUri, timestamp, beacon.beaconInfoId);
|
||||||
try {
|
try {
|
||||||
await this.matrixClient.sendEvent(beacon.roomId, M_BEACON.name, content);
|
await this.matrixClient!.sendEvent(beacon.roomId, M_BEACON.name, content);
|
||||||
this.incrementBeaconLocationPublishErrorCount(beacon.identifier, false);
|
this.incrementBeaconLocationPublishErrorCount(beacon.identifier, false);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error(error);
|
logger.error(error);
|
||||||
|
|
|
@ -27,7 +27,7 @@ export enum CachedRoomKey {
|
||||||
NotificationVolume,
|
NotificationVolume,
|
||||||
}
|
}
|
||||||
|
|
||||||
export class RoomEchoChamber extends GenericEchoChamber<RoomEchoContext, CachedRoomKey, RoomNotifState> {
|
export class RoomEchoChamber extends GenericEchoChamber<RoomEchoContext, CachedRoomKey, RoomNotifState | undefined> {
|
||||||
private properties = new Map<CachedRoomKey, RoomNotifState>();
|
private properties = new Map<CachedRoomKey, RoomNotifState>();
|
||||||
|
|
||||||
public constructor(context: RoomEchoContext) {
|
public constructor(context: RoomEchoContext) {
|
||||||
|
@ -67,11 +67,12 @@ export class RoomEchoChamber extends GenericEchoChamber<RoomEchoContext, CachedR
|
||||||
|
|
||||||
// ---- helpers below here ----
|
// ---- helpers below here ----
|
||||||
|
|
||||||
public get notificationVolume(): RoomNotifState {
|
public get notificationVolume(): RoomNotifState | undefined {
|
||||||
return this.getValue(CachedRoomKey.NotificationVolume);
|
return this.getValue(CachedRoomKey.NotificationVolume);
|
||||||
}
|
}
|
||||||
|
|
||||||
public set notificationVolume(v: RoomNotifState) {
|
public set notificationVolume(v: RoomNotifState | undefined) {
|
||||||
|
if (v === undefined) return;
|
||||||
this.setValue(
|
this.setValue(
|
||||||
_t("Change notification settings"),
|
_t("Change notification settings"),
|
||||||
CachedRoomKey.NotificationVolume,
|
CachedRoomKey.NotificationVolume,
|
||||||
|
|
|
@ -307,7 +307,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
|
||||||
|
|
||||||
public fetchSuggestedRooms = async (space: Room, limit = MAX_SUGGESTED_ROOMS): Promise<ISuggestedRoom[]> => {
|
public fetchSuggestedRooms = async (space: Room, limit = MAX_SUGGESTED_ROOMS): Promise<ISuggestedRoom[]> => {
|
||||||
try {
|
try {
|
||||||
const { rooms } = await this.matrixClient.getRoomHierarchy(space.roomId, limit, 1, true);
|
const { rooms } = await this.matrixClient!.getRoomHierarchy(space.roomId, limit, 1, true);
|
||||||
|
|
||||||
const viaMap = new EnhancedMap<string, Set<string>>();
|
const viaMap = new EnhancedMap<string, Set<string>>();
|
||||||
rooms.forEach((room) => {
|
rooms.forEach((room) => {
|
||||||
|
@ -979,7 +979,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
|
||||||
private onRoomState = (ev: MatrixEvent): void => {
|
private onRoomState = (ev: MatrixEvent): void => {
|
||||||
const room = this.matrixClient?.getRoom(ev.getRoomId());
|
const room = this.matrixClient?.getRoom(ev.getRoomId());
|
||||||
|
|
||||||
if (!room) return;
|
if (!this.matrixClient || !room) return;
|
||||||
|
|
||||||
switch (ev.getType()) {
|
switch (ev.getType()) {
|
||||||
case EventType.SpaceChild: {
|
case EventType.SpaceChild: {
|
||||||
|
|
|
@ -274,19 +274,22 @@ export class StopGapWidgetDriver extends WidgetDriver {
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
Object.entries(contentMap).flatMap(([userId, userContentMap]) =>
|
Object.entries(contentMap).flatMap(([userId, userContentMap]) =>
|
||||||
Object.entries(userContentMap).map(async ([deviceId, content]): Promise<void> => {
|
Object.entries(userContentMap).map(async ([deviceId, content]): Promise<void> => {
|
||||||
|
const devices = deviceInfoMap.get(userId);
|
||||||
|
if (!devices) return;
|
||||||
|
|
||||||
if (deviceId === "*") {
|
if (deviceId === "*") {
|
||||||
// Send the message to all devices we have keys for
|
// Send the message to all devices we have keys for
|
||||||
await client.encryptAndSendToDevices(
|
await client.encryptAndSendToDevices(
|
||||||
Array.from(deviceInfoMap.get(userId).values()).map((deviceInfo) => ({
|
Array.from(devices.values()).map((deviceInfo) => ({
|
||||||
userId,
|
userId,
|
||||||
deviceInfo,
|
deviceInfo,
|
||||||
})),
|
})),
|
||||||
content,
|
content,
|
||||||
);
|
);
|
||||||
} else {
|
} else if (devices.has(deviceId)) {
|
||||||
// Send the message to a specific device
|
// Send the message to a specific device
|
||||||
await client.encryptAndSendToDevices(
|
await client.encryptAndSendToDevices(
|
||||||
[{ userId, deviceInfo: deviceInfoMap.get(userId).get(deviceId) }],
|
[{ userId, deviceInfo: devices.get(deviceId)! }],
|
||||||
content,
|
content,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -363,7 +363,7 @@ export class WidgetLayoutStore extends ReadyWatchingStore {
|
||||||
}
|
}
|
||||||
|
|
||||||
public getContainerWidgets(room: Optional<Room>, container: Container): IApp[] {
|
public getContainerWidgets(room: Optional<Room>, container: Container): IApp[] {
|
||||||
return this.byRoom.get(room?.roomId)?.get(container)?.ordered || [];
|
return (room && this.byRoom.get(room.roomId)?.get(container)?.ordered) || [];
|
||||||
}
|
}
|
||||||
|
|
||||||
public isInContainer(room: Room, widget: IApp, container: Container): boolean {
|
public isInContainer(room: Room, widget: IApp, container: Container): boolean {
|
||||||
|
|
|
@ -58,7 +58,7 @@ export class WidgetPermissionStore {
|
||||||
return OIDCState.Unknown;
|
return OIDCState.Unknown;
|
||||||
}
|
}
|
||||||
|
|
||||||
public setOIDCState(widget: Widget, kind: WidgetKind, roomId: string, newState: OIDCState): void {
|
public setOIDCState(widget: Widget, kind: WidgetKind, roomId: string | undefined, newState: OIDCState): void {
|
||||||
const settingsKey = this.packSettingKey(widget, kind, roomId);
|
const settingsKey = this.packSettingKey(widget, kind, roomId);
|
||||||
|
|
||||||
let currentValues = SettingsStore.getValue<{
|
let currentValues = SettingsStore.getValue<{
|
||||||
|
|
|
@ -67,7 +67,7 @@ interface IActivityScore {
|
||||||
// We do this by checking every room to see who has sent a message in the last few hours, and giving them
|
// We do this by checking every room to see who has sent a message in the last few hours, and giving them
|
||||||
// a score which correlates to the freshness of their message. In theory, this results in suggestions
|
// a score which correlates to the freshness of their message. In theory, this results in suggestions
|
||||||
// which are closer to "continue this conversation" rather than "this person exists".
|
// which are closer to "continue this conversation" rather than "this person exists".
|
||||||
export function buildActivityScores(cli: MatrixClient): { [key: string]: IActivityScore | undefined } {
|
export function buildActivityScores(cli: MatrixClient): { [userId: string]: IActivityScore } {
|
||||||
const now = new Date().getTime();
|
const now = new Date().getTime();
|
||||||
const earliestAgeConsidered = now - 60 * 60 * 1000; // 1 hour ago
|
const earliestAgeConsidered = now - 60 * 60 * 1000; // 1 hour ago
|
||||||
const maxMessagesConsidered = 50; // so we don't iterate over a huge amount of traffic
|
const maxMessagesConsidered = 50; // so we don't iterate over a huge amount of traffic
|
||||||
|
@ -75,6 +75,7 @@ export function buildActivityScores(cli: MatrixClient): { [key: string]: IActivi
|
||||||
.flatMap((room) => takeRight(room.getLiveTimeline().getEvents(), maxMessagesConsidered))
|
.flatMap((room) => takeRight(room.getLiveTimeline().getEvents(), maxMessagesConsidered))
|
||||||
.filter((ev) => ev.getTs() > earliestAgeConsidered);
|
.filter((ev) => ev.getTs() > earliestAgeConsidered);
|
||||||
const senderEvents = groupBy(events, (ev) => ev.getSender());
|
const senderEvents = groupBy(events, (ev) => ev.getSender());
|
||||||
|
// If the iteratee in mapValues returns undefined that key will be removed from the resultant object
|
||||||
return mapValues(senderEvents, (events) => {
|
return mapValues(senderEvents, (events) => {
|
||||||
if (!events.length) return;
|
if (!events.length) return;
|
||||||
const lastEvent = maxBy(events, (ev) => ev.getTs())!;
|
const lastEvent = maxBy(events, (ev) => ev.getTs())!;
|
||||||
|
@ -87,7 +88,7 @@ export function buildActivityScores(cli: MatrixClient): { [key: string]: IActivi
|
||||||
// an approximate maximum for being selected.
|
// an approximate maximum for being selected.
|
||||||
score: Math.max(1, inverseTime / (15 * 60 * 1000)), // 15min segments to keep scores sane
|
score: Math.max(1, inverseTime / (15 * 60 * 1000)), // 15min segments to keep scores sane
|
||||||
};
|
};
|
||||||
});
|
}) as { [key: string]: IActivityScore };
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IMemberScore {
|
interface IMemberScore {
|
||||||
|
@ -96,13 +97,14 @@ interface IMemberScore {
|
||||||
numRooms: number;
|
numRooms: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function buildMemberScores(cli: MatrixClient): { [key: string]: IMemberScore | undefined } {
|
export function buildMemberScores(cli: MatrixClient): { [userId: string]: IMemberScore } {
|
||||||
const maxConsideredMembers = 200;
|
const maxConsideredMembers = 200;
|
||||||
const consideredRooms = joinedRooms(cli).filter((room) => room.getJoinedMemberCount() < maxConsideredMembers);
|
const consideredRooms = joinedRooms(cli).filter((room) => room.getJoinedMemberCount() < maxConsideredMembers);
|
||||||
const memberPeerEntries = consideredRooms.flatMap((room) =>
|
const memberPeerEntries = consideredRooms.flatMap((room) =>
|
||||||
room.getJoinedMembers().map((member) => ({ member, roomSize: room.getJoinedMemberCount() })),
|
room.getJoinedMembers().map((member) => ({ member, roomSize: room.getJoinedMemberCount() })),
|
||||||
);
|
);
|
||||||
const userMeta = groupBy(memberPeerEntries, ({ member }) => member.userId);
|
const userMeta = groupBy(memberPeerEntries, ({ member }) => member.userId);
|
||||||
|
// If the iteratee in mapValues returns undefined that key will be removed from the resultant object
|
||||||
return mapValues(userMeta, (roomMemberships) => {
|
return mapValues(userMeta, (roomMemberships) => {
|
||||||
if (!roomMemberships.length) return;
|
if (!roomMemberships.length) return;
|
||||||
const maximumPeers = maxConsideredMembers * roomMemberships.length;
|
const maximumPeers = maxConsideredMembers * roomMemberships.length;
|
||||||
|
@ -112,5 +114,5 @@ export function buildMemberScores(cli: MatrixClient): { [key: string]: IMemberSc
|
||||||
numRooms: roomMemberships.length,
|
numRooms: roomMemberships.length,
|
||||||
score: Math.max(0, Math.pow(1 - totalPeers / maximumPeers, 5)),
|
score: Math.max(0, Math.pow(1 - totalPeers / maximumPeers, 5)),
|
||||||
};
|
};
|
||||||
});
|
}) as { [userId: string]: IMemberScore };
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,7 +28,7 @@ export const deviceNotificationSettingsKeys = [
|
||||||
"audioNotificationsEnabled",
|
"audioNotificationsEnabled",
|
||||||
];
|
];
|
||||||
|
|
||||||
export function getLocalNotificationAccountDataEventType(deviceId: string): string {
|
export function getLocalNotificationAccountDataEventType(deviceId: string | null): string {
|
||||||
return `${LOCAL_NOTIFICATION_SETTINGS_PREFIX.name}.${deviceId}`;
|
return `${LOCAL_NOTIFICATION_SETTINGS_PREFIX.name}.${deviceId}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -43,7 +43,7 @@ describe("<MessageEditHistory />", () => {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
function mockEdits(...edits: { msg: string; ts: number | undefined }[]) {
|
function mockEdits(...edits: { msg: string; ts?: number }[]) {
|
||||||
client.relations.mockImplementation(() =>
|
client.relations.mockImplementation(() =>
|
||||||
Promise.resolve({
|
Promise.resolve({
|
||||||
events: edits.map(
|
events: edits.map(
|
||||||
|
|
Loading…
Reference in a new issue