Add option to display tooltip on link hover (#8394)
* 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>
This commit is contained in:
parent
530b51a5ac
commit
6f21a155a4
8 changed files with 223 additions and 3 deletions
85
src/utils/tooltipify.tsx
Normal file
85
src/utils/tooltipify.tsx
Normal file
|
@ -0,0 +1,85 @@
|
|||
/*
|
||||
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);
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue