diff --git a/src/BasePlatform.ts b/src/BasePlatform.ts index b7f52d38952..ee7ba560ff4 100644 --- a/src/BasePlatform.ts +++ b/src/BasePlatform.ts @@ -222,6 +222,14 @@ export default abstract class BasePlatform { } } + /** + * Returns true if the platform requires URL previews in tooltips, otherwise false. + * @returns {boolean} whether the platform requires URL previews in tooltips + */ + needsUrlTooltips(): boolean { + return false; + } + /** * Returns a promise that resolves to a string representing the current version of the application. */ diff --git a/src/HtmlUtils.tsx b/src/HtmlUtils.tsx index ac26eccc718..7cb22b034c3 100644 --- a/src/HtmlUtils.tsx +++ b/src/HtmlUtils.tsx @@ -18,6 +18,7 @@ limitations under the License. */ import React, { ReactNode } from 'react'; +import ReactDOM from 'react-dom'; import sanitizeHtml from 'sanitize-html'; import cheerio from 'cheerio'; import classNames from 'classnames'; @@ -35,6 +36,8 @@ import { getEmojiFromUnicode } from "./emoji"; import { mediaFromMxc } from "./customisations/Media"; import { ELEMENT_URL_PATTERN, options as linkifyMatrixOptions } from './linkify-matrix'; import { stripHTMLReply, stripPlainReply } from './utils/Reply'; +import TextWithTooltip from './components/views/elements/TextWithTooltip'; +import PlatformPeg from './PlatformPeg'; // Anything outside the basic multilingual plane will be a surrogate pair const SURROGATE_PAIR_PATTERN = /([\ud800-\udbff])([\udc00-\udfff])/; @@ -635,6 +638,57 @@ export function linkifyAndSanitizeHtml(dirtyHtml: string, options = linkifyMatri return sanitizeHtml(linkifyString(dirtyHtml, options), sanitizeHtmlParams); } +const getAbsoluteUrl = (() => { + let a: HTMLAnchorElement; + + return (url: string) => { + if (!a) { + a = document.createElement('a'); + } + a.href = url; + return a.href; + }; +})(); + +/** + * Recurses depth-first through a DOM tree, adding tooltip previews for link elements. + * + * @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. + */ +export function tooltipifyLinks(rootNodes: ArrayLike, ignoredNodes: 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 href = node.getAttribute("href"); + const tooltip = + + ; + ReactDOM.render(tooltip, node); + tooltipified = true; + } + + if (node.childNodes && node.childNodes.length && !tooltipified) { + tooltipifyLinks(node.childNodes as NodeListOf, ignoredNodes); + } + + node = node.nextSibling as Element; + } +} + /** * Returns if a node is a block element or not. * Only takes html nodes into account that are allowed in matrix messages. diff --git a/src/components/views/messages/EditHistoryMessage.tsx b/src/components/views/messages/EditHistoryMessage.tsx index 30922b62e5b..cfb07d2be13 100644 --- a/src/components/views/messages/EditHistoryMessage.tsx +++ b/src/components/views/messages/EditHistoryMessage.tsx @@ -93,8 +93,16 @@ export default class EditHistoryMessage extends React.PureComponent { // we should be pillify them here by doing the linkifying BEFORE the pillifying. pillifyLinks([this.contentRef.current], this.props.mxEvent, this.pills); HtmlUtils.linkifyElement(this.contentRef.current); + HtmlUtils.tooltipifyLinks([this.contentRef.current], this.pills); this.calculateUrlPreview(); if (this.props.mxEvent.getContent().format === "org.matrix.custom.html") {