Switch to linkify-react for element Linkification as it handles React subtrees without exploding (#10060
* Switch to linkify-react instead of our faulty implementation Fixes a series of soft crashes where errors include "The node to be removed is not a child of this node." * Improve types * Fix types * Update snapshots * Add test * Fix test
This commit is contained in:
parent
089557005a
commit
2bde31dcff
15 changed files with 101 additions and 193 deletions
|
@ -1,39 +0,0 @@
|
|||
/*
|
||||
Copyright 2022 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React, { useLayoutEffect, useRef } from "react";
|
||||
|
||||
import { linkifyElement } from "../../../HtmlUtils";
|
||||
|
||||
interface Props {
|
||||
as?: string;
|
||||
children: React.ReactNode;
|
||||
onClick?: (ev: MouseEvent) => void;
|
||||
}
|
||||
|
||||
export function Linkify({ as = "div", children, onClick }: Props): JSX.Element {
|
||||
const ref = useRef();
|
||||
|
||||
useLayoutEffect(() => {
|
||||
linkifyElement(ref.current);
|
||||
}, [children]);
|
||||
|
||||
return React.createElement(as, {
|
||||
children,
|
||||
ref,
|
||||
onClick,
|
||||
});
|
||||
}
|
|
@ -29,9 +29,8 @@ import InfoDialog from "../dialogs/InfoDialog";
|
|||
import { useDispatcher } from "../../../hooks/useDispatcher";
|
||||
import MatrixClientContext from "../../../contexts/MatrixClientContext";
|
||||
import AccessibleButton from "./AccessibleButton";
|
||||
import { Linkify } from "./Linkify";
|
||||
import TooltipTarget from "./TooltipTarget";
|
||||
import { topicToHtml } from "../../../HtmlUtils";
|
||||
import { Linkify, topicToHtml } from "../../../HtmlUtils";
|
||||
|
||||
interface IProps extends React.HTMLProps<HTMLDivElement> {
|
||||
room?: Room;
|
||||
|
@ -71,12 +70,14 @@ export default function RoomTopic({ room, ...props }: IProps): JSX.Element {
|
|||
description: (
|
||||
<div>
|
||||
<Linkify
|
||||
as="p"
|
||||
onClick={(ev: MouseEvent) => {
|
||||
if ((ev.target as HTMLElement).tagName.toUpperCase() === "A") {
|
||||
modal.close();
|
||||
}
|
||||
options={{
|
||||
attributes: {
|
||||
onClick() {
|
||||
modal.close();
|
||||
},
|
||||
},
|
||||
}}
|
||||
as="p"
|
||||
>
|
||||
{body}
|
||||
</Linkify>
|
||||
|
|
|
@ -436,7 +436,7 @@ export default class TextualBody extends React.Component<IBodyProps, IState> {
|
|||
private onBodyLinkClick = (e: MouseEvent): void => {
|
||||
let target = e.target as HTMLLinkElement;
|
||||
// links processed by linkifyjs have their own handler so don't handle those here
|
||||
if (target.classList.contains(linkifyOpts.className)) return;
|
||||
if (target.classList.contains(linkifyOpts.className as string)) return;
|
||||
if (target.nodeName !== "A") {
|
||||
// Jump to parent as the `<a>` may contain children, e.g. an anchor wrapping an inline code section
|
||||
target = target.closest<HTMLLinkElement>("a");
|
||||
|
|
|
@ -19,7 +19,7 @@ import { decode } from "html-entities";
|
|||
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
|
||||
import { IPreviewUrlResponse } from "matrix-js-sdk/src/client";
|
||||
|
||||
import { linkifyElement } from "../../../HtmlUtils";
|
||||
import { Linkify } from "../../../HtmlUtils";
|
||||
import SettingsStore from "../../../settings/SettingsStore";
|
||||
import Modal from "../../../Modal";
|
||||
import * as ImageUtils from "../../../ImageUtils";
|
||||
|
@ -35,21 +35,8 @@ interface IProps {
|
|||
}
|
||||
|
||||
export default class LinkPreviewWidget extends React.Component<IProps> {
|
||||
private readonly description = createRef<HTMLDivElement>();
|
||||
private image = createRef<HTMLImageElement>();
|
||||
|
||||
public componentDidMount(): void {
|
||||
if (this.description.current) {
|
||||
linkifyElement(this.description.current);
|
||||
}
|
||||
}
|
||||
|
||||
public componentDidUpdate(): void {
|
||||
if (this.description.current) {
|
||||
linkifyElement(this.description.current);
|
||||
}
|
||||
}
|
||||
|
||||
private onImageClick = (ev): void => {
|
||||
const p = this.props.preview;
|
||||
if (ev.button != 0 || ev.metaKey) return;
|
||||
|
@ -155,8 +142,8 @@ export default class LinkPreviewWidget extends React.Component<IProps> {
|
|||
<span className="mx_LinkPreviewWidget_siteName">{" - " + p["og:site_name"]}</span>
|
||||
)}
|
||||
</div>
|
||||
<div className="mx_LinkPreviewWidget_description" ref={this.description}>
|
||||
{description}
|
||||
<div className="mx_LinkPreviewWidget_description">
|
||||
<Linkify>{description}</Linkify>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -37,7 +37,7 @@ const RoomInfoLine: FC<IProps> = ({ room }) => {
|
|||
const summary = useAsyncMemo(async (): Promise<Awaited<ReturnType<MatrixClient["getRoomSummary"]>> | null> => {
|
||||
if (room.getMyMembership() !== "invite") return null;
|
||||
try {
|
||||
return room.client.getRoomSummary(room.roomId);
|
||||
return await room.client.getRoomSummary(room.roomId);
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue