* Update all non-major dependencies * Prettier Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --------- Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Michael Telatynski <7t3chguy@gmail.com>
131 lines
4.1 KiB
TypeScript
131 lines
4.1 KiB
TypeScript
/*
|
|
Copyright 2019-2024 New Vector Ltd.
|
|
Copyright 2017 Vector Creations Ltd
|
|
Copyright 2016 OpenMarket Ltd
|
|
|
|
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
|
|
Please see LICENSE files in the repository root for full details.
|
|
*/
|
|
|
|
import React from "react";
|
|
import sanitizeHtml from "sanitize-html";
|
|
import classnames from "classnames";
|
|
import { logger } from "matrix-js-sdk/src/logger";
|
|
|
|
import { _t, TranslationKey } from "../../languageHandler";
|
|
import dis from "../../dispatcher/dispatcher";
|
|
import { MatrixClientPeg } from "../../MatrixClientPeg";
|
|
import MatrixClientContext from "../../contexts/MatrixClientContext";
|
|
import AutoHideScrollbar from "./AutoHideScrollbar";
|
|
import { ActionPayload } from "../../dispatcher/payloads";
|
|
|
|
interface IProps {
|
|
// URL to request embedded page content from
|
|
url?: string;
|
|
// Class name prefix to apply for a given instance
|
|
className?: string;
|
|
// Whether to wrap the page in a scrollbar
|
|
scrollbar?: boolean;
|
|
// Map of keys to replace with values, e.g {$placeholder: "value"}
|
|
replaceMap?: Record<string, string>;
|
|
}
|
|
|
|
interface IState {
|
|
page: string;
|
|
}
|
|
|
|
export default class EmbeddedPage extends React.PureComponent<IProps, IState> {
|
|
public static contextType = MatrixClientContext;
|
|
declare public context: React.ContextType<typeof MatrixClientContext>;
|
|
private unmounted = false;
|
|
private dispatcherRef?: string;
|
|
|
|
public constructor(props: IProps, context: React.ContextType<typeof MatrixClientContext>) {
|
|
super(props, context);
|
|
|
|
this.state = {
|
|
page: "",
|
|
};
|
|
}
|
|
|
|
private translate(s: TranslationKey): string {
|
|
return sanitizeHtml(_t(s));
|
|
}
|
|
|
|
private async fetchEmbed(): Promise<void> {
|
|
let res: Response;
|
|
|
|
try {
|
|
res = await fetch(this.props.url!, { method: "GET" });
|
|
} catch (err) {
|
|
if (this.unmounted) return;
|
|
logger.warn(`Error loading page: ${err}`);
|
|
this.setState({ page: _t("cant_load_page") });
|
|
return;
|
|
}
|
|
|
|
if (this.unmounted) return;
|
|
|
|
if (!res.ok) {
|
|
logger.warn(`Error loading page: ${res.status}`);
|
|
this.setState({ page: _t("cant_load_page") });
|
|
return;
|
|
}
|
|
|
|
let body = (await res.text()).replace(/_t\(['"]([\s\S]*?)['"]\)/gm, (match, g1) => this.translate(g1));
|
|
|
|
if (this.props.replaceMap) {
|
|
Object.keys(this.props.replaceMap).forEach((key) => {
|
|
body = body.split(key).join(this.props.replaceMap![key]);
|
|
});
|
|
}
|
|
|
|
this.setState({ page: body });
|
|
}
|
|
|
|
public componentDidMount(): void {
|
|
this.unmounted = false;
|
|
|
|
if (!this.props.url) {
|
|
return;
|
|
}
|
|
|
|
// We use fetch to inline the page into the react component
|
|
// so that it can inherit CSS and theming easily rather than mess around
|
|
// with iframes and trying to synchronise document.stylesheets.
|
|
this.fetchEmbed();
|
|
|
|
this.dispatcherRef = dis.register(this.onAction);
|
|
}
|
|
|
|
public componentWillUnmount(): void {
|
|
this.unmounted = true;
|
|
dis.unregister(this.dispatcherRef);
|
|
}
|
|
|
|
private onAction = (payload: ActionPayload): void => {
|
|
// HACK: Workaround for the context's MatrixClient not being set up at render time.
|
|
if (payload.action === "client_started") {
|
|
this.forceUpdate();
|
|
}
|
|
};
|
|
|
|
public render(): React.ReactNode {
|
|
// HACK: Workaround for the context's MatrixClient not updating.
|
|
const client = this.context || MatrixClientPeg.get();
|
|
const isGuest = client ? client.isGuest() : true;
|
|
const className = this.props.className;
|
|
const classes = classnames(className, {
|
|
[`${className}_guest`]: isGuest,
|
|
[`${className}_loggedIn`]: !!client,
|
|
});
|
|
|
|
const content = <div className={`${className}_body`} dangerouslySetInnerHTML={{ __html: this.state.page }} />;
|
|
|
|
if (this.props.scrollbar) {
|
|
return <AutoHideScrollbar className={classes}>{content}</AutoHideScrollbar>;
|
|
} else {
|
|
return <div className={classes}>{content}</div>;
|
|
}
|
|
}
|
|
}
|