Files
ThreadNet-Web/src/components/structures/EmbeddedPage.tsx
T

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

136 lines
4.2 KiB
TypeScript
Raw Normal View History

/*
2024-09-09 14:57:16 +01:00
Copyright 2019-2024 New Vector Ltd.
Copyright 2017 Vector Creations Ltd
2024-09-09 14:57:16 +01:00
Copyright 2016 OpenMarket Ltd
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
2024-09-09 14:57:16 +01:00
Please see LICENSE files in the repository root for full details.
*/
import React from "react";
import sanitizeHtml from "sanitize-html";
2021-10-22 17:23:32 -05:00
import classnames from "classnames";
import { logger } from "matrix-js-sdk/src/logger";
2025-02-05 13:25:06 +00:00
import { _t, type TranslationKey } from "../../languageHandler";
2020-05-13 20:41:41 -06:00
import dis from "../../dispatcher/dispatcher";
2021-06-29 13:11:58 +01:00
import { MatrixClientPeg } from "../../MatrixClientPeg";
2019-12-17 17:26:12 +00:00
import MatrixClientContext from "../../contexts/MatrixClientContext";
2020-03-13 23:39:42 +00:00
import AutoHideScrollbar from "./AutoHideScrollbar";
2025-02-05 13:25:06 +00:00
import { type ActionPayload } from "../../dispatcher/payloads";
2021-09-12 08:56:00 +02:00
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"}
2022-03-04 16:42:16 -07:00
replaceMap?: Record<string, string>;
2021-09-12 08:56:00 +02:00
}
2021-09-12 08:56:00 +02:00
interface IState {
page: string;
}
2021-09-12 08:56:00 +02:00
export default class EmbeddedPage extends React.PureComponent<IProps, IState> {
public static contextType = MatrixClientContext;
2024-12-02 09:39:36 +00:00
declare public context: React.ContextType<typeof MatrixClientContext>;
2021-09-13 18:14:55 +02:00
private unmounted = false;
private dispatcherRef?: string;
2021-09-12 08:56:00 +02:00
public constructor(props: IProps) {
super(props);
2019-02-07 10:33:03 +00:00
this.state = {
page: "",
2019-02-07 10:33:03 +00:00
};
}
private translate(s: TranslationKey): string {
return sanitizeHtml(_t(s));
}
private async fetchEmbed(): Promise<void> {
2022-10-12 18:59:07 +01:00
let res: Response;
try {
res = await fetch(this.props.url!, { method: "GET" });
2022-10-12 18:59:07 +01:00
} catch (err) {
if (this.unmounted) return;
logger.warn(`Error loading page: ${err}`);
this.setState({ page: _t("cant_load_page") });
2022-10-12 18:59:07 +01:00
return;
}
if (this.unmounted) return;
if (!res.ok) {
logger.warn(`Error loading page: ${res.status}`);
this.setState({ page: _t("cant_load_page") });
2022-10-12 18:59:07 +01:00
return;
}
// Replace '," and HTML encoded variants
let body = (await res.text()).replace(
/_t\((?:['"]|(?:&#(?:34|27);))([\s\S]*?)(?:['"]|(?:&#(?:34|27);))\)/gm,
(match, g1) => this.translate(g1),
);
2022-10-12 18:59:07 +01:00
if (this.props.replaceMap) {
Object.keys(this.props.replaceMap).forEach((key) => {
body = body.split(key).join(this.props.replaceMap![key]);
2022-10-12 18:59:07 +01:00
});
}
this.setState({ page: body });
}
2021-09-12 08:56:00 +02:00
public componentDidMount(): void {
this.unmounted = false;
2019-02-07 16:31:44 +00:00
if (!this.props.url) {
return;
}
2022-10-12 18:59:07 +01:00
// We use fetch to inline the page into the react component
2019-01-25 16:10:54 -06:00
// so that it can inherit CSS and theming easily rather than mess around
// with iframes and trying to synchronise document.stylesheets.
2022-10-12 18:59:07 +01:00
this.fetchEmbed();
2021-09-12 08:56:00 +02:00
this.dispatcherRef = dis.register(this.onAction);
}
2021-09-12 08:56:00 +02:00
public componentWillUnmount(): void {
this.unmounted = true;
dis.unregister(this.dispatcherRef);
}
2021-09-13 18:23:37 +02:00
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.
2019-12-17 17:26:12 +00:00
const client = this.context || MatrixClientPeg.get();
2019-02-07 16:25:09 +00:00
const isGuest = client ? client.isGuest() : true;
2019-02-07 11:12:28 +00:00
const className = this.props.className;
const classes = classnames(className, {
2019-02-07 11:12:28 +00:00
[`${className}_guest`]: isGuest,
[`${className}_loggedIn`]: !!client,
2019-02-01 15:33:05 -06:00
});
2019-02-07 16:25:09 +00:00
const content = <div className={`${className}_body`} dangerouslySetInnerHTML={{ __html: this.state.page }} />;
if (this.props.scrollbar) {
2020-03-13 23:39:42 +00:00
return <AutoHideScrollbar className={classes}>{content}</AutoHideScrollbar>;
2019-02-07 16:25:09 +00:00
} else {
return <div className={classes}>{content}</div>;
}
}
}