Conform more of the codebase to strictNullChecks (#10358

* Conform more of the codebase to `strictNullChecks`

* Fix types

* Iterate

* Iterate
This commit is contained in:
Michael Telatynski 2023-03-13 15:07:20 +00:00 committed by GitHub
parent 41d88ad6ae
commit 503df62191
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
76 changed files with 323 additions and 327 deletions

View file

@ -21,7 +21,7 @@ import { _t, _td } from "../../../languageHandler";
import withValidation, { IFieldState, IValidationResult } from "../elements/Validation"; import withValidation, { IFieldState, IValidationResult } from "../elements/Validation";
import * as Email from "../../../email"; import * as Email from "../../../email";
interface IProps extends Omit<IInputProps, "onValidate"> { interface IProps extends Omit<IInputProps, "onValidate" | "element"> {
id?: string; id?: string;
fieldRef?: RefCallback<Field> | RefObject<Field>; fieldRef?: RefCallback<Field> | RefObject<Field>;
value: string; value: string;

View file

@ -20,7 +20,7 @@ import Field, { IInputProps } from "../elements/Field";
import withValidation, { IFieldState, IValidationResult } from "../elements/Validation"; import withValidation, { IFieldState, IValidationResult } from "../elements/Validation";
import { _t, _td } from "../../../languageHandler"; import { _t, _td } from "../../../languageHandler";
interface IProps extends Omit<IInputProps, "onValidate" | "label"> { interface IProps extends Omit<IInputProps, "onValidate" | "label" | "element"> {
id?: string; id?: string;
fieldRef?: RefCallback<Field> | RefObject<Field>; fieldRef?: RefCallback<Field> | RefObject<Field>;
autoComplete?: string; autoComplete?: string;

View file

@ -23,7 +23,7 @@ import withValidation, { IFieldState, IValidationResult } from "../elements/Vali
import { _t, _td } from "../../../languageHandler"; import { _t, _td } from "../../../languageHandler";
import Field, { IInputProps } from "../elements/Field"; import Field, { IInputProps } from "../elements/Field";
interface IProps extends Omit<IInputProps, "onValidate"> { interface IProps extends Omit<IInputProps, "onValidate" | "element"> {
autoFocus?: boolean; autoFocus?: boolean;
id?: string; id?: string;
className?: string; className?: string;

View file

@ -179,7 +179,7 @@ export default class MessageContextMenu extends React.Component<IProps, IState>
private isPinned(): boolean { private isPinned(): boolean {
const room = MatrixClientPeg.get().getRoom(this.props.mxEvent.getRoomId()); const room = MatrixClientPeg.get().getRoom(this.props.mxEvent.getRoomId());
const pinnedEvent = room.currentState.getStateEvents(EventType.RoomPinnedEvents, ""); const pinnedEvent = room?.currentState.getStateEvents(EventType.RoomPinnedEvents, "");
if (!pinnedEvent) return false; if (!pinnedEvent) return false;
const content = pinnedEvent.getContent(); const content = pinnedEvent.getContent();
return content.pinned && Array.isArray(content.pinned) && content.pinned.includes(this.props.mxEvent.getId()); return content.pinned && Array.isArray(content.pinned) && content.pinned.includes(this.props.mxEvent.getId());
@ -389,7 +389,7 @@ export default class MessageContextMenu extends React.Component<IProps, IState>
timelineRenderingType === TimelineRenderingType.ThreadsList; timelineRenderingType === TimelineRenderingType.ThreadsList;
const isThreadRootEvent = isThread && mxEvent?.getThread()?.rootEvent === mxEvent; const isThreadRootEvent = isThread && mxEvent?.getThread()?.rootEvent === mxEvent;
let resendReactionsButton: JSX.Element; let resendReactionsButton: JSX.Element | undefined;
if (!mxEvent.isRedacted() && unsentReactionsCount !== 0) { if (!mxEvent.isRedacted() && unsentReactionsCount !== 0) {
resendReactionsButton = ( resendReactionsButton = (
<IconizedContextMenuOption <IconizedContextMenuOption
@ -400,7 +400,7 @@ export default class MessageContextMenu extends React.Component<IProps, IState>
); );
} }
let redactButton: JSX.Element; let redactButton: JSX.Element | undefined;
if (isSent && this.state.canRedact) { if (isSent && this.state.canRedact) {
redactButton = ( redactButton = (
<IconizedContextMenuOption <IconizedContextMenuOption
@ -411,7 +411,7 @@ export default class MessageContextMenu extends React.Component<IProps, IState>
); );
} }
let openInMapSiteButton: JSX.Element; let openInMapSiteButton: JSX.Element | undefined;
const shareableLocationEvent = getShareableLocationEvent(mxEvent, cli); const shareableLocationEvent = getShareableLocationEvent(mxEvent, cli);
if (shareableLocationEvent) { if (shareableLocationEvent) {
const mapSiteLink = createMapSiteLinkFromEvent(shareableLocationEvent); const mapSiteLink = createMapSiteLinkFromEvent(shareableLocationEvent);
@ -430,7 +430,7 @@ export default class MessageContextMenu extends React.Component<IProps, IState>
); );
} }
let forwardButton: JSX.Element; let forwardButton: JSX.Element | undefined;
const forwardableEvent = getForwardableEvent(mxEvent, cli); const forwardableEvent = getForwardableEvent(mxEvent, cli);
if (contentActionable && forwardableEvent) { if (contentActionable && forwardableEvent) {
forwardButton = ( forwardButton = (
@ -442,7 +442,7 @@ export default class MessageContextMenu extends React.Component<IProps, IState>
); );
} }
let pinButton: JSX.Element; let pinButton: JSX.Element | undefined;
if (contentActionable && this.state.canPin) { if (contentActionable && this.state.canPin) {
pinButton = ( pinButton = (
<IconizedContextMenuOption <IconizedContextMenuOption
@ -462,7 +462,7 @@ export default class MessageContextMenu extends React.Component<IProps, IState>
/> />
); );
let unhidePreviewButton: JSX.Element; let unhidePreviewButton: JSX.Element | undefined;
if (eventTileOps?.isWidgetHidden()) { if (eventTileOps?.isWidgetHidden()) {
unhidePreviewButton = ( unhidePreviewButton = (
<IconizedContextMenuOption <IconizedContextMenuOption
@ -473,7 +473,7 @@ export default class MessageContextMenu extends React.Component<IProps, IState>
); );
} }
let permalinkButton: JSX.Element; let permalinkButton: JSX.Element | undefined;
if (permalink) { if (permalink) {
permalinkButton = ( permalinkButton = (
<IconizedContextMenuOption <IconizedContextMenuOption
@ -493,7 +493,7 @@ export default class MessageContextMenu extends React.Component<IProps, IState>
); );
} }
let endPollButton: JSX.Element; let endPollButton: JSX.Element | undefined;
if (this.canEndPoll(mxEvent)) { if (this.canEndPoll(mxEvent)) {
endPollButton = ( endPollButton = (
<IconizedContextMenuOption <IconizedContextMenuOption
@ -504,7 +504,7 @@ export default class MessageContextMenu extends React.Component<IProps, IState>
); );
} }
let quoteButton: JSX.Element; let quoteButton: JSX.Element | undefined;
if (eventTileOps && canSendMessages) { if (eventTileOps && canSendMessages) {
// this event is rendered using TextualBody // this event is rendered using TextualBody
quoteButton = ( quoteButton = (
@ -517,7 +517,7 @@ export default class MessageContextMenu extends React.Component<IProps, IState>
} }
// Bridges can provide a 'external_url' to link back to the source. // Bridges can provide a 'external_url' to link back to the source.
let externalURLButton: JSX.Element; let externalURLButton: JSX.Element | undefined;
if ( if (
typeof mxEvent.getContent().external_url === "string" && typeof mxEvent.getContent().external_url === "string" &&
isUrlPermitted(mxEvent.getContent().external_url) isUrlPermitted(mxEvent.getContent().external_url)
@ -540,7 +540,7 @@ export default class MessageContextMenu extends React.Component<IProps, IState>
); );
} }
let collapseReplyChainButton: JSX.Element; let collapseReplyChainButton: JSX.Element | undefined;
if (collapseReplyChain) { if (collapseReplyChain) {
collapseReplyChainButton = ( collapseReplyChainButton = (
<IconizedContextMenuOption <IconizedContextMenuOption
@ -551,7 +551,7 @@ export default class MessageContextMenu extends React.Component<IProps, IState>
); );
} }
let jumpToRelatedEventButton: JSX.Element; let jumpToRelatedEventButton: JSX.Element | undefined;
const relatedEventId = mxEvent.getWireContent()?.["m.relates_to"]?.event_id; const relatedEventId = mxEvent.getWireContent()?.["m.relates_to"]?.event_id;
if (relatedEventId && SettingsStore.getValue("developerMode")) { if (relatedEventId && SettingsStore.getValue("developerMode")) {
jumpToRelatedEventButton = ( jumpToRelatedEventButton = (
@ -563,7 +563,7 @@ export default class MessageContextMenu extends React.Component<IProps, IState>
); );
} }
let reportEventButton: JSX.Element; let reportEventButton: JSX.Element | undefined;
if (mxEvent.getSender() !== me) { if (mxEvent.getSender() !== me) {
reportEventButton = ( reportEventButton = (
<IconizedContextMenuOption <IconizedContextMenuOption
@ -574,7 +574,7 @@ export default class MessageContextMenu extends React.Component<IProps, IState>
); );
} }
let copyLinkButton: JSX.Element; let copyLinkButton: JSX.Element | undefined;
if (link) { if (link) {
copyLinkButton = ( copyLinkButton = (
<IconizedContextMenuOption <IconizedContextMenuOption
@ -594,7 +594,7 @@ export default class MessageContextMenu extends React.Component<IProps, IState>
); );
} }
let copyButton: JSX.Element; let copyButton: JSX.Element | undefined;
if (rightClick && getSelectedText()) { if (rightClick && getSelectedText()) {
copyButton = ( copyButton = (
<IconizedContextMenuOption <IconizedContextMenuOption
@ -606,7 +606,7 @@ export default class MessageContextMenu extends React.Component<IProps, IState>
); );
} }
let editButton: JSX.Element; let editButton: JSX.Element | undefined;
if (rightClick && canEditContent(mxEvent)) { if (rightClick && canEditContent(mxEvent)) {
editButton = ( editButton = (
<IconizedContextMenuOption <IconizedContextMenuOption
@ -617,7 +617,7 @@ export default class MessageContextMenu extends React.Component<IProps, IState>
); );
} }
let replyButton: JSX.Element; let replyButton: JSX.Element | undefined;
if (rightClick && contentActionable && canSendMessages) { if (rightClick && contentActionable && canSendMessages) {
replyButton = ( replyButton = (
<IconizedContextMenuOption <IconizedContextMenuOption
@ -628,7 +628,7 @@ export default class MessageContextMenu extends React.Component<IProps, IState>
); );
} }
let replyInThreadButton: JSX.Element; let replyInThreadButton: JSX.Element | undefined;
if ( if (
rightClick && rightClick &&
contentActionable && contentActionable &&
@ -639,7 +639,7 @@ export default class MessageContextMenu extends React.Component<IProps, IState>
replyInThreadButton = <ReplyInThreadButton mxEvent={mxEvent} closeMenu={this.closeMenu} />; replyInThreadButton = <ReplyInThreadButton mxEvent={mxEvent} closeMenu={this.closeMenu} />;
} }
let reactButton; let reactButton: JSX.Element | undefined;
if (rightClick && contentActionable && canReact) { if (rightClick && contentActionable && canReact) {
reactButton = ( reactButton = (
<IconizedContextMenuOption <IconizedContextMenuOption
@ -651,7 +651,7 @@ export default class MessageContextMenu extends React.Component<IProps, IState>
); );
} }
let viewInRoomButton: JSX.Element; let viewInRoomButton: JSX.Element | undefined;
if (isThreadRootEvent) { if (isThreadRootEvent) {
viewInRoomButton = ( viewInRoomButton = (
<IconizedContextMenuOption <IconizedContextMenuOption
@ -662,7 +662,7 @@ export default class MessageContextMenu extends React.Component<IProps, IState>
); );
} }
let nativeItemsList: JSX.Element; let nativeItemsList: JSX.Element | undefined;
if (copyButton || copyLinkButton) { if (copyButton || copyLinkButton) {
nativeItemsList = ( nativeItemsList = (
<IconizedContextMenuOptionList> <IconizedContextMenuOptionList>
@ -672,7 +672,7 @@ export default class MessageContextMenu extends React.Component<IProps, IState>
); );
} }
let quickItemsList: JSX.Element; let quickItemsList: JSX.Element | undefined;
if (editButton || replyButton || reactButton) { if (editButton || replyButton || reactButton) {
quickItemsList = ( quickItemsList = (
<IconizedContextMenuOptionList> <IconizedContextMenuOptionList>
@ -703,12 +703,12 @@ export default class MessageContextMenu extends React.Component<IProps, IState>
</IconizedContextMenuOptionList> </IconizedContextMenuOptionList>
); );
let redactItemList: JSX.Element; let redactItemList: JSX.Element | undefined;
if (redactButton) { if (redactButton) {
redactItemList = <IconizedContextMenuOptionList red>{redactButton}</IconizedContextMenuOptionList>; redactItemList = <IconizedContextMenuOptionList red>{redactButton}</IconizedContextMenuOptionList>;
} }
let reactionPicker: JSX.Element; let reactionPicker: JSX.Element | undefined;
if (this.state.reactionPickerDisplayed) { if (this.state.reactionPickerDisplayed) {
const buttonRect = (this.reactButtonRef.current as HTMLElement)?.getBoundingClientRect(); const buttonRect = (this.reactButtonRef.current as HTMLElement)?.getBoundingClientRect();
reactionPicker = ( reactionPicker = (

View file

@ -64,7 +64,7 @@ interface ICanEditLevelFieldProps {
} }
const CanEditLevelField: React.FC<ICanEditLevelFieldProps> = ({ setting, roomId, level }) => { const CanEditLevelField: React.FC<ICanEditLevelFieldProps> = ({ setting, roomId, level }) => {
const canEdit = SettingsStore.canSetValue(setting, roomId, level); const canEdit = SettingsStore.canSetValue(setting, roomId ?? null, level);
const className = canEdit ? "mx_DevTools_SettingsExplorer_mutable" : "mx_DevTools_SettingsExplorer_immutable"; const className = canEdit ? "mx_DevTools_SettingsExplorer_mutable" : "mx_DevTools_SettingsExplorer_immutable";
return ( return (
<td className={className}> <td className={className}>

View file

@ -44,7 +44,7 @@ const validServer = withValidation<undefined, { error?: MatrixError }>({
// check if we can successfully load this server's room directory // check if we can successfully load this server's room directory
await MatrixClientPeg.get().publicRooms({ await MatrixClientPeg.get().publicRooms({
limit: 1, limit: 1,
server: value, server: value ?? undefined,
}); });
return {}; return {};
} catch (error) { } catch (error) {

View file

@ -14,7 +14,7 @@
limitations under the License. limitations under the License.
*/ */
import React, { HTMLAttributes, InputHTMLAttributes, ReactHTML, ReactNode } from "react"; import React, { HTMLAttributes, InputHTMLAttributes, ReactNode } from "react";
import classnames from "classnames"; import classnames from "classnames";
import { getKeyBindingsManager } from "../../../KeyBindingsManager"; import { getKeyBindingsManager } from "../../../KeyBindingsManager";
@ -91,7 +91,7 @@ export interface IAccessibleButtonProps extends React.InputHTMLAttributes<Elemen
* @returns {Object} rendered react * @returns {Object} rendered react
*/ */
export default function AccessibleButton<T extends keyof JSX.IntrinsicElements>({ export default function AccessibleButton<T extends keyof JSX.IntrinsicElements>({
element, element = "div" as T,
onClick, onClick,
children, children,
kind, kind,
@ -169,7 +169,6 @@ export default function AccessibleButton<T extends keyof JSX.IntrinsicElements>(
} }
AccessibleButton.defaultProps = { AccessibleButton.defaultProps = {
element: "div" as keyof ReactHTML,
role: "button", role: "button",
tabIndex: 0, tabIndex: 0,
}; };

View file

@ -61,8 +61,6 @@ export default class AppPermission extends React.Component<IProps, IState> {
// Set all this into the initial state // Set all this into the initial state
this.state = { this.state = {
widgetDomain: null,
isWrapped: null,
roomMember, roomMember,
...urlInfo, ...urlInfo,
}; };

View file

@ -33,7 +33,8 @@ export function getDesktopCapturerSources(): Promise<Array<DesktopCapturerSource
}, },
types: ["screen", "window"], types: ["screen", "window"],
}; };
return PlatformPeg.get().getDesktopCapturerSources(options); const plaf = PlatformPeg.get();
return plaf ? plaf?.getDesktopCapturerSources(options) : Promise.resolve<DesktopCapturerSource[]>([]);
} }
export enum Tabs { export enum Tabs {

View file

@ -69,7 +69,7 @@ export default class DialogButtons extends React.Component<IProps> {
}; };
private onCancelClick = (event: React.MouseEvent): void => { private onCancelClick = (event: React.MouseEvent): void => {
this.props.onCancel(event); this.props.onCancel?.(event);
}; };
public render(): React.ReactNode { public render(): React.ReactNode {
@ -77,9 +77,9 @@ export default class DialogButtons extends React.Component<IProps> {
if (this.props.primaryButtonClass) { if (this.props.primaryButtonClass) {
primaryButtonClassName += " " + this.props.primaryButtonClass; primaryButtonClassName += " " + this.props.primaryButtonClass;
} }
let cancelButton;
if (this.props.cancelButton || this.props.hasCancel) { let cancelButton: JSX.Element | undefined;
if (this.props.hasCancel) {
cancelButton = ( cancelButton = (
<button <button
// important: the default type is 'submit' and this button comes before the // important: the default type is 'submit' and this button comes before the
@ -95,7 +95,7 @@ export default class DialogButtons extends React.Component<IProps> {
); );
} }
let additive = null; let additive: JSX.Element | undefined;
if (this.props.additive) { if (this.props.additive) {
additive = <div className="mx_Dialog_buttons_additive">{this.props.additive}</div>; additive = <div className="mx_Dialog_buttons_additive">{this.props.additive}</div>;
} }

View file

@ -113,8 +113,8 @@ interface IState {
*/ */
export default class Dropdown extends React.Component<DropdownProps, IState> { export default class Dropdown extends React.Component<DropdownProps, IState> {
private readonly buttonRef = createRef<HTMLDivElement>(); private readonly buttonRef = createRef<HTMLDivElement>();
private dropdownRootElement: HTMLDivElement = null; private dropdownRootElement: HTMLDivElement | null = null;
private ignoreEvent: MouseEvent = null; private ignoreEvent: MouseEvent | null = null;
private childrenByKey: Record<string, ReactNode> = {}; private childrenByKey: Record<string, ReactNode> = {};
public constructor(props: DropdownProps) { public constructor(props: DropdownProps) {
@ -373,18 +373,14 @@ export default class Dropdown extends React.Component<DropdownProps, IState> {
); );
} }
const dropdownClasses: Record<string, boolean> = { const dropdownClasses = classnames("mx_Dropdown", this.props.className, {
mx_Dropdown: true, mx_Dropdown_disabled: !!this.props.disabled,
mx_Dropdown_disabled: this.props.disabled, });
};
if (this.props.className) {
dropdownClasses[this.props.className] = true;
}
// Note the menu sits inside the AccessibleButton div so it's anchored // Note the menu sits inside the AccessibleButton div so it's anchored
// to the input, but overflows below it. The root contains both. // to the input, but overflows below it. The root contains both.
return ( return (
<div className={classnames(dropdownClasses)} ref={this.collectRoot}> <div className={dropdownClasses} ref={this.collectRoot}>
<AccessibleButton <AccessibleButton
className="mx_Dropdown_input mx_no_textinput" className="mx_Dropdown_input mx_no_textinput"
onClick={this.onAccessibleButtonClick} onClick={this.onAccessibleButtonClick}

View file

@ -30,7 +30,7 @@ const EffectsOverlay: FunctionComponent<IProps> = ({ roomWidth }) => {
const canvasRef = useRef<HTMLCanvasElement>(null); const canvasRef = useRef<HTMLCanvasElement>(null);
const effectsRef = useRef<Map<string, ICanvasEffect>>(new Map<string, ICanvasEffect>()); const effectsRef = useRef<Map<string, ICanvasEffect>>(new Map<string, ICanvasEffect>());
const lazyLoadEffectModule = async (name: string): Promise<ICanvasEffect> => { const lazyLoadEffectModule = async (name: string): Promise<ICanvasEffect | null> => {
if (!name) return null; if (!name) return null;
let effect: ICanvasEffect | null = effectsRef.current.get(name) || null; let effect: ICanvasEffect | null = effectsRef.current.get(name) || null;
if (effect === null) { if (effect === null) {
@ -38,7 +38,7 @@ const EffectsOverlay: FunctionComponent<IProps> = ({ roomWidth }) => {
try { try {
const { default: Effect } = await import(`../../../effects/${name}`); const { default: Effect } = await import(`../../../effects/${name}`);
effect = new Effect(options); effect = new Effect(options);
effectsRef.current.set(name, effect); effectsRef.current.set(name, effect!);
} catch (err) { } catch (err) {
logger.warn(`Unable to load effect module at '../../../effects/${name}.`, err); logger.warn(`Unable to load effect module at '../../../effects/${name}.`, err);
} }
@ -70,7 +70,7 @@ const EffectsOverlay: FunctionComponent<IProps> = ({ roomWidth }) => {
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
const currentEffects = effectsRef.current; // this is not a react node ref, warning can be safely ignored const currentEffects = effectsRef.current; // this is not a react node ref, warning can be safely ignored
for (const effect in currentEffects) { for (const effect in currentEffects) {
const effectModule: ICanvasEffect = currentEffects.get(effect); const effectModule: ICanvasEffect = currentEffects.get(effect)!;
if (effectModule && effectModule.isRunning) { if (effectModule && effectModule.isRunning) {
effectModule.stop(); effectModule.stop();
} }

View file

@ -30,7 +30,7 @@ interface Props {
} }
interface IState { interface IState {
error: Error; error?: Error;
} }
/** /**
@ -41,9 +41,7 @@ export default class ErrorBoundary extends React.PureComponent<Props, IState> {
public constructor(props: Props) { public constructor(props: Props) {
super(props); super(props);
this.state = { this.state = {};
error: null,
};
} }
public static getDerivedStateFromError(error: Error): Partial<IState> { public static getDerivedStateFromError(error: Error): Partial<IState> {
@ -66,7 +64,7 @@ export default class ErrorBoundary extends React.PureComponent<Props, IState> {
MatrixClientPeg.get() MatrixClientPeg.get()
.store.deleteAllData() .store.deleteAllData()
.then(() => { .then(() => {
PlatformPeg.get().reload(); PlatformPeg.get()?.reload();
}); });
}; };
@ -121,7 +119,7 @@ export default class ErrorBoundary extends React.PureComponent<Props, IState> {
); );
} }
let clearCacheButton: JSX.Element; let clearCacheButton: JSX.Element | undefined;
// we only show this button if there is an initialised MatrixClient otherwise we can't clear the cache // we only show this button if there is an initialised MatrixClient otherwise we can't clear the cache
if (MatrixClientPeg.get()) { if (MatrixClientPeg.get()) {
clearCacheButton = ( clearCacheButton = (

View file

@ -113,7 +113,7 @@ export default class EventListSummary extends React.Component<IProps> {
private generateSummary( private generateSummary(
eventAggregates: Record<string, string[]>, eventAggregates: Record<string, string[]>,
orderedTransitionSequences: string[], orderedTransitionSequences: string[],
): string | JSX.Element { ): ReactNode {
const summaries = orderedTransitionSequences.map((transitions) => { const summaries = orderedTransitionSequences.map((transitions) => {
const userNames = eventAggregates[transitions]; const userNames = eventAggregates[transitions];
const nameList = this.renderNameList(userNames); const nameList = this.renderNameList(userNames);
@ -392,7 +392,7 @@ export default class EventListSummary extends React.Component<IProps> {
* @returns {string?} the transition type given to this event. This defaults to `null` * @returns {string?} the transition type given to this event. This defaults to `null`
* if a transition is not recognised. * if a transition is not recognised.
*/ */
private static getTransition(e: IUserEvents): TransitionType { private static getTransition(e: IUserEvents): TransitionType | null {
if (e.mxEvent.isRedacted()) { if (e.mxEvent.isRedacted()) {
return TransitionType.MessageRemoved; return TransitionType.MessageRemoved;
} }

View file

@ -79,7 +79,7 @@ export interface IInputProps extends IProps, InputHTMLAttributes<HTMLInputElemen
// The ref pass through to the input // The ref pass through to the input
inputRef?: RefObject<HTMLInputElement>; inputRef?: RefObject<HTMLInputElement>;
// The element to create. Defaults to "input". // The element to create. Defaults to "input".
element?: "input"; element: "input";
// The input's value. This is a controlled component, so the value is required. // The input's value. This is a controlled component, so the value is required.
value: string; value: string;
} }
@ -204,7 +204,7 @@ export default class Field extends React.PureComponent<PropShapes, IState> {
const value = this.inputRef.current?.value ?? null; const value = this.inputRef.current?.value ?? null;
const { valid, feedback } = await this.props.onValidate({ const { valid, feedback } = await this.props.onValidate({
value, value,
focused, focused: !!focused,
allowEmpty, allowEmpty,
}); });

View file

@ -36,7 +36,7 @@ interface IProps {
// The list of room members for which to show avatars next to the summary // The list of room members for which to show avatars next to the summary
"summaryMembers"?: RoomMember[]; "summaryMembers"?: RoomMember[];
// The text to show as the summary of this event list // The text to show as the summary of this event list
"summaryText"?: string | JSX.Element; "summaryText"?: ReactNode;
// An array of EventTiles to render when expanded // An array of EventTiles to render when expanded
"children": ReactNode[]; "children": ReactNode[];
// Called when the event list expansion is toggled // Called when the event list expansion is toggled

View file

@ -29,7 +29,7 @@ interface IProps {
interface IState { interface IState {
width: number; width: number;
IRCLayoutRoot: HTMLElement; IRCLayoutRoot: HTMLElement | null;
} }
export default class IRCTimelineProfileResizer extends React.Component<IProps, IState> { export default class IRCTimelineProfileResizer extends React.Component<IProps, IState> {
@ -77,7 +77,7 @@ export default class IRCTimelineProfileResizer extends React.Component<IProps, I
}; };
private updateCSSWidth(newWidth: number): void { private updateCSSWidth(newWidth: number): void {
this.state.IRCLayoutRoot.style.setProperty("--name-width", newWidth + "px"); this.state.IRCLayoutRoot?.style.setProperty("--name-width", newWidth + "px");
} }
private onMoueUp = (): void => { private onMoueUp = (): void => {

View file

@ -16,7 +16,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import React, { createRef } from "react"; import React, { createRef, CSSProperties } from "react";
import FocusLock from "react-focus-lock"; import FocusLock from "react-focus-lock";
import { MatrixEvent } from "matrix-js-sdk/src/models/event"; import { MatrixEvent } from "matrix-js-sdk/src/models/event";
@ -395,7 +395,7 @@ export default class ImageView extends React.Component<IProps, IState> {
}; };
private renderContextMenu(): JSX.Element { private renderContextMenu(): JSX.Element {
let contextMenu = null; let contextMenu: JSX.Element | undefined;
if (this.state.contextMenuDisplayed) { if (this.state.contextMenuDisplayed) {
contextMenu = ( contextMenu = (
<MessageContextMenu <MessageContextMenu
@ -419,11 +419,6 @@ export default class ImageView extends React.Component<IProps, IState> {
else if (this.state.moving || !this.imageIsLoaded) transitionClassName = ""; else if (this.state.moving || !this.imageIsLoaded) transitionClassName = "";
else transitionClassName = "mx_ImageView_image_animating"; else transitionClassName = "mx_ImageView_image_animating";
let cursor;
if (this.state.moving) cursor = "grabbing";
else if (this.state.zoom === this.state.minZoom) cursor = "zoom-in";
else cursor = "zoom-out";
const rotationDegrees = this.state.rotation + "deg"; const rotationDegrees = this.state.rotation + "deg";
const zoom = this.state.zoom; const zoom = this.state.zoom;
const translatePixelsX = this.state.translationX + "px"; const translatePixelsX = this.state.translationX + "px";
@ -432,15 +427,18 @@ export default class ImageView extends React.Component<IProps, IState> {
// First, we translate and only then we rotate, otherwise // First, we translate and only then we rotate, otherwise
// we would apply the translation to an already rotated // we would apply the translation to an already rotated
// image causing it translate in the wrong direction. // image causing it translate in the wrong direction.
const style = { const style: CSSProperties = {
cursor: cursor,
transform: `translateX(${translatePixelsX}) transform: `translateX(${translatePixelsX})
translateY(${translatePixelsY}) translateY(${translatePixelsY})
scale(${zoom}) scale(${zoom})
rotate(${rotationDegrees})`, rotate(${rotationDegrees})`,
}; };
let info; if (this.state.moving) style.cursor = "grabbing";
else if (this.state.zoom === this.state.minZoom) style.cursor = "zoom-in";
else style.cursor = "zoom-out";
let info: JSX.Element | undefined;
if (showEventMeta) { if (showEventMeta) {
const mxEvent = this.props.mxEvent; const mxEvent = this.props.mxEvent;
const showTwelveHour = SettingsStore.getValue("showTwelveHourTimestamps"); const showTwelveHour = SettingsStore.getValue("showTwelveHourTimestamps");
@ -449,7 +447,7 @@ export default class ImageView extends React.Component<IProps, IState> {
permalink = this.props.permalinkCreator.forEvent(this.props.mxEvent.getId()); permalink = this.props.permalinkCreator.forEvent(this.props.mxEvent.getId());
} }
const senderName = mxEvent.sender ? mxEvent.sender.name : mxEvent.getSender(); const senderName = mxEvent.sender?.name ?? mxEvent.getSender();
const sender = <div className="mx_ImageView_info_sender">{senderName}</div>; const sender = <div className="mx_ImageView_info_sender">{senderName}</div>;
const messageTimestamp = ( const messageTimestamp = (
<a <a
@ -491,7 +489,7 @@ export default class ImageView extends React.Component<IProps, IState> {
info = <div />; info = <div />;
} }
let contextMenuButton; let contextMenuButton: JSX.Element | undefined;
if (this.props.mxEvent) { if (this.props.mxEvent) {
contextMenuButton = ( contextMenuButton = (
<ContextMenuTooltipButton <ContextMenuTooltipButton
@ -519,7 +517,7 @@ export default class ImageView extends React.Component<IProps, IState> {
/> />
); );
let title: JSX.Element; let title: JSX.Element | undefined;
if (this.props.mxEvent?.getContent()) { if (this.props.mxEvent?.getContent()) {
title = ( title = (
<div className="mx_ImageView_title"> <div className="mx_ImageView_title">

View file

@ -293,7 +293,7 @@ interface IProps {
} }
interface IState { interface IState {
contentRect: DOMRect; contentRect?: DOMRect;
visible: boolean; visible: boolean;
} }
@ -312,7 +312,6 @@ export default class InteractiveTooltip extends React.Component<IProps, IState>
super(props); super(props);
this.state = { this.state = {
contentRect: null,
visible: false, visible: false,
}; };
} }
@ -331,7 +330,7 @@ export default class InteractiveTooltip extends React.Component<IProps, IState>
document.removeEventListener("mousemove", this.onMouseMove); document.removeEventListener("mousemove", this.onMouseMove);
} }
private collectContentRect = (element: HTMLElement): void => { private collectContentRect = (element: HTMLElement | null): void => {
// We don't need to clean up when unmounting, so ignore // We don't need to clean up when unmounting, so ignore
if (!element) return; if (!element) return;
@ -354,7 +353,7 @@ export default class InteractiveTooltip extends React.Component<IProps, IState>
} else { } else {
const targetRight = targetRect.right + window.scrollX; const targetRight = targetRect.right + window.scrollX;
const spaceOnRight = UIStore.instance.windowWidth - targetRight; const spaceOnRight = UIStore.instance.windowWidth - targetRight;
return contentRect && spaceOnRight - contentRect.width < MIN_SAFE_DISTANCE_TO_WINDOW_EDGE; return !!contentRect && spaceOnRight - contentRect.width < MIN_SAFE_DISTANCE_TO_WINDOW_EDGE;
} }
} }
@ -368,7 +367,7 @@ export default class InteractiveTooltip extends React.Component<IProps, IState>
} else { } else {
const targetBottom = targetRect.bottom + window.scrollY; const targetBottom = targetRect.bottom + window.scrollY;
const spaceBelow = UIStore.instance.windowHeight - targetBottom; const spaceBelow = UIStore.instance.windowHeight - targetBottom;
return contentRect && spaceBelow - contentRect.height < MIN_SAFE_DISTANCE_TO_WINDOW_EDGE; return !!contentRect && spaceBelow - contentRect.height < MIN_SAFE_DISTANCE_TO_WINDOW_EDGE;
} }
} }
@ -416,7 +415,7 @@ export default class InteractiveTooltip extends React.Component<IProps, IState>
document.removeEventListener("mousemove", this.onMouseMove); document.removeEventListener("mousemove", this.onMouseMove);
} }
private renderTooltip(): JSX.Element { private renderTooltip(): ReactNode {
const { contentRect, visible } = this.state; const { contentRect, visible } = this.state;
if (!visible) { if (!visible) {
ReactDOM.unmountComponentAtNode(getOrCreateContainer()); ReactDOM.unmountComponentAtNode(getOrCreateContainer());
@ -435,7 +434,7 @@ export default class InteractiveTooltip extends React.Component<IProps, IState>
// tooltip content would extend past the safe area towards the window // tooltip content would extend past the safe area towards the window
// edge, flip around to below the target. // edge, flip around to below the target.
const position: Partial<IRect> = {}; const position: Partial<IRect> = {};
let chevronFace: ChevronFace = null; let chevronFace: ChevronFace | null = null;
if (this.isOnTheSide) { if (this.isOnTheSide) {
if (this.onLeftOfTarget()) { if (this.onLeftOfTarget()) {
position.left = targetLeft; position.left = targetLeft;
@ -461,8 +460,7 @@ export default class InteractiveTooltip extends React.Component<IProps, IState>
const chevron = <div className={"mx_InteractiveTooltip_chevron_" + chevronFace} />; const chevron = <div className={"mx_InteractiveTooltip_chevron_" + chevronFace} />;
const menuClasses = classNames({ const menuClasses = classNames("mx_InteractiveTooltip", {
mx_InteractiveTooltip: true,
mx_InteractiveTooltip_withChevron_top: chevronFace === ChevronFace.Top, mx_InteractiveTooltip_withChevron_top: chevronFace === ChevronFace.Top,
mx_InteractiveTooltip_withChevron_left: chevronFace === ChevronFace.Left, mx_InteractiveTooltip_withChevron_left: chevronFace === ChevronFace.Left,
mx_InteractiveTooltip_withChevron_right: chevronFace === ChevronFace.Right, mx_InteractiveTooltip_withChevron_right: chevronFace === ChevronFace.Right,

View file

@ -40,7 +40,7 @@ interface IProps {
interface IState { interface IState {
searchQuery: string; searchQuery: string;
langs: Languages; langs: Languages | null;
} }
export default class LanguageDropdown extends React.Component<IProps, IState> { export default class LanguageDropdown extends React.Component<IProps, IState> {
@ -103,8 +103,8 @@ export default class LanguageDropdown extends React.Component<IProps, IState> {
// default value here too, otherwise we need to handle null / undefined // default value here too, otherwise we need to handle null / undefined
// values between mounting and the initial value propagating // values between mounting and the initial value propagating
let language = SettingsStore.getValue("language", null, /*excludeDefault:*/ true); let language = SettingsStore.getValue<string | undefined>("language", null, /*excludeDefault:*/ true);
let value = null; let value: string | undefined;
if (language) { if (language) {
value = this.props.value || language; value = this.props.value || language;
} else { } else {

View file

@ -76,7 +76,7 @@ interface IProps<T> {
} }
interface IState { interface IState {
renderRange: ItemRange; renderRange: ItemRange | null;
} }
export default class LazyRenderList<T = any> extends React.Component<IProps<T>, IState> { export default class LazyRenderList<T = any> extends React.Component<IProps<T>, IState> {
@ -93,7 +93,7 @@ export default class LazyRenderList<T = any> extends React.Component<IProps<T>,
}; };
} }
public static getDerivedStateFromProps(props: IProps<unknown>, state: IState): Partial<IState> { public static getDerivedStateFromProps(props: IProps<unknown>, state: IState): Partial<IState> | null {
const range = LazyRenderList.getVisibleRangeFromProps(props); const range = LazyRenderList.getVisibleRangeFromProps(props);
const intersectRange = range.expand(props.overflowMargin); const intersectRange = range.expand(props.overflowMargin);
const renderRange = range.expand(props.overflowItems); const renderRange = range.expand(props.overflowItems);

View file

@ -64,7 +64,8 @@ const MiniAvatarUploader: React.FC<IProps> = ({
const label = hasAvatar || busy ? hasAvatarLabel : noAvatarLabel; const label = hasAvatar || busy ? hasAvatarLabel : noAvatarLabel;
const { room } = useContext(RoomContext); const { room } = useContext(RoomContext);
const canSetAvatar = isUserAvatar || room?.currentState?.maySendStateEvent(EventType.RoomAvatar, cli.getUserId()); const canSetAvatar =
isUserAvatar || room?.currentState?.maySendStateEvent(EventType.RoomAvatar, cli.getSafeUserId());
if (!canSetAvatar) return <React.Fragment>{children}</React.Fragment>; if (!canSetAvatar) return <React.Fragment>{children}</React.Fragment>;
const visible = !!label && (hover || show); const visible = !!label && (hover || show);

View file

@ -50,7 +50,7 @@ export default class PersistentApp extends React.Component<IProps> {
app={app} app={app}
fullWidth={true} fullWidth={true}
room={this.room} room={this.room}
userId={this.context.credentials.userId} userId={this.context.getSafeUserId()}
creatorUserId={app.creatorUserId} creatorUserId={app.creatorUserId}
widgetPageTitle={WidgetUtils.getWidgetDataTitle(app)} widgetPageTitle={WidgetUtils.getWidgetDataTitle(app)}
waitForIframeLoad={app.waitForIframeLoad} waitForIframeLoad={app.waitForIframeLoad}

View file

@ -163,7 +163,12 @@ export default class PollCreateDialog extends ScrollableBaseModal<IProps, IState
doMaybeLocalRoomAction( doMaybeLocalRoomAction(
this.props.room.roomId, this.props.room.roomId,
(actualRoomId: string) => (actualRoomId: string) =>
this.matrixClient.sendEvent(actualRoomId, this.props.threadId, pollEvent.type, pollEvent.content), this.matrixClient.sendEvent(
actualRoomId,
this.props.threadId ?? null,
pollEvent.type,
pollEvent.content,
),
this.matrixClient, this.matrixClient,
) )
.then(() => this.props.onFinished(true)) .then(() => this.props.onFinished(true))

View file

@ -48,7 +48,7 @@ interface IProps {
parentEv?: MatrixEvent; parentEv?: MatrixEvent;
// called when the ReplyChain contents has changed, including EventTiles thereof // called when the ReplyChain contents has changed, including EventTiles thereof
onHeightChanged: () => void; onHeightChanged: () => void;
permalinkCreator: RoomPermalinkCreator; permalinkCreator?: RoomPermalinkCreator;
// Specifies which layout to use. // Specifies which layout to use.
layout?: Layout; layout?: Layout;
// Whether to always show a timestamp // Whether to always show a timestamp

View file

@ -71,7 +71,7 @@ export default class RoomAliasField extends React.PureComponent<IProps, IState>
const postfix = domain ? <span title={`:${domain}`}>{`:${domain}`}</span> : <span />; const postfix = domain ? <span title={`:${domain}`}>{`:${domain}`}</span> : <span />;
const maxlength = domain ? 255 - domain.length - 2 : 255 - 1; // 2 for # and : const maxlength = domain ? 255 - domain.length - 2 : 255 - 1; // 2 for # and :
const value = domain const value = domain
? this.props.value.substring(1, this.props.value.length - this.props.domain.length - 1) ? this.props.value.substring(1, this.props.value.length - domain.length - 1)
: this.props.value.substring(1); : this.props.value.substring(1);
return { prefix, postfix, value, maxlength }; return { prefix, postfix, value, maxlength };
@ -104,7 +104,7 @@ export default class RoomAliasField extends React.PureComponent<IProps, IState>
private onValidate = async (fieldState: IFieldState): Promise<IValidationResult> => { private onValidate = async (fieldState: IFieldState): Promise<IValidationResult> => {
const result = await this.validationRules(fieldState); const result = await this.validationRules(fieldState);
this.setState({ isValid: result.valid }); this.setState({ isValid: !!result.valid });
return result; return result;
}; };
@ -225,8 +225,9 @@ export default class RoomAliasField extends React.PureComponent<IProps, IState>
return this.state.isValid; return this.state.isValid;
} }
public validate(options: IValidateOpts): Promise<boolean> { public async validate(options: IValidateOpts): Promise<boolean> {
return this.fieldRef.current?.validate(options); const val = await this.fieldRef.current?.validate(options);
return val ?? false;
} }
public focus(): void { public focus(): void {

View file

@ -33,7 +33,7 @@ import TooltipTarget from "./TooltipTarget";
import { Linkify, topicToHtml } from "../../../HtmlUtils"; import { Linkify, topicToHtml } from "../../../HtmlUtils";
interface IProps extends React.HTMLProps<HTMLDivElement> { interface IProps extends React.HTMLProps<HTMLDivElement> {
room?: Room; room: Room;
} }
export default function RoomTopic({ room, ...props }: IProps): JSX.Element { export default function RoomTopic({ room, ...props }: IProps): JSX.Element {
@ -62,7 +62,7 @@ export default function RoomTopic({ room, ...props }: IProps): JSX.Element {
useDispatcher(dis, (payload) => { useDispatcher(dis, (payload) => {
if (payload.action === Action.ShowRoomTopic) { if (payload.action === Action.ShowRoomTopic) {
const canSetTopic = room.currentState.maySendStateEvent(EventType.RoomTopic, client.getUserId()); const canSetTopic = room.currentState.maySendStateEvent(EventType.RoomTopic, client.getSafeUserId());
const body = topicToHtml(topic?.text, topic?.html, ref, true); const body = topicToHtml(topic?.text, topic?.html, ref, true);
const modal = Modal.createDialog(InfoDialog, { const modal = Modal.createDialog(InfoDialog, {

View file

@ -71,7 +71,7 @@ export default function SearchWarning({ isRoomEncrypted, kind }: IProps): JSX.El
let text: ReactNode | undefined; let text: ReactNode | undefined;
let logo: JSX.Element | undefined; let logo: JSX.Element | undefined;
if (desktopBuilds.get("available")) { if (desktopBuilds?.get("available")) {
logo = <img src={desktopBuilds.get("logo")} />; logo = <img src={desktopBuilds.get("logo")} />;
const buildUrl = desktopBuilds.get("url"); const buildUrl = desktopBuilds.get("url");
switch (kind) { switch (kind) {

View file

@ -33,7 +33,7 @@ interface IProps {
} }
const showPickerDialog = ( const showPickerDialog = (
title: string, title: string | undefined,
serverConfig: ValidatedServerConfig, serverConfig: ValidatedServerConfig,
onFinished: (config: ValidatedServerConfig) => void, onFinished: (config: ValidatedServerConfig) => void,
): void => { ): void => {

View file

@ -69,14 +69,14 @@ export default class SettingsFlag extends React.Component<IProps, IState> {
private save = async (val?: boolean): Promise<void> => { private save = async (val?: boolean): Promise<void> => {
await SettingsStore.setValue( await SettingsStore.setValue(
this.props.name, this.props.name,
this.props.roomId, this.props.roomId ?? null,
this.props.level, this.props.level,
val !== undefined ? val : this.state.value, val !== undefined ? val : this.state.value,
); );
}; };
public render(): React.ReactNode { public render(): React.ReactNode {
const canChange = SettingsStore.canSetValue(this.props.name, this.props.roomId, this.props.level); const canChange = SettingsStore.canSetValue(this.props.name, this.props.roomId ?? null, this.props.level);
if (!canChange && this.props.hideIfCannotSet) return null; if (!canChange && this.props.hideIfCannotSet) return null;

View file

@ -38,7 +38,7 @@ interface SpellCheckLanguagesDropdownIProps {
interface SpellCheckLanguagesDropdownIState { interface SpellCheckLanguagesDropdownIState {
searchQuery: string; searchQuery: string;
languages: Languages; languages?: Languages;
} }
export default class SpellCheckLanguagesDropdown extends React.Component< export default class SpellCheckLanguagesDropdown extends React.Component<
@ -51,7 +51,6 @@ export default class SpellCheckLanguagesDropdown extends React.Component<
this.state = { this.state = {
searchQuery: "", searchQuery: "",
languages: null,
}; };
} }
@ -59,7 +58,7 @@ export default class SpellCheckLanguagesDropdown extends React.Component<
const plaf = PlatformPeg.get(); const plaf = PlatformPeg.get();
if (plaf) { if (plaf) {
plaf.getAvailableSpellCheckLanguages() plaf.getAvailableSpellCheckLanguages()
.then((languages) => { ?.then((languages) => {
languages.sort(function (a, b) { languages.sort(function (a, b) {
if (a < b) return -1; if (a < b) return -1;
if (a > b) return 1; if (a > b) return 1;
@ -92,11 +91,11 @@ export default class SpellCheckLanguagesDropdown extends React.Component<
} }
public render(): React.ReactNode { public render(): React.ReactNode {
if (this.state.languages === null) { if (!this.state.languages) {
return <Spinner />; return <Spinner />;
} }
let displayedLanguages; let displayedLanguages: Languages;
if (this.state.searchQuery) { if (this.state.searchQuery) {
displayedLanguages = this.state.languages.filter((lang) => { displayedLanguages = this.state.languages.filter((lang) => {
return languageMatchesSearchQuery(this.state.searchQuery, lang); return languageMatchesSearchQuery(this.state.searchQuery, lang);
@ -111,8 +110,8 @@ export default class SpellCheckLanguagesDropdown extends React.Component<
// default value here too, otherwise we need to handle null / undefined; // default value here too, otherwise we need to handle null / undefined;
// values between mounting and the initial value propagating // values between mounting and the initial value propagating
let language = SettingsStore.getValue("language", null, /*excludeDefault:*/ true); let language = SettingsStore.getValue<string | undefined>("language", null, /*excludeDefault:*/ true);
let value = null; let value: string | undefined;
if (language) { if (language) {
value = this.props.value || language; value = this.props.value || language;
} else { } else {

View file

@ -24,7 +24,7 @@ interface IProps extends HTMLAttributes<HTMLSpanElement> {
tooltipClass?: string; tooltipClass?: string;
tooltip: React.ReactNode; tooltip: React.ReactNode;
tooltipProps?: Omit<React.ComponentProps<typeof TooltipTarget>, "label" | "tooltipClassName" | "className">; tooltipProps?: Omit<React.ComponentProps<typeof TooltipTarget>, "label" | "tooltipClassName" | "className">;
onClick?: (ev?: React.MouseEvent) => void; onClick?: (ev: React.MouseEvent) => void;
} }
export default class TextWithTooltip extends React.Component<IProps> { export default class TextWithTooltip extends React.Component<IProps> {

View file

@ -40,7 +40,7 @@ export function UseCaseSelection({ onFinished }: Props): JSX.Element {
onFinished(selection); onFinished(selection);
}, TIMEOUT); }, TIMEOUT);
return () => { return () => {
clearTimeout(handler); if (handler !== null) clearTimeout(handler);
handler = null; handler = null;
}; };
} }

View file

@ -28,7 +28,7 @@ interface Props {
} }
export function UseCaseSelectionButton({ useCase, onClick, selected }: Props): JSX.Element { export function UseCaseSelectionButton({ useCase, onClick, selected }: Props): JSX.Element {
let label: string; let label: string | undefined;
switch (useCase) { switch (useCase) {
case UseCase.PersonalMessaging: case UseCase.PersonalMessaging:
label = _t("Friends and family"); label = _t("Friends and family");

View file

@ -43,7 +43,7 @@ interface IArgs<T, D = void> {
} }
export interface IFieldState { export interface IFieldState {
value: string; value: string | null;
focused: boolean; focused: boolean;
allowEmpty?: boolean; allowEmpty?: boolean;
} }

View file

@ -80,7 +80,7 @@ export default class DownloadActionButton extends React.PureComponent<IProps, IS
} }
public render(): React.ReactNode { public render(): React.ReactNode {
let spinner: JSX.Element; let spinner: JSX.Element | undefined;
if (this.state.loading) { if (this.state.loading) {
spinner = <Spinner w={18} h={18} />; spinner = <Spinner w={18} h={18} />;
} }

View file

@ -47,7 +47,7 @@ interface IProps {
interface IState { interface IState {
canRedact: boolean; canRedact: boolean;
sendStatus: EventStatus; sendStatus: EventStatus | null;
} }
export default class EditHistoryMessage extends React.PureComponent<IProps, IState> { export default class EditHistoryMessage extends React.PureComponent<IProps, IState> {
@ -59,12 +59,10 @@ export default class EditHistoryMessage extends React.PureComponent<IProps, ISta
super(props); super(props);
const cli = MatrixClientPeg.get(); const cli = MatrixClientPeg.get();
const { userId } = cli.credentials; const userId = cli.getSafeUserId();
const event = this.props.mxEvent; const event = this.props.mxEvent;
const room = cli.getRoom(event.getRoomId()); const room = cli.getRoom(event.getRoomId());
if (event.localRedactionEvent()) { event.localRedactionEvent()?.on(MatrixEventEvent.Status, this.onAssociatedStatusChanged);
event.localRedactionEvent().on(MatrixEventEvent.Status, this.onAssociatedStatusChanged);
}
const canRedact = room.currentState.maySendRedactionForEvent(event, userId); const canRedact = room.currentState.maySendRedactionForEvent(event, userId);
this.state = { canRedact, sendStatus: event.getAssociatedStatus() }; this.state = { canRedact, sendStatus: event.getAssociatedStatus() };
} }
@ -121,9 +119,7 @@ export default class EditHistoryMessage extends React.PureComponent<IProps, ISta
unmountPills(this.pills); unmountPills(this.pills);
unmountTooltips(this.tooltips); unmountTooltips(this.tooltips);
const event = this.props.mxEvent; const event = this.props.mxEvent;
if (event.localRedactionEvent()) { event.localRedactionEvent()?.off(MatrixEventEvent.Status, this.onAssociatedStatusChanged);
event.localRedactionEvent().off(MatrixEventEvent.Status, this.onAssociatedStatusChanged);
}
} }
public componentDidUpdate(): void { public componentDidUpdate(): void {
@ -133,12 +129,12 @@ export default class EditHistoryMessage extends React.PureComponent<IProps, ISta
private renderActionBar(): JSX.Element { private renderActionBar(): JSX.Element {
// hide the button when already redacted // hide the button when already redacted
let redactButton: JSX.Element; let redactButton: JSX.Element | undefined;
if (!this.props.mxEvent.isRedacted() && !this.props.isBaseEvent && this.state.canRedact) { if (!this.props.mxEvent.isRedacted() && !this.props.isBaseEvent && this.state.canRedact) {
redactButton = <AccessibleButton onClick={this.onRedactClick}>{_t("Remove")}</AccessibleButton>; redactButton = <AccessibleButton onClick={this.onRedactClick}>{_t("Remove")}</AccessibleButton>;
} }
let viewSourceButton: JSX.Element; let viewSourceButton: JSX.Element | undefined;
if (SettingsStore.getValue("developerMode")) { if (SettingsStore.getValue("developerMode")) {
viewSourceButton = ( viewSourceButton = (
<AccessibleButton onClick={this.onViewSourceClick}>{_t("View Source")}</AccessibleButton> <AccessibleButton onClick={this.onViewSourceClick}>{_t("View Source")}</AccessibleButton>
@ -189,9 +185,8 @@ export default class EditHistoryMessage extends React.PureComponent<IProps, ISta
} }
const timestamp = formatTime(new Date(mxEvent.getTs()), this.props.isTwelveHour); const timestamp = formatTime(new Date(mxEvent.getTs()), this.props.isTwelveHour);
const isSending = ["sending", "queued", "encrypting"].indexOf(this.state.sendStatus) !== -1; const isSending = ["sending", "queued", "encrypting"].includes(this.state.sendStatus!);
const classes = classNames({ const classes = classNames("mx_EventTile", {
mx_EventTile: true,
// Note: we keep the `sending` state class for tests, not for our styles // Note: we keep the `sending` state class for tests, not for our styles
mx_EventTile_sending: isSending, mx_EventTile_sending: isSending,
}); });

View file

@ -22,7 +22,7 @@ import { RovingAccessibleButton, useRovingTabIndex } from "../../../accessibilit
interface IProps { interface IProps {
ts: number; ts: number;
onDatePicked?: (dateString: string) => void; onDatePicked: (dateString: string) => void;
} }
const JumpToDatePicker: React.FC<IProps> = ({ ts, onDatePicked }: IProps) => { const JumpToDatePicker: React.FC<IProps> = ({ ts, onDatePicked }: IProps) => {

View file

@ -165,7 +165,7 @@ export default class LegacyCallEvent extends React.PureComponent<IProps, IState>
{this.props.timestamp} {this.props.timestamp}
</div> </div>
); );
} else if ([CallErrorCode.UserHangup, "user hangup"].includes(hangupReason) || !hangupReason) { } else if (!hangupReason || [CallErrorCode.UserHangup, "user hangup"].includes(hangupReason)) {
// workaround for https://github.com/vector-im/element-web/issues/5178 // workaround for https://github.com/vector-im/element-web/issues/5178
// it seems Android randomly sets a reason of "user hangup" which is // it seems Android randomly sets a reason of "user hangup" which is
// interpreted as an error code :( // interpreted as an error code :(

View file

@ -140,7 +140,7 @@ const MBeaconBody: React.FC<IBodyProps> = React.forwardRef(({ mxEvent, getRelati
error?.message === LocationShareError.MapStyleUrlNotConfigured || error?.message === LocationShareError.MapStyleUrlNotConfigured ||
error?.message === LocationShareError.MapStyleUrlNotReachable; error?.message === LocationShareError.MapStyleUrlNotReachable;
const displayStatus = getBeaconDisplayStatus( const displayStatus = getBeaconDisplayStatus(
isLive, !!isLive,
latestLocationState, latestLocationState,
// if we are unable to display maps because it is not configured for the server // if we are unable to display maps because it is not configured for the server
// don't display an error // don't display an error
@ -174,7 +174,7 @@ const MBeaconBody: React.FC<IBodyProps> = React.forwardRef(({ mxEvent, getRelati
map = ( map = (
<Map <Map
id={mapId} id={mapId}
centerGeoUri={latestLocationState.uri} centerGeoUri={latestLocationState?.uri}
onError={setError} onError={setError}
onClick={onClick} onClick={onClick}
className="mx_MBeaconBody_map" className="mx_MBeaconBody_map"
@ -184,7 +184,7 @@ const MBeaconBody: React.FC<IBodyProps> = React.forwardRef(({ mxEvent, getRelati
map={map} map={map}
id={`${mapId}-marker`} id={`${mapId}-marker`}
geoUri={latestLocationState.uri} geoUri={latestLocationState.uri}
roomMember={markerRoomMember} roomMember={markerRoomMember ?? undefined}
useMemberColor useMemberColor
/> />
)} )}

View file

@ -15,7 +15,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import React, { ComponentProps, createRef } from "react"; import React, { ComponentProps, createRef, ReactNode } from "react";
import { Blurhash } from "react-blurhash"; import { Blurhash } from "react-blurhash";
import classNames from "classnames"; import classNames from "classnames";
import { CSSTransition, SwitchTransition } from "react-transition-group"; import { CSSTransition, SwitchTransition } from "react-transition-group";
@ -47,8 +47,8 @@ enum Placeholder {
} }
interface IState { interface IState {
contentUrl?: string; contentUrl: string | null;
thumbUrl?: string; thumbUrl: string | null;
isAnimated?: boolean; isAnimated?: boolean;
error?: Error; error?: Error;
imgError: boolean; imgError: boolean;
@ -78,6 +78,8 @@ export default class MImageBody extends React.Component<IBodyProps, IState> {
this.reconnectedListener = createReconnectedListener(this.clearError); this.reconnectedListener = createReconnectedListener(this.clearError);
this.state = { this.state = {
contentUrl: null,
thumbUrl: null,
imgError: false, imgError: false,
imgLoaded: false, imgLoaded: false,
hover: false, hover: false,
@ -126,7 +128,7 @@ export default class MImageBody extends React.Component<IBodyProps, IState> {
}; };
} }
Modal.createDialog(ImageView, params, "mx_Dialog_lightbox", null, true); Modal.createDialog(ImageView, params, "mx_Dialog_lightbox", undefined, true);
} }
}; };
@ -177,7 +179,7 @@ export default class MImageBody extends React.Component<IBodyProps, IState> {
this.setState({ imgLoaded: true, loadedImageDimensions }); this.setState({ imgLoaded: true, loadedImageDimensions });
}; };
private getContentUrl(): string { private getContentUrl(): string | null {
// During export, the content url will point to the MSC, which will later point to a local url // During export, the content url will point to the MSC, which will later point to a local url
if (this.props.forExport) return this.media.srcMxc; if (this.props.forExport) return this.media.srcMxc;
return this.media.srcHttp; return this.media.srcHttp;
@ -187,7 +189,7 @@ export default class MImageBody extends React.Component<IBodyProps, IState> {
return mediaFromContent(this.props.mxEvent.getContent()); return mediaFromContent(this.props.mxEvent.getContent());
} }
private getThumbUrl(): string { private getThumbUrl(): string | null {
// FIXME: we let images grow as wide as you like, rather than capped to 800x600. // FIXME: we let images grow as wide as you like, rather than capped to 800x600.
// So either we need to support custom timeline widths here, or reimpose the cap, otherwise the // So either we need to support custom timeline widths here, or reimpose the cap, otherwise the
// thumbnail resolution will be unnecessarily reduced. // thumbnail resolution will be unnecessarily reduced.
@ -242,8 +244,8 @@ export default class MImageBody extends React.Component<IBodyProps, IState> {
private async downloadImage(): Promise<void> { private async downloadImage(): Promise<void> {
if (this.state.contentUrl) return; // already downloaded if (this.state.contentUrl) return; // already downloaded
let thumbUrl: string; let thumbUrl: string | null;
let contentUrl: string; let contentUrl: string | null;
if (this.props.mediaEventHelper.media.isEncrypted) { if (this.props.mediaEventHelper.media.isEncrypted) {
try { try {
[contentUrl, thumbUrl] = await Promise.all([ [contentUrl, thumbUrl] = await Promise.all([
@ -276,7 +278,7 @@ export default class MImageBody extends React.Component<IBodyProps, IState> {
// If there is no included non-animated thumbnail then we will generate our own, we can't depend on the server // If there is no included non-animated thumbnail then we will generate our own, we can't depend on the server
// because 1. encryption and 2. we can't ask the server specifically for a non-animated thumbnail. // because 1. encryption and 2. we can't ask the server specifically for a non-animated thumbnail.
if (isAnimated && !SettingsStore.getValue("autoplayGifs")) { if (isAnimated && !SettingsStore.getValue("autoplayGifs")) {
if (!thumbUrl || !content?.info.thumbnail_info || mayBeAnimated(content.info.thumbnail_info.mimetype)) { if (!thumbUrl || !content?.info?.thumbnail_info || mayBeAnimated(content.info.thumbnail_info.mimetype)) {
const img = document.createElement("img"); const img = document.createElement("img");
const loadPromise = new Promise((resolve, reject) => { const loadPromise = new Promise((resolve, reject) => {
img.onload = resolve; img.onload = resolve;
@ -364,7 +366,7 @@ export default class MImageBody extends React.Component<IBodyProps, IState> {
} }
} }
protected getBanner(content: IMediaEventContent): JSX.Element { protected getBanner(content: IMediaEventContent): ReactNode {
// Hide it for the threads list & the file panel where we show it as text anyway. // Hide it for the threads list & the file panel where we show it as text anyway.
if ( if (
[TimelineRenderingType.ThreadsList, TimelineRenderingType.File].includes(this.context.timelineRenderingType) [TimelineRenderingType.ThreadsList, TimelineRenderingType.File].includes(this.context.timelineRenderingType)
@ -429,9 +431,9 @@ export default class MImageBody extends React.Component<IBodyProps, IState> {
forcedHeight ?? this.props.maxImageHeight, forcedHeight ?? this.props.maxImageHeight,
); );
let img: JSX.Element; let img: JSX.Element | undefined;
let placeholder: JSX.Element; let placeholder: JSX.Element | undefined;
let gifLabel: JSX.Element; let gifLabel: JSX.Element | undefined;
if (!this.props.forExport && !this.state.imgLoaded) { if (!this.props.forExport && !this.state.imgLoaded) {
const classes = classNames("mx_MImageBody_placeholder", { const classes = classNames("mx_MImageBody_placeholder", {
@ -471,7 +473,7 @@ export default class MImageBody extends React.Component<IBodyProps, IState> {
gifLabel = <p className="mx_MImageBody_gifLabel">GIF</p>; gifLabel = <p className="mx_MImageBody_gifLabel">GIF</p>;
} }
let banner: JSX.Element; let banner: ReactNode | undefined;
if (this.state.showImage && this.state.hover) { if (this.state.showImage && this.state.hover) {
banner = this.getBanner(content); banner = this.getBanner(content);
} }
@ -526,7 +528,7 @@ export default class MImageBody extends React.Component<IBodyProps, IState> {
} }
// Overridden by MStickerBody // Overridden by MStickerBody
protected getPlaceholder(width: number, height: number): JSX.Element { protected getPlaceholder(width: number, height: number): ReactNode {
const blurhash = this.props.mxEvent.getContent().info?.[BLURHASH_FIELD]; const blurhash = this.props.mxEvent.getContent().info?.[BLURHASH_FIELD];
if (blurhash) { if (blurhash) {
@ -540,12 +542,12 @@ export default class MImageBody extends React.Component<IBodyProps, IState> {
} }
// Overridden by MStickerBody // Overridden by MStickerBody
protected getTooltip(): JSX.Element { protected getTooltip(): ReactNode {
return null; return null;
} }
// Overridden by MStickerBody // Overridden by MStickerBody
protected getFileBody(): string | JSX.Element { protected getFileBody(): ReactNode {
if (this.props.forExport) return null; if (this.props.forExport) return null;
/* /*
* In the room timeline or the thread context we don't need the download * In the room timeline or the thread context we don't need the download
@ -577,7 +579,7 @@ export default class MImageBody extends React.Component<IBodyProps, IState> {
} }
let contentUrl = this.state.contentUrl; let contentUrl = this.state.contentUrl;
let thumbUrl: string; let thumbUrl: string | undefined;
if (this.props.forExport) { if (this.props.forExport) {
contentUrl = this.props.mxEvent.getContent().url ?? this.props.mxEvent.getContent().file?.url; contentUrl = this.props.mxEvent.getContent().url ?? this.props.mxEvent.getContent().file?.url;
thumbUrl = contentUrl; thumbUrl = contentUrl;

View file

@ -41,7 +41,7 @@ export default class MJitsiWidgetEvent extends React.PureComponent<IProps> {
const widgetId = this.props.mxEvent.getStateKey(); const widgetId = this.props.mxEvent.getStateKey();
const widget = WidgetStore.instance.getRoom(room.roomId, true).widgets.find((w) => w.id === widgetId); const widget = WidgetStore.instance.getRoom(room.roomId, true).widgets.find((w) => w.id === widgetId);
let joinCopy = _t("Join the conference at the top of this room"); let joinCopy: string | null = _t("Join the conference at the top of this room");
if (widget && WidgetLayoutStore.instance.isInContainer(room, widget, Container.Right)) { if (widget && WidgetLayoutStore.instance.isInContainer(room, widget, Container.Right)) {
joinCopy = _t("Join the conference from the room information card on the right"); joinCopy = _t("Join the conference from the room information card on the right");
} else if (!widget) { } else if (!widget) {

View file

@ -124,7 +124,7 @@ export default class MKeyVerificationRequest extends React.Component<IProps> {
let title: string; let title: string;
let subtitle: string; let subtitle: string;
let stateNode: JSX.Element; let stateNode: JSX.Element | undefined;
if (!request.canAccept) { if (!request.canAccept) {
let stateLabel; let stateLabel;

View file

@ -37,7 +37,7 @@ import { IBodyProps } from "./IBodyProps";
import { createReconnectedListener } from "../../../utils/connection"; import { createReconnectedListener } from "../../../utils/connection";
interface IState { interface IState {
error: Error; error?: Error;
} }
export default class MLocationBody extends React.Component<IBodyProps, IState> { export default class MLocationBody extends React.Component<IBodyProps, IState> {
@ -58,9 +58,7 @@ export default class MLocationBody extends React.Component<IBodyProps, IState> {
this.reconnectedListener = createReconnectedListener(this.clearError); this.reconnectedListener = createReconnectedListener(this.clearError);
this.state = { this.state = {};
error: undefined,
};
} }
private onClick = (): void => { private onClick = (): void => {
@ -149,7 +147,12 @@ export const LocationBodyContent: React.FC<LocationBodyContentProps> = ({
const mapElement = ( const mapElement = (
<Map id={mapId} centerGeoUri={geoUri} onClick={onClick} onError={onError} className="mx_MLocationBody_map"> <Map id={mapId} centerGeoUri={geoUri} onClick={onClick} onError={onError} className="mx_MLocationBody_map">
{({ map }) => ( {({ map }) => (
<SmartMarker map={map} id={`${mapId}-marker`} geoUri={geoUri} roomMember={markerRoomMember} /> <SmartMarker
map={map}
id={`${mapId}-marker`}
geoUri={geoUri}
roomMember={markerRoomMember ?? undefined}
/>
)} )}
</Map> </Map>
); );

View file

@ -128,7 +128,7 @@ export function launchPollEditor(mxEvent: MatrixEvent, getRelationsForEvent?: Ge
PollCreateDialog, PollCreateDialog,
{ {
room: MatrixClientPeg.get().getRoom(mxEvent.getRoomId()), room: MatrixClientPeg.get().getRoom(mxEvent.getRoomId()),
threadId: mxEvent.getThread()?.id ?? null, threadId: mxEvent.getThread()?.id,
editingMxEvent: mxEvent, editingMxEvent: mxEvent,
}, },
"mx_CompoundDialog", "mx_CompoundDialog",

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 React from "react"; import React, { ReactNode } from "react";
import MImageBody from "./MImageBody"; import MImageBody from "./MImageBody";
import { BLURHASH_FIELD } from "../../../utils/image-media"; import { BLURHASH_FIELD } from "../../../utils/image-media";
@ -33,7 +33,7 @@ export default class MStickerBody extends MImageBody {
// MStickerBody doesn't need a wrapping `<a href=...>`, but it does need extra padding // MStickerBody doesn't need a wrapping `<a href=...>`, but it does need extra padding
// which is added by mx_MStickerBody_wrapper // which is added by mx_MStickerBody_wrapper
protected wrapImage(contentUrl: string, children: React.ReactNode): JSX.Element { protected wrapImage(contentUrl: string, children: React.ReactNode): JSX.Element {
let onClick = null; let onClick: React.MouseEventHandler | undefined;
if (!this.state.showImage) { if (!this.state.showImage) {
onClick = this.onClick; onClick = this.onClick;
} }
@ -46,7 +46,7 @@ export default class MStickerBody extends MImageBody {
} }
// Placeholder to show in place of the sticker image if img onLoad hasn't fired yet. // Placeholder to show in place of the sticker image if img onLoad hasn't fired yet.
protected getPlaceholder(width: number, height: number): JSX.Element { protected getPlaceholder(width: number, height: number): ReactNode {
if (this.props.mxEvent.getContent().info?.[BLURHASH_FIELD]) return super.getPlaceholder(width, height); if (this.props.mxEvent.getContent().info?.[BLURHASH_FIELD]) return super.getPlaceholder(width, height);
return ( return (
<img <img
@ -61,7 +61,7 @@ export default class MStickerBody extends MImageBody {
} }
// Tooltip to show on mouse over // Tooltip to show on mouse over
protected getTooltip(): JSX.Element { protected getTooltip(): ReactNode {
const content = this.props.mxEvent && this.props.mxEvent.getContent(); const content = this.props.mxEvent && this.props.mxEvent.getContent();
if (!content || !content.body || !content.info || !content.info.w) return null; if (!content || !content.body || !content.info || !content.info.w) return null;
@ -74,11 +74,11 @@ export default class MStickerBody extends MImageBody {
} }
// Don't show "Download this_file.png ..." // Don't show "Download this_file.png ..."
protected getFileBody(): JSX.Element { protected getFileBody(): ReactNode {
return null; return null;
} }
protected getBanner(content: IMediaEventContent): JSX.Element { protected getBanner(content: IMediaEventContent): ReactNode {
return null; // we don't need a banner, we have a tooltip return null; // we don't need a banner, we have a tooltip
} }
} }

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 React from "react"; import React, { ReactNode } from "react";
import { decode } from "blurhash"; import { decode } from "blurhash";
import { logger } from "matrix-js-sdk/src/logger"; import { logger } from "matrix-js-sdk/src/logger";
@ -31,13 +31,13 @@ import RoomContext, { TimelineRenderingType } from "../../../contexts/RoomContex
import MediaProcessingError from "./shared/MediaProcessingError"; import MediaProcessingError from "./shared/MediaProcessingError";
interface IState { interface IState {
decryptedUrl?: string; decryptedUrl: string | null;
decryptedThumbnailUrl?: string; decryptedThumbnailUrl: string | null;
decryptedBlob?: Blob; decryptedBlob: Blob | null;
error?: any; error?: any;
fetchingData: boolean; fetchingData: boolean;
posterLoading: boolean; posterLoading: boolean;
blurhashUrl: string; blurhashUrl: string | null;
} }
export default class MVideoBody extends React.PureComponent<IBodyProps, IState> { export default class MVideoBody extends React.PureComponent<IBodyProps, IState> {
@ -61,21 +61,21 @@ export default class MVideoBody extends React.PureComponent<IBodyProps, IState>
}; };
} }
private getContentUrl(): string | null { private getContentUrl(): string | undefined {
const content = this.props.mxEvent.getContent<IMediaEventContent>(); const content = this.props.mxEvent.getContent<IMediaEventContent>();
// During export, the content url will point to the MSC, which will later point to a local url // During export, the content url will point to the MSC, which will later point to a local url
if (this.props.forExport) return content.file?.url || content.url; if (this.props.forExport) return content.file?.url ?? content.url;
const media = mediaFromContent(content); const media = mediaFromContent(content);
if (media.isEncrypted) { if (media.isEncrypted) {
return this.state.decryptedUrl; return this.state.decryptedUrl ?? undefined;
} else { } else {
return media.srcHttp; return media.srcHttp ?? undefined;
} }
} }
private hasContentUrl(): boolean { private hasContentUrl(): boolean {
const url = this.getContentUrl(); const url = this.getContentUrl();
return url && !url.startsWith("data:"); return !!url && !url.startsWith("data:");
} }
private getThumbUrl(): string | null { private getThumbUrl(): string | null {
@ -227,7 +227,7 @@ export default class MVideoBody extends React.PureComponent<IBodyProps, IState>
); );
} }
private getFileBody = (): JSX.Element => { private getFileBody = (): ReactNode => {
if (this.props.forExport) return null; if (this.props.forExport) return null;
return this.showFileBody && <MFileBody {...this.props} showGenericPlaceholder={false} />; return this.showFileBody && <MFileBody {...this.props} showGenericPlaceholder={false} />;
}; };
@ -271,7 +271,7 @@ export default class MVideoBody extends React.PureComponent<IBodyProps, IState>
const contentUrl = this.getContentUrl(); const contentUrl = this.getContentUrl();
const thumbUrl = this.getThumbUrl(); const thumbUrl = this.getThumbUrl();
let poster = null; let poster: string | undefined;
let preload = "metadata"; let preload = "metadata";
if (content.info && thumbUrl) { if (content.info && thumbUrl) {
poster = thumbUrl; poster = thumbUrl;

View file

@ -63,7 +63,7 @@ interface IOptionsButtonProps {
mxEvent: MatrixEvent; mxEvent: MatrixEvent;
// TODO: Types // TODO: Types
getTile: () => any | null; getTile: () => any | null;
getReplyChain: () => ReplyChain; getReplyChain: () => ReplyChain | null;
permalinkCreator: RoomPermalinkCreator; permalinkCreator: RoomPermalinkCreator;
onFocusChange: (menuDisplayed: boolean) => void; onFocusChange: (menuDisplayed: boolean) => void;
getRelationsForEvent?: GetRelationsForEvent; getRelationsForEvent?: GetRelationsForEvent;
@ -97,10 +97,10 @@ const OptionsButton: React.FC<IOptionsButtonProps> = ({
[openMenu, onFocus], [openMenu, onFocus],
); );
let contextMenu: ReactElement | null; let contextMenu: ReactElement | undefined;
if (menuDisplayed) { if (menuDisplayed && button.current) {
const tile = getTile && getTile(); const tile = getTile && getTile();
const replyChain = getReplyChain && getReplyChain(); const replyChain = getReplyChain();
const buttonRect = button.current.getBoundingClientRect(); const buttonRect = button.current.getBoundingClientRect();
contextMenu = ( contextMenu = (
@ -109,7 +109,7 @@ const OptionsButton: React.FC<IOptionsButtonProps> = ({
mxEvent={mxEvent} mxEvent={mxEvent}
permalinkCreator={permalinkCreator} permalinkCreator={permalinkCreator}
eventTileOps={tile && tile.getEventTileOps ? tile.getEventTileOps() : undefined} eventTileOps={tile && tile.getEventTileOps ? tile.getEventTileOps() : undefined}
collapseReplyChain={replyChain && replyChain.canCollapse() ? replyChain.collapse : undefined} collapseReplyChain={replyChain?.canCollapse() ? replyChain.collapse : undefined}
onFinished={closeMenu} onFinished={closeMenu}
getRelationsForEvent={getRelationsForEvent} getRelationsForEvent={getRelationsForEvent}
/> />
@ -148,8 +148,8 @@ const ReactButton: React.FC<IReactButtonProps> = ({ mxEvent, reactions, onFocusC
onFocusChange(menuDisplayed); onFocusChange(menuDisplayed);
}, [onFocusChange, menuDisplayed]); }, [onFocusChange, menuDisplayed]);
let contextMenu; let contextMenu: JSX.Element | undefined;
if (menuDisplayed) { if (menuDisplayed && button.current) {
const buttonRect = button.current.getBoundingClientRect(); const buttonRect = button.current.getBoundingClientRect();
contextMenu = ( contextMenu = (
<ContextMenu {...aboveLeftOf(buttonRect)} onFinished={closeMenu} managed={false}> <ContextMenu {...aboveLeftOf(buttonRect)} onFinished={closeMenu} managed={false}>
@ -211,7 +211,7 @@ const ReplyInThreadButton: React.FC<IReplyInThreadButton> = ({ mxEvent }) => {
if (mxEvent.getThread() && !mxEvent.isThreadRoot) { if (mxEvent.getThread() && !mxEvent.isThreadRoot) {
defaultDispatcher.dispatch<ShowThreadPayload>({ defaultDispatcher.dispatch<ShowThreadPayload>({
action: Action.ShowThread, action: Action.ShowThread,
rootEvent: mxEvent.getThread().rootEvent, rootEvent: mxEvent.getThread()!.rootEvent,
initialEvent: mxEvent, initialEvent: mxEvent,
scroll_into_view: true, scroll_into_view: true,
highlighted: true, highlighted: true,
@ -293,7 +293,7 @@ interface IMessageActionBarProps {
reactions?: Relations | null | undefined; reactions?: Relations | null | undefined;
// TODO: Types // TODO: Types
getTile: () => any | null; getTile: () => any | null;
getReplyChain: () => ReplyChain | undefined; getReplyChain: () => ReplyChain | null;
permalinkCreator?: RoomPermalinkCreator; permalinkCreator?: RoomPermalinkCreator;
onFocusChange?: (menuDisplayed: boolean) => void; onFocusChange?: (menuDisplayed: boolean) => void;
toggleThreadExpanded: () => void; toggleThreadExpanded: () => void;
@ -421,7 +421,7 @@ export default class MessageActionBar extends React.PureComponent<IMessageAction
}; };
public render(): React.ReactNode { public render(): React.ReactNode {
const toolbarOpts = []; const toolbarOpts: JSX.Element[] = [];
if (canEditContent(this.props.mxEvent)) { if (canEditContent(this.props.mxEvent)) {
toolbarOpts.push( toolbarOpts.push(
<RovingAccessibleTooltipButton <RovingAccessibleTooltipButton
@ -452,8 +452,8 @@ export default class MessageActionBar extends React.PureComponent<IMessageAction
// We show a different toolbar for failed events, so detect that first. // We show a different toolbar for failed events, so detect that first.
const mxEvent = this.props.mxEvent; const mxEvent = this.props.mxEvent;
const editStatus = mxEvent.replacingEvent() && mxEvent.replacingEvent().status; const editStatus = mxEvent.replacingEvent()?.status;
const redactStatus = mxEvent.localRedactionEvent() && mxEvent.localRedactionEvent().status; const redactStatus = mxEvent.localRedactionEvent()?.status;
const allowCancel = canCancel(mxEvent.status) || canCancel(editStatus) || canCancel(redactStatus); const allowCancel = canCancel(mxEvent.status) || canCancel(editStatus) || canCancel(redactStatus);
const isFailed = [mxEvent.status, editStatus, redactStatus].includes(EventStatus.NOT_SENT); const isFailed = [mxEvent.status, editStatus, redactStatus].includes(EventStatus.NOT_SENT);
if (allowCancel && isFailed) { if (allowCancel && isFailed) {

View file

@ -58,7 +58,7 @@ interface IProps extends Omit<IBodyProps, "onMessageAllowed" | "mediaEventHelper
} }
export interface IOperableEventTile { export interface IOperableEventTile {
getEventTileOps(): IEventTileOps; getEventTileOps(): IEventTileOps | null;
} }
const baseBodyTypes = new Map<string, typeof React.Component>([ const baseBodyTypes = new Map<string, typeof React.Component>([
@ -159,12 +159,12 @@ export default class MessageEvent extends React.Component<IProps> implements IMe
if (this.props.mxEvent.isDecryptionFailure()) { if (this.props.mxEvent.isDecryptionFailure()) {
BodyType = DecryptionFailureBody; BodyType = DecryptionFailureBody;
} else if (type && this.evTypes.has(type)) { } else if (type && this.evTypes.has(type)) {
BodyType = this.evTypes.get(type); BodyType = this.evTypes.get(type)!;
} else if (msgtype && this.bodyTypes.has(msgtype)) { } else if (msgtype && this.bodyTypes.has(msgtype)) {
BodyType = this.bodyTypes.get(msgtype); BodyType = this.bodyTypes.get(msgtype)!;
} else if (content.url) { } else if (content.url) {
// Fallback to MFileBody if there's a content URL // Fallback to MFileBody if there's a content URL
BodyType = this.bodyTypes.get(MsgType.File); BodyType = this.bodyTypes.get(MsgType.File)!;
} else { } else {
// Fallback to UnknownBody otherwise if not redacted // Fallback to UnknownBody otherwise if not redacted
BodyType = UnknownBody; BodyType = UnknownBody;

View file

@ -142,7 +142,7 @@ export default class ReactionsRow extends React.PureComponent<IProps, IState> {
this.forceUpdate(); this.forceUpdate();
}; };
private getMyReactions(): MatrixEvent[] { private getMyReactions(): MatrixEvent[] | null {
const reactions = this.props.reactions; const reactions = this.props.reactions;
if (!reactions) { if (!reactions) {
return null; return null;
@ -206,7 +206,7 @@ export default class ReactionsRow extends React.PureComponent<IProps, IState> {
// Show the first MAX_ITEMS if there are MAX_ITEMS + 1 or more items. // Show the first MAX_ITEMS if there are MAX_ITEMS + 1 or more items.
// The "+ 1" ensure that the "show all" reveals something that takes up // The "+ 1" ensure that the "show all" reveals something that takes up
// more space than the button itself. // more space than the button itself.
let showAllButton: JSX.Element; let showAllButton: JSX.Element | undefined;
if (items.length > MAX_ITEMS_WHEN_LIMITED + 1 && !showAll) { if (items.length > MAX_ITEMS_WHEN_LIMITED + 1 && !showAll) {
items = items.slice(0, MAX_ITEMS_WHEN_LIMITED); items = items.slice(0, MAX_ITEMS_WHEN_LIMITED);
showAllButton = ( showAllButton = (

View file

@ -106,9 +106,9 @@ export default class ReactionsRowButton extends React.PureComponent<IProps, ISta
} }
const room = this.context.getRoom(mxEvent.getRoomId()); const room = this.context.getRoom(mxEvent.getRoomId());
let label: string; let label: string | undefined;
if (room) { if (room) {
const senders = []; const senders: string[] = [];
for (const reactionEvent of reactionEvents) { for (const reactionEvent of reactionEvents) {
const member = room.getMember(reactionEvent.getSender()); const member = room.getMember(reactionEvent.getSender());
senders.push(member?.name || reactionEvent.getSender()); senders.push(member?.name || reactionEvent.getSender());

View file

@ -43,5 +43,7 @@ export default function SenderProfile({ mxEvent, onClick, withTooltip }: IProps)
emphasizeDisplayName={true} emphasizeDisplayName={true}
withTooltip={withTooltip} withTooltip={withTooltip}
/> />
) : null; ) : (
<></>
);
} }

View file

@ -342,7 +342,7 @@ export default class TextualBody extends React.Component<IBodyProps, IState> {
if (node.tagName === "SPAN" && typeof node.getAttribute("data-mx-spoiler") === "string") { if (node.tagName === "SPAN" && typeof node.getAttribute("data-mx-spoiler") === "string") {
const spoilerContainer = document.createElement("span"); const spoilerContainer = document.createElement("span");
const reason = node.getAttribute("data-mx-spoiler"); const reason = node.getAttribute("data-mx-spoiler") ?? undefined;
node.removeAttribute("data-mx-spoiler"); // we don't want to recurse node.removeAttribute("data-mx-spoiler"); // we don't want to recurse
const spoiler = <Spoiler reason={reason} contentHtml={node.outerHTML} />; const spoiler = <Spoiler reason={reason} contentHtml={node.outerHTML} />;
@ -367,7 +367,7 @@ export default class TextualBody extends React.Component<IBodyProps, IState> {
const node = nodes[i]; const node = nodes[i];
if (node.tagName === "A" && node.getAttribute("href")) { if (node.tagName === "A" && node.getAttribute("href")) {
if (this.isLinkPreviewable(node)) { if (this.isLinkPreviewable(node)) {
links.push(node.getAttribute("href")); links.push(node.getAttribute("href")!);
} }
} else if (node.tagName === "PRE" || node.tagName === "CODE" || node.tagName === "BLOCKQUOTE") { } else if (node.tagName === "PRE" || node.tagName === "CODE" || node.tagName === "BLOCKQUOTE") {
continue; continue;
@ -380,7 +380,8 @@ export default class TextualBody extends React.Component<IBodyProps, IState> {
private isLinkPreviewable(node: Element): boolean { private isLinkPreviewable(node: Element): boolean {
// don't try to preview relative links // don't try to preview relative links
if (!node.getAttribute("href").startsWith("http://") && !node.getAttribute("href").startsWith("https://")) { const href = node.getAttribute("href") ?? "";
if (!href.startsWith("http://") && !href.startsWith("https://")) {
return false; return false;
} }
@ -389,9 +390,10 @@ export default class TextualBody extends React.Component<IBodyProps, IState> {
// or from a full foo.bar/baz style schemeless URL) - or be a markdown-style // or from a full foo.bar/baz style schemeless URL) - or be a markdown-style
// link, in which case we check the target text differs from the link value. // link, in which case we check the target text differs from the link value.
// TODO: make this configurable? // TODO: make this configurable?
if (node.textContent.indexOf("/") > -1) { if (node.textContent?.includes("/")) {
return true; return true;
} else { }
const url = node.getAttribute("href"); const url = node.getAttribute("href");
const host = url.match(/^https?:\/\/(.*?)(\/|$)/)[1]; const host = url.match(/^https?:\/\/(.*?)(\/|$)/)[1];
@ -400,7 +402,7 @@ export default class TextualBody extends React.Component<IBodyProps, IState> {
// what the matrix.to site looks like). // what the matrix.to site looks like).
if (isPermalinkHost(host)) return false; if (isPermalinkHost(host)) return false;
if (node.textContent.toLowerCase().trim().startsWith(host.toLowerCase())) { if (node.textContent?.toLowerCase().trim().startsWith(host.toLowerCase())) {
// it's a "foo.pl" style link // it's a "foo.pl" style link
return false; return false;
} else { } else {
@ -408,7 +410,6 @@ export default class TextualBody extends React.Component<IBodyProps, IState> {
return true; return true;
} }
} }
}
private onCancelClick = (): void => { private onCancelClick = (): void => {
this.setState({ widgetHidden: true }); this.setState({ widgetHidden: true });
@ -434,7 +435,7 @@ export default class TextualBody extends React.Component<IBodyProps, IState> {
* to start with (e.g. pills, links in the content). * to start with (e.g. pills, links in the content).
*/ */
private onBodyLinkClick = (e: MouseEvent): void => { private onBodyLinkClick = (e: MouseEvent): void => {
let target = e.target as HTMLLinkElement; let target: HTMLLinkElement | null = e.target as HTMLLinkElement;
// links processed by linkifyjs have their own handler so don't handle those here // links processed by linkifyjs have their own handler so don't handle those here
if (target.classList.contains(linkifyOpts.className as string)) return; if (target.classList.contains(linkifyOpts.className as string)) return;
if (target.nodeName !== "A") { if (target.nodeName !== "A") {

View file

@ -34,16 +34,14 @@ interface IProps {
} }
interface IState { interface IState {
error: Error; error?: Error;
} }
export default class TileErrorBoundary extends React.Component<IProps, IState> { export default class TileErrorBoundary extends React.Component<IProps, IState> {
public constructor(props: IProps) { public constructor(props: IProps) {
super(props); super(props);
this.state = { this.state = {};
error: null,
};
} }
public static getDerivedStateFromError(error: Error): Partial<IState> { public static getDerivedStateFromError(error: Error): Partial<IState> {

View file

@ -117,7 +117,7 @@ export default class Autocomplete extends React.PureComponent<IProps, IState> {
// Hide the autocomplete box // Hide the autocomplete box
hide: true, hide: true,
}); });
return Promise.resolve(null); return Promise.resolve();
} }
let autocompleteDelay = SettingsStore.getValue("autocompleteDelay"); let autocompleteDelay = SettingsStore.getValue("autocompleteDelay");
@ -204,7 +204,7 @@ export default class Autocomplete extends React.PureComponent<IProps, IState> {
this.setSelection(1 + index); this.setSelection(1 + index);
} }
public onEscape(e: KeyboardEvent): boolean { public onEscape(e: KeyboardEvent): boolean | undefined {
const completionCount = this.countCompletions(); const completionCount = this.countCompletions();
if (completionCount === 0) { if (completionCount === 0) {
// autocomplete is already empty, so don't preventDefault // autocomplete is already empty, so don't preventDefault

View file

@ -132,7 +132,7 @@ export default class BasicMessageEditor extends React.Component<IProps, IState>
private _isCaretAtEnd: boolean; private _isCaretAtEnd: boolean;
private lastCaret: DocumentOffset; private lastCaret: DocumentOffset;
private lastSelection: ReturnType<typeof cloneSelection>; private lastSelection: ReturnType<typeof cloneSelection> | null;
private readonly useMarkdownHandle: string; private readonly useMarkdownHandle: string;
private readonly emoticonSettingHandle: string; private readonly emoticonSettingHandle: string;
@ -188,7 +188,7 @@ export default class BasicMessageEditor extends React.Component<IProps, IState>
} }
} }
public replaceEmoticon(caretPosition: DocumentPosition, regex: RegExp): number { public replaceEmoticon(caretPosition: DocumentPosition, regex: RegExp): number | undefined {
const { model } = this.props; const { model } = this.props;
const range = model.startRange(caretPosition); const range = model.startRange(caretPosition);
// expand range max 9 characters backwards from caretPosition, // expand range max 9 characters backwards from caretPosition,
@ -352,7 +352,7 @@ export default class BasicMessageEditor extends React.Component<IProps, IState>
this.onCutCopy(event, "cut"); this.onCutCopy(event, "cut");
}; };
private onPaste = (event: ClipboardEvent<HTMLDivElement>): boolean => { private onPaste = (event: ClipboardEvent<HTMLDivElement>): boolean | undefined => {
event.preventDefault(); // we always handle the paste ourselves event.preventDefault(); // we always handle the paste ourselves
if (this.props.onPaste?.(event, this.props.model)) { if (this.props.onPaste?.(event, this.props.model)) {
// to prevent double handling, allow props.onPaste to skip internal onPaste // to prevent double handling, allow props.onPaste to skip internal onPaste
@ -415,7 +415,7 @@ export default class BasicMessageEditor extends React.Component<IProps, IState>
this.lastSelection = cloneSelection(document.getSelection()); this.lastSelection = cloneSelection(document.getSelection());
} }
private refreshLastCaretIfNeeded(): DocumentOffset { private refreshLastCaretIfNeeded(): DocumentOffset | undefined {
// XXX: needed when going up and down in editing messages ... not sure why yet // XXX: needed when going up and down in editing messages ... not sure why yet
// because the editors should stop doing this when when blurred ... // because the editors should stop doing this when when blurred ...
// maybe it's on focus and the _editorRef isn't available yet or something. // maybe it's on focus and the _editorRef isn't available yet or something.
@ -441,7 +441,7 @@ export default class BasicMessageEditor extends React.Component<IProps, IState>
} }
public isSelectionCollapsed(): boolean { public isSelectionCollapsed(): boolean {
return !this.lastSelection || this.lastSelection.isCollapsed; return !this.lastSelection || !!this.lastSelection.isCollapsed;
} }
public isCaretAtStart(): boolean { public isCaretAtStart(): boolean {
@ -537,7 +537,7 @@ export default class BasicMessageEditor extends React.Component<IProps, IState>
// there is no current autocomplete window, try to open it // there is no current autocomplete window, try to open it
this.tabCompleteName(); this.tabCompleteName();
handled = true; handled = true;
} else if ([KeyBindingAction.Delete, KeyBindingAction.Backspace].includes(accessibilityAction)) { } else if ([KeyBindingAction.Delete, KeyBindingAction.Backspace].includes(accessibilityAction!)) {
this.formatBarRef.current.hide(); this.formatBarRef.current.hide();
} }
@ -750,7 +750,7 @@ export default class BasicMessageEditor extends React.Component<IProps, IState>
}; };
public render(): React.ReactNode { public render(): React.ReactNode {
let autoComplete; let autoComplete: JSX.Element | undefined;
if (this.state.autoComplete) { if (this.state.autoComplete) {
const query = this.state.query; const query = this.state.query;
const queryLen = query.length; const queryLen = query.length;
@ -785,7 +785,7 @@ export default class BasicMessageEditor extends React.Component<IProps, IState>
const { completionIndex } = this.state; const { completionIndex } = this.state;
const hasAutocomplete = Boolean(this.state.autoComplete); const hasAutocomplete = Boolean(this.state.autoComplete);
let activeDescendant: string; let activeDescendant: string | undefined;
if (hasAutocomplete && completionIndex >= 0) { if (hasAutocomplete && completionIndex >= 0) {
activeDescendant = generateCompletionDomId(completionIndex); activeDescendant = generateCompletionDomId(completionIndex);
} }
@ -800,7 +800,7 @@ export default class BasicMessageEditor extends React.Component<IProps, IState>
/> />
<div <div
className={classes} className={classes}
contentEditable={this.props.disabled ? null : true} contentEditable={this.props.disabled ? undefined : true}
tabIndex={0} tabIndex={0}
onBlur={this.onBlur} onBlur={this.onBlur}
onFocus={this.onFocus} onFocus={this.onFocus}

View file

@ -15,7 +15,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import React, { useState } from "react"; import React, { CSSProperties, useState } from "react";
import classNames from "classnames"; import classNames from "classnames";
import { _t, _td } from "../../../languageHandler"; import { _t, _td } from "../../../languageHandler";
@ -44,7 +44,7 @@ const crossSigningRoomTitles: { [key in E2EState]?: string } = {
interface IProps { interface IProps {
isUser?: boolean; isUser?: boolean;
status: E2EState | E2EStatus; status?: E2EState | E2EStatus;
className?: string; className?: string;
size?: number; size?: number;
onClick?: () => void; onClick?: () => void;
@ -77,13 +77,15 @@ const E2EIcon: React.FC<IProps> = ({
); );
let e2eTitle: string | undefined; let e2eTitle: string | undefined;
if (status) {
if (isUser) { if (isUser) {
e2eTitle = crossSigningUserTitles[status]; e2eTitle = crossSigningUserTitles[status];
} else { } else {
e2eTitle = crossSigningRoomTitles[status]; e2eTitle = crossSigningRoomTitles[status];
} }
}
let style; let style: CSSProperties | undefined;
if (size) { if (size) {
style = { width: `${size}px`, height: `${size}px` }; style = { width: `${size}px`, height: `${size}px` };
} }
@ -91,7 +93,7 @@ const E2EIcon: React.FC<IProps> = ({
const onMouseOver = (): void => setHover(true); const onMouseOver = (): void => setHover(true);
const onMouseLeave = (): void => setHover(false); const onMouseLeave = (): void => setHover(false);
let tip; let tip: JSX.Element | undefined;
if (hover && !hideTooltip) { if (hover && !hideTooltip) {
tip = <Tooltip label={e2eTitle ? _t(e2eTitle) : ""} alignment={tooltipAlignment} />; tip = <Tooltip label={e2eTitle ? _t(e2eTitle) : ""} alignment={tooltipAlignment} />;
} }

View file

@ -47,6 +47,7 @@ import { getSlashCommand, isSlashCommand, runSlashCommand, shouldSendAnyway } fr
import { KeyBindingAction } from "../../../accessibility/KeyboardShortcuts"; import { KeyBindingAction } from "../../../accessibility/KeyboardShortcuts";
import { PosthogAnalytics } from "../../../PosthogAnalytics"; import { PosthogAnalytics } from "../../../PosthogAnalytics";
import { editorRoomKey, editorStateKey } from "../../../Editing"; import { editorRoomKey, editorStateKey } from "../../../Editing";
import DocumentOffset from "../../../editor/offset";
function getHtmlReplyFallback(mxEvent: MatrixEvent): string { function getHtmlReplyFallback(mxEvent: MatrixEvent): string {
const html = mxEvent.getContent().formatted_body; const html = mxEvent.getContent().formatted_body;
@ -130,7 +131,7 @@ class EditMessageComposer extends React.Component<IEditMessageComposerProps, ISt
private readonly editorRef = createRef<BasicMessageComposer>(); private readonly editorRef = createRef<BasicMessageComposer>();
private readonly dispatcherRef: string; private readonly dispatcherRef: string;
private model: EditorModel = null; private model: EditorModel;
public constructor(props: IEditMessageComposerProps, context: React.ContextType<typeof RoomContext>) { public constructor(props: IEditMessageComposerProps, context: React.ContextType<typeof RoomContext>) {
super(props); super(props);
@ -250,7 +251,7 @@ class EditMessageComposer extends React.Component<IEditMessageComposerProps, ISt
return localStorage.getItem(this.editorRoomKey) !== null; return localStorage.getItem(this.editorRoomKey) !== null;
} }
private restoreStoredEditorState(partCreator: PartCreator): Part[] { private restoreStoredEditorState(partCreator: PartCreator): Part[] | undefined {
const json = localStorage.getItem(this.editorStateKey); const json = localStorage.getItem(this.editorStateKey);
if (json) { if (json) {
try { try {
@ -382,7 +383,7 @@ class EditMessageComposer extends React.Component<IEditMessageComposerProps, ISt
// editorstate so it can be restored when the remote echo event tile gets rendered // editorstate so it can be restored when the remote echo event tile gets rendered
// in case we're currently editing a pending event // in case we're currently editing a pending event
const sel = document.getSelection(); const sel = document.getSelection();
let caret; let caret: DocumentOffset | undefined;
if (sel.focusNode) { if (sel.focusNode) {
caret = getCaretOffsetAndText(this.editorRef.current?.editorRef.current, sel).caret; caret = getCaretOffsetAndText(this.editorRef.current?.editorRef.current, sel).caret;
} }
@ -390,7 +391,7 @@ class EditMessageComposer extends React.Component<IEditMessageComposerProps, ISt
// if caret is undefined because for some reason there isn't a valid selection, // if caret is undefined because for some reason there isn't a valid selection,
// then when mounting the editor again with the same editor state, // then when mounting the editor again with the same editor state,
// it will set the cursor at the end. // it will set the cursor at the end.
this.props.editState.setEditorState(caret, parts); this.props.editState.setEditorState(caret ?? null, parts);
window.removeEventListener("beforeunload", this.saveStoredEditorState); window.removeEventListener("beforeunload", this.saveStoredEditorState);
if (this.shouldSaveStoredEditorState) { if (this.shouldSaveStoredEditorState) {
this.saveStoredEditorState(); this.saveStoredEditorState();
@ -462,7 +463,7 @@ class EditMessageComposer extends React.Component<IEditMessageComposerProps, ISt
model={this.model} model={this.model}
room={this.getRoom()} room={this.getRoom()}
threadId={this.props.editState?.getEvent()?.getThread()?.id} threadId={this.props.editState?.getEvent()?.getThread()?.id}
initialCaret={this.props.editState.getCaret()} initialCaret={this.props.editState.getCaret() ?? undefined}
label={_t("Edit message")} label={_t("Edit message")}
onChange={this.onChange} onChange={this.onChange}
/> />

View file

@ -69,14 +69,14 @@ interface IProps {
title?: string; title?: string;
avatarJsx?: JSX.Element; // <BaseAvatar /> avatarJsx?: JSX.Element; // <BaseAvatar />
className?: string; className?: string;
presenceState?: PresenceState; presenceState: PresenceState;
presenceLastActiveAgo?: number; presenceLastActiveAgo: number;
presenceLastTs?: number; presenceLastTs: number;
presenceCurrentlyActive?: boolean; presenceCurrentlyActive?: boolean;
showInviteButton?: boolean; showInviteButton: boolean;
onClick(): void; onClick(): void;
suppressOnHover?: boolean; suppressOnHover: boolean;
showPresence?: boolean; showPresence: boolean;
subtextLabel?: string; subtextLabel?: string;
e2eStatus?: E2EState; e2eStatus?: E2EState;
powerStatus?: PowerStatus; powerStatus?: PowerStatus;

View file

@ -274,8 +274,6 @@ export class UnwrappedEventTile extends React.Component<EventTileProps, IState>
verified: null, verified: null,
// The Relations model from the JS SDK for reactions to `mxEvent` // The Relations model from the JS SDK for reactions to `mxEvent`
reactions: this.getReactions(), reactions: this.getReactions(),
// Context menu position
contextMenu: null,
hover: false, hover: false,
@ -697,13 +695,13 @@ export class UnwrappedEventTile extends React.Component<EventTileProps, IState>
}); });
}; };
private renderE2EPadlock(): JSX.Element { private renderE2EPadlock(): ReactNode {
// if the event was edited, show the verification info for the edit, not // if the event was edited, show the verification info for the edit, not
// the original // the original
const ev = this.props.mxEvent.replacingEvent() ?? this.props.mxEvent; const ev = this.props.mxEvent.replacingEvent() ?? this.props.mxEvent;
// no icon for local rooms // no icon for local rooms
if (isLocalRoom(ev.getRoomId()!)) return; if (isLocalRoom(ev.getRoomId()!)) return null;
// event could not be decrypted // event could not be decrypted
if (ev.isDecryptionFailure()) { if (ev.isDecryptionFailure()) {
@ -713,9 +711,9 @@ export class UnwrappedEventTile extends React.Component<EventTileProps, IState>
// event is encrypted and not redacted, display padlock corresponding to whether or not it is verified // event is encrypted and not redacted, display padlock corresponding to whether or not it is verified
if (ev.isEncrypted() && !ev.isRedacted()) { if (ev.isEncrypted() && !ev.isRedacted()) {
if (this.state.verified === E2EState.Normal) { if (this.state.verified === E2EState.Normal) {
return; // no icon if we've not even cross-signed the user return null; // no icon if we've not even cross-signed the user
} else if (this.state.verified === E2EState.Verified) { } else if (this.state.verified === E2EState.Verified) {
return; // no icon for verified return null; // no icon for verified
} else if (this.state.verified === E2EState.Unauthenticated) { } else if (this.state.verified === E2EState.Unauthenticated) {
return <E2ePadlockUnauthenticated />; return <E2ePadlockUnauthenticated />;
} else if (this.state.verified === E2EState.Unknown) { } else if (this.state.verified === E2EState.Unknown) {
@ -729,16 +727,16 @@ export class UnwrappedEventTile extends React.Component<EventTileProps, IState>
// else if room is encrypted // else if room is encrypted
// and event is being encrypted or is not_sent (Unknown Devices/Network Error) // and event is being encrypted or is not_sent (Unknown Devices/Network Error)
if (ev.status === EventStatus.ENCRYPTING) { if (ev.status === EventStatus.ENCRYPTING) {
return; return null;
} }
if (ev.status === EventStatus.NOT_SENT) { if (ev.status === EventStatus.NOT_SENT) {
return; return null;
} }
if (ev.isState()) { if (ev.isState()) {
return; // we expect this to be unencrypted return null; // we expect this to be unencrypted
} }
if (ev.isRedacted()) { if (ev.isRedacted()) {
return; // we expect this to be unencrypted return null; // we expect this to be unencrypted
} }
// if the event is not encrypted, but it's an e2e room, show the open padlock // if the event is not encrypted, but it's an e2e room, show the open padlock
return <E2ePadlockUnencrypted />; return <E2ePadlockUnencrypted />;
@ -752,16 +750,16 @@ export class UnwrappedEventTile extends React.Component<EventTileProps, IState>
this.setState({ actionBarFocused }); this.setState({ actionBarFocused });
}; };
private getTile: () => IEventTileType = () => this.tile.current; private getTile: () => IEventTileType | null = () => this.tile.current;
private getReplyChain = (): ReplyChain => this.replyChain.current; private getReplyChain = (): ReplyChain | null => this.replyChain.current;
private getReactions = (): Relations => { private getReactions = (): Relations | null => {
if (!this.props.showReactions || !this.props.getRelationsForEvent) { if (!this.props.showReactions || !this.props.getRelationsForEvent) {
return null; return null;
} }
const eventId = this.props.mxEvent.getId(); const eventId = this.props.mxEvent.getId();
return this.props.getRelationsForEvent(eventId, "m.annotation", "m.reaction"); return this.props.getRelationsForEvent(eventId, "m.annotation", "m.reaction") ?? null;
}; };
private onReactionsCreated = (relationType: string, eventType: string): void => { private onReactionsCreated = (relationType: string, eventType: string): void => {
@ -795,7 +793,7 @@ export class UnwrappedEventTile extends React.Component<EventTileProps, IState>
// Return if we're in a browser and click either an a tag or we have // Return if we're in a browser and click either an a tag or we have
// selected text, as in those cases we want to use the native browser // selected text, as in those cases we want to use the native browser
// menu // menu
if (!PlatformPeg.get().allowOverridingNativeContextMenus() && (getSelectedText() || anchorElement)) return; if (!PlatformPeg.get()?.allowOverridingNativeContextMenus() && (getSelectedText() || anchorElement)) return;
// We don't want to show the menu when editing a message // We don't want to show the menu when editing a message
if (this.props.editState) return; if (this.props.editState) return;
@ -817,7 +815,7 @@ export class UnwrappedEventTile extends React.Component<EventTileProps, IState>
private onCloseMenu = (): void => { private onCloseMenu = (): void => {
this.setState({ this.setState({
contextMenu: null, contextMenu: undefined,
actionBarFocused: false, actionBarFocused: false,
}); });
}; };
@ -901,7 +899,7 @@ export class UnwrappedEventTile extends React.Component<EventTileProps, IState>
this.props.mxEvent.getContent().msgtype === MsgType.Emote, this.props.mxEvent.getContent().msgtype === MsgType.Emote,
}); });
const isSending = ["sending", "queued", "encrypting"].indexOf(this.props.eventSendStatus) !== -1; const isSending = ["sending", "queued", "encrypting"].includes(this.props.eventSendStatus!);
const isRedacted = isMessageEvent(this.props.mxEvent) && this.props.isRedacted; const isRedacted = isMessageEvent(this.props.mxEvent) && this.props.isRedacted;
const isEncryptionFailure = this.props.mxEvent.isDecryptionFailure(); const isEncryptionFailure = this.props.mxEvent.isDecryptionFailure();
@ -1110,7 +1108,7 @@ export class UnwrappedEventTile extends React.Component<EventTileProps, IState>
const groupPadlock = !useIRCLayout && !isBubbleMessage && this.renderE2EPadlock(); const groupPadlock = !useIRCLayout && !isBubbleMessage && this.renderE2EPadlock();
const ircPadlock = useIRCLayout && !isBubbleMessage && this.renderE2EPadlock(); const ircPadlock = useIRCLayout && !isBubbleMessage && this.renderE2EPadlock();
let msgOption; let msgOption: JSX.Element | undefined;
if (this.props.showReadReceipts) { if (this.props.showReadReceipts) {
if (this.shouldShowSentReceipt || this.shouldShowSendingReceipt) { if (this.shouldShowSentReceipt || this.shouldShowSendingReceipt) {
msgOption = <SentReceipt messageState={this.props.mxEvent.getAssociatedStatus()} />; msgOption = <SentReceipt messageState={this.props.mxEvent.getAssociatedStatus()} />;
@ -1127,7 +1125,7 @@ export class UnwrappedEventTile extends React.Component<EventTileProps, IState>
} }
} }
let replyChain; let replyChain: JSX.Element | undefined;
if ( if (
haveRendererForEvent(this.props.mxEvent, this.context.showHiddenEvents) && haveRendererForEvent(this.props.mxEvent, this.context.showHiddenEvents) &&
shouldDisplayReply(this.props.mxEvent) shouldDisplayReply(this.props.mxEvent)
@ -1480,7 +1478,7 @@ class E2ePadlock extends React.Component<IE2ePadlockProps, IE2ePadlockState> {
}; };
public render(): React.ReactNode { public render(): React.ReactNode {
let tooltip = null; let tooltip: JSX.Element | undefined;
if (this.state.hover) { if (this.state.hover) {
tooltip = <Tooltip className="mx_EventTile_e2eIcon_tooltip" label={this.props.title} />; tooltip = <Tooltip className="mx_EventTile_e2eIcon_tooltip" label={this.props.title} />;
} }
@ -1506,7 +1504,7 @@ function SentReceipt({ messageState }: ISentReceiptProps): JSX.Element {
mx_EventTile_receiptSending: !isSent && !isFailed, mx_EventTile_receiptSending: !isSent && !isFailed,
}); });
let nonCssBadge = null; let nonCssBadge: JSX.Element | undefined;
if (isFailed) { if (isFailed) {
nonCssBadge = <NotificationBadge notification={StaticNotificationState.RED_EXCLAMATION} />; nonCssBadge = <NotificationBadge notification={StaticNotificationState.RED_EXCLAMATION} />;
} }

View file

@ -24,11 +24,11 @@ import { _t } from "../../../languageHandler";
const HistoryTile: React.FC = () => { const HistoryTile: React.FC = () => {
const { room } = useContext(RoomContext); const { room } = useContext(RoomContext);
const oldState = room.getLiveTimeline().getState(EventTimeline.BACKWARDS); const oldState = room?.getLiveTimeline().getState(EventTimeline.BACKWARDS);
const encryptionState = oldState.getStateEvents("m.room.encryption")[0]; const encryptionState = oldState?.getStateEvents("m.room.encryption")[0];
const historyState = oldState.getStateEvents("m.room.history_visibility")[0]?.getContent().history_visibility; const historyState = oldState?.getStateEvents("m.room.history_visibility")[0]?.getContent().history_visibility;
let subtitle; let subtitle: string | undefined;
if (historyState == "invited") { if (historyState == "invited") {
subtitle = _t("You don't have permission to view messages from before you were invited."); subtitle = _t("You don't have permission to view messages from before you were invited.");
} else if (historyState == "joined") { } else if (historyState == "joined") {

View file

@ -54,7 +54,7 @@ const LinkPreviewGroup: React.FC<IProps> = ({ links, mxEvent, onCancelClick, onH
const showPreviews = expanded ? previews : previews.slice(0, INITIAL_NUM_PREVIEWS); const showPreviews = expanded ? previews : previews.slice(0, INITIAL_NUM_PREVIEWS);
let toggleButton: JSX.Element; let toggleButton: JSX.Element | undefined;
if (previews.length > INITIAL_NUM_PREVIEWS) { if (previews.length > INITIAL_NUM_PREVIEWS) {
toggleButton = ( toggleButton = (
<AccessibleButton onClick={toggleExpanded}> <AccessibleButton onClick={toggleExpanded}>
@ -94,7 +94,7 @@ const LinkPreviewGroup: React.FC<IProps> = ({ links, mxEvent, onCancelClick, onH
const fetchPreviews = (cli: MatrixClient, links: string[], ts: number): Promise<[string, IPreviewUrlResponse][]> => { const fetchPreviews = (cli: MatrixClient, links: string[], ts: number): Promise<[string, IPreviewUrlResponse][]> => {
return Promise.all<[string, IPreviewUrlResponse] | void>( return Promise.all<[string, IPreviewUrlResponse] | void>(
links.map(async (link): Promise<[string, IPreviewUrlResponse]> => { links.map(async (link): Promise<[string, IPreviewUrlResponse] | undefined> => {
try { try {
const preview = await cli.getUrlPreview(link, ts); const preview = await cli.getUrlPreview(link, ts);
if (preview && Object.keys(preview).length > 0) { if (preview && Object.keys(preview).length > 0) {

View file

@ -44,7 +44,7 @@ export default class LinkPreviewWidget extends React.Component<IProps> {
ev.preventDefault(); ev.preventDefault();
let src = p["og:image"]; let src = p["og:image"];
if (src && src.startsWith("mxc://")) { if (src?.startsWith("mxc://")) {
src = mediaFromMxc(src).srcHttp; src = mediaFromMxc(src).srcHttp;
} }
@ -68,7 +68,7 @@ export default class LinkPreviewWidget extends React.Component<IProps> {
}; };
} }
Modal.createDialog(ImageView, params, "mx_Dialog_lightbox", null, true); Modal.createDialog(ImageView, params, "mx_Dialog_lightbox", undefined, true);
}; };
public render(): React.ReactNode { public render(): React.ReactNode {
@ -78,7 +78,7 @@ export default class LinkPreviewWidget extends React.Component<IProps> {
} }
// FIXME: do we want to factor out all image displaying between this and MImageBody - especially for lightboxing? // FIXME: do we want to factor out all image displaying between this and MImageBody - especially for lightboxing?
let image = p["og:image"]; let image: string | null = p["og:image"] ?? null;
if (!SettingsStore.getValue("showImages")) { if (!SettingsStore.getValue("showImages")) {
image = null; // Don't render a button to show the image, just hide it outright image = null; // Don't render a button to show the image, just hide it outright
} }
@ -99,7 +99,7 @@ export default class LinkPreviewWidget extends React.Component<IProps> {
); );
} }
let img; let img: JSX.Element | undefined;
if (image) { if (image) {
img = ( img = (
<div className="mx_LinkPreviewWidget_image" style={{ height: thumbHeight }}> <div className="mx_LinkPreviewWidget_image" style={{ height: thumbHeight }}>

View file

@ -123,7 +123,9 @@ export default class MemberList extends React.Component<IProps, IState> {
const cli = MatrixClientPeg.get(); const cli = MatrixClientPeg.get();
const room = cli.getRoom(this.props.roomId); const room = cli.getRoom(this.props.roomId);
return room?.canInvite(cli.getUserId()) || (room?.isSpaceRoom() && room.getJoinRule() === JoinRule.Public); return (
!!room?.canInvite(cli.getSafeUserId()) || !!(room?.isSpaceRoom() && room.getJoinRule() === JoinRule.Public)
);
} }
private getMembersState(invitedMembers: Array<RoomMember>, joinedMembers: Array<RoomMember>): IState { private getMembersState(invitedMembers: Array<RoomMember>, joinedMembers: Array<RoomMember>): IState {
@ -276,7 +278,7 @@ export default class MemberList extends React.Component<IProps, IState> {
}); });
}; };
private getPending3PidInvites(): Array<MatrixEvent> { private getPending3PidInvites(): MatrixEvent[] | undefined {
// include 3pid invites (m.room.third_party_invite) state events. // include 3pid invites (m.room.third_party_invite) state events.
// The HS may have already converted these into m.room.member invites so // The HS may have already converted these into m.room.member invites so
// we shouldn't add them if the 3pid invite state key (token) is in the // we shouldn't add them if the 3pid invite state key (token) is in the

View file

@ -41,7 +41,7 @@ interface IProps {
interface IState { interface IState {
isRoomEncrypted: boolean; isRoomEncrypted: boolean;
e2eStatus: E2EState; e2eStatus?: E2EState;
} }
export default class MemberTile extends React.Component<IProps, IState> { export default class MemberTile extends React.Component<IProps, IState> {
@ -57,7 +57,6 @@ export default class MemberTile extends React.Component<IProps, IState> {
this.state = { this.state = {
isRoomEncrypted: false, isRoomEncrypted: false,
e2eStatus: null,
}; };
} }
@ -187,7 +186,7 @@ export default class MemberTile extends React.Component<IProps, IState> {
public render(): React.ReactNode { public render(): React.ReactNode {
const member = this.props.member; const member = this.props.member;
const name = this.getDisplayName(); const name = this.getDisplayName();
const presenceState = member.user?.presence ?? null; const presenceState = member.user?.presence as PresenceState | undefined;
const av = <MemberAvatar member={member} width={36} height={36} aria-hidden="true" />; const av = <MemberAvatar member={member} width={36} height={36} aria-hidden="true" />;
@ -222,7 +221,7 @@ export default class MemberTile extends React.Component<IProps, IState> {
return ( return (
<EntityTile <EntityTile
{...this.props} {...this.props}
presenceState={presenceState as PresenceState | null} presenceState={presenceState}
presenceLastActiveAgo={member.user ? member.user.lastActiveAgo : 0} presenceLastActiveAgo={member.user ? member.user.lastActiveAgo : 0}
presenceLastTs={member.user ? member.user.lastPresenceTs : 0} presenceLastTs={member.user ? member.user.lastPresenceTs : 0}
presenceCurrentlyActive={member.user ? member.user.currentlyActive : false} presenceCurrentlyActive={member.user ? member.user.currentlyActive : false}

View file

@ -172,7 +172,7 @@ export const UploadButtonContext = createContext<UploadButtonFn | null>(null);
interface IUploadButtonProps { interface IUploadButtonProps {
roomId: string; roomId: string;
relation?: IEventRelation | null; relation?: IEventRelation;
children: ReactNode; children: ReactNode;
} }
@ -197,11 +197,11 @@ const UploadButtonContextProvider: React.FC<IUploadButtonProps> = ({ roomId, rel
}); });
const onUploadFileInputChange = (ev: React.ChangeEvent<HTMLInputElement>): void => { const onUploadFileInputChange = (ev: React.ChangeEvent<HTMLInputElement>): void => {
if (ev.target.files.length === 0) return; if (ev.target.files?.length === 0) return;
// Take a copy, so we can safely reset the value of the form control // Take a copy, so we can safely reset the value of the form control
ContentMessages.sharedInstance().sendContentListToRoom( ContentMessages.sharedInstance().sendContentListToRoom(
Array.from(ev.target.files), Array.from(ev.target.files!),
roomId, roomId,
relation, relation,
cli, cli,
@ -316,7 +316,7 @@ class PollButton extends React.PureComponent<IPollButtonProps> {
}); });
} else { } else {
const threadId = const threadId =
this.props.relation?.rel_type === THREAD_RELATION_TYPE.name ? this.props.relation.event_id : null; this.props.relation?.rel_type === THREAD_RELATION_TYPE.name ? this.props.relation.event_id : undefined;
Modal.createDialog( Modal.createDialog(
PollCreateDialog, PollCreateDialog,

View file

@ -166,7 +166,7 @@ const NewRoomIntro: React.FC = () => {
const creator = room.currentState.getStateEvents(EventType.RoomCreate, "")?.getSender(); const creator = room.currentState.getStateEvents(EventType.RoomCreate, "")?.getSender();
const creatorName = room?.getMember(creator)?.rawDisplayName || creator; const creatorName = room?.getMember(creator)?.rawDisplayName || creator;
let createdText; let createdText: string;
if (creator === cli.getUserId()) { if (creator === cli.getUserId()) {
createdText = _t("You created this room."); createdText = _t("You created this room.");
} else { } else {
@ -175,15 +175,15 @@ const NewRoomIntro: React.FC = () => {
}); });
} }
let parentSpace: Room; let parentSpace: Room | undefined;
if ( if (
SpaceStore.instance.activeSpaceRoom?.canInvite(cli.getUserId()) && SpaceStore.instance.activeSpaceRoom?.canInvite(cli.getSafeUserId()) &&
SpaceStore.instance.isRoomInSpace(SpaceStore.instance.activeSpace, room.roomId) SpaceStore.instance.isRoomInSpace(SpaceStore.instance.activeSpace, room.roomId)
) { ) {
parentSpace = SpaceStore.instance.activeSpaceRoom; parentSpace = SpaceStore.instance.activeSpaceRoom;
} }
let buttons; let buttons: JSX.Element | undefined;
if (parentSpace && shouldShowComponent(UIComponent.InviteUsers)) { if (parentSpace && shouldShowComponent(UIComponent.InviteUsers)) {
buttons = ( buttons = (
<div className="mx_NewRoomIntro_buttons"> <div className="mx_NewRoomIntro_buttons">
@ -191,12 +191,12 @@ const NewRoomIntro: React.FC = () => {
className="mx_NewRoomIntro_inviteButton" className="mx_NewRoomIntro_inviteButton"
kind="primary" kind="primary"
onClick={() => { onClick={() => {
showSpaceInvite(parentSpace); showSpaceInvite(parentSpace!);
}} }}
> >
{_t("Invite to %(spaceName)s", { spaceName: parentSpace.name })} {_t("Invite to %(spaceName)s", { spaceName: parentSpace.name })}
</AccessibleButton> </AccessibleButton>
{room.canInvite(cli.getUserId()) && ( {room.canInvite(cli.getSafeUserId()) && (
<AccessibleButton <AccessibleButton
className="mx_NewRoomIntro_inviteButton" className="mx_NewRoomIntro_inviteButton"
kind="primary_outline" kind="primary_outline"
@ -209,7 +209,7 @@ const NewRoomIntro: React.FC = () => {
)} )}
</div> </div>
); );
} else if (room.canInvite(cli.getUserId()) && shouldShowComponent(UIComponent.InviteUsers)) { } else if (room.canInvite(cli.getSafeUserId()) && shouldShowComponent(UIComponent.InviteUsers)) {
buttons = ( buttons = (
<div className="mx_NewRoomIntro_buttons"> <div className="mx_NewRoomIntro_buttons">
<AccessibleButton <AccessibleButton

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 React, { MouseEvent } from "react"; import React, { MouseEvent, ReactNode } from "react";
import SettingsStore from "../../../settings/SettingsStore"; import SettingsStore from "../../../settings/SettingsStore";
import { XOR } from "../../../@types/common"; import { XOR } from "../../../@types/common";
@ -71,7 +71,7 @@ export default class NotificationBadge extends React.PureComponent<XOR<IProps, I
); );
} }
private get roomId(): string { private get roomId(): string | null {
// We should convert this to null for safety with the SettingsStore // We should convert this to null for safety with the SettingsStore
return this.props.roomId || null; return this.props.roomId || null;
} }
@ -110,7 +110,7 @@ export default class NotificationBadge extends React.PureComponent<XOR<IProps, I
}); });
}; };
public render(): React.ReactElement { public render(): ReactNode {
/* eslint @typescript-eslint/no-unused-vars: ["error", { "ignoreRestSiblings": true }] */ /* eslint @typescript-eslint/no-unused-vars: ["error", { "ignoreRestSiblings": true }] */
const { notification, showUnsentTooltip, forceCount, onClick } = this.props; const { notification, showUnsentTooltip, forceCount, onClick } = this.props;
@ -119,8 +119,8 @@ export default class NotificationBadge extends React.PureComponent<XOR<IProps, I
if (!notification.hasUnreadCount) return null; // Can't render a badge if (!notification.hasUnreadCount) return null; // Can't render a badge
} }
let label: string; let label: string | undefined;
let tooltip: JSX.Element; let tooltip: JSX.Element | undefined;
if (showUnsentTooltip && this.state.showTooltip && notification.color === NotificationColor.Unsent) { if (showUnsentTooltip && this.state.showTooltip && notification.color === NotificationColor.Unsent) {
label = _t("Message didn't send. Click for info."); label = _t("Message didn't send. Click for info.");
tooltip = <Tooltip className="mx_RoleButton_tooltip" label={label} />; tooltip = <Tooltip className="mx_RoleButton_tooltip" label={label} />;

View file

@ -42,9 +42,9 @@ export const READ_AVATAR_SIZE = 16;
interface Props { interface Props {
readReceipts: IReadReceiptProps[]; readReceipts: IReadReceiptProps[];
readReceiptMap: { [userId: string]: IReadReceiptInfo }; readReceiptMap: { [userId: string]: IReadReceiptInfo };
checkUnmounting: () => boolean; checkUnmounting?: () => boolean;
suppressAnimation: boolean; suppressAnimation: boolean;
isTwelveHour: boolean; isTwelveHour?: boolean;
} }
interface IAvatarPosition { interface IAvatarPosition {
@ -169,8 +169,8 @@ export function ReadReceiptGroup({
); );
} }
let contextMenu; let contextMenu: JSX.Element | undefined;
if (menuDisplayed) { if (menuDisplayed && button.current) {
const buttonRect = button.current.getBoundingClientRect(); const buttonRect = button.current.getBoundingClientRect();
contextMenu = ( contextMenu = (
<ContextMenu menuClassName="mx_ReadReceiptGroup_popup" onFinished={closeMenu} {...aboveLeftOf(buttonRect)}> <ContextMenu menuClassName="mx_ReadReceiptGroup_popup" onFinished={closeMenu} {...aboveLeftOf(buttonRect)}>
@ -226,7 +226,7 @@ export function ReadReceiptGroup({
} }
interface ReadReceiptPersonProps extends IReadReceiptProps { interface ReadReceiptPersonProps extends IReadReceiptProps {
isTwelveHour: boolean; isTwelveHour?: boolean;
onAfterClick?: () => void; onAfterClick?: () => void;
} }

View file

@ -48,7 +48,7 @@ interface IProps {
suppressAnimation?: boolean; suppressAnimation?: boolean;
// an opaque object for storing information about this user's RR in this room // an opaque object for storing information about this user's RR in this room
readReceiptInfo: IReadReceiptInfo; readReceiptInfo?: IReadReceiptInfo;
// A function which is used to check if the parent panel is being // A function which is used to check if the parent panel is being
// unmounted, to avoid unnecessary work. Should return true if we // unmounted, to avoid unnecessary work. Should return true if we

View file

@ -215,9 +215,9 @@ export default class RoomPreviewBar extends React.Component<IProps, IState> {
if (!myMember) { if (!myMember) {
return {}; return {};
} }
const kickerMember = this.props.room.currentState.getMember(myMember.events.member.getSender()); const kickerMember = this.props.room?.currentState.getMember(myMember.events.member.getSender());
const memberName = kickerMember ? kickerMember.name : myMember.events.member.getSender(); const memberName = kickerMember ? kickerMember.name : myMember.events.member.getSender();
const reason = myMember.events.member.getContent().reason; const reason = myMember.events.member?.getContent().reason;
return { memberName, reason }; return { memberName, reason };
} }
@ -252,8 +252,7 @@ export default class RoomPreviewBar extends React.Component<IProps, IState> {
if (!myMember) { if (!myMember) {
return false; return false;
} }
const memberEvent = myMember.events.member; const memberContent = myMember.events.member.getContent();
const memberContent = memberEvent.getContent();
return memberContent.membership === "invite" && memberContent.is_direct; return memberContent.membership === "invite" && memberContent.is_direct;
} }
@ -397,7 +396,7 @@ export default class RoomPreviewBar extends React.Component<IProps, IState> {
const errCodeMessage = _t( const errCodeMessage = _t(
"An error (%(errcode)s) was returned while trying to validate your " + "An error (%(errcode)s) was returned while trying to validate your " +
"invite. You could try to pass this information on to the person who invited you.", "invite. You could try to pass this information on to the person who invited you.",
{ errcode: this.state.threePidFetchError.errcode || _t("unknown error code") }, { errcode: this.state.threePidFetchError?.errcode || _t("unknown error code") },
); );
switch (joinRule) { switch (joinRule) {
case "invite": case "invite":

View file

@ -204,7 +204,7 @@ export class RoomTile extends React.PureComponent<ClassProps, State> {
private async generatePreview(): Promise<void> { private async generatePreview(): Promise<void> {
if (!this.showMessagePreview) { if (!this.showMessagePreview) {
return null; return;
} }
const messagePreview = await MessagePreviewStore.instance.getPreviewForRoom(this.props.room, this.props.tag); const messagePreview = await MessagePreviewStore.instance.getPreviewForRoom(this.props.room, this.props.tag);

View file

@ -511,7 +511,7 @@ export default class SettingsStore {
* check at. * check at.
* @return {boolean} True if the user may set the setting, false otherwise. * @return {boolean} True if the user may set the setting, false otherwise.
*/ */
public static canSetValue(settingName: string, roomId: string, level: SettingLevel): boolean { public static canSetValue(settingName: string, roomId: string | null, level: SettingLevel): boolean {
// Verify that the setting is actually a setting // Verify that the setting is actually a setting
if (!SETTINGS[settingName]) { if (!SETTINGS[settingName]) {
throw new Error("Setting '" + settingName + "' does not appear to be a setting."); throw new Error("Setting '" + settingName + "' does not appear to be a setting.");

View file

@ -30,7 +30,7 @@ export default class EditorStateTransfer {
public constructor(private readonly event: MatrixEvent) {} public constructor(private readonly event: MatrixEvent) {}
public setEditorState(caret: DocumentOffset, serializedParts: SerializedPart[]): void { public setEditorState(caret: DocumentOffset | null, serializedParts: SerializedPart[]): void {
this.caret = caret; this.caret = caret;
this.serializedParts = serializedParts; this.serializedParts = serializedParts;
} }

View file

@ -263,7 +263,7 @@ export function editEvent(
} }
} }
export function canCancel(status: EventStatus): boolean { export function canCancel(status?: EventStatus | null): boolean {
return status === EventStatus.QUEUED || status === EventStatus.NOT_SENT || status === EventStatus.ENCRYPTING; return status === EventStatus.QUEUED || status === EventStatus.NOT_SENT || status === EventStatus.ENCRYPTING;
} }

View file

@ -80,7 +80,7 @@ describe("PreferencesUserSettingsTab", () => {
await waitFor(() => expect(toggle).toHaveAttribute("aria-disabled", "false")); await waitFor(() => expect(toggle).toHaveAttribute("aria-disabled", "false"));
fireEvent.click(toggle); fireEvent.click(toggle);
expectSetValueToHaveBeenCalled("sendReadReceipts", undefined, SettingLevel.ACCOUNT, true); expectSetValueToHaveBeenCalled("sendReadReceipts", null, SettingLevel.ACCOUNT, true);
}); });
it("can be disabled", async () => { it("can be disabled", async () => {
@ -89,7 +89,7 @@ describe("PreferencesUserSettingsTab", () => {
await waitFor(() => expect(toggle).toHaveAttribute("aria-disabled", "false")); await waitFor(() => expect(toggle).toHaveAttribute("aria-disabled", "false"));
fireEvent.click(toggle); fireEvent.click(toggle);
expectSetValueToHaveBeenCalled("sendReadReceipts", undefined, SettingLevel.ACCOUNT, false); expectSetValueToHaveBeenCalled("sendReadReceipts", null, SettingLevel.ACCOUNT, false);
}); });
}); });
@ -104,7 +104,7 @@ describe("PreferencesUserSettingsTab", () => {
await waitFor(() => expect(toggle).toHaveAttribute("aria-disabled", "false")); await waitFor(() => expect(toggle).toHaveAttribute("aria-disabled", "false"));
fireEvent.click(toggle); fireEvent.click(toggle);
expectSetValueToHaveBeenCalled("sendReadReceipts", undefined, SettingLevel.ACCOUNT, true); expectSetValueToHaveBeenCalled("sendReadReceipts", null, SettingLevel.ACCOUNT, true);
}); });
it("cannot be disabled", async () => { it("cannot be disabled", async () => {