Skip to content
This repository was archived by the owner on Sep 11, 2024. It is now read-only.

Commit f18cce8

Browse files
committed
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: element-hq/element-web#6532 Signed-off-by: Johannes Marbach <[email protected]>
1 parent bbe0c94 commit f18cce8

File tree

4 files changed

+72
-0
lines changed

4 files changed

+72
-0
lines changed

src/BasePlatform.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,14 @@ export default abstract class BasePlatform {
222222
}
223223
}
224224

225+
/**
226+
* Returns true if the platform requires URL previews in tooltips, otherwise false.
227+
* @returns {boolean} whether the platform requires URL previews in tooltips
228+
*/
229+
needsUrlTooltips(): boolean {
230+
return false;
231+
}
232+
225233
/**
226234
* Returns a promise that resolves to a string representing the current version of the application.
227235
*/

src/HtmlUtils.tsx

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ limitations under the License.
1818
*/
1919

2020
import React, { ReactNode } from 'react';
21+
import ReactDOM from 'react-dom';
2122
import sanitizeHtml from 'sanitize-html';
2223
import cheerio from 'cheerio';
2324
import classNames from 'classnames';
@@ -35,6 +36,8 @@ import { getEmojiFromUnicode } from "./emoji";
3536
import { mediaFromMxc } from "./customisations/Media";
3637
import { ELEMENT_URL_PATTERN, options as linkifyMatrixOptions } from './linkify-matrix';
3738
import { stripHTMLReply, stripPlainReply } from './utils/Reply';
39+
import TextWithTooltip from './components/views/elements/TextWithTooltip';
40+
import PlatformPeg from './PlatformPeg';
3841

3942
// Anything outside the basic multilingual plane will be a surrogate pair
4043
const SURROGATE_PAIR_PATTERN = /([\ud800-\udbff])([\udc00-\udfff])/;
@@ -635,6 +638,57 @@ export function linkifyAndSanitizeHtml(dirtyHtml: string, options = linkifyMatri
635638
return sanitizeHtml(linkifyString(dirtyHtml, options), sanitizeHtmlParams);
636639
}
637640

641+
const getAbsoluteUrl = (() => {
642+
let a: HTMLAnchorElement;
643+
644+
return (url: string) => {
645+
if (!a) {
646+
a = document.createElement('a');
647+
}
648+
a.href = url;
649+
return a.href;
650+
};
651+
})();
652+
653+
/**
654+
* Recurses depth-first through a DOM tree, adding tooltip previews for link elements.
655+
*
656+
* @param {Element[]} rootNodes - a list of sibling DOM nodes to traverse to try
657+
* to add tooltips.
658+
* @param {Element[]} ignoredNodes: a list of nodes to not recurse into.
659+
*/
660+
export function tooltipifyLinks(rootNodes: ArrayLike<Element>, ignoredNodes: Element[]) {
661+
if (!PlatformPeg.get().needsUrlTooltips()) {
662+
return;
663+
}
664+
665+
let node = rootNodes[0];
666+
667+
while (node) {
668+
let tooltipified = false;
669+
670+
if (ignoredNodes.indexOf(node) >= 0) {
671+
node = node.nextSibling as Element;
672+
continue;
673+
}
674+
675+
if (node.tagName === "A" && node.getAttribute("href") && node.getAttribute("href") != node.textContent.trim()) {
676+
const href = node.getAttribute("href");
677+
const tooltip = <TextWithTooltip tooltip={getAbsoluteUrl(href)}>
678+
<span dangerouslySetInnerHTML={{ __html: node.innerHTML }} />
679+
</TextWithTooltip>;
680+
ReactDOM.render(tooltip, node);
681+
tooltipified = true;
682+
}
683+
684+
if (node.childNodes && node.childNodes.length && !tooltipified) {
685+
tooltipifyLinks(node.childNodes as NodeListOf<Element>, ignoredNodes);
686+
}
687+
688+
node = node.nextSibling as Element;
689+
}
690+
}
691+
638692
/**
639693
* Returns if a node is a block element or not.
640694
* Only takes html nodes into account that are allowed in matrix messages.

src/components/views/messages/EditHistoryMessage.tsx

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,8 +93,16 @@ export default class EditHistoryMessage extends React.PureComponent<IProps, ISta
9393
}
9494
}
9595

96+
private tooltipifyLinks(): void {
97+
// not present for redacted events
98+
if (this.content.current) {
99+
HtmlUtils.tooltipifyLinks(this.content.current.children, this.pills);
100+
}
101+
}
102+
96103
public componentDidMount(): void {
97104
this.pillifyLinks();
105+
this.tooltipifyLinks();
98106
}
99107

100108
public componentWillUnmount(): void {
@@ -107,6 +115,7 @@ export default class EditHistoryMessage extends React.PureComponent<IProps, ISta
107115

108116
public componentDidUpdate(): void {
109117
this.pillifyLinks();
118+
this.tooltipifyLinks();
110119
}
111120

112121
private renderActionBar(): JSX.Element {

src/components/views/messages/TextualBody.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ export default class TextualBody extends React.Component<IBodyProps, IState> {
9191
// we should be pillify them here by doing the linkifying BEFORE the pillifying.
9292
pillifyLinks([this.contentRef.current], this.props.mxEvent, this.pills);
9393
HtmlUtils.linkifyElement(this.contentRef.current);
94+
HtmlUtils.tooltipifyLinks([this.contentRef.current], this.pills);
9495
this.calculateUrlPreview();
9596

9697
if (this.props.mxEvent.getContent().format === "org.matrix.custom.html") {

0 commit comments

Comments
 (0)