2022-07-06 11:43:30 +02:00
|
|
|
/*
|
2024-09-09 14:57:16 +01:00
|
|
|
Copyright 2024 New Vector Ltd.
|
2022-07-06 11:43:30 +02:00
|
|
|
Copyright 2022 The Matrix.org Foundation C.I.C.
|
|
|
|
|
|
2025-01-06 11:18:54 +00:00
|
|
|
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.
|
2022-07-06 11:43:30 +02:00
|
|
|
*/
|
|
|
|
|
|
2024-10-25 12:20:25 +01:00
|
|
|
import React, { StrictMode } from "react";
|
2024-10-14 21:41:58 +05:30
|
|
|
import { TooltipProvider } from "@vector-im/compound-web";
|
2022-07-06 11:43:30 +02:00
|
|
|
|
|
|
|
|
import PlatformPeg from "../PlatformPeg";
|
|
|
|
|
import LinkWithTooltip from "../components/views/elements/LinkWithTooltip";
|
2024-11-06 12:44:54 +00:00
|
|
|
import { ReactRootManager } from "./react";
|
2022-07-06 11:43:30 +02:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* If the platform enabled needsUrlTooltips, recurses depth-first through a DOM tree, adding tooltip previews
|
|
|
|
|
* for link elements. Otherwise, does nothing.
|
|
|
|
|
*
|
|
|
|
|
* @param {Element[]} rootNodes - a list of sibling DOM nodes to traverse to try
|
|
|
|
|
* to add tooltips.
|
2024-11-06 12:44:54 +00:00
|
|
|
* @param {Element[]} ignoredNodes - a list of nodes to not recurse into.
|
|
|
|
|
* @param {ReactRootManager} tooltips - an accumulator of the DOM nodes which contain
|
2022-07-06 11:43:30 +02:00
|
|
|
* React components that have been mounted by this function. The initial caller
|
|
|
|
|
* should pass in an empty array to seed the accumulator.
|
|
|
|
|
*/
|
2024-11-06 12:44:54 +00:00
|
|
|
export function tooltipifyLinks(
|
|
|
|
|
rootNodes: ArrayLike<Element>,
|
|
|
|
|
ignoredNodes: Element[],
|
|
|
|
|
tooltips: ReactRootManager,
|
|
|
|
|
): void {
|
2022-07-06 11:43:30 +02:00
|
|
|
if (!PlatformPeg.get()?.needsUrlTooltips()) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let node = rootNodes[0];
|
|
|
|
|
|
|
|
|
|
while (node) {
|
2024-11-06 12:44:54 +00:00
|
|
|
if (ignoredNodes.includes(node) || tooltips.elements.includes(node)) {
|
2022-07-06 11:43:30 +02:00
|
|
|
node = node.nextSibling as Element;
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (
|
|
|
|
|
node.tagName === "A" &&
|
|
|
|
|
node.getAttribute("href") &&
|
2023-02-03 15:27:47 +00:00
|
|
|
node.getAttribute("href") !== node.textContent?.trim()
|
2022-07-06 11:43:30 +02:00
|
|
|
) {
|
2023-02-03 15:27:47 +00:00
|
|
|
let href = node.getAttribute("href")!;
|
2022-09-12 15:28:22 +01:00
|
|
|
try {
|
|
|
|
|
href = new URL(href, window.location.href).toString();
|
2024-10-16 16:43:07 +01:00
|
|
|
} catch {
|
2022-09-12 15:28:22 +01:00
|
|
|
// Not all hrefs will be valid URLs
|
|
|
|
|
}
|
2022-07-06 11:43:30 +02:00
|
|
|
|
2022-08-09 15:37:55 +01:00
|
|
|
// The node's innerHTML was already sanitized before being rendered in the first place, here we are just
|
|
|
|
|
// wrapping the link with the LinkWithTooltip component, keeping the same children. Ideally we'd do this
|
|
|
|
|
// without the superfluous span but this is not something React trivially supports at this time.
|
2022-09-12 15:28:22 +01:00
|
|
|
const tooltip = (
|
2024-10-25 12:20:25 +01:00
|
|
|
<StrictMode>
|
|
|
|
|
<TooltipProvider>
|
|
|
|
|
<LinkWithTooltip tooltip={href}>
|
|
|
|
|
<span dangerouslySetInnerHTML={{ __html: node.innerHTML }} />
|
|
|
|
|
</LinkWithTooltip>
|
|
|
|
|
</TooltipProvider>
|
|
|
|
|
</StrictMode>
|
2022-07-06 11:43:30 +02:00
|
|
|
);
|
|
|
|
|
|
2025-01-03 16:40:05 +00:00
|
|
|
tooltips.render(tooltip, node, null);
|
2022-08-09 15:37:55 +01:00
|
|
|
} else if (node.childNodes?.length) {
|
2024-11-06 12:44:54 +00:00
|
|
|
tooltipifyLinks(node.childNodes as NodeListOf<Element>, ignoredNodes, tooltips);
|
2022-07-06 11:43:30 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
node = node.nextSibling as Element;
|
|
|
|
|
}
|
|
|
|
|
}
|