Apply strictNullChecks
to src/components/views/elements/*
(#10462
* Apply `strictNullChecks` to `src/components/views/elements/*` * Iterate * Iterate * Iterate * Apply `strictNullChecks` to `src/components/views/elements/*` * Iterate * Iterate * Iterate * Update snapshot
This commit is contained in:
parent
cefd94859c
commit
a47b3eb0ee
24 changed files with 158 additions and 121 deletions
|
@ -17,7 +17,7 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { ReactElement, ReactNode } from "react";
|
import React, { LegacyRef, ReactElement, ReactNode } from "react";
|
||||||
import sanitizeHtml from "sanitize-html";
|
import sanitizeHtml from "sanitize-html";
|
||||||
import cheerio from "cheerio";
|
import cheerio from "cheerio";
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
|
@ -93,8 +93,8 @@ const MEDIA_API_MXC_REGEX = /\/_matrix\/media\/r0\/(?:download|thumbnail)\/(.+?)
|
||||||
* positives, but useful for fast-path testing strings to see if they
|
* positives, but useful for fast-path testing strings to see if they
|
||||||
* need emojification.
|
* need emojification.
|
||||||
*/
|
*/
|
||||||
function mightContainEmoji(str: string): boolean {
|
function mightContainEmoji(str?: string): boolean {
|
||||||
return SURROGATE_PAIR_PATTERN.test(str) || SYMBOL_PATTERN.test(str);
|
return !!str && (SURROGATE_PAIR_PATTERN.test(str) || SYMBOL_PATTERN.test(str));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -463,7 +463,7 @@ const emojiToJsxSpan = (emoji: string, key: number): JSX.Element => (
|
||||||
* @returns if isHtmlMessage is true, returns an array of strings, otherwise return an array of React Elements for emojis
|
* @returns if isHtmlMessage is true, returns an array of strings, otherwise return an array of React Elements for emojis
|
||||||
* and plain text for everything else
|
* and plain text for everything else
|
||||||
*/
|
*/
|
||||||
function formatEmojis(message: string, isHtmlMessage: boolean): (JSX.Element | string)[] {
|
function formatEmojis(message: string | undefined, isHtmlMessage: boolean): (JSX.Element | string)[] {
|
||||||
const emojiToSpan = isHtmlMessage ? emojiToHtmlSpan : emojiToJsxSpan;
|
const emojiToSpan = isHtmlMessage ? emojiToHtmlSpan : emojiToJsxSpan;
|
||||||
const result: (JSX.Element | string)[] = [];
|
const result: (JSX.Element | string)[] = [];
|
||||||
let text = "";
|
let text = "";
|
||||||
|
@ -641,9 +641,9 @@ export function bodyToHtml(content: IContent, highlights: Optional<string[]>, op
|
||||||
* @return The HTML-ified node.
|
* @return The HTML-ified node.
|
||||||
*/
|
*/
|
||||||
export function topicToHtml(
|
export function topicToHtml(
|
||||||
topic: string,
|
topic?: string,
|
||||||
htmlTopic?: string,
|
htmlTopic?: string,
|
||||||
ref?: React.Ref<HTMLSpanElement>,
|
ref?: LegacyRef<HTMLSpanElement>,
|
||||||
allowExtendedHtml = false,
|
allowExtendedHtml = false,
|
||||||
): ReactNode {
|
): ReactNode {
|
||||||
if (!SettingsStore.getValue("feature_html_topic")) {
|
if (!SettingsStore.getValue("feature_html_topic")) {
|
||||||
|
|
|
@ -14,12 +14,13 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from "react";
|
import React, { ReactElement } from "react";
|
||||||
|
|
||||||
import { COUNTRIES, getEmojiFlag, PhoneNumberCountryDefinition } from "../../../phonenumber";
|
import { COUNTRIES, getEmojiFlag, PhoneNumberCountryDefinition } from "../../../phonenumber";
|
||||||
import SdkConfig from "../../../SdkConfig";
|
import SdkConfig from "../../../SdkConfig";
|
||||||
import { _t } from "../../../languageHandler";
|
import { _t } from "../../../languageHandler";
|
||||||
import Dropdown from "../elements/Dropdown";
|
import Dropdown from "../elements/Dropdown";
|
||||||
|
import { NonEmptyArray } from "../../../@types/common";
|
||||||
|
|
||||||
const COUNTRIES_BY_ISO2: Record<string, PhoneNumberCountryDefinition> = {};
|
const COUNTRIES_BY_ISO2: Record<string, PhoneNumberCountryDefinition> = {};
|
||||||
for (const c of COUNTRIES) {
|
for (const c of COUNTRIES) {
|
||||||
|
@ -131,7 +132,7 @@ export default class CountryDropdown extends React.Component<IProps, IState> {
|
||||||
{_t(country.name)} (+{country.prefix})
|
{_t(country.name)} (+{country.prefix})
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
});
|
}) as NonEmptyArray<ReactElement & { key: string }>;
|
||||||
|
|
||||||
// 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
|
||||||
|
|
|
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { ReactNode, useContext, useMemo, useRef, useState } from "react";
|
import React, { ReactElement, ReactNode, useContext, useMemo, useRef, useState } from "react";
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
import { Room } from "matrix-js-sdk/src/models/room";
|
import { Room } from "matrix-js-sdk/src/models/room";
|
||||||
import { sleep } from "matrix-js-sdk/src/utils";
|
import { sleep } from "matrix-js-sdk/src/utils";
|
||||||
|
@ -41,6 +41,7 @@ import QueryMatcher from "../../../autocomplete/QueryMatcher";
|
||||||
import LazyRenderList from "../elements/LazyRenderList";
|
import LazyRenderList from "../elements/LazyRenderList";
|
||||||
import { useSettingValue } from "../../../hooks/useSettings";
|
import { useSettingValue } from "../../../hooks/useSettings";
|
||||||
import { filterBoolean } from "../../../utils/arrays";
|
import { filterBoolean } from "../../../utils/arrays";
|
||||||
|
import { NonEmptyArray } from "../../../@types/common";
|
||||||
|
|
||||||
// These values match CSS
|
// These values match CSS
|
||||||
const ROW_HEIGHT = 32 + 12;
|
const ROW_HEIGHT = 32 + 12;
|
||||||
|
@ -415,17 +416,19 @@ export const SubspaceSelector: React.FC<ISubspaceSelectorProps> = ({ title, spac
|
||||||
value={value.roomId}
|
value={value.roomId}
|
||||||
label={_t("Space selection")}
|
label={_t("Space selection")}
|
||||||
>
|
>
|
||||||
{options.map((space) => {
|
{
|
||||||
const classes = classNames({
|
options.map((space) => {
|
||||||
mx_SubspaceSelector_dropdownOptionActive: space === value,
|
const classes = classNames({
|
||||||
});
|
mx_SubspaceSelector_dropdownOptionActive: space === value,
|
||||||
return (
|
});
|
||||||
<div key={space.roomId} className={classes}>
|
return (
|
||||||
<RoomAvatar room={space} width={24} height={24} />
|
<div key={space.roomId} className={classes}>
|
||||||
{space.name || getDisplayAliasForRoom(space) || space.roomId}
|
<RoomAvatar room={space} width={24} height={24} />
|
||||||
</div>
|
{space.name || getDisplayAliasForRoom(space) || space.roomId}
|
||||||
);
|
</div>
|
||||||
})}
|
);
|
||||||
|
}) as NonEmptyArray<ReactElement & { key: string }>
|
||||||
|
}
|
||||||
</Dropdown>
|
</Dropdown>
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -40,7 +40,7 @@ interface IProps {
|
||||||
interface IState {
|
interface IState {
|
||||||
roomMember: RoomMember;
|
roomMember: RoomMember;
|
||||||
isWrapped: boolean;
|
isWrapped: boolean;
|
||||||
widgetDomain: string;
|
widgetDomain: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class AppPermission extends React.Component<IProps, IState> {
|
export default class AppPermission extends React.Component<IProps, IState> {
|
||||||
|
@ -66,14 +66,14 @@ export default class AppPermission extends React.Component<IProps, IState> {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private parseWidgetUrl(): { isWrapped: boolean; widgetDomain: string } {
|
private parseWidgetUrl(): { isWrapped: boolean; widgetDomain: string | null } {
|
||||||
const widgetUrl = url.parse(this.props.url);
|
const widgetUrl = url.parse(this.props.url);
|
||||||
const params = new URLSearchParams(widgetUrl.search);
|
const params = new URLSearchParams(widgetUrl.search ?? undefined);
|
||||||
|
|
||||||
// HACK: We're relying on the query params when we should be relying on the widget's `data`.
|
// HACK: We're relying on the query params when we should be relying on the widget's `data`.
|
||||||
// This is a workaround for Scalar.
|
// This is a workaround for Scalar.
|
||||||
if (WidgetUtils.isScalarUrl(this.props.url) && params && params.get("url")) {
|
if (WidgetUtils.isScalarUrl(this.props.url) && params?.get("url")) {
|
||||||
const unwrappedUrl = url.parse(params.get("url"));
|
const unwrappedUrl = url.parse(params.get("url")!);
|
||||||
return {
|
return {
|
||||||
widgetDomain: unwrappedUrl.host || unwrappedUrl.hostname,
|
widgetDomain: unwrappedUrl.host || unwrappedUrl.hostname,
|
||||||
isWrapped: true,
|
isWrapped: true,
|
||||||
|
|
|
@ -586,7 +586,7 @@ export default class AppTile extends React.Component<IProps, IState> {
|
||||||
<AppWarning errorMsg={_t("Error loading Widget")} />
|
<AppWarning errorMsg={_t("Error loading Widget")} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
} else if (!this.state.hasPermissionToLoad) {
|
} else if (!this.state.hasPermissionToLoad && this.props.room) {
|
||||||
// only possible for room widgets, can assert this.props.room here
|
// only possible for room widgets, can assert this.props.room here
|
||||||
const isEncrypted = this.context.isRoomEncrypted(this.props.room.roomId);
|
const isEncrypted = this.context.isRoomEncrypted(this.props.room.roomId);
|
||||||
appTileBody = (
|
appTileBody = (
|
||||||
|
@ -689,11 +689,9 @@ export default class AppTile extends React.Component<IProps, IState> {
|
||||||
|
|
||||||
const layoutButtons: ReactNode[] = [];
|
const layoutButtons: ReactNode[] = [];
|
||||||
if (this.props.showLayoutButtons) {
|
if (this.props.showLayoutButtons) {
|
||||||
const isMaximised = WidgetLayoutStore.instance.isInContainer(
|
const isMaximised =
|
||||||
this.props.room,
|
this.props.room &&
|
||||||
this.props.app,
|
WidgetLayoutStore.instance.isInContainer(this.props.room, this.props.app, Container.Center);
|
||||||
Container.Center,
|
|
||||||
);
|
|
||||||
const maximisedClasses = classNames({
|
const maximisedClasses = classNames({
|
||||||
mx_AppTileMenuBar_iconButton: true,
|
mx_AppTileMenuBar_iconButton: true,
|
||||||
mx_AppTileMenuBar_iconButton_collapse: isMaximised,
|
mx_AppTileMenuBar_iconButton_collapse: isMaximised,
|
||||||
|
|
|
@ -123,7 +123,7 @@ export default class DesktopCapturerSourcePicker extends React.Component<PickerI
|
||||||
};
|
};
|
||||||
|
|
||||||
private onShare = (): void => {
|
private onShare = (): void => {
|
||||||
this.props.onFinished(this.state.selectedSource.id);
|
this.props.onFinished(this.state.selectedSource?.id);
|
||||||
};
|
};
|
||||||
|
|
||||||
private onTabChange = (): void => {
|
private onTabChange = (): void => {
|
||||||
|
|
|
@ -23,6 +23,7 @@ import { _t } from "../../../languageHandler";
|
||||||
import { getKeyBindingsManager } from "../../../KeyBindingsManager";
|
import { getKeyBindingsManager } from "../../../KeyBindingsManager";
|
||||||
import { KeyBindingAction } from "../../../accessibility/KeyboardShortcuts";
|
import { KeyBindingAction } from "../../../accessibility/KeyboardShortcuts";
|
||||||
import { objectHasDiff } from "../../../utils/objects";
|
import { objectHasDiff } from "../../../utils/objects";
|
||||||
|
import { NonEmptyArray } from "../../../@types/common";
|
||||||
|
|
||||||
interface IMenuOptionProps {
|
interface IMenuOptionProps {
|
||||||
children: ReactElement;
|
children: ReactElement;
|
||||||
|
@ -77,7 +78,7 @@ export interface DropdownProps {
|
||||||
label: string;
|
label: string;
|
||||||
value?: string;
|
value?: string;
|
||||||
className?: string;
|
className?: string;
|
||||||
children: ReactElement[];
|
children: NonEmptyArray<ReactElement & { key: string }>;
|
||||||
// negative for consistency with HTML
|
// negative for consistency with HTML
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
// The width that the dropdown should be. If specified,
|
// The width that the dropdown should be. If specified,
|
||||||
|
@ -102,7 +103,7 @@ export interface DropdownProps {
|
||||||
|
|
||||||
interface IState {
|
interface IState {
|
||||||
expanded: boolean;
|
expanded: boolean;
|
||||||
highlightedOption: string | null;
|
highlightedOption: string;
|
||||||
searchQuery: string;
|
searchQuery: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -122,14 +123,14 @@ export default class Dropdown extends React.Component<DropdownProps, IState> {
|
||||||
|
|
||||||
this.reindexChildren(this.props.children);
|
this.reindexChildren(this.props.children);
|
||||||
|
|
||||||
const firstChild = React.Children.toArray(props.children)[0] as ReactElement;
|
const firstChild = props.children[0];
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
// True if the menu is dropped-down
|
// True if the menu is dropped-down
|
||||||
expanded: false,
|
expanded: false,
|
||||||
// The key of the highlighted option
|
// The key of the highlighted option
|
||||||
// (the option that would become selected if you pressed enter)
|
// (the option that would become selected if you pressed enter)
|
||||||
highlightedOption: firstChild ? (firstChild.key as string) : null,
|
highlightedOption: firstChild.key,
|
||||||
// the current search query
|
// the current search query
|
||||||
searchQuery: "",
|
searchQuery: "",
|
||||||
};
|
};
|
||||||
|
@ -144,7 +145,7 @@ export default class Dropdown extends React.Component<DropdownProps, IState> {
|
||||||
this.reindexChildren(this.props.children);
|
this.reindexChildren(this.props.children);
|
||||||
const firstChild = this.props.children[0];
|
const firstChild = this.props.children[0];
|
||||||
this.setState({
|
this.setState({
|
||||||
highlightedOption: String(firstChild?.key) ?? null,
|
highlightedOption: firstChild.key,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -156,7 +157,7 @@ export default class Dropdown extends React.Component<DropdownProps, IState> {
|
||||||
private reindexChildren(children: ReactElement[]): void {
|
private reindexChildren(children: ReactElement[]): void {
|
||||||
this.childrenByKey = {};
|
this.childrenByKey = {};
|
||||||
React.Children.forEach(children, (child) => {
|
React.Children.forEach(children, (child) => {
|
||||||
this.childrenByKey[child.key] = child;
|
this.childrenByKey[(child as DropdownProps["children"][number]).key] = child;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -291,13 +292,11 @@ export default class Dropdown extends React.Component<DropdownProps, IState> {
|
||||||
return keys[index <= 0 ? keys.length - 1 : (index - 1) % keys.length];
|
return keys[index <= 0 ? keys.length - 1 : (index - 1) % keys.length];
|
||||||
}
|
}
|
||||||
|
|
||||||
private scrollIntoView(node: Element): void {
|
private scrollIntoView(node: Element | null): void {
|
||||||
if (node) {
|
node?.scrollIntoView({
|
||||||
node.scrollIntoView({
|
block: "nearest",
|
||||||
block: "nearest",
|
behavior: "auto",
|
||||||
behavior: "auto",
|
});
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private getMenuOptions(): JSX.Element[] {
|
private getMenuOptions(): JSX.Element[] {
|
||||||
|
@ -317,7 +316,7 @@ export default class Dropdown extends React.Component<DropdownProps, IState> {
|
||||||
</MenuOption>
|
</MenuOption>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
if (options.length === 0) {
|
if (!options?.length) {
|
||||||
return [
|
return [
|
||||||
<div key="0" className="mx_Dropdown_option" role="option" aria-selected={false}>
|
<div key="0" className="mx_Dropdown_option" role="option" aria-selected={false}>
|
||||||
{_t("No results")}
|
{_t("No results")}
|
||||||
|
@ -363,9 +362,13 @@ export default class Dropdown extends React.Component<DropdownProps, IState> {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!currentValue) {
|
if (!currentValue) {
|
||||||
const selectedChild = this.props.getShortOption
|
let selectedChild: ReactNode | undefined;
|
||||||
? this.props.getShortOption(this.props.value)
|
if (this.props.value) {
|
||||||
: this.childrenByKey[this.props.value];
|
selectedChild = this.props.getShortOption
|
||||||
|
? this.props.getShortOption(this.props.value)
|
||||||
|
: this.childrenByKey[this.props.value];
|
||||||
|
}
|
||||||
|
|
||||||
currentValue = (
|
currentValue = (
|
||||||
<div className="mx_Dropdown_option" id={`${this.props.id}_value`}>
|
<div className="mx_Dropdown_option" id={`${this.props.id}_value`}>
|
||||||
{selectedChild || this.props.placeholder}
|
{selectedChild || this.props.placeholder}
|
||||||
|
|
|
@ -87,6 +87,7 @@ export default class EditableText extends React.Component<IProps, IState> {
|
||||||
}
|
}
|
||||||
|
|
||||||
private showPlaceholder = (show: boolean): void => {
|
private showPlaceholder = (show: boolean): void => {
|
||||||
|
if (!this.editableDiv.current) return;
|
||||||
if (show) {
|
if (show) {
|
||||||
this.editableDiv.current.textContent = this.props.placeholder;
|
this.editableDiv.current.textContent = this.props.placeholder;
|
||||||
this.editableDiv.current.setAttribute(
|
this.editableDiv.current.setAttribute(
|
||||||
|
@ -134,7 +135,7 @@ export default class EditableText extends React.Component<IProps, IState> {
|
||||||
if (!(ev.target as HTMLDivElement).textContent) {
|
if (!(ev.target as HTMLDivElement).textContent) {
|
||||||
this.showPlaceholder(true);
|
this.showPlaceholder(true);
|
||||||
} else if (!this.placeholder) {
|
} else if (!this.placeholder) {
|
||||||
this.value = (ev.target as HTMLDivElement).textContent;
|
this.value = (ev.target as HTMLDivElement).textContent ?? "";
|
||||||
}
|
}
|
||||||
|
|
||||||
const action = getKeyBindingsManager().getAccessibilityAction(ev);
|
const action = getKeyBindingsManager().getAccessibilityAction(ev);
|
||||||
|
@ -163,7 +164,7 @@ export default class EditableText extends React.Component<IProps, IState> {
|
||||||
range.setStart(node, 0);
|
range.setStart(node, 0);
|
||||||
range.setEnd(node, ev.target.childNodes.length);
|
range.setEnd(node, ev.target.childNodes.length);
|
||||||
|
|
||||||
const sel = window.getSelection();
|
const sel = window.getSelection()!;
|
||||||
sel.removeAllRanges();
|
sel.removeAllRanges();
|
||||||
sel.addRange(range);
|
sel.addRange(range);
|
||||||
}
|
}
|
||||||
|
@ -190,7 +191,7 @@ export default class EditableText extends React.Component<IProps, IState> {
|
||||||
};
|
};
|
||||||
|
|
||||||
private onBlur = (ev: React.FocusEvent<HTMLDivElement>): void => {
|
private onBlur = (ev: React.FocusEvent<HTMLDivElement>): void => {
|
||||||
const sel = window.getSelection();
|
const sel = window.getSelection()!;
|
||||||
sel.removeAllRanges();
|
sel.removeAllRanges();
|
||||||
|
|
||||||
if (this.props.blurToCancel) {
|
if (this.props.blurToCancel) {
|
||||||
|
|
|
@ -54,14 +54,14 @@ const EffectsOverlay: FunctionComponent<IProps> = ({ roomWidth }) => {
|
||||||
};
|
};
|
||||||
const onAction = (payload: { action: string }): void => {
|
const onAction = (payload: { action: string }): void => {
|
||||||
const actionPrefix = "effects.";
|
const actionPrefix = "effects.";
|
||||||
if (payload.action.indexOf(actionPrefix) === 0) {
|
if (canvasRef.current && payload.action.startsWith(actionPrefix)) {
|
||||||
const effect = payload.action.slice(actionPrefix.length);
|
const effect = payload.action.slice(actionPrefix.length);
|
||||||
lazyLoadEffectModule(effect).then((module) => module?.start(canvasRef.current));
|
lazyLoadEffectModule(effect).then((module) => module?.start(canvasRef.current!));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const dispatcherRef = dis.register(onAction);
|
const dispatcherRef = dis.register(onAction);
|
||||||
const canvas = canvasRef.current;
|
const canvas = canvasRef.current;
|
||||||
canvas.height = UIStore.instance.windowHeight;
|
if (canvas) canvas.height = UIStore.instance.windowHeight;
|
||||||
UIStore.instance.on(UI_EVENTS.Resize, resize);
|
UIStore.instance.on(UI_EVENTS.Resize, resize);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
|
|
|
@ -78,7 +78,9 @@ enum TransitionType {
|
||||||
|
|
||||||
const SEP = ",";
|
const SEP = ",";
|
||||||
|
|
||||||
export default class EventListSummary extends React.Component<IProps> {
|
export default class EventListSummary extends React.Component<
|
||||||
|
IProps & Required<Pick<IProps, "summaryLength" | "threshold" | "avatarsMaxLength" | "layout">>
|
||||||
|
> {
|
||||||
public static contextType = RoomContext;
|
public static contextType = RoomContext;
|
||||||
public context!: React.ContextType<typeof RoomContext>;
|
public context!: React.ContextType<typeof RoomContext>;
|
||||||
|
|
||||||
|
@ -508,12 +510,12 @@ export default class EventListSummary extends React.Component<IProps> {
|
||||||
const type = e.getType();
|
const type = e.getType();
|
||||||
|
|
||||||
let userKey = e.getSender()!;
|
let userKey = e.getSender()!;
|
||||||
if (type === EventType.RoomThirdPartyInvite) {
|
if (e.isState() && type === EventType.RoomThirdPartyInvite) {
|
||||||
userKey = e.getContent().display_name;
|
userKey = e.getContent().display_name;
|
||||||
} else if (type === EventType.RoomMember) {
|
} else if (e.isState() && type === EventType.RoomMember) {
|
||||||
userKey = e.getStateKey();
|
userKey = e.getStateKey()!;
|
||||||
} else if (e.isRedacted()) {
|
} else if (e.isRedacted() && e.getUnsigned()?.redacted_because) {
|
||||||
userKey = e.getUnsigned()?.redacted_because?.sender;
|
userKey = e.getUnsigned().redacted_because!.sender;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialise a user's events
|
// Initialise a user's events
|
||||||
|
|
|
@ -38,8 +38,6 @@ export interface IValidateOpts {
|
||||||
interface IProps {
|
interface IProps {
|
||||||
// The field's ID, which binds the input and label together. Immutable.
|
// The field's ID, which binds the input and label together. Immutable.
|
||||||
id?: string;
|
id?: string;
|
||||||
// id of a <datalist> element for suggestions
|
|
||||||
list?: string;
|
|
||||||
// The field's label string.
|
// The field's label string.
|
||||||
label?: string;
|
label?: string;
|
||||||
// The field's placeholder string. Defaults to the label.
|
// The field's placeholder string. Defaults to the label.
|
||||||
|
@ -119,7 +117,7 @@ interface IState {
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class Field extends React.PureComponent<PropShapes, IState> {
|
export default class Field extends React.PureComponent<PropShapes, IState> {
|
||||||
private id: string;
|
private readonly id: string;
|
||||||
private inputRef: RefObject<HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement>;
|
private inputRef: RefObject<HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement>;
|
||||||
|
|
||||||
public static readonly defaultProps = {
|
public static readonly defaultProps = {
|
||||||
|
@ -243,7 +241,6 @@ export default class Field extends React.PureComponent<PropShapes, IState> {
|
||||||
tooltipContent,
|
tooltipContent,
|
||||||
forceValidity,
|
forceValidity,
|
||||||
tooltipClassName,
|
tooltipClassName,
|
||||||
list,
|
|
||||||
validateOnBlur,
|
validateOnBlur,
|
||||||
validateOnChange,
|
validateOnChange,
|
||||||
validateOnFocus,
|
validateOnFocus,
|
||||||
|
@ -262,7 +259,11 @@ export default class Field extends React.PureComponent<PropShapes, IState> {
|
||||||
inputProps.onBlur = this.onBlur;
|
inputProps.onBlur = this.onBlur;
|
||||||
|
|
||||||
// Appease typescript's inference
|
// Appease typescript's inference
|
||||||
const inputProps_ = { ...inputProps, ref: this.inputRef, list };
|
const inputProps_: React.HTMLAttributes<HTMLSelectElement | HTMLInputElement | HTMLTextAreaElement> &
|
||||||
|
React.ClassAttributes<HTMLSelectElement | HTMLInputElement | HTMLTextAreaElement> = {
|
||||||
|
...inputProps,
|
||||||
|
ref: this.inputRef,
|
||||||
|
};
|
||||||
|
|
||||||
const fieldInput = React.createElement(this.props.element, inputProps_, children);
|
const fieldInput = React.createElement(this.props.element, inputProps_, children);
|
||||||
|
|
||||||
|
@ -287,7 +288,7 @@ export default class Field extends React.PureComponent<PropShapes, IState> {
|
||||||
});
|
});
|
||||||
|
|
||||||
// Handle displaying feedback on validity
|
// Handle displaying feedback on validity
|
||||||
let fieldTooltip;
|
let fieldTooltip: JSX.Element | undefined;
|
||||||
if (tooltipContent || this.state.feedback) {
|
if (tooltipContent || this.state.feedback) {
|
||||||
let role: React.AriaRole;
|
let role: React.AriaRole;
|
||||||
if (tooltipContent) {
|
if (tooltipContent) {
|
||||||
|
|
|
@ -14,11 +14,12 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from "react";
|
import React, { ReactElement } from "react";
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
|
|
||||||
import { Icon as CheckmarkIcon } from "../../../../res/img/element-icons/roomlist/checkmark.svg";
|
import { Icon as CheckmarkIcon } from "../../../../res/img/element-icons/roomlist/checkmark.svg";
|
||||||
import Dropdown, { DropdownProps } from "./Dropdown";
|
import Dropdown, { DropdownProps } from "./Dropdown";
|
||||||
|
import { NonEmptyArray } from "../../../@types/common";
|
||||||
|
|
||||||
export type FilterDropdownOption<FilterKeysType extends string> = {
|
export type FilterDropdownOption<FilterKeysType extends string> = {
|
||||||
id: FilterKeysType;
|
id: FilterKeysType;
|
||||||
|
@ -63,13 +64,15 @@ export const FilterDropdown = <FilterKeysType extends string = string>({
|
||||||
className={classNames("mx_FilterDropdown", className)}
|
className={classNames("mx_FilterDropdown", className)}
|
||||||
getShortOption={getSelectedFilterOptionComponent<FilterKeysType>(options, selectedLabel)}
|
getShortOption={getSelectedFilterOptionComponent<FilterKeysType>(options, selectedLabel)}
|
||||||
>
|
>
|
||||||
{options.map(({ id, label, description }) => (
|
{
|
||||||
<div className="mx_FilterDropdown_option" data-testid={`filter-option-${id}`} key={id}>
|
options.map(({ id, label, description }) => (
|
||||||
{id === value && <CheckmarkIcon className="mx_FilterDropdown_optionSelectedIcon" />}
|
<div className="mx_FilterDropdown_option" data-testid={`filter-option-${id}`} key={id}>
|
||||||
<span className="mx_FilterDropdown_optionLabel">{label}</span>
|
{id === value && <CheckmarkIcon className="mx_FilterDropdown_optionSelectedIcon" />}
|
||||||
{!!description && <span className="mx_FilterDropdown_optionDescription">{description}</span>}
|
<span className="mx_FilterDropdown_optionLabel">{label}</span>
|
||||||
</div>
|
{!!description && <span className="mx_FilterDropdown_optionDescription">{description}</span>}
|
||||||
))}
|
</div>
|
||||||
|
)) as NonEmptyArray<ReactElement & { key: string }>
|
||||||
|
}
|
||||||
</Dropdown>
|
</Dropdown>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -96,17 +96,24 @@ export default class ImageView extends React.Component<IProps, IState> {
|
||||||
|
|
||||||
const { thumbnailInfo } = this.props;
|
const { thumbnailInfo } = this.props;
|
||||||
|
|
||||||
|
let translationX = 0;
|
||||||
|
let translationY = 0;
|
||||||
|
if (thumbnailInfo) {
|
||||||
|
translationX = thumbnailInfo.positionX + thumbnailInfo.width / 2 - UIStore.instance.windowWidth / 2;
|
||||||
|
translationY =
|
||||||
|
thumbnailInfo.positionY +
|
||||||
|
thumbnailInfo.height / 2 -
|
||||||
|
UIStore.instance.windowHeight / 2 -
|
||||||
|
getPanelHeight() / 2;
|
||||||
|
}
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
zoom: 0, // We default to 0 and override this in imageLoaded once we have naturalSize
|
zoom: 0, // We default to 0 and override this in imageLoaded once we have naturalSize
|
||||||
minZoom: MAX_SCALE,
|
minZoom: MAX_SCALE,
|
||||||
maxZoom: MAX_SCALE,
|
maxZoom: MAX_SCALE,
|
||||||
rotation: 0,
|
rotation: 0,
|
||||||
translationX: thumbnailInfo?.positionX + thumbnailInfo?.width / 2 - UIStore.instance.windowWidth / 2 ?? 0,
|
translationX,
|
||||||
translationY:
|
translationY,
|
||||||
thumbnailInfo?.positionY +
|
|
||||||
thumbnailInfo?.height / 2 -
|
|
||||||
UIStore.instance.windowHeight / 2 -
|
|
||||||
getPanelHeight() / 2 ?? 0,
|
|
||||||
moving: false,
|
moving: false,
|
||||||
contextMenuDisplayed: false,
|
contextMenuDisplayed: false,
|
||||||
};
|
};
|
||||||
|
@ -143,6 +150,7 @@ export default class ImageView extends React.Component<IProps, IState> {
|
||||||
}
|
}
|
||||||
|
|
||||||
private imageLoaded = (): void => {
|
private imageLoaded = (): void => {
|
||||||
|
if (!this.image.current) return;
|
||||||
// First, we calculate the zoom, so that the image has the same size as
|
// First, we calculate the zoom, so that the image has the same size as
|
||||||
// the thumbnail
|
// the thumbnail
|
||||||
const { thumbnailInfo } = this.props;
|
const { thumbnailInfo } = this.props;
|
||||||
|
@ -226,22 +234,23 @@ export default class ImageView extends React.Component<IProps, IState> {
|
||||||
translationX: 0,
|
translationX: 0,
|
||||||
translationY: 0,
|
translationY: 0,
|
||||||
});
|
});
|
||||||
} else if (typeof anchorX !== "number" && typeof anchorY !== "number") {
|
} else if (typeof anchorX !== "number" || typeof anchorY !== "number") {
|
||||||
// Zoom relative to the center of the view
|
// Zoom relative to the center of the view
|
||||||
this.setState({
|
this.setState({
|
||||||
zoom: newZoom,
|
zoom: newZoom,
|
||||||
translationX: (this.state.translationX * newZoom) / oldZoom,
|
translationX: (this.state.translationX * newZoom) / oldZoom,
|
||||||
translationY: (this.state.translationY * newZoom) / oldZoom,
|
translationY: (this.state.translationY * newZoom) / oldZoom,
|
||||||
});
|
});
|
||||||
} else {
|
} else if (this.image.current) {
|
||||||
// Zoom relative to the given point on the image.
|
// Zoom relative to the given point on the image.
|
||||||
// First we need to figure out the offset of the anchor point
|
// First we need to figure out the offset of the anchor point
|
||||||
// relative to the center of the image, accounting for rotation.
|
// relative to the center of the image, accounting for rotation.
|
||||||
let offsetX: number | undefined;
|
let offsetX: number;
|
||||||
let offsetY: number | undefined;
|
let offsetY: number;
|
||||||
// The modulo operator can return negative values for some
|
// The modulo operator can return negative values for some
|
||||||
// rotations, so we have to do some extra work to normalize it
|
// rotations, so we have to do some extra work to normalize it
|
||||||
switch (((this.state.rotation % 360) + 360) % 360) {
|
const rotation = (((this.state.rotation % 360) + 360) % 360) as 0 | 90 | 180 | 270;
|
||||||
|
switch (rotation) {
|
||||||
case 0:
|
case 0:
|
||||||
offsetX = this.image.current.clientWidth / 2 - anchorX;
|
offsetX = this.image.current.clientWidth / 2 - anchorX;
|
||||||
offsetY = this.image.current.clientHeight / 2 - anchorY;
|
offsetY = this.image.current.clientHeight / 2 - anchorY;
|
||||||
|
@ -384,7 +393,7 @@ export default class ImageView extends React.Component<IProps, IState> {
|
||||||
private onEndMoving = (): void => {
|
private onEndMoving = (): void => {
|
||||||
// Zoom out if we haven't moved much
|
// Zoom out if we haven't moved much
|
||||||
if (
|
if (
|
||||||
this.state.moving === true &&
|
this.state.moving &&
|
||||||
Math.abs(this.state.translationX - this.previousX) < ZOOM_DISTANCE &&
|
Math.abs(this.state.translationX - this.previousX) < ZOOM_DISTANCE &&
|
||||||
Math.abs(this.state.translationY - this.previousY) < ZOOM_DISTANCE
|
Math.abs(this.state.translationY - this.previousY) < ZOOM_DISTANCE
|
||||||
) {
|
) {
|
||||||
|
@ -397,7 +406,7 @@ export default class ImageView extends React.Component<IProps, IState> {
|
||||||
|
|
||||||
private renderContextMenu(): JSX.Element {
|
private renderContextMenu(): JSX.Element {
|
||||||
let contextMenu: JSX.Element | undefined;
|
let contextMenu: JSX.Element | undefined;
|
||||||
if (this.state.contextMenuDisplayed) {
|
if (this.state.contextMenuDisplayed && this.props.mxEvent) {
|
||||||
contextMenu = (
|
contextMenu = (
|
||||||
<MessageContextMenu
|
<MessageContextMenu
|
||||||
{...aboveLeftOf(this.contextMenuButton.current.getBoundingClientRect())}
|
{...aboveLeftOf(this.contextMenuButton.current.getBoundingClientRect())}
|
||||||
|
@ -445,7 +454,7 @@ export default class ImageView extends React.Component<IProps, IState> {
|
||||||
const showTwelveHour = SettingsStore.getValue("showTwelveHourTimestamps");
|
const showTwelveHour = SettingsStore.getValue("showTwelveHourTimestamps");
|
||||||
let permalink = "#";
|
let permalink = "#";
|
||||||
if (this.props.permalinkCreator) {
|
if (this.props.permalinkCreator) {
|
||||||
permalink = this.props.permalinkCreator.forEvent(mxEvent.getId());
|
permalink = this.props.permalinkCreator.forEvent(mxEvent.getId()!);
|
||||||
}
|
}
|
||||||
|
|
||||||
const senderName = mxEvent.sender?.name ?? mxEvent.getSender();
|
const senderName = mxEvent.sender?.name ?? mxEvent.getSender();
|
||||||
|
|
|
@ -378,6 +378,7 @@ export default class InteractiveTooltip extends React.Component<IProps, IState>
|
||||||
private onMouseMove = (ev: MouseEvent): void => {
|
private onMouseMove = (ev: MouseEvent): void => {
|
||||||
const { clientX: x, clientY: y } = ev;
|
const { clientX: x, clientY: y } = ev;
|
||||||
const { contentRect } = this.state;
|
const { contentRect } = this.state;
|
||||||
|
if (!contentRect) return;
|
||||||
const targetRect = this.target.getBoundingClientRect();
|
const targetRect = this.target.getBoundingClientRect();
|
||||||
|
|
||||||
let direction: Direction;
|
let direction: Direction;
|
||||||
|
|
|
@ -14,10 +14,11 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from "react";
|
import React, { ReactElement } from "react";
|
||||||
import { JoinRule } from "matrix-js-sdk/src/@types/partials";
|
import { JoinRule } from "matrix-js-sdk/src/@types/partials";
|
||||||
|
|
||||||
import Dropdown from "./Dropdown";
|
import Dropdown from "./Dropdown";
|
||||||
|
import { NonEmptyArray } from "../../../@types/common";
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
value: JoinRule;
|
value: JoinRule;
|
||||||
|
@ -45,13 +46,15 @@ const JoinRuleDropdown: React.FC<IProps> = ({
|
||||||
<div key={JoinRule.Public} className="mx_JoinRuleDropdown_public">
|
<div key={JoinRule.Public} className="mx_JoinRuleDropdown_public">
|
||||||
{labelPublic}
|
{labelPublic}
|
||||||
</div>,
|
</div>,
|
||||||
];
|
] as NonEmptyArray<ReactElement & { key: string }>;
|
||||||
|
|
||||||
if (labelRestricted) {
|
if (labelRestricted) {
|
||||||
options.unshift(
|
options.unshift(
|
||||||
<div key={JoinRule.Restricted} className="mx_JoinRuleDropdown_restricted">
|
(
|
||||||
{labelRestricted}
|
<div key={JoinRule.Restricted} className="mx_JoinRuleDropdown_restricted">
|
||||||
</div>,
|
{labelRestricted}
|
||||||
|
</div>
|
||||||
|
) as ReactElement & { key: string },
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -15,13 +15,14 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from "react";
|
import React, { ReactElement } from "react";
|
||||||
|
|
||||||
import * as languageHandler from "../../../languageHandler";
|
import * as languageHandler from "../../../languageHandler";
|
||||||
import SettingsStore from "../../../settings/SettingsStore";
|
import SettingsStore from "../../../settings/SettingsStore";
|
||||||
import { _t } from "../../../languageHandler";
|
import { _t } from "../../../languageHandler";
|
||||||
import Spinner from "./Spinner";
|
import Spinner from "./Spinner";
|
||||||
import Dropdown from "./Dropdown";
|
import Dropdown from "./Dropdown";
|
||||||
|
import { NonEmptyArray } from "../../../@types/common";
|
||||||
|
|
||||||
type Languages = Awaited<ReturnType<typeof languageHandler.getAllLanguagesFromJson>>;
|
type Languages = Awaited<ReturnType<typeof languageHandler.getAllLanguagesFromJson>>;
|
||||||
|
|
||||||
|
@ -99,7 +100,7 @@ export default class LanguageDropdown extends React.Component<IProps, IState> {
|
||||||
|
|
||||||
const options = displayedLanguages.map((language) => {
|
const options = displayedLanguages.map((language) => {
|
||||||
return <div key={language.value}>{language.label}</div>;
|
return <div key={language.value}>{language.label}</div>;
|
||||||
});
|
}) as NonEmptyArray<ReactElement & { key: string }>;
|
||||||
|
|
||||||
// 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
|
||||||
|
|
|
@ -16,7 +16,7 @@ limitations under the License.
|
||||||
|
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
import { EventType } from "matrix-js-sdk/src/@types/event";
|
import { EventType } from "matrix-js-sdk/src/@types/event";
|
||||||
import React, { useContext, useRef, useState, MouseEvent, ReactNode } from "react";
|
import React, { useContext, useRef, useState, MouseEvent, ReactNode, RefObject } from "react";
|
||||||
|
|
||||||
import MatrixClientContext from "../../../contexts/MatrixClientContext";
|
import MatrixClientContext from "../../../contexts/MatrixClientContext";
|
||||||
import RoomContext from "../../../contexts/RoomContext";
|
import RoomContext from "../../../contexts/RoomContext";
|
||||||
|
@ -59,7 +59,7 @@ const MiniAvatarUploader: React.FC<IProps> = ({
|
||||||
setShow(false);
|
setShow(false);
|
||||||
}, 13000); // hide after being shown for 10 seconds
|
}, 13000); // hide after being shown for 10 seconds
|
||||||
|
|
||||||
const uploadRef = useRef<HTMLInputElement>();
|
const uploadRef = useRef() as RefObject<HTMLInputElement>;
|
||||||
|
|
||||||
const label = hasAvatar || busy ? hasAvatarLabel : noAvatarLabel;
|
const label = hasAvatar || busy ? hasAvatarLabel : noAvatarLabel;
|
||||||
|
|
||||||
|
@ -97,7 +97,7 @@ const MiniAvatarUploader: React.FC<IProps> = ({
|
||||||
})}
|
})}
|
||||||
disabled={busy}
|
disabled={busy}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
uploadRef.current.click();
|
uploadRef.current?.click();
|
||||||
}}
|
}}
|
||||||
onMouseOver={() => setHover(true)}
|
onMouseOver={() => setHover(true)}
|
||||||
onMouseLeave={() => setHover(false)}
|
onMouseLeave={() => setHover(false)}
|
||||||
|
|
|
@ -31,7 +31,7 @@ import { Action } from "../../../dispatcher/actions";
|
||||||
import Spinner from "./Spinner";
|
import Spinner from "./Spinner";
|
||||||
import ReplyTile from "../rooms/ReplyTile";
|
import ReplyTile from "../rooms/ReplyTile";
|
||||||
import { Pill, PillType } from "./Pill";
|
import { Pill, PillType } from "./Pill";
|
||||||
import AccessibleButton, { ButtonEvent } from "./AccessibleButton";
|
import AccessibleButton from "./AccessibleButton";
|
||||||
import { getParentEventId, shouldDisplayReply } from "../../../utils/Reply";
|
import { getParentEventId, shouldDisplayReply } from "../../../utils/Reply";
|
||||||
import RoomContext from "../../../contexts/RoomContext";
|
import RoomContext from "../../../contexts/RoomContext";
|
||||||
import { MatrixClientPeg } from "../../../MatrixClientPeg";
|
import { MatrixClientPeg } from "../../../MatrixClientPeg";
|
||||||
|
@ -45,7 +45,7 @@ const SHOW_EXPAND_QUOTE_PIXELS = 60;
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
// the latest event in this chain of replies
|
// the latest event in this chain of replies
|
||||||
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;
|
||||||
|
@ -91,7 +91,7 @@ export default class ReplyChain extends React.Component<IProps, IState> {
|
||||||
err: false,
|
err: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
this.room = this.matrixClient.getRoom(this.props.parentEv.getRoomId());
|
this.room = this.matrixClient.getRoom(this.props.parentEv.getRoomId())!;
|
||||||
}
|
}
|
||||||
|
|
||||||
private get matrixClient(): MatrixClient {
|
private get matrixClient(): MatrixClient {
|
||||||
|
@ -155,7 +155,7 @@ export default class ReplyChain extends React.Component<IProps, IState> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async getEvent(eventId: string): Promise<MatrixEvent | null> {
|
private async getEvent(eventId?: string): Promise<MatrixEvent | null> {
|
||||||
if (!eventId) return null;
|
if (!eventId) return null;
|
||||||
const event = this.room.findEventById(eventId);
|
const event = this.room.findEventById(eventId);
|
||||||
if (event) return event;
|
if (event) return event;
|
||||||
|
@ -180,7 +180,8 @@ export default class ReplyChain extends React.Component<IProps, IState> {
|
||||||
this.initialize();
|
this.initialize();
|
||||||
};
|
};
|
||||||
|
|
||||||
private onQuoteClick = async (event: ButtonEvent): Promise<void> => {
|
private onQuoteClick = async (): Promise<void> => {
|
||||||
|
if (!this.state.loadedEv) return;
|
||||||
const events = [this.state.loadedEv, ...this.state.events];
|
const events = [this.state.loadedEv, ...this.state.events];
|
||||||
|
|
||||||
let loadedEv: MatrixEvent | null = null;
|
let loadedEv: MatrixEvent | null = null;
|
||||||
|
|
|
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { useCallback, useContext, useRef } from "react";
|
import React, { RefObject, useCallback, useContext, useRef } from "react";
|
||||||
import { Room } from "matrix-js-sdk/src/models/room";
|
import { Room } from "matrix-js-sdk/src/models/room";
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
import { EventType } from "matrix-js-sdk/src/@types/event";
|
import { EventType } from "matrix-js-sdk/src/@types/event";
|
||||||
|
@ -38,7 +38,7 @@ interface IProps extends React.HTMLProps<HTMLDivElement> {
|
||||||
|
|
||||||
export default function RoomTopic({ room, ...props }: IProps): JSX.Element {
|
export default function RoomTopic({ room, ...props }: IProps): JSX.Element {
|
||||||
const client = useContext(MatrixClientContext);
|
const client = useContext(MatrixClientContext);
|
||||||
const ref = useRef<HTMLDivElement>();
|
const ref = useRef() as RefObject<HTMLDivElement>;
|
||||||
|
|
||||||
const topic = useTopic(room);
|
const topic = useTopic(room);
|
||||||
const body = topicToHtml(topic?.text, topic?.html, ref);
|
const body = topicToHtml(topic?.text, topic?.html, ref);
|
||||||
|
|
|
@ -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, { ReactElement } from "react";
|
||||||
|
|
||||||
import Dropdown from "../../views/elements/Dropdown";
|
import Dropdown from "../../views/elements/Dropdown";
|
||||||
import PlatformPeg from "../../../PlatformPeg";
|
import PlatformPeg from "../../../PlatformPeg";
|
||||||
|
@ -22,6 +22,7 @@ import SettingsStore from "../../../settings/SettingsStore";
|
||||||
import { _t } from "../../../languageHandler";
|
import { _t } from "../../../languageHandler";
|
||||||
import Spinner from "./Spinner";
|
import Spinner from "./Spinner";
|
||||||
import * as languageHandler from "../../../languageHandler";
|
import * as languageHandler from "../../../languageHandler";
|
||||||
|
import { NonEmptyArray } from "../../../@types/common";
|
||||||
|
|
||||||
type Languages = Awaited<ReturnType<typeof languageHandler.getAllLanguagesFromJson>>;
|
type Languages = Awaited<ReturnType<typeof languageHandler.getAllLanguagesFromJson>>;
|
||||||
function languageMatchesSearchQuery(query: string, language: Languages[0]): boolean {
|
function languageMatchesSearchQuery(query: string, language: Languages[0]): boolean {
|
||||||
|
@ -106,7 +107,7 @@ export default class SpellCheckLanguagesDropdown extends React.Component<
|
||||||
|
|
||||||
const options = displayedLanguages.map((language) => {
|
const options = displayedLanguages.map((language) => {
|
||||||
return <div key={language.value}>{language.label}</div>;
|
return <div key={language.value}>{language.label}</div>;
|
||||||
});
|
}) as NonEmptyArray<ReactElement & { key: string }>;
|
||||||
|
|
||||||
// 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
|
||||||
|
|
|
@ -14,11 +14,12 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from "react";
|
import React, { ReactElement } from "react";
|
||||||
|
|
||||||
import { formatDuration } from "../../../DateUtils";
|
import { formatDuration } from "../../../DateUtils";
|
||||||
import { _t } from "../../../languageHandler";
|
import { _t } from "../../../languageHandler";
|
||||||
import Dropdown from "../elements/Dropdown";
|
import Dropdown from "../elements/Dropdown";
|
||||||
|
import { NonEmptyArray } from "../../../@types/common";
|
||||||
|
|
||||||
const DURATION_MS = {
|
const DURATION_MS = {
|
||||||
fifteenMins: 900000,
|
fifteenMins: 900000,
|
||||||
|
@ -68,11 +69,13 @@ const LiveDurationDropdown: React.FC<Props> = ({ timeout, onChange }) => {
|
||||||
onOptionChange={onOptionChange}
|
onOptionChange={onOptionChange}
|
||||||
className="mx_LiveDurationDropdown"
|
className="mx_LiveDurationDropdown"
|
||||||
>
|
>
|
||||||
{options.map(({ key, label }) => (
|
{
|
||||||
<div data-test-id={`live-duration-option-${key}`} key={key}>
|
options.map(({ key, label }) => (
|
||||||
{label}
|
<div data-test-id={`live-duration-option-${key}`} key={key}>
|
||||||
</div>
|
{label}
|
||||||
))}
|
</div>
|
||||||
|
)) as NonEmptyArray<ReactElement & { key: string }>
|
||||||
|
}
|
||||||
</Dropdown>
|
</Dropdown>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { useMemo } from "react";
|
import React, { ReactElement, useMemo } from "react";
|
||||||
|
|
||||||
import { _t } from "../../../languageHandler";
|
import { _t } from "../../../languageHandler";
|
||||||
import { Action } from "../../../dispatcher/actions";
|
import { Action } from "../../../dispatcher/actions";
|
||||||
|
@ -26,6 +26,7 @@ import { SettingLevel } from "../../../settings/SettingLevel";
|
||||||
import dis from "../../../dispatcher/dispatcher";
|
import dis from "../../../dispatcher/dispatcher";
|
||||||
import { RecheckThemePayload } from "../../../dispatcher/payloads/RecheckThemePayload";
|
import { RecheckThemePayload } from "../../../dispatcher/payloads/RecheckThemePayload";
|
||||||
import PosthogTrackers from "../../../PosthogTrackers";
|
import PosthogTrackers from "../../../PosthogTrackers";
|
||||||
|
import { NonEmptyArray } from "../../../@types/common";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
requestClose: () => void;
|
requestClose: () => void;
|
||||||
|
@ -86,9 +87,11 @@ const QuickThemeSwitcher: React.FC<Props> = ({ requestClose }) => {
|
||||||
value={selectedTheme}
|
value={selectedTheme}
|
||||||
label={_t("Space selection")}
|
label={_t("Space selection")}
|
||||||
>
|
>
|
||||||
{themeOptions.map((theme) => (
|
{
|
||||||
<div key={theme.id}>{theme.name}</div>
|
themeOptions.map((theme) => <div key={theme.id}>{theme.name}</div>) as NonEmptyArray<
|
||||||
))}
|
ReactElement & { key: string }
|
||||||
|
>
|
||||||
|
}
|
||||||
</Dropdown>
|
</Dropdown>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -118,7 +118,10 @@ describe("EventListSummary", function () {
|
||||||
...mockClientMethodsUser(),
|
...mockClientMethodsUser(),
|
||||||
});
|
});
|
||||||
|
|
||||||
const defaultProps: ComponentProps<typeof EventListSummary> = {
|
const defaultProps: Omit<
|
||||||
|
ComponentProps<typeof EventListSummary>,
|
||||||
|
"summaryLength" | "threshold" | "avatarsMaxLength"
|
||||||
|
> = {
|
||||||
layout: Layout.Bubble,
|
layout: Layout.Bubble,
|
||||||
events: [],
|
events: [],
|
||||||
children: [],
|
children: [],
|
||||||
|
|
|
@ -7,8 +7,8 @@ exports[`<FilterDropdown /> renders dropdown options in menu 1`] = `
|
||||||
role="listbox"
|
role="listbox"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
aria-selected="false"
|
aria-selected="true"
|
||||||
class="mx_Dropdown_option"
|
class="mx_Dropdown_option mx_Dropdown_option_highlight"
|
||||||
id="test__one"
|
id="test__one"
|
||||||
role="option"
|
role="option"
|
||||||
>
|
>
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue