* Add option to display tooltip on link hover This makes it possible for platforms like Electron apps, which lack a built-in URL preview in the status bar, to enable tooltip previews of links. Relates to: vector-im/element-web#6532 Signed-off-by: Johannes Marbach <johannesm@element.io> * Gracefully handle missing platform * Use public access modifier Co-authored-by: Travis Ralston <travpc@gmail.com> * Use exact inequality Co-authored-by: Travis Ralston <travpc@gmail.com> * Document getAbsoluteUrl * Appease the linter * Clarify performance impact in comment Co-authored-by: Travis Ralston <travpc@gmail.com> * Use URL instead of anchor element hack * Wrap anchor in tooltip target and only allow focus on anchor * Use optional chaining Co-authored-by: Michael Telatynski <7t3chguy@gmail.com> * Use double quotes for consistency * Accumulate and unmount tooltips and extract tooltipify.tsx * Fix indentation * Blur tooltip target on click * Remove space * Mention platform flag in comment * Add (simplistic) tests * Fix lint errors * Fix lint errors ... for real * Replace snapshot tests with structural assertions * Add missing semicolon * Add tooltips in link previews * Fix copyright Co-authored-by: Travis Ralston <travpc@gmail.com> Co-authored-by: Michael Telatynski <7t3chguy@gmail.com>
85 lines
2.9 KiB
TypeScript
85 lines
2.9 KiB
TypeScript
/*
|
|
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 from "react";
|
|
import ReactDOM from 'react-dom';
|
|
|
|
import PlatformPeg from "../PlatformPeg";
|
|
import LinkWithTooltip from "../components/views/elements/LinkWithTooltip";
|
|
|
|
/**
|
|
* 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.
|
|
* @param {Element[]} ignoredNodes: a list of nodes to not recurse into.
|
|
* @param {Element[]} containers: an accumulator of the DOM nodes which contain
|
|
* React components that have been mounted by this function. The initial caller
|
|
* should pass in an empty array to seed the accumulator.
|
|
*/
|
|
export function tooltipifyLinks(rootNodes: ArrayLike<Element>, ignoredNodes: Element[], containers: Element[]) {
|
|
if (!PlatformPeg.get()?.needsUrlTooltips()) {
|
|
return;
|
|
}
|
|
|
|
let node = rootNodes[0];
|
|
|
|
while (node) {
|
|
let tooltipified = false;
|
|
|
|
if (ignoredNodes.indexOf(node) >= 0) {
|
|
node = node.nextSibling as Element;
|
|
continue;
|
|
}
|
|
|
|
if (node.tagName === "A" && node.getAttribute("href")
|
|
&& node.getAttribute("href") !== node.textContent.trim()
|
|
) {
|
|
const container = document.createElement("span");
|
|
const href = node.getAttribute("href");
|
|
|
|
const tooltip = <LinkWithTooltip tooltip={new URL(href, window.location.href).toString()}>
|
|
<span dangerouslySetInnerHTML={{ __html: node.outerHTML }} />
|
|
</LinkWithTooltip>;
|
|
|
|
ReactDOM.render(tooltip, container);
|
|
node.parentNode.replaceChild(container, node);
|
|
containers.push(container);
|
|
tooltipified = true;
|
|
}
|
|
|
|
if (node.childNodes?.length && !tooltipified) {
|
|
tooltipifyLinks(node.childNodes as NodeListOf<Element>, ignoredNodes, containers);
|
|
}
|
|
|
|
node = node.nextSibling as Element;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Unmount tooltip containers created by tooltipifyLinks.
|
|
*
|
|
* It's critical to call this after tooltipifyLinks, otherwise
|
|
* tooltips will leak.
|
|
*
|
|
* @param {Element[]} containers - array of tooltip containers to unmount
|
|
*/
|
|
export function unmountTooltips(containers: Element[]) {
|
|
for (const container of containers) {
|
|
ReactDOM.unmountComponentAtNode(container);
|
|
}
|
|
}
|